当前位置: 首页 > article >正文

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

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

    • 1.1 漏洞分析
    • 1.2 漏洞合约
    • 1.3 攻击分析
      • 结果
    • 1.4 攻击合约
    • 1.5 知识点:槽位 slot
        • 存储规则:
        • 示例存储分析:
        • 变量存储位置:
        • 如何获取槽位数据:
        • 存储槽位计算:

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

重点: 变量直接存储在链上,而链上的所有数据(无论是 public 还是 private)都可以被直接读取。因此,用户的密码缺乏安全性,容易被恶意行为者获取。利用vm.load() 或类似的链上存储读取方法,可以直接获取存储数据,从而轻松获取用户的 password。
如果不理解槽位slot可以先学习一下目录里1.5 知识点

1.1 漏洞分析

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

1.2 漏洞合约

// 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 攻击合约

// 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. 结构体
    • 结构体的每个成员按顺序分配存储,类似于多个状态变量。
示例存储分析:

假设合约如下:

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 示例:

    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(键, 映射基础槽位))

http://www.kler.cn/a/458886.html

相关文章:

  • CSS系列(42)-- Backdrop Filter详解
  • 矩阵的因子分解1-奇异值分解
  • uniapp3 手写签名组件(vue3 语法)封装与应用
  • 使用three.js 实现vr全景图展示,复制即可用
  • [python SQLAlchemy数据库操作入门]-16.CTE:简化你的复杂查询
  • win32汇编环境下,对话框程序中生成listview列表控件,点击标题栏自动排序的示例
  • Javascript数据结构——图Graph
  • C++ 设计模式:代理模式(Proxy Pattern)
  • 力扣第116题:填充每个节点的下一个右侧节点指针 - C语言解法
  • 代码随想录day21 | leetcode 77.组合 77.组合 加剪枝操作 216.组合总和III
  • [图形渲染]【Unity Shader】【游戏开发】 Shader数学基础17-法线变换基础与应用
  • Java:192 基于SSM框架的失物招领信息管理系统
  • debian12安装docker
  • Linux的进程替换以及基础IO
  • 初学stm32 --- 高级定时器PWM输入模式
  • Github 2024-12-26 Go开源项目日报 Top10
  • (二)当人工智能是一个函数时,怎么去训练它?
  • 【机器学习】机器学习的基本分类-半监督学习-Ladder Networks
  • 【day20】集合深入探讨
  • Optional类:避免NullPointerException
  • Go语言的字符串处理
  • 每天40分玩转Django:Django Channels
  • react-native键盘遮盖底部输入框问题修复
  • 对于多个网站的爬虫管理和配置支持
  • 前端处理跨域的几种方式
  • AI 加持下的智能家居行业:变革、挑战与机遇