Lists 26.300.0–26.400.0: закрыты XSS в Живой ленте и удаление чужих процессов, notifyAdmin пишет только администраторам
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
Модуль lists вышел двумя версиями, 26.300.0 и 26.400.0, и обе закрывают дыры. В 26.300.0 закрыли XSS при создании процесса из Живой ленты и уведомления «администратору», которые уходили любому сотруднику с любой ссылкой. В 26.400.0 сотрудник больше не удалит бизнес-процесс чужого документа. Перед обновлением проверьте наследников действия «Создать элемент списка», свои вызовы notifyAdmin и шаблоны БП, которые сравнивают текст HTML-свойств.
Новое
Пишите в «Деньги» число без валюты (26.300.0)
BizprocDocument::updateDocument() вызывается, когда элемент списка меняет бизнес-процесс (например, действием «Изменение документа») или ваш код через сервис документов bizproc. Для свойств типа «Деньги» (S:Money) метод теперь сам дописывает к числу без валюты базовую валюту портала в формате сумма|валюта. Если у валюты десятичный разделитель не точка, сумма переписывается под него без разделителя тысяч. Готовое значение проверяет само свойство, и при ошибке в элемент уходит исходная строка. Строки с минусом и запись вида 1e3 метод не трогает, их собирайте в формате сумма|валюта сами. Значение, где валюта уже стоит после |, уходит как есть.
<?php declare(strict_types=1);
use Bitrix\Main\Loader;
Loader::requireModule('bizproc');
Loader::requireModule('lists');
$documentId = \BizprocDocument::getDocumentComplexId('lists', 42); // тип инфоблока (у обычных списков lists) и ID элемента
// в свойство с кодом BUDGET уйдёт сумма с базовой валютой через «|»
\CBPRuntime::getRuntime()
->getDocumentService()
->updateDocument($documentId, ['PROPERTY_BUDGET' => '1500.50']);
Что заметят пользователи (26.300.0)
Двойной клик по «Сохранить» в форме добавления элемента больше не создаёт дубль. Скрипт шаблона проверяет поля, блокирует кнопку и отправляет форму один раз. Защита есть только в шаблоне .default компонента lists.element.edit и только для форм с formId, который начинается с lists_element_add_. Если у вас своя копия шаблона, перенесите в неё изменения script.js.
Что сломается
Допишите третий параметр в наследника «Создать элемент списка» (26.300.0)
У CBPCreateListsDocumentActivity появился свой метод prepareFieldsValues(array $documentType, array $values, ?array $documentFields = null): array, у родителя CBPCreateDocumentActivity параметров по-прежнему два. Если ваше действие в /local/activities/ наследует CBPCreateListsDocumentActivity и переопределяет этот метод с двумя параметрами, PHP остановится с fatal error из-за несовместимой сигнатуры, как только подключит файл действия.
Добавьте третий параметр и передайте его родителю, тогда поля документа не будут загружаться второй раз.
<?php declare(strict_types=1);
CBPRuntime::getRuntime()->includeActivityFile('CreateListsDocumentActivity');
final class CBPVendorCreateListsDocumentActivity extends CBPCreateListsDocumentActivity
{
protected function prepareFieldsValues(
array $documentType,
array $values,
?array $documentFields = null,
): array {
$values['NAME'] = trim((string)($values['NAME'] ?? ''));
return parent::prepareFieldsValues($documentType, $values, $documentFields);
}
}
Проверьте свои вызовы notifyAdmin (26.300.0)
Компонент lists.element.edit предлагает написать администратору, когда константы шаблона БП не настроены и у пользователя нет прав администратора списка. AJAX-действие notifyAdmin теперь проверяет адрес и получателя и при отказе отвечает ошибкой (фраза LISTS_NOTIFY_ERROR). elementUrl должен быть путём от корня сайта с одним / в начале, без \, управляющих символов, кавычек и угловых скобок. Получатель userId должен быть администратором списка, то есть иметь полный доступ к инфоблоку, выданный лично (код U<id>), или быть активным пользователем в группе администраторов.
Штатное окно показывает как раз этих людей, для него ничего не меняется. Сломаются свои шаблоны и скрипты, которые шлют уведомление другому сотруднику или передают адрес со схемой.
Если в параметре LIST_ELEMENT_URL компонента стоит абсолютный адрес вида https://..., уведомление уйдёт со ссылкой /<каталог>/https://..., потому что компонент приклеивает текущий каталог ко всему, что не начинается с /. Замените адрес на путь от корня сайта.
Шаблоны БП получат HTML-свойства без сущностей (26.300.0)
BizprocDocument::getDocument() после HTMLToTxt() прогоняет значения HTML-свойств, одиночных и множественных, через html_entity_decode(). Бизнес-процесс получает &, <, > и ' там, где раньше были &, <, > и ', а из визуального редактора становится неразрывным пробелом (U+00A0), который на глаз не отличить от обычного. Кавычки " раскодировал ещё HTMLToTxt(), для них ничего не меняется. В текстах, куда шаблон подставляет такое свойство, амперсанды и угловые скобки станут обычными. Условия, которые сравнивали свойство со строкой в сущностях, перестанут срабатывать.
Если ваше действие раскодировало значение само, уберите этот вызов, теперь он лишний.
Что закрыли
- 26.300.0. Создание процесса из Живой ленты (
lists.live.feed) бралоNAME,PREVIEW_TEXTиDETAIL_TEXTпрямо из$_POST. Теперь из названия вырезаются теги, а из описания с визуальным редактором (USE_EDITOR=Y) удаляется активное содержимое: скрипты, обработчики событий, ссылкиjavascript:. Санитайзер подключён только здесь, сохранение основной формыlists.element.editрелиз не менял. - 26.300.0.
notifyAdminбольше не отправит уведомление с произвольной ссылкой произвольному сотруднику. - 26.300.0. Файлы списков рабочих групп в CRM (
lists.element.attached.crm) скачиваются по правам в группе, то есть по роли участника и только при включённых в группе «Списках». - 26.400.0.
CLists::completeWorkflow()проверял права на элемент изelementId, а процесс удалял поworkflowIdиз запроса, не сверяя их. Сотрудник с правами на один элемент мог удалить процесс любого документа портала, если знал ID процесса. ТеперьkillWorkflow()получает$documentIdи возвращает ошибку (фразаBPCGDOC_INVALID_WF_MSGVER_1), если процесс принадлежит другому документу, а сам процесс остаётся на месте. Штатныеlists.element.editиlists.listшлют согласованную пару и разницы не заметят.
Если ваш контроллер удаляет процессы по ID из запроса, передавайте документ третьим аргументом так же, как теперь делает lists:
<?php declare(strict_types=1);
namespace Vendor\Module\Application\Service;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Result;
final class WorkflowRemover
{
public function remove(string $workflowId, array $documentId, int $userId): Result
{
$result = new Result();
Loader::requireModule('bizproc');
$canDelete = \CBPDocument::canUserOperateDocument(
\CBPCanUserOperateOperation::CreateWorkflow,
$userId,
$documentId,
);
if (!$canDelete) {
return $result->addError(new Error('Access denied', 'ACCESS_DENIED'));
}
// без $documentId удалится любой процесс, чей ID прислали
foreach (\CBPDocument::killWorkflow($workflowId, true, $documentId) as $error) {
$result->addError(new Error((string)$error['message']));
}
return $result;
}
}
Для своих форм, которые создают элементы инфоблока из POST, подойдёт приём из Живой ленты: strip_tags() для названия и CBXSanitizer с уровнем SECURE_LEVEL_LOW и applyDoubleEncode(false) для HTML. В Живой ленте чистка не зависит от модуля bitrix24, а админская форма инфоблока включает такой же CBXSanitizer только при установленном bitrix24.
Мелочи и находки
- В форме создания процесса из Живой ленты у полей «Дата/время» только для чтения появились скрытые
input, так что их значение уходит вместе с формой (26.300.0). - Если значение одиночного поля типа «Список» приходит в «Создать элемент списка» ассоциативным массивом, действие берёт его первый ключ (26.300.0).
- Поле «Дата» или «Дата/время» в
lists.field.editсо значением по умолчанию, пришедшим массивом, теперь сохраняется. Компонент берёт первый непустой элемент, а раньше отвечал ошибкой «Неверный формат поля „Значение по умолчанию“». Значение'0'теперь тоже проверяется как дата и получает эту ошибку (26.300.0). Прошлую правку компонента мы разбирали в lists 26.200.100.