原文标题:《 Solidity 极简入门 ERC721 专题:4. BAYC 合约严重漏洞 》
原文来源:0xAA
我最近在重新学 solidity,巩固一下细节,也写一个「Solidity 极简入门」,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。
所有代码开源在 github:github.com/AmazingAng/WTFSolidity
不知不觉我已经完成了 Solidity 极简教程的前 13 讲(基础),内容包括:Helloworld.sol,变量类型,存储位置,函数,控制流,构造函数,修饰器,事件,继承,抽象合约,接口,库,异常。在进阶内容之前,我决定做一个 ERC721 的专题,把之前的内容综合运用,帮助大家更好的复习基础知识,并且更深刻的理解 ERC721 合约。希望在学习完这个专题之后,每个人都能发行自己的 NFT。

TL;DR 太长不看

– 在我查看 BAYC 合约的时候我发现了两个严重漏洞,其中一个可能会导致 BAYC 超发,超过设定的供应量上限 10,000 枚。
– 超发漏洞是由合约中用于项目方预留 BAYC NFT 的 reserveApes 函数没有检查是否超发,owner 地址可以随时铸造,不受供应量上限的限制。
– 两种情况下漏洞会被触发:项目方做恶(几乎不可能)和黑客盗取 owner 私钥(有可能)。
– 受漏洞影响的包括 BAYC 以及复用其代码的其他 NFT 项目方。
– 最简单的解决办法就是 BAYC 项目方放弃 ownership。
– 发现这两个漏洞并不难,我肯定不是第一个发现的(见这篇去年 10 月的博客),但是这些漏洞并没有引起足够的重视。鉴于现在 BAYC 市值超过 40 亿美元,希望能引起项目方的重视。
声明:本文只是技术探讨,没有 FUD BAYC。我很喜欢 BAYC,希望漏洞永远不被触发。

BAYC

这是 WTF Solidity 极简入门 ERC721 专题的第 4 讲,我们将介绍 BAYC 合约及其漏洞。
无聊猿 BAYC(Bored Ape Yacht Club)是最顶级的 NFT 项目,2021 年 4 月底以 0.08 ETH 的价格发售,一共 10000 枚。目前地板价约 130 ETH,涨了 1000 多倍,市值超过$40 亿美元。

BAYC 合约

BAYC 的合约在etherscan上开源,所有人均可查看,今天我们就来仔细学习下它。

BAYC 的合约在 etherscan 上开源

BAYC 的合约是 flat 形式的,一个文件把所有父合约都包括了,一共 2021 行。但其实前面 1900 行都是父合约的内容,只有最后的 100 行是主合约,也是我们将重点学习的。

继承

BAYC 合约的 solidity 版本是 0.7.0,继承了两个合约,ERC721 和 Ownable。ERC721 合约详见我的 ERC721 专题1,2, 3讲,Ownable 合约最重要的是实现了 onlyOwner 修饰器,使得特定函数只能由合约的 owner 地址调用,详见Solidity 极简教程第 8 讲:构造函数和修饰器。

状态变量

由于合约的 solidity 版本是 0.7.0,尚未内置 SafeMath,因此它用 using-for 声明了对 uint256 类型使用 SafeMath 库,防止溢出错误。
合约里的状态变量一共 8 个:
BAYC_PROVENANCE:把所有 NFT 图片的 hash 按一定顺序合并到一起。这个其实是个很巧妙的设计,可证明的把图片的内容和顺序确定下来,又不用在开图之前暴露图片信息,还可以解决 NFT 项目方偷把稀有度高的换给自己。Bug 1。但是 BAYC 项目方犯了个错误,他们加了一个 setProvenanceHash 函数,可以让 owner 无数次更改 BAYC_PROVENANCE,与它的初衷相违背,并且留有做恶可能,比如偷换图片并更改 BAYC_PROVENANCE。正确的改法是把 BAYC_PROVENANCE 设成 immutable,在 constructor 里初始化后就不能再被修改:

startingIndexBlock:开始发售的区块高度。

startingIndex:看起来像是个多余的变量?

apePrice:BAYC 发售价格,0.08 ETH。

maxApePurchase:每次 mint 的数量限制,最多一次铸造 20 只。一笔交易 mint 多个 NFT,比每次只能 mint 一个要节省 gas。

MAX_APES:NFT 的最大供给,10,000 个。

Bug 2saleIsActive:是否开始公售。

REVEAL_TIMESTAMP:开图的区块高度。

函数

BAYC 主合约定义了 10 个函数:
constuctor:构造函数,初始化代币名称,代号,MAX_APES,REVEAL_TIMESTAMP。

withdraw:取出销售 BAYC 得到的 ETH。

  reserveApes:**重要!**项目方给自己预留 BAYC,每次调用给自己 mint30 个。只有合约的 owner 可以调用。Bug 2

setRevealTimestamp:更改 REVEAL_TIMESTAMP。

setProvenanceHash:更改 BAYC_PROVENANCE。Bug 1

setBaseURI:设定 BAYC 的 BaseURI(ERC721 合约中的状态变量)。

flipSaleState:打开/暂停公售。

mintApe:**重要!**买家支付 ETH 并铸造 BAYC。调用 ERC721 的_safeMint 函数。条件:

