Severity Daily

IT and AI security incidents, checked against the primary source

Tag: JWT

  • Mstore API checked the Firebase token’s alg, kid, aud and iss, and never verified its signature

    Mstore API checked the Firebase token’s alg, kid, aud and iss, and never verified its signature

    A forged Firebase Phone Auth token signed with the attacker’s own RSA key impersonates any phone number on the store; the fix landed in 4.21.0, as one security line inside a 25-item feature release.

    What happened

    NVD published CVE-2026-13447 at 2026-09-05T06:17:09.403, assigned and scored by [email protected] at CVSS 9.8 with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, filed under CWE-287. The record is still Received, so the CNA’s own description is currently the whole of it. It reads:

    “The Mstore Api plugin for WordPress is vulnerable to Authentication Bypass via JWT Forgery in versions up to, and including, 4.20.0 This is due to missing cryptographic signature verification in the FirebasePhoneAuthHelper::verify_id_token() function, which decodes and validates Firebase ID token claims (alg, kid, aud, iss) but never calls openssl_verify() or any equivalent to validate the JWT signature against Google’s actual public key certificates. This makes it possible for unauthenticated attackers to forge a Firebase Phone Auth JWT signed with a self-generated RSA key pair and impersonate any phone number, resulting in unauthorized access to existing WordPress accounts or creation of new arbitrary accounts.”

    Mstore API is the WordPress-side bridge for FluxBuilder, an app builder that turns a WooCommerce store into native Android and iOS apps. It exposes the REST endpoints those apps call, and Firebase Phone Auth is one sign-in route it offers: the shopper gets an SMS code, Firebase issues an ID token, and the app hands that token to the store to be exchanged for a WordPress session.

    WordPress.org reports 3,000+ active installations and lists 4.21.3 as current, last updated about two weeks before this was written. The vendor’s changelog places the fix in 4.21.0, in an entry that reads “Security: Fix Firebase JWT signature, audience validation, and key caching,” alongside roughly twenty-five other items covering Listeo listing fields, booking options, push notifications, product fields, and vendor registration.

    What the guard did check

    The function is not empty. Per the record it decodes the token and validates four things: alg, kid, aud, and iss. Those are the header and payload fields that describe the signature and its issuer. It confirms the token says it was signed with the expected algorithm, names a key ID, is addressed to this Firebase project, and claims to come from Google.

    Then it never calls openssl_verify(), or anything equivalent, against Google’s public certificates. Every one of those four checks is a check on what the token asserts about itself, and the token is attacker-supplied in full. An attacker generates an RSA key pair and signs a token whose header says RS256 with a plausible kid, whose aud is the store’s Firebase project ID and whose iss is Google’s issuer string, with the phone number claim set to the victim’s. All four guards pass. The one operation that would have failed — checking the signature against a key the store trusts — is the one that is absent.

    Why it matters

    The alg check is what makes this worth writing up rather than filing as another missing-validation bug.

    Validating alg is the standard remediation for the best-known JWT vulnerability there is: the alg: none attack, and its cousin where a token declaring HS256 is verified with an RSA public key as the HMAC secret. Every JWT hardening guide says to pin the expected algorithm and reject anything else. This code did that. Somebody read the guidance, understood the famous failure mode, and implemented the defense against it — and shipped a verifier that does not verify. That the sophisticated check is present is evidence the developer was thinking about JWT security, which is why nothing about the function looks wrong on review.

    The kid check compounds it. A key ID is only useful if you are about to select a key and use it. Checking that a kid is present and well-formed, and then not fetching or applying the key it names, is a control that has assembled every part of the operation except the operation. The vendor’s own fix line — “Fix Firebase JWT signature, audience validation, and key caching” — suggests key handling was reworked at the same time, which fits: the caching machinery was there, and the cached keys were not being used to verify anything.

    This is the fifth instance in a week of a control that reads correctly in English and tests something adjacent to the property that matters. Hummingbird’s debug-log guard called class_exists() on the wrong namespace, so it never matched in any version. python-jose checked a key’s armor strings instead of its type. Kestra’s auth filter called endsWith on a path instead of asking whether the route was auth-exempt. goose’s recipe scanner scanned every field except the two that execute commands. The common defect is not carelessness. It is that each check tests a proxy for the property, and the proxy is cheap to satisfy.

    What separates this one from the other four is the identity it forges. The others produce code execution or a bypassed route. This produces a session as a specific named person, chosen by the attacker, on a storefront. A phone number is the account key here, and phone numbers are public: they appear on invoices, in order confirmations, and in the store’s own customer records. There is no discovery step. An attacker who wants the account belonging to a particular customer, or to the store owner if the owner signed in through the app, needs only that number.

    The account-creation half deserves separate weight. The record says the flaw permits “creation of new arbitrary accounts,” which means an attacker is not limited to taking over an existing customer. They can mint WordPress users at will against a store whose registration policy may otherwise be closed. On a WooCommerce site, a customer account carries order history, saved addresses, stored payment references, and in many configurations store credit or loyalty balances — and the vendor’s own 4.21.2 release, two versions after the fix, adds “Security: Require authentication and ownership on the loyalty points endpoints,” which indicates loyalty balances are reachable through this same API surface.

    On scale, be careful in both directions. WordPress.org’s 3,000+ band counts installs of the free plugin from the directory. Mstore API is the backend for a commercially sold app builder, so an unknown number of deployments arrive through purchase rather than the directory and are invisible to that count. Nobody publishes the real figure. The honest statement is that the directory reports 3,000+ and the true population is larger by an unknown factor.

    What to do

    • Update to 4.21.0 at minimum; 4.21.3 is current. The Firebase JWT signature fix is in 4.21.0 per the vendor changelog, and 4.21.1 and 4.21.2 each carry further security entries on adjacent endpoints — order-completion and wallet bypasses in 4.21.1, loyalty-point authorization and check-user enumeration throttling in 4.21.2. Going to 4.21.0 and stopping leaves those.
    • Check whether phone sign-in is actually in use. The record states no precondition, and it could not be established from here whether the Firebase Phone Auth route is reachable on an installation that never configured it. Treat it as reachable until you have confirmed otherwise in your own configuration.
    • Audit accounts, not just logins. Because a forged token produces an ordinary authenticated session, there is nothing anomalous in the access log to search for. The check that works is inventory: list users created or modified since you deployed a version at or below 4.20.0, look for accounts whose phone number matches an existing customer, and look for registrations that should not have been possible under your registration policy.
    • Review balances and stored value on any store using the loyalty or wallet features, given what 4.21.1 and 4.21.2 fixed on that same API surface.
    • For anyone writing token verification: assert the outcome, not the mechanism. A test that feeds the verifier a syntactically perfect token signed with a key the server has never seen, and requires rejection, fails on day one against this function. A test that checks “does the verifier validate alg” passes.

    Sourcing note

    The CVE record was read from NVD’s API and its description is reproduced above complete and unedited, including the missing period after “4.20.0,” which is in the record as published. The record is vulnStatus: Received: no NVD enrichment, no CPE configuration, no independent vector. No CISA KEV fields are present — this is not a KEV entry and carries no federal deadline.

    Version, install count, and changelog text come from the plugin’s WordPress.org directory page, read directly. One caveat on the 4.21.0 quotation: two retrievals of that page returned the release differently, one itemizing the security line quoted above and one describing 4.21.0 only as roughly twenty-five improvements “including Firebase JWT validation.” Both place the Firebase JWT signature fix in 4.21.0, but the exact wording of that line rests on a single retrieval. The 4.21.1, 4.21.2 and 4.21.3 entries were itemized identically on both.

    Wordfence’s own advisory page for this CVE returned no usable content, as its threat-intel pages have on every attempt this site has made; researcher credit and Wordfence’s disclosure timeline are therefore unavailable. The Trac line references in the record — flutter-user.php lines 829 and 940, and firebase-phone-auth-helper.php line 5, at tag 4.18.4 and on trunk — could not be opened, because plugins.trac.wordpress.org/browser/ URLs are disallowed to automated retrieval. The description of what the function does and does not check is therefore the CNA’s, not an independent reading of the source.

    Unresolved: the disclosure date and the time between report and fix. The fix is in 4.21.0 and the CVE published after 4.21.3 shipped, so the record trailed the patch by at least three releases, but by how long cannot be stated from the sources reachable here.

  • python-jose still lets a public key serve as an HMAC secret, because the 2024 fix checked the key format instead of the key

    python-jose still lets a public key serve as an HMAC secret, because the 2024 fix checked the key format instead of the key

    VulnCheck published CVE-2026-85394 against the python-jose JOSE library on September 3, 2026, at 7:17 p.m. UTC. The record describes an algorithm-confusion bypass, and its last sentence is the story: “This is an incomplete fix for CVE-2024-33663.” The 2024 fix tried to stop a public key from being used as an HMAC secret by checking whether the key looked like a PEM or SSH key. A DER-encoded public key looks like neither. There is no patched release, the GitHub issue has been open since July, and python-jose has not shipped a version since May 2025.

    What happened

    The full NVD description, verbatim: “python-jose through 3.5.0 fails to properly validate asymmetric keys in HMAC initialization, accepting DER-encoded public keys that lack PEM armor or SSH prefixes. Attackers holding the service’s public key can forge HS256 tokens that pass verification when algorithms are not explicitly restricted. This is an incomplete fix for CVE-2024-33663.”

    The weakness is CWE-347, improper verification of cryptographic signature. VulnCheck scored it 9.1 under CVSS v3.1 with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N, and 9.3 under CVSS v4.0. The record’s vulnStatus was “Received” at the time of writing.

    Algorithm confusion is an old and well-understood class. A JSON Web Token carries its own algorithm identifier in the header. A service that verifies RS256 tokens holds an RSA public key, which is not a secret — it is typically published at a JWKS endpoint so that anyone can verify the service’s tokens. If an attacker can get the verification routine to treat that public key as an HMAC shared secret instead, the attacker can mint HS256 tokens signed with the key everybody already has, and the library will accept them. The canonical instance is CVE-2022-29217 in PyJWT.

    python-jose took this in April 2024 as CVE-2024-33663, filed on GitHub as GHSA-6c5p-j8vq-pqhj, titled “python-jose algorithm confusion with OpenSSH ECDSA keys,” affecting versions before 3.4.0 and patched in 3.4.0. The patch added a guard. In the library’s native backend the check reduces to this shape:

    if is_pem_format(key) or is_ssh_key(key):
        raise JWKError

    That is a formatting test, not a type test. is_pem_format looks for the -----BEGIN armor. is_ssh_key looks for the prefix that opens an OpenSSH public key line. Both are string patterns wrapped around the same underlying object: a DER-encoded key. PEM is base64-encoded DER with a header and footer glued on. Strip the armor and base64-decode it and you have the identical key material with none of the strings the guard is looking for.

    That is what GitHub issue 414 against mpdavis/python-jose reports. It is titled “Algorithm-confusion guard bypassed by DER-encoded public keys (incomplete CVE-2024-33663 fix),” was opened on July 6, 2026 by the account geo-chen, and is still open with no maintainer response recorded. The reporter demonstrates forging an HS256 token using the DER form of the service’s public key and having it verify successfully on decode. The recommended fix is to stop pattern-matching entirely: attempt to load the supplied HMAC secret as an asymmetric key and reject it if it parses. The reporter also recommends requiring explicit algorithm allowlists on verification.

    The release history is the other half. On PyPI, python-jose 3.3.0 was uploaded on June 5, 2021. Version 3.4.0 — the release carrying the incomplete fix — was uploaded on February 18, 2025. Version 3.5.0, the current release and the one the CVE names as vulnerable, was uploaded on May 28, 2025. Nothing since. The issue reporting the bypass has been open for roughly two months, and the CVE is now public.

    Why it matters

    The interesting failure here is not that a library had an algorithm-confusion bug. It is how the fix failed, and that failure mode generalizes past this library.

    The 2024 patch answered the question “is this key text that looks like an asymmetric public key?” The question it needed to answer was “is this key an asymmetric public key?” Those diverge the moment the same value can be spelled more than one way, and cryptographic key material can always be spelled more than one way: PEM, DER, JWK, OpenSSH, base64 of any of them. A validator built on string prefixes has to enumerate the spellings. A validator built on parsing has to enumerate nothing — if the bytes load as a public key, they are a public key, whatever they look like. The reporter’s recommendation is the structurally correct one, and it is also the one that does not need to be revisited when a sixth encoding shows up.

    This is the same shape as findings this publication has covered on other projects: a denylist bypassed because it enumerated bad inputs instead of defining good ones, an escape routine defeated because the grammar it escaped for was not the grammar the value ended up in. Guards that describe the attack rather than the property are the recurring cause. What makes this instance sharper is that the guard was written specifically as a security fix, carried a CVE number, and shipped in a release whose whole purpose was to close the hole.

    The second condition in the CVE — “when algorithms are not explicitly restricted” — is where an operator has leverage, and it is also where most of the real-world exposure sits. Passing an explicit allowlist to the decode call removes the attack entirely, because the token’s own header no longer gets a vote. Code that omits the allowlist is common, partly because omitting it works fine in testing and fails only against an attacker.

    The maintenance picture is what turns this from a bug into a decision. There is no patched version to move to. The last release predates the report by more than a year, the issue has drawn no maintainer response in two months, and the CVE is now published with the current version named as affected. That is not an accusation — python-jose is a volunteer project and nobody is owed a release — but it is the fact a team has to plan around. If your dependency graph contains python-jose, the realistic options are a mitigation you apply yourself, a fork, or a different library. Waiting is not one of them right now.

    Worth stating plainly: there is no reported exploitation, no KEV listing, and no federal deadline attached to this. The urgency here comes from the absence of a patch, not from anyone attacking it.

    What to do

    • Pass an explicit algorithms allowlist on every verification call. This is the mitigation, and it is complete. A decode that is told to accept only RS256 will not fall through to HMAC no matter what the token header claims or what encoding the key arrived in. Grep your codebase for decode calls that omit the parameter.
    • Audit what you hand in as the key. The attack needs the verification routine to be initialized with the public key as an HMAC secret. Code paths that pass a single key variable through to both asymmetric and symmetric verification are the ones to look at.
    • Treat your public key as public, because it is. The CVE’s precondition — “attackers holding the service’s public key” — is met by default for any service publishing a JWKS endpoint. Do not count that as a barrier.
    • Decide about the dependency. python-jose 3.5.0 is the current release and is the affected version. If you cannot guarantee the allowlist across every call site, including transitive callers, plan a migration rather than a wait.
    • Track GitHub issue 414. It is the only place a fix will surface first, and it is where a maintainer response, if one comes, will appear.

    Sourcing note

    Checked: the NVD record for CVE-2026-85394, retrieved from the NIST CVE API on September 4, 2026, from which the description, scores, vectors, and CWE are taken verbatim; GitHub issue 414 on mpdavis/python-jose, which supplied the guard shape, the report date, and the recommended fix; the GitHub Security Advisory GHSA-6c5p-j8vq-pqhj for the prior CVE-2024-33663, which supplied the affected and patched versions; the PyPI JSON metadata for python-jose, which supplied the upload dates for 3.3.0, 3.4.0, and 3.5.0.

    Unresolved: whether the maintainers have responded privately. The GitHub issue shows no public maintainer comment, which is not the same as no contact. Whether a fix is in progress. The CVE was assigned by VulnCheck as CNA rather than by the project, and VulnCheck’s advisory does not describe a coordination timeline; the report and the CVE come from two different parties, roughly two months apart. No exploitation has been reported by anyone, and no second party has independently reproduced the DER bypass in public.