Стартапам нужно ответственнее относиться к безопасности

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

Text telling users to download Cerca
Текст с предложением скачать Cerca

Я скачал приложение и запустил Charles Proxy (для приложения iPhone), чтобы перехватывать сетевые запросы и контролировать происходящее внутри приложения.

Для начала давайте выполним вход. Приложение использует вход на основе OTP (код отправляется по номеру телефона), поэтому я решил проверить ответ на запрос одноразового пароля. БУМ – OTP находился непосредственно в ответе, то есть к аккаунту любого пользователя можно получить доступ, зная только телефонный номер.

Screenshot of OTP response revealing the one-time password vulnerability

Однако мне теперь нужно как-то узнать, кому принадлежит аккаунт, я не могу просто перебирать номера. Я перешёл к конечной точке api.cercadating.com и воспользовался directory fuzzer, чтобы составить список путей, надеясь найти нужные конечные точки. Без соответствующего заголовка приложения я не мог получить доступ ни к одной из частей сайта:

App version information required by the API

Поэтому я передал заголовок при помощи Gobuster, и к моему (не очень сильному) удивлению оказалось, что раскрыты все конечные точки; это удалось выяснить благодаря найденной конечной точке /docs, передававшей openapi.json!

Screenshot of the found /docs endpoint fro Gobuster

Я запустил Burp Suite и использовал инструменты сопоставления и замены, чтобы заголовок версии приложения и токен носителя (извлечённый из Charles Proxy) передавались автоматически. И тут всё стало ещё любопытнее.

Screenshot showing all API endpoints exposed
Все раскрытые конечные точки API

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

Screenshot of the match endpoint used to force user matches

Но другие, например, получение конечной точки профиля пользователя (user/{user_id}), казались более любопытными. Эта конечная точка получает валидный ID пользователя и возвращает всю личную информацию (в том числе и телефонные номера, которых благодаря уязвимости OTP достаточно для полного захвата аккаунта). Я написал короткий скрипт на Python для подбора валидных ID и смог создать список всех пользователей; формат ответа выглядел примерно так:

{

  "status": "success",

  "message": "string",

  "results": 0,

  "data": {

    "first_name": "string",

    "last_name": "string",

    "gender": "MALE",

    "interested_genders": [

      "MALE"

    ],

    "city": "string",

    "latitude": 0,

    "longitude": 0,

    "university_email": "user@example.com",

    "university_email_verified": false,

    "industry": "string",

    "profession": "string",

    "date_of_birth": "2025-02-21",

    "height": 0,

    "university_id": 0,

    "university_name": "string",

    "profile_completed": false,

    "national_id_verified": false,

    "mobile_verified": false,

    "email_verified": false,

    "premium": false,

    "premium_expiry": "2025-02-21T21:31:06.213Z",

    "active": true,

    "paused": false,

    "onboarded": false,

    "profile_type": "PROFESHIONAL",

    "mobile_number": "string",

    "email": "user@example.com",

    "user_type": [

      "user"

    ],

    "user_id": 0,

    "remaining_searches": 0,

    "profile_images": [],

    "university": {

      "id": 0,

      "name": "string"

    },

    "score": [],

    "match_preferences": [],

    "user_prompts": [],

    "mutual_contact_previews": [],

    "mutual_contact_preview_data": [],

    "mutual_contact_count": 0,

    "created_at": "2025-02-21T21:31:06.213Z",

    "updated_at": "2025-02-21T21:31:06.213Z",

    "zodiac_info": {},

    "distance_km": 0,

    "final_score": 0,

    "age": 0

  },

  "meta": {}

}

Теперь я не только мог узнать все валидные номера телефонов, связанные с аккаунтом (который затем можно было захватить благодаря ошибке конфигурации OTP), но и все личные данные пользователей без необходимости входа по OTP! Но ситуация оказалась ещё хуже — особенно меня обеспокоило поле national_id_verified. Разумеется, сервис хранит в системе и информацию паспорта или другого удостоверяющего личность документа:

Screenshot of the user profile (ID) endpoint returning personal data
{

  "status": "success",

  "message": "string",

  "results": 0,

  "data": {

    "verification_type": "PASSPORT",

    "document_number": "string",

    "front_side_url": "string",

    "back_side_url": "string",

    "selfie_url": "string",

    "status": "pending",

    "id": 0,

    "user_id": 0

  },

  "meta": {}

}

