// inside head tag

Denial of Service (DoS) Attacks in Smart Contracts

Security

January 14, 2025

Introduction

Smart contracts are self-executing programs designed to enforce agreements without intermediaries. Their immutable nature makes security vulnerabilities particularly challenging to address. One potential threat is the Denial of Service (DoS) attack vector, which involves exploiting the contract’s logic to disrupt either the whole protocol’s functionality or at least render some parts of the protocol unusable for other users. DoS attacks can lead to financial losses or disrupt a protocol’s normal working flow.

In traditional network security, a DoS attack overwhelms a machine or network resource with excessive traffic, making it unavailable to intended users. In other words, the attacker attempts to make a computer or network resource unavailable to its intended users by disrupting the services of a host connected to the Internet. The goal of a DoS attack is to overwhelm the targeted system with an enormous amount of requests or data, causing it to become unresponsive or crash.

Imagine a group of pranksters repeatedly calling a pizza place to make fake orders. The phone lines stay tied up, and real customers can't get through to place their orders. A Denial of Service attack works similarly. Hackers flood a website or online service with excessive requests, overwhelming it and preventing legitimate users from accessing it.

Denial of Service (DoS) in the Web3 realm

A DoS attack against a smart contract aims to make specific functions or the entire contract unusable for legitimate users. This can result in users being unable to interact with the contract as intended, either temporarily or permanently.

Imagine a DEX with a placeOrder function that allows only 10 orders to be placed at a time for the whole exchange. A user could launch a DoS attack by continuously placing batches of 10 orders with very small amounts (e.g., $0.10 each), preventing other users from placing legitimate orders.

Types of DoS attacks in smart contracts

There are multiple ways in which a DoS attack can manifest and there is no straightforward fix. Most of the time DoS attack opportunities are created by:

  • Logical errors in the system’s flow.
  • Wrong assumptions about the state of the contract when certain interactions happen.
  • Certain edge cases the developers might’ve missed, and the list goes on…

To make it easier to understand how a DoS attack can take place, take a look at the example below.

DoS via Malicious Recipient Contract

Let’s say we have an auction contract for a rare collectible item. People bid on the collectible using this smart contract. How it is supposed to work? Bidders send Ether to the smart contract to place their bids. The smart contract automatically tracks the current highest bidder and refunds the previous highest bidder. At the end of the auction, the highest bidder wins the item.

Code example of DoS via Malicious Recipient Contract:

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

contract SimpleAuction {
    address public highestBidder;
    uint public highestBid;

    function bid() public payable {
        require(msg.value > highestBid, "Bid not high enough");

        if (highestBidder != address(0)) {
            // Refund the previous highest bidder
            (bool success, ) = highestBidder.call{value: highestBid}("");
            require(success, "Refund failed");
        }

        highestBidder = msg.sender;
        highestBid = msg.value;
    }
}

Above we have a very basic Auction smart contract that does the following:

  • Tracks the address of the current highest bidder in the highestBidder variable.
  • Tracks the most recent highest bid in the highestBid variable.
  • Implements a bid function that will check if the function caller is sending in an amount (msg.value) greater than the last highestBid. If the new bidder sends in a higher amount, the contract will refund the previous highest bidder, before overwriting the highestBidder and highestBid values.

For an untrained eye, the contract may seem fine. However, a malicious user can DoS the auction if they want to.

See the attacker contract below.

//import SimpleAuction contract here

pragma solidity ^0.8.0;

contract DOSAuction {
    SimpleAuction public victim;

    //pass in the address of the SimpleAuction contract that we want to DOS
    constructor(address _auctionAddress) {
        victim = SimpleAuction(_auctionAddress);
    }
    
    // Function to place a bid
    function attack() external payable {
        victim.bid{value: msg.value}();
    }

    // receive function that always reverts
    receive() external payable {
        revert("I will not accept any refunds!");
    }
}

Quick breakdown of what the DOSAuction contract does:

  • Inside the constructor we pass in the address of the legitimate SimpleAuction contract and store its address inside the victim variable.
  • The attack function allows the user to deposit Ether into the SimpleAuction contract from the DOSAuction contract. After calling the attack function successfully, the highestBidder address inside the SimpleAuction contract will be the address of the DOSAuction contract.
  • It implements a receive function that always reverts. This causes all native ether transfers to our DOSAuction contract to fail. This contract can not receive native ETH.

This attack exploits the SimpleAuction contract's assumption that refunds will always succeed.

When a new bidder tries to bid more than our DOSAuction contract the transaction will always fail, because the refund of the previous highest bidder (which is our malicious contract) will always fail.

To fix the auction contract, one solution would be to change the design of the refund logic. The idea is to shift from a "push" to a "pull" payment system. Here's how to fix it:

  1. Separate the bidding logic from the refund logic. Instead of immediately trying to refund the previous highest bidder, we can store the amount they're owed and let them withdraw it later.
  2. Implement a withdrawal pattern. Create a function that allows users to withdraw their funds after the auction ends, rather than the contract automatically sending funds.

Other Types of Denial of Service Attacks

As stated earlier there are no straightforward “DoS” attacks. This attack vector will be different from one protocol to another and so will be its impact.

Sometimes, causing a DoS may have no benefit for the attacker apart from temporarily griefing other users. In other cases, it may lead to financial losses for other users. In order to be better equipped to prevent these attacks against your protocol, here are some things to watch out for during development:

  • Looping over unbounded arrays can lead to DoS due to excessive gas costs. Imagine that you want to distribute rewards to a very large number of users, and all their addresses are stored in an array. If the array gets too big, you may be unable to distribute the rewards because the function call will run out of gas. A good solution would be to implement partial distribution logic so that you can distribute the rewards in smaller batches.
  • Pull payment patterns instead of push. Like in the example above, the SimpleAuction contract would’ve been safer if the logic for bidding and refunding had been split into two separate functions.
  • Frontrunning and race conditions. Keep in mind that most blockchains have public mempools and user transactions can be frontrun/backrun. While developing, ask yourself if there are situations that would benefit attackers if they frontrun/backrun other users.

Conclusion

DoS attacks represent a significant threat to the functionality and reliability of smart contracts and blockchain networks. These attacks can render contracts unusable, disrupt critical operations, and potentially cause financial losses. They are typically harder to spot because they differ from one protocol to another.

Addressing DoS vulnerabilities requires consideration at multiple levels from individual contract design to network-level protocols.

Developers should prioritize regular audits, implement robust security mechanisms, and stay informed about emerging threats.

Latest articles