Ответ разработчика на статью про «бизнес-схему»

от автора

4 мая на Хабре вышел материал с громким заголовком про 180 тысяч MAU, детей, «филькину грамоту» и найденную бизнес-схему. Я один из разработчиков данного сервиса и дам прямой технический ответ на исходную публикацию.

Содержание


Коротко: где в публикации ломается логика

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

IDOR и публичные анкеты

В исходной статье: «There is no auth».

Через несколько абзацев в той же статье появляется уточнение: запросу нужна валидная PHPSESSID. Валидная сессия и отсутствие аутентификации не могут одновременно описывать один запрос.

Он противоречит сам себе. Сначала он утверждает, что для доступа к анкете авторизация не нужна, а затем пишет, что запрос требует валидной сессии. Получается, без авторизации открыть анкету нельзя — она все-таки необходима. Странная логика.

IDOR возникает не потому, что в URL есть число. Уязвимость появляется, когда сервер отдает объект пользователю, которому этот объект по правилам продукта не положен. Поэтому перед ярлыком IDOR надо ответить на простой вопрос: какие поля конкретный участник имеет право увидеть?

Mimolet является публичным сервисом общения и знакомств. Пользователь создает анкету именно для того, чтобы другие участники увидели имя, возраст, описание, интересы и выбранные медиа. Открытие публичной анкеты по прямому запросу не превращает ее поля в секретные.

Если profile_view.php обходил блокировку, скрытие профиля или другой серверный запрет, это был бы конкретный дефект объектной авторизации. Его и следовало доказать отдельно. Вместо этого в статье уязвимостью объявляется сам факт просмотра публичной анкеты вне одного конкретного экрана.

Что проверяется сейчас:

  • GET /api/v1/users/{id} без bearer-токена возвращает 401;

  • старый profile_view.php больше не обслуживает продукт и возвращает 404;

  • точечные просмотры чужих профилей имеют отдельный rate limit и суточный бюджет разных анкет.

Правильный вопрос звучит не «есть ли ID в запросе?», а «получил ли пользователь данные, которые ему запрещено получать?». В исходной статье этот ключевой шаг пропущен.

Публичные фото и S3

Там же: «постоянными ссылками».

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

У S3 нет одного переключателя «безопасно». Есть отдельные полномочия:

  • GetObject по известному ключу;

  • ListObjects для перечисления каталога;

  • запись новых объектов;

  • изменение и удаление.

Открытое чтение известного публичного объекта не означает, что бакет разрешает анонимно перечислить все файлы или что посторонний может что-то загрузить и удалить. На момент повторной проверки анонимный ListObjectsV2 для mimoletpublic возвращает 403 AccessDenied.

Публичное фото анкеты существует именно для публичного показа. Любой файл, уже показанный приложением, технически можно сохранить, записать с экрана или переснять другим устройством. Подписанная ссылка на пять минут не отменяет этот факт. Она лишь меняет способ доставки.

Новые фото и видео получают UUID-имена вместо предсказуемого внешнего идентификатора. Публичные анкеты сохраняют стабильную доставку, потому что это помогает кешированию и скорости загрузки.

В Telegram можно открыть аватарку любого пользователя и скачать ее. В Instagram можно зайти в комментарии, где находятся тысячи пользователей, и скачать их публичные фотографии. То же самое можно сделать в TikTok и других социальных сетях.

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

Приватные ссылки с ограниченным сроком действия нужны для голосовых сообщений, видеокружков и других медиафайлов, отправленных в личных чатах. В Мимолете такие материалы всегда были защищены именно так.

Он попросту не понимает разницы между публичными и приватными данными. Фотографии публичных анкет могут иметь постоянные URL, и это не называется взломом. По этой логике, если зайти в любую Telegram-группу и скачать аватарки сотен участников, получится, что мы взломали Telegram. Естественно, это не так.

Discord много лет раздавал вложения по прямым CDN-ссылкам без обязательного срока внутри URL. В конце 2023 года компания начала переводить attachment-ссылки на подписанную схему. При этом документация Discord и сейчас различает истекающие attachment URL и стандартные публичные CDN endpoints.

Требовать presigned URL для публичной аватарки только потому, что термин звучит безопаснее, значит путать контроль аудитории с подписью в query string.

Лайки, токены и платная функция

Далее упоминается: «action_token».

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

В публичном дейтинге возможность лайкнуть известную и допустимую анкету является функцией продукта. Человек может встретить ее в ленте, топе, группе, профиле или другом разделе. Требование принимать реакцию только после выдачи конкретной карточки не защищает секрет. Оно привязывает бизнес-действие к одной экранной последовательности и ломает остальные точки знакомства.

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

