9 Most Common Smart Contract Vulnerabilities Found By Blaize
Smart contracts are specialized programs stored on a blockchain typically used to automate the execution of an agreement so that all counterparties can be certain of the outcome without the need to trust one another or any intermediaries. A smart contract guarantees that its execution will correspond exactly to the logic that was originally written in it. And after the execution of said predetermined logic, the final state on the network will stay immutable. In this article, we will tell about the most common smart contract security issues and weaknesses.
But unfortunately, the correct execution of the smart contract code cannot guarantee its complete safety. In fact, analysis of existing smart contracts shows that a significant part of them are indeed vulnerable.
Ethereum blockchain is the first and, for the time being, the most used platform for smart contracts implementation. Being programmable, the platform allows its users to implement processes of varying complexity. The code for these contracts is stored in a distributed ledger and executed by the Ethereum Virtual Machine (EVM) – the core of this blockchain platform.
Smart contracts vulnerabilities
Security issues in smart contracts are serious enough for several reasons:
- Most of the smart contracts deal with financial assets;
- Errors in smart contracts, once published, cannot be corrected due to the immutable nature of the blockchain;
- Changes in blockchain’s state generated by transactions of faulty or fraudulent contracts cannot be rolled back.
The main reason why there are vulnerable smart contracts could be boiled down to an incorrect understanding of edge cases of the implemented logic in connection with the platform.
Types of smart contracts vulnerabilities
See the full list of smart contract security services on Blaize.Security webpage.
The list of the most common security issues contains:
01. Indirect execution of unknown code
A possibility of indirect execution is there due to the presence of the fallback function feature in smart contracts. There are several reasons why this function can be called:
- Calling a function of another contract using ABI: if there is a typo in the signature string passed for encoding, or a function with such a signature does not exist, then the fallback function will be called;
- Deposit to another contract generates a call to its fallback function;
- Calling a function of another contract using the API: if the developer makes a mistake when declaring the interface of the called contract (for example, mixes up the type of a parameter), then the fallback function will be called.
In Ethereum, calls to functions of other smart contracts occur synchronously. That is, the calling code waits for the end of the execution of the external method before continuing its own work. This can cause the called contract to use the intermediate state of the calling contract.
This situation is not always obvious during development, if possible fraudulent actions on the part of the called contract are not taken into account. Let’s consider the following example.
Suppose there is a vulnerable contract that tops up the balance of the caller by 10 Gwei one time and writes its address into a variable to avoid double-spending. At each next call, the contract checks whether the balance has been topped up at this address earlier, in which case another deposit does not occur. In order to steal all the Gwei available on this contract, one should simply re-enter the pay function before the address is written into the variable. Since the call function is used for deposit, re-entry can be performed by writing the appropriate logic in the fallback function of the fraudulent contract.
For a successful attack, one needs to place such a contract on the network and call the pay function of the vulnerable contract, passing the address of the fraudulent contract as an argument.
Read also: Basics of Liquidity Vampire Attacks.
03. Incorrect calculation of the output token amount
Most modern DeFi smart contracts deal with enormous amounts of money depicted in tokens or ETH value. Thus, a lot of operations in contract logic are connected with tokens’ transfers to and from the contract. It creates a wide field for different mistakes connected to correct percentages, fees, and profits calculations. Top of such errors are:
- incorrect decimals handling, especially when dialling with such token as USDT;
- incorrect order of operation during fees calculations, which leads to the significant accuracy loss;
- actually forgotten accuracy constant in the mat operations
All these errors lead to lost users’ funds or even tokens locked forever. Thus, one of the tasks of the contracts’ auditor is to check the correctness of the math operation. Such checks are also implemented in the most modern automated tools and static analyzers.
04. Interface / naming issues
An example of this is the Rubixi smart contract. Rubixi was a Ponzi game, in which its owner could transfer the fees accumulated in the financial pyramid to himself. Usually, the owner is set in the constructor of the contract, which is called when it is created, the same logic was implemented in the Rubixi smart contract. It should be noted that in the Solidity versions prior to 0.4.22, constructors were defined as functions with the same name as the name of the contract. At some point, the contract was renamed from Dynamic Pyramid to Rubixi, but the developer forgot to change the name of the constructor, so anyone who called the Dynamic Pyramid function could become the owner of a contract and could steal the accumulated funds.
05. Dependency on the order of execution
The state of a contract is determined by the values of its variables, which are changed by calling its functions. Calling a smart contract function is the same transaction as a transaction of ETH or ERC-20 token transfer. These transactions are finalized by the network only after the next block creation is complete.
Thus, when a user sends a transaction to call a contract function, he cannot be sure that the transaction will be executed in the same state of the contract in which he was at the time of sending. This can happen because other transactions in the same block have changed the state of the contract.
Moreover, miners have some freedom in ordering transactions when forming a block, as well as in choosing to include a particular transaction in a block. In some cases, the impossibility of determining the state of the contract, in which the transaction will be executed, can cause vulnerability of the contract itself.
It also becomes especially dangerous to interact with contracts written in such a way that their behavior can be changed over time.
06. Time component
Sometimes the logic of smart contracts can be time-dependent. The time for a contract is only available in the context of a transaction. The timestamp of a transaction, in turn, is equal to the label of the block in which it is included. Thus, consistency with the state of a smart contract is achieved.
However, this also creates an opportunity for the miner to abuse his/her position due to some freedom in setting a timestamp for the block. So, the miner has some advantage over other parties to a contract that he/she could exploit to his/her own benefit.
07. Using the blockhash function
Using the blockhash function is similar to the timestamp dependency, it is not recommended to use it for important components for the same reason as with the timestamp dependency, because miners can manipulate these functions and change the withdrawal of funds in their own favor. This is especially noticeable when block hash is used as a source of randomness.
08. Incorrectly handled exceptions
There are many situations where an exception can be thrown in Solidity, but the way these exceptions are handled is not always the same. Exception handling is based on interactions between contracts. Thus, contracts are vulnerable to attack from malicious users, if these exceptions are not handled properly, transactions will be rolled back.
09. Incorrect work with ERC20 token
There is a well-known OpenZeppelin implementation of the ERC-20 token, overused in modern protocols. In most cases, it is completely applicable, and its functionality is enough for correct financial operations. Though there is a place for custom implementations of a token standard. Thus, it creates a place for discrepancies between the newly created token and actual ERC20 standard – small inconsistencies like missing return value in transfer() function. Though, such a small change may lead to the non-functional method of the contract, since it will not be able to recognize the interface. It is a very tiny mistake, almost not noticeable during testing, but in production, it leads to stuck funds and blocked contracts.
The bottom line
Writing a completely secure smart contract is a complex and painstaking task. Plus, the developed contracts require a thorough audit before being published on the network. The attack examples presented in this article confirm that the common reason for the vulnerability of smart contracts is the semantics of programmable blockchain platforms that are not yet familiar to all developers.
That’s why it is of vital importance to pick the right development team for your smart contract creation. Blaize.Security has an in-depth understanding and wide experience in the field. Let’s build secure projects together!