Security issues in smart contracts and how to solve them

Drawing of 4 people surrounding a very large laptop computer, one of them holding a pen and is signing a contract on the keyboard area of the laptop
Blockchain is often promoted as a distributed and immutable database, and it is generally viewed as very secure. While blockchain protocols may indeed be secure, one only needs to perform a quick search online to find a staggering amount of articles about hacks, scams and stolen cryptocurrencies. Many of these intrusions have to do with hacked exchanges that store user keys. By stealing the keys from the database of these exchanges, the hacker can steal users’ cryptocurrencies, which is why service providers generally recommend storing your own keys.

Similarly, cryptocurrencies are stolen by hackers using phishing attacks on Telegram, Slack, and Reddit. In all of these cases, the issue does not lie on blockchain security flaws, but rather on poor key management by part of the user. So, are there common security issues in applications built on top of the blockchain protocol? As you can imagine there are. In this blog, we will focus primarily on smart contracts built on public blockchains like Ethereum. We will cover the hack that resulted in the DAO collapse of 2016 - which was recently exploited in a somewhat more sophisticated way. We will use some code in this blog, so you’ll need a basic, technical understanding of blockchain.

Overflow and underflow

The first type of security flaws we discuss here are overflow and underflow bugs. Overflow and underflow bugs are infamous bugs that are not just problematic in smart contracts, but also in traditional systems. These kinds of bugs have to do with the fact that computers don’t understand the concept of infinity. When creating a number, we have to define a maximum. For example, uint256 is an unsigned integer with a size of 256 bits, meaning the minimum of the integer can be 0 and the maximum 2256-1. An underflow occurs when trying to read a number smaller than the minimum - in this case, 0. When a computer tries to read -1, rather than correctly interpreting -1, it will read the maximum value as 2256-1 instead. For -2, this would be the maximum value -1, and so on... Alternatively, if a number is higher than the maximum, for instance 2256, it will read 0 rather than the maximum.

Real-life examples of these bugs can be found in the computer game Civilization. In this strategy game, there was a number which basically resulted in a computer-controlled character (personified as Gandhi) being either very peaceful or a complete warmonger. Due to an underflow bug, the computer-controlled Gandhi, who was supposed to be very peaceful, would become a warmonger instead. In this case, the bug is kind of funny. But these bugs can have very serious consequences too. For instance, an overflow bug resulted in a SpaceX rocket crash due to a miscalculation of the landing angle.

For SpaceX, this bug resulted in a lot of damage. Most of the time, one can simply freeze the system, find the bug and fix it. With blockchain, this is not as simple. Since many blockchain projects contain a lot of money in the form of cryptocurrencies, these types of bugs can quickly result in big losses. A real-world example happened 2 years ago with the POWH token. With the POWH token, holders can accumulate Ethereum as a dividend which is stored on the POWH contract. To withdraw the Ethereum dividend from the POWH token, the contract had a programmed condition:

balances[msg.sender] – _value >= 0.

In other words, if the user tried to withdraw a dividend from the contract, it would first check if their balance was higher or equal to the amount they tried to withdraw. A hacker quickly noticed an underflow bug that made this always yield true. As a result, the hacker was able to withdraw all the Ethereum stored in the contract, which resulted in 2.000 Ethereum being stolen. At the time, little could be done since the blockchain cannot just be shut down by a central administrator, like a traditional database. Currently, POWH uses a different contract that does not contain this bug.

Short address attack

Another example of an underflow is a short address attack. This happened in some Ethereum wallets containing ERC20 tokens managed by exchanges. This type of attack has to do with functions such as Send (address, uint, string). In this kind of attack, the hacker looks for a generated address that ends with a zero, for example 0x1234567890123456789012345678901234567800. Under the hood, the arguments of address and unit are passed around, such that when inserting the address without the zero at the end, one byte of the leading zeros from the amount is given to the shortened address. This leaves us with the same address we started with, so tokens sent there will be transferable. When the parser is getting to the end of its bytes, however, it has an underflow since there aren’t enough bytes left to make a uint256 for the amount. So, it just adds zeros at the end. This means that if the hacker can find an exchange with 256.000 tokens, he can send 1.000 tokens to this generated address. When withdrawing these 1.000 tokens, he can fool the Ethereum Virtual Machine (EVM) into returning 256.000 tokens instead. The reason this attack is possible is basically a design flaw in the EVM. Luckily, there is an easy fix to prevent such attacks. Namely, by checking whether the transfer function has the correct length using: len(msg.data). When an attacker tries this attack, it will result in an error.

Race condition attack

