8 min readJohnny UnarJohnny Unar

Three WordPress SSO CVEs in 90 Days All Have the Same Root Cause

SAML signature confusion keeps producing unauthenticated admin takeover. The problem isn't plugin hygiene, it's what you inherit when you delegate auth to a dependency.

the same bug, three times

CVE-2026-15013, CVE-2026-57807, and CVE-2026-61979 landed inside a three month window, all against WordPress SSO plugins, and if you read the advisories back to back you start to feel like you're reading the same disclosure with the CVE number find-and-replaced. Different plugins, different vendors, identical failure. In every case the plugin took a field out of an inbound SAML or OAuth response, a field that the attacker fully controls because they're the one crafting the response, and it trusted that field to decide how to verify the message instead of enforcing what the WordPress admin had configured locally. The result each time was unauthenticated administrator takeover. Not privilege escalation from a low-priv account. Not something that needs a phishing step. Just POST a forged assertion to the ACS endpoint and you're wp-admin.

Signature confusion is the classic version of this. A SAML response is XML, it can carry a signature, and the signature can sit at the response level or the assertion level, and a lazy verifier will check whichever signature it happens to find first, or worse, check that a valid signature exists somewhere in the document without checking that it actually covers the assertion whose contents you're about to trust. So you take a legitimately signed assertion from a low-value user, wrap it, splice in a new unsigned assertion that says you're admin@victim.com, and the verifier validates the signature it can see and reads the identity from the part nobody signed. XML Signature Wrapping has been documented since roughly 2012. It is not a novel attack. And it keeps shipping.

The OAuth variant of CVE-2026-61979 is a cousin of this: the plugin read the algorithm out of the JWT header, saw alg set to something it shouldn't accept, and verified accordingly, when the entire point of a locally configured trust relationship is that you decide the algorithm, not the token.

why this is a protocol failure, not a maintenance one

The easy read on three CVEs in 90 days is that WordPress plugins are undermaintained and you should just update faster. That read is comfortable and it's wrong, or at least it misses the part that actually matters. SAML is a genuinely hard protocol to implement correctly. The spec is enormous, XML canonicalization is a minefield, the signing model has two valid places to put a signature and multiple valid transforms, and the number of ways to get validation subtly wrong vastly outnumbers the ways to get it right. Every team that writes a SAML verifier is re-solving a problem that has eaten security teams at companies far larger than a WordPress plugin shop.

When you install an SSO plugin, you are not adding a feature. You are outsourcing the single most security-critical decision your application makes, which is 'who is this person and should I believe them,' to a dependency whose author had to correctly implement a protocol that trips up specialists. You inherit their interpretation of the spec, their choice of XML library, their decision about where to check the signature, and their assumptions about which fields are trustworthy. If they got any of it wrong, and the base rate says a lot of implementations get something wrong, you own that bug in your login flow, and it's the kind of bug that doesn't degrade gracefully. Auth either holds or it hands over the keys.

This is the difference between delegating something like image resizing and delegating authentication. If the image resizer has a bug, you get a broken thumbnail. If the auth layer has a signature confusion bug, you get an attacker with a valid admin session and no log entry that looks abnormal, because from the application's perspective a successful, forged login is indistinguishable from a successful, real one. That asymmetry is the whole argument. The blast radius of a bug in your auth dependency is total, so the bar for what you're willing to delegate should be much higher than it is for basically anything else in your stack.

what actually goes wrong in the verifier

It's worth being concrete about the mechanics, because 'validate the signature properly' sounds obvious right up until you're staring at a python-saml or ruby-saml config trying to figure out which of fifteen options controls the thing you care about. The three failures cluster into a few categories.

First, checking the wrong signature. The verifier confirms a signature is present and valid but doesn't tie it to the specific assertion whose subject and attributes it's about to read. Fix: verify that the signature covers the exact element you extract identity from, reject responses where the signed element and the read element aren't the same node, and reject documents with more assertions than you expect.

