说明
前面NFT常见认证漏洞总结这篇文章讲解了NFT项目在认证过程中常见的漏洞,本篇文章则是以Akutar这个项目为例讲解一些匪夷所思的漏洞,你永远不知道业务方也写出来什么样的代码。所以,当我们在实际参与项目时,如果项目方的合约代码是开源的,最好先人为审计保证没有安全问题之后再参与。
目前合约的漏洞,导致目前合约中还被封锁了11539.5ETH,永远没有办法取出。
基本信息
Akutar NFT在线查看Akutar NFT
其中与漏洞有关的函数分别是:processRefunds()和claimProjectFunds()。
processRefunds(),用于处理用户退款claimProjectFunds(),用于项目方从合约中取出所有的资金
这个合约最基本的防止重入和禁止合约调用都没有校验,这也为后面的漏洞利用埋下了隐患。
转账校验漏洞
此漏洞对应的就是processRefunds()。这个函数中使用循环来给所有用户进行退款。源代码如下:
1 | function processRefunds() external { |
这个函数存在两个很严重的漏洞:
- 没有限制仅有owner可以调用,这意味着任何人都可以调用
- 没有限制合约调用,导致合约可以调用这个方法
这个函数的主要功能就是为所有的用户退款,转账中最为关键的代码是:
1 | for (uint256 i=_refundProgress; gasUsed < 5000000 && i < _bidIndex; i++) { |
因为没有对bidData.bidder校验是EOA账户。如果是一个部署了fallback()函数(fallback的原理参考发送和接受ETH方式汇总)的恶意合约,可以revert这个转账交易,这就会导致所有的用户的转账交易都会被revert。
以下就是一个简单的攻击示例程序。
逻辑漏洞
这个逻辑漏洞对应的函数是claimProjectFunds(),限制了仅owner调用,用于项目方取出所有的资金。具体的代码逻辑如下:
1 | function claimProjectFunds() external onlyOwner { |
如果需要成功取款,必须满足三个必备的条件:
require(block.timestamp > expiresAt, "Auction still in progress"),要求所有的拍卖进程完成后进行require(refundProgress >= totalBids, "Refunds not yet processed"),要求所有的用户完成退款require(akuNFTs.airdropProgress() >= totalBids, "Airdrop not complete");,要求空头必须全部完成
最关键的是第二个逻辑require(refundProgress >= totalBids, "Refunds not yet processed"),由于这个逻辑永远无法满足,所以导致合约中的钱用于无法取出。下面开始分析:
refundProgress
refundProgress是在processRefunds()由 _refundProgressß赋值的,表示已经处理过的退款的用户数量。
totalBids
在_bid中由uint256 _totalBids = totalBids + amount;,实际表示的是所有商品的数量
因为商品的数量是一定大于用户的数量的,这就导致refundProgress >= totalBids永远无法满足,导致require(refundProgress >= totalBids, "Refunds not yet processed")永远失败,最后无法提款成功
修正
refundProgress的比较对象应该是bidIndex,这里的bidIndex指的是投标账户的总数,在bid函数中,每当有一个账户投标,bitIndex就会加1,且不会重复。bidIndex的逻辑如下所示:
1 | function _bid(uint8 amount, uint256 value) internal { |
总结
漏洞的本质的原因是因为Akutar NFT的逻辑复杂,编写NFT的开发人员并没有按照常规的开发逻辑去实现,导致整个合约的逻辑复杂,变量命名不够规范,所以基本的安全防护也没有。变量命名不规范这也是导致最后取款操作失败的原因,这也从侧面反映了这个合约没有进行很好的代码测试,没有测试出claimProjectFunds()存在的问题。
区块链的透明性和不可篡改性一方面成就了区块链,但是也对开发人员的要求更高。作为我们个人用户,也需要综合分析判断项目方。