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] 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 e1c202926..b5e04442b 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 86505b7b1..0f81eefb9 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.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 5876a7f06..f6d7432cb 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))