This is a control room with one jammed doorbell and one password scribbled in a maintenance log
Plugin tenable:168746 rolls up two different issues from VMSA-2022-0030 affecting VMware vCenter Server 6.5 before 6.5 U3u, 6.7 before 6.7 U3s, and 7.0 before 7.0 U3i. CVE-2022-31697 leaks plaintext credentials into logs, but only during specific VCSA ISO lifecycle operations such as install, upgrade, migrate, or restore, and the attacker needs access to the workstation involved in that workflow. CVE-2022-31698 is the more reachable bug: a pre-auth denial of service in the content library service over TCP/443 via a crafted HTTP header.
The vendor's MEDIUM label is basically fair, but for enterprise patch prioritization I'd shave it down from the headline score. One half of this plugin is post-compromise and workflow-dependent, and the other half is availability-only, not code execution, auth bypass, or privilege escalation. On a badly exposed internet-facing vCenter, the DoS path matters; in most real deployments where vCenter sits on an admin network, this is important hygiene rather than a stop-everything incident.
4 steps from start to impact.
Reach the vCenter management plane
curl or a short Python requests script is enough to touch the vulnerable content library handler; this is not an authentication-dependent path.- Target is running vCenter Server below 6.5 U3u / 6.7 U3s / 7.0 U3i
- Attacker can reach TCP/443 on the vCenter server
- Content library service path is exposed through the standard management interface
- Many enterprises keep vCenter on a restricted management VLAN or behind VPN/bastion access
- North-south firewalls often block arbitrary Internet reachability to vCenter
- If vCenter is not externally exposed, this becomes an internal-only abuse path
Send the crafted header
- Attacker can send arbitrary HTTP requests to the target
- Target is unpatched for CVE-2022-31698
- Reverse proxies, WAFs, or strict header normalization can reduce reliability
- Operational monitoring may quickly reveal repeated malformed requests against vCenter
Disrupt content-library-backed operations
- Crafted request successfully triggers the vulnerable service state
- Operational staff do not rapidly detect and recover the affected service
- Service restarts, monitoring, and administrator intervention limit dwell time
- Blast radius is largely confined to vCenter management functions rather than guest OS takeover
Alternate path: harvest plaintext credentials from logs
- A vulnerable vCenter version was used during the affected ISO workflow
- Attacker already has local access to the involved workstation
- Relevant logs are still present and readable
- Requires prior foothold on an admin or operator system
- Only specific lifecycle workflows create the exposure
- Many environments do these operations infrequently
- A standard EDR-only posture does not meaningfully stop the pre-auth HTTP header DoS path; this is a network/service robustness problem, not a malware execution problem.
- Relying on MFA does not help for CVE-2022-31698 because the remote DoS path does not require authentication.
- Patching only ESXi hosts does nothing for this plugin's vCenter findings; the affected components are in vCenter Server and VCF bundles that include it.
The supporting signals.
| In-the-wild status | No public evidence of active exploitation found in the reviewed sources, and no CISA KEV entry was located for CVE-2022-31697 or CVE-2022-31698. |
|---|---|
| Proof-of-concept availability | CVE-2022-31698 has a public technical write-up from Cisco Talos (TALOS-2022-1588) plus Snort rule 60408. I did not find a widely used weaponized GitHub exploit for either CVE in the reviewed results. |
| EPSS / exploit likelihood | Secondary feeds show very low EPSS scores: Feedly snippets show about 0.04% for CVE-2022-31697 and 0.05% for CVE-2022-31698, which aligns with a *low near-term exploitation probability*. |
| KEV status | Not KEV-listed based on reviewed CISA catalog search results; no known ransomware-campaign linkage was identified for these CVEs. |
| CVSS and what it really means | CVE-2022-31697: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N = local, low-priv, confidentiality-only. CVE-2022-31698: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L = pre-auth network reachable, but availability-only. |
| Affected version range | vCenter Server 6.5 < U3u, 6.7 < U3s, 7.0 < U3i. VMware Cloud Foundation deployments that bundle these vCenter components are also affected; VMware points VCF customers to KB90336. |
| Fixed versions | Fixed at vCenter 6.5 U3u (build 20510539), 6.7 U3s (vpxd build 20504362 on VCSA), and 7.0 U3i (build 20845200). |
| Scanning / exposure reality | The product family is not hypothetical on the public Internet: Censys has historically observed thousands of Internet-facing vCenter services. That does not make this a critical bug, but it does mean any externally exposed vCenter leaves the CVE-2022-31698 DoS path directly reachable. |
| Disclosure date | VMware published VMSA-2022-0030 on 2022-12-08; NVD published both CVEs on 2022-12-13. |
| Researcher / reporting org | VMware credits Marcin 'Icewall' Noga of Cisco Talos for CVE-2022-31698. Public attribution for CVE-2022-31697 is not similarly emphasized in the reviewed advisory text. |
Sources.
Why this verdict
- Downward adjustment: CVE-2022-31697 is post-compromise by design — it requires access to a workstation that already performed a VCSA ISO install/upgrade/migrate/restore operation, which implies the attacker is already on an admin path.
- Downward adjustment: the unauthenticated remote path is only DoS — CVE-2022-31698 is reachable over the network, but the impact is service disruption, not code execution, auth bypass, or durable compromise.
- Downward adjustment: exposure population is narrower than generic HTTPS bugs — healthy enterprises do not publish vCenter broadly, so the reachable population is much smaller than a typical edge appliance.
- Upward adjustment: this is still the virtualization control plane — even low-grade disruption on vCenter can interrupt admin workflows, maintenance, provisioning, and some dependent operations.
Why not higher?
There is no reviewed evidence of active exploitation, KEV listing, or commodity exploit ecosystem around these specific CVEs. More importantly, the only network-reachable branch here is availability-only, and the credential-leak branch assumes an attacker is already on the right workstation at the right time.
Why not lower?
I am not dropping this to LOW because a pre-auth network path still exists against a high-value management platform, and many shops have weaker-than-ideal segmentation around vCenter. If your vCenter is Internet-facing or broadly reachable from user or server networks, the operational impact of a remote DoS is real enough to keep this in MEDIUM.
Crowdsourced verification payload.
Run this on the target vCenter host itself — VCSA or legacy Windows vCenter — not from an auditor workstation. Invoke it as python3 check_vcenter_vmsa_2022_0030.py on VCSA or py -3 check_vcenter_vmsa_2022_0030.py on Windows; root/Administrator is recommended so the script can query local binaries, files, or registry keys reliably.
#!/usr/bin/env python3
# check_vcenter_vmsa_2022_0030.py
# Detects whether the local VMware vCenter host is vulnerable to VMSA-2022-0030
# (Tenable plugin 168746 / CVE-2022-31697 and CVE-2022-31698)
#
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN
import os
import platform
import re
import subprocess
import sys
THRESHOLDS = {
'6.5': 20510539, # 6.5 U3u
'6.7': 20504362, # 6.7 U3s (VCSA vpxd build)
'7.0': 20845200, # 7.0 U3i
}
def run(cmd):
try:
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=10)
return p.stdout.strip()
except Exception:
return ''
def parse_version_build(text):
# Accepts common outputs like:
# VMware VirtualCenter 7.0.3 build-20845200
# 7.0.3.01100 build 20845200
if not text:
return None, None
version = None
build = None
m = re.search(r'(\d+\.\d+\.\d+(?:\.\d+)?)', text)
if m:
version = m.group(1)
m = re.search(r'build[-\s:]*(\d{5,})', text, re.IGNORECASE)
if m:
build = int(m.group(1))
else:
nums = re.findall(r'\b(\d{7,})\b', text)
if nums:
build = int(nums[-1])
return version, build
def linux_collect():
candidates = [
['/usr/sbin/vpxd', '-v'],
['/usr/lib/vmware-vpxd/vpxd', '-v'],
['vpxd', '-v'],
]
for cmd in candidates:
out = run(cmd)
v, b = parse_version_build(out)
if v and b:
return v, b, 'command: ' + ' '.join(cmd)
for path in ['/etc/vmware/.buildInfo', '/etc/applmgmt/appliance/update.conf']:
if os.path.exists(path):
try:
data = open(path, 'r', encoding='utf-8', errors='ignore').read()
v, b = parse_version_build(data)
if v and b:
return v, b, 'file: ' + path
except Exception:
pass
return None, None, ''
def windows_collect():
try:
import winreg
except Exception:
return None, None, ''
reg_paths = [
r'SOFTWARE\VMware, Inc.\VMware VirtualCenter',
r'SOFTWARE\WOW6432Node\VMware, Inc.\VMware VirtualCenter',
]
values = []
for root in (0x80000002,): # HKEY_LOCAL_MACHINE
for reg_path in reg_paths:
try:
key = winreg.OpenKey(root, reg_path)
i = 0
while True:
try:
name, val, _ = winreg.EnumValue(key, i)
values.append(f'{name}={val}')
i += 1
except OSError:
break
except OSError:
continue
joined = '\n'.join(values)
v, b = parse_version_build(joined)
if v and b:
return v, b, 'registry'
candidates = [
r'C:\Program Files\VMware\Infrastructure\VirtualCenter Server\vpxd.exe',
r'C:\Program Files\VMware\Infrastructure\vCenter Server\vpxd.exe',
]
for path in candidates:
if os.path.exists(path):
out = run([path, '-v'])
v, b = parse_version_build(out)
if v and b:
return v, b, 'binary: ' + path
return None, None, ''
def branch(version_str):
if version_str.startswith('6.5.'):
return '6.5'
if version_str.startswith('6.7.'):
return '6.7'
if version_str.startswith('7.0.'):
return '7.0'
return None
def main():
system = platform.system().lower()
version = build = source = None
if 'windows' in system:
version, build, source = windows_collect()
else:
version, build, source = linux_collect()
if not version or not build:
print('UNKNOWN - could not determine local vCenter version/build')
sys.exit(2)
rel = branch(version)
if rel is None:
print(f'UNKNOWN - detected version {version} build {build} from {source}, but it is outside 6.5/6.7/7.0 scope')
sys.exit(2)
threshold = THRESHOLDS[rel]
if build < threshold:
print(f'VULNERABLE - detected vCenter {version} build {build} from {source}; fixed threshold for {rel} is build {threshold}')
sys.exit(1)
else:
print(f'PATCHED - detected vCenter {version} build {build} from {source}; meets or exceeds fixed threshold {threshold}')
sys.exit(0)
if __name__ == '__main__':
main()