Understanding Smart Contract Vulnerabilities

17 min read

Discover different types of smart contract vulnerabilities, and ways to safeguard against them.

Smart contracts are a breakthrough technology that has the potential to transform the way we do business and transfer value. However, just like any new technology, there are risks and vulnerabilities that must be taken into consideration. These vulnerabilities can be exploited by hackers to steal funds or disrupt the regular functioning of the contract. As the usage of smart contracts continues to expand, it is important for developers, users, and project communities to be aware of potential risks and vulnerabilities.

Unfortunately, in 2022, there were over 300 incidents stemming from various attack vectors such as smart contract hacks, rug pulls, web-based attacks like DNS attacks, phishing attacks, and many others. These attacks resulted in over $4.37 billion in investor and user funds being stolen from blockchain projects. According to a report, the total amount of funds lost due to smart contract exploits was $215 million in 2020, approximately $1.3 billion in 2021, and over double that amount in 2022, reaching $2.7 billion. The increasing number of web3 attacks caused by smart contract vulnerabilities is a pressing issue that cannot be ignored.

To promote a better understanding of smart contract security, we would like to outline a few attack vectors and ways to mitigate them.

Integer Overflow, and Underflow#

Integer overflow and underflow attacks are common and relatively simple to execute in a smart contract. These attacks focus on transactions that allow unauthorized data or values to be input.

In the Ethereum Virtual Machine, integer data types have a finite size limit. For example, a uint256 variable can only store numbers within the range of [0, 2**256-1]. Attempting to store a value that exceeds the upper limit of this range would result in an overflow, and the output would be zero. Conversely, underflow occurs when a variable is initialized at zero and an arithmetic function attempts to modify it by subtracting 1. The result would become 2**256, causing the variable to underflow.

If user input for Solidity variables is not properly validated and calculations are performed that exceed the range of the data type storing them, they can be vulnerable to exploitation. It is essential to ensure that data types are correctly managed, and input values are validated before performing calculations to avoid these attacks.

Front-Running Attack#

Smart contract front-running is a type of attack where a front-runner attempts to execute a transaction by offering a higher fee. Miners then prioritize the front-runner's transaction, preempting transactions that were already pending. All front-running attacks have a common feature: the victim is attempting to execute a function from a contract with a particular state, while the attacker is trying to invoke the same function but earlier than the victim.

NFT marketplaces are one of the most profitable areas for front-running attacks, as a malicious actor may attempt to preempt transaction execution to gain an advantage. The attacker can purchase digital assets at a lower price and sell them for a higher profit when they become more valuable in the market.

One way to mitigate user-originating front-running attacks is to include a logic in the contract that sets an upper limit on gas fees. This prevents users from increasing the gas fee above this maximum threshold and receiving preferred transaction ordering. By implementing this method, the likelihood of front-running attacks can be reduced to a greater extent.

Sandwich Attack#

A sandwich attack is a form of front-running that targets DeFi protocols and services. In this type of attack, a malicious actor searches for a pending transaction on a network like Ethereum. Sandwiching involves placing one order just before and one order shortly after the target trade. The attacker essentially performs both a front-run and a back-run simultaneously, with the victim's transaction in the middle.

The attacker places two orders at the same time as other pending transactions to manipulate asset values. They first buy the asset the victim is exchanging to, for example, using Binance Coin (BNB) to exchange to Ether (ETH), knowing that the price of ETH is rising. Then, in order to make the victim pay a higher price for ETH, the attacker buys it at a lower price. The attacker later sells ETH for a higher price, making a profit.

The amount of Ether given to the initial user is sandwiched by the attacker's two transactions. The following trade will be more expensive because the attacker was successful in filling the order at their intended price. This sequence of events inflates the price of ETH, allowing the attacker to make money by outsmarting the trader and artificially increasing the price.

To avoid being sandwiched, the simplest technique is to place a limit order. Users can specify their fill price instead of relying on standard market orders, which are susceptible to slippage.

Signature Replay Attack#

Cryptographic signatures are crucial components for authenticating transactions on the blockchain. When a transaction is signed with the corresponding private key, the initiator is associated with the transaction, enabling the blockchain accounting system to function effectively.

However, a Signature Replay Attack occurs when a cryptographic signature is used multiple times. To execute a function inside a contract, a signature must be submitted, but if the same signature is used to execute the same function multiple times, it creates a vulnerability.

