Metabase 10.0 из 10: админ без пароля — что сделать с самохостом сегодня

Если у вас в хоумлабе или на проде крутится самохостовый Metabase — отложите чтение остального и посмотрите версию прямо сейчас. 6 августа Metabase опубликовала advisory о критической уязвимости с оценкой CVSS 10.0, которую уже эксплуатировали в реальных атаках. Пароль не нужен, взаимодействие пользователя не нужно, результат — права администратора инстанса и доступ к сохранённым кредам всех подключённых баз.

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

Warning:

Уязвимые ветки: v58.0–v58.22, v59.0–v59.19, v60.0–v60.15, v61.0–v61.9, v62.0–v62.7, v63.0–v63.2.

Исправленные версии: 0.58.24, 0.59.21, 0.60.17, 0.61.11, 0.62.9, 0.63.5 (для Enterprise/Pro — те же номера с префиксом 1.).

Metabase Cloud пропатчен вендором. Самохост — только руками.

Что именно сломано

Уязвимость получила идентификатор GHSA-vwf4-m7j8-wcjf. CVE на момент публикации не назначен — это важно, потому что если вы сканируете инфраструктуру только по CVE-фидам, эта дыра у вас не подсветится.

Точка входа — эндпоинт POST /api/session/reset_password, тот самый, что обслуживает форму «забыли пароль». Через него неаутентифицированный клиент может подставить произвольный SQL в служебную базу самого Metabase (application database — та, где живут пользователи, дашборды, права и настройки подключений). Формулировка advisory: инъекция «can give them administrator access to the instance».

Вектор в терминах CVSS v3.1: сеть, низкая сложность, привилегии не требуются, взаимодействие пользователя не требуется, Scope: Changed — отсюда и ровные 10.0. Такую оценку в реальной жизни видишь нечасто.

Почему BI-система — худшее место для дыры такого класса

Тут стоит остановиться, потому что многие недооценивают масштаб. Metabase по своей природе — это шкаф с ключами. Чтобы строить дашборды, он хранит учётные данные подключённых источников: production-Postgres, реплика MySQL, ClickHouse, S3-подобные хранилища, что вы туда завели. Получив админа в Metabase, атакующий:

  • читает и выгружает данные через штатный SQL-редактор — с точки зрения вашей БД это легитимный запрос от легитимного пользователя;
  • добирается до сохранённых кредов подключений и может пойти в базы напрямую, минуя Metabase;
  • меняет настройки, заводит себе API-ключи и остаётся в системе после того, как вы обновитесь.
Important:

Компрометация Metabase — это почти всегда компрометация всего, к чему Metabase подключён. Обновление закрывает вход, но не выгоняет того, кто уже внутри. Поэтому «обновился и забыл» здесь не работает.

Шаг 1. Узнать свою версию (30 секунд)

Metabase отдаёт версию на публичном эндпоинте — авторизация не требуется:

curl -s http://localhost:3000/api/session/properties | jq -r '.version.tag'
# → "v0.62.7"

Если jq нет:

curl -s http://localhost:3000/api/session/properties | grep -o '"tag":"[^"]*"' | head -1

Для Docker-развёртывания полезно посмотреть, какой образ реально запущен, — тег latest любит расходиться с тем, что вы думаете:

docker inspect --format '{{.Config.Image}} {{.Image}}' metabase
Note:

Про нумерацию. У Metabase две линейки одного и того же кода: 0.x — открытая версия (OSS), 1.x — Enterprise/Pro. То есть 0.63.5 и 1.63.5 — это одна и та же сборка с разным набором лицензионных фич. В advisory ветки называют без префикса («v63»), в тегах Docker-образов префикс есть. Не запутайтесь при сравнении.

Шаг 2. Обновиться — или закрыть эндпоинт руками

Правильный путь — поднять версию до исправленной в своей ветке. Для Docker это смена тега на конкретный номер (не на latest) и пересоздание контейнера:

services:
  metabase:
    image: metabase/metabase:v0.63.5   # или v0.62.9, v0.61.11, v0.60.17, v0.59.21, v0.58.24

Если обновление прямо сейчас невозможно — окно обслуживания, зависимости, миграция схемы, — advisory прямо разрешает временный обход: закрыть /api/session/reset_password на реверс-прокси. Функционально вы теряете только самообслуживаемое восстановление пароля.

nginx:

location = /api/session/reset_password {
    return 404;
}

Caddy:

@reset path /api/session/reset_password
respond @reset 404

Traefik (метка на сервисе, middleware по пути):

- "traefik.http.middlewares.mb-block.replacepath.path=/blocked"
- "traefik.http.routers.mb-block.rule=Host(`bi.example.com`) && Path(`/api/session/reset_password`)"
- "traefik.http.routers.mb-block.middlewares=mb-block"
Error:

Чего делать нельзя: считать, что вас спасёт «Metabase же у меня за VPN / в приватной сети». Проверьте это утверждение, а не примите на веру. Типовая ловушка самохостера — контейнер, опубликованный как 3000:3000 на хосте с белым IP, при том что в UFW «всё закрыто»: Docker пишет правила в DOCKER-USER/nat и обходит пользовательские цепочки INPUT. На форуме про это есть отдельный разбор — UFW-Docker: как закрыть порты Docker-контейнеров.

