区块链安全常见的攻击分析——私有数据泄露 (Private Data Exposure)【7】

区块链安全常见的攻击分析------私有数据泄露 Private Data Exposure【7】

name: 私有数据泄露 (Private Data Exposure)

重点: 变量直接存储在链上,而链上的所有数据(无论是 public 还是 private)都可以被直接读取。因此,用户的密码缺乏安全性,容易被恶意行为者获取。利用vm.load() 或类似的链上存储读取方法,可以直接获取存储数据,从而轻松获取用户的 password。

如果不理解槽位slot可以先学习一下目录里1.5 知识点

1.1 漏洞分析

password直接存储在链上,所有存储在链上的数据(无论是 public 还是 private)都可以被读取。因此,用户的密码并不安全,容易被恶意行为者获取。

1.2 漏洞合约

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "forge-std/Test.sol";

/*
名称: 私有数据泄露 (Private Data Exposure)

描述:
Solidity 将合约中定义的变量存储在槽位(slot)中。每个槽位最多可以容纳 32 字节或 256 位。
由于所有存储在链上的数据(无论是 public 还是 private)都可以被读取,因此可以通过预测私有数据在 Vault 合约中的存储槽位,读取其中的私有数据。

如果在生产环境中使用 Vault 合约,恶意行为者可能会使用类似技术访问敏感信息,例如用户密码。

缓解措施:
避免在链上存储敏感数据。

参考:
https://quillaudits.medium.com/accessing-private-data-in-smart-contracts-quillaudits-fe847581ce6d
*/

contract Vault {
    // slot 0
    uint256 private password;

    constructor(uint256 _password) {
        password = _password;
        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);
    }
}

1.3 攻击分析

通过vm.load()直接获取链上数据

  • 可以获取user数组中user结构体内的password
  • 也可以直接读取 password 存储的槽位 0

结果

1.4 攻击合约

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "forge-std/Test.sol";
import "./Privatedata.sol";

contract ContractTest is Test {
    Vault PrivatedataContract;
    address Koko;
    address Aquarius;

    function setUp() public {
        PrivatedataContract = new Vault(211233);
        Koko = vm.addr(1);
        Aquarius = vm.addr(2);
        // vm.deal(address(Koko), 1 ether);
        // vm.deal(address(Aquarius), 1 ether);
    }

    function testPrivatedata() public {
        vm.prank(Koko);
        bytes32 leet = vm.load(
            address(PrivatedataContract),
            bytes32(uint256(0))
        );
        console.log(uint256(leet));

        // users in slot 1 - length of array
        // starting from slot hash(1) - array elements
        // slot where array element is stored = keccak256(slot)) + (index * elementSize)
        // where slot = 1 and elementSize = 2 (1 (uint) +  1 (bytes32))
        bytes32 user = vm.load(
            address(PrivatedataContract),
            PrivatedataContract.getArrayLocation(1, 1, 1)
        );
        console.log(uint256(user));
    }

    function testPasswordSlot() public {
        // 读取 password 存储的槽位 0
        bytes32 password = vm.load(
            address(PrivatedataContract),
            bytes32(uint256(0))
        );
        console.log("Password stored in slot 0:", uint256(password));
    }
}

1.5 知识点:槽位 slot

存储规则:
  1. 状态变量顺序
    • 状态变量按其定义顺序依次存储在存储槽(slot)中。
    • 每个槽的大小为 256 位(32 字节)。
    • 如果变量大小小于 256 位(例如 uint128bool),多个变量可能共享一个槽。
  2. 动态数组和映射
    • 动态数组和映射本身占用一个槽,存储的是其内容的起始位置的哈希值。
    • 数组元素和映射值存储在独立的哈希槽中。
  3. 结构体
    • 结构体的每个成员按顺序分配存储,类似于多个状态变量。
示例存储分析:

假设合约如下:

solidity 复制代码
contract Vault {
    uint256 private password; // slot 0
    struct User {
        uint256 id;          // slot 1 (in storage for arrays/mappings)
        bytes32 password;    // slot 2 (in storage for arrays/mappings)
    }
    User[] public users;      // slot 1 (base slot for dynamic array)
    mapping(uint256 => User) public idToUser; // slot 2 (base slot for mapping)

    constructor(uint256 _password) {
        password = _password;
        User memory user = User({id: 0, password: bytes32(_password)});
        users.push(user); // Stored starting from keccak256(slot 1)
        idToUser[0] = user; // Stored starting from keccak256(0 + slot 2)
    }
}

变量存储位置:
  1. password (slot 0) :
    • 存储在槽位 0,因为它是合约中定义的第一个变量,占用完整的 256 位。
  2. users 动态数组 (slot 1) :
    • 动态数组的起始槽存储在槽位 1。
    • 数组内容从 keccak256(slot 1) 开始的槽位存储。
  3. idToUser 映射 (slot 2) :
    • 映射的基础槽位是 2。
    • 映射中的每个键值对存储在 keccak256(键 + slot 2)

如何获取槽位数据:
  1. 通过 getStorageAt 读取存储数据: 使用 Solidity 或工具(如 Foundry 的 vm.load)读取指定槽位的数据。

  2. Foundry 示例:

    solidity 复制代码
    contract VaultTest is Test {
        Vault vault;
    
        function setUp() public {
            vault = new Vault(123456);
        }
    
        function testStorage() public {
            // 读取槽位 0 的存储数据
            bytes32 slot0 = vm.load(address(vault), bytes32(uint256(0)));
            console.log("Password (slot 0):", uint256(slot0));
    
            // 读取数组起始槽位 1 的哈希值
            bytes32 arraySlot1 = vm.load(address(vault), bytes32(uint256(1)));
            console.log("Array base slot 1:", arraySlot1);
    
            // 读取映射键 0 的哈希槽
            bytes32 mapSlot = keccak256(abi.encodePacked(uint256(0), uint256(2)));
            bytes32 userInMapping = vm.load(address(vault), mapSlot);
            console.log("Mapping data:", userInMapping);
        }
    }

存储槽位计算:
  1. 单变量
    • 按声明顺序从槽位 0 开始。
  2. 动态数组
    • 数组本身的基础槽位存储数组长度。
    • 元素从 keccak256(基础槽位) 开始依次存储。
  3. 映射
    • 每个键值对的存储槽 = keccak256(abi.encodePacked(键, 映射基础槽位))
相关推荐
用户962377954482 天前
VulnHub DC-3 靶机渗透测试笔记
安全
叶落阁主3 天前
Tailscale 完全指南:从入门到私有 DERP 部署
运维·安全·远程工作
用户962377954485 天前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机5 天前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机5 天前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954485 天前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star5 天前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户962377954485 天前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
cipher7 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
kida_yuan8 天前
【以太来袭】4. Geth 原理与解析
区块链