NBA发行的NFT项目The Association NFT,合约出现了白名单用户的签名数据被攻击者利用,可以无限制铸造NFT的重大漏洞,现在让我们看看这个合约的漏洞是怎么产生的.
NBA这个发行的NFT项目有两个合约地址,一个是NFT本身的合约The_Association_Token,另一个是售卖合约The_Association_Sales,问题的根源出现在这个售卖合约上面。
售卖合约有个mint_approved方法,通过检验用户传送过来的签名数据,然后去调用The_Association_Token合约铸造NFT。
function mint_approved(
vData memory info,
uint256 number_of_items_requested,
uint16 _batchNumber
) external {
require(batchNumber == _batchNumber, "!batch");
address from = msg.sender;
// info是包含用户签名的数据,通过调用verify方法去验证签名数据是否正确
require(verify(info), "Unauthorised access secret");
_discountedClaimedPerWallet[msg.sender] += 1;
require(
_discountedClaimedPerWallet[msg.sender] <= 1,
"Number exceeds max discounted per address"
);
presold[from] = 1;
// 如果上面verify方法验证通过,这里调用会NFT合约铸造NFT
_mintCards(number_of_items_requested, from);
emit batchWhitelistMint(_batchNumber, msg.sender);
}
再看看verify方法它是如何检验签名数据的
function verify(vData memory info) public view returns (bool) {
require(info.from != address(0), "INVALID_SIGNER");
bytes memory cat =
abi.encode(
info.from,
info.start,
info.end,
info.eth_price,
info.dust_price,
info.max_mint,
info.mint_free
);
// mint数据的hash值
bytes32 hash = keccak256(cat);
require(info.signature.length == 65, "Invalid signature length");
bytes32 sigR;
bytes32 sigS;
uint8 sigV;
bytes memory signature = info.signature;
// 通过签名获取到R,S,V
assembly {
sigR := mload(add(signature, 0x20))
sigS := mload(add(signature, 0x40))
sigV := byte(0, mload(add(signature, 0x60)))
}
bytes32 data =
keccak256(
abi.encodePacked("\\x19Ethereum Signed Message:\
32", hash)
);
// 通过mint数据的hash值,签名R,S,V获取到签名的所属用户地址
address recovered = ecrecover(data, sigV, sigR, sigS);
// 判断地址是合约配置的
return signer == recovered;
}
从上面代码可以看到,这个签名校验方法是没有对发送者msg.sender进行校验的,只对签名所属者和合约配置白名单地址进匹对检验,那么攻击者完全可以使用正常白名单的用户签名数据,绕过这个身份验证方法去铸造NFT。
现在我们在ethscan上面看看哪些铸造NFT交易是攻击者发起的。
首先我们在ethscan上面查看售卖合约The_Association_Sales
有漏洞的铸造合约方法的id是
0x8df9da46
根据方法id对交易进行筛选
定位到早期发起的一些交易记录。
现在我们来看这笔交易记录铸造tokenid为2489的交易记录
通过对交易输入的数据解析可以知道,红框圈住的输入数据,是白名单发起者的地址。
现在我们再来看铸造tokenid为9657的这笔交易记录
这里可以看到输入数据和上面的交易一模一样,但是发起者地址和输入数据的地址不符,说明这笔交易属于攻击者利用合约漏洞,使用正常用户的签名数据生成的。
另外对铸造方法分析,铸造NFT数量的参数number_of_items_requested不属于参数数据的一部分,那么修改这个参数值,可以实现一次性铸造多个。
验证签名方法需要加入
msg.sender校验为了更节省存储gas费用,可以采用MerkleProof算法实现白名单功能
