· 7 мин чтения

Безопасность платежей на сайте: подпись запросов, журнал, защита от подделки

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

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

Какие угрозы реальны для платёжной интеграции

Не нужно строить защиту от всего. Для типичного сайта с онлайн-оплатой основные риски такие:

  1. Поддельное уведомление об оплате. Кто-то отправляет на ваш адрес запрос, имитирующий «оплата прошла», и получает товар бесплатно.
  2. Подмена суммы. Покупатель меняет в форме цену, и заказ оформляется на меньшую сумму.
  3. Повторное уведомление. Одно и то же подтверждение обрабатывается дважды: баланс начислен два раза, товар отправлен дважды.
  4. Перебор идентификаторов. Злоумышленник подбирает номера заказов, чтобы увидеть чужие данные или изменить статус.
  5. Утечка секретов. Ключи провайдера попали в публичный репозиторий, логи или клиентский код.
  6. Подмена реквизитов или ссылки на оплату через уязвимость самого сайта.

Против каждого из них есть простое средство, и ниже они идут по очереди.

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

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

Схематично на PHP (иллюстрация принципа, а не формат конкретного провайдера):

$secret   = getenv('PAY_SECRET');          // секрет не в коде и не в репозитории
$body     = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

$expected = hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $received)) {
    http_response_code(403);
    // записать попытку в журнал, не раскрывая детали ответа
    exit;
}

Что важно в этом фрагменте:

  • подпись считается от сырого тела запроса, а не от разобранных и заново собранных полей: изменение порядка или пробела ломает проверку;
  • используется hash_equals, а не ==, чтобы избежать атак по времени сравнения;
  • секрет берётся из окружения;
  • при ошибке ответ минимален, подробности остаются в журнале.

Если провайдер использует иной механизм, например базовую авторизацию в заголовке или список разрешённых IP-адресов, принцип тот же: проверка идёт первой, до любого обращения к базе. Фильтр по IP-адресам полезен как дополнительный слой, но не заменяет подпись: адреса меняются, а заголовки можно подделать за прокси. Актуальные адреса и способы проверки сверяйте у провайдера.

Сумма и статус определяются только на сервере

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

Конкретная последовательность проверок в обработчике уведомления:

  1. Подпись верна.
  2. Заказ с таким идентификатором существует.
  3. Сумма в уведомлении равна сумме заказа в вашей базе.
  4. Валюта совпадает.
  5. Заказ находится в статусе, из которого допустим переход в «оплачен».
  6. Платёж с таким идентификатором транзакции ещё не обработан.

Только после шести проверок меняется статус. Любое отклонение оформляется как ошибка по правилам провайдера и записывается в журнал для разбора.

Переход статусов задавайте как явный конечный автомат: допустимые переходы перечислены, остальные запрещены. Это защищает от странных последовательностей, например «отменён, затем оплачен».

Повторы и идемпотентность

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

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

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

Журнал: что писать и как хранить

Журнал это основа разбора спорных ситуаций. Когда клиент говорит «я заплатил», а сайт не видит оплаты, вам нужны факты, а не предположения. Что стоит записывать:

  • время получения запроса и адрес источника;
  • сырое тело запроса и ключевые заголовки (без секретов);
  • результат проверки подписи;
  • идентификатор заказа и транзакции;
  • принятое решение и отправленный ответ;
  • изменения статусов с указанием причины и источника (уведомление, ручное действие, сверка).

Чего писать нельзя:

  • полные данные банковских карт;
  • секретные ключи и токены;
  • персональные данные сверх необходимого.

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

Секреты, доступы и инфраструктура

Многие инциденты происходят не из-за кода обработчика, а из-за окружения.

  • Ключи провайдера хранятся в переменных окружения или менеджере секретов, а не в файле, который лежит в репозитории.
  • Для тестового и боевого режимов используются разные ключи.
  • Доступ к панели провайдера защищён двухфакторной аутентификацией; об этом подробнее в материале про пароли и двухфакторную аутентификацию в компании.
  • Адрес обработчика работает только по https с корректным сертификатом.
  • Сайт не раскрывает сообщения об ошибках с путями файлов и деталями базы.
  • Зависимости и CMS обновляются, а административная часть закрыта от посторонних.
  • Права на запись в каталоги сайта минимальны.

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

Защита страницы оплаты и форм

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

  • ссылка на оплату формируется на сервере, параметры нельзя подменить из браузера, либо защищены подписью;
  • идентификаторы заказов непредсказуемы или доступ к заказу проверяется по владельцу;
  • заголовки безопасности настроены (подробнее: безопасность сайта: CSP, HSTS и другие заголовки);
  • сторонние скрипты на странице оформления сведены к минимуму, потому что скрипт аналитики, скомпрометированный на странице оплаты, становится вектором атаки.

Мониторинг и сверка

Даже идеальный код не защищает от сбоев в цепочке. Настройте оповещения о событиях, которые указывают на проблему: всплеск ошибок подписи, платежи в статусе «ожидает» дольше обычного, расхождение сумм, резкое падение числа успешных оплат. Раз в день сверяйте ваши данные с выгрузкой провайдера. Если у вас учёт в 1С, подумайте о том, как автоматизировать сверку платежей с 1С.

Частые вопросы

Нужно ли мне проходить сертификацию по стандартам карточной индустрии?

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

Достаточно ли проверять только IP-адрес провайдера?

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

Что делать, если пришло уведомление с неверной подписью?

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

Как проверить свою защиту?

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

Если нужен аудит существующей оплаты или разработка интеграции с нуля, посмотрите страницу об интеграциях платёжных систем или напишите в Telegram.