security · Проактивная защита 26.300.0 Безопасность

Security 26.300.0: xscan ищет файлы-маркеры взлома и снова работает на новом main

2 мин чтения Устаревших API: 1
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

Обновление безопасности

Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.

Security 26.300.0 чинит и усиливает сканер «Поиск троянов» (xscan). Он ищет семь файлов, которые остаются на сайте после взлома, проверяет файлы, чьё имя отличается от системного только регистром, и снова запускается на main 26.900.0 и новее. А на порталах с push-OTP и обязательной 2FA резервный email из security 26.200.0 теперь можно подтвердить ещё до входа.

Новое

Проверьте сайт на файлы-маркеры

Новый метод CBitrixXscan::CheckBadFiles() срабатывает при каждом запуске сканирования, вместе с проверкой событий, агентов и шаблонов. Он ищет в корне сайта такие файлы:

  • /bitrix/templates/.mod.txt
  • /b_user.json
  • /bitrix/tools/.sess
  • /bitrix/tools/.idSess
  • /.security.log
  • /bitrix/components/bitrix/app.layout/params.php
  • /info_files.tar.gz

Каждая находка попадает в результаты с типом file и сообщением bad file. Маркер в app.layout похож на штатный .parameters.php из того же компонента, но файла params.php в bitrix:app.layout нет.

Список зашит в код модуля, и сверить с ним можно любой сайт, даже где security ещё не обновлён. Скрипт для крона завершается с кодом 1, если нашёл хоть один маркер:

        <?php

declare(strict_types=1);

// php check-markers.php /var/www/site
$root = rtrim($argv[1] ?? getcwd(), '/');

$markers = [
    '/bitrix/templates/.mod.txt',
    '/b_user.json',
    '/bitrix/tools/.sess',
    '/bitrix/tools/.idSess',
    '/.security.log',
    '/bitrix/components/bitrix/app.layout/params.php',
    '/info_files.tar.gz',
];

$found = array_filter(
    $markers,
    static fn (string $path): bool => is_file($root . $path),
);

foreach ($found as $path) {
    printf("%s\t%s\n", $path, date('Y-m-d H:i:s', (int) filemtime($root . $path)));
}

exit($found === [] ? 0 : 1);

    

По дате изменения рядом с путём видно, с какого дня искать следы в логах.

Перезапустите сканирование

С main 26.900.0 в ядре лежит php-parser ветки 5.x. У его ParserFactory нет ни create(), ни PREFER_PHP7, а сканер из security 26.200.0 вызывал их в конструкторе, так что на свежем main xscan падал, не начав обход. Теперь он берёт createForHostVersion(), а старый вызов оставлен запасным для php-parser 4 из main 26.800.0 и более ранних версий.

Белый список системных файлов в CBitrixXscan::SystemFile() стал регистрозависимым. Раньше файл, чьё имя отличалось от записи в списке только регистром, считался системным и проверку пропускал, теперь сканер его разбирает. Вызовы Bitrix\Call\Rest\Auth::authorizeById, наоборот, больше не помечаются ни в обработчиках событий ([050] dangerous method at event, check arguments), ни в коде файлов ([307] danger method).

В гриде результатов («Поиск троянов» → «Сканирование файлов») появился пресет фильтра not modified. Он прячет строки [201] file was modified, то есть файлы, которые не совпали с эталонным хешем. Файл, у которого кроме [201] есть другие находки, пресет не прячет. Когда ядро правлено руками, строки [201] забивают грид, и с пресетом проще увидеть остальное. Изменённый файл ядра тоже может оказаться закладкой, так что хотя бы раз просмотрите выдачу без пресета. Подпись у пресета не переведена, в списке он стоит по-английски рядом с not /bitrix/modules.

Резервный email при обязательной 2FA подтверждается до входа

Если на портале 2FA обязательна, пользователь, который её ещё не настроил, после логина и пароля останавливается на шаге OTP. Теперь модуль разрешает ему на этом шаге указать резервный адрес и подтвердить его кодом из письма, ещё не войдя. Для этого с действий security.pushotp.sendConfirmationEmail и security.pushotp.confirmEmail сняли фильтр авторизации.

Неавторизованный запрос проходит, только если выполнено всё сразу:

  • запрос пришёл через AJAX;
  • в сессии записан отказ во входе из-за обязательной 2FA;
  • OTP включён и обязателен;
  • пользователь активен, пропустить 2FA не может и ещё её не активировал.