Защита лайка должна находиться на сервере и отвечать на другие вопросы:

  • кто выполняет действие;

  • существует ли цель и разрешено ли с ней взаимодействовать;

  • нет ли блокировки между пользователями;

  • хватает ли суточной квоты;

  • не была ли та же реакция уже принята;

  • не пытается ли пустой или подозрительный профиль массово отправлять действия.

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

Сейчас реакции проходят через единую точку:

POST /api/v1/feed/reactAuthorization: Bearer <token>Content-Type: application/json

Серверная квота расходуется атомарно только при новой принятой реакции. Повтор не списывает действие второй раз. Профиль без медиа не может отправлять реакции прямым API-запросом.

12 тысяч анкет, offset и нагрузка

В публикации: «обычный скрейпинг».

В публикации склеиваются три разных утверждения:

  1. Старую выдачу было дешево автоматизировать.

  2. Получен доступ к приватной базе.

  3. Доказан полный набор пользователей и утечка всей системы.

Статья показывает первое. Второе и третье из него не следуют.

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

Существует множество программ, которые автоматизируют сбор и анализ открытого контента из Instagram, Telegram, TikTok, дейтинг-сервисов и других платформ. С их помощью можно собирать сотни тысяч общедоступных материалов в день. Но никто не называет такой сбор взломом Telegram, TikTok или Instagram.

Он скачал публичные фотографии из открытых анкет. Это не взлом.

В статье указан темп до тысячи запросов в минуту. Это не поведение обычного пользователя. Один скрипт формально не является распределенной DDoS-атакой, но вполне способен создать DoS-эффект. Называть такую нагрузку обычной не значит делать ее нормальной.

Что изменено:

  • параметра swipe_offset в текущем API нет;

  • старый feed_classic_api.php возвращает 404;

  • реакции, профили, жалобы, блокировки, медиа и вход имеют раздельные лимиты и бизнес-квоты;

  • подозрительные массовые сценарии ограничиваются независимо друг от друга.

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

В исходной статье не показан доступ к таблицам базы, личным чатам, токенам, платежам или приватным медиа. Фактически показана дешевая автоматизация старой ленты. Называть это «полной базой» удобно для заголовка, но технически неточно.

Возраст и громкие 43 процента

Еще один тезис: «43% базы».

Mimolet является многофункциональной социальной платформой 16+, которая объединяет знакомства, мессенджер, сообщества, события, звонки и ленту. Это не закрытый клуб только для анкет 18+.

В том же Telegram полно профилей детей 8, 10 и 12 лет, которые не блокируют месяцами. Такая же проблема есть в TikTok, Instagram и «ВКонтакте». Социальные сети, сервисы общения и большинство дейтинг-платформ не проверяют каждого пользователя по паспорту, поэтому полностью контролировать, кто именно регистрируется, практически невозможно.

Странно требовать от продукта, которому на тот момент не исполнилось и восьми месяцев, стопроцентного контроля над всей пользовательской базой. С этой задачей не справляются даже корпорации с миллиардными бюджетами и тысячами сотрудников, включая Telegram, TikTok и Instagram.

Подтвержденный пользователь младше 16 лет нарушает правила и блокируется после проверки. Жалобы рассматриваются ежедневно. Это обязанность модерации, но она не превращает выборку из публикации и заявленный возраст в доказательство реального возраста каждого человека.

Сплошной сбор паспортов создал бы новый массив особо чувствительных документов. Его тоже пришлось бы хранить, ограничивать, удалять и защищать. Крупные платформы называют age assurance сложной отраслевой задачей и сочетают дату рождения, поведенческие сигналы, AI, жалобы, видеооценку и документы только в отдельных сценариях.

Что с подпиской

О подписке сказано: «Премиум-иллюзия».

Здесь смешаны две разные операции. Подписка продает знание о том, кто именно поставил скрытый лайк. Она не продает эксклюзивное право когда-либо лайкнуть того же человека, если его публичная анкета независимо встретилась в другом разделе.

Если старый заблюренный ответ раскрывал настоящий ID через путь медиа, это был дефект сокрытия личности. Но обычная реакция на уже известную публичную анкету не является автоматическим обходом подписки.

Текущая модель не отдает бесплатному пользователю настоящее имя, ID или исходный путь медиа скрытого лайкера. Закрытый список использует случайный teaser-путь, а массовая операция по входящим лайкам отдельно проверяет право подписки на сервере.

