๐Ÿ˜ˆ
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. 1.
    โ€‹About The Projectโ€‹
  2. 2.
    โ€‹Getting Startedโ€‹
  3. 4.
    โ€‹Attack vectorโ€‹
  4. 5.
    โ€‹Practiceโ€‹
  5. 6.
    โ€‹Preventionโ€‹
  6. 7.
    โ€‹Additional resourcesโ€‹
  7. 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. 1.
    Clone the repo first
1
git clone -b https://github.com/scaffold-eth/scaffold-eth-examples.git denial-of-service-example
2
cd denial-of-service-example
Copied!
  1. 1.
    Install dependencies
1
yarn install
Copied!
  1. 1.
    Start your React frontend
1
yarn start
Copied!
  1. 1.
    Spin up your local blockchain using Hardhatโ€‹
1
yarn chain
Copied!
  1. 1.
    Deploy your smart contracts to a local blockchain
1
yarn deploy
Copied!
Pro Tip: Use tmux to easily start all commands in a single terminal window!
This is how it looks like in my terminal:
โ€‹โ€‹
โ€‹
โ€‹
If everything worked fine, you have to have something like this opened in your browser:
โ€‹โ€‹
โ€‹
โ€‹

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:
1
function bid() payable external {
2
require(msg.value >= highestBid);
3
โ€‹
4
if (highestBidder != address(0)) {
5
(bool success, ) = highestBidder.call.value(highestBid)("");
6
require(success);
7
}
8
โ€‹
9
highestBidder = msg.sender;
10
highestBid = msg.value;
11
}
Copied!
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.
1
function () external payable {
2
assert(false);
3
}
Copied!
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:
โ€‹โ€‹
โ€‹
โ€‹
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.
โ€‹โ€‹
โ€‹
โ€‹
Now let's run our attack method as an attacker and disable our VulnerableAuction forever!
โ€‹โ€‹
โ€‹
โ€‹
Seems we became a new highestBidder.
โ€‹โ€‹
โ€‹
โ€‹
Now simple user can not become a new highestBidder even though he puts more ETH that we did.
โ€‹โ€‹
โ€‹
โ€‹

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.
1
function withdrawRefund() external {
2
uint refund = refunds[msg.sender];
3
refunds[msg.sender] = 0;
4
(bool success, ) = msg.sender.call.value(refund)("");
5
require(success);
6
}
Copied!
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!