Intro to Smart Contract Security Audit: DOS

Background

Denial of service (DoS) is a problem in smart contract security that is similar to traditional network security.

Prior Knowledge

Traditional network security denial of service (DoS): DoS is an abbreviation for denial of service. A denial of service occurs when an interference to a service reduces or eliminates its availability. The following are examples of common denial-of-service attacks against network protocols: SYN Flood, IP Spoofing, UDP Flood, Ping Flood, Teardrop Attack, LAND attack, Smurf attack, Fraggle attack, and so on.

Smart contract denial-of-service attack: A security issue that can result in code logic errors, compatibility issues, or excessive call depth (a feature of blockchain virtual machines), causing smart contracts to fail to function properly. Smart contract denial of service attack methods are relatively simple, including but not limited to the following three:

  • Denial of service attack based on code logic: This type of denial of service attack is typically caused by inaccuracy of the contract’s code logic. The most common example occurs when there is logic in the contract that loops through the incoming mapping or array. When there is no length limit on the incoming map or array, an attacker can consume a large amount of Gas by passing in a super-long map or array for loop traversal, causing the transaction’s Gas to overflow and eventually render the smart contract inoperable.

Vulnerability Example

In my opinion, thanks to common background knowledge, everyone is familiar with the concept of a denial of service attack. The external call denial of service attack is the most common type of the three types of DOS attacks. In order to provide a thorough introduction, we will walk you through a typical code example below:

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

contract KingOfEther {
address public king;
uint public balance;

function claimThrone() external payable {
require(msg.value > balance, "Need to pay more to become the king");

(bool sent, ) = king.call{value: balance}("");
require(sent, "Failed to send Ether");

balance = msg.value;
king = msg.sender;
}
}

Vulnerability Analysis

We can see from the above contract that the goal is to select the “King of Ether.” The claimThrone() contract allows users to compete for the title of “King of Ether” by entering any amount of Ether greater than the previous user. If the coin value is higher than the previous player’s ETH, the ETH will remain in the contract and the new player will be crowned “Ether King,” while the old player’s ETH will be returned to them in the same fashion.

We can see that the logic of generating the new king and returning the old king is completed in the same function, and the refund return value is also checked in claimThrone(). Let’s combine these features to complete the attack.

Attack Contract

Note: The following attack scenarios and contract code logic are merely examples and are for demonstration purposes only.

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

contract Attack {
KingOfEther kingOfEther;

constructor(KingOfEther _kingOfEther) {
kingOfEther = KingOfEther(_kingOfEther);
}

function attack() public payable {
kingOfEther.claimThrone{value: msg.value}();
}
}

Let’s start by first analyzing the attack process:

  1. Alice deploys the KingOfEther contract.

The wealthy and attractive Bob finds this frustrating; if he is so wealthy, why can’t he be king?

Let’s see why.

When Bob calls KingOfEther.claimThrone() to send 20 ethers to the KingOfEther contract, the refund logic of KingOfEther.claimThrone() will be triggered, and Eve’s 3 ethers will be returned to the Attack contract. Let’s examine the Attack contract once more. This contract does not implement the fallback() method of payable, so it cannot receive ether. As a result, the refund logic of KingOfEther.claimThrone() will always fail, and its return value will always be false. (sent, “Failed to send Ether”) checks are consistently reversed. As long as a refund is triggered, after the KingOfEther contract relays the Attack contract, no one can become the new king. Therefore, Eve executed a successful denial of service attack.

Suggestions for Repair

As a developer:

  1. Attention should be paid in the development of smart contracts to deal with consistent failures, such as asynchronous processing of potentially failing external call logic.

The following is an example of a fix for the previously mentioned vulnerable contract:

// SPDX-License-Identifier: MITpragma solidity ^0.8.13;
contract KingOfEther { address public king; uint public KingValue; mapping(address => uint) public balances;
function claimThrone() external payable { balances[msg.sender] += msg.value;
require(balances[msg.sender] > balance, "Need to pay more to become the king"); KingValue = balances[msg.sender]; king = msg.sender; }
function withdraw() public { require(msg.sender != king, "Current king cannot withdraw");
uint amount = balances[msg.sender]; balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}(""); require(sent, "Failed to send Ether"); }}

The balances map has been added to the repair contract, which records the total amount of ether put into the contract by each person. In comparison to the previous contract, the player can now add ether to reclaim the throne after losing it. The key point of the repaired version is that there is a method that handles the refund logic asynchronously. To receive a refund, players must manually call withdraw(). Even if a malicious player refuses to accept ether, this has no effect and will not result in the previously mentioned denial of service.

As an auditor:

  • Analysis of internal contracts:
  1. Determine if the contract contains any logical errors that affect its usability.
  • Analysis of external contracts:
  1. Pay close attention to compatibility issues when interacting with external contracts, such as: the return value compatibility of TRC20-USDT isn’t processed, which results in tokens being locked.
  • Analysis of rights management:

All function methods’ visibility and access rights must be examined and confirmed during the audit. It is necessary to combine the design documents provided by the project party in order to confirm the rights are in accordance with the design document descriptions during the audit. If it is determined that there are excessive authorizations or an unclear division of authority, it is essential to communicate with the project’s team about improving their processes and methods to ensure administrative and operational errors are prevented when the contract is in effect.

--

--

SlowMist is a Blockchain security firm established in 2018, providing services such as security audits, security consultants, red teaming, and more.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
SlowMist

SlowMist is a Blockchain security firm established in 2018, providing services such as security audits, security consultants, red teaming, and more.