Mastering Access Control Issue: Ensuring Only Authorized Interactions with Your Contract

25 October, 2024
article image
Education

Contents

The year 2024 began on a sour note with a significant hacking incident targeting the Animoca Brands-backed gaming platform, GAMEE. The unauthorized access to the project’s smart contracts resulted in the loss of 7$ million worth of GMEE tokens.

A detailed description of the incident can be found at the following link. Next, we will focus on analyzing the vulnerability of access control and measures to prevent it.

How Does It Work?

Imagine a smart lock on a door. This lock operates according to specific rules: someone with a key can open the door, while someone without a key cannot. A smart contract is like such a digital lock, but for data on a blockchain.

An access control vulnerability occurs when there is a hole in this digital lock, and anyone can open it without the necessary key. In other words, a malicious actor can gain access to the data or functions of a smart contract that should only be accessible to certain people.

Such vulnerabilities typically occur for two reasons. The rules defining who can access what may not be strict enough. The second reason is elementary errors in the code itself.

Example with the Code

Let’s move on to the practical part. Below we will consider examples of vulnerable contracts and analyze ways to eliminate the identified weaknesses.

Require Right

pragma solidity ^0.8.0;

contract AccessControlVulnerable {
    mapping(address => uint256) private userBalances;

    function deposit() public payable {
        userBalances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        userBalances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }

    function getUserBalance() public view returns (uint256) {
        return userBalances[msg.sender];
    }
}

As you can see, the withdraw function in the contract is susceptible to an access control vulnerability. It allows users to withdraw arbitrary amounts without verifying that they have sufficient funds. To mitigate this risk, we must implement a require statement to ensure that the sender’s balance is adequate before processing the withdrawal.

pragma solidity ^0.8.0;

contract AccessControlSolution {
    mapping(address => uint256) private userBalances;

    function deposit() public payable {
        userBalances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        // Check if the user has sufficient balance before withdrawal
        require(userBalances[msg.sender] >= amount, "Insufficient balance");

        userBalances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }

    function getUserBalance() public view returns (uint256) {
        return userBalances[msg.sender];
    }
}

Mastering Access Modifiers

pragma solidity ^0.8.0;

contract AccessControlVulnerability {
    address public _owner;
    uint256 public _secretNumber;

    constructor(uint256 _initialNumber) {
        _owner = msg.sender;
        _secretNumber = _initialNumber;
    }

    function setSecretNumber(uint256 _newNumber) public {
        _secretNumber = _newNumber;
    }

    function getSecretNumber() public view returns (uint256) {
        return _secretNumber;
    }
}

The current implementation of the contract lacks access control for the setSecretNumber function, which violates the developers’ intent to grant this privilege solely to the contract owner. This can be rectified by adding the onlyOwner modifier to prevent unauthorized modification of secretNumber and ensure the contract’s security.

pragma solidity ^0.8.0;

contract AccessControlSolution {
    address public _owner;
    uint256 public _secretNumber;

    constructor(uint256 _initialNumber) {
        _owner = msg.sender;
        _secretNumber = _initialNumber;
    }

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

    function setSecretNumber(uint256 _newNumber) public onlyOwner {
        _secretNumber = _newNumber;
    }

    function getSecretNumber() public view returns (uint256) {
        return _secretNumber;
    }
}

OpenZeppelin’s Ownable Interface

pragma solidity ^0.8.0;

contract VotingSystem {
    mapping(address => bool) public voted;
    uint256 public totalVoters;

    constructor() {
        totalVoters = 0;
    }

    function castVote() public {
        // Access control check is still missing
        require(!voted[msg.sender], "You have already voted");
        voted[msg.sender] = true;
        totalVoters++;
    }

    function hasVoted(address voter) public view returns (bool) {
        return voted[voter];
    }
}

The provided VotingSystem contract is not protected against unauthorized multiple voting. The lack of access control to the castVote function allows any network participant to infinitely increase their voting weight, undermining the “one person, one vote” principle.

To address this vulnerability, it is recommended to use the ready-made access control solutions provided by the OpenZeppelin library.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

contract VotingSystem is Ownable {
    mapping(address => bool) public voted;
    uint256 public totalVoters;

    constructor() Ownable(msg.sender) {
        totalVoters = 0;
    }

    function castVote() public onlyOwner {
        require(!voted[msg.sender], "You have already voted");
        voted[msg.sender] = true;
        totalVoters++;
    }

    function hasVoted(address voter) public view returns (bool) {
        return voted[voter];
    }
}

