Skip to content

Commit e11ea65

Browse files
committed
Minor patch
1 parent 90b14df commit e11ea65

4 files changed

Lines changed: 76 additions & 7 deletions

File tree

lib/controller/controller.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from lib.core.settings import ASP_NET_CONTROL_REGEX
7171
from lib.core.settings import CSRF_TOKEN_PARAMETER_INFIXES
7272
from lib.core.settings import DEFAULT_GET_POST_DELIMITER
73+
from lib.core.settings import NONSQL_TECHNIQUES
7374
from lib.core.settings import EMPTY_FORM_FIELDS_REGEX
7475
from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_REGEX
7576
from lib.core.settings import HASHDB_STALE_DAYS
@@ -532,12 +533,12 @@ def start():
532533

533534
checkJWT()
534535

535-
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)):
536+
if conf.mineParams and not any(conf.get(_) for _ in NONSQL_TECHNIQUES):
536537
from lib.utils.paraminer import mineParameters
537538
mineParameters()
538539

539-
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):
540-
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")
540+
if any(conf.get(_) for _ in NONSQL_TECHNIQUES) and (conf.reportJson or conf.resultsFile):
541+
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))
541542

542543
if conf.graphql:
543544
from lib.techniques.graphql.inject import graphqlScan

lib/core/option.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
from lib.core.settings import PRECONNECT_CANDIDATE_TIMEOUT
129129
from lib.core.settings import PROXY_ENVIRONMENT_VARIABLES
130130
from lib.core.settings import SOCKET_PRE_CONNECT_QUEUE_SIZE
131+
from lib.core.settings import NONSQL_TECHNIQUES
131132
from lib.core.settings import SQLMAP_ENVIRONMENT_PREFIX
132133
from lib.core.settings import SUPPORTED_DBMS
133134
from lib.core.settings import SUPPORTED_OS
@@ -2763,9 +2764,7 @@ def _checkTor():
27632764
logger.info(infoMsg)
27642765

27652766
def _basicOptionValidation():
2766-
_nonSqlTechniques = [name for name, enabled in (
2767-
("--graphql", conf.graphql), ("--nosql", conf.nosql), ("--ldap", conf.ldap),
2768-
("--xpath", conf.xpath), ("--ssti", conf.ssti), ("--xxe", conf.xxe), ("--xslt", conf.xslt), ("--hql", conf.hql), ("--sparql", conf.sparql), ("--odata", conf.odata)) if enabled]
2767+
_nonSqlTechniques = ["--%s" % _ for _ in NONSQL_TECHNIQUES if conf.get(_)]
27692768
if len(_nonSqlTechniques) > 1:
27702769
errMsg = "only one non-SQL technique switch may be used at a time (found: %s). " % ", ".join(_nonSqlTechniques)
27712770
errMsg += "each is a self-contained scan for a different back-end class - pick one"

lib/core/settings.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from thirdparty import six
2121

2222
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
23-
VERSION = "1.10.8.26"
23+
VERSION = "1.10.8.27"
2424
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
2525
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
2626
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 @@
14161416

14171417
HQL_ERROR_REGEX = r"(?i)(?:%s)" % '|'.join(regex for _, regex in HQL_ERROR_SIGNATURES)
14181418

1419+
# The self-contained non-SQL technique switches, by conf option name (each is also its switch spelling,
1420+
# '--<name>'). Each is a whole scan for a different back-end class, so at most one may run: the target
1421+
# loop branches on them and the option validation rejects a pair. Kept in ONE place because it used to
1422+
# be spelled out at every site and had already drifted - '--jwt' was missing from the validation, which
1423+
# let '--jwt --nosql' through to run neither (conf.jwt also suppresses the passive JWT heuristic)
1424+
NONSQL_TECHNIQUES = ("graphql", "nosql", "ldap", "xpath", "ssti", "xxe", "xslt", "hql", "sparql", "odata", "jwt")
1425+
14191426
# Small, fast dictionary the always-on JWT heuristic tries against an HS* signature (the full
14201427
# '--jwt' audit streams the shipped wordlist instead); these are the secrets seen over and over in
14211428
# tutorials, framework defaults and CTFs

tests/test_option.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import contextlib
2020
import logging
2121
import os
22+
import re
2223
import socket
2324
import sys
2425
import tempfile
@@ -28,13 +29,17 @@
2829
from _testutils import bootstrap
2930
bootstrap()
3031

