Skip to content

Commit 279a9b3

Browse files
authored
Added "Snowflake" DBMS support (#5980)
* Added SQL queries for 'Snowflake' DBMS * Added necessary constants for the 'Snowflake' DBMS * Added the 'Snowflake' DBMS to existing conditional which adds dynamic values to hardcoded statements (queries.xml) * Added plugin logic for the 'Snowflake' DBMS * Modified 'dbs' query to include 'ORDER BY' * Moved 'LIMIT' to appear before 'OFFSET'
1 parent e9a9d90 commit 279a9b3

File tree

14 files changed

+380
-4
lines changed

14 files changed

+380
-4
lines changed

data/xml/queries.xml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1786,4 +1786,66 @@
17861786
<search_table/>
17871787
<search_column/>
17881788
</dbms>
1789+
<dbms value="Snowflake">
1790+
<cast query="CAST(%s AS VARCHAR)"/>
1791+
<length query="LENGTH(%s)"/>
1792+
<isnull query="NVL(%s, ' ')"/>
1793+
<delimiter query="||"/>
1794+
<limit query="LIMIT %d OFFSET %d"/>
1795+
<limitregexp query="\s+LIMIT\s+([\d]+)\s+OFFSET\s+([\d]+)"/>
1796+
<limitgroupstart query="1"/>
1797+
<limitgroupstop query="2"/>
1798+
<limitstring query=" LIMIT "/>
1799+
<order query="ORDER BY %s ASC"/>
1800+
<count query="COUNT(%s)"/>
1801+
<comment query="--"/>
1802+
<concatenate query="%s||%s"/>
1803+
<case query="SELECT CASE WHEN (%s) THEN 1 ELSE 0 END"/>
1804+
<inference query="ASCII(SUBSTR((%s),%d,1))>%d"/>
1805+
<banner query="SELECT CURRENT_VERSION()"/>
1806+
<current_user query="SELECT CURRENT_USER()"/>
1807+
<current_db query="SELECT CURRENT_DATABASE()"/>
1808+
<hostname/>
1809+
<table_comment/>
1810+
<column_comment/>
1811+
<is_dba query="CURRENT_ROLE()='ACCOUNTADMIN'"/>
1812+
1813+
<dbs>
1814+
<inband query="SELECT DATABASE_NAME FROM SNOWFLAKE.INFORMATION_SCHEMA.DATABASES"/>
1815+
<blind query="SELECT DATABASE_NAME FROM SNOWFLAKE.INFORMATION_SCHEMA.DATABASES ORDER BY DATABASE_NAME LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM SNOWFLAKE.INFORMATION_SCHEMA.DATABASES"/>
1816+
</dbs>
1817+
1818+
<tables>
1819+
<inband query="SELECT TABLE_CATALOG, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES" condition="TABLE_TYPE='BASE TABLE' AND TABLE_CATALOG"/>
1820+
1821+
<blind query="SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_CATALOG='%s' ORDER BY TABLE_NAME LIMIT 1 OFFSET %d" count="SELECT COUNT(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_CATALOG='%s'"
1822+
/>
1823+
</tables>
1824+
1825+
<columns>
1826+
<inband query="SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='%s' AND TABLE_CATALOG='%s'"/>
1827+
<blind query="SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='%s' AND TABLE_CATALOG='%s' LIMIT 1 OFFSET %d" query2="SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='%s' AND COLUMN_NAME='%s' AND TABLE_CATALOG='%s'" count="SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='%s' AND TABLE_CATALOG='%s'"/>
1828+
</columns>
1829+
1830+
<dump_table>
1831+
<inband query="SELECT %s FROM TABLE('%s')"/>
1832+
<blind query="SELECT %s FROM %s.%s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s"/>
1833+
</dump_table>
1834+
1835+
<users>
1836+
<inband query="SELECT NAME FROM SNOWFLAKE.ACCOUNT_USAGE.USERS"/>
1837+
<blind query="SELECT NAME FROM SNOWFLAKE.ACCOUNT_USAGE.USERS LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM SNOWFLAKE.ACCOUNT_USAGE.USERS"/>
1838+
</users>
1839+
1840+
<roles>
1841+
<inband query="SELECT NAME FROM SNOWFLAKE.ACCOUNT_USAGE.ROLES"/>
1842+
<blind query="SELECT NAME FROM SNOWFLAKE.ACCOUNT_USAGE.ROLES LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM SNOWFLAKE.ACCOUNT_USAGE.ROLES"/>
1843+
</roles>
1844+
1845+
<privileges/>
1846+
<statements/>
1847+
<search_db/>
1848+
<search_table/>
1849+
<search_column/>
1850+
</dbms>
17891851
</root>

lib/controller/handler.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from lib.core.settings import SYBASE_ALIASES
4242
from lib.core.settings import VERTICA_ALIASES
4343
from lib.core.settings import VIRTUOSO_ALIASES
44+
from lib.core.settings import SNOWFLAKE_ALIASES
4445
from lib.utils.sqlalchemy import SQLAlchemy
4546

4647
from plugins.dbms.access.connector import Connector as AccessConn
@@ -99,6 +100,8 @@
99100
from plugins.dbms.vertica import VerticaMap
100101
from plugins.dbms.virtuoso.connector import Connector as VirtuosoConn
101102
from plugins.dbms.virtuoso import VirtuosoMap
103+
from plugins.dbms.snowflake.connector import Connector as SnowflakeConn
104+
from plugins.dbms.snowflake import SnowflakeMap
102105

103106
def setHandler():
104107
"""
@@ -107,6 +110,7 @@ def setHandler():
107110
"""
108111

109112
items = [
113+
(DBMS.SNOWFLAKE, SNOWFLAKE_ALIASES, SnowflakeMap, SnowflakeConn),
110114
(DBMS.MYSQL, MYSQL_ALIASES, MySQLMap, MySQLConn),
111115
(DBMS.ORACLE, ORACLE_ALIASES, OracleMap, OracleConn),
112116
(DBMS.PGSQL, PGSQL_ALIASES, PostgreSQLMap, PostgreSQLConn),
@@ -135,6 +139,7 @@ def setHandler():
135139
(DBMS.FRONTBASE, FRONTBASE_ALIASES, FrontBaseMap, FrontBaseConn),
136140
(DBMS.RAIMA, RAIMA_ALIASES, RaimaMap, RaimaConn),
137141
(DBMS.VIRTUOSO, VIRTUOSO_ALIASES, VirtuosoMap, VirtuosoConn),
142+
# TODO: put snowflake stuff on this line
138143
]
139144

140145
_ = max(_ if (conf.get("dbms") or Backend.getIdentifiedDbms() or kb.heuristicExtendedDbms or "").lower() in _[1] else () for _ in items)

lib/core/dicts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from lib.core.settings import VERTICA_ALIASES
4040
from lib.core.settings import VIRTUOSO_ALIASES
4141
from lib.core.settings import CLICKHOUSE_ALIASES
42+
from lib.core.settings import SNOWFLAKE_ALIASES
4243

4344
FIREBIRD_TYPES = {
4445
261: "BLOB",
@@ -250,6 +251,7 @@
250251
DBMS.FRONTBASE: (FRONTBASE_ALIASES, None, None, None),
251252
DBMS.RAIMA: (RAIMA_ALIASES, None, None, None),
252253
DBMS.VIRTUOSO: (VIRTUOSO_ALIASES, None, None, None),
254+
DBMS.SNOWFLAKE: (SNOWFLAKE_ALIASES, None, None, "snowflake"),
253255
}
254256

255257
# Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/

lib/core/enums.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class DBMS(object):
6060
FRONTBASE = "FrontBase"
6161
RAIMA = "Raima Database Manager"
6262
VIRTUOSO = "Virtuoso"
63+
SNOWFLAKE = "Snowflake"
6364

6465
class DBMS_DIRECTORY_NAME(object):
6566
ACCESS = "access"
@@ -90,6 +91,7 @@ class DBMS_DIRECTORY_NAME(object):
9091
FRONTBASE = "frontbase"
9192
RAIMA = "raima"
9293
VIRTUOSO = "virtuoso"
94+
SNOWFLAKE = "snowflake"
9395

9496
class FORK(object):
9597
MARIADB = "MariaDB"

lib/core/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@
292292
FRONTBASE_SYSTEM_DBS = ("DEFINITION_SCHEMA", "INFORMATION_SCHEMA")
293293
RAIMA_SYSTEM_DBS = ("",)
294294
VIRTUOSO_SYSTEM_DBS = ("",)
295+
SNOWFLAKE_SYSTEM_DBS = ("INFORMATION_SCHEMA",)
295296

296297
# Note: (<regular>) + (<forks>)
297298
MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms")
@@ -322,10 +323,11 @@
322323
FRONTBASE_ALIASES = ("frontbase",)
323324
RAIMA_ALIASES = ("raima database manager", "raima", "raimadb", "raimadm", "rdm", "rds", "velocis")
324325
VIRTUOSO_ALIASES = ("virtuoso", "openlink virtuoso")
326+
SNOWFLAKE_ALIASES = ("snowflake",)
325327

326328
DBMS_DIRECTORY_DICT = dict((getattr(DBMS, _), getattr(DBMS_DIRECTORY_NAME, _)) for _ in dir(DBMS) if not _.startswith("_"))
327329

328-
SUPPORTED_DBMS = set(MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES + MONETDB_ALIASES + DERBY_ALIASES + VERTICA_ALIASES + MCKOI_ALIASES + PRESTO_ALIASES + ALTIBASE_ALIASES + MIMERSQL_ALIASES + CLICKHOUSE_ALIASES + CRATEDB_ALIASES + CUBRID_ALIASES + CACHE_ALIASES + EXTREMEDB_ALIASES + RAIMA_ALIASES + VIRTUOSO_ALIASES)
330+
SUPPORTED_DBMS = set(MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES + H2_ALIASES + INFORMIX_ALIASES + MONETDB_ALIASES + DERBY_ALIASES + VERTICA_ALIASES + MCKOI_ALIASES + PRESTO_ALIASES + ALTIBASE_ALIASES + MIMERSQL_ALIASES + CLICKHOUSE_ALIASES + CRATEDB_ALIASES + CUBRID_ALIASES + CACHE_ALIASES + EXTREMEDB_ALIASES + RAIMA_ALIASES + VIRTUOSO_ALIASES + SNOWFLAKE_ALIASES)
329331
SUPPORTED_OS = ("linux", "windows")
330332

331333
DBMS_ALIASES = ((DBMS.MSSQL, MSSQL_ALIASES), (DBMS.MYSQL, MYSQL_ALIASES), (DBMS.PGSQL, PGSQL_ALIASES), (DBMS.ORACLE, ORACLE_ALIASES), (DBMS.SQLITE, SQLITE_ALIASES), (DBMS.ACCESS, ACCESS_ALIASES), (DBMS.FIREBIRD, FIREBIRD_ALIASES), (DBMS.MAXDB, MAXDB_ALIASES), (DBMS.SYBASE, SYBASE_ALIASES), (DBMS.DB2, DB2_ALIASES), (DBMS.HSQLDB, HSQLDB_ALIASES), (DBMS.H2, H2_ALIASES), (DBMS.INFORMIX, INFORMIX_ALIASES), (DBMS.MONETDB, MONETDB_ALIASES), (DBMS.DERBY, DERBY_ALIASES), (DBMS.VERTICA, VERTICA_ALIASES), (DBMS.MCKOI, MCKOI_ALIASES), (DBMS.PRESTO, PRESTO_ALIASES), (DBMS.ALTIBASE, ALTIBASE_ALIASES), (DBMS.MIMERSQL, MIMERSQL_ALIASES), (DBMS.CLICKHOUSE, CLICKHOUSE_ALIASES), (DBMS.CRATEDB, CRATEDB_ALIASES), (DBMS.CUBRID, CUBRID_ALIASES), (DBMS.CACHE, CACHE_ALIASES), (DBMS.EXTREMEDB, EXTREMEDB_ALIASES), (DBMS.FRONTBASE, FRONTBASE_ALIASES), (DBMS.RAIMA, RAIMA_ALIASES), (DBMS.VIRTUOSO, VIRTUOSO_ALIASES))

plugins/dbms/snowflake/__init__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org)
5+
See the file 'LICENSE' for copying permission
6+
"""
7+
8+
from lib.core.enums import DBMS
9+
from lib.core.settings import SNOWFLAKE_SYSTEM_DBS
10+
from lib.core.unescaper import unescaper
11+
from plugins.dbms.snowflake.enumeration import Enumeration
12+
from plugins.dbms.snowflake.filesystem import Filesystem
13+
from plugins.dbms.snowflake.fingerprint import Fingerprint
14+
from plugins.dbms.snowflake.syntax import Syntax
15+
from plugins.dbms.snowflake.takeover import Takeover
16+
from plugins.generic.misc import Miscellaneous
17+
18+
class SnowflakeMap(Syntax, Fingerprint, Enumeration, Filesystem, Miscellaneous, Takeover):
19+
"""
20+
This class defines Snowflake methods
21+
"""
22+
23+
def __init__(self):
24+
self.excludeDbsList = SNOWFLAKE_SYSTEM_DBS
25+
26+
for cls in self.__class__.__bases__:
27+
cls.__init__(self)
28+
29+
unescaper[DBMS.SNOWFLAKE] = Syntax.escape
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org)
5+
See the file 'LICENSE' for copying permission
6+
"""
7+
8+
try:
9+
import snowflake.connector
10+
except:
11+
pass
12+
13+
import logging
14+
15+
from lib.core.common import getSafeExString
16+
from lib.core.convert import getText
17+
from lib.core.data import conf
18+
from lib.core.data import logger
19+
from lib.core.exception import SqlmapConnectionException
20+
from plugins.generic.connector import Connector as GenericConnector
21+
22+
class Connector(GenericConnector):
23+
"""
24+
Homepage: https://www.snowflake.com/
25+
User guide: https://docs.snowflake.com/en/developer-guide/python-connector/python-connector
26+
API: https://docs.snowflake.com/en/developer-guide/python-connector/python-connector-api
27+
"""
28+
29+
def __init__(self):
30+
GenericConnector.__init__(self)
31+
32+
def connect(self):
33+
self.initConnection()
34+
35+
try:
36+
self.connector = snowflake.connector.connect(
37+
user=self.user,
38+
password=self.password,
39+
account=self.account,
40+
warehouse=self.warehouse,
41+
database=self.db,
42+
schema=self.schema
43+
)
44+
cursor = self.connector.cursor()
45+
cursor.execute("SELECT CURRENT_VERSION()")
46+
cursor.close()
47+
48+
except Exception as ex:
49+
raise SqlmapConnectionException(getSafeExString(ex))
50+
51+
self.initCursor()
52+
self.printConnected()
53+
54+
def fetchall(self):
55+
try:
56+
return self.cursor.fetchall()
57+
except Exception as ex:
58+
logger.log(logging.WARNING if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex))
59+
return None
60+
61+
def execute(self, query):
62+
try:
63+
self.cursor.execute(getText(query))
64+
except Exception as ex:
65+
logger.log(logging.WARNING if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex))
66+
return None
67+
68+
def select(self, query):
69+
self.execute(query)
70+
return self.fetchall()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org)
5+
See the file 'LICENSE' for copying permission
6+
"""
7+
8+
from lib.core.data import logger
9+
from lib.core.exception import SqlmapUnsupportedFeatureException
10+
from plugins.generic.enumeration import Enumeration as GenericEnumeration
11+
12+
class Enumeration(GenericEnumeration):
13+
def getPasswordHashes(self):
14+
warnMsg = "on Snowflake it is not possible to enumerate the user password hashes"
15+
logger.warning(warnMsg)
16+
return {}
17+
18+
def getHostname(self):
19+
warnMsg = "on Snowflake it is not possible to enumerate the hostname"
20+
logger.warning(warnMsg)
21+
22+
def searchDb(self):
23+
warnMsg = "on Snowflake it is not possible to search databases"
24+
logger.warning(warnMsg)
25+
return []
26+
27+
def searchColumn(self):
28+
errMsg = "on Snowflake it is not possible to search columns"
29+
raise SqlmapUnsupportedFeatureException(errMsg)
30+
31+
def getPrivileges(self, *args, **kwargs):
32+
warnMsg = "on SQLite it is not possible to enumerate the user privileges"
33+
logger.warning(warnMsg)
34+
return {}
35+
36+
def getStatements(self):
37+
warnMsg = "on Snowflake it is not possible to enumerate the SQL statements"
38+
logger.warning(warnMsg)
39+
return []
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org)
5+
See the file 'LICENSE' for copying permission
6+
"""
7+
8+
from lib.core.exception import SqlmapUnsupportedFeatureException
9+
from plugins.generic.filesystem import Filesystem as GenericFilesystem
10+
11+
class Filesystem(GenericFilesystem):
12+
def readFile(self, remoteFile):
13+
errMsg = "on Snowflake it is not possible to read files"
14+
raise SqlmapUnsupportedFeatureException(errMsg)
15+
16+
def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False):
17+
errMsg = "on Snowflake it is not possible to write files"
18+
raise SqlmapUnsupportedFeatureException(errMsg)

0 commit comments

Comments
 (0)