Шаг 3. Проверить логи: приходили ли к вам

Metabase опубликовала внятный индикатор компрометации. Признак успешной атаки — связка из двух запросов с одного адреса:

  1. POST /api/session/reset_password → ответ 400
  2. следом GET /api/user/current → ответ 200

Второй запрос с кодом 200 означает, что у клиента появилась валидная сессия — то есть попытка сработала. Одиночный 400 на первом эндпоинте сам по себе ещё ни о чём не говорит: так же выглядит обычная опечатка в форме восстановления.

Грубый поиск по access-логу nginx:

grep -E 'POST /api/session/reset_password HTTP/[0-9.]+" 400' /var/log/nginx/access.log

Чуть аккуратнее — вытащить IP, у которых есть обе половины связки:

awk '$0 ~ /reset_password/ && $0 ~ / 400 / {bad[$1]=1}
     $0 ~ /\/api\/user\/current/ && $0 ~ / 200 / && bad[$1] {print $1}' \
     /var/log/nginx/access.log | sort -u

Скрипт наивный (он не сверяет порядок и время), поэтому воспринимайте его как фильтр первого приближения: получили список адресов — идите смотреть их сырые строки глазами.

Info:

Если Metabase у вас опубликован без реверс-прокси, ищите те же обращения во внутреннем журнале самого приложения (docker logs metabase) и в аудит-логе активности. Помните про ретенцию: если логи живут неделю, а атака была 3 августа — вы можете смотреть уже в пустоту. Отсутствие следов ≠ отсутствие взлома.

Шаг 4. Если версия была уязвимой — считайте, что вас достали

Это неприятный, но правильный дефолт: инстанс, торчавший наружу уязвимой версией с 3 по 7 августа, надо разбирать так, как будто в нём побывали. Порядок из advisory, плюс здравый смысл:

Success:

Чек-лист после обновления

  1. Убить все сессии — очистить таблицу core_session в служебной БД Metabase. Для внешней Postgres/MySQL: DELETE FROM core_session;. Для встроенной H2 (дефолт «поиграться») отдельного клиента у вас, скорее всего, нет — проще пересоздать инстанс с нуля.
  2. Проверить API-ключи — Admin → Settings → Authentication → API keys. Всё, чего не заводили вы, — удалить.
  3. Пересмотреть список администраторов — новые аккаунты, внезапно повышенные роли, изменённые адреса почты у существующих админов.
  4. Ротировать пароли всех подключённых БД — не в Metabase, а на стороне самих баз. Это ключевой пункт: без него обновление бессмысленно.
  5. Посмотреть логи хранилищ, а не только Metabase: нетипичные SELECT * по большим таблицам, выгрузки, обращения с чужих адресов.
  6. Пересмотреть права сервисной учётки, под которой Metabase ходит в базы. Read-only и ограниченный набор схем — минимум, который стоило сделать ещё вчера.

Кого уже задело

Публично о доступе к своим данным через этот вектор сообщили как минимум производитель ноутбуков Framework и сервис форм Tally. Framework описывает утёкший набор как имена, адреса электронной почты, IP входа, платёжные и адресные данные, телефоны и названия компаний; платёжные реквизиты, по заявлению компании, не пострадали, так как обрабатываются на стороне Stripe. Обе компании использовали не свой самохост, а облачный Metabase — то есть под удар попали и те, кто вообще не занимался эксплуатацией BI-системы.

Отдельно стоит вчитаться в формулировку из ответа Framework: компания пишет, что теперь «оценивает объём и глубину данных, передаваемых в BI-платформы, и сужает их доступ до колонок, необходимых для анализа». Это, по сути, признание типовой ошибки — в аналитику по умолчанию заливают всё подряд, потому что так проще, а разграничение колонок откладывают на потом.

Security:

Полезное упражнение на вечер, не связанное с этой конкретной дырой: откройте свои подключения в Metabase (или Superset, Redash, Grafana — неважно) и честно ответьте, зачем аналитическому контуру доступ к таблице с телефонами, адресами и хешами паролей. В 90 % хоумлаб- и небольших продакшен-инсталляций ответ — «просто дали доступ ко всей базе, чтобы не возиться». Это ровно та переменная, которая превращает «взломали дашборд» в «утекла база клиентов».

Что здесь непроверенного

Честно обозначу границы. Технические детали эксплойта (как именно формируется полезная нагрузка) вендор не раскрывает, и это правильно — публичный PoC сейчас только ускорил бы массовое сканирование. Оценок числа торчащих в интернет уязвимых инстансов в официальных материалах нет; цифры, которые начнут ходить по новостям в ближайшие дни, до появления данных Shadowserver или Censys стоит считать оценочными. Атрибуции атакующих тоже нет — кто это был, публично не установлено.

Источники

Question:

А как у вас разграничен доступ аналитики к боевым базам — отдельная read-only роль с явным списком схем, реплика или всё-таки «дали тот же пароль, что и приложению»? И держите ли вы BI вообще открытым наружу, или он живёт только за VPN?