
# Análisis técnico de CVE-2026-26717: Ataque de temporización HMAC en la autenticación de webhooks de OpenFUN Richie LMS ## Resumen CVE-2026-26717 es una vulnerabilidad de ataque de temporización en el mecanismo de autenticación de webhooks de OpenFUN Richie LMS. La vulnerabilidad reside en la comparación insegura de firmas HMAC, lo que permite a un atacante remoto autenticado adivinar la firma secreta mediante análisis de temporización, comprometiendo potencialmente la integridad de las solicitudes entrantes de webhook. ## Código vulnerable El siguiente fragmento de código muestra la comparación insegura de HMAC presente en las versiones afectadas: ```python import hmac import hashlib def verify_webhook_signature(payload, signature, secret): expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() # Comparación vulnerable: no utiliza hmac.compare_digest return expected == signature ``` ## Impacto - **Gravedad:** Alta (CVSS 8.1) - **Vector de ataque:** Remoto - **Complejidad de ataque:** Alta - **Privilegios requeridos:** Ninguno - **Impacto en la confidencialidad:** Ninguno - **Impacto en la integridad:** Alto - **Impacto en la disponibilidad:** Ninguno Un atacante puede explotar esta vulnerabilidad para: 1. Realizar un ataque de temporización y recuperar gradualmente la firma HMAC secreta. 2. Forjar solicitudes de webhook legítimas. 3. Inyectar datos maliciosos en el LMS. 4. Manipular eventos del curso, inscripciones y otros datos sensibles. ## Corrección La solución recomendada es utilizar `hmac.compare_digest` para realizar comparaciones de firmas en tiempo constante: ```python import hmac import hashlib def verify_webhook_signature(payload, signature, secret): expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest() # Corrección: comparación en tiempo constante return hmac.compare_digest(expected, signature) ``` ## Referencias - [Advisory oficial de OpenFUN](https://openfun.fr/security/advisories/CVE-2026-26717) - [Documentación de Python sobre hmac.compare_digest](https://docs.python.org/3/library/hmac.html#hmac.compare_digest) - [CWE-208: Exposición de información mediante discrepancia de temporización](https://cwe.mitre.org/data/definitions/208.html)
Autor: Elvin Latifli
ID CVE: CVE-2026-26717
Gravedad: Media (CVSS 3.1: 4.8 — AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N)
Producto Afectado: OpenFUN Richie (todas las versiones anteriores al commit a1b5bbd)
Componente Afectado: src/richie/apps/courses/api.py — sync_course_runs_from_request
Commit de Corrección: a1b5bbda
Richie es un CMS de código abierto desarrollado por France Université Numérique (OpenFUN), construido sobre DjangoCMS. Está diseñado para ayudar a las organizaciones a crear portales de educación en línea con todas las funciones, con catálogos de cursos sincronizados desde Sistemas de Gestión del Aprendizaje (LMS) como OpenEdX. Richie gestiona la sincronización de datos de cursos mediante webhooks autenticados — uno de los cuales contenía la vulnerabilidad descrita en este informe.
El endpoint del webhook sync_course_runs_from_request utilizaba el operador estándar == para comparar el encabezado Authorization entrante con la firma HMAC esperada:
# VULNERABLE
signature_is_valid = any(
authorization_header == get_signature(message, secret)
for secret in getattr(settings, "RICHIE_COURSE_RUN_SYNC_SECRETS", [])
)
El operador == realiza una comparación no constante en tiempo — se detiene tan pronto como encuentra el primer carácter que no coincide. Al enviar muchas solicitudes manipuladas y medir las diferencias en el tiempo de respuesta, un atacante remoto puede deducir la firma correcta carácter por carácter, forjando finalmente un token de autenticación válido y eludiendo por completo la autenticación del webhook.
Un ataque exitoso permite a un atacante remoto no autenticado:
La corrección reemplaza == con hmac.compare_digest() de Python, que está diseñado para la comparación de cadenas en tiempo constante y elimina el canal lateral de temporización:
# CORREGIDO
signature_is_valid = any(
hmac.compare_digest(authorization_header, get_signature(message, secret))
for secret in getattr(settings, "RICHIE_COURSE_RUN_SYNC_SECRETS", [])
)