Smart Contracts Vulnerabilities

Redouane OTMANI
Coinmonks
11 min readNov 28, 2022

--

As we have introduced in previous article Ethereum Smart contract and some of basic concepts of Ethereum Blockchain. It’s time for us to bring up the security aspect, since smart contracts process and transfer considerable amount of assets, so in addition to its proper execution, it is crucial that after being implemented still protected against attacks, lets not forget its immutable nature.

In this article, I am going through a bunch of vulnerabilities in smart contracts. Then, in the next few articles, I’ll try going through series of some of famous attacks that exploited these vulnerabilities last years, allowing hackers to steal money or cause other damage, and we’ll see how to prevent and secure, using two attacks detection methods.

Outline of the article

1. Blockchain level

2. EVM level

3. Solidity level

There are multiple vulnerabilities in Ethereum smart contracts that could cause serious security problems, and may occur at different levels Blockchain level, Ethereum Virtual Machine (EVM) level or Solidity level.

Blockchain level
Bad Randomness
Timestamp Dependence
Solidity incorrect blockhash
Transaction-order Dependence
Unencrypted private data On-chain
EVM level
Short address attack
Immutable bugs
Stack size limit
Solidity level
Call control
- Reentrancy
- Message call with hardcoded gas amount
- Call to the Unknown
-
Delegatecall to Untrusted Callee
- Solidity call without data
- Solidity Unchecked external call
Access control
- Protection Issues
-
Unprotected selfdestruct
- Unprotected Ether Withdrawal, Leaking Ether to arbitrary address
- Unexpected Ether
- Authorization through tx.origin
- Missing protection against Sig Replay Attack
- Lack of Proper Signature Verification
- Insufficient signature information (Signature Malleability)
- Hash collisions with MVLA
- Visibility Issues
-
Functions Default Visibility
- State Variable Default Visibility
Data control
- Arithmetic Issues
-
Integer overflow/underflow
- Array overflow
- Ether lost (Frozen Ether, locked money)
Resource control
- DoS
-
DoS with Failed Call
- DoS With Block Gas Limit
- Code With No effects
- Presence of unused variables
- Insufficient Gas Greifing
Solidity Programming Issues
- Tool control
-
Outdated compiler version
- Floating or No Pragma
- Uninitialized Storage Pointer
- Use of Deprecated functions constructions
- Erroneous constructor name
- Variable Shadowing
- Write to Arbitrary Storage Location
- Incorrect Inheritance Order
- Typographical Errors
- Forcing Ether to
Exception Handling
-
Assert Violation
- Requirement Violation

Well, I’ve tried my best to draw it so you can see it more clearly.

Smart contracts Vulnerabilities

Lets go through some of these vulnerabilities, most famous to be more accurate.

1. Blockchain level

1. 1 Transaction-order Dependence

Each block contains a set of transactions, while the state of the blockchain is updated several times, the state of a smart contract is determined by the value of its fields and the current balance. Furthermore, the actual state of a smart contract is unpredictable for any user when it is being called via a transaction by a user. As a result, there is no guarantee that the transaction will be in the same state as the contract when it is initialized.

If a block includes two transactions that invoke the same contract at the same time, then users will not be able to know which state the contract is in at the time their individual transactions were/will be executed.

As shown above, if user 1 and user 2 send transaction Ti and Tj to a smart contract t the same time t, respectively, both users do not know at what state the contract is in when the first transaction is executed. In addition, the order of these transactions is determined by only the miners of th block. Even if user 1 sends transaction Ti before user 2 send Tj. There is absolutely no guarantee that Ti will be executd before Tj. If Ti is executed first, the state of the contract will change from state S to state Si; But, if Tj is executed first, the state of the contract will change from state S to state Sj. Therefore, the final state of a contract depends on the order of execution of the transactions, which is determined by the order of block mining.

Exploiting this vulnerability would be critical in real-world situtations where buyers and sellers use smart contracts for their financial transactions. This is because sellers often update the price of their items for sale, and buyers send their purchase requests with the prices they observed when sending the transaction. As a result, buyers may have to spend much more than expected in the case of an attack that exploits transaction order dependency.

How ever, this problem could be avoided by using a transaction counter to “lock in” the initial price agreed on.