To mitigate a breach caused by this attack, a contract should include a nonce in the signed data. The nonce is a unique sequential number that is part of the signed message, and it ensures that the signatures required for each successful call are distinct. As a result, an attacker will not be able to use the previously observed message, making replaying unsuccessful.

In addition to using a nonce, another best practice is to use a chain-specific signature scheme that includes the chain ID in the signed message. By doing so, transactions signed on one chain will not be recognized as valid on another chain with a different ID. This practice ensures that a transaction signature is only valid on the intended chain and cannot be replicated on another chain.

TX Origin Attack#

Solidity, the programming language used to write smart contracts on the Ethereum blockchain, includes a global variable called tx.origin that can be used to retrieve the address of the account that initiated the first call in a given transaction. However, this variable can be vulnerable to Tx Origin attacks, where malicious actors use phishing attacks to trick users into conducting authenticated tasks on vulnerable contracts that rely on tx.origin for user authentication and access control.

If an attacker can convince a trustworthy smart contract to make a call on their behalf, the contract's address will be saved in tx.origin, allowing the attacker to bypass access controls and use normally restricted functionality. In a call stack scenario where contract A calls contract B, which calls contract C, and so on until contract D, the value of msg.sender will be contract C's address, while the value of tx.origin will be contract A's address.

The best defense against Tx Origin attacks is to avoid using tx.origin for authentication purposes and instead rely on msg.sender. This can prevent malicious actors from manipulating the call stack and bypassing access controls.

Precision Loss#

In Solidity, mathematical operations like addition, subtraction, multiplication, and division are used, just like in other programming languages. When performing mathematical operations on Solidity, it's essential to be aware of precision loss or rounding errors, which can occur due to the limited number of digits that can be stored in a fixed amount of memory. This limitation can lead to the loss of precision during calculations, which can cause the result to be inaccurate.

Rounding errors can have a significant impact on data accuracy, especially when multiple operations are conducted. It's essential to be mindful of these rounding errors because they can accumulate over time, particularly when the arithmetic operations are conducted repeatedly. Therefore, it's always best to perform the multiplication operation before the division operation to avoid these inaccuracies. By following this approach, the vulnerability caused by rounding errors can be prevented.

Visibility Specifier#

Solidity's visibility specifiers are important for controlling how state variables and functions can be accessed in a smart contract. These specifiers indicate whether the variables and functions can be called by users, other derived contracts, or both internally and externally. Incorrect use of visibility specifiers can leave smart contracts open to vulnerabilities.

State variables can be specified as public, internal, or private, while functions can be specified as external. It is always best practice to explicitly define the visibility of variables and functions in a contract. The latest versions of Solidity even show warnings during compilation for functions without explicitly declared visibility.

However, even when visibility is explicitly defined, improper use of a function’s visibility can still introduce vulnerabilities. For instance, if a function that should only be called internally is mistakenly marked as public, it can be accessed by any user, potentially compromising the security of the contract.

Since deployed contracts are immutable, any vulnerability introduced through incorrect visibility cannot be fixed after deployment. Therefore, it is crucial to thoroughly test for visibility-related vulnerabilities before deployment. One improperly defined function visibility can create a vulnerability that could jeopardize the entire contract. Thus, the visibility of the functions or variables should always be specified and set, and extra precautions need to be taken to ensure that the contract is not vulnerable to attacks due to incorrect visibility.

Bad Randomness#

Solidity includes functions and variables that rely on sources of randomness, such as block numbers, which can be difficult to predict. However, these sources of randomness can be compromised by bad actors who can replicate them to attack the function. This is known as "bad randomness."

For instance, an attacker could exploit a smart contract that generates a random number for a lottery game based on the current block number. The attacker creates an exploitative contract that checks if the current block number is a winning number, and if it is, the attacker calls the lottery contract within the same transaction. Since the block number stays the same across both contracts in the same transaction, the attacker can repeatedly call the malicious contract to profit from the game.

To prevent this type of attack, it is recommended to avoid using block variables as sources of entropy. Instead, a two-transaction approach can be used to secure the randomness of the source. The first transaction would secure a future block number as the source of entropy, and the second transaction would use the blockhash of the chosen block as the source of entropy to carry out the necessary logic after that block has been mined.

Timestamp Dependence#

A smart contract relies on the block.timestamp function for critical logic, it is susceptible to a timestamp dependence vulnerability. This vulnerability can affect activities such as transmitting ETH or using the function as a source of entropy for generating random numbers.

