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.
# 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
# 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
controller.access.control.factory.class with BasicAuthAccessControlFactory; Pinot supports basic auth and JWT authentication for both Controller and Broker APIspinot.broker.timeout.ms and query concurrency limits to prevent resource exhaustion from expensive long-running queries| Security Test | Method | Risk |
|---|---|---|
| Controller and Broker APIs without authentication | GET /tables and POST /query/sql without credentials — 200 with data means no auth configured | Critical |
| All table data queryable via SQL | SELECT * FROM table LIMIT N — exports analytics data without authorization | Critical |
| SSRF via ingestion spec URL | Submit ingestion job with inputDirURI pointing to http://169.254.169.254/ — triggers outbound request from Pinot node | High |
| Table deletion without authorization | DELETE /tables/{tableName} — destroys production analytics tables without authentication | High |
| Schema enumeration via Controller API | GET /tables/{tableName}/schema — reveals all column names and data types for every table | Medium |
| ZooKeeper accessible without authentication | zkCli connect to ZK port 2181 — reads all Pinot routing metadata and segment assignments | High |
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