#38807 [BC-Low] DoS any reth node via ban logic exploit
Submitted on Jan 14th 2025 at 06:38:58 UTC by @Blobism for Attackathon | Ethereum Protocol
Report ID: #38807
Report Type: Blockchain/DLT
Report severity: Low
Target: https://github.com/paradigmxyz/reth
Impacts:
Shutdown of less than 10% of network processing nodes without brute force actions, but does not shut down the network
Description
Brief/Intro
The latest reth
release (v1.1.5
) contains a ban logic vulnerability, enabling a DoS attack that prevents all new incoming peers from connecting. The exploit can easily be executed on all reth
nodes on the network. The exploit works by making a malicious node intentionally get banned on the target reth
node via a specific code path. Then, a bug in the counting logic of incoming peers can be exploited to conduct the DoS.
Vulnerability Details
The primary bug exists in crates/net/network/src/peers.rs
, in the function on_incoming_session_established
. The bug occurs when the early return is reached below:
The bug here is that if the early return gets reached while the peer is in state PeerConnectionState::Idle
, then the self.connection_info.inc_in()
will not have a corresponding decrement. Therefore, if an attacker has control of a banned malicious peer that can reach this early return, they can increment this counter as much as they desire. This leads to a DoS, as the counter controls whether or not new incoming peers are accepted.
The next challenge is how to reach this early return with a banned peer. It would initially seem that this point is unreachable, given that there is a check early in the function to see if the peer is in the ban_list
:
In fact, this can be circumvented, and the early return with the bug can be reached. This is achieved by making the malicious peer pass the peer.is_banned()
check WITHOUT being on the ban_list
.
An attacker can make a malicious peer fulfill these conditions by exploiting a bug in function on_connection_failure
from the same file:
The bug here is that the reputation of the peer is reduced, but the peer is not added to the ban_list
when their reputation falls below the threshold. Any call to peer.is_banned()
will be true once the reputation falls below the threshold. Therefore, an attacker can exploit this by repeatedly sending a message from the peer that triggers this reputation reduction until their reputation falls below the threshold.
A simple message such as this (sent from a modified, malicious reth client) is enough to reach this code path:
This message should trigger an EthStreamError::InvalidMessage
which can reach the desired point in the code. The process of reconnecting the malicious peer and sending this message can repeat until peer.is_banned()
is true, but the peer is still not on the ban_list
.
Finally, the attacker can repeatedly reconnect this banned peer, and it will constantly increment the incoming connection count without ever decrementing, exploiting the first bug. This ability to keep incrementing the counter leads to an incoming peer DoS.
The fix for these bugs would be to maintain the invariant that if peer.is_banned()
is true, then the peer MUST be in the ban_list
. This should make the early return in the first bug unreachable.
Impact Details
This exploit can easily be executed on any reth node on the network. The percentage of reth execution nodes is 2% according to clientdiversity.org
. Therefore, this is a Low vulnerability bug that falls into the following category:
"Shutdown of less than 10% of network processing nodes without brute force actions, but does not shut down the network" - No new incoming peers will be accepted, so this processing node could potentially no longer propagate or receive new transactions, effectively shutting down the node
References
https://github.com/paradigmxyz/reth/blob/v1.1.5/crates/net/network/src/peers.rs
Link to Proof of Concept
https://gist.github.com/blobism/82618bf98ee87add058670492d9b5d0b
Proof of Concept
Proof of Concept
The exploit follows this procedure:
Start a normal reth node (the target)
Start a malicious node
The normal reth node adds the malicious node as a peer
The malicious node repeatedly sends an invalid message, then reconnects, until it passes the banned threshold
The malicious node keeps trying to reconnect, incrementing the incoming peer counter until the DoS condition is reached
Unit Test PoC
This PoC unit test should be added to crates/net/network/src/peers.rs
:
Run it with:
Local testnet PoC
Create the following chain.json
:
Create and run normal node
Fetch reth and run normal reth node:
Note down the p2p enode ID for this client from the logs. The --max-inbound-peers
has been set to 3 only for demonstration purposes. The exploit works with any max inbound size.
Create and run malicious node
The malicious node here is a reth node with one minor modification. It could in principle be any modified execution client or custom client.
Fetch reth
Only for this malicious reth version, add the following line to crates/net/network/src/manager.rs
at the beginning of the SwarmEvent::PeerAdded(peer_id)
block (line 758):
The result should look like this:
This malicious client works by sending an invalid message to a peer the moment that peer is added.
Run malicious node:
Note down the p2p enode ID for this client from the logs.
Start exploit
Now make the normal node connect to the malicious node (normal node is running on default port):
At this point, the malicious peer should already fulfill peer.is_banned()
on the normal reth node, because the malicious peer retries connecting.
Repeat the following steps:
Stop malicious node
Start malicious node
Run the command below, making the malicious node try to connect to the normal node
After repeating this process 3 times, the --max-inbound-peers
of 3 should be reached, and the DoS has occurred. This could be automated to happen much faster, but the attack also works when executed slowly.
No inbound peers should be able to connect. This can be confirmed by trying to connect a new, normal reth peer and seeing it get rejected (make sure to use a different --datadir
than before):
Now try to have it connect to the original node
You will see a log line similar to this on the target node:
Last updated
Was this helpful?