Безопасная разработка платёжных форм

Good Carder

Professional
Messages
759
Reaction score
493
Points
63

Введение: три фундаментальных принципа​

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

Безопасная платёжная форма строится на трёх фундаментальных принципах, которые должны быть реализованы совместно:
  • Никогда не прикасаться к сырым платёжным данным. Ваш сервер не должен видеть номер карты, срок действия или CVV/CVC. Если эти данные оказались на вашем бэкенде — вы уже в зоне риска.
  • Контролировать, что исполняется. Любой скрипт на странице — потенциальный скиммер. Запрещать небезопасные источники, подписывать разрешённые скрипты через Subresource Integrity, использовать строгую Content Security Policy без unsafe-inline и unsafe-eval, а также внедрять Trusted Types для защиты от DOM XSS.
  • Защищать от контекстных атак. Если платёжная форма встроена в iframe на чужом сайте (кликджекинг), атакующий может перехватить ввод, подменив визуальный слой.

Эти три принципа трансформируются в конкретные технические меры, которые мы разберём далее.

Часть 1. Защита от XSS с помощью CSP и Trusted Types​

Cross‑Site Scripting (XSS) — одна из самых распространённых и опасных уязвимостей в веб-приложениях. Атакующий, внедривший свой скрипт на платёжную страницу, может перехватывать вводимые номера карт, CVV и отправлять их на свой сервер в реальном времени. Это превращает любую, даже самую современную форму в инструмент массового скимминга.

1.1. Что такое XSS и почему CSP — ключевая защита​

DOM‑based XSS возникает, когда код читает данные из управляемого атакующим источника (например, location.hash, document.referrer или URLSearchParams) и записывает их на страницу небезопасным способом. Абсолютное правило: никогда не передавать непроверенные данные в sinks, интерпретирующие HTML или выполняющие код.

Небезопасные sinks, которых следует избегать с динамическими данными:
JavaScript:
// ❌ Все эти конструкции могут выполнить внедрённые скрипты
element.innerHTML = userInput;
element.outerHTML = userInput;
document.write(userInput);
eval(userInput);
setTimeout(userInput, 0);    // только строковая форма
new Function(userInput)();

Content Security Policy (CSP) — это механизм безопасности браузера, который позволяет контролировать, какие внешние источники может загружать ваша страница, и блокирует выполнение неавторизованного кода. В контексте платёжных форм CSP решает три задачи:
  • Запрещает inline-скрипты и javascript: URL (unsafe-inline)
  • Запрещает динамическую генерацию кода через eval() (unsafe-eval)
  • Ограничивает загрузку скриптов доверенными доменами

Принцип работы CSP: браузер проверяет каждый скрипт перед выполнением. Если скрипт не соответствует политике — он блокируется, не доходя до исполнения.

1.2. Строгая CSP с nonce‑ами​

Наиболее эффективная конфигурация CSP для платёжных страниц — использование nonce (одноразовых чисел), которые генерируются сервером для каждого запроса и вставляются в атрибут nonce каждого легитимного script и style. Nonce уникален для каждой загрузки страницы и бесполезен для атакующего на следующем запросе.

Пример CSP-заголовка:
Code:
Content-Security-Policy: default-src 'self'; script-src 'self' https://js.stripe.com 'nonce-{RANDOM}'; style-src 'self' 'nonce-{RANDOM}';
Директивы unsafe-inline и unsafe-eval категорически не рекомендуются для платёжных страниц — каждая из них делает CSP бесполезной против XSS.

1.3. Рекомендации Adyen по построению CSP​

Adyen, один из крупнейших платёжных процессоров, предлагает практический подход к внедрению CSP:
  1. Начать с Content-Security-Policy-Report-Only, который логирует нарушения, но не блокирует загрузку ресурсов. Это позволяет выявить все необходимые источники без риска сломать платёжную форму.
  2. Включить директиву default-src 'self', разрешив загрузку ресурсов только с собственного домена.
  3. Добавить исключения для платёжного провайдера: script-src 'self' https://*.adyen.com.
  4. После валидации всех нарушений в отчётах заменить Report-Only на активную блокирующую политику.

При использовании платёжных провайдеров необходимо добавить их домены в CSP. Для Trust Payments, например, требуется разрешить домен https://*.cardinaltrusted.com.

1.4. Новая угроза 2026 года: обход CSP через WebRTC​

В марте 2026 года исследователи Sansec обнаружили новый тип скиммера, использующего WebRTC DataChannels для обхода CSP. WebRTC-соединения, как выяснилось, не регулируются стандартными правилами CSP, что позволяет атакующим обходить защиту даже на hardened-сайтах. Трафик уходит через зашифрованные DTLS-пакеты по UDP, которые не инспектируются большинством сетевых средств безопасности.

