
Публичное техническое уведомление и доказательства воспроизведения для CVE-2026-52134, затрагивающего обработку повторного воспроизведения (replay) GOOSE в libiec61850 v1.6.
CVE-2026-52134 присвоен. Публикация соответствующей записи CVE ожидается.
src/goose/goose_receiver.cparseGoosePayload()Путь приёма подписчика GOOSE в libiec61850 v1.6 недостаточно полно отклоняет определённые повторно воспроизведённые или устаревшие сообщения GOOSE перед обновлением состояния, видимого подписчику, и вызовом зарегистрированного обратного вызова слушателя.
Два контролируемых эксперимента демонстрируют связанное поведение в одном и том же пути приёма:
stNum: после того как подписчик обработал stNum=2, повторное воспроизведение ранее захваченного кадра с stNum=1 привело к тому, что слушатель снова сообщил более старое состояние и данные.sqNum: когда повторно воспроизводился ранее захваченный кадр с тем же stNum, но более старым sqNum, кадр помечался как недействительный, но обратные вызовы слушателя по-прежнему происходили, а повторно переданные данные оставались видимыми.Они представлены как два связанных наблюдения одной проблемы обработки повторного воспроизведения/свежести сообщений, а не как две отдельные CVE.
Атакующий должен иметь возможность:
0x88B8Это не произвольная удалённая атака через Интернет.
Соответствующая логика приёма проверяет sqNum, когда полученный stNum равен сохранённому stNum. Протестированный путь кода не отклоняет меньший stNum до того, как состояние подписчика будет обновлено и будет вызван слушатель.
Тестируемый файл библиотеки не изменялся. Оригинальная и рабочая копии src/goose/goose_receiver.c имели одинаковое значение SHA-256:
c42f2aec80f605c458d0bca524ccb09f2e35f9b83a02f854f95d9444616c075b

veth0 / veth1tcpdumpТестовый стенд обеспечивал контролируемые переходы состояний и выводил значения stNum, sqNum, признак достоверности и данные, видимые в обратных вызовах. Сам уязвимый файл библиотеки не изменялся.
Контролируемый издатель отправлял два состояния:
State A: stNum=1, sqNum=0, data=1111
State B: stNum=2, sqNum=0, data=2222
Базовый захват пакетов содержал два кадра GOOSE в следующем порядке:
Frame 1: stNum=1, sqNum=0
Frame 2: stNum=2, sqNum=0

В выводе подписчика также присутствовал вспомогательный обратный вызов для stNum=1, sqNum=0, помеченный valid=false перед состоянием B. Этот вспомогательный обратный вызов сохранён в материалах, но не используется как основание для вывода об откате. Решающим состоянием до повторного воспроизведения был более поздний обратный вызов, содержащий stNum=2 и значение данных 2222.
Скрипт повторного воспроизведения загрузил базовый захват из двух кадров, выбрал кадр 1 и отправил его один раз через veth0.
Непосредственно перед повторным воспроизведением слушатель сообщил:
stNum=2, sqNum=0, valid=true, allData={2222}
После однократного повторного воспроизведения старого кадра слушатель сообщил:
stNum=1, sqNum=0, valid=true, allData={1111}

Это демонстрирует наблюдаемый откат состояния подписчика с stNum=2 на stNum=1 после повторного воспроизведения ранее захваченного кадра с меньшим stNum.
Было отправлено ещё четыре копии того же старого кадра. Все четыре содержали stNum=1, sqNum=0.
Более поздние копии были помечены как valid=false, но номера обратных вызовов продолжали увеличиваться, а повторно переданные данные оставались видимыми:

Это вторичное наблюдение показывает, что пометка повторного кадра как недействительного не предотвращала доставку слушателю в протестированном пути приёма.
Более раннее воспроизведение использовало официальные примеры издателя и подписчика. Издатель формировал обычную последовательность с:
stNum=1
sqNum=0, 1, 2, 3
Первый захваченный кадр (stNum=1, sqNum=0) затем повторно воспроизводился после того, как подписчик уже обработал более позднее значение последовательности.

Повторно воспроизведённые копии были помечены как недействительные, поскольку полученный sqNum=0 не был новее сохранённого значения последовательности. Однако подписчик продолжал выводить события слушателя, содержащие повторно переданные данные:

Этот эксперимент подтверждает более узкое, но связанное наблюдение:
Исходные необработанные скриншоты повторного воспроизведения сохранены в качестве вспомогательных материалов:
Эти необработанные изображения содержат несвязанные предупреждения об импорте дополнительных модулей Scapy. Предупреждения не помешали скрипту сообщить о захваченных кадрах GOOSE и завершить передачу, однако для просмотра технического результата предпочтительнее приведённые выше более чистые скриншоты.
Эксперименты демонстрируют разные ветви одной и той же проблемы повторного воспроизведения/свежести сообщений GOOSE:
| Observation | Received message | Observed result |
|---|---|---|
Повторное воспроизведение с меньшим stNum | Сохранён stNum=2; получен старый stNum=1 | Старые состояние и данные были доставлены слушателю и в тестовом запуске помечены как достоверные |
Повторное воспроизведение с невозрастающим sqNum | Тот же stNum; получен более старый или повторный sqNum | Сообщение было помечено как недействительное, но обратные вызовы слушателя и доставка повторно переданных данных продолжились |