Access Control Vulnerabilities
Access control vulnerabilities in smart contracts were one of the primary factors leading to the Poly Network cross-chain bridge hack with losses of $611 million and contributed to a $300,000 hack of the ShadowFi DeFi project on Binance Smart Chain (BSC).
More insights on the detailed incidents of these attacks can be gleaned from the following two articles:
- Abusing Smart Contracts to Steal $600 million: How the Poly Network Hack Actually Happened
- ShadowFi $301K Burn function Exploit Analysis
Access control in smart contracts defines the permissions of different roles within the application. Typically, actions such as minting tokens, withdrawing funds, or pausing transactions require users with higher privileges. Incorrect configuration of these permissions can lead to unforeseen losses.
Below, we discuss two common types of access control vulnerabilities.
1. Misconfigured Permissions
Lack of proper access control on critical functions allows anyone to mint an excessive number of tokens or withdraw all funds from the contract. For instance, the Poly Network bridge contract failed to restrict the function for transferring guardianship, allowing a hacker to redirect $611 million to their own address.
In the following code, the flawedCreateToken()
function lacks access restrictions, permitting unrestricted token minting by any user.
// Flawed createToken function without access control
function flawedCreateToken(address recipient, uint256 quantity) public {
_mint(recipient, quantity);
}
2. Flawed Authorization Checks
Another common vulnerability arises when functions do not verify whether the caller has sufficient permissions. For example, ShadowFi's token contract on BSC omitted a crucial check in its burn
function, allowing attackers to arbitrarily burn tokens owned by other addresses. After burning tokens in the liquidity pool, the hacker could extract all BNB
from the pool by selling a minimal amount of tokens, netting $300,000.
In the provided code snippet, the flawedDestroyToken()
function does not implement necessary authorization checks, thereby allowing any user to burn tokens without restrictions.
// Flawed destroyToken function without proper authorization checks
function flawedDestroyToken(address holder, uint256 quantity) public {
_burn(holder, quantity);
}
Prevention Strategies
There are two main strategies to prevent access control vulnerabilities:
- Utilize access control libraries like OpenZeppelin to assign appropriate permissions to special functions, for instance using the
OnlyOwner
modifier to restrict function calls to the contract owner.
// Correct createToken function using the onlyOwner modifier for access control
function correctCreateToken(address recipient, uint256 quantity) public onlyOwner {
_mint(recipient, quantity);
}
- Ensure that function logic verifies the caller has the necessary permissions.
// Correct destroyToken function with authorization check
function correctDestroyToken(address holder, uint256 quantity) public {
if(msg.sender != holder){
_spendAllowance(holder, msg.sender, quantity);
}
_burn(holder, quantity);
}