Elastasticsearch security check, or “I see your indices”

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
345
Reaction score
508
Deposit
0$
Elasticsearch often turns out to be the “heart” of logging, search, and analytics. It involves events of applications, nginks, audit trail logs, trading, payment events, user actions, debug information and sometimes what should not have gotten there at all.

That is why the outlet of Elasticsearch on 9200/tcp looks like a red lamp on the panel: if the cluster is available without authentication, the attacker can see the names of indexes, data diagrams, volumes of documents, and with the worst configuration, the content of the records.

Below we will analyze the basic check: how to look for your hosts, how to carefully look at the indices and why the data found is almost always more interesting than it seems at first glance.
1. Search for hosts
The first sign of the Elastasticsearch default is the HTTP API on the port 9200. In old or poorly configured installations, this port could be available from the outside: because of the open security group, the inflated Docker port, a temporary test stand or “let’s then close”.

To check their own infrastructure, several approaches are usually used:
• Cloud inventory: security groups, firewall rules, load balancers;
• internal scanning of permitted ranges;
• masscan or nmap over its own networks;
• external search services like Shodan, Censys, FOFA, ZoomEye and similar platforms to see what has already been indexed outside.
Important: Masscan very fast and easily transformed from an audit tool into the source of the incident. Use it only through your ranges and speed control.
An example of a team for a laboratory network:
masscan 192.168.56.0/24 -p9200 --rate 1000
Where 192.168.56.06.40/24 – your study range, such as the VirtualBox/VMware/Proxmox network.
Python: Elastasticsearch search on the training network
Below is an example of a simple script to check your addresses. He does nothing “magical”: trying to connect to 9200, sends an HTTP request and sees if the answer is similar to Elasticsearch.
Python:
#!/usr/bin/env python3

import ipaddress

import socket

import json

from urllib.request import Request, urlopen

from urllib.error import URLError, HTTPError

NETWORK = "192.168.56.0/24"

PORT = 9200

TIMEOUT = 1.5

def is_port_open(host: str, port: int) -> bool:

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:

sock.settimeout(TIMEOUT)

return sock.connect_ex((host, port)) == 0

def check_elasticsearch(host: str):

url = f"http://{host}:{PORT}/"

request = Request(url, headers={"User-Agent": "internal-es-audit/1.0"})

try:

with urlopen(request, timeout=TIMEOUT) as response:

body = response.read(4096).decode("utf-8", errors="replace")

data = json.loads(body)

if "cluster_name" in data or "tagline" in data:

return {

"host": host,

"url": url,

"cluster_name": data.get("cluster_name"),

"version": data.get("version", {}).get("number"),

"tagline": data.get("tagline"),

}

except (URLError, HTTPError, TimeoutError, json.JSONDecodeError):

return None

return None

def main():

network = ipaddress.ip_network(NETWORK, strict=False)

for ip in network.hosts():

host = str(ip)

if not is_port_open(host, PORT):

continue

result = check_elasticsearch(host)

if result:

print(json.dumps(result, ensure_ascii=False, indent=2))

else:



if __name__ == "__main__":

main()

Example of expected conclusion:
{
"host": "192.168.56.12",
"url": "http://192.168.56.12:9200/",
"cluster_name": "docker-cluster",
"version": "8.12.0",
"Tagline": "You Know, for Search"
}
In real verification, it is useful to save results in CSV/JSON, compare them with service owners and immediately check why the port was available.
2. Viewing indices
If Elasticsearch responds, the next safe step in the audit is not to read the documents, but to see the meta-information: the list of indices, their size, status, number of documents. This is often enough to understand the risk.

For manual testing in the laboratory you can use:
curl http://192.168.56.12:9200/_cat/indices?v
If the authentication is included:
curl -u elastic:changeme http://192.168.56.12:9200/_cat/indices?v

Pay attention to the names of the indexes. Even without reading documents, they can tell a lot:
Users-2026.07
Payments-prod
nginx-access-logs
auth-events
crm-contacts
kibana_sample_data_ecommerce
Names like Payments-prod, Users, Tokens, Sessions, auth, crm, orders, passport, Support-tickets already tell the auditor that the system requires immediate access verification.
Python: Safe Viewing of Index List
This script only requests a list of indices through _cat/indices? format=json. He does not upload documents and does not turn to _search.
Python:
#!/usr/bin/env python3

import argparse

import json

from getpass import getpass

from urllib.request import Request, urlopen

from urllib.error import HTTPError, URLError

import base64

def build_auth_header(username: str | None, password: str | None):

if not username:

return {}

token = f"{username}:{password}".encode("utf-8")

