diff --git a/lib/core/enums.py b/lib/core/enums.py index 0e4798e33..e74f92997 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -117,6 +117,7 @@ class FORK(object): DORIS = "Doris" STARROCKS = "StarRocks" TRINO = "Trino" + DUCKDB = "DuckDB" class CUSTOM_LOGGING(object): PAYLOAD = 9 diff --git a/lib/core/settings.py b/lib/core/settings.py index ba4a2e6f5..3ab6bdcbe 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.7.93" +VERSION = "1.10.7.94" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" 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) @@ -368,7 +368,7 @@ HANA_SYSTEM_DBS = ("SYS", "SYSTEM", "_SYS_BI", "_SYS_BIC", "_SYS_REPO", "_SYS_ST # Note: () + () MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms") MYSQL_ALIASES = ("mysql", "my") + ("mariadb", "maria", "memsql", "tidb", "percona", "drizzle", "doris", "starrocks") -PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb", "opengauss") +PGSQL_ALIASES = ("postgresql", "postgres", "pgsql", "psql", "pg") + ("cockroach", "cockroachdb", "amazon redshift", "redshift", "greenplum", "yellowbrick", "enterprisedb", "yugabyte", "yugabytedb", "opengauss", "duckdb") ORACLE_ALIASES = ("oracle", "orcl", "ora", "or", "dm8") SQLITE_ALIASES = ("sqlite", "sqlite3") ACCESS_ALIASES = ("microsoft access", "msaccess", "access", "jet") diff --git a/plugins/dbms/postgresql/enumeration.py b/plugins/dbms/postgresql/enumeration.py index 181384bec..e52208d67 100644 --- a/plugins/dbms/postgresql/enumeration.py +++ b/plugins/dbms/postgresql/enumeration.py @@ -5,7 +5,11 @@ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ +from lib.core.common import Backend from lib.core.data import logger +from lib.core.data import queries +from lib.core.enums import DBMS +from lib.core.enums import FORK from plugins.generic.enumeration import Enumeration as GenericEnumeration @@ -13,3 +17,55 @@ class Enumeration(GenericEnumeration): def getHostname(self): warnMsg = "on PostgreSQL it is not possible to enumerate the hostname" logger.warning(warnMsg) + + def getColumns(self, *args, **kwargs): + if not Backend.isFork(FORK.DUCKDB): + return GenericEnumeration.getColumns(self, *args, **kwargs) + + # DuckDB (PostgreSQL fork) exposes column metadata through information_schema instead of the + # pg_catalog tables (pg_attribute yields no rows), so swap those queries in for the generic routine + columns = queries[DBMS.PGSQL].columns + backup = (columns.inband.query, columns.inband.condition, columns.blind.query, columns.blind.query2, columns.blind.count, columns.blind.condition) + + columns.inband.query = "SELECT column_name,data_type FROM information_schema.columns WHERE table_name='%s' AND table_schema='%s' ORDER BY column_name" + columns.blind.query = "SELECT column_name FROM information_schema.columns WHERE table_name='%s' AND table_schema='%s' ORDER BY column_name" + columns.blind.query2 = "SELECT data_type FROM information_schema.columns WHERE table_name='%s' AND column_name='%s' AND table_schema='%s'" + columns.blind.count = "SELECT COUNT(column_name) FROM information_schema.columns WHERE table_name='%s' AND table_schema='%s'" + columns.inband.condition = columns.blind.condition = "column_name" + + try: + return GenericEnumeration.getColumns(self, *args, **kwargs) + finally: + columns.inband.query, columns.inband.condition, columns.blind.query, columns.blind.query2, columns.blind.count, columns.blind.condition = backup + + def getUsers(self): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the users" + logger.warning(warnMsg) + return [] + + return GenericEnumeration.getUsers(self) + + def getPasswordHashes(self): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the user password hashes" + logger.warning(warnMsg) + return {} + + return GenericEnumeration.getPasswordHashes(self) + + def getPrivileges(self, query2=False): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the user privileges" + logger.warning(warnMsg) + return {} + + return GenericEnumeration.getPrivileges(self, query2) + + def getRoles(self, query2=False): + if Backend.isFork(FORK.DUCKDB): + warnMsg = "on DuckDB it is not possible to enumerate the user roles" + logger.warning(warnMsg) + return {} + + return GenericEnumeration.getRoles(self, query2) diff --git a/plugins/dbms/postgresql/fingerprint.py b/plugins/dbms/postgresql/fingerprint.py index 20eed02a9..3bcf8a510 100644 --- a/plugins/dbms/postgresql/fingerprint.py +++ b/plugins/dbms/postgresql/fingerprint.py @@ -45,6 +45,8 @@ class Fingerprint(GenericFingerprint): fork = FORK.OPENGAUSS elif inject.checkBooleanExpression("AURORA_VERSION() LIKE '%'"): # Reference: https://aws.amazon.com/premiumsupport/knowledge-center/aurora-version-number/ fork = FORK.AURORA + elif inject.checkBooleanExpression("[1,2,3][2]=2"): # NOTE: bare list literal with 1-based indexing is DuckDB-only (invalid syntax on PostgreSQL) + fork = FORK.DUCKDB else: fork = "" @@ -109,7 +111,8 @@ class Fingerprint(GenericFingerprint): logger.info(infoMsg) # NOTE: Vertica works too without the CONVERT_TO() - result = inject.checkBooleanExpression("CONVERT_TO('[RANDSTR]', QUOTE_IDENT(NULL)) IS NULL") + # NOTE: DuckDB (PostgreSQL dialect fork) lacks CONVERT_TO()/QUOTE_IDENT(), so it is accepted via its list-literal instead + result = inject.checkBooleanExpression("CONVERT_TO('[RANDSTR]', QUOTE_IDENT(NULL)) IS NULL") or inject.checkBooleanExpression("[1,2,3][2]=2") if result: infoMsg = "confirming %s" % DBMS.PGSQL