sqlmap/tests/test_dbms_enum_b.py
Miroslav Štampar cb20a446ae
Some checks are pending
/ build (macos-latest, 3.8) (push) Waiting to run
/ build (ubuntu-latest, pypy-2.7) (push) Waiting to run
/ build (windows-latest, 3.14) (push) Waiting to run
Update of unit tests
2026-06-28 14:28:42 +02:00

469 lines
17 KiB
Python

#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
Second batch of DBMS-specific enumeration override tests (companion to
tests/test_dbms_enum.py, which covers Microsoft SQL Server getTables).
Each test drives a FULL per-DBMS handler (the *Map class in
plugins/dbms/<dbms>/__init__.py) with the injection layer mocked, so the
dialect-specific table/column/user/privilege discovery paths run without a live
target, network, or DBMS. The in-band (UNION/error/direct) branch is taken via
conf.direct=True; conf.batch=True avoids interactive prompts.
Covered here:
* Sybase - getUsers, getDbs, getTables, getColumns, getPrivileges,
searchDb/searchTable/searchColumn, getHostname, getStatements
* SAP MaxDB - getDbs, getTables, getColumns, getPrivileges,
getPasswordHashes, getHostname, getStatements
* Microsoft SQL Server - getPrivileges, searchTable, searchColumn
(getTables already covered by test_dbms_enum.py)
* IBM DB2 - getPasswordHashes, getStatements
* Informix - searchDb, searchTable, searchColumn, getStatements
* Firebird - getDbs, getPasswordHashes, searchDb, getHostname, getStatements
* HSQLDB - getBanner, getPrivileges, getHostname, getStatements,
getCurrentDb
Sybase/MaxDB enumeration goes through lib.utils.pivotdumptable.pivotDumpTable
(imported into the module namespace), so for those we mock that wrapper - it is
part of the same data-retrieval layer - and mock inject.getValue elsewhere.
stdlib unittest only (no pytest / no pip); works on Python 2.7 and 3.x.
"""
import importlib
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.data import conf, kb
from lib.core.common import Backend
from lib.core.enums import EXPECTED
from lib.request import inject
def _fresh_cached():
kb.data.cachedDbs = []
kb.data.cachedTables = {}
kb.data.cachedColumns = {}
kb.data.cachedUsers = []
kb.data.cachedUsersPrivileges = {}
kb.data.cachedCounts = {}
kb.data.cachedStatements = []
kb.data.banner = None
class _NoOpDumper(object):
"""Swallow every dumper call so search methods don't emit/prompt."""
def __getattr__(self, name):
return lambda *a, **k: None
def _handler(display_name, dirname):
"""Instantiate the full *Map handler for the given DBMS."""
set_dbms(display_name)
main = importlib.import_module("plugins.dbms.%s" % dirname)
cls = [getattr(main, n) for n in dir(main) if n.endswith("Map")][0]
return cls()
class _EnumBase(unittest.TestCase):
"""Snapshot/restore every global these enumerators mutate."""
# subclasses set these
display_name = None
dirname = None
def setUp(self):
# config snapshot
self._direct = conf.direct
self._batch = conf.batch
self._db = conf.db
self._tbl = conf.tbl
self._col = conf.col
self._user = conf.user
self._exclude = conf.exclude
self._search = conf.search
self._getBanner = conf.getBanner
self._excludeSysDbs = conf.excludeSysDbs
self._dumper = conf.get("dumper")
# kb snapshot
self._cached = {k: kb.data.get(k) for k in (
"cachedDbs", "cachedTables", "cachedColumns", "cachedUsers",
"cachedUsersPrivileges", "cachedCounts", "cachedStatements", "banner",
)}
self._hintValue = kb.hintValue
self._injectionData = kb.injection.data
self._currentDb = kb.data.get("currentDb")
self._hasIS = kb.data.get("has_information_schema")
# injection layer snapshot
self._gv = inject.getValue
self._cbe = getattr(inject, "checkBooleanExpression", None)
# baseline config the in-band/non-interactive paths need
conf.direct = True
conf.batch = True
kb.data.has_information_schema = True
_fresh_cached()
# restore the chosen DBMS for every test
self.handler = _handler(self.display_name, self.dirname)
# the enumeration module whose pivotDumpTable some tests stub
self.em = importlib.import_module("plugins.dbms.%s.enumeration" % self.dirname)
def tearDown(self):
conf.direct = self._direct
conf.batch = self._batch
conf.db = self._db
conf.tbl = self._tbl
conf.col = self._col
conf.user = self._user
conf.exclude = self._exclude
conf.search = self._search
conf.getBanner = self._getBanner
conf.excludeSysDbs = self._excludeSysDbs
conf.dumper = self._dumper
for k, v in self._cached.items():
kb.data[k] = v
kb.hintValue = self._hintValue
kb.injection.data = self._injectionData
kb.data.currentDb = self._currentDb
kb.data.has_information_schema = self._hasIS
inject.getValue = self._gv
if self._cbe is not None:
inject.checkBooleanExpression = self._cbe
if hasattr(self.em, "pivotDumpTable"):
# restore the pristine reference from the wrapper module
import lib.utils.pivotdumptable as _pdt
self.em.pivotDumpTable = _pdt.pivotDumpTable
# ---------------------------------------------------------------------------
# Sybase
# ---------------------------------------------------------------------------
class TestSybaseEnum(_EnumBase):
display_name = "Sybase"
dirname = "sybase"
def _pivot(self, *value_lists):
"""Make em.pivotDumpTable return canned (entries, lengths) per call.
Each successive call pops the next mapping of {colName: [values]}.
"""
calls = list(value_lists)
def fake(table, colList, count=None, blind=True, alias=None):
mapping = calls.pop(0) if calls else {}
entries = {}
lengths = {}
for col in colList:
vals = mapping.get(col.split(".")[-1], [])
entries[col] = list(vals)
lengths[col] = 0
return entries, lengths
self.em.pivotDumpTable = fake
def test_get_users(self):
self._pivot({"name": ["sa", "guest"]})
users = self.handler.getUsers()
self.assertIn("sa", users)
self.assertIn("guest", users)
def test_get_dbs(self):
self._pivot({"name": ["master", "model"]})
dbs = self.handler.getDbs()
self.assertEqual(sorted(dbs), ["master", "model"])
def test_get_tables(self):
conf.db = "testdb"
self._pivot({"name": ["users", "logs"]})
tables = self.handler.getTables()
self.assertIn("testdb", tables)
self.assertEqual(sorted(tables["testdb"]), ["logs", "users"])
def test_get_columns(self):
conf.db = "testdb"
conf.tbl = "users"
# column pivot returns name + usertype: REAL Sybase numeric type ids that
# getColumns resolves through SYBASE_TYPES (7 -> "int", 2 -> "varchar").
from lib.core.dicts import SYBASE_TYPES
self._pivot({"name": ["id", "name"], "usertype": ["7", "2"]})
cols = self.handler.getColumns()
self.assertIn("testdb", cols)
# table key is identifier-normalized (may be schema-qualified)
tbls = cols["testdb"]
self.assertTrue(any("users" in t for t in tbls))
colset = list(tbls.values())[0]
# the VALUE is the resolved type name, not the raw usertype number:
# proves the SYBASE_TYPES numeric->name mapping actually ran.
self.assertEqual(colset["id"], SYBASE_TYPES[7]) # "int"
self.assertEqual(colset["name"], SYBASE_TYPES[2]) # "varchar"
def test_get_privileges(self):
# getPrivileges -> getUsers (pivot) then isDba (checkBooleanExpression).
# Drive the admin-set branch BOTH ways via the isDba oracle so the result
# is not forced by a constant-True stub.
conf.user = None
# oracle True: every user is flagged DBA -> admins == all users
self._pivot({"name": ["sa", "guest"]})
inject.checkBooleanExpression = lambda *a, **k: True
privs, admins = self.handler.getPrivileges()
self.assertIn("sa", privs) # users still enumerated as privilege keys
self.assertIn("guest", privs)
self.assertEqual(admins, set(["sa", "guest"]))
# oracle False: nobody is a DBA -> admins is empty, but users still listed
_fresh_cached()
self._pivot({"name": ["sa", "guest"]})
inject.checkBooleanExpression = lambda *a, **k: False
privs, admins = self.handler.getPrivileges()
self.assertIn("sa", privs)
self.assertEqual(admins, set())
def test_search_not_implemented(self):
# these intentionally return [] with a warning on Sybase
self.assertEqual(self.handler.searchDb(), [])
self.assertEqual(self.handler.searchTable(), [])
self.assertEqual(self.handler.searchColumn(), [])
def test_get_hostname(self):
# not possible on Sybase; just must not raise
self.assertIsNone(self.handler.getHostname())
def test_get_statements(self):
self.assertEqual(self.handler.getStatements(), [])
# ---------------------------------------------------------------------------
# SAP MaxDB
# ---------------------------------------------------------------------------
class TestMaxDBEnum(_EnumBase):
display_name = "SAP MaxDB"
dirname = "maxdb"
def _pivot(self, *value_lists):
calls = list(value_lists)
def fake(table, colList, count=None, blind=True, alias=None):
mapping = calls.pop(0) if calls else {}
entries = {}
lengths = {}
for col in colList:
vals = mapping.get(col.split(".")[-1], [])
entries[col] = list(vals)
lengths[col] = 0
return entries, lengths
self.em.pivotDumpTable = fake
def test_get_dbs(self):
self._pivot({"schemaname": ["SYSTEM", "DOMAIN"]})
dbs = self.handler.getDbs()
self.assertEqual(sorted(dbs), ["DOMAIN", "SYSTEM"])
def test_get_tables(self):
conf.db = "SYSTEM"
self._pivot({"tablename": ["USERS", "TABLES"]})
tables = self.handler.getTables()
# db key is identifier-normalized (uppercase names get quoted)
self.assertEqual(len(tables), 1)
tbls = list(tables.values())[0]
self.assertEqual(sorted(tbls), ["TABLES", "USERS"])
def test_get_columns(self):
conf.db = "SYSTEM"
conf.tbl = "USERS"
self._pivot({
"columnname": ["ID", "NAME"],
"datatype": ["INTEGER", "CHAR"],
"len": ["4", "32"],
})
cols = self.handler.getColumns()
self.assertEqual(len(cols), 1)
tbls = list(cols.values())[0]
self.assertIn("USERS", tbls)
self.assertEqual(tbls["USERS"]["ID"], "INTEGER(4)")
def test_get_privileges_empty(self):
self.assertEqual(self.handler.getPrivileges(), {})
def test_get_password_hashes_empty(self):
self.assertEqual(self.handler.getPasswordHashes(), {})
def test_get_hostname(self):
self.assertIsNone(self.handler.getHostname())
def test_get_statements(self):
self.assertEqual(self.handler.getStatements(), [])
# ---------------------------------------------------------------------------
# Microsoft SQL Server (methods NOT covered by test_dbms_enum.py)
# ---------------------------------------------------------------------------
class TestMSSQLServerExtraEnum(_EnumBase):
display_name = "Microsoft SQL Server"
dirname = "mssqlserver"
def test_get_privileges(self):
# getPrivileges -> getUsers (generic, inject.getValue) then isDba.
# Exercise the admin-set branch BOTH ways via the isDba oracle.
conf.user = None
inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"]
# oracle True: all users flagged DBA
inject.checkBooleanExpression = lambda *a, **k: True
privs, admins = self.handler.getPrivileges()
self.assertIn("sa", privs)
self.assertEqual(admins, set(["sa", "BUILTIN\\Administrators"]))
# oracle False: none are DBA -> empty admin set, users still enumerated
_fresh_cached()
inject.getValue = lambda q, *a, **k: ["sa", "BUILTIN\\Administrators"]
inject.checkBooleanExpression = lambda *a, **k: False
privs, admins = self.handler.getPrivileges()
self.assertIn("sa", privs)
self.assertEqual(admins, set())
def test_search_table(self):
conf.db = "testdb"
conf.tbl = "users"
# in-band branch: getValue returns matching table name(s)
inject.getValue = lambda q, *a, **k: ["users"]
# capture the discovered tables instead of dumping them
captured = {}
conf.dumper = _NoOpDumper()
self.handler.dumpFoundTables = lambda tables: captured.update(tables)
self.handler.searchTable()
# at least one database mapped to the matched table
flat = set()
for tbls in captured.values():
flat.update(tbls)
self.assertTrue(any("users" in t for t in flat))
def test_search_column(self):
conf.db = "testdb"
conf.tbl = None
conf.col = "password"
# exact match (no wildcard) so no recursive getColumns call;
# getValue returns the tables that contain the column
inject.getValue = lambda q, *a, **k: ["users"]
captured = {}
conf.dumper = _NoOpDumper()
self.handler.dumpFoundColumn = lambda dbs, foundCols, colConsider: captured.update(dbs)
self.handler.searchColumn()
# the searched column was located in at least one table
flat = set()
for tbls in captured.values():
flat.update(tbls)
self.assertTrue(any("users" in t for t in flat))
# ---------------------------------------------------------------------------
# IBM DB2
# ---------------------------------------------------------------------------
class TestDB2Enum(_EnumBase):
display_name = "IBM DB2"
dirname = "db2"
def test_get_password_hashes_empty(self):
self.assertEqual(self.handler.getPasswordHashes(), {})
def test_get_statements_empty(self):
self.assertEqual(self.handler.getStatements(), [])
# ---------------------------------------------------------------------------
# Informix
# ---------------------------------------------------------------------------
class TestInformixEnum(_EnumBase):
display_name = "Informix"
dirname = "informix"
def test_search_db(self):
self.assertEqual(self.handler.searchDb(), [])
def test_search_table(self):
self.assertEqual(self.handler.searchTable(), [])
def test_search_column(self):
self.assertEqual(self.handler.searchColumn(), [])
def test_get_statements(self):
self.assertEqual(self.handler.getStatements(), [])
# ---------------------------------------------------------------------------
# Firebird
# ---------------------------------------------------------------------------
class TestFirebirdEnum(_EnumBase):
display_name = "Firebird"
dirname = "firebird"
def test_get_dbs_empty(self):
self.assertEqual(self.handler.getDbs(), [])
def test_get_password_hashes_empty(self):
self.assertEqual(self.handler.getPasswordHashes(), {})
def test_search_db_empty(self):
self.assertEqual(self.handler.searchDb(), [])
def test_get_hostname(self):
self.assertIsNone(self.handler.getHostname())
def test_get_statements_empty(self):
self.assertEqual(self.handler.getStatements(), [])
# ---------------------------------------------------------------------------
# HSQLDB
# ---------------------------------------------------------------------------
class TestHSQLDBEnum(_EnumBase):
display_name = "HSQLDB"
dirname = "hsqldb"
def test_get_banner(self):
conf.getBanner = True
kb.data.banner = None
# getValue returns a single-element LIST; getBanner pipes it through
# unArrayizeValue, which must unwrap it to the scalar banner string.
inject.getValue = lambda q, *a, **k: ["HSQLDB 2.5.1"]
banner = self.handler.getBanner()
self.assertEqual(banner, "HSQLDB 2.5.1")
def test_get_privileges_empty(self):
self.assertEqual(self.handler.getPrivileges(), {})
def test_get_hostname(self):
self.assertIsNone(self.handler.getHostname())
def test_get_statements_empty(self):
self.assertEqual(self.handler.getStatements(), [])
def test_get_current_db_default_schema(self):
from lib.core.settings import HSQLDB_DEFAULT_SCHEMA
self.assertEqual(self.handler.getCurrentDb(), HSQLDB_DEFAULT_SCHEMA)
if __name__ == "__main__":
unittest.main()