原文标题:《 Solidity 极简入门 ERC721 专题:4. BAYC 合约严重漏洞 》
原文来源:0xAA
我最近在重新学 solidity,巩固一下细节,也写一个「Solidity 极简入门」,供小白们使用(编程大佬可以另找教程),每周更新 1-3 讲。
所有代码开源在 github:github.com/AmazingAng/WTFSolidity
不知不觉我已经完成了 Solidity 极简教程的前 13 讲(基础),内容包括:Helloworld.sol,变量类型,存储位置,函数,控制流,构造函数,修饰器,事件,继承,抽象合约,接口,库,异常。在进阶内容之前,我决定做一个 ERC721 的专题,把之前的内容综合运用,帮助大家更好的复习基础知识,并且更深刻的理解 ERC721 合约。希望在学习完这个专题之后,每个人都能发行自己的 NFT。
- 在我查看 BAYC 合约的时候我发现了两个严重漏洞,其中一个可能会导致 BAYC 超发,超过设定的供应量上限 10,000 枚。
- 超发漏洞是由合约中用于项目方预留 BAYC NFT 的 reserveApes 函数没有检查是否超发,owner 地址可以随时铸造,不受供应量上限的限制。
- 两种情况下漏洞会被触发:项目方做恶(几乎不可能)和黑客盗取 owner 私钥(有可能)。
- 受漏洞影响的包括 BAYC 以及复用其代码的其他 NFT 项目方。
- 最简单的解决办法就是 BAYC 项目方放弃 ownership。
- 发现这两个漏洞并不难,我肯定不是第一个发现的(见这篇去年 10 月的博客),但是这些漏洞并没有引起足够的重视。鉴于现在 BAYC 市值超过 40 亿美元,希望能引起项目方的重视。
声明:本文只是技术探讨,没有 FUD BAYC。我很喜欢 BAYC,希望漏洞永远不被触发。
这是 WTF Solidity 极简入门 ERC721 专题的第 4 讲,我们将介绍 BAYC 合约及其漏洞。
无聊猿 BAYC(Bored Ape Yacht Club)是最顶级的 NFT 项目,2021 年 4 月底以 0.08 ETH 的价格发售,一共 10000 枚。目前地板价约 130 ETH,涨了 1000 多倍,市值超过$40 亿美元。
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 超发,超过设定的 10,000 枚。
用于证明图片没有被篡改、调换的 BAYC_PROVENANCE 变量可以被合约 owner 随意更改。攻击者(项目方或盗取私钥黑客)可以调换图片,利用 setBaseURI 函数设定新的 metadata 存放网址,然后算出新图片的 BAYC_PROVENANCE 并更新。这样,人们没法通过 BAYC_PROVENANCE 验证是否被篡改。
正确改写方法:将 BAYC_PROVENANCE 变量设定为 immutable,并在构造函数中初始化,之后不能被更改。
用于给项目方预留 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,希望漏洞永远不被触发。
原文链接
欢迎加入律动 BlockBeats 官方社群:
Telegram 订阅群:https://t.me/theblockbeats
Telegram 交流群:https://t.me/BlockBeats_App
Twitter 官方账号:https://twitter.com/BlockBeatsAsia