
# Análise Técnica do CVE-2026-26717: Ataque de Temporização HMAC na Autenticação de Webhooks do OpenFUN Richie LMS O CVE-2026-26717 é uma vulnerabilidade de ataque de temporização HMAC na autenticação de webhooks do OpenFUN Richie LMS. Este artigo apresenta o código vulnerável, o impacto e a correção utilizando `hmac.compare_digest`.
Autor: Elvin Latifli
ID CVE: CVE-2026-26717
Gravidade: Média (CVSS 3.1: 4.8 — AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N)
Produto Afetado: OpenFUN Richie (todas as versões anteriores ao commit a1b5bbd)
Componente Afetado: src/richie/apps/courses/api.py — sync_course_runs_from_request
Commit de Correção: a1b5bbda
Richie é um CMS de código aberto desenvolvido pela France Université Numérique (OpenFUN), construído sobre o DjangoCMS. Ele foi projetado para ajudar organizações a criar portais de educação online completos, com catálogos de cursos sincronizados a partir de Sistemas de Gestão de Aprendizagem (LMS), como o OpenEdX. O Richie gerencia a sincronização de dados dos cursos por meio de webhooks autenticados — um dos quais continha a vulnerabilidade descrita neste relatório.
O endpoint do webhook sync_course_runs_from_request usava o operador padrão == para comparar o cabeçalho Authorization recebido com a assinatura HMAC esperada:
# VULNERÁVEL
signature_is_valid = any(
authorization_header == get_signature(message, secret)
for secret in getattr(settings, "RICHIE_COURSE_RUN_SYNC_SECRETS", [])
)
O operador == realiza uma comparação de tempo não constante — ele interrompe a execução assim que o primeiro caractere divergente é encontrado. Ao enviar muitas solicitações elaboradas e medir diferenças no tempo de resposta, um atacante remoto pode deduzir a assinatura correta um caractere por vez, eventualmente forjando um token de autenticação válido e contornando completamente a autenticação do webhook.
Um ataque bem-sucedido permite que um atacante remoto não autenticado:
A correção substitui == por hmac.compare_digest() do Python, que foi projetado para comparação de strings em tempo constante e elimina o canal lateral de temporização:
# CORRIGIDO
signature_is_valid = any(
hmac.compare_digest(authorization_header, get_signature(message, secret))
for secret in getattr(settings, "RICHIE_COURSE_RUN_SYNC_SECRETS", [])
)