
# Analisi tecnica di CVE-2026-26717: attacco di timing HMAC nell'autenticazione webhook di OpenFUN Richie LMS Questo documento analizza CVE-2026-26717, un attacco di timing HMAC nell'autenticazione webhook di OpenFUN Richie LMS, includendo il codice vulnerabile, l'impatto e la correzione tramite `hmac.compare_digest`.
Autore: Elvin Latifli
ID CVE: CVE-2026-26717
Gravità: Media (CVSS 3.1: 4.8 — AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N)
Prodotto interessato: OpenFUN Richie (tutte le versioni precedenti al commit a1b5bbd)
Componente interessato: src/richie/apps/courses/api.py — sync_course_runs_from_request
Commit di correzione: a1b5bbda
Richie è un CMS open-source sviluppato da France Université Numérique (OpenFUN), costruito su DjangoCMS. È progettato per aiutare le organizzazioni a creare portali di formazione online completi con cataloghi di corsi sincronizzati da Learning Management Systems (LMS) come OpenEdX. Richie gestisce la sincronizzazione dei dati dei corsi tramite webhook autenticati — uno dei quali conteneva la vulnerabilità descritta in questo report.
L'endpoint webhook sync_course_runs_from_request utilizzava l'operatore standard == per confrontare l'header Authorization in arrivo con la firma HMAC attesa:
# VULNERABILE
signature_is_valid = any(
authorization_header == get_signature(message, secret)
for secret in getattr(settings, "RICHIE_COURSE_RUN_SYNC_SECRETS", [])
)
L'operatore == esegue un confronto non a tempo costante — si interrompe non appena viene trovato il primo carattere non corrispondente. Inviando molte richieste appositamente costruite e misurando le differenze nei tempi di risposta, un attaccante remoto può dedurre la firma corretta un carattere alla volta, riuscendo infine a forgiare un token di autenticazione valido e bypassando completamente l'autenticazione del webhook.
Un attacco riuscito consente a un attaccante remoto non autenticato di:
La correzione sostituisce == con hmac.compare_digest() di Python, progettato per il confronto di stringhe a tempo costante ed elimina il canale laterale di timing:
# CORRETTO
signature_is_valid = any(
hmac.compare_digest(authorization_header, get_signature(message, secret))
for secret in getattr(settings, "RICHIE_COURSE_RUN_SYNC_SECRETS", [])
)