sale · Интернет-магазин 26.500.0 Безопасность

Sale 26.500.0: пачка защитных правок от личного кабинета до импорта местоположений

5 мин чтения

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

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

Sale 26.500.0 почти целиком состоит из точечных защитных правок: CSRF-проверки в ajax-обработчиках личного кабинета, сверка платежа с платёжной системой, ограничения на пути и адреса при импорте местоположений, экранирование в админке и печатных формах. Заодно установщик модуля переехал с install.sql на миграции ядра, а в описании схемы для новых установок шесть денежных колонок платежей и отгрузок получили точность decimal(26,8). ALTER для существующих баз в диффе нет. Сломаться после обновления могут кастомные шаблоны личного кабинета, фронтенд оплаты, своя касса и скрипты импорта местоположений.

Проверьте запросы к ajax.php личного кабинета

В ajax.php компонентов sale.order.payment.change, sale.personal.order.detail и sale.personal.order.list исправили одну логическую операцию. Раньше проверка в начале обработчика выглядела так:

        if (!check_bitrix_sessid() && !$request->isPost())

    

С && скрипт умирал, только если не было ни сессии, ни POST. POST-запрос без sessid проходил, GET-запрос с sessid тоже. Теперь там ||, и нужны оба условия сразу. templateName во всех трёх читается только из POST и приводится к строке. В sale.personal.order.detail и sale.personal.order.list так же читается orderData, и из него выбрасываются нескалярные значения. sale.order.payment.change берёт данные заказа, как и раньше, из getPostList()->toArray() без этого фильтра.

Если кастомный шаблон этих компонентов шлёт запросы GET-ом или забывает sessid, после обновления он получит пустой ответ.

Платёж должен принадлежать платёжной системе из запроса

tools/sale_ps_ajax.php и tools/sale/applepay_gateway.php раньше вызывали initiatePay() для платежа по PAYMENT_ID и сервиса по PAYSYSTEM_ID, не проверяя, связаны ли они. Теперь запрос обрабатывается, только если заказ найден, платёж в нём есть и $payment->getPaymentSystemId() совпадает с PAYSYSTEM_ID из запроса. Во всех остальных случаях первый скрипт вернёт SALE_PS_AJAX_PARAMS_ERROR, второй STATUS_FAIL.

Фронтенд, который передаёт в sale_ps_ajax.php одну платёжную систему для платежа, заведённого на другую, теперь получит ошибку.

Импорт местоположений пускают только во временную папку

Функции saleLocationLoadFile() и saleLocationImport() из general/location_import.php раньше брали TMP_PATH, DLZIPFILE и адрес сервера из параметров как есть. Теперь:

  • TMP_PATH проходит через новый Import\TempPath::resolve() и должен быть абсолютным путём внутри временного каталога Битрикса (CTempFile::GetAbsoluteRoot()), причём проверка идёт и по realpath(), так что симлинк наружу не поможет;
  • DLZIPFILE обязан быть ровно 'zip_ussr.csv';
  • скачивание идёт через новый Import\Downloader, который пропускает только http и https без управляющих символов и запрещает запросы на приватные IP (setPrivateIp(false)).

Неверный TMP_PATH или DLZIPFILE функции отклоняют сразу с ошибкой SL_IMPORT_ERROR_FILES. Если адрес не пропустил Downloader, download() вернёт null, и на шаге скачивания выйдет ошибка загрузки SL_LOADER_FILE_ERROR. Скрипты мастера sale.locations (scripts/loader.php, scripts/import.php) теперь требуют POST и check_bitrix_sessid(), а его import.js переключили с CPHttpRequest.Send на CPHttpRequest.Post.

Своим скриптам, которые передают TMP_PATH за пределами временного каталога, например /home/bitrix/geo/, придётся перенести папку внутрь него. По коду main в эталонной установке это папка из опции upload_dir (по умолчанию upload) плюс /tmp в корне сайта, если не задана константа BX_TEMPORARY_FILES_DIRECTORY.

Пробейте тестовый чек

В кассовом модуле поменялся выбор ставки НДС для позиции чека. Cashbox\Check::getProductVatId() раньше брал НДС товара из каталога и только если его не было, искал ставку по BasketItem::getVatRate(), причём лишь при ставке больше нуля. Теперь порядок обратный. Сначала ищется ставка из корзины, включая нулевую, и только потом берётся НДС товара из каталога.

AbstractCheck::getVatIdByVatRate() сравнивает ставки как строки с двумя знаками после точки (number_format(..., 2, '.', '')) вместо (int), пропускает ставки с RATE = null и нечисловые значения. Для товаров, у которых ставка в корзине расходится с каталогом, в чек уйдёт другой идентификатор ставки.

Цена строки из формы товаров считается заново

Helpers\Order\Builder\Converter\CatalogJSProductForm превращает строки JS-формы товаров в позиции корзины. Приватный consistentFields() заменили на resolvePriceInBaseCoords(). PRICE теперь берётся из price при taxIncluded = 'Y' и из priceExclusive при 'N', раньше всегда из priceExclusive ?? price. DISCOUNT_PRICE считается как PriceMaths::roundPrecision(basePrice - price), а присланные формой discount и discountRate больше не читаются. Соберите на тестовом стенде заказ из формы в режиме «НДС сверху» и сверьте цену и скидку позиции.

Новое: TempPath и Downloader

Оба класса появились ради импорта местоположений, но они публичные и пригодятся в своём импорте, если нужно скачать файл и положить его во временную папку:

        <?php declare(strict_types=1);

use Bitrix\Main\Loader;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Sale\Location\Import\Downloader;
use Bitrix\Sale\Location\Import\TempPath;

Loader::requireModule('sale');

$dir = TempPath::resolve(null, 'vendor_geo');
// для null каталог только вычисляется, создать его нужно самим
if ($dir === null || !CheckDirPath($dir)) {
    throw new \RuntimeException('Не удалось подготовить временную папку');
}

$csv = (new Downloader(new HttpClient()))->download(
    server: 'https://geo.example.com',
    port: null,
    path: '/export/',
    fileName: 'cities.csv',
    method: 'GET',
);

if ($csv !== null) {
    file_put_contents($dir . 'cities.csv', $csv);
}

    

С null вместо пути TempPath::resolve() отдаёт путь CTempFile::GetDirectoryName(12, $defaultContext) со слешем в конце, но саму папку не создаёт, поэтому в примере стоит CheckDirPath(). Переданный путь метод возвращает, только если тот лежит внутри временного каталога, и заодно создаёт папку. Иначе будет null. Downloader::download() склеивает адрес из сервера, порта, пути и имени файла и возвращает null, если адрес не прошёл проверку или ответ пришёл не строкой.

БД и установщик

Файлы install/db/{mysql,pgsql}/install.sql и uninstall.sql удалены, InstallDB() и UnInstallDB() вызывают унаследованные от CModule installMigrations() и uninstallMigrations(). Таблицы описаны в install/migrations/tables.php, агенты в agents.php (семь штук, от Product2ProductTable::deleteOldProducts(10) до агентов аналитики), события в events.php. В migration_config.json в качестве defaultTableName указана b_sale_auxiliary. Кроме install/components, install/js и install/tools, в маппинг каталогов входят install/gadgets, install/images и install/wizards.

Из обработчиков событий 77 описаны декларативно через register() и registerCompatible(), а ещё 24 старых вызова RegisterModuleDependences() остались как есть и обёрнуты в проверку $migration->context()->getDatabaseUpdateMode(). При ModuleInstall они регистрируются, при ModuleUninstall снимаются. Среди них main:OnBeforeProlog с подключением файла /modules/sale/affiliate.php. В сумме выходит 101 обработчик, столько же, сколько было в старом InstallDB().

tables.php описывает те же 146 таблиц, что и старый MySQL-скрипт, кроме шести колонок. SUM, PS_SUM, PRICE_COD в b_sale_order_payment и PRICE_DELIVERY, DISCOUNT_PRICE, BASE_PRICE_DELIVERY в b_sale_order_delivery в install.sql были decimal(18,4), в миграции стали decimal(26,8). В ORM эти поля PaymentTable и ShipmentTable получили 'scale' => 8. ALTER для существующих баз в диффе нет, так что после обновления стоит посмотреть, какая точность у колонок на вашей базе.

Ещё одно расхождение касается PostgreSQL. В старом pgsql/install.sql у TIMESTAMP_X девяти таблиц (b_sale_affiliate, b_sale_user_account, b_sale_recurring и других) стоял DEFAULT CURRENT_TIMESTAMP, в миграции его нет.

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

  • Ни одна правка в диффе не помечена как исправление уязвимости.
  • Несколько защитных правок пришлись на админку. Кнопка создания стандартных отчётов в admin/report.php получила проверку sessid. Сортировка в admin/exchange_log.php принимает только поля из заголовков таблицы и направления ASC/DESC. Создание Z-отчёта кассы в admin/order_ajax.php требует права на модуль не ниже W. Названия политик eBay в ebay_policy.php и ebay_wizard.php теперь экранируются.
  • admin/print.php экранирует все строковые значения $arOrderProps до подключения шаблона печатной формы. Если ваш шаблон экранирует свойства заказа сам, после обновления получится двойное экранирование. Название товара в invoice.php и invoice_en.php тоже выводится через htmlspecialcharsbx().
  • Добавление платёжной системы через Controller\Action\PaySystem\AddPaySystemAction проверяет ACTION_FILE. Это должна быть папка обработчика или точный, с учётом регистра, код REST-обработчика, сегменты ., .. и пустые запрещены. Для отказа завели код ADD_PAY_SYSTEM_ACTION_ACTION_FILE_INVALID = 202650000015.
  • Из sale убрали CHTTP::urlAddParams(), urlDeleteParams(), URN2URI(), создание new CHTTP() и QueryGetData(). Вызовы CHTTP::SetStatus() в модуле остались. Старые обработчики доставки DHL USA, CPCR и EMS и проверка HTTPS в admin/ymarket.php перешли на HttpClient. У DHL USA и CPCR протокол теперь выбирается по порту из констант: 80 даёт http, любой другой https.
  • Helpers\Rest\Http создаёт HTTP-клиент через Rest\Public\Provider\Application\HandlerHttpFactory, если модуль rest установлен и класс существует.
  • TargetSaleMailConnector::onConnectorList() теперь возвращает пустой массив, и коннектор больше не предлагается модулю sender.
  • В sale.personal.section у отменённого заказа ссылка на список заказов теряет filter_history=Y. Раньше show_canceled=Y добавлялся к уже изменённому пути, теперь к исходному.
  • В удалённом install1.sql лежали таблицы b_catalog_currency* с курсом доллара 30.2979. Ссылок на файл в диффе установщика нет.
  • VERSION_DATE у 26.500.0 указан 15 июля 2026, раньше, чем у предыдущей 26.450.0 (11 сентября).
  • Deprecated в релизе нет.

Что делать

  • Проверьте кастомные шаблоны sale.order.payment.change, sale.personal.order.detail и sale.personal.order.list: запросы к их ajax.php должны идти POST-ом и с sessid.
  • Если вызываете saleLocationLoadFile() или saleLocationImport() сами, проверьте, что TMP_PATH лежит внутри временного каталога Битрикса, или не передавайте его вовсе.
  • Пробейте тестовый чек по товару, у которого ставка НДС в корзине отличается от ставки в каталоге.
  • Посмотрите тип колонок b_sale_order_payment.SUM и b_sale_order_delivery.PRICE_DELIVERY на своей базе после обновления.
  • Если печатная форма заказа своя, откройте её и проверьте, нет ли в свойствах заказа &amp;quot;.

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

sale 26.450.0 Ломающее Свежее

Sale 26.450.0: «Сбербанк Онлайн» переехал на новый шлюз и сломал наследников

Весь sale 26.450.0 посвящён одному обработчику: `SberbankOnlineHandler` переписан с нуля под новый шлюз Сбера. Схема БД и ядро модуля не тронуты, дифф небольшой (11 файлов, +1195/−604), и магазинам без Сбера и Альфы можно обновляться спокойно. Если же магазин принимает оплату через Сбер или у вас в...

4 мин
voximplant 26.700.0 Ломающее Свежее

Voximplant 26.700.0: оценку качества связи убрали, у notifyAdmins() появился тип

Модуль телефонии voximplant 26.700.0 вышел в коробочный Битрикс24 11 сентября 2026 года, по данным официального канала «Битрикс24 changelog». Из карточки звонка убрали оценку качества связи вместе с её JS-событиями, у `Im::notifyAdmins()` появился тип параметра, а публичные константы `SipStatusInfor...

3 мин
ui 26.687.0 Рутинное Свежее

UI 26.687.0: триал VibePlus в облаке и дизайн чипа TintedBitrixGpt

`Bitrix\UI\Controller\InfoHelper` при активации демо сначала спрашивает у модуля bitrix24, включён ли старт VibePlus, и если да, запускает триал VibePlus вместо обычного демо тарифа. Ветка срабатывает только при подключённом модуле bitrix24 и определённой константе `BX24_HOST_NAME`. В `ui.system.chi...

1 мин
ui 26.675.0 Ломающее Свежее

UI 26.675.0: ui.actionpanel переехал в бандл, а rich_text по-новому обходится с квадратными скобками

Модуль ui 26.675.0 собран 26 августа 2026. Из PHP поменялись `Converter` и `Whitelist` в `Bitrix\UI\Format\BBCode`, и пользовательские поля типа `rich_text` теперь иначе сохраняют и индексируют текст с квадратными скобками. На фронтенде старую панель групповых действий `ui.actionpanel` перевели на б...

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

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

на связи

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

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

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

Войти