The block.timestamp function is vulnerable to manipulation since the value of the actual timestamp can be influenced by the node that creates a block with a transaction running the smart contract code. Nodes accept timestamps within a certain range as block timestamps are flexible. As a result, if a smart contract uses block.timestamp to transfer ETH, miners can exploit the vulnerability by altering the block timestamps themselves, creating favorable conditions for their own success.

Therefore, it is important to avoid relying solely on block.timestamp for critical logic in smart contracts. Instead, other sources of time or randomness should be used to ensure the security and integrity of the contract.

Simple Logic Error#

In the context of smart contracts, simple logical errors are a frequent exploit factor that can arise due to various factors such as typographical errors, specification misinterpretation, or programming mistakes. These types of errors can pose a significant risk to the security and functionality of smart contracts.

To mitigate the risks associated with logical errors, it is crucial to have a thorough understanding of the code base, documentation, and the project's intended functionality and contract specifications. This requires the expertise of experienced smart contract auditors who can accurately identify vulnerabilities that may otherwise go unnoticed. By conducting a thorough audit, potential logical errors can be identified, and appropriate measures can be taken to address them.

Overall, given the potential impact of simple logical errors on the security and functionality of smart contracts, it is critical to engage experienced auditors who possess the necessary skills and knowledge to accurately identify and mitigate these types of vulnerabilities. By doing so, the integrity of smart contracts can be upheld, ensuring the safe and secure execution of transactions on blockchain networks.

Delegate Call#

Delegate call is a type of message call that is very similar to a regular message call. The primary difference is that when using delegate call, the target address is processed in the context of the calling contract, and msg.sender and msg.value remain unchanged.

While delegate call can be useful for allowing other contracts to modify the storage of the calling contract, it is crucial to only use it with trusted contracts, such as those that you have written yourself. This is because delegate call grants significant control over a contract and can lead to vulnerabilities if used improperly. If the target address is provided by a user, it is essential to ensure that it is a trusted contract.

To use delegate call safely, it is necessary to pay close attention to the context in which the code operates. Additionally, it is recommended to use stateless libraries whenever possible to further minimize the risks associated with delegate call. By taking these precautions, it is possible to utilize delegate call in a secure and reliable manner.

Block Gas Limit Vulnerability#

Gas is the fuel that powers transactions in Ethereum. It represents the amount of ether that the sender is willing to spend per unit of gas to have the transaction processed. This is necessary because every transaction is executed by thousands of miners worldwide, and gas is used to limit the amount of resources each transaction can consume.

Smart contracts require a specified amount of gas to be executed, and each block in Ethereum has a gas limit that determines the total amount of gas that can be used by all transactions in the block. If a transaction uses too much gas, it will not be processed and can become a potential vector for a Denial of Service attack.

Data stored in resizable arrays and accessed through looping can also result in gas exhaustion, particularly as the size of the dataset increases during production. This vulnerability is not always caught during testing, but can cause serious problems as the volume of data rises. In some cases, repeated payments may not even be able to recover funds lost due to this type of vulnerability.

One benefit of the block gas limit is that it prevents attackers from setting up infinite transaction loops, as transactions will fail if their gas usage exceeds a predefined threshold. It is important to carefully manage gas usage when developing smart contracts to prevent vulnerabilities and ensure efficient processing of transactions.

Insufficient Gas Griefing#

Griefing is a common attack in video games that can also be used to disrupt intended transactions. It can be executed against contracts that accept data and use it in a sub-call on another contract. This technique is commonly used in multi signature wallets and transaction relayers. If the sub-call fails, the transaction is either reversed or execution is resumed, leaving the contract vulnerable to attacks.

This vulnerability can be found in smart contracts that don't check the gas needed to execute a sub-call or don't verify the returned value of the call. For instance, a smart contract-based game that requires users to send ether to another smart contract to create an object may be vulnerable to an attacker who can trick the contract and avoid payment if it does not verify that the ether was successfully sent.

To prevent insufficient gas griefing, two options are available: allowing only trusted users to relay transactions and ensuring that the forwarder has enough gas. By doing so, smart contract developers can mitigate the risks associated with this type of attack and improve the security of their contracts.

Unexpected Ether#

When Ether is transferred to a contract, it typically triggers a call to a fallback or another contract-defined function. However, there are situations where Ether can remain in a contract without any code execution. Hackers can exploit this vulnerability by forcefully sending Ether to a contract that depends on code execution to spend all the Ether that is sent to it.

