Перейти к содержимому

Шифрование, JWT и CAPTCHA в 1С-Битрикс

Продолжение темы безопасности: подсистемы, которые нужны, когда базовые экранирование и токены уже на месте. Шифрование секретов, токены доступа, защита форм от ботов, файловые права и запрет встраивания сайта во фрейм.

Как это работает

Единый ключ шифрования. Значение crypto_key в файле /bitrix/.settings.php обслуживает шифрованные ORM-поля и защищённые куки. В новых дистрибутивах он генерируется автоматически и хранится с флагом только для чтения, чтобы его нельзя было изменить из админки. Класс Cipher при прямом вызове ключ из настроек не берёт - его передаёт разработчик.

Криптография стоит на OpenSSL. По умолчанию используется алгоритм aes-256-ctr и контроль целостности через sha256. Шифрование недетерминированное: при каждом вызове создаётся случайный вектор инициализации, поэтому один и тот же текст с одним ключом даёт разный шифротекст.

JWT - это не шифрование. Токен состоит из заголовка, полезной нагрузки и подписи. Подпись защищает от подделки, но нагрузка читается кем угодно в base64. JWT решает задачи целостности и аутентификации, а не конфиденциальности.

Права доступа на двух уровнях. Файловый уровень - это .access.php, который проверяется в прологе ещё до загрузки ядра. Логика модуля - права и роли из его настроек. Итоговые права пользователя считаются от максимума прав всех его групп.

Кликджекинг закрывается заголовком. Либо модулем защиты от фреймов на сайте, либо конфигурацией веб-сервера - но никогда двумя способами сразу, иначе получится дубль заголовка.

Примеры

1. Шифрование значения

use Bitrix\Main\Security\Cipher;
$cipher = new Cipher();
$encrypted = $cipher->encrypt($plainText, $key);
$stored = base64_encode($encrypted); // в базу только так
// чтение
$decrypted = $cipher->decrypt(base64_decode($stored), $key);

Две ошибки, которые ломают шифрование на практике. Первая: метод возвращает бинарные данные, и запись их в колонку без кодирования приводит к порче - при чтении вы получите исключение. Вторая: шифротекст примерно в полтора раза длиннее исходного плюс служебные блоки, и колонка должна это выдерживать.

Ключи генерируйте криптостойко:

$key = \Bitrix\Main\Security\Random::getString(32);

Хранить ключ рядом с зашифрованными данными бессмысленно - его место в переменных окружения или защищённом хранилище.

2. Шифрованные поля ORM

use Bitrix\Main\ORM\Fields\CryptoField;
(new CryptoField('API_SECRET'))
->configureRequired(),

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

Для автогенерируемых секретов есть SecretField - он сам создаёт значение при добавлении записи.

3. JWT

use Bitrix\Main\Web\JWT;
$token = JWT::encode(['userId' => $userId, 'exp' => time() + 3600], $key, 'HS256');
// проверка - обязательно со списком разрешённых алгоритмов
$payload = JWT::decode($token, $key, ['HS256']);

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

Для проверки по набору публичных ключей есть класс JWK - он нужен при использовании асимметричных алгоритмов.

4. Защищённые куки

use Bitrix\Main\Web\CryptoCookie;
$cookie = new CryptoCookie('user_prefs', $value);
\Bitrix\Main\Context::getCurrent()->getResponse()->addCookie($cookie);

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

5. CAPTCHA

// генерация
$captchaCode = $APPLICATION->CaptchaGetCode();
// в форме
<input type="hidden" name="captcha_sid" value="<?= htmlspecialcharsbx($captchaCode) ?>">
<img src="/bitrix/tools/captcha.php?captcha_sid=<?= htmlspecialcharsbx($captchaCode) ?>">
<input type="text" name="captcha_word">
// проверка на сервере - обязательна
if (!$APPLICATION->CaptchaCheckCode($_POST['captcha_word'], $_POST['captcha_sid'])) {
// код не совпал
}

Без серверной проверки капча бесполезна: бот просто отправит POST-запрос мимо картинки.

6. Файловые права и защита от фреймов

// .access.php в служебной папке
<?php
$PERM['/upload/private/']['*'] = 'D'; // всем запрещено
$PERM['/upload/private/']['1'] = 'W'; // администраторам запись

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

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

Справочник API

APIНазначениеОсобенности
crypto_key в .settings.phpобщий ключ шифрованияфлаг только для чтения; менять нельзя после шифрования данных
Security\Cipherсимметричное шифрованиепо умолчанию aes-256-ctr, ключ передаёт разработчик
Security\Random::getString()генерация ключей и токеноввместо самодельных строк
ORM\Fields\CryptoFieldшифрованное поле сущностиодна колонка - один режим
ORM\Fields\SecretFieldавтогенерируемый секретзначение создаётся при добавлении
Web\JWT::encode() / decode()токены доступапри проверке обязателен список алгоритмов
Web\JWKнабор ключей для проверкидля асимметричных алгоритмов
Web\CryptoCookieшифрованная кукатребует общего ключа на всех серверах
CaptchaGetCode() / CaptchaCheckCode()капчапроверка обязательно на сервере
.access.phpфайловые правапроверяются до загрузки ядра
X-Frame-Optionsзащита от кликджекингавключать одним способом, не двумя
Проактивный фильтр (WAF)фильтрация типовых атакдополняет код, не заменяет
Стоп-листблокировка адресов и подсетей

Частые ошибки

Сменили ключ шифрования и потеряли данные. После смены существующие зашифрованные поля и куки не расшифровываются. Ключ ставят один раз и держат неизменным - и одинаковым на всех серверах кластера.

Бинарный шифротекст пишут в базу как есть. Нужна кодировка в base64 при записи и обратное преобразование при чтении.

Колонка мала для шифротекста. Зашифрованное значение заметно длиннее исходного. Размер колонки считают заранее.

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

Проверка JWT без списка алгоритмов. Позволяет навязать слабый алгоритм из заголовка токена.

Секреты в полезной нагрузке токена. Она не шифруется и читается без ключа.

Капчу проверяют только на клиенте. Серверная проверка обязательна.

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

Где хранится ключ шифрования и можно ли его менять?

В файле /bitrix/.settings.php, в параметре crypto_key, с флагом только для чтения - чтобы его нельзя было случайно изменить из админки. Менять ключ после того, как им зашифрованы данные, нельзя: расшифровать их станет невозможно. В кластере ключ должен быть одинаковым на всех узлах, иначе куки и поля, созданные одним сервером, не прочитает другой.

JWT можно использовать вместо шифрования?

Нет, это разные задачи. Подпись токена гарантирует, что его не подделали, но полезная нагрузка не зашифрована - её читает кто угодно, раскодировав base64. Поэтому в токен кладут идентификаторы и ссылки, а не пароли и персональные данные. И при проверке всегда передавайте явный список разрешённых алгоритмов.

Как включить шифрование поля, в котором уже есть данные?

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

Достаточно ли проактивного фильтра для защиты сайта?

Нет. Фильтр отсекает типовые атаки и полезен как второй эшелон, но он не знает вашей бизнес-логики: подмену идентификатора заказа в запросе он пропустит, потому что формально запрос корректен. Экранирование по контексту, валидация ввода, CSRF-токены и явная проверка прав остаются обязанностью кода.

Связанные темы

Первоисточники