Главное Авторские колонки Вакансии Вопросы
Выбор редакции:
😼
Выбор
редакции
671 0 В избр. Сохранено
Авторизуйтесь
Вход с паролем

Почему наш первый платежный модуль рухнул под хаками и чему это научило маленькую команду

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

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

Дальше — разбор реальных технических шагов и тех мелочей, которые в итоге стали критичными.

1. Слишком смелый оптимизм при проектировании

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

  1. API сделали без полноценной аутентификации для внутренних сервисов.
  2. Использовали старую библиотеку JWT, которая уже не поддерживалась.
  3. Логи писали в общий каталог, без ротации и без внимания к объему.

Когда расписали архитектуру на доске, она выглядела аккуратно. На деле это была конструкция, которой достаточно было пнуть в бок.

2. Точка входа, о которой никто не вспомнил

Всё сломалось из-за одного параметры запроса, который никто не проверял.

Модуль принимал callback от платежного провайдера с параметром amount. Бэкенд должен был сверять сумму с локальной базой, но проверка так и не появилась. В итоге злоумышленник заметил, что модуль отвечает на запросы без проверки подписи, и начал отправлять кастомные callback. Он не мог вывести деньги, но смог менять статус заказов и накрутить бесплатные продукты.

Сценарий атаки был примитивным:

  1. Находится открытый URL для callback.
  2. Подставляется любой order_id.
  3. Модуль принимает статус success и меняет запись в базе.

Дальше пошло накручивание заказов, и только нагрузка на сервис выдала проблему.

3. Как обнаружили взлом и что было в логах

Первыми заподозрили неладное админы, которые работали в соседнем проекте. Один увидел вывод команды, которая выглядела так, будто API получает сотни похожих запросов подряд с разными order_id.

Когда дошли до логов, там было примерно следующее:

  1. десятки подряд callback с одинакового IP;
  2. странные суммы типа 999999;
  3. статусы success без подписи;
  4. запросы к тестовым заказам давно закрытой витрины.

Увидев, что база меняется, команда заблокировала endpoint на nginx. Только после этого стало ясно, что модуль вообще не был защищён.

4. Разбор полетов: что оказалось слабее всего

Когда эмоции улеглись, выяснилось: слабое место было не одно. Уязвимость выглядела как цепочка:

  1. отсутствие проверки подписи callback;
  2. отсутствие ограничения на количество запросов;
  3. отсутствие валидации входящих данных;
  4. отсутствие мониторинга активности;
  5. отсутствие чёткой схемы обработки ошибок.

И что самое неприятное — каждый считал, что другой этим займётся позже.

5. Что исправили сразу

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

  1. внедрили проверку подписи по секретному ключу;
  2. добавили жесткое соответствие суммы локальным данным;
  3. включили rate limit на уровне nginx;
  4. вынесли callback в отдельный закрытый маршрут;
  5. настроили уведомления на подозрительные запросы.

Параллельно пересмотрели архитектуру всего платежного процесса, включая внутренние вызовы.

6. Итоги аудита кода

После инцидента пригласили стороннего специалиста на экспресс-аудит. Он нашёл ещё несколько неприятных вещей:

  1. Старая библиотека для JWT хранила ключ в простом config-файле.
  2. Логирование работало через самописный класс, который мог писать ошибки в тело ответа.
  3. Несколько обработчиков имели уязвимость к SQL-инъекциям из-за неаккуратной работы с ORM.
  4. Один из эндпоинтов возвращал слишком подробные ошибки, позволяя определить структуру базы.

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

7. Главное, что поняла команда из пяти человек

Ситуация показала неприятную правду: маленькая команда не может позволить себе роскошь «потом сделаем нормально». Любой временный костыль в платежах — это потенциальная уязвимость, которая рано или поздно выстрелит.

Ключевые выводы:

  1. безопасность должна быть частью планирования, а не украшением в конце;
  2. никакой упрощённый callback не должен попадать в боевой контур;
  3. мониторинг важнее, чем кажется, особенно в маленькой инфраструктуре;
  4. проще сразу сделать неидеально, но безопасно.

Вывод

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

0
В избр. Сохранено
Авторизуйтесь
Вход с паролем
Комментарии
Выбрать файл
Блог проекта
Расскажите историю о создании или развитии проекта, поиске команды, проблемах и решениях
Написать
Личный блог
Продвигайте свои услуги или личный бренд через интересные кейсы и статьи
Написать

Spark использует cookie-файлы. С их помощью мы улучшаем работу нашего сайта и ваше взаимодействие с ним.