Ethernaut(Lvl 2): Tiny Mistakes, Big Problems

Classic case of when developer mistakes cause big issues in a smart contract.

In the realm of smart contract development, even seemingly minor mistakes made by developers can have profound consequences. This phenomenon is exemplified in several classic cases where small errors in coding semantics led to significant issues within smart contracts, impacting their functionality and security. Let's delve into a few noteworthy examples to understand how such developer mistakes resulted in substantial challenges for smart contract implementations.

Examples

  1. The BatchOverflow Bug : This vulnerability was caused by a small semantic error in the way integer arithmetic was performed in certain smart contracts on the Ethereum blockchain. The error allowed attackers to manipulate the contract's code to mint an unlimited number of tokens by exploiting an integer overflow issue.

     function transferTokens(address _to, uint256 _value) public {
         balances[msg.sender] -= _value;  // Small semantic error here
         balances[_to] += _value;
     }
     //The error here is that the subtraction operation (-=) isn't
     // properly checked for potential underflow, leading to an integer 
     //overflow issue.
    
  2. The Parity Wallet Multisig Bug (2017): In this case, a small semantic error in the deployment process of Parity's multi-signature wallet smart contract led to the accidental triggering of a vulnerability, resulting in the locking up of over $150 million worth of Ether.

     var contract = web3.eth.contract(abi);
     contract.new({data: bytecode}, function(err, contract) {
         if (!err) {
             if (!contract.address) {
                 console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
             } else {
                 console.log("Contract mined! Address: " + contract.address);
             }
         }
     });
    

    In this deployment script, a semantic error in the conditional check led to the contract's deployment without proper initialization, resulting in the vulnerability.

  3. The Rubixi Bug: As mentioned earlier, this incident involved a semantic error where a developer changed the contract's name but forgot to update the constructor function accordingly. This oversight allowed adversaries to exploit the contract and gain control over its ethers.

     function DynamicPyramid() public {  // Semantic error here
         // constructor code
     }
    
     function Rubixi() public {
         // constructor code
     }
    

The Challenge:

The challenge requires you to claim ownership of the contract to complete the level.

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

import 'openzeppelin-contracts-06/math/SafeMath.sol';

contract Fallout {

  using SafeMath for uint256;
  mapping (address => uint) allocations;
  address payable public owner;


  /* constructor */
  function Fal1out() public payable {
    owner = msg.sender;
    allocations[owner] = msg.value;
  }

  modifier onlyOwner {
            require(
                msg.sender == owner,
                "caller is not the owner"
            );
            _;
        }

  function allocate() public payable {
    allocations[msg.sender] = allocations[msg.sender].add(msg.value);
  }

  function sendAllocation(address payable allocator) public {
    require(allocations[allocator] > 0);
    allocator.transfer(allocations[allocator]);
  }

  function collectAllocations() public onlyOwner {
    msg.sender.transfer(address(this).balance);
  }

  function allocatorBalance(address allocator) public view returns (uint) {
    return allocations[allocator];
  }
}

This basic level presents an opportunity to assert control over a contract by capitalizing on a developer's minor typographical error. Here's how it unfolds:

Upon closer inspection, you'll observe that the function name fallout() is mistakenly spelled as fal1out() .This mistake inadvertently transforms the constructor function into a publicly accessible function that can be invoked at any time.

// Simply call this with ether
function Fal1out() public payable { 
    owner = msg.sender; 
    allocations[owner] = msg.value; 
}

Executing this function with a nominal amount of ether assigns you as the contract owner. Consequently, querying contract.owner() in your console will now display your identity as the rightful owner of the contract!

Developer TidBits

Engage in test-driven development for your projects, a recommended approach to ensure robustness and reliability. Consider utilizing the Foundry framework, which provides convenient built-in testing capabilities for your next decentralized application (DApp). Additionally, pay close attention to compiler warnings and ensure you're utilizing the latest compiler version to leverage the latest features and enhancements. Enhance the security of your smart contracts by employing security analysis tools, readily available and proficient at detecting common human errors, often at no cost.

This article was written with inspiration from here