saleIsActive 为 true,即公售开始。

numberOfTokens <= maxApePurchase,每次只能 mint20 个。

mint 后的流通量小于总供给(10,000 枚)。

支付的 ETH 要大于 0.08 * mint 数量。

setStartingIndex:看起来像是个多余的函数?

emergencySetStartingIndexBlock:在紧急情况下,开始公售。将 startingIndexBlock 设为当前区块高度。

BAYC 合约的严重漏洞

BAYC 合约一共有两个严重漏洞,一个可能导致图片被调换,另一个更严重,会使 BAYC 超发,超过设定的 10,000 枚。

Bug 1:图片被换风险

用于证明图片没有被篡改、调换的 BAYC_PROVENANCE 变量可以被合约 owner 随意更改。攻击者(项目方或盗取私钥黑客)可以调换图片,利用 setBaseURI 函数设定新的 metadata 存放网址,然后算出新图片的 BAYC_PROVENANCE 并更新。这样,人们没法通过 BAYC_PROVENANCE 验证是否被篡改。
正确改写方法:将 BAYC_PROVENANCE 变量设定为 immutable,并在构造函数中初始化,之后不能被更改。

Bug 2:增发风险

用于给项目方预留 BAYC 的 reserveApes 函数没有像公售 mintApe 函数一样检查供应量,因此,即使最大供给量 MAX_APES 被设定为 10,000,合约的 owner(项目方或盗取私钥黑客)仍可以调用 reserveApes 铸造新的 BAYC,使得 BAYC#10000,BAYC#10001 等等被 mint 出来,没有上限。
正确改写办法:在 reserveApes 函数中加入最大供应量检查。

在什么情况下会被攻击

前面讲的 BAYC 合约中两个严重漏洞,都需要合约 owner 去执行。一种情况就是项目方做恶,去攻击漏洞。但很显然 BAYC 非常成功,项目方不做恶会获得更大的收益,完全没有做恶的动机,因此这种情况几乎不会发生。第二种情况就是 owner 钱包私钥被黑客盗取,黑客做恶调用 reserveApes 超发 BAYC 出售。鉴于 BAYC 官方 ins 前几天刚被盗,这种情况发生不是绝无可能。目前 BAYC 合约的 owner 地址为:0xaba7161a7fb69c88e16ed9f455ce62b791ee4d03
让我们默默祈祷。

如何解决?

其实这两个漏洞的方法很简单,就是 BAYC 项目方调用 renounceOwnership 放弃合约 owner。因为这两个漏洞的相关函数都需要 owner 去调用,放弃 owner 权限之后将没人可以攻击漏洞。
其他的解决办法大家也可以讨论。
彩蛋:项目方预留的 BAYC 给了谁?
BAYC 项目方总共只调用过一次 reserveApes 函数,一共给自己预留了 30 个 BAYC,#0 到 #29,并发送给了 30 个地址,大家可以挖掘一下他们都是谁(按 tokenId 排序):
1、emperortomatoketchup.eth
2、20x46efbaedc92067e6d60e84ed6395099723252496
3、0xc5c7b46843014b1591e9af24de797156cde67f08
4、garga.eth
5、0xwave.eth
6、0xed7c0117d7d35850d71e2a3f390972406f8d5d46
7、0x898c4607809945b49d65ea51580101798931b241
8、rdlwriter.eth
9、bmouse.eth
10、cryptorobo.eth
11、puzzle.eth
12、0xddb338bc464fde06b382d28f37e57cb3727c2e1b
13、chrishol.eth
14、keltron.eth
15、yourstruly.eth
16、0x7225fd5032038bcf49c36deba23a16262521ede9
17、dmnets.eth
18、nabito.eth
19、tropofarmer.eth
20、0xc3fbc3f485f0d9b0bd21b13a4aaa8340160156cb
21、nftfox.eth
22、d34thst4lker.eth
23、thephotographer.eth
24、0xed2d1254e79835bf5911aa8946e23bf508477da4
25、0xf9cb2a5944654b0c9b07d2311715728e30d3ee82
26、billymcsmithers.eth
27、web3ireland.eth
28、yourstruly.eth
29、0x822a16309a9ee40f15e196898f11a010ecb1c963
30、brulik.eth

总结

– 这是 Solidity 极简教学 ERC721 专题的第 4 讲,我们介绍 BAYC 合约及其漏洞。
– 在我查看 BAYC 合约的时候我发现了两个严重漏洞,其中一个可能会导致 BAYC 超发,超过设定的供应量上限 10,000 枚。
– 超发漏洞是由合约中用于项目方预留 BAYC NFT 的 reserveApes 函数没有检查是否超发,owner 地址可以随时铸造,不受供应量上限的限制。
– 两种情况下漏洞会被触发:项目方做恶(几乎不可能)和黑客盗取 owner 私钥(有可能)。
– 受漏洞影响的包括 BAYC 以及复用其代码的其他 NFT 项目方。
– 最简单的解决办法就是 BAYC 项目方放弃 ownership。
– NFT 项目方需要更严格的审计合约!
声明:本文只是技术探讨,没有 FUD BAYC。我很喜欢 BAYC,希望漏洞永远不被触发。