Иначе действие вернёт ошибку OTP_PARAMS. CSRF-проверка осталась. Ещё до этих условий оба действия, как и раньше, проверяют настройки канала. Без опции otp_allow_email они отвечают EMAIL_DISABLED, а если Otp::isPushPossible() возвращает false, то PUSH_OTP_DISABLED. Для push-OTP нужны установленный модуль mobile, модуль pull и включённый сервер очередей.

sendConfirmationEmail принимает email и возвращает signedData и timeLeft. confirmEmail ждёт шестизначный code и тот же signedData. На «Бизнесе» сценарий не заработает и с вашей формой, потому что в нашем эталоне БУС нет модуля mobile и оба действия отвечают PUSH_OTP_DISABLED. Штатные формы входа этого эталона их и не вызывают.

Мелочи и находки

  • Защищённый статический Otp::getCacheDir() заменён на getCacheId(), который возвращает пару [cacheId, cacheDir], а ключ кеша данных OTP сменился с otp_users_{id}_v1 на otp_users_{id}_v2. Метод нужен только наследникам Otp, а их ядро не создаёт: Otp::getByUser() возвращает new self. Если чистили кеш OTP по ключу руками, переходите на Otp::cleanCache($userId).
  • В кеше OTP теперь лежит логин пользователя, а CSecurityUser::onAfterUserUpdate() сбрасывает этот кеш, только если в полях обновления есть ACTIVE. Если ваш код меняет логин через CUser::Update() без ACTIVE, письмо подтверждения адреса может прийти со старым логином, пока кеш не истечёт. По умолчанию он живёт сутки (86400 секунд).
  • Счётчик редиректов LOCAL_REDIRECTS переехал из kernel-сессии в Application::getLocalSession('security_redirect') и пишется только при запущенной сессии. Если ваш код читал этот ключ, ищите его там.
  • Модуль ушёл с устаревших CHTTP::sGet() и CHTTP::isPathTraversalUri(). В своём коде меняйте их на те же (new HttpClient())->get($url) и (new Uri($uri))->isPathTraversal() из Bitrix\Main\Web. HttpClient по умолчанию идёт за редиректами, до пяти переходов, поэтому тест окружения теперь тоже по ним ходит.

Устаревшие и удалённые API в этой версии

Символ Статус Чем заменять
Bitrix\Security\Mfa\Otp::getCacheDir() Удалено Bitrix\Security\Mfa\Otp::getCacheId()

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

Читайте дальше

Security 26.200.0: одноразовый пароль можно получить на резервный email

В security 26.200.0 у push-OTP появился третий канал доставки кода. Раньше код приходил пушем в мобильное приложение или по SMS, теперь его можно получить письмом на резервный адрес, который пользователь подтверждает кодом. Канал выключен по умолчанию, так что сам релиз ничего не включит. Внимательн…

6 мин
ui 26.700.0 Безопасность Свежее

UI 26.696.0–26.700.0: закрыты дыры в правах на карточки, Lexical 0.50 и подсказки ui.system

Вышли две версии ui: 26.696.0 научила холст `ui.block-diagram` копировать блоки, а 26.700.0 на 5290 файлов принесла расширения `ui.system`, Lexical 0.50 и права доступа V2 со значениями из entity-selector. Severity security из-за 26.700.0, где закрыты копирование чужого представления карточки, выдач…

2 мин
translate 26.100.100 Рутинное Свежее

Translate 26.100.100: страница «Перевод языковых файлов» не падает на FTS-таблице удалённого языка, skipCache в getLanguages() работает в каждом вызове

Translate 26.100.100 вышел патчем в две строки после [26.100.0](/bitrix-updates/translate/translate-26-100-0), публичный API и схема БД не менялись. У внутреннего (`@internal`) класса `Bitrix\Translate\Config` метод `getLanguages(skipCache: true)` раньше обходил кэш, только если вызов был первым за…

1 мин
sale 26.600.0 Безопасность Свежее

Sale 26.600.0: закрыты дыры в правах контроллеров и REST, появился провайдер покупателей

Как и [26.500.0](/bitrix-updates/sale/sale-26-500-0), Sale 26.600.0 закрывает пачку дыр в безопасности, на этот раз в правах контроллеров и REST. Контроллеры статистики интеграции с Б24 и синхронизатора не проверяли права на модуль sale, а `sale.order.payment.change` открывал чужую оплату; REST заяв…

2 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…

Можно закрыть чат и перейти на другую страницу. Когда ответ будет готов, на кнопке Домового появится отметка.

Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти