report · Конструктор отчетов 26.200.0 Безопасность

Report 26.200.0: белый список агрегатов против SQL-инъекции и самопочинка дат в фильтрах

2 мин чтения

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

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

В модуле конструктора отчётов report закрыта SQL-инъекция через агрегатную функцию из настроек отчёта, которая попадала в запрос без проверки. Ещё одна правка чинит отчёты, которые падали при каждом открытии из-за даты с годом вне диапазона БД, и ради этого report.view может сам переписать b_report.SETTINGS. Сигнатуры совместимы, схема БД не менялась.

Проверьте отчёты с нестандартными агрегатами

CReport::prepareSelectViewElement() и CReport::appendHrefSelectElements() вставляли в SQL значение aggr из настроек колонки. По комментарию в коде, aggr может прийти из импортированных или вручную собранных настроек в обход проверки в интерфейсе, и тогда это SQL-инъекция. Теперь значение сверяется с белым списком:

        protected static $allowedAggrFuncs = [
	'SUM', 'AVG', 'MIN', 'MAX', 'COUNT_DISTINCT', 'GROUP_CONCAT'
];

    

Если aggr не пустой и в список не входит, обе функции бросают BXUserException с фразой REPORT_INVALID_AGGREGATION_FUNCTION. Отчёт с таким значением в настройках после обновления не построится.

Найти такие отчёты заранее можно новым CReport::isAllowedAggregationFunction(). Смотреть надо в двух местах. Первое — aggr самой колонки, второе — aggr элементов href['elements'], их проверяет appendHrefSelectElements().

        <?php declare(strict_types=1);

use Bitrix\Main\Loader;
use Bitrix\Report\ReportTable;

Loader::requireModule('report');

$rows = ReportTable::query()
	->setSelect(['ID', 'TITLE', 'SETTINGS'])
	->fetchAll();

foreach ($rows as $row)
{
	$settings = unserialize((string)$row['SETTINGS'], ['allowed_classes' => false]);

	foreach ((array)($settings['select'] ?? []) as $column)
	{
		$candidates = [$column['aggr'] ?? ''];

		foreach ((array)($column['href']['elements'] ?? []) as $hrefElement)
		{
			$candidates[] = $hrefElement['aggr'] ?? '';
		}

		foreach ($candidates as $aggr)
		{
			// то же условие, что в ядре: непустое и не из белого списка
			if (!empty($aggr) && !CReport::isAllowedAggregationFunction($aggr))
			{
				printf("#%d %s: aggr=%s\n", $row['ID'], $row['TITLE'], var_export($aggr, true));
			}
		}
	}
}

    

Сам список отдаёт CReport::getAllowedAggregationFunctions().

Отчёт с битой датой больше не падает при каждом открытии

Судя по комментариям в коде, дата с годом за пределами диапазона БД (например, пятизначным) превращалась в пустой литерал даты в SQL и роняла отчёт. Такая дата сохранялась в настройки отчёта или в персональные параметры просмотра, и отчёт падал на каждом открытии. Сохранённый просмотр к тому же редиректил на те же сломанные параметры.

Как теперь:

  • report.construct не даёт сохранить период, если в нём год вне диапазона БД или строка, которая не разбирается как дата. В условиях фильтра по полю даты отклоняется год вне диапазона и строка, которую не разбирает даже strtotime(), поэтому относительные значения вроде today и -1 week проходят. В обоих случаях форма показывает REPORT_ERR_FILTER_DATE_INVALID, условие BETWEEN проверяется по каждой границе.
  • report.view сохраняет свежий фильтр в персональные параметры только после валидации. Раньше он писал их до проверки.
  • Если битая дата лежит в сохранённом периоде отчёта, а пользователь может редактировать отчёт и отчёт не стандартный (MARK_DEFAULT), период сбрасывается на текущий месяц, настройки сохраняются, пользователь видит сообщение REPORT_FILTER_DATE_FIXED. Остальные получают REPORT_ERR_FILTER_DATE_LOCKED с просьбой обратиться к владельцу.
  • Битое условие фильтра из сохранённых настроек выбрасывается, а определение отчёта пересохраняется из снимка настроек, снятого до всех runtime-преобразований. Права и сообщения те же, что для периода.
  • Сломанный персональный просмотр удаляется только у текущего пользователя и только для текущего шаблона.
  • Строка в поле даты, которая не разбирается ни как абсолютная, ни как относительная дата, раньше молча превращалась в текущую дату. Теперь это ошибка для свежего ввода и повод починить сохранённое значение.
  • Если при выгрузке в Excel случилась такая ошибка, файл не отдаётся. Раньше пользователь скачал бы пустой «успешный» отчёт.

Открытие отчёта теперь может изменить его определение в b_report.SETTINGS. Внешний код, который держит копию этих настроек или сравнивает их с эталоном, после такого открытия увидит другой период или меньше условий фильтра.

Возьмите готовый классификатор дат

Для проверки дат в CReport добавлены публичные статические методы:

  • classifyFilterDate($rawValue, $format = false) возвращает ['type' => ..., 'timestamp' => ...], где тип — одна из констант FILTER_DATE_EMPTY, FILTER_DATE_VALID, FILTER_DATE_INVALID или FILTER_DATE_UNRESOLVED;
  • isFilterDateInvalid(): дата разобрана, но в БД не влезет;
  • isFilterDateUnusable(): то же плюс строка, которая не разбирается и как относительная дата;
  • isTimestampStorable($timestamp): сможет ли Bitrix\Main\Type\DateTime представить эту метку;
  • periodInputHasInvalidDate($type, $rawFrom, $rawTo, $format = false) и periodHasUnstorableDate($period) проверяют период из запроса и из сохранённых настроек;
  • updateSettings($ID, $settings, $viewParamsUserId = null) обновляет только колонку SETTINGS, не трогая заголовок, описание и MARK_DEFAULT. Даты он сам не проверяет, а после записи зовёт clearViewParams($ID, $viewParamsUserId), так что без третьего аргумента сохранённые просмотры отчёта пропадут у всех пользователей.

Если у вас свой фильтр по датам поверх отчётов, классификатор можно взять как есть:

        <?php declare(strict_types=1);

use Bitrix\Main\Loader;

Loader::requireModule('report');

$type = CReport::classifyFilterDate($rawDate)['type'];

$hint = match ($type) {
	CReport::FILTER_DATE_EMPTY => 'дата не задана',
	CReport::FILTER_DATE_VALID => 'можно фильтровать',
	CReport::FILTER_DATE_INVALID => 'дата разобрана, но в БД не влезет',
	CReport::FILTER_DATE_UNRESOLVED => 'не абсолютная дата, возможно today или -1 week',
};

    

У CReport::clearViewParams($id, $onlyUserId = null) появился второй параметр. Без него метод, как и раньше, чистит сохранённые просмотры отчёта у всех пользователей, а с ним только у одного:

        CReport::clearViewParams($reportId, onlyUserId: $userId);

    

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

  • Комментарии в report.view/component.php трижды объясняют, что clearViewParams() снёс бы просмотры у всех пользователей, поэтому компонент зовёт CUserOptions::DeleteOption() напрямую. Метод clearViewParams() в том же релизе научился чистить просмотры одного пользователя, но снёс бы их по всем шаблонам, а компоненту нужен только текущий.
  • В report.view/component.php из 315 добавленных строк 115 занимают комментарии.
  • Новые фразы REPORT_* добавлены в lang-файлы компонентов report.view и report.construct. Lang-файла для classes/general/report.php в изменениях нет.
  • updateSettings() написан на старом $DB->PrepareUpdate и QueryBind, без ORM.
  • В install/version.php массив переписан на короткий синтаксис. Дата версии 26.200.0 — 1 сентября 2026.
  • Deprecated, удалённых методов и изменений схемы БД нет.

Что делать

  • Прогоните проверку aggr по своим отчётам, особенно по импортированным и созданным программно.
  • Если отчёты создаются или правятся кодом, проверьте даты в period и filter на год вне диапазона БД и нераспознаваемые строки.
  • Предупредите тех, кто редактирует отчёты: при открытии отчёта с битой датой период может сброситься на текущий месяц.

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

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 мин
ui 26.650.0 Рутинное Свежее

UI 26.650.0: клавиатура в пикере реакций и аудио во вьюере

Релиз на 2 МБ, из PHP в нём только `config.php` расширений и номер версии. Пикером реакций теперь можно управлять с клавиатуры (роль `menu`, стрелки, Escape), у него появились опции `priorityReaction` и `contextAction` и методы `focus()` и `destroy()`. Если пикер привязан к кнопке, ссылке или элемен...

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

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

на связи

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

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

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

Войти