Эта атака стала широко эксплуатироваться с 19 марта 2026 года, затронув более половины уязвимых магазинов. Скиммер крадёт валидный CSP nonce из существующих скриптов для внедрения своей полезной нагрузки, выполняя её в периоды бездействия браузера для снижения вероятности обнаружения.

Защита от этого вектора включает:
  • Блокировку неожиданного исходящего UDP-трафика на сетевом уровне
  • Расширение мониторинга за пределы HTTP-инспекции для покрытия зашифрованных UDP/DTLS потоков
  • Аудит генерации и обработки nonce для предотвращения их кражи

1.5. Trusted Types API — новая линия обороны от DOM XSS​

С февраля 2026 года Trusted Types API считается широко доступным (Baseline 2026) во всех современных браузерах. Этот API заставляет разработчика явно указывать, что данные прошли через политику очистки перед передачей в опасные sinks (innerHTML, eval, src и т.д.).

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

Trusted Types не заменяет CSP, а дополняет его: CSP ограничивает источники скриптов, а Trusted Types контролирует то, как эти скрипты манипулируют DOM. Лучшая практика — комбинировать строгую CSP без unsafe-inline с включённым заголовком Require-Trusted-Types-For 'script'.

1.6. Уязвимые реализации и их исправление​

Пример уязвимой реализации: WordPress плагин оплаты для Stripe версии ≤1.4.6 содержал уязвимость stored XSS (CVE-2026-0751). Исправление заключается в экранировании вывода всех пользовательских данных, сохранённых в базе, перед их отображением на странице.

Пример небезопасного кода:
JavaScript:
// ❌ Опасно: пользовательские данные записываются напрямую
document.getElementById('payment-message').innerHTML = userInput;

// ✅ Безопасно: использование textContent
document.getElementById('payment-message').textContent = userInput;

// ✅ Безопасно: очистка через DOMPurify перед вставкой
element.innerHTML = DOMPurify.sanitize(userInput);

CSP должна применяться в дополнение к экранированию и санитизации. Ни один из методов не является панацеей — для обеспечения многоуровневой защиты необходима комбинация всех трёх.

Часть 2. Защита от кликджекинга (Clickjacking)​

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

2.1. X-Frame-Options и CSP frame-ancestors​

Защита от кликджекинга строится на двух механизмах:
  • X-Frame-Options. Старый, но всё ещё работающий заголовок. Значение DENY полностью запрещает встраивание страницы в любой iframe; SAMEORIGIN разрешает встраивание только с того же домена.
  • CSP frame-ancestors. Более современный и гибкий механизм. Директива указывает, какие родительские источники могут встраивать страницу через <frame>, <iframe>, <object> или <embed>. CSP frame-ancestors считается более мощным, чем X-Frame-Options, поскольку поддерживает список разрешённых доменов и вложенные проверки для всех предков во фреймворке.

Пример надёжной защиты:
Code:
Content-Security-Policy: frame-ancestors 'none';
X-Frame-Options: DENY

Если платёжная форма интегрируется через iframe с поддоменов того же сайта:
Code:
Content-Security-Policy: frame-ancestors 'self' https://checkout.example.com;
Важно: директива frame-ancestors не наследуется от default-src, поэтому её необходимо указывать явно даже при наличии default-src 'self'.

2.2. Разница между frame-ancestors и frame-src​

CSP различает два типа ограничений встраивания:
  • frame-ancestors контролирует, кто может встраивать вашу страницу. Это защита от кликджекинга — кто может поместить ваш iframe на свой сайт.
  • frame-src контролирует, какие источники ваша страница может загружать в собственные iframe. Это ограничение того, куда указывают ссылки на вашей странице.

2.3. Ограничения и рекомендации​

Согласно документации MDN, директива frame-ancestors не поддерживается в элементе <meta> и должна задаваться через HTTP-заголовок.

Кроме того, существуют различия в обработке frame-ancestors и X-Frame-Options при использовании вложенных фреймов. Если страница встроена в несколько уровней iframe, каждый предок должен быть разрешён директивой frame-ancestors конечного фрейма, иначе загрузка будет отменена.

2.4. Уязвимые реализации и их исправление​

Уязвимая реализация: платёжная страница не задаёт заголовки X-Frame-Options и CSP frame-ancestors. Атакующий встраивает её в невидимый iframe на своём сайте и перехватывает клики.

HTML:
<!-- Уязвимо: хакер встраивает вашу форму -->
<iframe src="https://your-shop.com/checkout" style="opacity:0; position:absolute;"></iframe>

