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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

decode() still assumes deserialization always succeeds and can emit warnings/fail unexpectedly on malformed data (and the new test assertion can be strengthened to avoid false passes).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR hardens the WMI plugin’s command construction and credential decoding to mitigate command injection and PHP object injection risks in the Linux WMIC execution path.

Changes:

  • Escape/sanitize WMI hostname and namespace before building the wmic command line, and quote the --delimiter argument to avoid shell pipeline splitting.
  • Restrict credential blob deserialization by passing allowed_classes => false to unserialize().
  • Add a standalone regression test to validate command-injection neutralization behavior.
File summaries
File Description
linux_wmi.php Escapes hostname/namespace, quotes delimiter for shell safety, and restricts unserialize() class instantiation.
tests/WmiCommandInjectionTest.php Adds a standalone regression test for hostname/namespace escaping and Windows metachar stripping.
.gitignore Ignores the .omc/ directory.
Review details
  • Files reviewed: 2/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread linux_wmi.php
Comment on lines 309 to 312
$info = base64_decode($info);
$info = unserialize($info);
$info = unserialize($info, array('allowed_classes' => false));
$info = $info['password'];

Comment on lines +43 to +44
check(strpos($cmd, '; touch /tmp/pwned') === false || strpos($cmd, "'127.0.0.1; touch /tmp/pwned #'") !== false,
'injected hostname is contained inside a quoted argument');
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

2 participants