top of page

Web3 Security Vulnerabilities: Comprehensive Guide to Protecting Digital Assets

Updated: Apr 8

Web3: where the blockchain is strong, the code is weak, and the hackers are always one step ahead.

Introduction to Web3 Security


Web3, also known as "Web 3.0" or "Decentralized Web," is the next evolution of the internet that utilizes blockchain technology and decentralized networks to create a more secure, private, decentralized online ecosystem. Unlike the current Web 2.0, centralized and controlled by a few large corporations, Web3 is built on decentralized protocols and distributed ledger technology, allowing for greater transparency, security, and privacy.


Unlike the conventional internet, where data is stored on centralized servers, Web3 operates on a decentralized network, making it more secure and transparent.


Responsibilities of smart contract auditor
Responsibilities of smart contract auditor

One of the significant advantages of Web3 is its potential to give users complete control over their digital assets, including cryptocurrencies, personal data, and digital identities. However, this power comes with an increased responsibility to safeguard those assets from potential threats.


Despite its numerous benefits, Web3 is not immune to security vulnerabilities. This two-part blog series will uncover the top web3 risks and vulnerabilities. Among these Web3 issues, smart contract vulnerabilities are one of the most significant concerns surrounding Web3 security. Smart contracts are self-executing contracts with agreement terms directly coded into the contract. Malicious actors can exploit a smart contract to steal assets or execute malicious code if a smart contract contains a flaw.

Reentrancy Attack in Web3


A reentrancy attack is an exploit where an attacker takes advantage of a vulnerability in the smart contract code. By exploiting this vulnerability, the attacker can call the same function multiple times before it has finished executing. This behaviour can allow the attacker to execute malicious code repeatedly and potentially cause damage to the program or steal data or resources.

Workflow of Reentrancy Attack
Workflow of Reentrancy Attack

A contract can alter its state multiple times within a transaction, which makes reentrancy attacks possible. An attacker can exploit this vulnerability to repeatedly call a function and execute malicious code while the contract's state remains updated.


The receive function is a fallback function that executes automatically when a contract receives Ether without data. If a contract receives Ether without specifying any function to call, the receive function will be called automatically.

In Solidity, a contract can have only one receive function, declared using the syntax receive() external payable { ... }. The receive function cannot accept arguments, cannot return anything, and must have external visibility and payable state mutability.


Functions of reentrancy
Functions of reentrancy

Understanding Vulnerable Code


Vulnerable code example for reentrancy attacks

The function first retrieves the balance of Ether in the user's account on the smart contract using the balances mapping, which maps addresses to their respective balances. If the balance is greater than 0, then the function proceeds to the next step.

The function then attempts to send the Ether to the user's address using msg.sender.call{value: bal}(""). This action transfers the Ether to the user's address, but it is done using the low-level call function, which says it is vulnerable to reentrancy attacks.

Mitigation against reentrancy attack


There are two prevention techniques which are applicable :

  1. Limit External Function Calls.

  2. Implement Mutex Locks.

Preventing reentrancy attacks requires careful code review by developers to identify potential vulnerabilities. Testers can use automated tools can be used to detect weaknesses in the code. Developers should avoid using external calls wherever possible and carefully manage the control flow within their smart contracts.

One way to prevent reentrancy attacks is to use mutex locks to prevent concurrent calls to a function. Mutex locks ensure that a function can only be called once at a time, preventing reentrancy attacks. Another approach is limiting the amount of gas available to a contract, preventing an attacker from executing multiple transactions in a single function call.


Default Visibility Attack


A default visibility attack is a security vulnerability that can occur in smart contracts. It arises when the visibility modifier for a function is not explicitly defined, making the function accessible to anyone, including other contracts. This behaviour can allow unauthorized parties to call the function and potentially exploit the contract's vulnerabilities.


In previous Solidity versions, contract functions were set to "public" visibility by default. This meant that all functions were publicly visible and could be accessed by anyone, including other contracts on the blockchain. This default setting posed a security risk as it made it easier for attackers to exploit vulnerabilities in a contract.

Suppose a contract's functions are set to "public" visibility without proper access control. In such a case, an attacker can exploit this vulnerable configuration by creating a malicious contract that calls the victim contract's public function and modifies its state. This can result in loss of funds or other unintended consequences, such as compromising the integrity of the contract or causing it to behave unexpectedly.

To address this issue, Solidity has since been updated to require developers to define the visibility of their contract functions explicitly, which means that developers must now specify the visibility modifier for each function, such as "public," "private," "internal," or "external," to ensure that only authorized parties can access the function. By doing so, developers can better protect their contracts from security vulnerabilities and prevent unauthorized access to their functions.