Second, trusting attacker-controlled metadata to select verification parameters. Reading alg out of a JWT header, or reading the issuer out of the response and using it to pick which key to trust, means the attacker chooses how you check them. Your code should pin the algorithm and the signing key from local configuration and hard-fail on any mismatch. If the token says alg is none, or HS256 when you configured RS256, that's not a fallback, that's an attack, and the correct response is a 400 and a log line, never a verification attempt with the attacker's chosen scheme.

Third, not validating the boring fields. Audience restriction, NotBefore and NotOnOrAfter windows, the Recipient on the subject confirmation, the InResponseTo matching a request you actually issued, and replay protection on the assertion ID. Every one of these is a control that a rushed implementation skips because the happy path works fine without them, and every one of them is load-bearing. A forged assertion that would fail audience validation sails right through if you never check the audience.

a first-party integration in django or next.js

When we build customer portals and SaaS login at steezr, the default is to integrate SSO ourselves against a vetted, single-purpose library rather than pull in a bundle that promises to do everything. In Django that means wiring the SAML flow through a maintained library where you configure the IdP certificate, the entity IDs, and the algorithm explicitly, and then you write your own thin view that receives the assertion, hands it to the verifier, and does absolutely nothing with the response fields until verification returns clean.

The shape that matters looks like this. You load the IdP's signing certificate from your own config, not from the response. You set the accepted algorithm to a fixed value and configure the library to reject anything else, so RS256 means RS256 and a document claiming otherwise fails closed. You enable strict mode, which in most decent SAML libs turns on exactly the audience, timing, and destination checks people otherwise forget. Then in your view you extract the NameID and attributes only after successful validation, you map them to a user through an explicit allowlist of attributes you care about, and you never auto-provision an admin. If the assertion claims a role your app doesn't recognize, you drop the login, you don't guess.

For a Next.js app the same principles apply through a library like Auth.js, but the trap there is different. It's tempting to trust the id_token claims straight out of the provider response, and for OIDC you have to verify the token signature against the provider's published JWKS, pin the expected algorithm, validate iss and aud against your configured values, and check nonce against the one you stored server side. The nonce check is the one people skip, and skipping it reopens the replay door you thought the framework closed.

The amount of code this takes is small. A verify function, a config block with the cert and the pinned algorithm, and a user-mapping function that fails closed. Small enough that you can read all of it in one sitting, which is the entire point, because you can't audit a plugin's verifier the way you can audit forty lines you wrote.

how to audit what you already have

If you run SSO today, whether it's a WordPress plugin or a homegrown integration, there's a short set of questions that will tell you most of what you need to know without reading the source. Send yourself a login and capture the raw SAML response or id_token. Now tamper with it. Change a claim, strip the signature, swap the algorithm to none, wrap a second assertion in, backdate the timing window, point the audience at some other service. If any of those tampered messages produces a valid session, you have a finding, and probably a severe one.

Ask where the signing key comes from. If the answer involves anything read out of the inbound message, that's the CVE-2026-61979 pattern and it needs fixing now. Ask whether strict mode is on. Ask whether there's replay protection, because a lot of otherwise-correct implementations validate everything except assertion ID reuse and are wide open to a captured-response replay. Ask what happens on a role the app doesn't recognize, and if the answer is 'it creates the user with default permissions,' figure out whether default plus a forged attribute equals admin anywhere.

The uncomfortable conclusion from those three CVEs is that 'we use a well-known SSO plugin' is not a security posture, it's a bet that someone you've never met implemented a protocol correctly under commercial time pressure. Sometimes that bet pays off. Three times in ninety days it didn't, and the downside each time was full administrative compromise with no phishing, no credential theft, and no anomaly in the logs. For anything where an attacker owning an admin account ends your day, own the verifier, keep it small, and make it fail closed. That's fifty lines of code you control against an unbounded liability you don't.

Johnny Unar

Written by

Johnny Unar

Want to work with us?

SAML signature confusion keeps producing unauthenticated admin takeover. The problem isn't plugin hygiene, it's what you inherit when you delegate auth to a dependency.