encoded = base64.b64encode(token).decode("ascii")

return {"Authorization": f"Basic {encoded}"}

def fetch_indices(base_url: str, username: str | None, password: str | None):

url = base_url.rstrip("/") + "/_cat/indices?format=json&bytes=mb"

headers = {

"User-Agent": "internal-es-index-audit/1.0",

**build_auth_header(username, password),

}

request = Request(url, headers=headers)

with urlopen(request, timeout=5) as response:

return json.loads(response.read().decode("utf-8"))

def main():

parser = argparse.ArgumentParser(

description="List Elasticsearch indices for authorized security checks."

)

parser.add_argument("url", help="Example: http://192.168.56.12:9200")

parser.add_argument("-u", "--username", help="Elasticsearch username")

args = parser.parse_args()

password = getpass("Password: ") if args.username else None

try:

indices = fetch_indices(args.url, args.username, password)

print(f"{'health':<8} {'status':<8} {'index':<40} {'docs.count':>12} {'store.size':>12}")

print("-" * 88)

for item in indices:

print(

f"{item.get('health', ''):<8} "

f"{item.get('status', ''):<8} "

f"{item.get('index', ''):<40} "

f"{item.get('docs.count', ''):>12} "

f"{item.get('store.size', ''):>12}"

)

except HTTPError as error:

print(f"HTTP error: {error.code} {error.reason}")

except URLError as error:

print(f"Connection error: {error.reason}")

except json.JSONDecodeError:

print("Response was not valid JSON")

if __name__ == "__main__":

main()

Running without authentication in the laboratory:
python3 list_es_indices.py http://192.168.56.12:9200
Launch with user:
python3 list_es_indices.py http://192.168.56.12:9200 -u elastic
3. What happens in indices
The most unpleasant thing in the open Elasticsearch is not the fact of an open port. It is not pleasant that Elasticsearch is rarely empty. Usually it exists because it has been paperlessly put there for a long time and carefully.

Indices may be found:
Type of data
Examples
Logs of Applications
stack traces, debug messages, payload requests
Accounting data
email, username, sometimes passwords or tokens
Personal data
Name, phone numbers, addresses, documents
Payment events
orders, amounts, statuses, masked/unmask card data
Sessions and Tokens
JWT, refreshing tokens, API keys
Logs of infrastructure
IP addresses, user-agent, internal hostnames
CRM Data
customers, transactions, appeals in support of
Search events
requests of users, behavioral analytics
Service indices
.kibana`, .security. monitoring, ingest pipelines

Debug logics are especially dangerous. The developer could temporarily block the body of the HTTP request, authorization headers or the response of the external API. Then the service went on sale, logging remained, and Elasticsearch became an archive of small compromises.
A Few Major Public Stories
Open sources describe many incidents where the problem was not in the “elasticsearch vulnerability”, but in an unprotected configuration: the cluster was available from the Internet without a password, with an open API or with erroneous firewall/security group rules. Below are a few indicative cases. The numbers in such publications usually mean the number of records, and not necessarily the number of unique people.
Incident
Scale
What was inside
Why it is important
CAM4, 2020
10.88 billion records
PII, email, IP addresses, payment logs, chats, tokens, has passwordhes
One of the largest publicly described cases with misconfigured Elasticsearch; the sensitive context increased the risk of blackmail and phishing.
Advanced Info Service / AIS, 2020
8.3 billion records
DNS query logs and NetFlow-logies of Thai operator users
Even “technical” network logs can reveal user behavioral patterns.
Keepnet Labs, 2020
more than 5 billion records
Collection of data from past leaks: email, passwords in different formats, sources and dates of leakage
An ironic example: the database of past incidents itself was discovered during the Elastasticsearch migration.
Exactis, 2018
about 340 million records
Marketing profiles: names, addresses, phones, emails, interests, demography
Even without card numbers and SSN, such detail is convenient for social engineering.
Microsoft Support, 2019/2020
about 250 million records
Logs of appeals in support: email, IP, geography, numbers, application descriptions, internal notes
Support often stores a context that helps fraudsters look convincing.
Decathlon, 2020
123 million records
Customer and employee data mentioned in publications about the unprotected Elasticsearch
A good example of what risk concerns not only IT companies: retail, sports, logistics and HR also often add data to search indices.
Chinese Aggregate Dataset, 2025
Approximately 4 billion records
PII, Financial Data, WeChat/Alipay-related Collections, Address and Banking Sets
Shows the scale of risk when Elasticsearch is used as a centralized point of aggregation of heterogeneous data.
 
Top Bottom