TL;DR
- This vulnerability allows unauthenticated attackers to completely bypass login screens and seize administrative control of internal help desk systems, exposing sensitive corporate data and service tickets without needing any credentials.
- CVE-2026-28323 is a critical, unauthenticated SAML authentication bypass in SolarWinds Web Help Desk (WHD) versions 2026.1 and earlier. An attacker can forge a SAML Response and POST it to the login endpoint without a valid Identity Provider, signature, or credentials. To obtain a usable session, the forged response must contain the username of an existing account. The entire attack fits in a single HTTP request once a valid username is known. SolarWinds patched the issue in WHD 2026.2.1 by replacing the legacy SAML stack with Spring Security's SAML2 service provider library. The fix is adequate: it moves signature verification from application logic into a framework that enforces it by default. Credit for the original goes to Dhabaleshwar Das.
Summary
On July 30, 2026, SolarWinds released Web Help Desk 2026.2.1, fixing two vulnerabilities: CVE-2026-28323 (SAML authentication bypass, CVSS 9.8) and the related CVE-2026-28299 (denial of service, CVSS 8.2). The advisory describes CVE-2026-28323 as allowing "a remote unauthenticated attacker [to] establish a session without valid credentials" when SAML 2.0 authentication is enabled.
Our research included:
- Firmware extraction and decompilation of both the vulnerable (2026.1) and patched (2026.2.1) releases
- Root cause analysis of the SAML processing code in
whd-core.jar - End-to-end exploitation against a lab instance, confirming unauthenticated session takeover
- Patch analysis of the new Spring Boot SAML stack
WHD is commonly deployed as internet-facing help desk software and has drawn attacker attention before. CISA added a separate WHD vulnerability (CVE-2026-28318) to the Known Exploited Vulnerabilities catalog on June 5, 2026.
What Defenders Should Do Right Now
- Patch to WHD 2026.2.1 or later. This is the only complete remediation.
- Disable SAML as a workaround if patching is not immediately possible. Switch to local or LDAP/AD authentication with MFA enforced through VPN or ZTNA.
- Audit SAML configuration: if your WHD instance has SAML enabled but no verification certificate uploaded, the instance is exploitable with zero additional effort.
- Check access logs for POST requests to
/helpdesk/WebObjects/Helpdesk.woacontaining aSAMLResponseparameter that did not follow a legitimate IdP redirect.
Background
Web Help Desk is SolarWinds' on-premises IT service management and ticketing platform. Organizations deploy it to manage internal support requests, asset tracking, and SLA workflows. WHD supports several external authentication mechanisms through its preferences panel, including LDAP/AD, CAS 2.0, and SAML 2.0.
The vulnerable release (2026.1) is a Java web application built on the WebObjects framework, running inside an embedded Apache Tomcat with an embedded PostgreSQL 13 database. The application ships as an RPM containing everything: JRE, Tomcat, PostgreSQL, and the WHD WAR files.
The SAML implementation lives in whd-core.jar, using the legacy OpenSAML 2.x library (end of life). When SAML is enabled, WHD acts as a SAML Service Provider (SP): it redirects unauthenticated users to the configured Identity Provider (IdP) and processes the SAMLResponse POST that comes back after the user authenticates at the IdP. The vulnerability was in the code that processes that response.
The Vulnerability
When a request arrives at the WHD login page, the LoginHandler class determines which authentication provider to use. If SAML is enabled in the database preferences and the request contains a SAMLResponse form parameter, WHD routes it through the ExternalAuthenticationProvider class:
// From ExternalAuthenticationProvider.isProviderValidForContext()
if (session.aPreference().useSamlAuth()) {
z = wOContext.request().formValueForKey("SAMLResponse") != null;
}
The provider extracted the raw SAMLResponse value and passed it, along with the optional SAML verification certificate from preferences to the core SAML processor:
private static String getSamlAuthUsername(WOContext wOContext, NSData nSData) {
String samlResponse = wOContext.request().stringFormValueForKey("SAMLResponse");
if (samlResponse != null) {
return SamlConsumer.getAuthenticatedUserFromSamlResponse(samlResponse, nSData);
}
return null;
}
The nSData parameter here is session.aPreference().samlVerificationCert(). If no certificate has been uploaded in WHD's setup, this value is null.
The First Bug: Conditional Signature Verification
The SamlConsumer.getAuthenticatedUserFromSamlResponse() method was where the authentication decision happened. The method base64-decoded the SAMLResponse, parsed the XML into an OpenSAML Response object, and then extracted the authenticated username from the first assertion's NameID field. The problem was in what happens between parsing and extraction:
public static String getAuthenticatedUserFromSamlResponse(
String str, NSData nSData) {
byte[] decoded = Base64.decode(str.trim());
Response response = unmarshall(decoded);
if (nSData != null) {// <-- only checks signature if cert exists
checkSignature(response, nSData);
}
// ... status code logging (non-blocking) ...
return authenticatedUserInResponse(response); // extracts NameID, returns username
}
The signature verification call was gated behind a null check on the certificate data. If an administrator configured SAML authentication but did not upload a verification certificate, the checkSignature() call never executed. The method parsed the attacker-supplied XML, extracted whatever NameID the attacker placed in the assertion, and returned it as the authenticated username.
The Second Bug: Unsigned Assertion Accepted
Even when a certificate was configured, a second bug in checkSignature() let unsigned responses through:
private static boolean checkSignature(Response response, NSData nSData) {
boolean z = false;
try {
X509Certificate cert = MDSUtils.x509CertificateFromCertData(nSData);
BasicX509Credential cred = new BasicX509Credential();
cred.setPublicKey(cert.getPublicKey());
SignatureValidator validator = new SignatureValidator(cred);
Signature sig = getAssertionSignature(response);
if (sig == null) {
_logger.warn("No signature provided in SAML Response; "
+ "cannot verify authenticity based on SAML certificate.");
z = true; // <-- accepts unsigned response
} else {
validator.validate(sig);
z = true;
}
} catch (ValidationException e2) {
throw e2; // only rejects if signature is present AND invalid
}
return z;
}
The getAssertionSignature() helper looked for a <Signature> element in the first assertion, then felled back to the response-level signature. If neither existed, it returned null. When checkSignature() received a null signature, it logged a warning and returned true.
This meant the only scenario where signature verification actually rejected a response was when a <Signature> element was present but failed cryptographic validation. For a successful attack, just omitting the signature was enough to receive an authentication token.
Missing Validation
Beyond signature verification, the SAML processing code performed no additional checks on the incoming assertion. The authenticatedUserInResponse() method directly extracted the NameID:
private static String authenticatedUserInResponse(Response response) {
NameID nameID = ((Assertion) response.getAssertions().get(0))
.getSubject().getNameID();
return nameID != null ? nameID.getValue() : null;
}
None of the standard SAML security properties were validated:
- Destination: the response could be intended for a completely different SP
- Audience Restriction: no check that the assertion was issued for this WHD instance
- NotBefore / NotOnOrAfter: temporal validity is ignored; a response from 2020 would be accepted
- InResponseTo: no correlation with any
AuthnRequestthat WHD actually sent - Issuer: no verification that the response came from the expected IdP
The code trusted whatever XML the client sends, as long as it parsed as a valid SAML Response.
Username Requirement
Because the forged SAML response must name a valid user to produce a usable session, an attacker needs to know or guess an existing username. Although the AdminAccountConfigurer class sets a default admin account during WHD setup, this default value is not reliable. Administrators can change the default username during installation, and in our lab environment, doing so was enough to prevent us from obtaining a working session cookie.
A Note on Auto Provisioning
We noticed that if an attacker were to supply a non-existing username through the NameID parameter, WHD would not reject the login outright. Instead, the LoginHandler.loadAndValidateHelpdeskUser() method would create a new Client object on the fly:
if (helpdeskUser == null) {
helpdeskUser = createHelpdeskUserForUserPrincipal(editingContext, userPrincipal);
}
However, this auto-provisioned account has limited practical value. The resulting session cookie cannot be used to access any API endpoint, making the authenticated session unusable. An attacker must target an existing user account to gain meaningful access to the application.
Exploitation
We developed a proof-of-concept exploit that forges a SAML Response for a target username and submits it to the WHD login endpoint. The tool handles session setup, CSRF token extraction, and payload construction automatically:
$ python3 exploit_cve_2026_28323.py --target https://whd.local:8443 --user admin
[*] Target: https://whd.local:8443
[*] Forging SAML Response for user: admin
[+] Form action: /helpdesk/WebObjects/Helpdesk.woa
[+] CSRF token: 33ee49f0-c74b-4b94-a667-03aeee7326d1
[+] SUCCESS - Authenticated as 'admin'!
[+] Page title: Web Help Desk - My Tickets
JSESSIONID=4EC4420477C09CC2FADA9A3A7C7FDA54
We validated this end to end in our lab running WHD 2026.1.21384 with SAML 2.0 enabled. The entire attack completes in a single HTTP exchange after initial session setup.
The Patch
WHD 2026.2.1 is a ground-up rewrite. The application moves from WebObjects to Spring Boot, the web server changes from Tomcat to Caddy, and a Next.js frontend replaces the WebObjects template engine. The SAML stack is entirely replaced.
The custom SamlConsumer class and its OpenSAML 2.x dependency are gone. SAML processing now runs through Spring Security's spring-security-saml2-service-provider (version 6.5.5) backed by OpenSAML 5.1.4. The new implementation delegates to OpenSaml5AuthenticationProvider, which is Spring Security's default SAML2 authentication provider.
This patch fixes the identified issues highlighted above, since Spring Security's SAML2 provider enforces the following by default, with no opt-out:
- Signature verification: responses or assertions must carry a valid signature matching the registered IdP's certificate. Unsigned assertions are rejected.
- Destination validation: the
Destinationattribute must match the registered Assertion Consumer Service (ACS) URL. - Audience restriction: the assertion's audience must include the SP's entity ID.
- Temporal validation:
NotBeforeandNotOnOrAfterconditions are enforced against server time with a configurable clock skew tolerance. - InResponseTo correlation: the response must reference a pending authentication request.
In our lab testing, the forged-response attack from the vulnerable version failed at the first check. Without a valid signature from the registered IdP's private key, the response was rejected before any identity extraction occurs.
Detection
The vulnerable and patched releases use different technology stacks, which makes fingerprinting straightforward.
For the vulnerable versions (2026.1 and earlier), the login page includes static assets (CSS, JS, images) with a cache-buster query parameter that leaks the exact build version. For example:
/helpdesk/stylesheets/helpdesk.css?v=2026_1_21384
The version string follows the pattern YYYY_MAJOR_BUILD. Any version matching 2026_1_* or earlier is vulnerable. The login page is also served from /helpdesk/WebObjects/Helpdesk.woa and the HTTP response includes the header x-webobjects-servlet: YES.
For the patched version (2026.2.1), the application runs on Spring Boot with a Next.js frontend behind Caddy. Although the WebObjects headers and asset cache-busters are absent, the version can still be extracted from the Next.js RSC payload from the login page, where it appears in a format like 2026.2.1.xxxxx:
$ curl https://whd.local:8443/ -L | grep "2026.2.1"
…omitted for brevity…
<script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"2026.2.1.117\",\"p\":\"\",\"c\":[\"\",\"login?returnUrl=%2F\"]
…omitted for brevity…
Both techniques can be used to identify vulnerable versions of SolarWinds Web Help Desk.
Conclusion
CVE-2026-28323 is a textbook SAML implementation failure. The vulnerable code treated signature verification as a conditional enhancement rather than a security requirement and omitted every other validation that the SAML specification mandates for relying parties. The result is that any network-adjacent attacker who knows a valid username could forge a SAML Response and authenticate as that user with a single POST request.
The severity is accurately reflected by the CVSS 9.8 score. The attack is unauthenticated and requires no user interaction. The prerequisites are that SAML 2.0 authentication is enabled on the target instance and that the attacker knows a valid username. While the application accepts arbitrary usernames in the forged response, only sessions tied to existing accounts can interact with the application's API, so username knowledge is a practical requirement for exploitation.
SolarWinds addressed the issue comprehensively by replacing the SAML stack with Spring Security's battle-tested implementation. The fix closes the vulnerability and brings the SAML handling in line with current security standards. If you run Web Help Desk with SAML enabled, patch now. If you cannot patch, disable SAML and switch to an alternative authentication method until you can.
Cosmos customers were notified about this vulnerability research shortly after the vendor advisory published. If you are interested in learning more about managed services delivered through our Cosmos platform, visit bishopfox.com/services/continuous-threat-exposure-management.
For more vulnerability intelligence insights, visit the Bishop Fox Blog.
Subscribe to our blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.
Recommended Posts
You might be interested in these related posts.
No Crash Required: Verifying the Citrix NetScaler SAML Patch for CVE-2026-8452
Critical SQL Injection in Metabase via Password Reset: CVE-2026-72898
A Millisecond of Predictability: Why CVE-2026-11374 Is Hard to Exploit