5 Common Smart Contract Vulnerabilities and How to Fix Them

In recent times, the world of decentralized applications (DApps) has witnessed a significant cyber attack that exposed a prominent DApp to severe financial losses. This incident has served as a wake-up call for the entire blockchain community, highlighting the critical importance of securing smart contracts. Today, we’re here to empower you with essential knowledge on five common smart contract vulnerabilities that could jeopardize your assets. By understanding these vulnerabilities and implementing the recommended solutions, you can fortify your DApp against potential attacks and safeguard your valuable assets.

Here are the Top 5 Smart Contract Vulnerabilities :​

1. Reentrancy Attacks:

One of the most notorious vulnerabilities is the reentrancy attack. This vulnerability allows an attacker to repeatedly call a contract’s function within a single transaction, exploiting the order of operations. Consider the following vulnerable code snippet

            mapping(address => uint256) balances;

function withdraw() public {
    uint256 amount = balances[msg.sender];
    require(amount > 0);
    (bool success,) = msg.sender.call{value: amount}("");
    if (success) {
        balances[msg.sender] = 0;
    }
}

        

To fix this vulnerability, adopt the “checks-effects-interactions” pattern. This involves performing all necessary checks and updates before interacting with external contracts or accounts. Here’s an updated version of the code:

            mapping(address => uint256) balances;

function withdraw() public {
    uint256 amount = balances[msg.sender];
    require(amount > 0);
    balances[msg.sender] = 0;
    (bool success,) = msg.sender.call{value: amount}("");
    require(success);
}

        

2. Integer Overflow/Underflow:

Another common vulnerability arises from improper handling of integer arithmetic. When an integer value exceeds its maximum or goes below its minimum, it wraps around unexpectedly, potentially leading to undesired outcomes. Consider this vulnerable code snippet:

            uint256 public totalSupply;

function transfer(address _to, uint256 _value) public {
    require(balances[msg.sender] >= _value);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    assert(balances[_to] >= _value);
    totalSupply -= _value;
}

        

To mitigate this vulnerability, use the OpenZeppelin SafeMath library or similar techniques to perform secure arithmetic operations. Here’s an updated version of the code:

            using SafeMath for uint256;

uint256 public totalSupply;

function transfer(address _to, uint256 _value) public {
    require(balances[msg.sender] >= _value);
    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    assert(balances[_to] >= _value);
    totalSupply = totalSupply.sub(_value);
}

        

Looking for Smart Contract Auditor?

Get A Free Quote

Please fill in the form

3. Unchecked External Calls:

Smart contracts often interact with external contracts or addresses. However, if not properly validated, these external calls can introduce security vulnerabilities. Consider this vulnerable code snippet:

            function transfer(address _to, uint256 _value) public {
    require(balances[msg.sender] >= _value);
    (bool success,) = _to.call{value: _value}("");
    require(success);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
}

        

To fix this vulnerability, use the “transfer” method provided by the ERC-20 token standard or employ a more robust approach, such as the OpenZeppelin SafeERC20 library. Here’s an updated version of the code:

            function transfer(address _to, uint256 _value) public {
    require(balances[msg.sender] >= _value);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
}

        

4. Front-running attacks

Front-running attacks occur when a transaction’s execution is influenced by other transactions that exploit timing differences. This vulnerability can allow attackers to manipulate the order of transactions to their advantage. While preventing front-running attacks entirely is challenging, you can minimize the risk by reducing the reliance on transaction order. Consider adopting techniques like using commit-reveal schemes or verifiable delay functions to mitigate this vulnerability. Example code snippet:

            pragma solidity ^0.8.0;

contract Auction {
    address public highestBidder;
    uint256 public highestBid;

    function bid() public payable {
        require(msg.value > highestBid);
        highestBidder = msg.sender;
        highestBid = msg.value;
    }
}

        

To mitigate front-running attacks, you can implement a commit-reveal scheme. Here’s an updated version of the code using a commit-reveal approach:

            pragma solidity ^0.8.0;

contract Auction {
    address public highestBidder;
    uint256 public highestBid;
    mapping(address => bytes32) public commitments;

    function commit(bytes32 _commitment) public payable {
        require(msg.value > highestBid);
        commitments[msg.sender] = _commitment;
    }

    function reveal(uint256 _value, bytes32 _secret) public {
        bytes32 commitment = keccak256(abi.encodePacked(_value, _secret));
        require(commitment == commitments[msg.sender]);

        if (_value > highestBid) {
            highestBid = _value;
            highestBidder = msg.sender;
        }
    }
}

        

In this updated code, bidders first commit a hashed value of their bid and a secret. Later, they reveal the actual bid value and the secret. By using a hashed commitment, the order of bids is not apparent until the reveal phase, reducing the vulnerability to front-running attacks.

5. Access Control Issues:

Incorrect or missing access control checks can lead to unauthorized individuals gaining control over critical contract functions or assets. To avoid this vulnerability, ensure that only authorized users or contracts can execute sensitive operations. Implement proper access control mechanisms such as role-based access control (RBAC) or the Ownable pattern to secure your smart contracts effectively.

            pragma solidity ^0.8.0;

contract Voting {
    mapping(address => uint256) public votes;

    function vote(uint256 _candidateId) public {
        votes[msg.sender] = _candidateId;
    }
}

        

To address access control issues, you can implement the Ownable pattern using a modifier that restricts certain functions to the contract owner. Here’s an updated version of the code:

            pragma solidity ^0.8.0;

contract Voting {
    mapping(address => uint256) public votes;
    address public contractOwner;

    modifier onlyOwner() {
        require(msg.sender == contractOwner, "Only the contract owner can call this function.");
        _;
    }

    constructor() {
        contractOwner = msg.sender;
    }

    function vote(uint256 _candidateId) public {
        votes[msg.sender] = _candidateId;
    }

    function setContractOwner(address _newOwner) public onlyOwner {
        contractOwner = _newOwner;
    }
}

        

In this updated code, the onlyOwner modifier ensures that only the contract owner can call the setContractOwner function, which allows changing the contract owner address. This adds an additional layer of access control to protect critical functions or data within the smart contract.

Conclusion:

By understanding and addressing these five common smart contract vulnerabilities, you can significantly reduce the risk of falling victim to a cyber attack. Incorporate security best practices into your smart contract development process and leverage proven libraries and frameworks. Remember, securing your smart contracts is essential to safeguarding your digital assets and maintaining user trust. Stay informed, take action, and protect yourself from potential threats in the exciting world of decentralized applications.

https://scrutify.io

Scrutify is a team of blockchain and cybersecurity experts specializing in smart contract audits, dedicated to ensuring web3 security that never sleeps. We provide valuable insights on blockchain news and empower individuals and businesses with the knowledge they need to safeguard their assets in the ever-evolving world of blockchain and cryptocurrencies

About us

Scrutify is a blockchain security platform, powered by Novvr, on a mission to secure Web3 for the future.

Audits

Get in Touch

Office

Greater Noida (UP),
India – 201009

© 2024 Scrutify a product by Novvr. All Rights Reserved.