// inside head tag
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.
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.
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:
To make it easier to understand how a DoS attack can take place, take a look at the example below.
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:
highestBidder
variable.highestBid
variable.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:
constructor
we pass in the address of the legitimate SimpleAuction
contract and store its address inside the victim
variable.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.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:
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:
SimpleAuction
contract would’ve been safer if the logic for bidding and refunding had been split into two separate functions.
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.