## https://sploitus.com/exploit?id=9F4E7FE1-F077-526B-8F43-1B82AC97495B
# CVE-2026-52134: GOOSE replay and message-freshness handling in libiec61850 v1.6
## Status
**CVE-2026-52134 has been assigned. Publication of the corresponding CVE Record is pending.**
## Affected product
- Vendor: MZ Automation GmbH
- Product: libiec61850
- Tested version: 1.6
- Status of other versions: Not confirmed
- Affected file: `src/goose/goose_receiver.c`
- Affected function: `parseGoosePayload()`
## Summary
The GOOSE subscriber receive path in libiec61850 v1.6 does not adequately reject certain replayed or stale GOOSE messages before updating subscriber-visible state and invoking the registered listener callback.
Two controlled experiments demonstrate related behavior in the same receive path:
1. **Lower-`stNum` rollback:** after the subscriber processed `stNum=2`, replaying a previously captured `stNum=1` frame caused the listener to report the older state and data again.
2. **Non-increasing `sqNum` replay:** when a previously captured frame with the same `stNum` but an older `sqNum` was replayed, the frame was marked invalid, but listener callbacks still occurred and replayed data remained visible.
These are presented as two related observations of one replay/message-freshness handling issue, not as two separate CVEs.
## Attack prerequisites
An attacker must be able to:
- Access the relevant IEEE 802.3 Layer-2 broadcast domain or VLAN
- Capture legitimate GOOSE Ethernet frames
- Inject Ethernet frames using EtherType `0x88B8`
This is not an arbitrary Internet-based remote attack.
## Technical basis
The relevant receive logic checks `sqNum` when the received `stNum` equals the stored `stNum`. The tested code path does not reject a lower `stNum` before subscriber state is updated and the listener is invoked.
The tested library file was not modified. The original and working copies of `src/goose/goose_receiver.c` had the same SHA-256 value:
```text
c42f2aec80f605c458d0bca524ccb09f2e35f9b83a02f854f95d9444616c075b
```

## Test environment
- Ubuntu 20.04.6 LTS
- libiec61850 v1.6
- Linux `veth0` / `veth1` isolated virtual Ethernet pair
- `tcpdump`
- TShark
- Python 3 and Scapy
- Modified example publisher and subscriber used only as a test harness
The test harness produced controlled state transitions and printed callback-visible `stNum`, `sqNum`, validity, and data values. The vulnerable library file itself was not modified.
## Experiment A: lower-stNum rollback
### Baseline
The controlled publisher sent two states:
```text
State A: stNum=1, sqNum=0, data=1111
State B: stNum=2, sqNum=0, data=2222
```
The baseline packet capture contained two GOOSE frames in this order:
```text
Frame 1: stNum=1, sqNum=0
Frame 2: stNum=2, sqNum=0
```

The subscriber output also contained an auxiliary callback for `stNum=1, sqNum=0` marked `valid=false` before State B. This auxiliary callback is retained in the evidence but is not used as the basis for the rollback conclusion. The decisive pre-replay state was the later callback containing `stNum=2` and data value `2222`.
### Single replay
The replay script loaded the two-frame baseline capture, selected frame 1, and sent it once through `veth0`.
Immediately before the replay, the listener had reported:
```text
stNum=2, sqNum=0, valid=true, allData={2222}
```
After the old frame was replayed once, the listener reported:
```text
stNum=1, sqNum=0, valid=true, allData={1111}
```

This demonstrates an observed subscriber-state rollback from `stNum=2` to `stNum=1` after replaying a previously captured lower-`stNum` frame.
### Additional identical replays
Four more copies of the same old frame were sent. All four contained `stNum=1, sqNum=0`.
The later copies were reported as `valid=false`, but callback numbers continued to increase and the replayed data remained visible:

This secondary observation shows that marking a repeated frame invalid did not prevent listener delivery in the tested receive path.
## Experiment B: non-increasing sqNum replay
An earlier reproduction used the official example publisher and subscriber. The publisher produced a normal sequence with:
```text
stNum=1
sqNum=0, 1, 2, 3
```
The first captured frame (`stNum=1, sqNum=0`) was then replayed after the subscriber had already processed the later sequence value.

The replayed copies were reported as invalid because the received `sqNum=0` was not newer than the stored sequence value. However, the subscriber continued to print listener events containing the replayed data:

This experiment supports a narrower but related observation:
- The same-state replay was detected through the validity flag.
- Detection did not prevent callback delivery of the replayed message in the tested example.
The raw original replay screenshots are retained as supporting material:
- [`12-original-sqnum-replay-command-raw.png`](evidence/12-original-sqnum-replay-command-raw.png)
- [`13-original-sqnum-replay-terminal-raw.png`](evidence/13-original-sqnum-replay-terminal-raw.png)
Those raw images contain unrelated Scapy optional-module import warnings. The warnings did not prevent the script from reporting the captured GOOSE frames and completing transmission, but the cleaner screenshots above are preferred for reviewing the technical result.
## Relationship between the two experiments
The experiments demonstrate different branches of the same GOOSE replay/message-freshness problem:
| Observation | Received message | Observed result |
|---|---|---|
| Lower-`stNum` replay | Stored `stNum=2`; received old `stNum=1` | Old state and data were delivered to the listener and reported as valid in the tested run |
| Non-increasing `sqNum` replay | Same `stNum`; received older or duplicate `sqNum` | Message was reported invalid, but listener callbacks and replayed data delivery continued |
The first observation is the primary CVE finding because it demonstrates rollback from a newer state to an older state. The second observation is supporting evidence about how invalid same-state replays continue through the listener path.
## Observed impact
The demonstrated software-level impact is:
- A previously accepted GOOSE state can be delivered to the listener after a newer state has already been processed.
- Subscriber-visible state can move from a newer `stNum` to an older `stNum`.
- Repeated stale frames can continue to cause listener activity even when later copies are reported as invalid.
Applications that consume callback data without independent freshness enforcement may process stale or replayed values.
The downstream effect depends on the subscriber application, configuration, interlocking logic, and protection logic.
**No physical protection relay, trip circuit, circuit breaker, or production substation network was tested or operated in these experiments.**
## Suggested remediation direction
Before updating subscriber state or invoking the listener:
1. Reject a received `stNum` that is older than the last accepted `stNum`.
2. When `stNum` is unchanged, reject a non-increasing `sqNum`.
3. Ensure that a frame classified as invalid cannot overwrite the last accepted state or deliver stale data through the normal listener path.
4. Account for legitimate restart, resynchronization, and counter-handling behavior in the final implementation.
## Temporary risk reduction
- Restrict access to GOOSE Ethernet segments and VLANs.
- Prevent unauthorized devices from injecting Layer-2 frames.
- Monitor unexpected `stNum` rollback and repeated `sqNum` values.
- Validate subscriber data freshness before using callback values in critical logic.
- Test changes in an isolated laboratory environment before production deployment.
## Supporting evidence
The following images provide provenance and environment information. They support the reproduction but are not individually required to understand the main result.
### Test harness inspection

### Successful build

### Isolated veth network

### Baseline capture summary

### Artifact checksums

## Proposed classification
- Weakness: replay and message-freshness validation weakness
- Proposed CWE: CWE-294, Authentication Bypass by Capture-replay
The demonstrated result is replay acceptance and stale-state delivery. It does not depend on proving that cryptographic authentication was enabled in the tested deployment.
## References
- [Official libiec61850 repository](https://github.com/mz-automation/libiec61850)
- [Affected source file in the v1.6 tree](https://github.com/mz-automation/libiec61850/blob/v1.6/src/goose/goose_receiver.c)
- [CVE-2026-52134 on CVE.org](https://vulners.com/cve/CVE-2026-52134)
## Credits
Reported by:
- Wang Jing
- Li Chen Yu
- Guo Lu Lu
- Guo Jia Xin
- Zhang Jia Tu