The Association NFT 合约漏洞分析

漏洞描述

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

p1
  • 根据方法id对交易进行筛选

p2
  • 定位到早期发起的一些交易记录。

  • 现在我们来看这笔交易记录铸造tokenid为2489的交易记录

p3
p4

通过对交易输入的数据解析可以知道,红框圈住的输入数据,是白名单发起者的地址。

p5
  • 现在我们再来看铸造tokenid为9657的这笔交易记录

p6
p7

这里可以看到输入数据和上面的交易一模一样,但是发起者地址和输入数据的地址不符,说明这笔交易属于攻击者利用合约漏洞,使用正常用户的签名数据生成的。

另外对铸造方法分析,铸造NFT数量的参数number_of_items_requested不属于参数数据的一部分,那么修改这个参数值,可以实现一次性铸造多个。

预防措施

  • 验证签名方法需要加入msg.sender校验

  • 为了更节省存储gas费用,可以采用MerkleProof算法实现白名单功能

https://github.com/MetaplasiaTeam