This attack is made possible by using the self-destruct function, which can forcibly send Ether to a contract without triggering any code execution. The vulnerability often arises from the incorrect use of the this.balance feature, which can be intentionally manipulated.

To prevent this type of attack, contract logic should avoid relying on the exact values of a contract's balance as much as possible. If the exact values of deposited Ether are necessary, it is recommended to use a self-defined variable that is incremented using payable functions to track the deposited Ether reliably. This will ensure that forced Ether provided via a self-destruct call will have no effect on the variable.

Reentrancy Attack#

Reentrancy is a well-known vulnerability in smart contracts that occurs when a contract calls another contract and then continues execution after the call is completed. This vulnerability is often exploited by attackers who use external calls through callback functions to create a recursive call back to the contract and repeatedly execute the withdraw function to drain a contract's funds.

To avoid this vulnerability, the check-effect-interact pattern should be used. This involves performing checks and modifications to required variables before interacting with internal or external smart contract functions. Additionally, a "reentrancy guard" can be used to prevent this type of attack. This is a simple storage variable whose value is checked before executing the rest of the function logic. By using these techniques, smart contracts can avoid reentrancy attacks and protect against the loss of funds.

Read-Only Reetrancy Attack#

The read-only reentrancy attack is a type of smart contract vulnerability that occurs when a view function is reentered. These types of functions are usually not protected because they are read-only and do not modify the state of the contract. However, if the state of the contract is inconsistent, incorrect values could be returned, which could deceive other protocols relying on these return values and lead to malicious actions.

To prevent read-only reentrancy attacks, reentrancy locks should be implemented in smart contracts. Reentrancy locks are variables that are set to true when a function is executed and then set to false when the function completes. If a function attempts to re enter while the reentrancy lock is still active, it will be prevented from doing so. These locks should be made public so that developers can choose whether or not to revert if the lock is active.

Implementing reentrancy locks can help prevent read-only reentrancy attacks and protect smart contracts from malicious actions. Developers should be aware of this vulnerability and take steps to ensure that their smart contracts are protected against it.

Short Address Attack#

A Short Address attack is a vulnerability that can occur when a smart contract receives less data than expected, and the Solidity language fills in the missing bytes with zeros. This can cause problems for the contract because it can mistakenly take the extra zeros as part of the proper value. It is important to note that this exploit does not directly target Solidity contracts, but rather third-party programs that may interact with them.

To better understand how parameters in contracts can be changed, let's consider how smart contracts send money to specific accounts. The contract requires the address of the account holder and the amount to send, which are encoded in a specific format and sent to the EVM. The EVM receives fewer bytes from the contract and adds zeros to the end locations. This is where attackers can launch a Short Address attack by using smart contracts.

To prevent this type of exploit, it is recommended to remove the onlyPayloadSize modifier and implement security checks on the exchange. However, this can cause issues for some smart contracts. A better solution would be for the EVM to include some verifications on their end. By doing so, the risk of Short Address attacks can be greatly reduced.

Neptune Mutual and the Decentralized Coverage#

It is crucial to take a proactive approach to smart contract security by implementing best practices such as thorough testing and auditing, strong access controls, and proactive monitoring and response to potential threats. By doing so, we can help ensure that the potential of this groundbreaking technology is realized without unnecessary risk or disruption.

Neptune Mutual is a marketplace that provides users with an additional coverage against smart contract risk or custody risk for a fee. The platform offers parametric cover policies that allow users to receive payouts without having to provide evidence of loss. Payouts can be claimed as soon as an incident is resolved through the incident resolution system. This makes it easy for users to get coverage quickly and efficiently.

The marketplace is available on two popular blockchain networks, Ethereum, and Arbitrum, making it accessible to a wide range of users. The platform is designed to provide users with peace of mind when using smart contracts or holding cryptocurrency assets. The coverage provided by Neptune Mutual can help mitigate the financial consequences of unforeseen incidents, such as hacks, bugs, or other risks associated with blockchain transactions.

Two different diversified cover pools have been listed: Prime dApps, consisting of cover products such as Curve Finance V2, Synthetix V2, and Uniswap V2; and Popular DeFi Apps, consisting of cover products such as Sushiswap, Aave V3, Bancor V3, and Compound Finance. There are two different dedicated cover pools: Binance Exchange and OkX Exchange Custody.

Reference Sources Solidity VulnerabilitiesVulnerability Analysis of Smart ContractsHacken