Minor patch

This commit is contained in:
Miroslav Štampar
2026-08-11 23:13:20 +02:00
parent 90b14df46c
commit e11ea65230
4 changed files with 76 additions and 7 deletions
+4 -3
View File
@@ -70,6 +70,7 @@ from lib.core.exception import SqlmapValueException
from lib.core.settings import ASP_NET_CONTROL_REGEX from lib.core.settings import ASP_NET_CONTROL_REGEX
from lib.core.settings import CSRF_TOKEN_PARAMETER_INFIXES from lib.core.settings import CSRF_TOKEN_PARAMETER_INFIXES
from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import DEFAULT_GET_POST_DELIMITER
from lib.core.settings import NONSQL_TECHNIQUES
from lib.core.settings import EMPTY_FORM_FIELDS_REGEX from lib.core.settings import EMPTY_FORM_FIELDS_REGEX
from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX
from lib.core.settings import HASHDB_STALE_DAYS from lib.core.settings import HASHDB_STALE_DAYS
@@ -532,12 +533,12 @@ def start():
checkJWT() checkJWT()
if conf.mineParams and not any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.xslt, conf.hql, conf.sparql, conf.odata, conf.jwt)): if conf.mineParams and not any(conf.get(_) for _ in NONSQL_TECHNIQUES):
from lib.utils.paraminer import mineParameters from lib.utils.paraminer import mineParameters
mineParameters() mineParameters()
if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.xslt, conf.hql, conf.sparql, conf.odata, conf.jwt)) and (conf.reportJson or conf.resultsFile): if any(conf.get(_) for _ in NONSQL_TECHNIQUES) and (conf.reportJson or conf.resultsFile):
singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xslt/--xxe/--hql/--sparql/--odata/--jwt) findings; these are reported on the console only") singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (%s) findings; these are reported on the console only" % '/'.join("--%s" % _ for _ in NONSQL_TECHNIQUES))
if conf.graphql: if conf.graphql:
from lib.techniques.graphql.inject import graphqlScan from lib.techniques.graphql.inject import graphqlScan
+2 -3
View File
@@ -128,6 +128,7 @@ from lib.core.settings import PARAMETER_SPLITTING_REGEX
from lib.core.settings import PRECONNECT_CANDIDATE_TIMEOUT from lib.core.settings import PRECONNECT_CANDIDATE_TIMEOUT
from lib.core.settings import PROXY_ENVIRONMENT_VARIABLES from lib.core.settings import PROXY_ENVIRONMENT_VARIABLES
from lib.core.settings import SOCKET_PRE_CONNECT_QUEUE_SIZE from lib.core.settings import SOCKET_PRE_CONNECT_QUEUE_SIZE
from lib.core.settings import NONSQL_TECHNIQUES
from lib.core.settings import SQLMAP_ENVIRONMENT_PREFIX from lib.core.settings import SQLMAP_ENVIRONMENT_PREFIX
from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import SUPPORTED_DBMS
from lib.core.settings import SUPPORTED_OS from lib.core.settings import SUPPORTED_OS
@@ -2763,9 +2764,7 @@ def _checkTor():
logger.info(infoMsg) logger.info(infoMsg)
def _basicOptionValidation(): def _basicOptionValidation():
_nonSqlTechniques = [name for name, enabled in ( _nonSqlTechniques = ["--%s" % _ for _ in NONSQL_TECHNIQUES if conf.get(_)]
("--graphql", conf.graphql), ("--nosql", conf.nosql), ("--ldap", conf.ldap),
("--xpath", conf.xpath), ("--ssti", conf.ssti), ("--xxe", conf.xxe), ("--xslt", conf.xslt), ("--hql", conf.hql), ("--sparql", conf.sparql), ("--odata", conf.odata)) if enabled]
if len(_nonSqlTechniques) > 1: if len(_nonSqlTechniques) > 1:
errMsg = "only one non-SQL technique switch may be used at a time (found: %s). " % ", ".join(_nonSqlTechniques) errMsg = "only one non-SQL technique switch may be used at a time (found: %s). " % ", ".join(_nonSqlTechniques)
errMsg += "each is a self-contained scan for a different back-end class - pick one" errMsg += "each is a self-contained scan for a different back-end class - pick one"
+8 -1
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.8.26" VERSION = "1.10.8.27"
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)
@@ -1416,6 +1416,13 @@ HQL_ERROR_SIGNATURES = (
HQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in HQL_ERROR_SIGNATURES) HQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in HQL_ERROR_SIGNATURES)
# The self-contained non-SQL technique switches, by conf option name (each is also its switch spelling,
# '--<name>'). Each is a whole scan for a different back-end class, so at most one may run: the target
# loop branches on them and the option validation rejects a pair. Kept in ONE place because it used to
# be spelled out at every site and had already drifted - '--jwt' was missing from the validation, which
# let '--jwt --nosql' through to run neither (conf.jwt also suppresses the passive JWT heuristic)
NONSQL_TECHNIQUES = ("graphql", "nosql", "ldap", "xpath", "ssti", "xxe", "xslt", "hql", "sparql", "odata", "jwt")
# Small, fast dictionary the always-on JWT heuristic tries against an HS* signature (the full # Small, fast dictionary the always-on JWT heuristic tries against an HS* signature (the full
# '--jwt' audit streams the shipped wordlist instead); these are the secrets seen over and over in # '--jwt' audit streams the shipped wordlist instead); these are the secrets seen over and over in
# tutorials, framework defaults and CTFs # tutorials, framework defaults and CTFs
+62
View File
@@ -19,6 +19,7 @@ shared state stays pristine for the other test files in the suite.
import contextlib import contextlib
import logging import logging
import os import os
import re
import socket import socket
import sys import sys
import tempfile import tempfile
@@ -28,13 +29,17 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap from _testutils import bootstrap
bootstrap() bootstrap()
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
from lib.core.data import conf, kb, logger from lib.core.data import conf, kb, logger
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.enums import AUTH_TYPE from lib.core.enums import AUTH_TYPE
from lib.core.optiondict import optDict
from lib.core.enums import HTTP_HEADER from lib.core.enums import HTTP_HEADER
from lib.core.settings import DEFAULT_USER_AGENT from lib.core.settings import DEFAULT_USER_AGENT
from lib.core.settings import IGNORE_CODE_WILDCARD from lib.core.settings import IGNORE_CODE_WILDCARD
from lib.core.settings import MAX_CONNECT_RETRIES from lib.core.settings import MAX_CONNECT_RETRIES
from lib.core.settings import NONSQL_TECHNIQUES
from lib.core.exception import SqlmapFilePathException from lib.core.exception import SqlmapFilePathException
from lib.core.exception import SqlmapGenericException from lib.core.exception import SqlmapGenericException
from lib.core.exception import SqlmapMissingMandatoryOptionException from lib.core.exception import SqlmapMissingMandatoryOptionException
@@ -713,6 +718,37 @@ class TestBasicOptionValidation(_BackendGuard):
self._base() self._base()
option._basicOptionValidation() # must not raise option._basicOptionValidation() # must not raise
def test_one_non_sql_technique_at_a_time(self):
"""Every pair must be refused, and refused BY NAME - assertRaises alone would be satisfied by
any unrelated validation error, which is how a missing entry hid here in the first place."""
with _preserve(conf, *(self._KEYS + NONSQL_TECHNIQUES)):
for name in NONSQL_TECHNIQUES:
for other in NONSQL_TECHNIQUES:
if other == name:
continue
self._base()
for _ in NONSQL_TECHNIQUES:
conf[_] = False
conf[name] = conf[other] = True
try:
option._basicOptionValidation()
except SqlmapSyntaxException as ex:
message = str(ex)
self.assertIn("--%s" % name, message)
self.assertIn("--%s" % other, message)
else:
self.fail("'--%s --%s' was accepted" % (name, other))
def test_single_non_sql_technique_passes(self):
with _preserve(conf, *(self._KEYS + NONSQL_TECHNIQUES)):
for name in NONSQL_TECHNIQUES:
self._base()
for _ in NONSQL_TECHNIQUES:
conf[_] = False
conf[name] = True
option._basicOptionValidation() # must not raise
def test_bad_level_raises(self): def test_bad_level_raises(self):
with _preserve(conf, *self._KEYS): with _preserve(conf, *self._KEYS):
self._base() self._base()
@@ -1586,5 +1622,31 @@ class TestOptionSetAuthCred(unittest.TestCase):
) )
class TestNonSqlTechniqueRegistry(unittest.TestCase):
"""NONSQL_TECHNIQUES governs three things that used to be spelled out separately: the target loop's
branches, the '--mine-params'/'--report-json' gates, and the one-at-a-time validation. They had
already drifted - '--jwt' was in the branches but not the validation, so '--jwt --nosql' was
accepted and then ran NEITHER (the nosql branch wins, and conf.jwt suppresses the passive JWT
heuristic). Anchoring the list to the branches that consume it is what makes the next engine safe."""
def _read(self, *parts):
with open(os.path.join(_ROOT, *parts)) as f:
return f.read()
def test_every_branch_is_registered(self):
branches = set(re.findall(r"from lib\.techniques\.(\w+)\.inject import", self._read("lib", "controller", "controller.py")))
self.assertTrue(branches)
self.assertEqual(sorted(branches - set(NONSQL_TECHNIQUES)), [])
def test_every_registered_technique_has_a_branch_and_a_switch(self):
controller = self._read("lib", "controller", "controller.py")
cmdline = self._read("lib", "parse", "cmdline.py")
for name in NONSQL_TECHNIQUES:
self.assertIn("from lib.techniques.%s.inject import" % name, controller, name)
self.assertIn('dest="%s"' % name, cmdline, name)
self.assertEqual(optDict["Techniques"].get(name), "boolean", name)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main(verbosity=2) unittest.main(verbosity=2)