Transaction-order Dependence

1. 2 Timestamp Dependence

To perform any operation on Ethereum Blockchain such as Ether transfer, a smart contract receives a timestamp that specifies the time when the block was generated. This timestamp is set by the miners according to the time of their local system. Therefore, it can be manipulated and modified by’em easily.

This vulnerability occurs when a smart contract uses the block timestamp as a part of the trigger condition for the execution of critical operation or as a source of random generator. A malicious miner could adjust the provided timestamp by a few seconds, thus changing the output of the contact to their own advantage.

The timestamp dependency is due to the fact that Ethereum only requires that a timestamp must be greater that that of its previous block and within 900 seconds of the current clock’s future. Thus, if a contract uses a timestamp-based condition to determine whether or not to transfer money, a malicious miner can slightly shift the timestamp to satisfy the condition for the attacker’s benefit. To avoid this vulnerability, the authors suggest using the block index instead, because it is incremental and is protected from manipulation.

2. Ethereum Virtual Machine (EVM) level

2.1 Immutable bugs

One of the characteristics of smart contracts is that they are immutable. They cannot be modified or deleted after being deployed in the blockchain. This also implies their behavior will be the one expected by the users, since it is guaranteed by the consensus protocol. However, if the contract contains a bug, there is no way to fix it, for this reason, it’s important to educate programmers and developers to design more secure contracts and provide ways to modify or terminate(or destroy -to be more accurate-) them during implementation.

2.2 Stack size limit

Each time a contract invokes another contract or itself, the call stack associated with the transaction increases by one frame. Since the call stack is limited to 1024 frames, a new invocation beyond this limit will cause an exception. This vulnerability was addressed by hard work on the Ethereum blockchain and resulted in a “fork” that redefined how call gas consumption is calculated. Thus, a caller can allocate at most 63 units out of 64 : Since, currently, the gas limit per block is 4.7M units, this implies that the maximum reachable depth of the call stack is always less than 1024.

3. Solidity level

3.1 Reentrancy

Reentrancy was first observed in 2016 during the attack on the DAO. It occurs when external contract calls are allowed to make new calls to the calling contract before the initial execution is complete.

When the contract does not update its state before sending funds, an attacker could recursively call the target’s fund withdrawal function. Therefore, this allows the attacker to bypass the validity check until the caller’s contract is drained of all its ether or the transaction runs out of gas.

This vulnerability is due to the structure of the fallback function allowing an attacker to repeatedly call the caller’s function also by the lack of a gas limit when transferring the control flow to another contract.

However, this can be avoided by ensuring that the state variables of the contract are updated before calling another contract. This is done by introducing a “mutex” lock on the state of the contract to ensure that only the owner of the lock can change the state and using the transfer method to send money to other contracts because this method only transmits 2300 gas to called contract.

Note that there are two main types of Reentrancy : Single function reentrancy and cross function reentrancy.

Single-function. Is simpler and easier to prevent that cross-function reentrancy. It occurs when the vulnerable function is the only function the attacker is trying to call each time.

Cross-function. Is more difficult to detect. It occurs when a vulnerable function shares a state with another function that has an advantageous effect for the attacker.

3.2 Mishandled exceptions

In Ethereum, there are several ways for one contract to call another, for example, via sending an instruction or directly calling a function of the contract itself.

This vulnerability occurs when exceptions are not handled properly. Indeed, depending on how the call is made, the exception in the called contract may propagate to the caller. These inconsistencies in exception propagation open the door to new vulnerabilities that can be exploited by malicious users who invoke calling contracts by causing their sending functions fail. In addition, exceptions can be caused by many situations, such as running out of gas, exceeding the call stack limit, unexpected system error in the called node. Thus, one way to address this problem is to set an upper limit on the gas usage in a call.

3.3 Gasless send

Sending without gas causes a transaction to fail if not enough gas is supplied for a specific call. Since the maximum gas limit on the network can vary over time based on transaction charges, it is important to provide an outage exception based on gas consumption. It is also important to develop functions that do not require too much gas.

3.4 Unchecked external call

