Mastering Access Control Issue: Ensuring Only Authorized Interactions with Your Contract
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.
- Safemoon
- https://www.halborn.com/blog/post/explained-the-safe-moon-hack-march-2023
- Total Loss: $8.9 million
- SwapX
- https://quillaudits.medium.com/decoding-swapx-1-million-exploit-quillaudits-502c5e7a542c
- Total Loss: $1 million
- Crema Finance
- https://www.halborn.com/blog/post/explained-the-crema-finance-hack-july-2022
- Total Loss: $8 million
- DODO
- https://www.halborn.com/blog/post/explained-the-dodo-dex-hack-march-2021
- Total Loss: $3.8 million
- 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.
Contents
YOU MAY ALSO LIKE
What is Solana Storage?
Education
Discover Solana's unique approach to blockchain storage with an in-depth exploration of its Programs and Accounts model.
Defending Against DoS: Strategies to Prevent Denial of Service Attacks in Smart Contracts
Education
Explore how DoS attacks like Unexpected Reverts, Block Gas Limits, and Block Stuffing disrupt Solidity smart contracts. Learn security methods to safeguard your blockchain projects.
Why Smart Contract Audits are Non-Negotiable
Education
Unlock Web3's full potential by securing your smart contracts. Learn why audits are essential, explore real-world risks, and see how Oxor.io fortifies your blockchain projects against threats.
Integer Overflows and Underflows
Education
Learn about the risks of integer overflows and underflows in Solidity smart contracts. Discover how to mitigate these vulnerabilities using SafeMath and Solidity's built-in checks to enhance your blockchain project's security with OXORIO.
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.