Техническая статья • Secure code review

Аудит ЭЦП в платёжном API: типовые ошибки реализации

Разбор схем подписания запросов в платёжном API на примере FreeKassa: SCI (подпись на MD5) и API v1 (HMAC-SHA256). Источник требований/примеры: официальная документация сервиса. docs.freekassa.net

ЭЦП / подписи запросов MD5 HMAC-SHA256 Webhook / уведомления Модель угроз

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

TL;DR

  • SCI (MD5): устаревший криптопримитив + высокий риск компрометации подписи при слабом секрете и утечках параметров.
  • API v1 (HMAC-SHA256): правильный выбор алгоритма, но стойкость упирается в качество/энтропию ключа и корректную каноникализацию данных.
  • Webhook: одной подписи мало — нужны проверки бизнес‑инвариантов и защита от повтора (nonce/время/идемпотентность).

Модель угроз

Ключевой принцип: секрет, на котором держится подпись, не должен восстанавливаться по наблюдаемым данным, а проверка уведомлений должна быть устойчива к повтору/подмене.

SCI / подпись на MD5

В документации SCI описана подпись параметров платежной формы как MD5 от конкатенации полей (магазин/сумма/секрет/валюта/заказ), а также подпись уведомления (Result URL) как MD5 от (магазин/сумма/секрет2/заказ). Это прямо следует из официальных примеров и описаний подписи. Источник

Риск №1: MD5 считается устаревшим для криптографических целей; при слабом секрете возможен подбор/компрометация подписи (особенно если секрет “человеческий”).
Риск №2: подпись передаётся как параметр URL платежа (GET), что повышает шанс утечки через логи, историю браузера, referrer и сторонние системы мониторинга.
// Паттерн из документации (упрощённо):
sign = md5(merchant_id + ":" + amount + ":" + secret + ":" + currency + ":" + order_id)
webhook_sign = md5(merchant_id + ":" + amount + ":" + secret2 + ":" + order_id)
Почему это важно: если злоумышленник сможет восстановить секрет (или найти способ обхода проверки), он получит возможность формировать валидные ссылки на оплату и/или подделывать уведомления об оплате.

API v1 / HMAC-SHA256

В документации JSON API указано: “сортируем параметры по ключам, соединяем значения через ‘|’, затем считаем HMAC-SHA256 с использованием API-ключа” — пример приводится напрямую. Источник

// Пример паттерна из документации (PHP):
ksort(data)
signature = HMAC_SHA256( implode("|", data_values), api_key )
Нормально: переход на HMAC-SHA256 — правильное направление, так как HMAC устойчив при корректном выборе секретного ключа.
Слабое место реализации: если в качестве “api_key” фактически используется короткий ключ (например, 32-битное значение), представленный в виде hex-строки, то эффективная энтропия ключа остаётся низкой, несмотря на то что “строка выглядит длинной”.

Рекомендации

Практический чек-лист аудита подписи: 1) криптоалгоритм и каноникализация, 2) длина/генерация секрета, 3) где “живёт” подпись (URL/headers/body), 4) защита webhook от повтора, 5) проверка бизнес-инвариантов (orderId/amount/currency), 6) логирование и обработка ошибок.

Ссылки

Другие статьи