The executeTransation
function performs a low level call to complete the previously submitted transaction:
One of the main risks of using low-level calls (call
) is that the target address (txn.to
) can be a contract with a malicious receive()
or fallback()
function. In this specific case, reentrancy is not an issue because txn.executed = true;
is set before the external call. This prevents the reentrant function from executing executeTransaction()
again.
But call
does not provide gas stipends like transfer
(2300 gas) and can consume all available gas, potentially leading to out-of-gas (OOG) failures if the recipient contract is complex.
Here is a proof of concept of the potential attack:
Attacker ask for an ETH transference from the owners of the multisig.
Attacker provides the address of a malicious contract to the owners.
Malicious contract has a receive()
function with an excessive gas consumption.
Owner One creates a new transaction with that address and the other owner approves it.
Owner Two (the actual victim) calls executeTransaction
, which performs the low level call.
The transaction revert but Owner Two spent excessive gas.
While this issue is categorized as Low severity, it can escalate in severity if the multisig owners lack experience. A skilled attacker can socially engineer the owners into repeatedly executing a gas-draining transaction, leading to significant ETH loss in gas fees.
Slither
Consider implementing a Gas Limit on .call
. Adding a reasonable gas cap ({value: txn.value, gas: 50_000}
) would prevent excessive gas consumption.
Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.