32+
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
33+
3134
from lib.core.data import conf, kb, logger
3235
from lib.core.common import Backend
3336
from lib.core.enums import AUTH_TYPE
37+
from lib.core.optiondict import optDict
3438
from lib.core.enums import HTTP_HEADER
3539
from lib.core.settings import DEFAULT_USER_AGENT
3640
from lib.core.settings import IGNORE_CODE_WILDCARD
3741
from lib.core.settings import MAX_CONNECT_RETRIES
42+
from lib.core.settings import NONSQL_TECHNIQUES
3843
from lib.core.exception import SqlmapFilePathException
3944
from lib.core.exception import SqlmapGenericException
4045
from lib.core.exception import SqlmapMissingMandatoryOptionException
@@ -713,6 +718,37 @@ def test_clean_baseline_passes(self):
713718
self._base()
714719
option._basicOptionValidation() # must not raise
715720

721+
def test_one_non_sql_technique_at_a_time(self):
722+
"""Every pair must be refused, and refused BY NAME - assertRaises alone would be satisfied by
723+
any unrelated validation error, which is how a missing entry hid here in the first place."""
724+
725+
with _preserve(conf, *(self._KEYS + NONSQL_TECHNIQUES)):
726+
for name in NONSQL_TECHNIQUES:
727+
for other in NONSQL_TECHNIQUES:
728+
if other == name:
729+
continue
730+
self._base()
731+
for _ in NONSQL_TECHNIQUES:
732+
conf[_] = False
733+
conf[name] = conf[other] = True
734+
try:
735+
option._basicOptionValidation()
736+
except SqlmapSyntaxException as ex:
737+
message = str(ex)
738+
self.assertIn("--%s" % name, message)
739+
self.assertIn("--%s" % other, message)
740+
else:
741+
self.fail("'--%s --%s' was accepted" % (name, other))
742+
743+
def test_single_non_sql_technique_passes(self):
744+
with _preserve(conf, *(self._KEYS + NONSQL_TECHNIQUES)):
745+
for name in NONSQL_TECHNIQUES:
746+
self._base()
747+
for _ in NONSQL_TECHNIQUES:
748+
conf[_] = False
749+
conf[name] = True
750+
option._basicOptionValidation() # must not raise
751+
716752
def test_bad_level_raises(self):
717753
with _preserve(conf, *self._KEYS):
718754
self._base()
@@ -1586,5 +1622,31 @@ def test_adds_credentials_to_manager(self):
15861622
)
15871623

15881624

1625+
class TestNonSqlTechniqueRegistry(unittest.TestCase):
1626+
"""NONSQL_TECHNIQUES governs three things that used to be spelled out separately: the target loop's
1627+
branches, the '--mine-params'/'--report-json' gates, and the one-at-a-time validation. They had
1628+
already drifted - '--jwt' was in the branches but not the validation, so '--jwt --nosql' was
1629+
accepted and then ran NEITHER (the nosql branch wins, and conf.jwt suppresses the passive JWT
1630+
heuristic). Anchoring the list to the branches that consume it is what makes the next engine safe."""
1631+
1632+
def _read(self, *parts):
1633+
with open(os.path.join(_ROOT, *parts)) as f:
1634+
return f.read()
1635+
1636+
def test_every_branch_is_registered(self):
1637+
branches = set(re.findall(r"from lib\.techniques\.(\w+)\.inject import", self._read("lib", "controller", "controller.py")))
1638+
self.assertTrue(branches)
1639+
self.assertEqual(sorted(branches - set(NONSQL_TECHNIQUES)), [])
1640+
1641+
def test_every_registered_technique_has_a_branch_and_a_switch(self):
1642+
controller = self._read("lib", "controller", "controller.py")
1643+
cmdline = self._read("lib", "parse", "cmdline.py")
1644+
1645+
for name in NONSQL_TECHNIQUES:
1646+
self.assertIn("from lib.techniques.%s.inject import" % name, controller, name)
1647+
self.assertIn('dest="%s"' % name, cmdline, name)
1648+
self.assertEqual(optDict["Techniques"].get(name), "boolean", name)
1649+
1650+
15891651
if __name__ == "__main__":
15901652
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)