Security of blockchain-based smart contracts II – Known Vulnerabilities and Pitfalls

In the previous part of this series on blockchain security we looked at the risks associated with deploying autonomously executing smart contracts on a public blockchain. We also introduced some high-profile examples of attacks on smart contracts that have caused the loss of large sums of money and changed the way we look at business interactions on the blockchain.

In this episode we will review some known issues and vulnerabilities.

Private Key Leakage

Using unsafe private keys is really a case of user error, rather than a vulnerability. However, we mention this nevertheless, as it happens surprisingly often, and certain players have specialized in stealing funds from unsafe addresses.

What usually happens is that development addresses (such as those used by testing tools, such as Ganache/TestPRC) are used in production. These are addresses generated from publicly known private keys. Some users have even unknowingly imported these keys into wallet software, by using the original seed words used in private key generation.

Attackers are monitoring these addresses and any amount transferred to such an address on the main Ethereum network tends to disappear immediately (within 2 blocks).

This highly lucrative “sweeping” activity has been investigated in this interesting study, which found that one sweeper account had managed to accumulate funds worth $ 23 million.

Reentrancy and Race Conditions

Reentrancy vulnerabilities consist in unexpected behavior, if a function is called various times before the execution has completed.

Let’s look at the following function, which can be used to withdraw the total balance of the caller from a contract:

The call.value() invocation causes contract external code to be executed. The caller may be assumed to be a cryptocurrency wallet software, but can also be another contract.

If the caller is another contract, this means that the contracts fallback function is executed. A fallback function’s purpose is receiving funds.

A rogue contract, implements a callback function that calls payOut() again recursively, before the balance is set to 0, thereby obtaining more funds than available.

The solution to this is to use the alternative functions send() or transfer(). These prevent recursive calls, by forwarding just enough gas for some basic bookkeeping and any attempt at calling payOut() again would fail. Alternatively (or additionally), the order of operation may be reversed, i.e. setting the balance to 0 before making the money transfer.

The DAO attack mentioned in part I used a variation of this vulnerability.

Under-/Overflow

Balances are usually represented by unsigned integers, typically 256-bit numbers in Solidity. When unsigned integers overflow or underflow, their value changes dramatically. Let’s look at the following example of a more common underflow (numbers shortened for readability):

0x0003
– 0x0004
———–
0xFFFF

It’s easy to see the issue here. Subtracting 1 more than available balance causes an underflow. The resulting balance is now a large number.

Also note, that in integer arithmetic division is troublesome, due to rounding errors.

The solution is to always check for under- or overflows in the code. There are safe libraries to assist with this, such as SafeMath by OpenZeppelin.

Transaction Ordering Assumptions

Transactions enter a pool of unconfirmed transactions and maybe included in blocks by miners in any order, depending on the miner’s transaction selection criteria, which is probably some algorithm aimed at achieving maximum earnings from transaction fees, but could be anything.

Hence, the order of transactions being included can be completely different from the order in which they are generated. Therefore, contract code cannot make any assumptions on transaction order.

Apart from unexpected results in contract execution, there is a possible attack vector in this, as transactions are visible, and their execution can be predicted. This maybe an issue in trading, where delaying a transaction may be used for personal advantage by a rogue miner. In fact, simply being aware of certain transactions before they are executed can be used as an advantage by anyone, not just miners. Transactions can be “overtaken” by paying a higher gas price, incentivizing miners to include them rapidly

Timestamp Dependencies

In blockchains, timestamps are generated by miners. Therefore, no contract should rely on the block timestamp for critical operations, such as using it as a seed for random number generation. Consensys give a 12 minutes rule in their guidelines, which states that it is safe to use block.timestamp, if your time depending code can deal with a 12 minute time variation.

Conclusion

So far, we have seen various high-profile cases of attacks on blockchain-based smart contracts. We also discussed some common vulnerabilities which were exploited in the past. In an upcoming post we will cover some more complex attacks that rely on the way the blockchain and specific operations work.