DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Invalid

Low level call on `MultiSig::executeTransaction`, which can lead to a potential gas exhaustion attack

Summary

The executeTransationfunction performs a low level call to complete the previously submitted transaction:

function executeTransaction(uint256 _txId) external onlyOwners {
require(_txId < transactions.length, "Invalid transaction ID");
Transaction storage txn = transactions[_txId];
require(!txn.executed, "Transaction already executed");
require(txn.approvedByOwner1 && txn.approvedByOwner2, "Not enough approvals");
txn.executed = true;
@> (bool success, ) = payable(txn.to).call{value: txn.value}("");
require(success, "Transaction failed");
emit TransactionExecuted(_txId, txn.to, txn.value);
}

Vulnerability Details

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.

Impact

Here is a proof of concept of the potential attack:

  1. Attacker ask for an ETH transference from the owners of the multisig.

  2. Attacker provides the address of a malicious contract to the owners.

  3. Malicious contract has a receive() function with an excessive gas consumption.

  4. Owner One creates a new transaction with that address and the other owner approves it.

  5. Owner Two (the actual victim) calls executeTransaction, which performs the low level call.

  6. 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.

Tools Used

Slither

Recommendations

Consider implementing a Gas Limit on .call. Adding a reasonable gas cap ({value: txn.value, gas: 50_000}) would prevent excessive gas consumption.

function executeTransaction(uint256 _txId) external onlyOwners {
require(_txId < transactions.length, "Invalid transaction ID");
Transaction storage txn = transactions[_txId];
require(!txn.executed, "Transaction already executed");
require(txn.approvedByOwner1 && txn.approvedByOwner2, "Not enough approvals");
txn.executed = true;
+ (bool success, ) = payable(txn.to).call{value: txn.value, gas: 50_000}("");
- (bool success, ) = payable(txn.to).call{value: txn.value}("");
require(success, "Transaction failed");
emit TransactionExecuted(_txId, txn.to, txn.value);
}
Updates

Appeal created

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Users mistake, only impacting themselves.

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.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.