Understanding Vulnerable Code


Vulnerable code example for default visibility attack

In this example, the visibility modifier for the "getBalance()" function is not explicitly defined. By default, it is public, meaning anyone, including other contracts, can access it. An attacker can exploit this vulnerability by creating a malicious contract that calls the getBalance() function and retrieves the value of the balance variable. This allows the attacker to obtain sensitive information about the contract and potentially steal funds. Defining the visibility modifier for all functions as public, private, internal, or external is essential to avoid such attacks.


Mitigation against default visibility attack


To mitigate the default visibility vulnerability in web3, developers can explicitly set the visibility modifier for their functions.

Newer versions of Solidity set functions to "internal" visibility by default, which restricts their usage to within the contract where they are defined, making it a more secure default visibility setting than "public." Developers can restrict function access using access modifiers such as "private" or "external."


Time Dependence Attack


Time dependence attacks, also known as timestamp dependence attacks, are a security vulnerability in smart contracts. These attacks exploit that smart contracts may rely on the current block timestamp to execute or prevent specific actions. Smart contracts that depend on timestamps to make decisions or execute logic are vulnerable to these attacks.

The block.timestamp variable in Solidity returns the current timestamp of the current block in Unix epoch format, which represents the number of seconds that have elapsed since January 1, 1970. This value is set by the miner who creates the block and is based on their system clock. However, miners can manipulate the timestamp, which could affect the outcome of time-dependent operations in a smart contract. For example, they could change the clock on their mining nodes to set a timestamp earlier or later than the current time. This means that smart contracts that rely on timestamps to make decisions or execute logic are vulnerable to time-dependence attacks.


In April 2018, an attacker conducted a Time Dependence attack on the "Golem Network smart" contract, exploiting a vulnerability in the contract's code. The attacker created multiple malicious transactions with invalid timestamps and conspired with a specific miner to include these transactions in the blocks they mined. Although the attacker did not steal any funds, the attack caused disruptions to the Golem Network. The attacker acted as a "white hat" and worked with the Golem team to fix the vulnerability and prevent future attacks.


Understanding Vulnerable Code


Vulnerable code example for time dependence attacks

In this code, the "claimReward()" function can only be executed if the current block timestamp is greater than or equal to the deadline. However, this code is vulnerable to a time-dependency attack as a malicious miner could manipulate the block timestamp to prevent the require() statement from being triggered, allowing them to claim the reward before the deadline.


Mitigation against time dependence attack


To reduce the likelihood of miner manipulation of timestamps, developers should aim to create contracts that can withstand potential timestamp alterations. One way to achieve this is by utilizing alternative time sources or incorporating methods to identify and manage incorrect timestamps.

For instance, instead of relying solely on "block.timestamp", contracts can leverage an external time oracle to obtain the current time. Additionally, developers can implement checks that validate the timestamp range and reject transactions with timestamps that significantly deviate from the anticipated value. These measures can help reduce the risk of timestamp-dependence attacks and increase the overall security of the smart contract.


Floating Points and Precision Attack


Floating-point numbers are commonly used to represent non-integral numbers in web3 programming languages. However, they are subject to rounding errors due to the limited number of bits used to represent the fraction part of the number. Precision attacks in web3 exploit these rounding errors in floating-point arithmetic, which can cause issues for smart contracts that rely on precise calculations involving these numbers.


This attack is particularly concerning for financial transactions, where accuracy is crucial. For instance, a rounding error could lead to significant financial losses or incorrect asset transfers if a smart contract calculates interest rates or converts one cryptocurrency to another using floating-point numbers. Developers must mitigate the risk of precision attacks, such as using alternative data types or implementing error-checking mechanisms.


Solidity does not fully support floating-point numbers, which can cause precision loss when performing arithmetic operations. This precision loss can lead to unexpected behaviour in smart contracts and create vulnerabilities. One typical example of a floating-point precision attack is when a smart contract uses floating-point numbers for financial calculations, such as in a decentralized exchange. If the precision is not carefully managed, an attacker can use rounding errors to manipulate the contract and gain an unfair advantage in trading.


Understanding Vulnerable Code


Vulnerable code example for floating point & precision attacks

In this example, the "tokenPrice" is defined as a uint256 variable, but it is calculated based on the current ether price, which is a floating-point number. As a result, rounding errors can occur during the multiplication operations in the buyTokensand sellTokens functions.

An attacker could exploit this vulnerability by manipulating the calculations to gain an unfair advantage in the buying and selling tokens. For example, they could round down the cost variable in the buyTokens function to pay less than the actual cost of the tokens or round up the payout variable in the sellTokens function to receive more Ether than they should.