Исправление: добавить HTTP-заголовки на сервере (Nginx):
Code:
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none';" always;

Часть 3. Защита от внедрения скриптов через Subresource Integrity (SRI)​

Subresource Integrity (SRI) — это механизм безопасности, позволяющий браузеру проверить, что загружаемый ресурс (например, скрипт из CDN или стиль) не был изменён атакующим. Браузер вычисляет криптографическую хэш-сумму загруженного файла и сравнивает с заданным в атрибуте integrity.

3.1. Принцип работы SRI​

SRI защищает от атак на цепочку поставок (supply chain attacks), когда хакер взламывает CDN и подменяет библиотеку на вредоносную, перехватывающую платёжные данные.

Пример использования:
HTML:
<script src="https://js.stripe.com/v3/"
        integrity="sha384-..."
        crossorigin="anonymous"></script>

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

3.2. SRI и соблюдение стандарта PCI DSS 6.4.3​

Требование 6.4.3 стандарта PCI DSS (обязательное с марта 2025 года) обязывает организации гарантировать целостность скриптов, загружаемых на платёжные страницы, и однозначно определять, какие именно скрипты должны выполняться, почему они необходимы и как отслеживать их изменения.

Многие платёжные провайдеры предоставляют официальные хэш-суммы для своих скриптов в документации. Например, Braintree, Adyen и PaymentsOS рекомендуют использовать SRI с версионированными SDK вместо автоматического обновления до последней версии.

3.3. Пример для Braintree Hosted Fields​

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

3.4. Важное отличие SRI от CSP​

CSP определяет откуда можно загружать скрипты (контроль источников). SRI проверяет какой именно скрипт загружается (контроль содержимого). Эти механизмы работают совместно, создавая эшелонированную защиту.

3.5. Интеграция с PCI DSS: требования 6.4.3 и 11.6.1​

Важно понимать, что CSP и SRI — это технические механизмы, но они не являются полным решением для соблюдения требований PCI DSS. QSA-аудиторы задают три ключевых вопроса, которые CSP не может покрыть:
  1. Что именно исполняется на вашей платёжной странице прямо сейчас? (не просто политика CSP, а фактическое содержимое в браузере)
  2. Как каждый скрипт попал на страницу и кто его утвердил?
  3. Как вы узнаете, если authorised вендор изменит свой скрипт завтра?

Поэтому помимо CSP и SRI необходимы:
  • Инвентаризация всех скриптов на платёжной странице с указанием их назначения и источника
  • Документированное одобрение каждого скрипта перед добавлением
  • Системы непрерывного мониторинга изменений скриптов (например, Feroot Security)

Часть 4. Токенизация на стороне клиента: убираем платёжные данные из вашей инфраструктуры​

4.1. Архитектурный принцип: никогда не прикасаться к данным карты​

Самый эффективный способ защитить платёжные данные — не обрабатывать их вообще. Ключевой архитектурный принцип: номера карт, CVV и сроки действия никогда не должны попадать на ваш сервер. Вся обработка должна происходить на стороне клиента, в iframe или hosted-полях, принадлежащих платёжному провайдеру.

Если платёжные данные не достигают вашего сервера, ваша инфраструктура исключается из области PCI DSS (out-of-scope), что снимает 80%+ требований комплаенса. Правильно спроектированная система имеет 5–10 систем в области PCI, тогда как неправильная — 50+ при сопоставимых затратах на порядок выше.

4.2. Stripe Elements​

Stripe Elements — это коллекция предварительно стилизованных компонентов UI в виде безопасных iframe, которые собирают и токенизируют платёжные данные. Данные карты передаются напрямую Stripe через iframe; ваш сервер получает только одноразовый токен (PaymentMethod ID), который можно безопасно использовать для проведения платежа, возвратов и рекуррентных списаний.

Стандартный поток работы: клиент заполняет поля внутри iframe → Stripe токенизирует данные и возвращает payment_method_id → ваш сервер получает токен → создаёт PaymentIntent через API Stripe с этим токеном. Токенизированные данные означают, что ваши системы никогда не работают с номерами карт, что снижает риски и упрощает соответствие стандартам безопасности.

Преимущества Stripe Elements:
  • Ваш сервер остаётся вне PCI-периметра
  • Карточные данные никогда не попадают в логи, базы данных или кеши
  • Стилизация остаётся под вашим контролем

4.3. Braintree Hosted Fields​

Braintree Hosted Fields реализует аналогичную концепцию: каждое поле ввода (номер карты, срок действия, CVV) размещается в собственном прозрачном iframe, хостед на домене Braintree (PayPal). При отправке формы Braintree возвращает одноразовый payment_method_nonce, который ваш сервер может безопасно использовать для проведения транзакции.

