РАЗБОР AppSec Статья

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

M
Men1 Newbie · 1 сообщений
Подписаться
1,2 тыс.
[ обложка статьи ]
Режим чтения
Аудит безопасности 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?» Иногда ответ на него звучит неприятно: «Все».
Полезно · 3

Комментарии

0