The next vulnerability we want to discuss are race condition attacks. A race condition attack is a transaction ordering dependency attack. As the name suggests, this has to do with transaction ordering in a database. Consider a digital marketplace where someone tries to sell a laptop for $500. A potential buyer finds this laptop on the marketplace and accepts this price to buy the laptop. Most marketplaces contain the functionality for the seller to update the price. Herein lies a problem, since databases do not process transactions instantly. In other words, when a buyer accepts an offer, it takes some time before this action is processed and correctly updated in the database. The seller has a small time window to raise the price shortly after the buyer has accepted the offer. By default, the database doesn't really care about the order in which it has to process these transactions. So, the order in which these actions are processed can result in the buyer spending $500 or $1.000. In a traditional database, this is unlikely to occur since transactions are processed in the order of milliseconds, leaving a very small time window for the seller to perform such an attack. On a typical public blockchain, however, finalising a transaction often takes several minutes. Thus, it is more likely for such an attack to occur. To make matters worse, a seller can actually influence the processing time of their transaction to update the asking price in the blockchain. This is because in the Ethereum blockchain transactions are processed by miners and people pay a miner fee for every transaction. Miners, for obvious reasons, typically process transactions with a higher mining fee first. So, if the seller pays a much higher mining fee than the buyer, his transaction to raise the price of the laptop may likely be processed first. A race condition attack is easy to prevent by building in a transaction counter to lock in the price. In practice, developers often overlook the issue when working on a blockchain project, as it’s not as common in traditional applications.

Re-Entrancy attack

Another type of transaction ordering dependency attack is the re-entrancy attack. This attack is somewhat more sophisticated. Re-entrancy attacks can occur when a contract calls an unknown external contract. When calling an unknown external contract it is possible for that contract to take over the control flow, and make changes to your data that the calling function wasn’t expecting.  A re-entrancy attack can take many forms. One of the most famous re-entrancy attacks led to the collapse of the DAO in Ethereum back in 2016. To understand how a re-entrancy attack works, let’s first look at a piece of code

function withdrawBalance() public {
bool result = msg.sender.call.value(balances[msg.sender]) ();
if (!result) {
       throw;
}
Balances[msg.sender] = 0;
}

This piece of code is at the basis of a basic DAO contract with a re-entrancy vulnerability in the withdrawBalance() function. The withdrawBalance() function is a function without arguments. In Ethereum, it’s also called a fallback function. A fallback function is triggered when the contract receives plain ether without any other kind of data associated with the transaction. Many Ethereum contracts have a fallback function that acts to protect the user. In this example, it makes sure the contract is able to send received ether back to the sender. After the sender has received the ether, their balance is set back to zero to make sure they can’t continuously call this function to withdraw even more. 

The vulnerability lies in the fact that call.value accepts maximum gas. This allows the attacker to hijack the external call by forcing the contract to execute further code, including callbacks into itself. Hence the term re-entry, since the code re-enters the contract.

The hacker is able to create a contract that initiates the withdrawBalance() function and then uses a fallback function like this:

function () public payable {
       if (address(this).balance < 99999 ether) {
               callWithdrawBalance{msg.sender);
        }
}

What happens is that the attacker first sends some Ether from his created contract to the DAO and then uses this contract to initiate the withdrawal function to ask for a legitimate withdrawal. The Ether is then sent back to the attacker's contract, which will trigger his fallback function to ask for another withdrawal, hence repeat. This works since the WithdrawBalance() sets the balance to zero until it finishes execution. The attacker contract makes sure that the execution is considered finished only after the recursive calls have taken place. In this case, when either 99.999 ether has been withdrawn or when the DAO contract no longer contains any ether. An attack like this could have been easily prevented if we would have used a payable modifier instead of using call.value. This way, executing the attacker contract would require too much gas. There is an even better way to protect our contract by zeroing out. If we put the balance of the attack to zero prior to the execution, repeated calls would result in recursive calls to attempt withdrawing zero ether. 

Recently, on April 19, 2020,  a re-entrancy attack was used to steal 25 million worth of cryptocurrency from an exchange platform called Lendf.me. The attack exploited a bug in a ERC777 token. In this case, the attacker re-entered a vulnerable function of the ERC777 token, which basically resulted in the Lendf.me exchange buying ETH from the attacker for a higher price. If you want to read more about this exploit,  click here. Luckily, the stolen currency was returned in this case.

Conclusion

The blockchain protocol is very robust and nearly impossible to manipulate, but as the vulnerabilities in this article illustrate, this presents a problem in itself. Because the protocol is hard to manipulate, applications built on the blockchain can’t be frozen when a bug is found. Since blockchain contracts often contain lots of cryptocurrencies, these bugs can quickly result in the theft of millions of dollars. It is therefore extremely important to be extra careful when developing on blockchain. Let others review your code. It is also recommended to deploy a beta version on a test net first, and award bug bounties to find and fix bugs before going live. Even if your contract doesn’t have any bugs, external contracts being used by your contract might have them, like an ERC20 or ERC777 token. Make sure to use as few external calls as possible and build in safety measures to deal with the unexpected. If you are interested in smart contracts or want your contract reviewed, feel free to contact us!

Image of a computer screen with an envelop on it and a paper plane flying around it