Skip to content

Latest commit

 

History

History
187 lines (115 loc) · 7.29 KB

denial-of-service.md

File metadata and controls

187 lines (115 loc) · 7.29 KB

😈 Denial of Service

Branch Info

Author: Temirzhan Yussupov****
Source code: https://github.com/scaffold-eth/scaffold-eth-examples/tree/denial-of-service-example
Intended audience: Intermediate
Topics: Scaffold-eth basics, Smart Contracts

🏃‍♀️ Quick Start

Make contract unusable by exploiting push external calls 😈

Table of Contents

  1. About The Project
  2. Getting Started
  3. Exploring smart contracts
  4. Attack vector
  5. Practice
  6. Prevention
  7. Additional resources
  8. Contact

About The Project

This little side quest will allow you to explore the concept of "Denial of Service".

One exploit we introduce here is denial of service by making the function to send Ether fail.

Getting Started

Prerequisites

You should be familiar with calls and reverts.

Installation

Let's start our environment for tinkering and exploring how "DoS attack" works.

  1. Clone the repo first
git clone -b https://github.com/scaffold-eth/scaffold-eth-examples.git denial-of-service-example
cd denial-of-service-example
  1. Install dependencies
yarn install
  1. Start your React frontend
yarn start
  1. Spin up your local blockchain using Hardhat
yarn chain
  1. Deploy your smart contracts to a local blockchain
yarn deploy

Pro Tip: Use tmux to easily start all commands in a single terminal window!

This is how it looks like in my terminal:

image

If everything worked fine, you have to have something like this opened in your browser:

image

Smart contracts

Let's navigate to packages/hardhat/contracts folder and check out what contracts we have there.

VulnerableAuction.sol

This smart contract will become unusable once we exploit it.

The logic is pretty straightforward:

function bid() payable external {
  require(msg.value >= highestBid);

  if (highestBidder != address(0)) {
    (bool success, ) = highestBidder.call.value(highestBid)("");
    require(success); 
  }

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

Smart contract immitates auction by keeping track of the highest bid made. If you want to become a highestBidder you have to send ETH greater than the previous highestBid.

Try to find a way to exploit this contract (make it unusable) before reading further.

Attack.sol

Our contract for exploitation.

Note that this block of code is commented in the contract.

function () external payable {
  assert(false);
}

Try to guess why :)

Attack vector

The attack we are going to do is called DoS with (Unexpected) revert. So how does it work?

Basically, If attacker bids using a smart contract which has a fallback function that reverts any payment, the attacker can win any auction.

When it tries to refund the old leader, it reverts if the refund fails. This means that a malicious bidder can become the leader while making sure that any refunds to their address will always fail. In this way, they can prevent anyone else from calling the bid() function, and stay the leader forever.

This is why part with fallback() was commented in our Attack.sol.

Practice

Let's use our awesome frontend provided by scaffold-eth to make sure our assumption works fine.

Run two different sessions. One will be for a simple user and one will be for an evil hacker.

This is how it looks like for me:

image

My first tab is for a simple user and second tab is for a hacker.

Let's make an initial bid and become a highestBidder as a simple user.

image

Now let's run our attack method as an attacker and disable our VulnerableAuction forever!

image

Seems we became a new highestBidder.

image

Now simple user can not become a new highestBidder even though he puts more ETH that we did.

image

Prevention

In order to mitigate this attack, we have to favor pull over push for external calls.

This is demonstrated nicely in GoodAuction.sol. Note how we added a new method withdrawRefund. Now we do not depend on any push external calls like sending money back to someone.

function withdrawRefund() external {
    uint refund = refunds[msg.sender];
    refunds[msg.sender] = 0;
    (bool success, ) = msg.sender.call.value(refund)("");
    require(success);
}

Using this we are no longer vulnerable!

Additonal resources

Contact

Join the telegram support chat 💬 to ask questions and find others building with 🏗 scaffold-eth!