// HackTricks · Linux

VMware Tools service discovery LPE (CWE-426) via regex-based binary discovery (CVE-2025-41244)

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

  1. Stage a binary in a path matching one of the permissive regexes, e.g. /tmp/httpd or ./nginx.[1]
  2. Run it as a low-privileged user and ensure it opens any listening socket.[1]
  3. 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

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.