Apache Superset: SQL Injection on where_in JINJA macro
Description
A where_in JINJA macro allows users to specify a quote, which combined with a carefully crafted statement would allow for SQL injection in Apache Superset.This issue affects Apache Superset: before 2.1.2, from 3.0.0 before 3.0.2.
Users are recommended to upgrade to version 3.0.2, which fixes the issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CVE-2023-49736 is a SQL injection in Apache Superset's where_in Jinja macro, fixed in versions 2.1.2 and 3.0.2.
Vulnerability
Overview
CVE-2023-49736 is a SQL injection vulnerability found in Apache Superset's where_in JINJA macro. The macro allows users to specify a quote character, which, when combined with a carefully crafted statement, can lead to SQL injection. This issue affects Apache Superset versions before 2.1.2 and from 3.0.0 before 3.0.2 [1].
Exploitation
Details
The vulnerability resides in the where_in macro, which constructs SQL IN clauses. The original implementation used a user-supplied mark parameter for quoting values, but did not properly escape or parameterize the input, allowing an attacker to inject arbitrary SQL [2][4]. The fix replaces the manual quoting approach with SQLAlchemy's bindparam and dialect-specific compilation, ensuring proper escaping and preventing injection [4].
Impact
A successful exploit could allow an attacker with access to the Superset SQL editor or templated queries to execute arbitrary SQL commands on the underlying database. This could lead to unauthorized data access, modification, or deletion, depending on database permissions.
Mitigation
Users should upgrade to Apache Superset version 2.1.2 or 3.0.2, which contain the fix. The patch removes the insecure mark parameter and uses parameterized queries for SQL generation [2][4]. No workarounds are mentioned, but restricting access to the SQL editor and templated queries can reduce risk.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
apache-supersetPyPI | < 2.1.3 | 2.1.3 |
apache-supersetPyPI | >= 3.0.0, < 3.0.2 | 3.0.2 |
Affected products
3- osv-coords2 versions
< 2.1.2+ 1 more
- (no CPE)range: < 2.1.2
- (no CPE)range: < 2.1.3
- Apache Software Foundation/Apache Supersetv5Range: 0
Patches
21d403dab9822fix: DB-specific quoting in Jinja macro (#25779)
2 files changed · +38 −16
superset/jinja_context.py+31 −14 modified@@ -25,6 +25,7 @@ from jinja2 import DebugUndefined from jinja2.sandbox import SandboxedEnvironment from sqlalchemy.engine.interfaces import Dialect +from sqlalchemy.sql.expression import bindparam from sqlalchemy.types import String from typing_extensions import TypedDict @@ -397,23 +398,39 @@ def validate_template_context( return validate_context_types(context) -def where_in(values: list[Any], mark: str = "'") -> str: - """ - Given a list of values, build a parenthesis list suitable for an IN expression. +class WhereInMacro: # pylint: disable=too-few-public-methods + def __init__(self, dialect: Dialect): + self.dialect = dialect - >>> where_in([1, "b", 3]) - (1, 'b', 3) + def __call__(self, values: list[Any], mark: Optional[str] = None) -> str: + """ + Given a list of values, build a parenthesis list suitable for an IN expression. - """ + >>> from sqlalchemy.dialects import mysql + >>> where_in = WhereInMacro(dialect=mysql.dialect()) + >>> where_in([1, "Joe's", 3]) + (1, 'Joe''s', 3) - def quote(value: Any) -> str: - if isinstance(value, str): - value = value.replace(mark, mark * 2) - return f"{mark}{value}{mark}" - return str(value) + """ + binds = [bindparam(f"value_{i}", value) for i, value in enumerate(values)] + string_representations = [ + str( + bind.compile( + dialect=self.dialect, compile_kwargs={"literal_binds": True} + ) + ) + for bind in binds + ] + joined_values = ", ".join(string_representations) + result = f"({joined_values})" + + if mark: + result += ( + "\n-- WARNING: the `mark` parameter was removed from the `where_in` " + "macro for security reasons\n" + ) - joined_values = ", ".join(quote(value) for value in values) - return f"({joined_values})" + return result class BaseTemplateProcessor: @@ -449,7 +466,7 @@ def __init__( self.set_context(**kwargs) # custom filters - self._env.filters["where_in"] = where_in + self._env.filters["where_in"] = WhereInMacro(database.get_dialect()) def set_context(self, **kwargs: Any) -> None: self._context.update(kwargs)
tests/unit_tests/jinja_context_test.py+7 −2 modified@@ -20,17 +20,22 @@ import pytest from pytest_mock import MockFixture +from sqlalchemy.dialects import mysql from superset.datasets.commands.exceptions import DatasetNotFoundError -from superset.jinja_context import dataset_macro, where_in +from superset.jinja_context import dataset_macro, WhereInMacro def test_where_in() -> None: """ Test the ``where_in`` Jinja2 filter. """ + where_in = WhereInMacro(mysql.dialect()) assert where_in([1, "b", 3]) == "(1, 'b', 3)" - assert where_in([1, "b", 3], '"') == '(1, "b", 3)' + assert where_in([1, "b", 3], '"') == ( + "(1, 'b', 3)\n-- WARNING: the `mark` parameter was removed from the " + "`where_in` macro for security reasons\n" + ) assert where_in(["O'Malley's"]) == "('O''Malley''s')"
34101594e284fix: DB-specific quoting in Jinja macro (#25779)
2 files changed · +38 −16
superset/jinja_context.py+31 −14 modified@@ -35,6 +35,7 @@ from jinja2 import DebugUndefined from jinja2.sandbox import SandboxedEnvironment from sqlalchemy.engine.interfaces import Dialect +from sqlalchemy.sql.expression import bindparam from sqlalchemy.types import String from typing_extensions import TypedDict @@ -407,23 +408,39 @@ def validate_template_context( return validate_context_types(context) -def where_in(values: List[Any], mark: str = "'") -> str: - """ - Given a list of values, build a parenthesis list suitable for an IN expression. +class WhereInMacro: # pylint: disable=too-few-public-methods + def __init__(self, dialect: Dialect): + self.dialect = dialect - >>> where_in([1, "b", 3]) - (1, 'b', 3) + def __call__(self, values: List[Any], mark: Optional[str] = None) -> str: + """ + Given a list of values, build a parenthesis list suitable for an IN expression. - """ + >>> from sqlalchemy.dialects import mysql + >>> where_in = WhereInMacro(dialect=mysql.dialect()) + >>> where_in([1, "Joe's", 3]) + (1, 'Joe''s', 3) - def quote(value: Any) -> str: - if isinstance(value, str): - value = value.replace(mark, mark * 2) - return f"{mark}{value}{mark}" - return str(value) + """ + binds = [bindparam(f"value_{i}", value) for i, value in enumerate(values)] + string_representations = [ + str( + bind.compile( + dialect=self.dialect, compile_kwargs={"literal_binds": True} + ) + ) + for bind in binds + ] + joined_values = ", ".join(string_representations) + result = f"({joined_values})" + + if mark: + result += ( + "\n-- WARNING: the `mark` parameter was removed from the `where_in` " + "macro for security reasons\n" + ) - joined_values = ", ".join(quote(value) for value in values) - return f"({joined_values})" + return result class BaseTemplateProcessor: @@ -459,7 +476,7 @@ def __init__( self.set_context(**kwargs) # custom filters - self._env.filters["where_in"] = where_in + self._env.filters["where_in"] = WhereInMacro(database.get_dialect()) def set_context(self, **kwargs: Any) -> None: self._context.update(kwargs)
tests/unit_tests/jinja_context_test.py+7 −2 modified@@ -20,17 +20,22 @@ import pytest from pytest_mock import MockFixture +from sqlalchemy.dialects import mysql from superset.datasets.commands.exceptions import DatasetNotFoundError -from superset.jinja_context import dataset_macro, where_in +from superset.jinja_context import dataset_macro, WhereInMacro def test_where_in() -> None: """ Test the ``where_in`` Jinja2 filter. """ + where_in = WhereInMacro(mysql.dialect()) assert where_in([1, "b", 3]) == "(1, 'b', 3)" - assert where_in([1, "b", 3], '"') == '(1, "b", 3)' + assert where_in([1, "b", 3], '"') == ( + "(1, 'b', 3)\n-- WARNING: the `mark` parameter was removed from the " + "`where_in` macro for security reasons\n" + ) assert where_in(["O'Malley's"]) == "('O''Malley''s')"
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-jfxj-xf67-x723ghsaADVISORY
- lists.apache.org/thread/1kf481bgs3451qcz6hfhobs7xvhp8n1pghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2023-49736ghsaADVISORY
- www.openwall.com/lists/oss-security/2023/12/19/2ghsaWEB
- github.com/apache/superset/commit/1d403dab9822a8cee6108669c53e53fad881c751ghsaWEB
- github.com/apache/superset/commit/34101594e284ab3acce692f41aff7759ccb4bf1dghsaWEB
- github.com/apache/superset/pull/25779ghsaWEB
News mentions
0No linked articles in our index yet.