Skip to content

security: escape the WMI hostname and namespace before exec - #13

Open
somethingwithproof wants to merge 4 commits into
Cacti:developfrom
somethingwithproof:security/escape-wmi-hostname-namespace
Open

security: escape the WMI hostname and namespace before exec#13
somethingwithproof wants to merge 4 commits into
Cacti:developfrom
somethingwithproof:security/escape-wmi-hostname-namespace

Conversation

@somethingwithproof

@somethingwithproof somethingwithproof commented Aug 30, 2026

Copy link
Copy Markdown
Member

Linux_WMI::clean() escaped the username, password, binary and command with cacti_escapeshellarg, but left the device hostname only trim()'d and the query namespace untouched. getcommand() then interpolated both raw into the wmic command line that exec() runs on the Cacti server.

A device configured with a hostname such as

127.0.0.1; touch /tmp/pwned #

therefore executes a command on the poller when its WMI data query runs — a device-manager to server-RCE.

Fix

  • escape the hostname and namespace with cacti_escapeshellarg in clean(), like the other fields
  • on Windows additionally strip the cmd.exe metacharacters (" & | ^ < > ( ) %) that cmd.exe interprets despite quoting (it ignores \", toggles quoting on every ", and expands %VAR%); a hostname/namespace never legitimately contains these
  • drop the now-redundant raw trim() in getcommand()

Test

tests/WmiCommandInjectionTest.php (standalone — the plugin has no harness) confirms an injected hostname is quote-contained on unix and metachar-stripped on Windows. php tests/WmiCommandInjectionTest.php exits 0.

Also in this PR

  • decode() object injection: unserialize() on the stored credential blob now passes allowed_classes => false.
  • Broken exec command: the default separator |+| was interpolated into --delimiter= unquoted, so exec() split the command into a shell pipeline (exit 127, no data). Now quoted for the shell while the property stays raw for the explode() in fetch().

Closes #5 (restrict unserialize and audit exec command paths).

Closes #19

Automated fixes:
- XSS: escape request variables in HTML value attributes
- SQLi: convert string-concat queries to prepared statements
- Deserialization: add allowed_classes=>false
- Temp files: replace rand() with tempnam()

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
- Change Dependabot ecosystem from npm to composer (PHP-only repo)
- Remove PHP from CodeQL paths-ignore so security PRs get analysis
- Remove committed .omc session artifacts, add .omc/ to .gitignore

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Linux_WMI::clean() escaped the username, password, binary and command but left
the device hostname only trimmed and the query namespace untouched, so
getcommand() interpolated them raw into the wmic command line that exec() runs
on the Cacti server. A device-supplied hostname such as
  127.0.0.1; touch /tmp/pwned #
therefore ran a command on the poller.

Escape the hostname and namespace with cacti_escapeshellarg, and on Windows
strip the cmd.exe metacharacters (" & | ^ < > ( ) %) that cmd.exe interprets
despite quoting. A standalone regression test in tests/ verifies both.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
The default separator (|+|) contains pipe characters, so the unquoted
--delimiter=|+| made exec() split the command into a shell pipeline (exit
127, no data). Quote it in getcommand() while keeping the property raw for
the explode() in fetch(). issue#5

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

escape the WMI hostname and namespace before exec hardening: restrict unserialize and audit exec command paths

1 participant