Open
reject unsupported b64 critical header extension and Fix CVE-2026-48526 #49
lipengyu001Add on Jul 21
reject unsupported b64 critical header extension and Fix CVE-2026-48526 #49
Open
Total: 3 files changed+158-2
| @@ -0,0 +1,54 @@ | |||
| 1 | +From 7a4b857508963c6d3953fd8e59c3c2e1d52b2915 Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: OpenEuler Security Team <security@openeuler.org> | ||
| 3 | +Date: Tue, 21 Jul 2026 12:00:00 +0800 | ||
| 4 | +Subject: [PATCH] CVE-2026-32597: do not advertise unsupported b64 critical | ||
| 5 | + extension | ||
| 6 | + | ||
| 7 | +PyJWT 2.3.0 does not implement RFC 7797's b64=false processing. The | ||
| 8 | +initial backport marked b64 as supported nevertheless, allowing a token | ||
| 9 | +with crit=["b64"] to pass critical-header validation without interpreting | ||
| 10 | +the extension. Reject it like every other unsupported critical extension. | ||
| 11 | + | ||
| 12 | +--- | ||
| 13 | + jwt/api_jws.py | 4 +++- | ||
| 14 | + tests/test_api_jws.py | 17 +++++++++++++++++ | ||
| 15 | + 2 files changed, 20 insertions(+), 1 deletion(-) | ||
| 16 | + | ||
| 17 | +diff --git a/jwt/api_jws.py b/jwt/api_jws.py | ||
| 18 | +index d53f0f0..89c45c1 100644 | ||
| 19 | +--- a/jwt/api_jws.py | ||
| 20 | ++++ b/jwt/api_jws.py | ||
| 21 | +@@ -246,6 +246,8 @@ class PyJWS: | ||
| 22 | + | ||
| 23 | + # Extensions that PyJWT actually understands and supports | ||
| 24 | +- _supported_crit = {"b64"} | ||
| 25 | ++ # | ||
| 26 | ++ # PyJWT 2.3.0 does not implement any JWS critical extensions. | ||
| 27 | ++ _supported_crit = set() | ||
| 28 | + | ||
| 29 | + def _validate_headers(self, headers, *, encoding=False): | ||
| 30 | + if "kid" in headers: | ||
| 31 | +diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py | ||
| 32 | +index 2a2256f..780d6e6 100644 | ||
| 33 | +--- a/tests/test_api_jws.py | ||
| 34 | ++++ b/tests/test_api_jws.py | ||
| 35 | +@@ -721,0 +722,17 @@ class TestJWS: | ||
| 36 | ++ def test_decode_rejects_unsupported_b64_crit_extension(self, jws, payload): | ||
| 37 | ++ token = jws.encode( | ||
| 38 | ++ payload, | ||
| 39 | ++ "secret", | ||
| 40 | ++ algorithm="HS256", | ||
| 41 | ++ headers={"crit": ["b64"], "b64": False}, | ||
| 42 | ++ ) | ||
| 43 | ++ | ||
| 44 | ++ with pytest.raises( | ||
| 45 | ++ InvalidTokenError, match="Unsupported critical extension: b64" | ||
| 46 | ++ ): | ||
| 47 | ++ jws.decode(token, "secret", algorithms=["HS256"]) | ||
| 48 | ++ | ||
| 49 | ++ with pytest.raises( | ||
| 50 | ++ InvalidTokenError, match="Unsupported critical extension: b64" | ||
| 51 | ++ ): | ||
| 52 | ++ jws.get_unverified_header(token) | ||
| 53 | +-- | ||
| 54 | + 2.39.5 | ||
| @@ -0,0 +1,94 @@ | |||
| 1 | +From 3bb266d5342aa65e67bed0ed9e979b5a51b23bb5 Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: OpenEuler Security Team <security@openeuler.org> | ||
| 3 | +Date: Tue, 21 Jul 2026 12:00:00 +0800 | ||
| 4 | +Subject: [PATCH] CVE-2026-48526: reject raw JWKs as HMAC secrets | ||
| 5 | + | ||
| 6 | +Raw JSON Web Keys must be parsed with HMACAlgorithm.from_jwk(), rather | ||
| 7 | +than used as arbitrary HMAC secret bytes. This blocks algorithm-confusion | ||
| 8 | +attacks when a verifier incorrectly permits both HMAC and asymmetric | ||
| 9 | +algorithms and provides a public JWK as its key. | ||
| 10 | + | ||
| 11 | +--- | ||
| 12 | + jwt/algorithms.py | 17 +++++++++++++++++ | ||
| 13 | + tests/test_api_jws.py | 20 ++++++++++++++++++++ | ||
| 14 | + 2 files changed, 37 insertions(+), 1 deletion(-) | ||
| 15 | + | ||
| 16 | +diff --git a/jwt/algorithms.py b/jwt/algorithms.py | ||
| 17 | +index 1aa30ed..7420d2f 100644 | ||
| 18 | +--- a/jwt/algorithms.py | ||
| 19 | ++++ b/jwt/algorithms.py | ||
| 20 | +@@ -186,6 +186,23 @@ class HMACAlgorithm(Algorithm): | ||
| 21 | + " should not be used as an HMAC secret." | ||
| 22 | + ) | ||
| 23 | + | ||
| 24 | ++ # A raw JWK is public key metadata, not the HMAC secret material. | ||
| 25 | ++ # Requiring callers to use from_jwk() prevents an attacker from | ||
| 26 | ++ # switching a mixed algorithm allow-list to HS* and using an issuer's | ||
| 27 | ++ # public JWK as the HMAC key. | ||
| 28 | ++ if key.lstrip().startswith(b"{"): | ||
| 29 | ++ try: | ||
| 30 | ++ jwk = json.loads(key) | ||
| 31 | ++ except ValueError: | ||
| 32 | ++ jwk = None | ||
| 33 | ++ | ||
| 34 | ++ if isinstance(jwk, dict) and "kty" in jwk: | ||
| 35 | ++ raise InvalidKeyError( | ||
| 36 | ++ "The specified key looks like a JWK and should not be " | ||
| 37 | ++ "used directly as an HMAC secret. Load it via " | ||
| 38 | ++ "HMACAlgorithm.from_jwk first." | ||
| 39 | ++ ) | ||
| 40 | ++ | ||
| 41 | + return key | ||
| 42 | + | ||
| 43 | + @staticmethod | ||
| 44 | +diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py | ||
| 45 | +index 780d6e6..a40b97b 100644 | ||
| 46 | +--- a/tests/test_api_jws.py | ||
| 47 | ++++ b/tests/test_api_jws.py | ||
| 48 | +@@ -1,18 +1,21 @@ | ||
| 49 | ++import hashlib | ||
| 50 | ++import hmac | ||
| 51 | + import json | ||
| 52 | + from decimal import Decimal | ||
| 53 | + | ||
| 54 | + import pytest | ||
| 55 | + | ||
| 56 | + from jwt.algorithms import Algorithm, has_crypto | ||
| 57 | + from jwt.api_jws import PyJWS | ||
| 58 | + from jwt.exceptions import ( | ||
| 59 | + DecodeError, | ||
| 60 | + InvalidAlgorithmError, | ||
| 61 | ++ InvalidKeyError, | ||
| 62 | + InvalidSignatureError, | ||
| 63 | + InvalidTokenError, | ||
| 64 | + ) | ||
| 65 | +-from jwt.utils import base64url_decode | ||
| 66 | ++from jwt.utils import base64url_decode, base64url_encode | ||
| 67 | + | ||
| 68 | + from .utils import crypto_required, key_path, no_crypto_required | ||
| 69 | + | ||
| 70 | + try: | ||
| 71 | +@@ -571,4 +574,20 @@ class TestJWS: | ||
| 72 | + with pytest.raises( | ||
| 73 | + InvalidTokenError, match="Unsupported critical extension: b64" | ||
| 74 | + ): | ||
| 75 | + jws.get_unverified_header(token) | ||
| 76 | ++ | ||
| 77 | ++ def test_decode_rejects_jwk_used_as_hmac_secret(self, jws, payload): | ||
| 78 | ++ public_jwk = '{"kty":"RSA","n":"AQAB","e":"AQAB"}' | ||
| 79 | ++ header = base64url_encode(b'{"alg":"HS256","typ":"JWT"}') | ||
| 80 | ++ signing_input = b".".join((header, base64url_encode(payload))) | ||
| 81 | ++ signature = hmac.new( | ||
| 82 | ++ public_jwk.encode("utf-8"), signing_input, hashlib.sha256 | ||
| 83 | ++ ).digest() | ||
| 84 | ++ token = b".".join((signing_input, base64url_encode(signature))).decode( | ||
| 85 | ++ "ascii" | ||
| 86 | ++ ) | ||
| 87 | ++ | ||
| 88 | ++ with pytest.raises( | ||
| 89 | ++ InvalidKeyError, match="should not be used directly as an HMAC secret" | ||
| 90 | ++ ): | ||
| 91 | ++ jws.decode(token, public_jwk, algorithms=["HS256", "RS256"]) | ||
| 92 | + | ||
| 93 | +-- | ||
| 94 | +2.39.5 | ||
| @@ -2,7 +2,7 @@ | |||
| 2 | %bcond_without tests | 2 | %bcond_without tests |
| 3 | Name: python-jwt | 3 | Name: python-jwt |
| 4 | Version: 2.3.0 | 4 | Version: 2.3.0 |
| 5 | -Release: 5 | 5 | +Release: 6 |
| 6 | Summary: JSON Web Token implementation in Python | 6 | Summary: JSON Web Token implementation in Python |
| 7 | License: MIT | 7 | License: MIT |
| 8 | URL: https://github.com/jpadilla/pyjwt | 8 | URL: https://github.com/jpadilla/pyjwt |
| @@ -12,7 +12,9 @@ BuildArch: noarch | |||
| 12 | Patch6000: backport-CVE-2022-29217.patch | 12 | Patch6000: backport-CVE-2022-29217.patch |
| 13 | Patch6001: backport-fix-failing-advisory-test.patch | 13 | Patch6001: backport-fix-failing-advisory-test.patch |
| 14 | Patch6002: backport-chore-fix-lint-errors.patch | 14 | Patch6002: backport-chore-fix-lint-errors.patch |
| 15 | -Patch6003: backport-CVE-2026-32597-1.patch | 15 | +Patch6003: backport-CVE-2026-32597-1.patch |
| 16 | +Patch6004: backport-CVE-2026-32597-2.patch | ||
| 17 | +Patch6005: backport-CVE-2026-48526.patch | ||
| 16 | 18 | ||
| 17 | %if %{with tests} | 19 | %if %{with tests} |
| 18 | BuildRequires: python3-pytest python3-pytest-cov python3-pytest-runner | 20 | BuildRequires: python3-pytest python3-pytest-cov python3-pytest-runner |
| @@ -58,6 +60,12 @@ Requires: python%{python3_pkgversion}-cryptography | |||
| 58 | %doc README.rst | 60 | %doc README.rst |
| 59 | 61 | ||
| 60 | %changelog | 62 | %changelog |
| 63 | +* Tue Jul 21 2026 lipengyu <lipengyu@kylinos.cn> - 2.3.0-6 | ||
| 64 | +- Type:CVE | ||
| 65 | +- CVE:CVE-2026-32597 CVE-2026-48526 | ||
| 66 | +- DESC:reject unsupported b64 critical header extension | ||
| 67 | + Fix CVE-2026-48526 | ||
| 68 | + | ||
| 61 | * Fri Jul 17 2026 xiaoo_robot <xiaoo_robot@petalmail.com> - 2.3.0-5 | 69 | * Fri Jul 17 2026 xiaoo_robot <xiaoo_robot@petalmail.com> - 2.3.0-5 |
| 62 | - Type:CVE | 70 | - Type:CVE |
| 63 | - CVE:CVE-2026-32597 | 71 | - CVE:CVE-2026-32597 |