Она доступна только для пользователя, выполнившего вход, но поскольку я был способен выполнить вход в любой аккаунт, то мог и просмотреть информацию документов любого человека, если он её вводил (я, разумеется, этого не делал). Я не только мог просматривать личные сообщения с потенциальными парами, но и получать паспортную информацию! Я набросал скрипт, чтобы проверить, о скольких пользователях я могу получить информацию, сколько зарегистрировано студентов Йеля (предполагаю, студентов Йеля было больше; возможно, кто-то не указал свой университет) и сколько пользователей ввело информацию из своих документов. По сути, скрипт просто подсчитывал количество найденных валидных пользователей; если, проверив 1000 порядковых ID, он ничего не находил, то прекращал выполнение. Так что, возможно, их больше (сами владельцы Cerca заявили о 10 тысячах пользователей за первую неделю), но мне удалось найти 6117 пользователей; 207 из них ввели информацию из удостоверяющих личность документов, а 19 указали, что учатся в Йеле.

Screenshot of the script output showing the number of users, Yale students, and users with ID information
Screenshot of Cerca's instagram post claiming 10k users in the first week
Скриншот поста в Instagram Cerca

Это просто безумная утечка! У меня появился доступ к сексуальным предпочтениям, интимным сообщениям и всевозможной личной информации десятков тысяч (согласно данным самого Cerca) ничего не подозревающих пользователей. В своей политике конфиденциальности Cerca заявляет: «Мы используем шифрование и другие стандартные меры для защиты вашей информации», но это, очевидно, не так. Это подвергает существенному риску безопасность и конфиденциальность пользователей. Учитывая то, что я просто студент колледжа и это мой хобби-проект, возможно, существуют и другие критические уязвимости.

Последствием использования такой уязвимости станет полное нарушение конфиденциальности с потенциально очень серьёзным ущербом в реальной жизни. Разработчикам нужно научиться делать безопасные приложения и не говорить, что их приложения безопасны, хотя это не так. Особенно, когда дело касается дейтинг-приложений! Нельзя ожидать. что пользователи будут заниматься всеми проверками, которые выполнил я. Кто знает, сколько людей уже имели доступ ко всем этим данным до того, как их обнаружил я. Кто-то мог уже скачать полную базу данных с личной информацией более чем шести тысяч пользователей и личные чаты. Если какой-то злоумышленник получит доступ к этой информации, то это может привести к краже личности, преследованиям или шантажу. Подобные уязвимости очень страшны, они во мгновение могут сломать чью-то жизнь. Разработчики должны отдавать высокий приоритет защите пользовательских данных, а не просто публиковать приложение, надеясь на его популярность. Я занялся поиском этой уязвимости не для того, чтобы написать пост, но поскольку разработчики Cerca не отвечали на мои письма и не уведомили своих пользователей, я решил, что справедливо будет опубликовать эти сведения. Я не хочу никого взламывать, а просто стремлюсь к повышению безопасности Интернета!

Хронология и ответственное раскрытие: выявив эти уязвимости, 23 февраля 2025 года я связался с командой Cerca по почте. На следующий день (24 февраля) у нас состоялся продуктивный видеозвонок, в котором мы обсудили уязвимости, потенциальные способы их устранения и дальнейшие шаги. В беседе команда Cerca признала серьёзность этих проблем, выразила благодарность за ответственное раскрытие и уверила меня, что должным образом устранит уязвимости и проинформирует пользователей.

С тех пор я пробовал связаться с ними несколько раз (5 марта и 13 марта), желая узнать новости об устранении проблемы и уведомлении пользователей. К сожалению, на дату публикации моего поста (21 апреля 2025 года) никаких ответов я не получил. Насколько я знаю, Cerca не признала публично наличие инцидента и не проинформировала пользователей об уязвимости, несмотря на данные мне обещания. Также разработчики больше не связывались со мной после видеозвонка и игнорировали все мои последующие письма.

Однако мне удалось самостоятельно убедиться, что описанные в этом посте уязвимости были пропатчены, что позволяет мне опубликовать свои находки.

Комментарии (18)


  1. corvair
    13.05.2025 08:06

    Так скрыли они дыру или устранили?


  1. anonymous
    13.05.2025 08:06