Hi Redash team,
Found several issues, two of which are critical RCE vectors.
RestrictedPython sandbox escape (Critical)
In redash/query_runner/python.py at lines 317-319, raw getattr and setattr are assigned as the _getattr_/_setattr_ guards for RestrictedPython. This completely defeats the sandbox's security model — those guards are specifically what RestrictedPython relies on to prevent attribute access to dangerous objects.
Any user who can run Python queries can escape trivially: ().__class__.__bases__[0].__subclasses__() walks the class hierarchy to find os and call system(). Full RCE on the Redash server.
Command injection via script runner (Critical)
redash/query_runner/script.py:8-19 — when the script path is configured as *, the user's query text gets passed directly to subprocess.check_output(script, shell=True). Anything in the query box runs as a shell command on the server.
SQL injection in 4 query runners (High)
Four query runners interpolate user-controlled values into SQL without parameterization:
query_runner/__init__.py:283 — table names via % into select count(*)
query_runner/sqlite.py:45 — PRAGMA table_info("%s") with unescaped table name
query_runner/duckdb.py:90-96 — extension names via f-string into INSTALL/LOAD
query_runner/memsql_ds.py:76-93 — schema/table names via %
SSRF bypassing advocate protection (High)
query_runner/graphite.py:79-84 and several other runners (prometheus, clickhouse, elasticsearch) use raw requests.get() instead of the advocate-wrapped session. This bypasses the ENFORCE_PRIVATE_ADDRESS_BLOCK setting entirely — internal network access from any authenticated user.
CSRF disabled by default (High)
redash/settings/__init__.py:464 — ENFORCE_CSRF defaults to "false". The comment in the code acknowledges the risk but leaves it off. Any state-changing API call is vulnerable to cross-site request forgery out of the box.
The sandbox escape and command injection are the urgent ones — both give full server RCE to any user with query access.
— ProScan AppSec | proscan.one
Hi Redash team,
Found several issues, two of which are critical RCE vectors.
RestrictedPython sandbox escape (Critical)
In
redash/query_runner/python.pyat lines 317-319, rawgetattrandsetattrare assigned as the_getattr_/_setattr_guards for RestrictedPython. This completely defeats the sandbox's security model — those guards are specifically what RestrictedPython relies on to prevent attribute access to dangerous objects.Any user who can run Python queries can escape trivially:
().__class__.__bases__[0].__subclasses__()walks the class hierarchy to findosand callsystem(). Full RCE on the Redash server.Command injection via script runner (Critical)
redash/query_runner/script.py:8-19— when the script path is configured as*, the user's query text gets passed directly tosubprocess.check_output(script, shell=True). Anything in the query box runs as a shell command on the server.SQL injection in 4 query runners (High)
Four query runners interpolate user-controlled values into SQL without parameterization:
query_runner/__init__.py:283— table names via%intoselect count(*)query_runner/sqlite.py:45—PRAGMA table_info("%s")with unescaped table namequery_runner/duckdb.py:90-96— extension names via f-string intoINSTALL/LOADquery_runner/memsql_ds.py:76-93— schema/table names via%SSRF bypassing advocate protection (High)
query_runner/graphite.py:79-84and several other runners (prometheus, clickhouse, elasticsearch) use rawrequests.get()instead of the advocate-wrapped session. This bypasses theENFORCE_PRIVATE_ADDRESS_BLOCKsetting entirely — internal network access from any authenticated user.CSRF disabled by default (High)
redash/settings/__init__.py:464—ENFORCE_CSRFdefaults to"false". The comment in the code acknowledges the risk but leaves it off. Any state-changing API call is vulnerable to cross-site request forgery out of the box.The sandbox escape and command injection are the urgent ones — both give full server RCE to any user with query access.
— ProScan AppSec | proscan.one