The Most Common Smart Contract Hacks & Prevention Tips for Developers

Welcome to the world of Decentralized Finance (DeFi) where trust is replaced with code and billions of dollars are at stake. But just like with any other financial system, DeFi is not immune to cyber-attacks and hacking attempts. In this article, we’ll uncover the most common ways hackers use to attack smart contracts in the DeFi space and the necessary steps that smart contract developers can take to prevent them.

Reentrancy Attacks

One of the most common hacking methods is reentrancy. Where a malicious contract calls the target contract multiple times before the first call is completed. Leading to an overflow of the contract’s balance. Example: Fei Protocol lost $80 million

Consider the following contract:

				
					pragma solidity ^0.8.0;

contract ReentrancyAttack {
    uint public balance;

    function deposit() public payable {
        balance += msg.value;
    }

    function withdraw(uint amount) public {
        require(balance >= amount, "Insufficient balance");
        msg.sender.transfer(amount);
        balance -= amount;
    }
}

				
			

In this contract, the withdraw function is vulnerable to a reentrancy attack. A malicious contract can call deposit multiple times before the first call to deposit is completed. Leading to an overflow of the contract’s balance. To prevent this, a mutex (a lock) should be added to the contract:

				
					pragma solidity ^0.8.0;

contract ReentrancyAttack {
    uint public balance;
    bool public locked;

    function deposit() public payable {
        balance += msg.value;
    }

    function withdraw(uint amount) public {
        require(balance >= amount, "Insufficient balance");
        require(!locked, "Contract is locked");
        locked = true;
        msg.sender.transfer(amount);
        balance -= amount;
        locked = false;
    }
}

				
			

Integer Overflow and Underflow

Another common attack is the manipulation of integer values. Leading to an overflow or underflow, which results in unintended behavior of the contract.

An integer overflow occurs when an arithmetic operation results in a value that is too large to be stored in an integer variable. This can cause the value to wrap around to a negative number or to zero, leading to incorrect results. In smart contracts, an integer overflow can have serious consequences. As allowing an attacker to steal funds or manipulate the contract in unintended ways.

An integer underflow occurs when an arithmetic operation results in a negative number that is too small to be represented by an unsigned integer. This can cause the value to wrap around to a very large positive number, also leading to incorrect results.

Consider the following contract:

				
					pragma solidity ^0.8.0;

contract IntegerOverflowUnderflow {
    uint public balance;

    function deposit() public payable {
        balance += msg.value;
    }

    function withdraw(uint amount) public {
        require(balance >= amount, "Insufficient balance");
        balance -= amount;
        msg.sender.transfer(amount);
    }

    function add(uint a, uint b) public pure returns (uint) {
        return a + b;
    }
}

				
			

In the add function, if the result of a + b is larger than the maximum value of a uint, an overflow occurs, leading to unintended behavior.

To prevent integer overflow and underflow attacks, smart contract developers need to be aware of the limitations of integers and take steps to ensure that their contracts handle arithmetic operations in a secure manner. This can include using libraries that provide safe arithmetic functions, or implementing custom logic. To check for overflow and underflow conditions and prevent them from occurring. The SafeMath library can be used:

				
					pragma solidity ^0.8.0;

import "@openzeppelin/contracts/math/SafeMath.sol";

contract IntegerOverflowUnderflow {
    uint public balance;
    using SafeMath for uint;

    function deposit() public payable {
        balance = balance.add(msg.value);
    }

    function withdraw(uint amount) public {
        require(balance >= amount, "Insufficient balance");
        balance = balance.sub(amount);
        msg.sender.transfer(amount);
    }

    function add(uint a, uint b) public pure returns (uint) {
        return a.add(b);
    }
}

				
			

Unchecked Approval

An unchecked approval attack is a security vulnerability that can occur in smart contracts. That uses the ERC-20 token standard in the Ethereum network. This type of attack takes advantage of the way the “approve” function is implemented in the ERC-20 standard.

