|
19 | 19 | import contextlib |
20 | 20 | import logging |
21 | 21 | import os |
| 22 | +import re |
22 | 23 | import socket |
23 | 24 | import sys |
24 | 25 | import tempfile |
|
28 | 29 | from _testutils import bootstrap |
29 | 30 | bootstrap() |
30 | 31 |
|
| 32 | +_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 33 | + |
31 | 34 | from lib.core.data import conf, kb, logger |
32 | 35 | from lib.core.common import Backend |
33 | 36 | from lib.core.enums import AUTH_TYPE |
| 37 | +from lib.core.optiondict import optDict |
34 | 38 | from lib.core.enums import HTTP_HEADER |
35 | 39 | from lib.core.settings import DEFAULT_USER_AGENT |
36 | 40 | from lib.core.settings import IGNORE_CODE_WILDCARD |
37 | 41 | from lib.core.settings import MAX_CONNECT_RETRIES |
| 42 | +from lib.core.settings import NONSQL_TECHNIQUES |
38 | 43 | from lib.core.exception import SqlmapFilePathException |
39 | 44 | from lib.core.exception import SqlmapGenericException |
40 | 45 | from lib.core.exception import SqlmapMissingMandatoryOptionException |
@@ -713,6 +718,37 @@ def test_clean_baseline_passes(self): |
713 | 718 | self._base() |
714 | 719 | option._basicOptionValidation() # must not raise |
715 | 720 |
|
| 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 | + |
716 | 752 | def test_bad_level_raises(self): |
717 | 753 | with _preserve(conf, *self._KEYS): |
718 | 754 | self._base() |
@@ -1586,5 +1622,31 @@ def test_adds_credentials_to_manager(self): |
1586 | 1622 | ) |
1587 | 1623 |
|
1588 | 1624 |
|
| 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 | + |
1589 | 1651 | if __name__ == "__main__": |
1590 | 1652 | unittest.main(verbosity=2) |
0 commit comments