From aa8d9b98416e7c0fb3a329b8f31ea108d106d0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0tampar?= Date: Sat, 8 Aug 2026 00:55:37 +0200 Subject: [PATCH] Patching non-SQLi techniques regarding reflective values --- extra/vulnserver/vulnserver.py | 12 ++++++++++++ lib/core/settings.py | 2 +- lib/core/testing.py | 4 ++++ lib/techniques/ldap/inject.py | 8 +++++++- lib/techniques/nosql/inject.py | 8 +++++++- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py index e154363db..3bb4b54ea 100644 --- a/extra/vulnserver/vulnserver.py +++ b/extra/vulnserver/vulnserver.py @@ -1194,6 +1194,18 @@ class ReqHandler(BaseHTTPRequestHandler): self.wfile.write(output.encode(UNICODE_ENCODING)) return + if self.url == "/echo": + # A pure reflector: no engine of any kind behind it, it only shows the parameter back. Every + # non-SQL switch must stay silent here. A differential built on "the page changed" is + # satisfied by reflection alone, which is how several engines reported this shape as + # injectable - so this endpoint is the regression gate for that whole class. + 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(("you searched for: %s" % self.params.get("q", "")).encode(UNICODE_ENCODING)) + return + if self.url in ("/xslt/element", "/xslt/value"): # VULNERABLE: user input is concatenated into a stylesheet which is then compiled and applied element = self.url.endswith("element") diff --git a/lib/core/settings.py b/lib/core/settings.py index 76949364e..a4920899f 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.8.14" +VERSION = "1.10.8.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/core/testing.py b/lib/core/testing.py index ad44a8ee2..35e599138 100644 --- a/lib/core/testing.py +++ b/lib/core/testing.py @@ -96,6 +96,10 @@ def vulnTest(tests=None, label="vuln"): ("-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", "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 \"echo?q=x\" --xslt --flush-session --disable-hashing", ("no parameter appears to be injectable", "~is vulnerable to")), # reflection is not evaluation: a pure echo endpoint must not satisfy any non-SQL engine's oracle + ("-u \"echo?q=x\" --xpath --flush-session --disable-hashing", ("no parameter appears to be injectable", "~is vulnerable to")), # same gate for --xpath (it used to confirm XPath injection from reflected text alone) + ("-u \"echo?q=x\" --ldap --flush-session --disable-hashing", ("no parameter appears to be injectable", "~is vulnerable to", "~Traceback")), # same gate for --ldap (it used to CRASH here with an unhandled InconclusiveError) + ("-u \"echo?q=x\" --nosql --flush-session --disable-hashing", ("no parameter appears to be injectable", "~is vulnerable to")), # same gate for --nosql (it used to report a "Lucene query_string-compatible back-end" from q=*) ("-u \"xslt/element?tpl=x\" --xslt --flush-session --disable-hashing", ("is vulnerable to XSLT injection", "element context", "Engine: libxslt", "Type: XSLT injection", "XSLT scan complete")), # XSLT: the engine names itself through system-property('xsl:vendor') in the element slot - a value the application cannot produce on its own ("-u \"xslt/value?sort=name\" --xslt --flush-session --disable-hashing", ("is vulnerable to XSLT injection", "value context", "XSLT compile-differential", "XSLT scan complete")), # XSLT: the value slot reflects nothing, so detection rests on the compile differential plus positive XPath-syntax evidence ("-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 diff --git a/lib/techniques/ldap/inject.py b/lib/techniques/ldap/inject.py index 126b0c5f4..106e72fb3 100644 --- a/lib/techniques/ldap/inject.py +++ b/lib/techniques/ldap/inject.py @@ -12,6 +12,7 @@ from collections import namedtuple from lib.core.common import beep from lib.core.common import randomStr +from lib.core.common import removeReflectiveValues from lib.core.convert import getUnicode from lib.core.data import conf from lib.core.data import logger @@ -173,7 +174,12 @@ def _send(place, parameter, value): # oracle sample - signal None so `_boolean`/`extract` (which reject None) can't decide on it if blockedStatus(code): return None - return page or "" + # Strip the payload back out before anything compares pages. An endpoint that merely ECHOES the + # parameter differs between any two probes because the payloads differ, which satisfies a + # true/false differential without a single expression being evaluated - that reported plain + # reflective search pages as injectable. On a blind target the payload is not in the page, so + # this is a no-op. + return removeReflectiveValues(page, value, suppressWarning=True) or "" except Exception as ex: logger.debug("LDAP probe request failed: %s" % getUnicode(ex)) return None diff --git a/lib/techniques/nosql/inject.py b/lib/techniques/nosql/inject.py index 8b98c4485..573203397 100644 --- a/lib/techniques/nosql/inject.py +++ b/lib/techniques/nosql/inject.py @@ -14,6 +14,7 @@ from collections import OrderedDict from lib.core.common import beep from lib.core.common import randomStr +from lib.core.common import removeReflectiveValues from lib.core.convert import getUnicode from lib.utils.nonsql import userDecision from lib.utils.nonsql import sqlErrorPresent @@ -366,7 +367,12 @@ def _send(place, parameter, segment=None, jsonValue=_UNSET): finally: conf.skipUrlEncode = skipUrlEncode - return page or "" + # Strip the payload back out before anything compares pages. An endpoint that merely ECHOES the + # parameter differs between any two probes because the payloads differ, which satisfies a true/false + # differential - and the wildcard always-true check - without a single operator being interpreted. + # That reported a plain reflective search page as a "Lucene query_string-compatible back-end". On a + # blind target the payload is not in the page, so this is a no-op. + return removeReflectiveValues(page, payload, suppressWarning=True) or "" def _isError(page): # a server-error status, a recognizable NoSQL error body, OR a recognized SQL/DBMS error marks a