The “approve” function allows one smart contract to authorize another smart contract to transfer a certain amount of tokens on its behalf. If this function is not properly checked, an attacker can transfer more tokens than they were authorized to. Leading to a loss of funds for the owner of the token contract.

This type of attack occurs when the smart contract that is being approved does not properly check the balance of the approved contract before transferring tokens. If the approved contract has a bug that allows it to transfer more tokens than it was authorized, the owner of the token contract could lose their funds.

To prevent this vulnerability, it’s important for smart contract developers to carefully check and verify the code of any contract. That they plan to approve and to ensure that the “approve” function is implemented in a secure manner.

Lack of Updater Contract

In absence of an updater contract, a malicious party can exploit a vulnerability in the smart contract and replace it with a malicious version.

To prevent attack, smart contract developers can follow these best practices:

  1. Implement proper access control: The contract’s update mechanism should only be accessible to authorized parties. Such as the contract owner or a designated administrator. Access control can be implemented using access control mechanisms like Role-Based Access Control (RBAC) or Access Control Lists (ACLs).

  2. Use a separate updater contract: Instead of directly updating the contract’s code. A separate updater contract can be used to make updates. This allows for better control over the update process and reduces the risk of malicious code being introduced.

Replay Attacks

A replay attack is one of the most common types of attacks and involves an attacker replaying a transaction multiple times. This exploits the immutable nature of smart contracts. Allowing the attacker to drain funds from unsuspecting users. To defend against this type of attack, developers should use nonce values when sending a transaction. This ensures that the transaction can only be broadcast once and not replayed. For example, Solidity code to implement a nonce might look like this:

				
					uint nonce = 0;

function sendTransaction(uint amount) {
  require(nonce < 2**63 - 1);
  nonce++;
  // Send transaction with amount and nonce
}
				
			

Front Running

Front running is another type of attack that takes advantage of the speed of the blockchain to get ahead of other users. When a user places a transaction, a malicious actor notices it before it is added to the blockchain and quickly places their own transaction to get ahead of the original user. To protect against this type of attack, developers should use a decentralized exchange. That makes it harder for malicious actors to observe transactions. For example, a decentralized exchange might use the 0x protocol to prevent front-running:

				
					pragma solidity ^0.8.0;

import "0x.sol";

contract Exchange {
  using 0x for *;
  // Exchange code
}
				
			

Private Key Theft

Private key theft is another type of attack that is becoming more prevalent in the DeFi space. Hackers can use a variety of methods to steal user keys, such as phishing and social engineering. To counteract this, developers should use secure wallets and store their private keys in a secure location.

Race Condition Vulnerability

A race condition vulnerability occurs when a smart contract allows multiple transactions to be processed at the same time, resulting in unexpected behavior. To prevent this type of attack, developers need to make sure their smart contracts are carefully written and tested to prevent race conditions from occurring.

To avoid these and other potential security threats, it is crucial that smart contract developers follow industry-standard security best practices, such as:

  1. Regular code reviews and testing
  2. Proper use of secure libraries
  3. Implementation of access controls
  4. Adequate gas usage for contract operations

However, even with the best intentions, mistakes can happen. That’s where Novvr and Scrutify come in. Novvr is a leading smart contract auditing firm that provides comprehensive security assessments for DeFi projects. Our team of experts uses the latest tools and techniques to identify vulnerabilities and provide actionable recommendations for improvement. Visit Smart Contract Audit to learn more about our process and how we can help secure your smart contracts.

Conclusion

In conclusion, the DeFi space is still in its infancy, and security threats will continue to evolve. It’s essential that smart contract developers stay vigilant and implement best practices to ensure the safety of funds in their contracts. Novvr and Scrutify are here to help. Don’t let the dark side of DeFi catch you by surprise – take action today. Schedule a Free Consultation.

Novvr is a Blockchain Development company based in India, has helped over 150+ brands in 80+ countries. We are committed to quality and unparalleled customer service in all aspects of the business. 

© 2024 Novvr Technologies. All Rights Reserved
GSTN: 09EUQPS4052G1Z2