# Двухфакторная аутентификация в 1С-Битрикс

URL: https://dmeshcheryakov.ru/blog/security-bitrix-2fa/
Раздел: Безопасность
Теги: Безопасность, Битрикс, 2FA
Опубликовано: 2026-09-23
Обновлено: 2026-09-07
Автор: Дмитрий Мещеряков (https://dmeshcheryakov.ru)

> Настройка 2FA для администраторов и пользователей. OTP, приложения-аутентификаторы, резервные коды.

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

Разберём настройку и, отдельно, границы того, что 2FA реально защищает: у неё есть слабые места, и знать их нужно до того, как вы решите, что вопрос закрыт.

## Зачем 2FA

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

**Двухфакторная аутентификация** — это второй барьер помимо пароля: одноразовый код, который есть только у владельца телефона.

По статистике Google, 2FA блокирует 100% автоматизированных атак, 96% фишинговых атак и 76% таргетированных атак.

## Встроенная 2FA в Битрикс

Битрикс поддерживает OTP (One-Time Password) из коробки начиная с версии 14.5.

### Включение для администраторов

**Шаг 1: Включаем модуль**

Админка → Настройки → Настройки продукта → Модули → **Проактивная защита**

Убедитесь, что модуль **security** установлен и активен.

**Шаг 2: Настраиваем OTP**

Админка → Настройки → Проактивная защита → **Одноразовые пароли**

```
☑ Включить одноразовые пароли
☑ Обязательное использование для группы: Администраторы [admin]
   Количество дней отсрочки: 3
```

**Шаг 3: Пользователь настраивает свой OTP**

При следующем входе администратор увидит форму настройки:

1. Установить приложение-аутентификатор (Google Authenticator, Authy, 1Password)
2. Отсканировать QR-код
3. Ввести проверочный код
4. Сохранить резервные коды

### Приложения-аутентификаторы

| Приложение | Платформы | Особенности |
|------------|-----------|-------------|
| **Google Authenticator** | iOS, Android | Простое, без облака |
| **Authy** | iOS, Android, Desktop | Облачный бэкап, мультидевайс |
| **1Password** | Все | Интеграция с менеджером паролей |
| **Microsoft Authenticator** | iOS, Android | Интеграция с MS-аккаунтом |

Google Authenticator не делает бэкапов. При потере телефона — потеря доступа. Используйте Authy или сохраняйте резервные коды.

## Настройка через код

### Принудительное включение 2FA

```php
// /local/php_interface/init.php
use Bitrix\Main\EventManager;

// Требуем 2FA для всех администраторов
EventManager::getInstance()->addEventHandler(
    'main',
    'OnAfterUserAuthorize',
    function ($arParams) {
        global $USER;
        
        if (!$USER->IsAuthorized()) {
            return;
        }
        
        // Проверяем, администратор ли это
        if (!$USER->IsAdmin()) {
            return;
        }
        
        // Проверяем, настроен ли OTP
        $otp = \Bitrix\Security\Mfa\Otp::getByUser($USER->GetID());
        
        if (!$otp || !$otp->isActivated()) {
            // Перенаправляем на настройку OTP
            LocalRedirect('/bitrix/admin/security_otp.php?init=Y');
        }
    }
);
```

### Проверка статуса 2FA

```php
use Bitrix\Security\Mfa\Otp;

function isOtpEnabled(int $userId): bool
{
    $otp = Otp::getByUser($userId);
    return $otp && $otp->isActivated();
}

function requireOtpForAdmins(): void
{
    global $DB;
    
    // Получаем всех администраторов без 2FA
    $admins = $DB->Query("
        SELECT u.ID, u.LOGIN, u.EMAIL
        FROM b_user u
        JOIN b_user_group ug ON u.ID = ug.USER_ID
        WHERE ug.GROUP_ID = 1
          AND u.ACTIVE = 'Y'
          AND u.ID NOT IN (
              SELECT USER_ID FROM b_security_otp WHERE ACTIVE = 'Y'
          )
    ");
    
    $withoutOtp = [];
    while ($admin = $admins->Fetch()) {
        $withoutOtp[] = $admin;
    }
    
    if (!empty($withoutOtp)) {
        // Отправляем уведомление или логируем
        \CEventLog::Add([
            'SEVERITY' => 'WARNING',
            'AUDIT_TYPE_ID' => 'SECURITY_OTP',
            'MODULE_ID' => 'security',
            'DESCRIPTION' => 'Администраторы без 2FA: ' . json_encode($withoutOtp),
        ]);
    }
}
```

### API для работы с OTP

```php
use Bitrix\Security\Mfa\Otp;
use Bitrix\Security\Mfa\OtpAlgorithm;

// Создание нового OTP для пользователя
function setupOtpForUser(int $userId): array
{
    // Генерируем секретный ключ
    $secret = Otp::generateSecret();
    
    // Создаём объект OTP
    $otp = new Otp();
    $otp->setUserId($userId);
    $otp->setSecret($secret);
    $otp->setType(OtpAlgorithm::TOTP); // Time-based OTP
    
    // Сохраняем (но не активируем)
    $otp->save();
    
    // Генерируем QR-код
    $qrCodeUrl = $otp->getProvisioningUri(
        'Битрикс Админка', // Название сервиса
        $userId            // Идентификатор аккаунта
    );
    
    // Генерируем резервные коды
    $backupCodes = [];
    for ($i = 0; $i < 10; $i++) {
        $backupCodes[] = Otp::generateRecoveryCode();
    }
    
    return [
        'secret' => $secret,
        'qr_url' => $qrCodeUrl,
        'backup_codes' => $backupCodes,
    ];
}

// Проверка OTP-кода
function verifyOtpCode(int $userId, string $code): bool
{
    $otp = Otp::getByUser($userId);
    
    if (!$otp) {
        return false;
    }
    
    return $otp->verify($code);
}

// Активация OTP после первой успешной проверки
function activateOtp(int $userId, string $code): bool
{
    $otp = Otp::getByUser($userId);
    
    if (!$otp || $otp->isActivated()) {
        return false;
    }
    
    if ($otp->verify($code)) {
        $otp->activate();
        $otp->save();
        return true;
    }
    
    return false;
}
```

## Резервные коды

Резервные коды — страховка на случай потери телефона. Каждый код одноразовый.

### Генерация резервных кодов

```php
use Bitrix\Security\Mfa\Otp;

function generateBackupCodes(int $userId, int $count = 10): array
{
    $codes = [];
    
    for ($i = 0; $i < $count; $i++) {
        $codes[] = Otp::generateRecoveryCode();
    }
    
    // Сохраняем хэши кодов в БД
    $otp = Otp::getByUser($userId);
    if ($otp) {
        $otp->setRecoveryCodes($codes);
        $otp->save();
    }
    
    return $codes; // Показываем пользователю один раз
}
```

### Хранение резервных кодов

```php
// Структура таблицы b_security_otp_recovery
// USER_ID | CODE_HASH | USED | DATE_USED

function useBackupCode(int $userId, string $code): bool
{
    global $DB;
    
    $codeHash = hash('sha256', $code);
    
    $result = $DB->Query("
        UPDATE b_security_otp_recovery
        SET USED = 'Y', DATE_USED = NOW()
        WHERE USER_ID = {$userId}
          AND CODE_HASH = '{$DB->ForSql($codeHash)}'
          AND USED = 'N'
    ");
    
    return $DB->AffectedRowsCount() > 0;
}
```

Рекомендация: храните резервные коды в менеджере паролей (1Password, Bitwarden) или в защищённом облачном хранилище.

## Кастомная форма входа с 2FA

```php
// /local/templates/main/components/bitrix/system.auth.form/.default/template.php
<?php if ($arResult['NEED_OTP']): ?>
    <div class="auth-otp-form">
        <h3>Введите код из приложения</h3>
        <p>Откройте Google Authenticator или другое приложение и введите 6-значный код.</p>
        
        <form method="POST" action="<?= POST_FORM_ACTION_URI ?>">
            <?= bitrix_sessid_post() ?>
            <input type="hidden" name="AUTH_FORM" value="Y" />
            <input type="hidden" name="TYPE" value="OTP" />
            
            <div class="form-group">
                <input 
                    type="text" 
                    name="USER_OTP" 
                    placeholder="000 000"
                    maxlength="6"
                    pattern="[0-9]{6}"
                    autocomplete="one-time-code"
                    inputmode="numeric"
                    autofocus
                    required
                />
            </div>
            
            <div class="form-group">
                <label>
                    <input type="checkbox" name="OTP_REMEMBER" value="Y" />
                    Запомнить это устройство на 30 дней
                </label>
            </div>
            
            <button type="submit" class="btn btn-primary">
                Подтвердить
            </button>
            
            <a href="?backup_code=Y" class="link-secondary">
                Использовать резервный код
            </a>
        </form>
    </div>
<?php else: ?>
    <!-- Обычная форма входа -->
<?php endif; ?>
```

## Доверенные устройства

Битрикс позволяет «запомнить» устройство на определённый срок.

```php
// Настройка в админке
// Проактивная защита → Одноразовые пароли → Доверенные устройства

// Параметры:
// - Срок действия: 30 дней (по умолчанию)
// - Привязка к IP: да/нет
// - Привязка к User-Agent: да/нет
```

### Программное управление доверенными устройствами

```php
use Bitrix\Security\Mfa\TrustedDevice;

// Добавить текущее устройство как доверенное
function trustCurrentDevice(int $userId, int $days = 30): void
{
    $device = new TrustedDevice();
    $device->setUserId($userId);
    $device->setDeviceHash(TrustedDevice::generateDeviceHash());
    $device->setIp($_SERVER['REMOTE_ADDR']);
    $device->setUserAgent($_SERVER['HTTP_USER_AGENT']);
    $device->setExpiresAt(new \DateTime("+{$days} days"));
    $device->save();
}

// Проверить, доверенное ли устройство
function isDeviceTrusted(int $userId): bool
{
    $deviceHash = TrustedDevice::generateDeviceHash();
    return TrustedDevice::isTrusted($userId, $deviceHash);
}

// Отозвать все доверенные устройства
function revokeAllTrustedDevices(int $userId): void
{
    TrustedDevice::deleteByUser($userId);
}
```

## Мониторинг и алерты

### Логирование событий 2FA

```php
// /local/php_interface/init.php
use Bitrix\Main\EventManager;

EventManager::getInstance()->addEventHandler(
    'security',
    'OnOtpVerifySuccess',
    function ($userId) {
        \CEventLog::Add([
            'SEVERITY' => 'INFO',
            'AUDIT_TYPE_ID' => 'OTP_SUCCESS',
            'MODULE_ID' => 'security',
            'ITEM_ID' => $userId,
            'DESCRIPTION' => 'Успешная проверка OTP',
        ]);
    }
);

EventManager::getInstance()->addEventHandler(
    'security',
    'OnOtpVerifyFail',
    function ($userId) {
        \CEventLog::Add([
            'SEVERITY' => 'WARNING',
            'AUDIT_TYPE_ID' => 'OTP_FAIL',
            'MODULE_ID' => 'security',
            'ITEM_ID' => $userId,
            'DESCRIPTION' => 'Неудачная проверка OTP',
        ]);
        
        // Алерт при множественных неудачах
        $failCount = getRecentOtpFailures($userId, 10); // за 10 минут
        if ($failCount >= 5) {
            sendSecurityAlert("Множественные неудачные OTP для пользователя {$userId}");
        }
    }
);
```

## Итоги

### Чеклист внедрения 2FA

- [ ] Модуль security установлен и активен
- [ ] 2FA обязательна для группы «Администраторы»
- [ ] Все админы настроили свои аутентификаторы
- [ ] Резервные коды сгенерированы и сохранены
- [ ] Логирование событий 2FA включено
- [ ] Есть процедура восстановления доступа

### Что реально даёт 2FA

Таблицы вида «с 2FA фишинг бесполезен» встречаются часто, но они неточны, а неточность в вопросах безопасности приводит к ложному чувству защищённости. Разберём честно.

| Сценарий | Без 2FA | С 2FA (TOTP) |
|----------|---------|--------------|
| Пароль утёк из другой базы | Полная компрометация | Не сработает |
| Подбор пароля | Возможен | Практически бесполезен |
| Пароль подсмотрен или записан | Компрометация | Не сработает |
| Классический фишинг (сбор пароля) | Работает | Не сработает |
| **Фишинг в реальном времени** | Работает | **Работает** |
| **Перехват сессии после входа** | Работает | **Работает** |
| **Вредоносный код на устройстве админа** | Работает | **Работает** |

**Три сценария, от которых TOTP не защищает, и их стоит знать.**

**Фишинг в реальном времени.** Поддельная страница входа, которая передаёт введённые логин, пароль и одноразовый код на настоящий сайт немедленно. Код действует около минуты — этого более чем достаточно. Такие наборы инструментов существуют и доступны; TOTP их не останавливает. Останавливают аппаратные ключи (WebAuthn/FIDO2), где подпись привязана к домену и на поддельном сайте не сработает.

**Перехват уже установленной сессии.** 2FA проверяется при входе; после него работает кука сессии. Украденная кука даёт доступ без всякого второго фактора — отсюда важность `HttpOnly`, `Secure`, привязки сессии и разумного времени её жизни.

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

Это не аргументы против 2FA — она закрывает самый частый сценарий и включать её нужно. Это аргументы против того, чтобы считать вопрос безопасности админки решённым после её включения.

**Самое слабое место внедрения — процедура восстановления доступа.** Администратор потерял телефон, и дальше есть два плохих варианта: либо восстановление настолько простое, что им может воспользоваться злоумышленник (и тогда 2FA обходится через поддержку), либо настолько сложное, что доступ теряется вместе с телефоном.

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

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