Критическое требование: Чтобы соответствовать упрощённому уровню PCI DSS SAQ A, платёжные поля не могут находиться на вашей платёжной странице непосредственно. Они должны быть захостедены (hosted) — размещены в iframe на домене платёжного провайдера.

4.4. Почему токенизация не решает все проблемы безопасности платёжной формы​

Даже при использовании Hosted Fields зона ответственности продавца включает:
  • Контроль того, какие скрипты загружаются на страницу, содержащую iframe.
  • Мониторинг целостности этих скриптов (требования 6.4.3 и 11.6.1 PCI DSS).
  • Проверку подписи вебхуков платёжного провайдера перед обработкой уведомлений.

4.5. Пример уязвимой реализации и её исправление​

Уязвимая реализация: Ваш сервер принимает номер карты напрямую через POST, передаёт его в Stripe API и создаёт PaymentIntent.
Code:
// ❌ Опасно: сервер обрабатывает номер карты напрямую
app.post('/create-payment', (req, res) => {
  const { cardNumber, expMonth, expYear, cvc } = req.body;
  stripe.paymentIntents.create({
    amount: 1000,
    currency: 'usd',
    payment_method_data: { type: 'card', card: { number: cardNumber, exp_month: expMonth, exp_year: expYear, cvc } }
  });
});

Исправление: Используйте Stripe Elements (или аналоги) для токенизации на фронтенде и передавайте на сервер только payment_method_id.

Часть 5. Сквозной чек-лист безопасности платёжной формы​

CSP и XSS защита:
  • Строгая CSP без unsafe-inline и unsafe-eval. Использование nonce или хэшей для разрешённых скриптов.
  • Включён заголовок Content-Security-Policy со списком разрешённых источников, включая домены платёжного провайдера.
  • Настроена директива frame-ancestors 'none' или 'self' для предотвращения кликджекинга.
  • Trusted Types включены через заголовок Require-Trusted-Types-For 'script' в поддерживаемых браузерах.
  • Внедрена санитизация через DOMPurify для всех динамически вставляемых HTML-фрагментов.

SRI и целостность скриптов:
  • Атрибут integrity добавлен ко всем CDN-скриптам (Stripe, Braintree, Adyen, аналитика).
  • Использованы версионированные, а не динамические latest ссылки.
  • Создан и поддерживается инвентарь всех скриптов на платёжной странице с указанием назначения, источника и даты утверждения.
  • Разработана процедура документированного утверждения каждого нового скрипта перед его добавлением.
  • Настроен непрерывный мониторинг изменений скриптов с автоматическими оповещениями.

Токенизация и защита данных:
  • Платёжные данные собираются через hosted-поля (Stripe Elements, Braintree Hosted Fields, Adyen iframes), а не в обычные input.
  • На сервер передаются только одноразовые токены (payment_method_id, payment_nonce), а не данные карты.
  • Вебхуки платёжного провайдера проверяются на подлинность подписи (HMAC).

Защита от кликджекинга:
  • Заголовок X-Frame-Options: DENY или SAMEORIGIN установлен на сервере.
  • Директива CSP frame-ancestors настроена для предотвращения внешнего встраивания.
  • Проверено, что страница не встраивается на сторонние домены (тест через инструменты разработчика).

Заключение: безопасность платёжной формы — это не одна технология, а эшелонированная защита​

Безопасная платёжная форма не строится на одном инструменте. Это эшелонированная защита, где каждый слой прикрывает слабости других:
  • CSP + Trusted Types защищают от внедрения скриптов через XSS.
  • SRI гарантирует, что загруженные скрипты не были подменены на уровне CDN.
  • frame-ancestors + X-Frame-Options предотвращают кликджекинг.
  • Токенизация на стороне клиента полностью убирает платёжные данные из вашей инфраструктуры, радикально снижая поверхность атаки и упрощая PCI-комплаенс.

Три главных вывода этой статьи:
  1. CSP без unsafe-inline и unsafe-eval обязательна. Это единственный способ предотвратить выполнение несанкционированных скриптов на платёжной странице.
  2. SRI обязателен для всех CDN-ресурсов. Без него хакер, взломавший CDN, может перехватить все ваши транзакции.
  3. Токенизация на стороне клиента — это не опция, а необходимость. Если номера карт проходят через ваш сервер, вы находитесь в зоне высокого риска и широкой PCI-периметра.

Быстрая памятка на одну строку:
«Строгая CSP без unsafe‑inline, SRI для всех CDN, trust‑framing через X‑Frame‑Options и CSP frame‑ancestors, hosted‑токенизация через Elements или Hosted Fields — и никаких карточных данных на твоём сервере»
 
Top