Some external calls in Solidity, such as “send()”, “call()”, “delegatecall()” and “staticcall()” never throw exceptions, they only return a Boolean “false”. As a result, the execution of the contract will continue even if the call encounters a problem, if it fails accidentally or if an attacker forces its failure.

In order to solve this problem, it is recommended that the return value must be check manually by the developer, he can also use the “transfer()” function.

Vulnerable and not vulnerable code to Unchecked external call

3.5 Authorization through tx.origin

tx.origin is a Solidity variable that returns the address of the account that sent the transaction. Using this variable for authorization could make a contract vulnerable if an authorized account calls a vulnerable contract. A call could be made to the vulnerable contract that passes the authorization check since tx.origin returns the original sender of the transaction which, in this case, is the authorized account. To avoid this problem, it is recommended to use “msg.sender” instead of “tx.origin” authorization.

Vulnerable code using “tx.origin”

3.6 Unprotected selfdestruct

The “Selfdestruct” instruction destorys a contract on the blockchain and sends the contract balance to the specified address. It is so useful for destroying contract that are no longer in use or vulnerable contracts ( bugs, loopholes, ..). However, if it does not have proper access controls malicious parties could use it to self-destruct the contract.

3.7 Functions and Variables Default Visibility

Functions that do not have a specific visibility type are considered “public” by default. This can lead to a vulnerability if a developer forgets to set a visibility then a malicious user manages to make unauthorized or unintended state changes. To avoid this issue, it is recommended that the appropriate visibility must be specified based on the function. Note that functions can be specified as : “external”, “public”, “internal” and “private”.

3.8 Integer overflow and underflow

This occurs in transactions that accept unauthorized data or values (for examples, data that exceeds the 256-bit limit). This is because since Solidity can only handle numbers up to 256 bits, increasing (or decreasing) a number above (or below) the maximum (or minimum) value can result in overflows (or underflows). In addition, this vulnerability is due to the fact that the Solidity source code does not perform proper validation on numeric inputs, and neither the Solidity compiler, nor the EVM enforces integer underflow or overflow detection.

It’s therefore recommended to use the OpenZepplin SafeMath library to mitigate such attacks.

Vulnerable source code example : Integer overflow and underflow

3.9 Ether lost (Frozen Ether, locked money)

Contracts programmed to receive ether must implement methods such as “transfer”, “send” or “call.value” in order to withdraw ether. Therefore, if the contract does not implement these methods, it will cause a money block!

The source code below shows an example of contract programmed to receive ether but not to withdraw it.

Vulnerable source code example : Ether lost (Frozen Ether, locked money)

3.10 Denial of service (DoS)

An attack of this type makes the smart contract inaccessible to its users. It can be due to a costly loop, an infinite loop, underpriced operations, call failures or a gas limit.

3.10.1 A costly loop

These are loops containing costly operations that can drain a contract of all its gas.

3.10.2 An infinite loop

A bug or a programming error that can cause a possible infinite loop.

3.10.3 Under-Priced Operations

In general, the value of gas is proportional to the resources consumed by the operation. However, some operations known as “IO-heavy operations” underestimate this value, causing them to be executed in quantity in a single transaction. This way an attacker could launch a DOS attack on the contract or on the Ethreum blockchain. This problem has been solved by changing the values of the different operations.

3.10.4 DoS with Failed Call

External calls can fail accidentally or deliberately, which can result in a vulnerability that allows a DOS attack.

To minimize the damage caused by such failures, it is recommended to isolate each external call into its own transaction that can be initiated by the call recipient.

Vulnerable source code example : DOS with failed call

3.10.5 DoS With Block Gas Limit

When smart contracts are deployed or their internal functions are called, the execution of these actions requires a certain amount of gas. Depending on the computation power required, Ethereum specifies a gas limit per block, and the sum of all transactions included in a block cannot exceed a certain threshold.

Programming patterns, which are harmless in centralized applications, can lead to denials of service in smart contracts when the cost of executing a function exceeds the gas limit per block. Moreover, the modification of an array that size increases with the time can lead to a denial of service.

It is recommended to take into consideration arrays that grow with time as well as to avoid actions that require a loop on the whole data structure.

[Credit to Mélissa]

If you appreciate, please leave a comment, and follow me to get notified whenever I write new articles, you can also follow me on Twitter :)

--

--