Защищать надо личность скрытого лайкера. Запрещать обычный лайк публичной анкете только потому, что она могла появиться в другом разделе, продукту не требуется.


Баны, сессии и привязка к IP

Далее упоминается: «PHPSESSID».

Если заблокированный аккаунт продолжал выполнять защищенные действия, это была реальная проблема отзыва доступа. Здесь спорить не о чем: бан должен проверяться сервером, а старые полномочия должны переставать работать.

Но исходная статья называет это Session Fixation. Термин означает другой класс атаки: злоумышленник заранее навязывает жертве известный ему идентификатор сессии до входа. Жизнь ранее выданной сессии после блокировки относится к инвалидации и повторной проверке актуального статуса.

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

Пользователь постоянно меняет Wi-Fi на LTE, путешествует, использует VPN и находится за CGNAT. Точное равенство IP будет регулярно выбрасывать нормальных людей из аккаунта и не остановит злоумышленника за тем же NAT. OWASP предлагает IP и User-Agent как сигналы аномалии, а не как единственную защиту. Несколько одновременных входов являются продуктовым решением, а не готовой уязвимостью.

Сейчас каждый защищенный запрос проходит через Sanctum и BanGuard. Сервер повторно проверяет актуальный статус пользователя, а не доверяет старому экранному редиректу. Можно отозвать текущий токен или выйти со всех устройств.

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

Антиабьюз, диагностика и версии

Формулировка из статьи: «Костыль abuse_ban».

Обидное название не заменяет анализа. Независимые защитные барьеры закрывают разные риски. Ограничение повторной авторизации не становится бесполезным только потому, что рядом оставался дефект пагинации.

Живой продукт обычно защищается несколькими слоями:

  • аутентификация;

  • проверка актуального бана;

  • объектная авторизация;

  • квоты и rate limits;

  • поведенческие сигналы;

  • модерация и жалобы.

Один механизм не обязан изображать весь стек. Сегодня legacy-сессия и описанные PHP-файлы вообще не являются основной границей продукта. Все клиенты используют один Laravel API, а бизнес-решения принимаются сервером.

ISPmanager, версии и test.php

Оставлять диагностический файл публичным было плохой гигиеной. Его правильно удалили. Сейчас /test.php возвращает 404.

Но имя панели, версия PHP, наличие пакета или доступная функция не исполняют команду сами. Для RCE нужен доказанный путь контролируемых данных к опасному вызову. Такого пути исходная статья не показывает.

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

Разведывательные сведения могут помочь атакующему, поэтому диагностику и надо закрывать. Но между «я увидел версию ffmpeg» и «я получил выполнение кода» находится большая доказательная пропасть.

Вывод должен соответствовать доказательству. Раскрытие версии является раскрытием версии, а не готовой компрометацией инфраструктуры.


Реклама, MAU, счета и Орел

В публикации: «бизнес-схема».

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

Из количества собранных карточек, подписчиков Telegram-канала, регистрации ООО в Орле и места жизни разработчика в Москве он выводит рекламу, неофициальные счета, уход от налогов, ложный след для проверяющих и намеренное ослабление безопасности.

Mimolet никогда не продавал рекламу. У сервиса не было рекламодателей, рекламных договоров и рекламной выручки.

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

Продукт финансируется платными функциями и подписками. ООО «Дейтинг» является официальным оператором, неофициальных счетов для приема денег нет.

Отдельно стоит географический сюжет. В статье говорится, что ООО зарегистрировано в Орле, разработчик живет в Москве, поэтому ФНС и Роскомнадзор якобы пойдут по ложному следу. Это не технический вывод и не финансовая экспертиза. Это причинно-следственная связь, которую он придумал сам.

Для проверки этой логики достаточно посмотреть на обычный рынок. Оператор Twinby указывает лицензию и адрес в Дубае. Группа Elo, в которую входит Ашан Retail, зарегистрирована во Франции. География юрлица сама по себе не доказывает налоговое нарушение.

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

CVSS без векторов и юридические выводы

В статье указано: «CVSS v3.1».

В таблице указаны баллы 7.5, 8.9 и 8.1. Ни одной полной vector string рядом нет. Другой специалист не может воспроизвести расчеты и понять, какие значения были выбраны для Attack Vector, Privileges Required, Scope, Confidentiality и остальных метрик.

Спецификация FIRST требует публиковать оценку вместе с вектором. Без него число выглядит технически, но не проверяется.

