Статья Проверка безопасности Elasticsearch, или «я вижу ваши индексы»

Аудит безопасности Elasticsearch: открытый порт 9200 и утечки данных

Материал предназначен только для обучения и легальной проверки собственных систем: учебных виртуальных машин, CTF-лабораторий, тестовых стендов и инфраструктуры, на аудит которой у вас есть письменное разрешение. Сканирование чужих сетей, просмотр чужих индексов, сбор данных и попытки доступа к не принадлежащим вам кластерам могут нарушать законодательство и правила провайдеров. Автор не несет ответственности за незаконное применение описанных подходов.
Elasticsearch часто оказывается «сердцем» логирования, поиска и аналитики. В него летят события приложений, логи nginx, audit trail, трейсинг, платежные события, пользовательские действия, debug-информация и иногда то, что вообще не должно было туда попасть.

Именно поэтому открытый наружу Elasticsearch на
9200/tcp выглядит для защитника как красная лампа на панели: если кластер доступен без аутентификации, злоумышленник может увидеть названия индексов, схемы данных, объемы документов, а при худшей конфигурации еще и содержимое записей.

Ниже разберем базовую проверку: как искать свои хосты, как аккуратно посмотреть индексы и почему найденные данные почти всегда интереснее, чем кажется на первый взгляд.

1. Поиск хостов

Первый признак Elasticsearch по умолчанию — HTTP API на порту 9200. В старых или плохо настроенных инсталляциях этот порт мог оказаться доступен извне: из-за открытой security group, проброшенного Docker-порта, временного тестового стенда или «да потом закроем».

Для проверки собственной инфраструктуры обычно используют несколько подходов:

  • инвентаризация облака: security groups, firewall rules, load balancers;
  • внутреннее сканирование разрешенных диапазонов;
  • masscan или nmap по своим сетям;
  • внешние поисковые сервисы вроде Shodan, Censys, FOFA, ZoomEye и похожих платформ, чтобы увидеть, что уже проиндексировано снаружи.
Важно: masscan очень быстрый и легко превращается из инструмента аудита в источник инцидента. Используйте его только по своим диапазонам и с контролем скорости.
Пример команды для лабораторной сети:

masscan 192.168.56.0/24 -p9200 --rate 1000
Где 192.168.56.0/24 — ваш учебный диапазон, например сеть VirtualBox/VMware/Proxmox.

Python: поиск Elasticsearch в учебной сети

Ниже пример простого скрипта для проверки своих адресов. Он не делает ничего «магического»: пытается подключиться к 9200, отправляет HTTP-запрос и смотрит, похож ли ответ на 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:

            print(f"[?] {host}:{PORT} открыт, но ответ не похож на Elasticsearch")

if __name__ == "__main__":

    main()

Пример ожидаемого вывода:
{
"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"
}
В реальной проверке полезно сохранять результаты в CSV/JSON, сопоставлять их с владельцами сервисов и сразу проверять, почему порт оказался доступен.

2. Просмотр индексов

Если Elasticsearch отвечает, следующий безопасный шаг в рамках аудита — не читать документы, а посмотреть метаинформацию: список индексов, их размер, статус, количество документов. Этого часто достаточно, чтобы понять риск.

Для ручной проверки в лаборатории можно использовать:

curl http://192.168.56.12:9200/_cat/indices?v
Если включена аутентификация:
curl -u elastic:changeme http://192.168.56.12:9200/_cat/indices?v

Обратите внимание на названия индексов. Даже без чтения документов они могут многое рассказать:
users-2026.07
payments-prod
nginx-access-logs
auth-events
crm-contacts
kibana_sample_data_ecommerce
Названия вроде payments-prod, users, tokens, sessions, auth, crm, orders, passport, support-tickets уже говорят аудитору, что система требует немедленной проверки доступа.

Python: безопасный просмотр списка индексов

Этот скрипт запрашивает только список индексов через _cat/indices?format=json. Он не выгружает документы и не обращается к _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()

Запуск без аутентификации в лаборатории:
python3 list_es_indices.py http://192.168.56.12:9200
Запуск с пользователем:
python3 list_es_indices.py http://192.168.56.12:9200 -u elastic

Для отчета по аудиту стоит фиксировать:

Что проверятьЗачем
Доступность 9200/tcp извнеПонять, виден ли Elasticsearch за пределами доверенной сети
Наличие аутентификацииПроверить, может ли посторонний получить метаданные
Названия индексовБыстро оценить типы потенциально чувствительных данных
Размеры и количество документовОценить масштаб риска
Версию ElasticsearchПонять, есть ли устаревшие компоненты и настройки

3. Что бывает в индексах

Самое неприятное в открытом Elasticsearch — это не сам факт открытого порта. Неприятно то, что Elasticsearch редко бывает пустым. Обычно он существует потому, что туда уже долго и заботливо складывали данные.

В индексах могут встречаться:


Тип данныхПримеры
Логи приложенийstack traces, debug-сообщения, payload запросов
Учетные данныеemail, username, иногда пароли или токены
Персональные данныеФИО, телефоны, адреса, документы
Платежные событиязаказы, суммы, статусы, masked/unmasked card data
Сессии и токеныJWT, refresh tokens, API keys
Логи инфраструктурыIP-адреса, user-agent, internal hostnames
Данные CRMклиенты, сделки, обращения в поддержку
Поисковые событиязапросы пользователей, поведенческая аналитика
Служебные индексы.kibana`, .security, мониторинг, ingest pipelines

Особенно опасны debug-логи. Разработчик мог временно залогировать тело HTTP-запроса, заголовки авторизации или ответ внешнего API. Потом сервис ушел в прод, логирование осталось, а Elasticsearch стал архивом маленьких компромиссов.

Несколько крупных публичных историй

В открытых источниках описано много инцидентов, где проблема была не в «уязвимости Elasticsearch», а в незащищенной конфигурации: кластер был доступен из интернета без пароля, с открытым API или с ошибочными правилами firewall/security group. Ниже — несколько показательных случаев. Числа в таких публикациях обычно означают количество записей, а не обязательно количество уникальных людей.

ИнцидентМасштабЧто было внутриПочему это важно
CAM4, 202010,88 млрд записейPII, email, IP-адреса, платежные логи, чаты, токены, password hashesОдин из самых крупных публично описанных случаев с misconfigured Elasticsearch; чувствительный контекст усиливал риск шантажа и фишинга.
Advanced Info Service / AIS, 20208,3 млрд записейDNS query logs и NetFlow-логи пользователей тайского оператораДаже «технические» сетевые логи могут раскрывать поведенческие паттерны пользователей.
Keepnet Labs, 2020более 5 млрд записейКоллекция данных из прошлых утечек: email, пароли в разных форматах, источники и даты утечекИроничный пример: база с данными о прошлых инцидентах сама оказалась открыта во время миграции Elasticsearch.
Exactis, 2018около 340 млн записейМаркетинговые профили: имена, адреса, телефоны, email, интересы, демографияДаже без номеров карт и SSN такая детализация удобна для социальной инженерии.
Microsoft Support, 2019/2020около 250 млн записейЛоги обращений в поддержку: email, IP, география, case numbers, описания заявок, внутренние пометкиПоддержка часто хранит контекст, который помогает мошенникам выглядеть убедительно.
Decathlon, 2020123 млн записейДанные клиентов и сотрудников, упоминавшиеся в публикациях о незащищенном ElasticsearchХороший пример того, что риск касается не только IT-компаний: ритейл, спорт, логистика и HR тоже часто складывают данные в поисковые индексы.
Китайский агрегированный датасет, 2025около 4 млрд записейPII, финансовые данные, WeChat/Alipay-related коллекции, адресные и банковские наборыПоказывает масштаб риска, когда Elasticsearch используется как централизованная точка агрегации разнородных данных.

Источники по примерам: WIRED о CAM4 (An Adult Cam Site Exposed 10.88 Billion Records), Security Magazine о крупнейших инцидентах 2020 года (The top 10 data breaches of 2020), WIRED об Exactis (Marketing Firm Leaked Database With 340 Million Records), PC Gamer о Microsoft Support (https://www.pcgamer.com/microsoft-d...abase-error-that-exposed-250-million-records/), TechRadar о причинах утечек Elasticsearch (https://www.techradar.com/news/what-is-elasticsearch-and-why-is-it-involved-in-so-many-data-leaks), TechRadar о китайском агрегированном датасете (https://www.techradar.com/pro/secur...rgest-breach-ever-heres-what-you-need-to-know).

Есть и отдельная категория атак не на чтение, а на уничтожение данных. В 2020 году прошла волна так называемых Meow-атак: открытые базы Elasticsearch, MongoDB и другие хранилища массово удалялись или портились автоматизированными скриптами. По данным SearchSecurity/TechTarget, атаки затронули более тысячи открытых баз, а позже счет шел уже на тысячи инцидентов; Elastic тогда указывала, что пострадали кластеры без включенных security-функций. Источник: TechTarget ('Meow' attacks wipe more than 1,000 exposed databases | TechTarget).

Похожая мораль встречается и в современных утечках: открытый поисковый кластер может стать не просто «окном для просмотра», а полноценной точкой массовой компрометации. В новостях регулярно появляются случаи, где через незащищенный Elasticsearch находили логины, пароли, документы, платежные данные или внутренние токены.

Почему названия индексов уже важны

Даже если аудитор не читает документы, одни только имена индексов могут подсказать, где пожар:
prod-users
prod-orders
payment-events
auth-logs
support-messages
passport-verification
mobile-app-errors
Такой список уже отвечает на вопросы:
  • какие бизнес-процессы завязаны на кластер;
  • есть ли там персональные или платежные данные;
  • разделены ли dev/stage/prod;
  • кто владелец данных;
  • насколько срочно нужно закрывать доступ.

Что делать после обнаружения

Если в своей инфраструктуре найден Elasticsearch, доступный без должной защиты, порядок действий должен быть быстрым и спокойным:
  1. Ограничить сетевой доступ: firewall, security groups, VPN, private subnet.
  2. Включить аутентификацию и TLS.
  3. Проверить роли и права пользователей.
  4. Изучить логи доступа к Elasticsearch и reverse proxy.
  5. Оценить, какие индексы могли быть доступны.
  6. Проверить наличие чувствительных данных в логах.
  7. При необходимости запустить процедуру incident response.
  8. Добавить постоянный контроль: cloud posture, external attack surface monitoring, регулярные сканы своих диапазонов.

Финальная мысль

Elasticsearch сам по себе не «дырявый». Проблема почти всегда в окружении: порт случайно открыт наружу, аутентификация выключена, тестовый контейнер стал продом, временное правило firewall пережило релиз.
Хорошая проверка безопасности начинается не с громких эксплойтов, а с простого вопроса: «Кто вообще видит мой
9200?» Иногда ответ на него звучит неприятно: «Все».
 
  • Огонь
Реакции: Сергей Попов
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab