https://github.com/hackenproof-public/somnia
A vulnerability exists in the peer-to-peer handshake implementation that allows an attacker to impersonate legitimate nodes during connection establishment. This attack exploits the bidirectional nature of the handshake protocol to replay challenges between different connection sessions, enabling successful spoofing of node identities at the transport layer.
The peer-to-peer handshake protocol looks something like this (simplified):
The security of this handshake relies on the assumption that challenges are unpredictable and cannot be known in advance. However, the protocol's bidirectional nature creates a vulnerability window where:
The exploitation leverages the timing gap between challenge exchange and signature verification to perform a challenge replay attack:
Initial Connection Setup
challenge0) to Node AChallenge Harvesting
challenge1)challenge0 (not needed for attack)Challenge Replay
challenge1 (from Node A) to Node BIdentity Spoofing
challenge1 to the attackerThe attacker successfully impersonates Node B in Node A's view, establishing an authenticated connection under false identity. This breaks the fundamental assumption that authenticated connections represent genuine peer identities.
Severity: High
This peer impersonation attack allows an attacker to successfully establish authenticated connections while spoofing their identity at the transport layer.
Peer Discovery Manipulation: The attacker can send malicious ShareConnectedPeerMessage and RequestPeerDetailsMessage while impersonating legitimate nodes, potentially:
Mempool Transaction Attribution Bypass: The attacker can send transactions to the mempool while appearing to originate from a different peer, potentially:
Network Monitoring Evasion: The attacker can evade network-level monitoring and metrics collection that relies on transport-layer sender identification.
So to summarize, an attacker could use this vulnerability to disrupt network connectivity and potentially isolate nodes, while simultaneously conducting enhanced DoS attacks by evading per-peer rate limits.
Include connection-specific context in the challenge signature to prevent replay attacks:
For example, include the IP address of the peer as part of the signature data.
The following proof of concept demonstrates the vulnerability by implementing a controlled attack scenario where one node acts as an attacker and successfully impersonates another peer during handshake.
Apply the diff included with this report:
Add the following test to demonstrate the attack to the protocol_network_test.cc file:
TEST_F(ProtocolNetworkTest, ProtocolNetworkConnectionSpoofAttack) {
std::vector<std::unique_ptr<NodeActorManager>> nodes;
std::vector<crypto::PrivateKeys> keys;
std::vector<ChainParameters> params;
// Create 3 nodes: Attacker, Node A, Node B
for (int i = 0; i < 3; i++) {
keys.emplace_back(crypto::PrivateKeys::GenerateNonRandomKeys(1337 + i));
params.push_back(protocol_network_chain_parameters);
params[i].local_parameters.protocol_listen_port = 9051 + (i * 10);
params[i].local_parameters.data_listen_port = 9101 + (i * 10);
params[i].local_parameters.api_http_port = 9201 + (i * 10);
params[i].local_parameters.api_websocket_port = 9301 + (i * 10);
params[i].local_parameters.storage_database_directory =
fmt::format("/tmp/somnia_{}_{}", keys[i].GetAddress(), RandomHash());
nodes.emplace_back(std::make_unique<NodeActorManager>(params[i], keys[i], epoch_manager));
}
auto& main_network = nodes[0]->protocol_network.GetAsyncProtocolNetwork();
// Trigger the attack by connecting the attacker to both legitimate nodes
for (int i = 1; i < 3; i++) {
main_network.ConnectToPeer(keys[i].GetAddress(), "127.0.0." + std::to_string(i),
params[i].local_parameters.protocol_listen_port);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// Allow sufficient time for attack execution
std::this_thread::sleep_for(std::chrono::seconds(15));
}
bazel test --config no-avx -s //somnia/node:protocol_network_test --test_output=all --test_filter="ProtocolNetworkTest.*"
Note: The sleep timeout may need adjustment based on system performance and network conditions.
Upon successful execution, the test demonstrates the vulnerability through the following log evidence:
[2025-09-10 01:22:31.579] [protocol_net ] [info] PeerNetwork successfully completed handshake with 0x95f9f0834ceeeb6beaf51955a21d61b0ed21114f and my key is 0x371b429f37d6183509d07f94fe8a1aed928353f3
[2025-09-10 01:22:31.628] [protocol_net ] [info] PeerNetwork successfully completed handshake with 0x054a8494bea2c5251e70c3e8120138b21ab91193 and my key is 0x95f9f0834ceeeb6beaf51955a21d61b0ed21114f
[2025-09-10 01:22:31.639] [protocol_net ] [info] PeerNetwork successfully completed handshake with 0x371b429f37d6183509d07f94fe8a1aed928353f3 and my key is 0x054a8494bea2c5251e70c3e8120138b21ab91193
[2025-09-10 01:22:31.639] [protocol_net ] [info] PeerNetwork successfully completed handshake with 0x95f9f0834ceeeb6beaf51955a21d61b0ed21114f and my key is 0x054a8494bea2c5251e70c3e8120138b21ab91193
Node Identities:
0x054a8494bea2c5251e70c3e8120138b21ab911930x371b429f37d6183509d07f94fe8a1aed928353f30x95f9f0834ceeeb6beaf51955a21d61b0ed21114fResulting Connection State: The attack creates a false connection topology where Node A believes it has established a legitimate connection with Node B, but is actually connected to the attacker:
| Connection | Connected Node Perspective | Reality |
|---|---|---|
Node A ↔ Attacker |
Connected to Node B | Connected to Attacker |
Node B ↔ Attacker |
Connected to Attacker | Connected to Attacker |
Attacker ↔ Node A |
Connected to Node A | Connected to Node A |
Attacker ↔ Node B |
Connected to Node B | Connected to Node B |
This demonstrates successful identity spoofing where the attacker has inserted itself into the intended Node A ↔ Node B communication path while maintaining Node A's belief that it is connected to the legitimate Node B.
Important Note: In the PoC we connected to Node B just because it was simpler, but in reality, the attacker can drop the handshake with Node B and just connect to Node A without compromising it's own identity. Also, the attacker can do the same attack with Node B and connect to Node B as Node A, fooling them both.