Minor patch

This commit is contained in:
Miroslav Štampar 2026-07-10 12:51:25 +02:00
parent 986933e709
commit f4c4025bdb
4 changed files with 64 additions and 15 deletions

View file

@ -11,11 +11,13 @@ from __future__ import print_function
import base64 import base64
import json import json
import os
import random import random
import re import re
import sqlite3 import sqlite3
import string import string
import sys import sys
import tempfile
import threading import threading
import time import time
import traceback import traceback
@ -24,6 +26,18 @@ PY3 = sys.version_info >= (3, 0)
UNICODE_ENCODING = "utf-8" UNICODE_ENCODING = "utf-8"
DEBUG = False 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: if PY3:
from http.client import FORBIDDEN from http.client import FORBIDDEN
from http.client import INTERNAL_SERVER_ERROR from http.client import INTERNAL_SERVER_ERROR
@ -945,6 +959,26 @@ class ReqHandler(BaseHTTPRequestHandler):
self.wfile.write(b"<html><body>Request blocked: security policy violation (WAF)</body></html>") self.wfile.write(b"<html><body>Request blocked: security policy violation (WAF)</body></html>")
return 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 = "<result>%s</result>" % "".join(root.itertext()) # reflects expanded entities
except Exception as ex:
output = "<error>%s: %s</error>" % (type(ex).__name__, ex) # parser diagnostic (error-based tier)
self.wfile.write(output.encode(UNICODE_ENCODING, "ignore"))
return
if self.url == "/csrf": if self.url == "/csrf":
if self.params.get("csrf_token") == _csrf_token: if self.params.get("csrf_token") == _csrf_token:
self.url = "/" self.url = "/"

View file

@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.7.68" VERSION = "1.10.7.69"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)

View file

@ -97,6 +97,7 @@ def vulnTest(tests=None, label="vuln"):
("-u \"<base>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 \"<base>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 \"<base>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 \"<base>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 \"<base>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 \"<base>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 \"<base>xxe\" --data=\"<root><q>x</q></root>\" --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 \"<url>&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")), ("-u \"<url>&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),
("-d \"<direct>\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")), ("-d \"<direct>\" --flush-session --dump -T creds --dump-format=SQLITE --binary-fields=password_hash --where \"user_id=5\"", ("3137396164343563366365326362393763663130323965323132303436653831", "dumped to SQLITE database")),
("-d \"<direct>\" --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'",)), ("-d \"<direct>\" --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")), ("--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 # The vulnserver's XPath and XXE endpoints render with lxml and its SSTI endpoint with jinja2; where
# optional third-party engines are not importable (e.g. PyPy 2.7, which has no lxml wheel), skip # 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. # just those entries instead of failing the whole run - the rest of the suite is unaffected.
try: try:
__import__("lxml") __import__("lxml")
except ImportError: except ImportError:
TESTS = tuple(_ for _ in TESTS if "--xpath" not in _[0]) TESTS = tuple(_ for _ in TESTS if "--xpath" not in _[0] and "--xxe" not in _[0])
logger.warning("skipping the XPath vuln-test entry ('lxml' not available)") logger.warning("skipping the XPath and XXE vuln-test entries ('lxml' not available)")
try: try:
__import__("jinja2") __import__("jinja2")
except ImportError: except ImportError:

View file

@ -709,12 +709,18 @@ def _detectBoolean(slot, endpoint):
return None, None return None, None
truePage, _ = _gqlSend(endpoint, trueQuery) truePage, _ = _gqlSend(endpoint, trueQuery)
truePage2, _ = _gqlSend(endpoint, trueQuery)
falsePage, _ = _gqlSend(endpoint, falseQuery) falsePage, _ = _gqlSend(endpoint, falseQuery)
trueVal = _slotValue(truePage) trueVal = _slotValue(truePage)
trueVal2 = _slotValue(truePage2)
falseVal = _slotValue(falsePage) 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 "boolean-based blind (string)", truePage
return None, None return None, None
@ -731,21 +737,29 @@ def _detectTime(slot, endpoint):
if not baseQuery: if not baseQuery:
return None, None, None return None, None, None
start = time.time() def elapsed(query):
_gqlSend(endpoint, baseQuery) start = time.time()
baseline = time.time() - start _gqlSend(endpoint, query)
return time.time() - start
baseline = elapsed(baseQuery)
delay = conf.timeSec delay = conf.timeSec
cutoff = baseline + delay * 0.5
for dbms, dialect in DIALECTS.items(): for dbms, dialect in DIALECTS.items():
if not dialect.delay: if not dialect.delay:
continue continue
query = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=1", delay))) sleepQuery = _buildQuery(slot, "%s' OR %s-- " % (SENTINEL, dialect.delay("1=1", delay)))
if not query: if not sleepQuery or elapsed(sleepQuery) <= cutoff:
continue continue
start = time.time()
_gqlSend(endpoint, query) # Confirm before attributing: the delay must REPRODUCE and a false-condition
if (time.time() - start) > baseline + delay * 0.5: # control must stay fast. A single sample turns jitter/a uniformly-slow endpoint
return "time-based blind", baseline + delay * 0.5, dbms # 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 return None, None, None