Таблица выглядит научно только до первой попытки ее пересчитать.


Почему часть рекомендаций только звучит безопасно

В рекомендациях: «presigned URLs».

В финале предлагается закрыть все фото пяти минутами, привязать сессии к IP, поставить общий лимит 30 запросов на IP, разрешать профиль только из одного раздела и собирать паспорта.

Проблема не в том, что все советы бесполезны. Часть из них разумна, но часть закрывает симптом или ломает нормальный продукт.

  • Пять минут жизни URL не мешают сохранить уже показанный файл.

  • Полная привязка к IP ломает мобильные сети, VPN и путешествия.

  • Общий IP-лимит наказывает пользователей за CGNAT.

  • Привязка лайка к одной карточке ломает другие легитимные точки открытия профиля.

  • Паспорт каждого пользователя создает новую крайне чувствительную базу документов.

SAST, DAST, TDD и bug bounty полезны, но ни один инструмент не выдает сертификат неуязвимости. TDD является методом разработки, а случайная сумма награды не заменяет процесс приема, проверки и безопасного исправления отчетов.

Что в рекомендациях было разумным:

  • исключить отрицательный offset;

  • удалить публичную диагностику;

  • сделать квоты атомарными;

  • отказаться от предсказуемых внешних идентификаторов в новых именах файлов;

  • централизовать серверную бизнес-логику.

Все перечисленное реализовано в текущей архитектуре.

Об этике массовой выгрузки

Отдельно упоминается «этика».

Для доказательства ошибки offset не требуется сохранять 12 тысяч анкет и почти 24 тысячи медиафайлов. Минимальному PoC хватило бы нескольких тестовых записей. Масштаб сбора не усиливает доказательство, зато увеличивает воздействие на реальных пользователей.

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

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


Что работает в текущем Mimolet

Текущий продукт не продолжает старую россыпь PHP-действий. Web, Telegram Mini App, iOS и Android используют общий Laravel API. Клиент рисует интерфейс, но право на действие определяет сервер.

Единая защищенная группа маршрутов

Route::middleware([    'mimolet.auth',    'abuse.guard',])->group(function () {    Route::get('feed/cards', ...);    Route::post('feed/react', ...)        ->middleware('throttle:feed-react');    Route::get('users/{id}', ...);});

Бан проверяется на каждом запросе

$user = $request->user('sanctum');if ($user === null) {    throw new AuthenticationException();}$banGuard->assertNotBanned(    $user->tg_id,    $user->email,);

Новое медиа получает случайное имя

$relative = $user->id    . '/'    . Str::uuid()->toString()    . '.jpg';

Прямой чат требует серверное право

if ($existingThread || $match || $acceptedRequest) {    return;}throw new ApiException(    'direct_chat_not_allowed',    status: 403,);

Фрагменты сокращены до проверяемой сути и не раскрывают секреты или детали обхода. Полная бизнес-логика остается на сервере и покрывается автоматическими тестами.

На момент описанных в исходной статье событий продукту было около восьми месяцев, а основной поверхностью оставался Telegram-бот. Это не оправдание старых ошибок. Это контекст развития.

Тогда были legacy PHP, несколько точек логики и молодой антиабьюз-контур. Сейчас есть общий Laravel API, SvelteKit, Sanctum, Redis и единые правила для web, iOS и Android.

Mimolet меняется не потому, что кто-то написал драматичный пост. Живой продукт обязан развиваться: объединять клиентов вокруг одного API, переносить решения на сервер, закрывать старые маршруты, делать квоты атомарными и улучшать модерацию.


Что остается от «разоблачения»

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

  • отрицательный offset снижал стоимость массовой автоматизации;

  • часть логики была продублирована в старых PHP-маршрутах;

  • квоты требовали атомарности;

  • диагностический файл не должен был оставаться публичным;

  • старые сессии после бана требовали корректного отзыва.

Это реальные инженерные темы. Их не нужно отрицать, и они исправлены в текущей архитектуре.

Но одновременно рассыпаются самые громкие выводы:

  • тезис об отсутствии авторизации, когда запросу требовалась валидная сессия;

  • заявление об открытом каталоге, когда listing закрыт, а известное фото публичной анкеты читается по назначению;

  • заявление о полном наборе пользователей, когда показана выборка карточек без доказательства полноты;

  • CVSS без векторов и юридический итог без правового анализа;

  • реклама, неофициальные счета и налоговая схема без одного документа.


Источники

ссылка на оригинал статьи https://habr.com/ru/articles/1060732/