9 smart contract vulnerabilities and how to mitigate them
Smart contracts execute tasks automatically when specific events occur, and often handle large data and resource flows. This makes them particularly attractive to attackers.
Smart contracts execute processes, transactions and other tasks when specific events, conditions and logic are met, depending on how they are programmed. Smart contracts are deployed on a blockchain, such as Ethereum or other distributed ledger infrastructure, where they listen for events and updates from cryptographically secure data feeds called oracles. These contracts often control the flow of large amounts of valuable data and resources, such as transferring money, delivering services and unlocking protected content. This naturally makes them an attractive target to malicious actors.
Security must be a top priority when designing and developing a smart contract. Once a smart contract is deployed to a blockchain, it is difficult or impossible to patch; it must be removed, recreated and redeployed. Plus, vulnerabilities in a smart contract will be accessible to anyone once the smart contract is on a blockchain.
When deploying a smart contract written in Solidity on the popular smart contract platform Ethereum, development teams need to be especially aware of the following attack vectors and how to eliminate them.
1. Reentrancy attacks
Reentrancy attack vectors exist because Solidity smart contracts execute imperatively: Each line of code must execute before the next one starts. This means that when a contract makes an external call to a different contract, the calling contract's execution is paused until the call returns. This effectively gives the called contract temporary control over what happens next, creating the possibility of an infinite loop.
For example, a malicious contract could make a recursive call back to the original contract to withdraw resources without waiting for the first call to complete, so the original contract is never allowed to update its balance before the function completes. Various forms of reentrancy attacks exist, including single-function, cross-function, cross-contract and read-only reentrancy attacks. A list of exploits is maintained on GitHub.
Fix: This vulnerability occurs when the code logic of a smart contract is flawed. Developers need to carefully design external calls and always check and update the contract's state, such as decreasing the Ether balance before fulfilling requests to send funds. Adding a reentrancy guard can prevent more than one function from being executed at a time by locking the contract. Various audit tools, such as Slither, Mythril and Securify, can check for the presence of the different types of reentrancy vulnerabilities.
2. Oracle manipulation
Smart contracts access and consume external data from outside the blockchain via an oracle. This lets them interact with off-chain systems, such as stock markets. Incorrect or manipulated oracle data can erroneously trigger the execution of smart contracts; this is known as the oracle issue. Many decentralized finance applications have been exploited using this method, the favorite being a flash loan attack. Flash loans are essentially unsecured loans with no limit as to how much can be borrowed so long as the loan is repaid in the same transaction. Attacks use these loans to distort asset prices to generate profits while still abiding by a blockchain's rules.
Fix: Using a decentralized oracle, such as Chainlink or Tellor, or even multiple oracles, is the easiest way to ensure a contract receives accurate data. Such oracles make it harder and more expensive for an attacker to interfere with the data.
3. Gas griefing
To perform a transaction or execute a smart contract on the Ethereum blockchain platform, users must pay a gas fee. It is paid to incentivize validators (miners) to commit the resources needed to verify transactions. The price of gas is determined by supply, demand and network capacity at the time of the transaction.
Gas griefing occurs when a user sends the amount of gas required to execute the target smart contract but not enough to execute subcalls -- calls it makes to other contracts. If the contract does not check if the required gas to execute a subcall is available, the subcall will not execute as expected. This can have a significant effect on the application's logic.
Fix: No effective technique to prevent gas griefing exists. All a developer can do is code the contract so it sets the amount of gas to be sent, not the user. A rise in gas costs, however, could mean that the transaction fails.
4. Transaction order dependence attacks (frontrunning)
Smart contracts are publicly visible from the moment they are submitted to the network as a pending transaction. This enables a miner of a block to select the transaction with the highest gas fees. For example, users can include a priority fee -- a tip -- to incentivize miners to prioritize their transaction ahead of other transactions in the same block. However, this also enables attackers to watch for opportunities where they can front run profitable contracts by submitting an identical contract, but with a higher gas fee, so their contract is processed first. Because these attacks have to be implemented in fractions of a second, they are usually performed by bots or miners themselves.
Fix: These attacks are tricky to avoid. One option is to only accept transactions with a gas price below a predetermined threshold. Or use a commit-and-reveal scheme that involves a user first submitting a solution hash instead of the cleartext solution so the solution can't be viewed by potential frontrunners until it is too late. Various smart contract audit tools can detect if code introduces frontrunning vulnerabilities.
5. Force-feeding attacks
Force-feeding attacks take advantage of the fact that developers cannot prevent a smart contract from receiving Ether, Ethereum's native cryptocurrency. This makes it easy to transfer Ether to any contract -- force-feeding them -- to change the balance of Ether it holds and thereby manipulate any function logic that solely relies on the expected balance for internal accounting, such as paying out a reward if a balance increases above a certain level.
Fix: It is impossible to stop contract balance manipulation in this way. A contract's balance should never be used as a check or guard within a function because the actual Ether balance may be higher than the balance expected by the contract's internal code.
6. Timestamp dependence
Timestamp values are generated by the node that executes the smart contract. Due to the distributed nature of the Ethereum platform, it is almost impossible to guarantee that the time on every node is correctly synchronized. A node can then manipulate the timestamp value it uses to craft a logic attack against any contract that relies on the block.timestamp variable to execute time-critical operations.
Fix: To avoid this vulnerability, developers should not use the block.timestamp function as a control or logic check, or as a source of randomness.
7. Denial of service
Like any online service, smart contracts are vulnerable to DoS attacks. By overloading services, such as authentication, an attacker can block other contracts from executing or generate unexpected contract reverts, for example, where unused gas is returned and all state is reverted to the state before the transaction began to execute. This can result in auction results or values used in financial transactions being manipulated to the attacker's advantage.
Fix: Making these attacks costly for attackers is the best way to deter them. Time-lock puzzles and gas fees are just some of the ways of increasing an attacker's costs. Ensuring calls are only made to trusted contracts also reduces the likelihood of a DoS attack causing serious problems.
8. Integer underflows and overflows
Integer underflows and overflows occur when the result of an arithmetic operation falls outside the fixed-size range of values: 0 to 255 in the case of integer type uint8. Values higher than 255 overflow and are reset to 0, while values lower than 0 reset to 255. This causes unexpected changes to a contract's state variables and logic and triggers invalid operations.
Fix: Since version 0.8.0, the Solidity compiler no longer allows code that could result in integer underflows and overflows. Check any contracts compiled with earlier versions for functions involving arithmetic operations or use a library, such as SafeMath, to check for underflow and overflow issues.
9. Information and function exposure
Blockchains are accessible to anyone. Confidential or sensitive information should never be saved to a blockchain unless it is encrypted. Variables and functions within a smart contract can also be visible and accessible to other smart contracts, which leaves them open to possible misuse or abuse.
Fix: Developers should always implement proper access controls and use the principle of least privilege by using Solidity's variable and function visibility modifiers to assign the minimum level of visibility as is necessary and no more.
Keeping smart contracts vulnerability free
For smart contracts to be smart and secure, development teams must build in security from the start and rigorously test their logic and code execution.
Contract code is difficult to patch after it is deployed. Getting security right the first time is imperative. Always follow smart contract security best practices. Unless the development team includes dedicated smart contract security specialists who can audit smart contract code for logic flaws and other vulnerabilities by unit testing each function, use an auditing service specializing in smart contracts to identify any security issues.