This is a fire alarm that can empty the building, not a master key that opens every room
CVE-2026-28299 is a SolarWinds Web Help Desk memory-exhaustion denial-of-service issue in WHD 2026.1 and all previous versions, fixed in WHD 2026.2. The published impact is straightforward: a remote attacker can send crafted traffic to the WHD web service and push the server into insufficient-memory conditions, crashing the application.
SolarWinds scored it HIGH 8.2, but that overstates real enterprise urgency. The bug is unauthenticated and network-reachable, which matters, but the practical outcome is usually service interruption of one application, not code execution or tenant/domain takeover; and Web Help Desk is an on-premises product that many enterprises keep internal, behind VPN, reverse proxy, or ACLs, which sharply narrows the exposed population.
3 steps from start to impact.
Reach the WHD web listener with a basic HTTP client
curl, Burp Suite, or a custom Python requests loop. This is the biggest real-world gate: if the WHD instance is not internet-exposed and is only reachable from inside, over VPN, or from a management segment, the vuln is already post-initial-access rather than an external-entry bug.- Network path to the WHD web UI
- Target runs SolarWinds Web Help Desk 2026.1 or earlier
- WHD service is up and reachable over HTTP/HTTPS
- Many WHD deployments are internal-only because the product is self-hosted/on-premises
- Reverse proxies, VPN requirements, IP allowlists, or admin-segment placement cut exposure hard
- WAF/CDN/body-size limits may block some malformed or oversized requests before they hit WHD
Trigger resource exhaustion against the JVM-backed service
-Xmx1024m, which gives an attacker a finite memory ceiling to push against.- The vulnerable request path is reachable without authentication
- The target has enough request throughput available to sustain pressure
- No upstream control rate-limits or rejects the abusive pattern
- No public exploit kit or broad PoC was easy to locate as of 2026-06-04
- Rate limiting, reverse proxies, and request normalization can raise attacker cost
- Heavier-sized deployments may have more RAM headroom than the minimum config
Crash or destabilize the help desk service
- The target is single-instance or lacks resilient failover
- Service restart automation does not fully mask the outage
- The organization depends on WHD for live ticket intake
- Service managers often auto-restart crashed processes, shortening outage duration
- Load balancers or standby nodes can dilute impact
- Blast radius is primarily the help desk workflow, not domain-wide control
The supporting signals.
| In-the-wild status | As of 2026-06-04, I found no direct evidence that CVE-2026-28299 itself is being exploited. Important nuance: Microsoft documented active exploitation of other internet-exposed SolarWinds WHD flaws in February 2026, which proves attackers watch this product line, but that is not evidence for this specific DoS CVE. |
|---|---|
| Proof-of-concept availability | I found no obvious public GitHub PoC or weaponized exploit write-up for CVE-2026-28299 as of 2026-06-04. That is a real downward pressure on urgency for a DoS bug. |
| EPSS | 0.00059 from the user-supplied intel/NVD-linked ecosystem data, which is extremely low and consistent with a fresh, non-weaponized application DoS. |
| KEV status | Not listed in CISA KEV as of 2026-06-04. CISA's catalog contains other SolarWinds Web Help Desk entries, but not this CVE. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H = remote, low-complexity, no-auth, no-user-action. That baseline is why the vendor score starts high, but the published narrative still describes server crash from insufficient memory, so the business effect is much closer to availability outage than hostile takeover. |
| Affected versions | SolarWinds Web Help Desk 2026.1 and all previous versions per the vendor advisory. |
| Fixed version | SolarWinds Web Help Desk 2026.2. No distro-backport channel is relevant here because WHD is a vendor-distributed application, not an OS package maintained by RHEL/Ubuntu. |
| Exposure reality | WHD is a self-hosted/on-premises product and SolarWinds says it runs entirely within customer infrastructure. That cuts both ways: some orgs expose it for external users, but many keep it internal or VPN-gated, which materially shrinks the reachable population. |
| Platform/architecture note | Current WHD supports Windows Server, RHEL/CentOS/Fedora, and macOS, so exposure is cross-platform. The 2026.2 documentation also notes the backend JVM defaults to -Xmx1024m, which aligns with the memory-exhaustion failure mode. |
| Disclosure and reporter | Published 2026-06-02. The advisory credits Tenable in acknowledgments, and NVD shows SolarWinds as the CVE source. |
noisgate verdict.
The decisive factor is blast radius: this is a remote unauthenticated bug, but the published effect is an application crash, not code execution or privilege gain. The second major downtick is exposure reality—WHD is an on-premises service that many enterprises do not publish broadly, so the reachable population is much smaller than a generic AV:N score implies.
Why this verdict
- Vendor baseline starts high because it is unauthenticated, network-reachable, and low-complexity.
- First downgrade: impact is mostly service availability. The advisory describes a crash from insufficient memory, which is bad for operations but not equivalent to RCE or credential compromise.
- Second downgrade: attacker position often requires exposure you may not have. If WHD is internal-only or VPN-gated, exploitation already assumes prior access or a trusted network foothold.
- Third downgrade: exposed population is narrower than the CVSS vector suggests because WHD is an on-premises ticketing product, not a ubiquitous public SaaS edge service.
- Fourth downgrade: no KEV and no clear public PoC as of 2026-06-04 reduce the odds this becomes a near-term mass exploitation event.
- Small upward adjustment remains because service desk outages hurt incident intake, user support, and IT operations—especially in single-instance deployments.
Why not higher?
I am not taking this to HIGH or CRITICAL because the currently published outcome is denial of service, not remote execution, auth bypass, or data theft. Even where reachable, the blast radius is usually the WHD service itself, and many enterprises already reduce reachability by keeping WHD off the public internet.
Why not lower?
I am not dropping this to LOW because the bug is still remote and unauthenticated, and a help desk outage can meaningfully disrupt operations. If you knowingly expose WHD externally or run it as a single point of failure for support intake, the operational risk is real even without adversary code execution.
What to do — in priority order.
- Put WHD behind VPN or IP allowlists — If the service does not need public access, stop giving attackers step one. For a MEDIUM finding there is no noisgate mitigation SLA; do this in the next routine change window while you work toward patching within the 365-day remediation window.
- Rate-limit and size-limit upstream requests — Apply reverse-proxy, WAF, or load-balancer controls that cap abusive request rates and reject oversized or malformed bodies before they reach WHD. Again, there is no noisgate mitigation SLA here, so use normal change control unless the instance is externally exposed.
- Enable aggressive service-health monitoring — Alert on WHD process exits, JVM OOM conditions, wrapper restarts, and sustained memory growth so a crash becomes a short outage instead of a blind spot. This is especially valuable for single-instance service desks and should be folded into regular monitoring work while patching proceeds.
- Remove single-instance fragility where practical — If WHD is business-critical, add restart automation, standby capacity, or maintenance failover so one process crash does not become full ticketing paralysis. There is no noisgate mitigation SLA for MEDIUM, but externally reachable environments should still prioritize resilience sooner.
- Endpoint AV alone does not prevent a memory-exhaustion DoS; there may be no payload to catch.
- MFA does not help when the published attack path is unauthenticated.
- Credential rotation does not address this flaw because the issue is resource exhaustion, not account compromise.
- Internal segmentation helps only if WHD is actually off the attacker path; once the app is reachable, segmentation does not stop the request pattern itself.
Crowdsourced verification payload.
Run this on the target WHD host or from an auditor workstation with filesystem access to the WHD install directory. Invoke it as python3 whd_cve_2026_28299_check.py --root /opt/webhelpdesk on Linux/macOS or py whd_cve_2026_28299_check.py --root "C:\Program Files\WebHelpDesk" on Windows; it needs read access only, though elevated rights may help when reading Program Files or service-owned directories.
#!/usr/bin/env python3
# CVE-2026-28299 local version check for SolarWinds Web Help Desk
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 1=vulnerable, 0=patched, 2=unknown
import argparse
import os
import platform
import re
import sys
from typing import List, Optional, Tuple
FIXED = (2026, 2)
VERSION_RE = re.compile(r'\b(20\d{2})\.(\d+)\b')
TEXT_EXTS = {'.txt', '.log', '.cfg', '.conf', '.ini', '.xml', '.json', '.properties', '.yml', '.yaml', '.html', '.htm', '.md'}
FILE_HINTS = {
'version', 'release', 'readme', 'build', 'manifest', 'application.properties',
'wrapper.conf', 'whd.env', 'package.json'
}
def normpath(p: str) -> str:
return os.path.abspath(os.path.expanduser(p))
def default_roots() -> List[str]:
system = platform.system().lower()
roots = []
if system == 'windows':
roots.extend([
r'C:\Program Files\WebHelpDesk',
r'C:\Program Files\SolarWinds\WebHelpDesk',
r'C:\Program Files (x86)\WebHelpDesk',
r'C:\Program Files (x86)\SolarWinds\WebHelpDesk',
])
elif system == 'darwin':
roots.extend([
'/Applications/WebHelpDesk.app/Contents/Resources',
'/usr/local/webhelpdesk',
'/opt/webhelpdesk',
'/usr/local/solarwinds/webhelpdesk',
])
else:
roots.extend([
'/opt/webhelpdesk',
'/usr/local/webhelpdesk',
'/opt/solarwinds/webhelpdesk',
'/usr/local/solarwinds/webhelpdesk',
])
return [r for r in roots if os.path.exists(r)]
def parse_version_tuple(s: str) -> Optional[Tuple[int, int]]:
m = VERSION_RE.search(s)
if not m:
return None
return int(m.group(1)), int(m.group(2))
def compare_versions(a: Tuple[int, int], b: Tuple[int, int]) -> int:
return (a > b) - (a < b)
def should_read_file(path: str) -> bool:
base = os.path.basename(path).lower()
ext = os.path.splitext(base)[1].lower()
if ext in TEXT_EXTS:
return True
return any(h in base for h in FILE_HINTS)
def candidate_files(root: str, max_depth: int = 3) -> List[str]:
out = []
root = normpath(root)
for cur, dirs, files in os.walk(root):
rel = os.path.relpath(cur, root)
depth = 0 if rel == '.' else rel.count(os.sep) + 1
if depth > max_depth:
dirs[:] = []
continue
for name in files:
p = os.path.join(cur, name)
try:
if should_read_file(p) and os.path.getsize(p) <= 2 * 1024 * 1024:
out.append(p)
except OSError:
pass
return out
def extract_versions_from_file(path: str) -> List[Tuple[int, int, str]]:
found = []
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
data = f.read()
except Exception:
return found
for m in VERSION_RE.finditer(data):
year = int(m.group(1))
minor = int(m.group(2))
found.append((year, minor, path))
return found
def assess_root(root: str) -> Tuple[str, str]:
root = normpath(root)
if not os.path.exists(root):
return 'UNKNOWN', f'Path does not exist: {root}'
versions = []
for path in candidate_files(root):
versions.extend(extract_versions_from_file(path))
# Prefer explicit WHD 20xx.x style versions from nearby files
versions = sorted(set(versions))
if versions:
# Choose the highest discovered version as the most likely installed version
highest = max((v[:2] for v in versions), key=lambda x: (x[0], x[1]))
evidence = [v for v in versions if v[0] == highest[0] and v[1] == highest[1]][:3]
if compare_versions(highest, FIXED) >= 0:
src = '; '.join(x[2] for x in evidence)
return 'PATCHED', f'Detected WHD version {highest[0]}.{highest[1]} (evidence: {src})'
else:
src = '; '.join(x[2] for x in evidence)
return 'VULNERABLE', f'Detected WHD version {highest[0]}.{highest[1]} which is earlier than 2026.2 (evidence: {src})'
# Heuristic fallback: Caddy 2.11.1 presence strongly suggests 2026.2+, but keep UNKNOWN if no explicit WHD version found.
caddy_hits = []
for path in candidate_files(root, max_depth=4):
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
sample = f.read(200000)
if 'Caddy 2.11.1' in sample or 'caddy 2.11.1' in sample.lower():
caddy_hits.append(path)
except Exception:
pass
if caddy_hits:
return 'UNKNOWN', 'Found references to Caddy 2.11.1, which aligns with WHD 2026.2 documentation, but no explicit installed WHD version string was located.'
return 'UNKNOWN', 'Could not determine the installed WHD version from local files. Re-run with --root pointing to the exact WHD install directory.'
def main() -> int:
parser = argparse.ArgumentParser(description='Check SolarWinds Web Help Desk for CVE-2026-28299 exposure by local version.')
parser.add_argument('--root', action='append', help='WHD install root path. Can be supplied multiple times.')
args = parser.parse_args()
roots = [normpath(r) for r in (args.root or [])]
if not roots:
roots = default_roots()
if not roots:
print('UNKNOWN - No candidate WHD install paths found. Supply --root explicitly.')
return 2
overall = []
for root in roots:
status, detail = assess_root(root)
overall.append((status, root, detail))
# Precedence: VULNERABLE > PATCHED > UNKNOWN
for status in ('VULNERABLE', 'PATCHED', 'UNKNOWN'):
matches = [x for x in overall if x[0] == status]
if matches:
chosen = matches[0]
print(f'{status} - {chosen[2]}')
if status == 'VULNERABLE':
return 1
if status == 'PATCHED':
return 0
return 2
print('UNKNOWN - Unexpected state')
return 2
if __name__ == '__main__':
sys.exit(main())
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.