
Avis technique public et preuves de reproduction pour CVE-2026-52134 affectant la gestion de la relecture GOOSE dans libiec61850 v1.6.
Le CVE-2026-52134 a été attribué. La publication de l'enregistrement CVE correspondant est en attente.
src/goose/goose_receiver.cparseGoosePayload()Le chemin de réception de l'abonné GOOSE dans libiec61850 v1.6 ne rejette pas correctement certains messages GOOSE rejoués ou obsolètes avant de mettre à jour l'état visible par l'abonné et d'invoquer le callback de l'écouteur enregistré.
Deux expériences contrôlées démontrent un comportement connexe dans le même chemin de réception :
stNum inférieur : après que l'abonné a traité , la relecture d'une trame précédemment capturée a amené l'écouteur à rapporter à nouveau l'état et les données plus anciens.stNum=2stNum=1sqNum non croissant : lorsqu'une trame précédemment capturée avec le même stNum mais un sqNum plus ancien a été rejouée, la trame a été marquée comme invalide, mais les callbacks de l'écouteur se sont tout de même produits et les données rejouées sont restées visibles.Ces deux observations sont présentées comme deux manifestations liées d'un même problème de gestion du rejeu et de la fraîcheur des messages, et non comme deux CVE distincts.
Un attaquant doit être en mesure de :
0x88B8Il ne s'agit pas d'une attaque distante arbitraire via Internet.
La logique de réception concernée vérifie sqNum lorsque le stNum reçu est égal au stNum stocké. Le chemin de code testé ne rejette pas un stNum inférieur avant que l'état de l'abonné soit mis à jour et que l'écouteur soit invoqué.
Le fichier de la bibliothèque testée n'a pas été modifié. Les copies originale et de travail de src/goose/goose_receiver.c avaient la même valeur SHA-256 :
c42f2aec80f605c458d0bca524ccb09f2e35f9b83a02f854f95d9444616c075b

veth0 / veth1 sous LinuxtcpdumpLe banc de test a produit des transitions d'état contrôlées et affiché les valeurs stNum, sqNum, la validité et les données telles que visibles par le callback. Le fichier vulnérable de la bibliothèque lui-même n'a pas été modifié.
L'éditeur contrôlé a envoyé deux états :
State A: stNum=1, sqNum=0, data=1111
State B: stNum=2, sqNum=0, data=2222
La capture de paquets de référence contenait deux trames GOOSE dans cet ordre :
Frame 1: stNum=1, sqNum=0
Frame 2: stNum=2, sqNum=0

La sortie de l'abonné contenait également un callback auxiliaire pour stNum=1, sqNum=0 marqué valid=false avant l'état B. Ce callback auxiliaire est conservé dans les preuves mais n'est pas utilisé comme base de la conclusion sur la régression. L'état déterminant avant le rejeu était le callback ultérieur contenant stNum=2 et la valeur de données 2222.
Le script de rejeu a chargé la capture de référence à deux trames, sélectionné la trame 1, puis l'a envoyée une fois via veth0.
Immédiatement avant le rejeu, l'écouteur avait rapporté :
stNum=2, sqNum=0, valid=true, allData={2222}
Une fois l'ancienne trame rejouée, l'écouteur a rapporté :
stNum=1, sqNum=0, valid=true, allData={1111}

Cela démontre une régression observée de l'état de l'abonné, de stNum=2 à stNum=1, après rejeu d'une trame précédemment capturée avec un stNum inférieur.
Quatre copies supplémentaires de la même ancienne trame ont été envoyées. Les quatre contenaient stNum=1, sqNum=0.
Les copies ultérieures ont été rapportées comme valid=false, mais les numéros de callback ont continué d'augmenter et les données rejouées sont restées visibles :

Cette observation secondaire montre que le fait de marquer une trame répétée comme invalide n'a pas empêché sa livraison à l'écouteur dans le chemin de réception testé.
Une reproduction antérieure utilisait l'éditeur et l'abonné d'exemple officiels. L'éditeur a produit une séquence normale avec :
stNum=1
sqNum=0, 1, 2, 3
La première trame capturée (stNum=1, sqNum=0) a ensuite été rejouée après que l'abonné avait déjà traité la valeur de séquence ultérieure.

Les copies rejouées ont été rapportées comme invalides car le sqNum=0 reçu n'était pas plus récent que la valeur de séquence stockée. Cependant, l'abonné a continué d'afficher des événements d'écouteur contenant les données rejouées :

Cette expérience étaye une observation plus ciblée mais connexe :
Les captures d'écran brutes du rejeu d'origine sont conservées comme éléments à l'appui :
Ces images brutes contiennent des avertissements sans rapport concernant l'importation de modules optionnels de Scapy. Les avertissements n'ont pas empêché le script de rapporter les trames GOOSE capturées et de terminer la transmission, mais les captures d'écran plus nettes ci-dessus sont préférées pour examiner le résultat technique.
Les expériences démontrent différentes branches d'un même problème de rejeu et de fraîcheur des messages GOOSE :
| Observation | Message reçu | Résultat observé |
|---|---|---|
Rejeu avec stNum inférieur | stNum=2 stocké ; ancien stNum=1 reçu | L'ancien état et les anciennes données ont été livrés à l'écouteur et rapportés comme valides lors de l'exécution testée |
Rejeu avec sqNum non croissant | Même stNum ; sqNum plus ancien ou dupliqué reçu | Le message a été rapporté invalide, mais les callbacks de l'écouteur et la livraison des données rejouées ont continué |
La première observation constitue le résultat principal du CVE car elle démontre une régression d'un état plus récent vers un état plus ancien. La seconde observation est une preuve à l'appui de la manière dont les rejeux invalides du même état continuent de traverser le chemin de l'écouteur.
L'impact démontré au niveau logiciel est le suivant :
stNum plus récent à un stNum plus ancien.Les applications qui consomment les données de callback sans appliquer de contrôle indépendant de la fraîcheur peuvent traiter des valeurs obsolètes ou rejouées.
L'effet en aval dépend de l'application de l'abonné, de la configuration, de la logique d'interverrouillage et de la logique de protection.
Aucun relais de protection physique, circuit de déclenchement, disjoncteur ni réseau de sous-station en production n'a été testé ou utilisé dans ces expériences.
Avant de mettre à jour l'état de l'abonné ou d'invoquer l'écouteur :
stNum reçu qui est plus ancien que le dernier stNum accepté.stNum est inchangé, rejeter tout sqNum non croissant.stNum et les valeurs de sqNum répétées.Les images suivantes fournissent des informations sur la provenance et l'environnement. Elles appuient la reproduction mais ne sont pas individuellement nécessaires pour comprendre le résultat principal.





Le résultat démontré est l'acceptation du rejeu et la livraison d'un état obsolète. Il ne dépend pas de la preuve que l'authentification cryptographique était activée dans le déploiement testé.
Signalé par :