Перейти к содержанию

181 874 встречи нараспашку: разбор дыры в tl;dv и чек-лист по Firestore Rules

Опубликовано
  • Админы
181 874 встречи в открытом доступе
181 874 встречи в открытом доступе

Есть класс уязвимостей, у которого нет ни CVE, ни эксплойта, ни патча. Просто где-то в проекте не написали пять строк. История с сервисом расшифровки встреч tl;dv — образцовый экземпляр, и заодно повод посмотреть на собственный Firebase-проект.

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

28 января: отчёт уходит в компанию

Исследователь под ником BobDaHacker пишет сооснователю tl;dv Рафаэлю Альштадту в LinkedIn и, отдельно, техническому директору. Суть отчёта: коллекция meetings в Firestore отдаётся любому аутентифицированному пользователю сервиса, без разделения по клиентам.

29–30 января Альштадт отвечает, что CTO свяжется немедленно. CTO не связывается.

Что именно было открыто

По публикации исследователя, доступными оказались:

  • 181 874 записи о встречах;
  • 84 312 уникальных пользователей;
  • 35 003 почтовых домена, включая государственные из 23 стран;
  • около 1 000 встреч в статусе recording — то есть идущих прямо сейчас;
  • более 1 000 публично доступных записей, где вскрылись 715 адресов приглашённых из 228 доменов.

В полях записи лежали почта создателя встречи, платформа конференции, метки времени, статус записи и — это ключевое — идентификатор конференции. То есть строка, по которой в комнату Google Meet или Teams можно просто зайти. Исследователь это проверил: подключился к двум чужим звонкам, включая совещание Министерства образования Малайзии на 157+ участников.

⛔ Так делать нельзя

Отдельно стоит зафиксировать сам метод проверки. Подключение к чужому идущему звонку — это уже не «чтение открытых данных», а несанкционированный доступ, независимо от того, насколько дырявой была система. Этичный research останавливается на доказательстве возможности, а не на её реализации. Не повторяйте это ни на чьей инфраструктуре, включая свою SaaS-подписку.

Как это работало технически

Механика на удивление скучная, и в этом всё дело.

Клиент tl;dv логинится, получает JWT, обменивает его на Firebase-токен через gw.tldv.io/v1/users/firebase/token — и дальше ходит напрямую в Firestore, в проект lmi-store. Никакого промежуточного API, который проверял бы «а твоя ли это встреча», в этой цепочке нет вообще.

Как клиент ходит в Firestore напрямую
Как клиент ходит в Firestore напрямую

Именно в этом главная ловушка Firebase-архитектуры. В классической трёхзвенке между клиентом и базой стоит ваш сервер, и «забыть проверить tenant_id» надо в конкретном обработчике. В Firebase сервера посередине нет: Security Rules — это и есть весь бэкенд по части авторизации. Пропущенное правило на одной коллекции означает, что коллекция открыта целиком.

❗ Главное

request.auth != null — это проверка «человек зарегистрировался», а не «человек имеет право на эту запись». В продукте, где регистрация открыта всем желающим, первое условие не значит ровно ничего.

14 февраля — 22 июля: тишина

Дальше в хронологии почти нет событий, и это самая неприятная её часть.

Дата Что произошло
28 января 2026 первый отчёт, LinkedIn + письмо CTO
29–30 января обещание, что CTO ответит немедленно
14–19 февраля повторные напоминания, ответа нет
6 марта исследователь перепроверяет — дыра на месте
22 июля спустя почти полгода дыра всё ещё на месте
4 августа материал выходит в Dark Reading, реакции по-прежнему нет

На странице безопасности tl;dv заявлено время реакции на сообщения об уязвимостях — 24 часа. Публичного заявления компании по существу на момент подготовки этого текста найти не удалось.

⚠️ Осторожно

Вторая находка того же исследования: внутреннее приложение с прогнозами на чемпионат мира на поддомене worldcup.tldv.io отдавало данные сотрудников через эндпоинт /api/entities/* вообще без авторизации. Конкретное число раскрытых записей в пересказах расходится, поэтому цифру здесь не привожу — важен сам паттерн: внутренняя игрушка на том же домене, что и продакшен, живёт по правилам «да кто её найдёт».

Что проверить в своём проекте сегодня

Если у вас есть хоть что-то на Firebase — вот минимальный прогон.

1. Найдите коллекции без правил. Правило по умолчанию в «тестовом режиме» выглядит так и живёт ровно 30 дней, после чего часто заменяется на что-нибудь «временное»:

allow read, write: if request.time < timestamp.date(2026, 9, 1);

Ищите в firestore.rules любые if request.auth != null без дополнительных условий — это и есть уязвимая форма.

2. Привяжите доступ к владельцу, а не к факту логина.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    match /meetings/{meetingId} {
      allow get: if request.auth != null
                 && resource.data.ownerId == request.auth.uid;

      allow list: if request.auth != null
                  && request.query.limit <= 100;

      allow create: if request.auth != null
                    && request.resource.data.ownerId == request.auth.uid;

      allow update, delete: if request.auth != null
                            && resource.data.ownerId == request.auth.uid;
    }
  }
}

3. Помните, что правила — не фильтр. Документация Firebase формулирует это прямо: «Вы не можете написать запрос на все документы коллекции и ожидать, что Cloud Firestore вернёт только те документы, к которым у клиента есть доступ». Если правило требует совпадения ownerId, то и сам клиентский запрос обязан содержать соответствующий where — иначе он просто упадёт. Многих это удивляет, и в этот момент рождается соблазн «пока ослаблю правило, потом верну». Не возвращают.

✅ Как правильно

Регламент, который реально работает:

  • правила лежат в репозитории и катятся через firebase deploy --only firestore:rules, а не правятся в консоли руками;
  • на каждую коллекцию есть тест в firebase emulators:start — проверяющий не только «свой видит своё», но и «чужой не видит чужого»;
  • любой поддомен продакшен-зоны считается продакшеном, включая внутренние поделки;
  • в форме приёма сообщений об уязвимостях указан адрес, который читает не только маркетинг.

🔒 Безопасность

И потребительский вывод, который касается всех: ИИ-ноутейкер в вашем календаре — это внешняя компания, у которой лежат аудио, транскрипты и список участников всех ваших встреч. Модель угроз тут не «сломают шифрование», а «в одной коллекции забудут одну строчку». Если через встречу проходят персональные данные, коммерческая тайна или NDA, решение о подключении такого сервиса — это решение об обработке данных третьей стороной, со всеми вытекающими вопросами к договору и юрисдикции.

💬 Вопрос к сообществу

Кто разбирал свои Firestore Rules позже, чем «когда написал первую версию приложения»? И отдельный вопрос к тем, кто отправлял отчёты об уязвимостях в SaaS: сколько раз вам вообще отвечали?

Источники

Featured Replies

No posts to show

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

Последние посетители 0

  • Ни одного зарегистрированного пользователя не просматривает данную страницу
Яндекс.Метрика

Account

Navigation

Поиск

Поиск

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.