Offensive Vyper - Challenge 0 - Password Vault
0) Intro
This article is the first write-up of the Offensive Vyper challenges created by @jtriley_eth.
You can find instructions on how to get started here.
1) Challenge
test/secrets/dont-peek.js
.2) Code Review
PasswordVault.vy
contract (source code):
|
|
Contract main functions:
-
set_new_password
is used to set a new password. It accepts the old password and the new password to set. It checks if theold_password_hash
parameter equals the initial password (set during contract creation) and if themsg.sender
equals the owner. If one of these two conditions is false, the function fails. So, the owner can change the password even without knowing the old one, and anyone who knows the current password can change it with a new one. -
withdraw
allows to withdraw the contract balance and sends it to themsg.sender
. To withdraw the entire amount is required to provide the password hash. There are two checks in place: one checks if the password provided is equal to thepassword_hash
, and the other if themsg.sender
is the contract owner.
The @dev
comment of both functions says:
Throws when password is invalid and the caller is not the owner.
These conditions are evaluated in a logical disjunction (OR), so only one of the two needs to be true to execute the functions.
Therefore, since we are interested in stealing all the ETH, if we find a way to discover the value of the password_hash
, we can withdraw the entire amount and solve the challenge (the condition self.password_hash == password_hash
will be true).
password_hash
value stored?password_hash
is a contract variable and is initialized during the contract creation (line 14
). This variable is not public, so it’s not accessible from the external. However, reading the documentation:
Storage variables are located within a smart contract at specific storage slots. By default, the compiler allocates the first variable to be stored at slot 0; subsequent variables are stored in order after that.
The password_hash
is the first contract variable (line 8
), so it’s stored in the slot 0
.
Using the getStorageAt
from the ethers
library we can access the contract storage.
3) Solution
The solution consists of two steps:
- retrieve the password hash from the contract storage
- call the withdraw function providing this password
password-vault.challenge.js
:
|
|
PasswordVaultExploit.vy
:
|
|
You can find the complete code here and here.