#!/usr/bin/env python """ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission Additional unit tests for the generic plugin mixins, driving branches NOT already covered by tests/test_search_enum.py / tests/test_databases_enum.py: * plugins/generic/entries.py - dumpTable column/table --exclude filtering, the --where (conf.dumpWhere) query rewrite, disableHashing toggle, METADB suffix db handling, the "no usable columns" / "missing columns" skip branches, and dumpAll over multiple dbs/tables (dict and list shapes) plus dumpFoundTables / dumpFoundColumn interactive flows. * plugins/generic/custom.py - sqlQuery SELECT/non-query/stacked branches, the MSSQL FROM rewrite, METADB suffix stripping, SqlmapNoneDataException handling, and sqlFile. * plugins/generic/misc.py - getRemoteTempPath (posix / windows-direct / MSSQL ErrorLog), getVersionFromBanner, delRemoteFile, createSupportTbl, likeOrExact. * plugins/generic/takeover.py - the PURE helpers only: Takeover.__init__ table naming and the regRead/regAdd/regDel/osBof/osSmb control flow with the process/ network collaborators stubbed out (no metasploit/icmpsh/UDF spawning). The injection layer (lib.request.inject.{getValue,goStacked}) is patched per module, conf.direct=True selects the simple inband branches, conf.batch=True keeps prompts non-interactive, and conf.dumper is a recording stub. Every test restores all touched conf.* / kb.* / patched module attributes in tearDown so nothing leaks. """ import os import sys import unittest sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from _testutils import bootstrap, set_dbms bootstrap() from lib.core.common import Backend from lib.core.data import conf, kb from lib.core.enums import DBMS, OS from lib.core.settings import NULL import plugins.generic.entries as emod import plugins.generic.custom as cmod import plugins.generic.misc as mmod import plugins.generic.takeover as tmod from plugins.generic.entries import Entries from plugins.generic.custom import Custom from plugins.generic.misc import Miscellaneous class _RecordingDumper(object): """Recording stand-in for conf.dumper (no printing / file writing).""" def __init__(self): self.tableValues = [] self.sqlQueries = [] def dbTableValues(self, tableValues): self.tableValues.append(tableValues) def sqlQuery(self, query, queryRes): self.sqlQueries.append((query, queryRes)) # --------------------------------------------------------------------------- # # entries.py # --------------------------------------------------------------------------- # class _TestEntries(Entries): """Entries with cross-mixin collaborators stubbed. forceDbmsEnum / getCurrentDb / getColumns / getTables are normally supplied by sibling mixins; we emulate column/table discovery by populating kb.data.cached* from canned attributes, exactly as the production plugins do. """ def __init__(self): Entries.__init__(self) self.getColumnsResult = {} # assigned to kb.data.cachedColumns self.getTablesResult = {} # assigned to kb.data.cachedTables self.getColumnsCalls = [] self.getTablesCalls = 0 def forceDbmsEnum(self): pass def getCurrentDb(self): return "testdb" def getColumns(self, onlyColNames=False, colTuple=None, bruteForce=None, dumpMode=False): self.getColumnsCalls.append((conf.db, conf.tbl)) kb.data.cachedColumns = dict(self.getColumnsResult) def getTables(self, bruteForce=None): self.getTablesCalls += 1 kb.data.cachedTables = dict(self.getTablesResult) class _GenericBase(unittest.TestCase): """Snapshot/restore for everything the generic mixins touch.""" _CONF_KEYS = ( "db", "tbl", "col", "direct", "batch", "exclude", "search", "disableHashing", "noKeyset", "keyset", "forcePivoting", "dumpWhere", "tmpPath", "sqlQuery", "sqlFile", "regKey", "regVal", "regData", "regType", "osPwn", "osShell", "cleanup", "privEsc", ) def setUp(self): self._saved_conf = {k: conf.get(k) for k in self._CONF_KEYS} self._saved_dumper = conf.get("dumper") self._saved_getValue = { emod: emod.inject.getValue, cmod: cmod.inject.getValue, mmod: mmod.inject.getValue, } self._saved_goStacked = { cmod: cmod.inject.goStacked, mmod: mmod.inject.goStacked, } self._saved_emod_readInput = emod.readInput self._saved_mmod_readInput = mmod.readInput self._saved_kb = { "cachedColumns": kb.data.get("cachedColumns"), "cachedTables": kb.data.get("cachedTables"), "dumpedTable": kb.data.get("dumpedTable"), "has_information_schema": kb.data.get("has_information_schema"), "dumpKeyboardInterrupt": kb.get("dumpKeyboardInterrupt"), "permissionFlag": kb.get("permissionFlag"), "hintValue": kb.get("hintValue"), "injection_data": kb.injection.data, "bannerFp": kb.get("bannerFp"), "os": kb.get("os"), } self._saved_forceDbms = kb.get("forcedDbms") conf.direct = True conf.batch = True conf.exclude = None conf.search = False conf.disableHashing = True conf.noKeyset = True conf.keyset = False conf.forcePivoting = False conf.dumpWhere = None conf.dumper = _RecordingDumper() kb.data.cachedColumns = {} kb.data.cachedTables = {} kb.data.dumpedTable = {} kb.data.has_information_schema = True kb.dumpKeyboardInterrupt = False kb.permissionFlag = False def _readInput(message, default=None, checkBatch=True, boolean=False): if boolean: return default in (None, 'Y', 'y', True) return default emod.readInput = _readInput mmod.readInput = _readInput def tearDown(self): for k, v in self._saved_conf.items(): conf[k] = v conf.dumper = self._saved_dumper for mod, fn in self._saved_getValue.items(): mod.inject.getValue = fn for mod, fn in self._saved_goStacked.items(): mod.inject.goStacked = fn emod.readInput = self._saved_emod_readInput mmod.readInput = self._saved_mmod_readInput kb.data.cachedColumns = self._saved_kb["cachedColumns"] kb.data.cachedTables = self._saved_kb["cachedTables"] kb.data.dumpedTable = self._saved_kb["dumpedTable"] kb.data.has_information_schema = self._saved_kb["has_information_schema"] kb.dumpKeyboardInterrupt = self._saved_kb["dumpKeyboardInterrupt"] kb.permissionFlag = self._saved_kb["permissionFlag"] kb.hintValue = self._saved_kb["hintValue"] kb.injection.data = self._saved_kb["injection_data"] kb.bannerFp = self._saved_kb["bannerFp"] kb.os = self._saved_kb["os"] kb.forcedDbms = self._saved_forceDbms @staticmethod def _force_os(os_name): # Backend.setOs only assigns when kb.os is currently None; reset first so # tests can deterministically pin the back-end OS. kb.os = None Backend.setOs(os_name) class TestEntriesDumpTable(_GenericBase): def _entries(self, db="testdb", tbl="users", cols=("id", "name")): e = _TestEntries() e.getColumnsResult = {db: {tbl: {c: "varchar" for c in cols}}} return e def test_exclude_filters_columns(self): set_dbms("MySQL") e = self._entries(cols=("id", "secret")) conf.db = "testdb" conf.tbl = "users" conf.col = None conf.exclude = "secret" emod.inject.getValue = lambda *a, **k: [["1"]] e.dumpTable() dumped = conf.dumper.tableValues[-1] self.assertIn("id", dumped) self.assertNotIn("secret", dumped) def test_exclude_all_columns_skips(self): set_dbms("MySQL") e = self._entries(cols=("secret",)) conf.db = "testdb" conf.tbl = "users" conf.col = None conf.exclude = "secret" emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") e.dumpTable() # all columns excluded => "no usable column names" => nothing dumped self.assertEqual(conf.dumper.tableValues, []) def test_dumpwhere_rewrites_query(self): set_dbms("MySQL") e = self._entries(cols=("id",)) conf.db = "testdb" conf.tbl = "users" conf.col = None conf.dumpWhere = "id>5" captured = {} def gv(query, *a, **k): captured["query"] = query return [["9"]] emod.inject.getValue = gv e.dumpTable() # agent.whereQuery folds conf.dumpWhere into the dump query self.assertIn("id>5", captured["query"]) self.assertEqual(list(conf.dumper.tableValues[-1]["id"]["values"]), ["9"]) def test_disablehashing_false_path(self): # conf.disableHashing False => attackDumpedTable() is invoked; with no # hashes present it must complete without raising and still emit values. set_dbms("MySQL") e = self._entries(cols=("id", "name")) conf.db = "testdb" conf.tbl = "users" conf.col = None conf.disableHashing = False emod.inject.getValue = lambda *a, **k: [["1", "alice"]] # Spy on attackDumpedTable: with disableHashing False it MUST be invoked # after the values are dumped. A recorder replaces it so we can assert the # call happened (and no real dictionary attack runs). saved_attack = emod.attackDumpedTable calls = {"n": 0} emod.attackDumpedTable = lambda *a, **k: calls.__setitem__("n", calls["n"] + 1) try: e.dumpTable() finally: emod.attackDumpedTable = saved_attack self.assertEqual(calls["n"], 1) self.assertEqual(conf.dumper.tableValues[-1]["__infos__"]["count"], 1) def test_missing_columns_skips_table(self): # getColumns yields nothing for the targeted table => skip without fetching. set_dbms("MySQL") e = _TestEntries() e.getColumnsResult = {"testdb": {"other": {"id": "int"}}} conf.db = "testdb" conf.tbl = "users" conf.col = None emod.inject.getValue = lambda *a, **k: self.fail("should not fetch entries") e.dumpTable() self.assertEqual(conf.dumper.tableValues, []) def test_multiple_tables_one_dumped(self): set_dbms("MySQL") e = _TestEntries() e.getColumnsResult = {"testdb": {"users": {"id": "int"}, "posts": {"pid": "int"}}} conf.db = "testdb" conf.tbl = "users,posts" conf.col = None emod.inject.getValue = lambda *a, **k: [["1"]] e.dumpTable() # both tables share the same cachedColumns dict => both dumped tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] self.assertIn("users", tables) self.assertIn("posts", tables) def test_metadb_suffix_db(self): # A db whose name carries the METADB_SUFFIX must not get a "db" prefix in # kb.dumpTable, and dumping still succeeds. from lib.core.settings import METADB_SUFFIX set_dbms("MySQL") metadb = "x%s" % METADB_SUFFIX e = self._entries(db=metadb, tbl="t", cols=("c",)) conf.db = metadb conf.tbl = "t" conf.col = None emod.inject.getValue = lambda *a, **k: [["v"]] e.dumpTable() self.assertEqual(list(conf.dumper.tableValues[-1]["c"]["values"]), ["v"]) class TestEntriesDumpAll(_GenericBase): def test_dumpall_multiple_dbs_tables(self): set_dbms("MySQL") e = _TestEntries() conf.db = None conf.tbl = None conf.col = None e.getTablesResult = {"db1": ["t1"], "db2": ["t2"]} # dumpTable re-discovers columns per (db, tbl); supply both. e.getColumnsResult = { "db1": {"t1": {"a": "int"}}, "db2": {"t2": {"b": "int"}}, } emod.inject.getValue = lambda *a, **k: [["x"]] e.dumpAll() # Every table contributed a values batch. self.assertEqual(len(conf.dumper.tableValues), 2) def test_dumpall_list_cached_tables(self): # cachedTables as a bare list => wrapped under {None: [...]}. set_dbms("MySQL") e = _TestEntries() conf.db = None conf.tbl = None conf.col = None # getTables sets cachedTables; emulate the list shape directly. class _ListTables(_TestEntries): def getTables(self_inner, bruteForce=None): kb.data.cachedTables = ["users"] e = _ListTables() # dumpAll wraps a bare list as {None: [...]}; dumpTable then resolves the # None db via getCurrentDb() -> "testdb", so columns live under "testdb". e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} emod.inject.getValue = lambda *a, **k: [["1"]] e.dumpAll() self.assertTrue(conf.dumper.tableValues) # The bare-list None db must be resolved via getCurrentDb() -> "testdb" # before the dump; assert the dumped __infos__ carries the real db (not # None) for the requested "users" table. infos = conf.dumper.tableValues[-1]["__infos__"] self.assertEqual(infos["db"], "testdb") self.assertEqual(infos["table"], "users") def test_dumpall_exclude_skips_table(self): set_dbms("MySQL") e = _TestEntries() conf.db = None conf.tbl = None conf.col = None conf.exclude = "secret" e.getTablesResult = {"db1": ["secret", "users"]} e.getColumnsResult = {"db1": {"users": {"id": "int"}, "secret": {"id": "int"}}} emod.inject.getValue = lambda *a, **k: [["1"]] e.dumpAll() tables = [tv["__infos__"]["table"] for tv in conf.dumper.tableValues] self.assertIn("users", tables) self.assertNotIn("secret", tables) class TestEntriesDumpFound(_GenericBase): def _entries(self): e = _TestEntries() e.getColumnsResult = {"testdb": {"users": {"id": "int"}}} return e def test_dump_found_tables_yes_all(self): set_dbms("MySQL") e = self._entries() emod.inject.getValue = lambda *a, **k: [["1"]] # batch readInput -> 'Y' (boolean True) and 'a'/'a' for db/table choices. e.dumpFoundTables({"testdb": ["users"]}) self.assertTrue(conf.dumper.tableValues) # The interactive selection must dump the REQUESTED db/table, not just # "something": assert the dumped __infos__ maps to testdb.users. infos = conf.dumper.tableValues[-1]["__infos__"] self.assertEqual(infos["db"], "testdb") self.assertEqual(infos["table"], "users") def test_dump_found_tables_declined(self): set_dbms("MySQL") e = self._entries() def _no(message, default=None, checkBatch=True, boolean=False): if boolean: return False return default emod.readInput = _no emod.inject.getValue = lambda *a, **k: self.fail("must not dump when declined") e.dumpFoundTables({"testdb": ["users"]}) self.assertEqual(conf.dumper.tableValues, []) def test_dump_found_column_yes_all(self): set_dbms("MySQL") e = self._entries() emod.inject.getValue = lambda *a, **k: [["1"]] dbs = {"testdb": {"users": {"id": "int"}}} e.dumpFoundColumn(dbs, foundCols=None, colConsider='1') self.assertTrue(conf.dumper.tableValues) # The selection must dump the REQUESTED db/table mapping, not just # "something": assert the dumped __infos__ maps to testdb.users. infos = conf.dumper.tableValues[-1]["__infos__"] self.assertEqual(infos["db"], "testdb") self.assertEqual(infos["table"], "users") # --------------------------------------------------------------------------- # # custom.py # --------------------------------------------------------------------------- # class TestCustomSqlQuery(_GenericBase): def test_select_joins_listlike_rows(self): set_dbms("MySQL") c = Custom() cmod.inject.getValue = lambda query, **k: [["1", "alice"], ["2", "bob"]] out = c.sqlQuery("SELECT id, name FROM users;") # SELECT + list-like rows => each row joined into a single scalar string. self.assertEqual(len(out), 2) self.assertTrue(all(isinstance(_, str) for _ in out)) def test_select_scalar_passthrough(self): set_dbms("MySQL") c = Custom() captured = {} def gv(query, **k): captured["query"] = query captured["fromUser"] = k.get("fromUser") return "42" cmod.inject.getValue = gv out = c.sqlQuery("SELECT COUNT(*) FROM users") self.assertEqual(out, "42") self.assertTrue(captured["fromUser"]) def test_metadb_suffix_stripped(self): from lib.core.settings import METADB_SUFFIX set_dbms("MySQL") c = Custom() captured = {} def gv(query, **k): captured["query"] = query return "x" cmod.inject.getValue = gv c.sqlQuery("SELECT * FROM foo%s.bar" % METADB_SUFFIX) # the METADB-suffixed schema qualifier is stripped before injection self.assertNotIn(METADB_SUFFIX, captured["query"]) def test_mssql_from_dbo_rewrite(self): set_dbms("Microsoft SQL Server") c = Custom() captured = {} def gv(query, **k): captured["query"] = query return "x" cmod.inject.getValue = gv c.sqlQuery("SELECT * FROM mydb.users") # single-dot FROM target gets the .dbo. schema spliced in for MSSQL self.assertIn("mydb.dbo.users", captured["query"]) def test_nonquery_without_stacking_warns_none(self): set_dbms("MySQL") conf.direct = False kb.injection.data = {} # no stacking technique available c = Custom() cmod.inject.getValue = lambda *a, **k: self.fail("must not run a query") out = c.sqlQuery("DELETE FROM users") self.assertIsNone(out) def test_nonquery_stacked_returns_null(self): set_dbms("MySQL") conf.direct = True # direct => stacked execution allowed c = Custom() calls = {} def go(query, *a, **k): calls["query"] = query cmod.inject.goStacked = go out = c.sqlQuery("DROP TABLE users") self.assertEqual(out, NULL) self.assertIn("DROP TABLE users", calls["query"]) def test_nonedata_exception_handled(self): from lib.core.exception import SqlmapNoneDataException set_dbms("MySQL") c = Custom() def boom(*a, **k): raise SqlmapNoneDataException("no data") cmod.inject.getValue = boom # exception is swallowed and logged; output stays None self.assertIsNone(c.sqlQuery("SELECT 1")) class TestCustomSqlFile(_GenericBase): def test_sqlfile_select_snippets(self): set_dbms("MySQL") c = Custom() cmod.inject.getValue = lambda query, **k: "r" # getSQLSnippet reads from disk; patch it to return inline SQL. saved = cmod.getSQLSnippet try: cmod.getSQLSnippet = lambda dbms, filename, **kw: "SELECT 1;SELECT 2" conf.sqlFile = "dummy.sql" c.sqlFile() # two SELECT statements => two recorded dumper.sqlQuery calls self.assertEqual(len(conf.dumper.sqlQueries), 2) finally: cmod.getSQLSnippet = saved def test_sqlfile_nonselect_snippet(self): set_dbms("MySQL") conf.direct = True c = Custom() cmod.inject.goStacked = lambda *a, **k: None saved = cmod.getSQLSnippet try: cmod.getSQLSnippet = lambda dbms, filename, **kw: "DROP TABLE x" conf.sqlFile = "dummy.sql" c.sqlFile() # non-SELECT => single recorded call with the whole snippet self.assertEqual(len(conf.dumper.sqlQueries), 1) self.assertEqual(conf.dumper.sqlQueries[0][0], "DROP TABLE x") finally: cmod.getSQLSnippet = saved # --------------------------------------------------------------------------- # # misc.py # --------------------------------------------------------------------------- # class _TestMisc(Miscellaneous): """Miscellaneous with the OS/exec collaborators stubbed.""" cmdTblName = "sqlmapoutput" def __init__(self): Miscellaneous.__init__(self) self.checkDbmsOsCalls = 0 self.execCmdCalls = [] def checkDbmsOs(self, detailed=False, vatch=False): self.checkDbmsOsCalls += 1 def execCmd(self, cmd, silent=False): self.execCmdCalls.append((cmd, silent)) class TestMisc(_GenericBase): def test_remote_temp_path_posix(self): set_dbms("MySQL") self._force_os(OS.LINUX) conf.tmpPath = None m = _TestMisc() out = m.getRemoteTempPath() self.assertEqual(out, "/tmp") self.assertEqual(conf.tmpPath, "/tmp") def test_remote_temp_path_windows_direct(self): set_dbms("MySQL") self._force_os(OS.WINDOWS) conf.tmpPath = None conf.direct = True m = _TestMisc() out = m.getRemoteTempPath() self.assertEqual(out, "%TEMP%") def test_remote_temp_path_explicit_windows_drive(self): # An explicit Windows-style drive path flips Backend OS to Windows. set_dbms("MySQL") conf.tmpPath = "C:\\Temp" m = _TestMisc() out = m.getRemoteTempPath() self.assertTrue(Backend.isOs(OS.WINDOWS)) self.assertIn("Temp", out) self.assertNotIn("\\", out) # ntToPosixSlashes normalized the path def test_remote_temp_path_mssql_errorlog(self): set_dbms("Microsoft SQL Server") conf.tmpPath = None mmod.inject.getValue = lambda query, **k: "C:\\Logs\\ERRORLOG" m = _TestMisc() out = m.getRemoteTempPath() # ntpath.dirname strips the ERRORLOG filename, then ntToPosixSlashes # normalizes the slashes: the exact temp dir must be "C:/Logs". Asserting # the full path (and that the filename is gone) proves dirname ran. self.assertEqual(out, "C:/Logs") self.assertNotIn("ERRORLOG", out) def test_get_version_from_banner(self): set_dbms("MySQL") conf.direct = True kb.bannerFp = {} mmod.inject.getValue = lambda query, **k: "5.7.31-log" m = _TestMisc() m.getVersionFromBanner() # regex \d[\d.-]* extracts the leading numeric-ish run (trailing '-' kept) self.assertEqual(kb.bannerFp["dbmsVersion"], "5.7.31-") def test_get_version_from_banner_cached(self): set_dbms("MySQL") kb.bannerFp = {"dbmsVersion": "8.0"} mmod.inject.getValue = lambda *a, **k: self.fail("must not query when cached") m = _TestMisc() m.getVersionFromBanner() self.assertEqual(kb.bannerFp["dbmsVersion"], "8.0") def test_del_remote_file_posix(self): set_dbms("MySQL") self._force_os(OS.LINUX) m = _TestMisc() m.delRemoteFile("/tmp/foo") self.assertEqual(m.execCmdCalls[-1], ("rm -f /tmp/foo", True)) def test_del_remote_file_windows(self): set_dbms("MySQL") self._force_os(OS.WINDOWS) m = _TestMisc() m.delRemoteFile("C:/tmp/foo") cmd, silent = m.execCmdCalls[-1] self.assertTrue(cmd.startswith("del /F /Q")) self.assertTrue(silent) def test_del_remote_file_empty_noop(self): set_dbms("MySQL") m = _TestMisc() m.delRemoteFile(None) self.assertEqual(m.execCmdCalls, []) self.assertEqual(m.checkDbmsOsCalls, 0) def test_create_support_tbl(self): set_dbms("MySQL") m = _TestMisc() stacked = [] mmod.inject.goStacked = lambda query, **k: stacked.append(query) m.createSupportTbl("mytbl", "data", "TEXT") joined = " | ".join(stacked) self.assertIn("DROP TABLE mytbl", joined) self.assertIn("CREATE TABLE mytbl(data TEXT)", joined) def test_create_support_tbl_mssql_cmdtbl(self): set_dbms("Microsoft SQL Server") m = _TestMisc() stacked = [] mmod.inject.goStacked = lambda query, **k: stacked.append(query) m.createSupportTbl(m.cmdTblName, "data", "NVARCHAR(4000)") joined = " | ".join(stacked) # MSSQL cmd output table gets an IDENTITY id column self.assertIn("IDENTITY", joined) def test_like_or_exact_default(self): m = _TestMisc() mmod.readInput = lambda *a, **k: '1' choice, cond = m.likeOrExact("table") self.assertEqual(choice, '1') self.assertIn("LIKE", cond) def test_like_or_exact_exact(self): m = _TestMisc() mmod.readInput = lambda *a, **k: '2' choice, cond = m.likeOrExact("table") self.assertEqual(choice, '2') self.assertEqual(cond, "='%s'") def test_like_or_exact_invalid(self): from lib.core.exception import SqlmapNoneDataException m = _TestMisc() mmod.readInput = lambda *a, **k: '9' self.assertRaises(SqlmapNoneDataException, m.likeOrExact, "table") # --------------------------------------------------------------------------- # # takeover.py (pure helpers only) # --------------------------------------------------------------------------- # class _TestTakeover(tmod.Takeover): """Takeover with all process/network collaborators stubbed. Only the pure control-flow helpers (table naming, reg read/add/del dispatch, osBof/osSmb guards) are exercised; metasploit/icmpsh/UDF spawning is replaced with recorders so no external process or socket is ever created. """ def __init__(self): tmod.Takeover.__init__(self) self.regCalls = [] self.osVal = OS.WINDOWS self.smbCalled = False self.bofCalled = False self._regInitCalled = 0 # neutralize environment setup / OS detection def _regInit(self): self._regInitCalled += 1 def checkDbmsOs(self, detailed=False, vatch=False): pass def initEnv(self, *a, **k): pass def getRemoteTempPath(self): return "/tmp" def createMsfShellcode(self, *a, **k): pass def readRegKey(self, regKey, regValue, parse=False): self.regCalls.append(("read", regKey, regValue)) return "value" def addRegKey(self, regKey, regValue, regType, regData): self.regCalls.append(("add", regKey, regValue, regType, regData)) def delRegKey(self, regKey, regValue): self.regCalls.append(("del", regKey, regValue)) def smb(self): self.smbCalled = True def bof(self): self.bofCalled = True class TestTakeover(_GenericBase): def _saved_takeover_readInput(self): return tmod.readInput def setUp(self): _GenericBase.setUp(self) self._saved_t_readInput = tmod.readInput def tearDown(self): tmod.readInput = self._saved_t_readInput _GenericBase.tearDown(self) def test_init_cmd_table_name(self): set_dbms("MySQL") t = _TestTakeover() self.assertEqual(t.cmdTblName, "%soutput" % conf.tablePrefix) self.assertEqual(t.tblField, "data") def test_reg_read_from_conf(self): set_dbms("Microsoft SQL Server") conf.regKey = "HKLM\\Soft" conf.regVal = "Name" t = _TestTakeover() out = t.regRead() self.assertEqual(out, "value") self.assertEqual(t.regCalls[-1], ("read", "HKLM\\Soft", "Name")) self.assertEqual(t._regInitCalled, 1) def test_reg_read_defaults(self): set_dbms("Microsoft SQL Server") conf.regKey = None conf.regVal = None tmod.readInput = lambda message, default=None, **k: default t = _TestTakeover() t.regRead() kind, regKey, regVal = t.regCalls[-1] self.assertEqual(kind, "read") self.assertIn("CurrentVersion", regKey) self.assertEqual(regVal, "ProductName") def test_reg_add_from_conf(self): set_dbms("Microsoft SQL Server") conf.regKey = "HKLM\\Soft" conf.regVal = "Name" conf.regData = "data" conf.regType = "REG_SZ" t = _TestTakeover() t.regAdd() self.assertEqual(t.regCalls[-1], ("add", "HKLM\\Soft", "Name", "REG_SZ", "data")) def test_reg_add_missing_key_raises(self): from lib.core.exception import SqlmapMissingMandatoryOptionException set_dbms("Microsoft SQL Server") conf.regKey = None conf.regVal = None conf.regData = None conf.regType = None tmod.readInput = lambda *a, **k: "" # empty -> missing mandatory option t = _TestTakeover() self.assertRaises(SqlmapMissingMandatoryOptionException, t.regAdd) def test_reg_del_confirmed(self): set_dbms("Microsoft SQL Server") conf.regKey = "HKLM\\Soft" conf.regVal = "Name" tmod.readInput = lambda message, default=None, boolean=False, **k: True if boolean else default t = _TestTakeover() t.regDel() self.assertEqual(t.regCalls[-1], ("del", "HKLM\\Soft", "Name")) def test_reg_del_declined(self): set_dbms("Microsoft SQL Server") conf.regKey = "HKLM\\Soft" conf.regVal = "Name" tmod.readInput = lambda message, default=None, boolean=False, **k: False if boolean else default t = _TestTakeover() t.regDel() # declined => no delRegKey call recorded self.assertEqual([c for c in t.regCalls if c[0] == "del"], []) def test_osbof_wrong_dbms_raises(self): from lib.core.exception import SqlmapUnsupportedDBMSException set_dbms("MySQL") conf.direct = True t = _TestTakeover() self.assertRaises(SqlmapUnsupportedDBMSException, t.osBof) def test_osbof_no_stacking_returns(self): set_dbms("Microsoft SQL Server") conf.direct = False kb.injection.data = {} # no stacking, not direct => early return t = _TestTakeover() self.assertIsNone(t.osBof()) self.assertFalse(t.bofCalled) def test_ossmb_non_windows_raises(self): from lib.core.exception import SqlmapUnsupportedDBMSException set_dbms("MySQL") conf.direct = True t = _TestTakeover() # checkDbmsOs is a no-op here, so force the non-Windows OS explicitly self._force_os(OS.LINUX) self.assertRaises(SqlmapUnsupportedDBMSException, t.osSmb) self.assertFalse(t.smbCalled) def test_ossmb_windows_invokes_smb(self): set_dbms("MySQL") conf.direct = True self._force_os(OS.WINDOWS) t = _TestTakeover() t.osSmb() self.assertTrue(t.smbCalled) if __name__ == "__main__": unittest.main()