Шифрование, 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-токены и явная проверка прав остаются обязанностью кода.
Связанные темы
- Основы безопасности - XSS, SQL-инъекции, CSRF, права
- Ядро D7 - куки, конфигурация, фильтры действий
- D7 ORM - поля сущностей
- Страница и форма авторизации - капча и защита формы входа
- Раздел Безопасность