
WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
WTF Academy: wtf.academy



WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
WTF Academy: wtf.academy
Share Dialog
Share Dialog

Subscribe to 0xAA

Subscribe to 0xAA
>100 subscribers
>100 subscribers
我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
WTF技术社群discord,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): 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的合约是flat形式的,一个文件把所有父合约都包括了,一共2021行。但其实前面1900行都是父合约的内容,只有最后的100行是主合约,也是我们将重点学习的。
pragma solidity ^0.7.0;
contract BoredApeYachtClub is ERC721, Ownable {
BAYC合约的solidity版本是0.7.0,继承了两个合约,ERC721和Ownable。ERC721合约详见我的ERC721专题1,2, 3讲,Ownable合约最重要的是实现了onlyOwner修饰器,使得特定函数只能由合约的owner地址调用,详见Solidity极简教程第8讲:构造函数和修饰器。
using SafeMath for uint256;
string public BAYC_PROVENANCE = "";
uint256 public startingIndexBlock;
uint256 public startingIndex;
uint256 public constant apePrice = 80000000000000000; //0.08 ETH
uint public constant maxApePurchase = 20;
uint256 public MAX_APES;
bool public saleIsActive = false;
uint256 public REVEAL_TIMESTAMP;
由于合约的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里初始化后就不能再被修改:
string public immutable BAYC_PROVENANCE = "";
startingIndexBlock:开始发售的区块高度。
startingIndex:看起来像是个多余的变量?
apePrice:BAYC发售价格,0.08 ETH。
maxApePurchase:每次mint的数量限制,最多一次铸造20只。一笔交易mint多个NFT,比每次只能mint一个要节省gas。
MAX_APES:NFT的最大供给,10,000个。Bug 2
saleIsActive:是否开始公售。
REVEAL_TIMESTAMP:开图的区块高度。
BAYC主合约定义了10个函数:
constuctor:构造函数,初始化代币名称,代号,MAX_APES,REVEAL_TIMESTAMP。
constructor(string memory name, string memory symbol, uint256 maxNftSupply, uint256 saleStart) ERC721(name, symbol) {
MAX_APES = maxNftSupply;
REVEAL_TIMESTAMP = saleStart + (86400 * 9);
}
withdraw:取出销售BAYC得到的ETH。
function withdraw() public onlyOwner {
uint balance = address(this).balance;
msg.sender.transfer(balance);
}
reserveApes:**重要!**项目方给自己预留BAYC,每次调用给自己mint30个。只有合约的owner可以调用。Bug 2
function reserveApes() public onlyOwner {
uint supply = totalSupply();
uint i;
for (i = 0; i < 30; i++) {
_safeMint(msg.sender, supply + i);
}
}
setRevealTimestamp:更改REVEAL_TIMESTAMP。
function setRevealTimestamp(uint256 revealTimeStamp) public onlyOwner {
REVEAL_TIMESTAMP = revealTimeStamp;
}
setProvenanceHash:更改BAYC_PROVENANCE。Bug 1
function setProvenanceHash(string memory provenanceHash) public onlyOwner {
BAYC_PROVENANCE = provenanceHash;
}
setBaseURI:设定BAYC的BaseURI(ERC721合约中的状态变量)。
function setBaseURI(string memory baseURI) public onlyOwner {
_setBaseURI(baseURI);
}
flipSaleState:打开/暂停公售。
function flipSaleState() public onlyOwner {
saleIsActive = !saleIsActive;
}
mintApe:**重要!**买家支付ETH并铸造BAYC。调用ERC721的_safeMint函数。条件:
saleIsActive为true,即公售开始。
numberOfTokens <= maxApePurchase,每次只能mint20个。
mint后的流通量小于总供给(10,000枚)。
支付的ETH要大于0.08 * mint数量。
function mintApe(uint numberOfTokens) public payable {
require(saleIsActive, "Sale must be active to mint Ape");
require(numberOfTokens <= maxApePurchase, "Can only mint 20 tokens at a time");
require(totalSupply().add(numberOfTokens) <= MAX_APES, "Purchase would exceed max supply of Apes");
require(apePrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
for(uint i = 0; i < numberOfTokens; i++) {
uint mintIndex = totalSupply();
if (totalSupply() < MAX_APES) {
_safeMint(msg.sender, mintIndex);
}
}
// If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
// the end of pre-sale, set the starting index block
if (startingIndexBlock == 0 && (totalSupply() == MAX_APES || block.timestamp >= REVEAL_TIMESTAMP)) {
startingIndexBlock = block.number;
}
}
setStartingIndex:看起来像是个多余的函数?
function setStartingIndex() public {
require(startingIndex == 0, "Starting index is already set");
require(startingIndexBlock != 0, "Starting index block must be set");
startingIndex = uint(blockhash(startingIndexBlock)) % MAX_APES;
// Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
if (block.number.sub(startingIndexBlock) > 255) {
startingIndex = uint(blockhash(block.number - 1)) % MAX_APES;
}
// Prevent default sequence
if (startingIndex == 0) {
startingIndex = startingIndex.add(1);
}
}
emergencySetStartingIndexBlock:在紧急情况下,开始公售。将startingIndexBlock设为当前区块高度。
function emergencySetStartingIndexBlock() public onlyOwner {
require(startingIndex == 0, "Starting index is already set");
startingIndexBlock = block.number;
}
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函数中加入最大供应量检查:
function reserveApes() public onlyOwner {
require(totalSupply().add(30) <= MAX_APES, "Mint would exceed max supply of Apes");
uint supply = totalSupply();
uint i;
for (i = 0; i < 30; i++) {
_safeMint(msg.sender, supply + i);
}
}
前面讲的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排序):
这是Solidity极简教学ERC721专题的第4讲,我们介绍BAYC合约及其漏洞。
在我查看BAYC合约的时候我发现了两个严重漏洞,其中一个可能会导致BAYC超发,超过设定的供应量上限10,000枚。
超发漏洞是由合约中用于项目方预留BAYC NFT的reserveApes函数没有检查是否超发,owner地址可以随时铸造,不受供应量上限的限制。
两种情况下漏洞会被触发:项目方做恶(几乎不可能)和黑客盗取owner私钥(有可能)。
受漏洞影响的包括BAYC以及复用其代码的其他NFT项目方。
最简单的解决办法就是BAYC项目方放弃ownership。
NFT项目方需要更严格的审计合约!
声明:本文只是技术探讨,没有FUD BAYC。我很喜欢BAYC,希望漏洞永远不被触发。
我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
WTF技术社群discord,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): 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的合约是flat形式的,一个文件把所有父合约都包括了,一共2021行。但其实前面1900行都是父合约的内容,只有最后的100行是主合约,也是我们将重点学习的。
pragma solidity ^0.7.0;
contract BoredApeYachtClub is ERC721, Ownable {
BAYC合约的solidity版本是0.7.0,继承了两个合约,ERC721和Ownable。ERC721合约详见我的ERC721专题1,2, 3讲,Ownable合约最重要的是实现了onlyOwner修饰器,使得特定函数只能由合约的owner地址调用,详见Solidity极简教程第8讲:构造函数和修饰器。
using SafeMath for uint256;
string public BAYC_PROVENANCE = "";
uint256 public startingIndexBlock;
uint256 public startingIndex;
uint256 public constant apePrice = 80000000000000000; //0.08 ETH
uint public constant maxApePurchase = 20;
uint256 public MAX_APES;
bool public saleIsActive = false;
uint256 public REVEAL_TIMESTAMP;
由于合约的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里初始化后就不能再被修改:
string public immutable BAYC_PROVENANCE = "";
startingIndexBlock:开始发售的区块高度。
startingIndex:看起来像是个多余的变量?
apePrice:BAYC发售价格,0.08 ETH。
maxApePurchase:每次mint的数量限制,最多一次铸造20只。一笔交易mint多个NFT,比每次只能mint一个要节省gas。
MAX_APES:NFT的最大供给,10,000个。Bug 2
saleIsActive:是否开始公售。
REVEAL_TIMESTAMP:开图的区块高度。
BAYC主合约定义了10个函数:
constuctor:构造函数,初始化代币名称,代号,MAX_APES,REVEAL_TIMESTAMP。
constructor(string memory name, string memory symbol, uint256 maxNftSupply, uint256 saleStart) ERC721(name, symbol) {
MAX_APES = maxNftSupply;
REVEAL_TIMESTAMP = saleStart + (86400 * 9);
}
withdraw:取出销售BAYC得到的ETH。
function withdraw() public onlyOwner {
uint balance = address(this).balance;
msg.sender.transfer(balance);
}
reserveApes:**重要!**项目方给自己预留BAYC,每次调用给自己mint30个。只有合约的owner可以调用。Bug 2
function reserveApes() public onlyOwner {
uint supply = totalSupply();
uint i;
for (i = 0; i < 30; i++) {
_safeMint(msg.sender, supply + i);
}
}
setRevealTimestamp:更改REVEAL_TIMESTAMP。
function setRevealTimestamp(uint256 revealTimeStamp) public onlyOwner {
REVEAL_TIMESTAMP = revealTimeStamp;
}
setProvenanceHash:更改BAYC_PROVENANCE。Bug 1
function setProvenanceHash(string memory provenanceHash) public onlyOwner {
BAYC_PROVENANCE = provenanceHash;
}
setBaseURI:设定BAYC的BaseURI(ERC721合约中的状态变量)。
function setBaseURI(string memory baseURI) public onlyOwner {
_setBaseURI(baseURI);
}
flipSaleState:打开/暂停公售。
function flipSaleState() public onlyOwner {
saleIsActive = !saleIsActive;
}
mintApe:**重要!**买家支付ETH并铸造BAYC。调用ERC721的_safeMint函数。条件:
saleIsActive为true,即公售开始。
numberOfTokens <= maxApePurchase,每次只能mint20个。
mint后的流通量小于总供给(10,000枚)。
支付的ETH要大于0.08 * mint数量。
function mintApe(uint numberOfTokens) public payable {
require(saleIsActive, "Sale must be active to mint Ape");
require(numberOfTokens <= maxApePurchase, "Can only mint 20 tokens at a time");
require(totalSupply().add(numberOfTokens) <= MAX_APES, "Purchase would exceed max supply of Apes");
require(apePrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
for(uint i = 0; i < numberOfTokens; i++) {
uint mintIndex = totalSupply();
if (totalSupply() < MAX_APES) {
_safeMint(msg.sender, mintIndex);
}
}
// If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
// the end of pre-sale, set the starting index block
if (startingIndexBlock == 0 && (totalSupply() == MAX_APES || block.timestamp >= REVEAL_TIMESTAMP)) {
startingIndexBlock = block.number;
}
}
setStartingIndex:看起来像是个多余的函数?
function setStartingIndex() public {
require(startingIndex == 0, "Starting index is already set");
require(startingIndexBlock != 0, "Starting index block must be set");
startingIndex = uint(blockhash(startingIndexBlock)) % MAX_APES;
// Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
if (block.number.sub(startingIndexBlock) > 255) {
startingIndex = uint(blockhash(block.number - 1)) % MAX_APES;
}
// Prevent default sequence
if (startingIndex == 0) {
startingIndex = startingIndex.add(1);
}
}
emergencySetStartingIndexBlock:在紧急情况下,开始公售。将startingIndexBlock设为当前区块高度。
function emergencySetStartingIndexBlock() public onlyOwner {
require(startingIndex == 0, "Starting index is already set");
startingIndexBlock = block.number;
}
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函数中加入最大供应量检查:
function reserveApes() public onlyOwner {
require(totalSupply().add(30) <= MAX_APES, "Mint would exceed max supply of Apes");
uint supply = totalSupply();
uint i;
for (i = 0; i < 30; i++) {
_safeMint(msg.sender, supply + i);
}
}
前面讲的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排序):
这是Solidity极简教学ERC721专题的第4讲,我们介绍BAYC合约及其漏洞。
在我查看BAYC合约的时候我发现了两个严重漏洞,其中一个可能会导致BAYC超发,超过设定的供应量上限10,000枚。
超发漏洞是由合约中用于项目方预留BAYC NFT的reserveApes函数没有检查是否超发,owner地址可以随时铸造,不受供应量上限的限制。
两种情况下漏洞会被触发:项目方做恶(几乎不可能)和黑客盗取owner私钥(有可能)。
受漏洞影响的包括BAYC以及复用其代码的其他NFT项目方。
最简单的解决办法就是BAYC项目方放弃ownership。
NFT项目方需要更严格的审计合约!
声明:本文只是技术探讨,没有FUD BAYC。我很喜欢BAYC,希望漏洞永远不被触发。
No activity yet