Mitigation against floating points and precision attack

  1. Avoid using floating-point numbers for financial calculations - Instead, use fixed-point arithmetic or integer arithmetic with a high enough level of precision to perform the necessary calculations.

  2. Use safe libraries - Use established libraries audited and tested for floating-point arithmetic to perform financial calculations.

  3. Use appropriate data types - Use appropriate data types, such as integer or fixed-point numbers, to perform financial calculations.

  4. Avoid relying on a single source of randomness - Use multiple sources of randomness to avoid relying on a single source that could be manipulated.

  5. Validate input values - Validate input values to ensure they are within acceptable ranges and formats.

  6. Consider using higher-level programming languages - Higher-level programming languages such as Vyper or Solidity 0.8.0 have built-in support for fixed-point arithmetic.

  7. Use proper testing and auditing - Perform rigorous testing and auditing of smart contracts involving financial calculations to identify and address potential vulnerabilities.


By following these mitigation strategies, developers can minimize the risk of floating-point and precision attacks in their web3 applications and smart contracts.


Transaction-Ordering Dependence Attack


Transaction-Ordering Dependence (TOD) Attack, also referred to as "front-running", is a type of security attack in which an attacker takes advantage of the order in which the network processes transactions. The attacker closely watches the network for pending transactions and then submits their transaction with a higher gas price to ensure that it is processed before the targeted transaction, allowing them to manipulate the outcome to their advantage.

By monitoring the network for pending transactions, an attacker can submit their transaction with a higher gas price to ensure it is processed before the targeted transaction. This attack can be used to manipulate the outcome of specific transactions or to gain an unfair advantage over other participants in the network. An example would be front-running a trade on a decentralized exchange (DEX), where the attacker submits their trade with a higher gas price to ensure it is processed before the target trade, allowing the attacker to profit from the price difference.

In a well-known incident, attackers targeted the Bancor exchange using a Transaction-Ordering Dependence (TOD) attack. The attackers placed a large buy order for a specific token and then deliberately slowed down the processing of the first transaction by including a very high gas price. This resulted in the order being executed at a higher-than-market price, allowing the attackers to profit from the price difference. They repeated the attack multiple times by cancelling the original buy order using a third transaction. As a response, Bancor temporarily suspended trading and implemented new measures to prevent similar attacks in the future. These measures included a minimum time requirement for cancelling orders and a gas price limit for executing trades.

Understanding Vulnerable Code


Vulnerable code example for transaction-ordering dependence attacks

In this example, the "withdraw" function is vulnerable to a TOD attack because it can be front-run by an attacker. The "winner" variable is initially set to the address of the lottery winner. However, an attacker can submit a transaction with a higher gas price to the network, which will be processed before the original "withdraw" transaction.


The attacker can set their address as the new "winner" before processing the original "withdraw" transaction. This allows the attacker to withdraw the entire lottery balance, even if their balance is insufficient.

Mitigation against transaction-ordering dependence attack


  1. Use batch transactions - Bundling multiple transactions into one transaction can make it harder for attackers to front-run a specific transaction.

  2. Use commit and reveal schemes - Using them can ensure that transactions are not exposed until a certain point, making it difficult for attackers to front-run transactions.

  3. Use private transactions - Using private transactions can prevent attackers from monitoring the network for pending transactions, making it harder for them to identify and front-run specific transactions.

  4. Use decentralized exchanges (DEXs) with order matching algorithms - DEXs that use order matching algorithms can help prevent front-running by matching orders fairly and transparently.

  5. Increase gas limit and minimum gas price - Increasing the gas limit and minimum gas price can make it more expensive for attackers to front-run transactions, reducing the likelihood of success .Use time locks - Time locks can delay the execution of transactions, making it more difficult for attackers to front-run them. TOD attacks.

  6. Use time locks - Time locks can delay the execution of transactions, making it more difficult for attackers to front-run them.


By implementing these strategies, developers and users can reduce the risk of TOD attacks and improve the security of their applications and transactions on the blockchain.


You can find the vulnerable code references used in the blog can be found in our GitHub here.


Cryptocurrency in lockdown

This blog is part of a two-part series. Stay tuned for our upcoming blog post on the second part of web3 security! We appreciate your interest in this topic and can't wait to share our latest insights. Please keep your eyes glued for the latest posts!


 

Register for instructor-led online courses today!


Check out our free programs!


Contact us with your custom pen testing needs at: info@darkrelay.com or WhatsApp.

401 views

Recent Posts

See All
bottom of page