Ownable2Step

It is also worth noting the Ownable2Step module from the OpenZeppelin library. It implements two-factor authentication for transferring contract ownership, enhancing security management.

This works as follows: initially, the initiator transfers rights to the potential new owner, and then the latter must confirm their consent. For more details, please refer to this link.

Role-Based Access Control System

Role-Based Access Control (RBAC) is a mechanism that determines user permissions within a system based on their assigned roles. It enhances smart contract security by preventing unauthorized actions.

pragma solidity ^0.8.0;

contract LendingForUsers {
    address public _owner;

    constructor() {
        _owner = msg.sender;
    }

    // Vulnerable function allowing the owner to transfer ownership
    function transferOwnership(address newOwner) public {
        require(msg.sender == _owner, "Only the owner can transfer ownership");
        _owner = newOwner;

    }

    // Other functions...
}

In the vulnerable LendingForUsers contract, only the owner possesses all rights, which violates the principles of RBAC. Such a concentration of authority makes the contract vulnerable to the compromise of the owner’s account.

The LendingForUsersSolution contract offers more secure access management by introducing a manager role. The owner retains key permissions but can delegate some tasks, reducing the risks associated with a single point of failure. The onlyOwner and onlyManager modifiers ensure strict separation of privileges.

pragma solidity ^0.8.0;

contract LendingForUsersSolution {
    address public _owner;
    mapping(address => bool) public _managers; // Additional role

    constructor() {
        _owner = msg.sender;
        _managers[msg.sender] = true; // Assign the creator as manager
    }

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

    modifier onlyManager() {
        require(_managers[msg.sender], "Only managers can call this function");
        _;
    }

    // Functions with proper access control
    function transferOwnership(address newOwner) public onlyOwner {
        _owner = newOwner;
    }

    function addManager(address newManager) public onlyOwner {
        _managers[newManager] = true;
    }

    // Other functions...
}

Roles

The OpenZeppelin’s Roles library is a valuable tool for managing access control in smart contracts, enabling the assignment of various roles to addresses and controlling the operations they can perform.

For instance, roles such as “minters” for token minting or “burners” for token burning can be assigned. This aligns with the principle of least privilege, where each system component should have only the permissions necessary to fulfill its tasks, thus helping to prevent access control issues.

Victims of Attack

Let’s take a look at some of the victims who have been affected by this attack.

  1. Safemoon

- https://www.halborn.com/blog/post/explained-the-safe-moon-hack-march-2023

- Total Loss: $8.9 million

  1. SwapX

- https://quillaudits.medium.com/decoding-swapx-1-million-exploit-quillaudits-502c5e7a542c

- Total Loss: $1 million

  1. Crema Finance

- https://www.halborn.com/blog/post/explained-the-crema-finance-hack-july-2022

- Total Loss: $8 million

  1. DODO

- https://www.halborn.com/blog/post/explained-the-dodo-dex-hack-march-2021

- Total Loss: $3.8 million

  1. GAMEE Token

- https://www.halborn.com/blog/post/explained-the-gamee-token-hack-january-2024

- Total Loss: $7 million

How OXORIO Can Help?

Access control vulnerabilities occur when unauthorized users can perform actions that should be restricted, often due to insufficient or flawed permission checks in the smart contract code. These vulnerabilities can lead to unauthorized fund transfers, manipulation of contract states, and complete takeover of contract functionalities.

OXORIO specializes in identifying and mitigating these critical security flaws. Here’s how we can help:

  • Comprehensive Security Audits
  • Implementation of Best Practices
  • Secure Coding Guidance
  • Ongoing Support and Monitoring

By partnering with OXORIO, you gain access to a team dedicated to safeguarding your blockchain projects against the kinds of vulnerabilities that have led to significant financial losses in the past. We understand the intricacies of smart contract security and are committed to helping you build trust with your users and investors.

Don’t let access control vulnerabilities put your project at risk. Secure your smart contracts and protect your assets by collaborating with OXORIO. Together, we’ll strengthen your blockchain initiatives and contribute to a safer, more reliable Web3 ecosystem.

Telegram
Education

Contents

Telegram

Have a question?

Have a question?

Stay Connected with OXORIO

We're here to help and guide you through any inquiries you might have about blockchain security and audits.