Skip to content

Approval Vulnerabilities

Approvals are a fundamental component of tokens on Ethereum. They allow users to grant permission to third-party addresses, be it externally owned accounts (EOAs) or other smart contracts, to move funds on their behalf. This feature has benefited many systems, including NFT marketplaces and DeFi applications. These platforms often employ approvals to automate the transfer of funds. This could be when a trade is completed or funds must be moved based on specific business logic conditions.

Unlimited Approvals

A prevalent misstep among smart contract developers is requiring unlimited approvals for certain assets. This is usually justified by the system’s lack of knowledge of the necessary amount at a given time. This implies the following problem: Once granted, approvals can be invoked anytime. If a malicious party were to compromise the smart contract system, they could exploit these approvals, leading to a complete drain of all previously approved assets for the affected address.

Approval Frontrunning

Another issue is the susceptibility of the approval system to frontrunning. When a user submits multiple approve calls, they inadvertently open a window of opportunity for malicious actors. To illustrate:

  1. Using the approve function, a user allows a smart contract system to transfer x of their ERC20 tokens.
  2. Later, they opt to modify the allowance to y and thus send another approve request.
  3. In the meantime, before the user-given transaction gets included, an attacker initiates the transferFrom function to remove x tokens from the user’s wallet.
  4. If the attacker’s transaction is processed first, followed by the user's new approve transaction, the malicious actor can move an additional y tokens.
  5. The aggregate unauthorized transfer would amount to x+y tokens.

This vulnerability arises because the ERC20's _approve function directly sets the spender’s allowance.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

Meanwhile, the _spendAllowance function only verifies the current allowance against the amount specified in the transferFrom call. It lacks a mechanism to track funds transferred before the latest approve request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }

Security Best Practices for Approvals

One straightforward countermeasure against frontrunning is to modify how the allowance is managed. Rather than arbitrarily increasing or decreasing allowances by setting new values with direct approve calls, it’s best practice to call the safeIncreaseAllowance and safeDecreaseAllowance functions, as defined in OpenZeppelin's SafeERC20 implementation. These functions internally call the target's approve function but offer an additional parameter specifying the value difference.

Another notable threat, although not a direct, smart contract vulnerability, is "approval phishing." In scenarios where a frontend is exposed to content injection attacks or other manipulations, attackers can modify the underlying code to divert approvals not to the intended smart contract address but to their own, hijacking the process.

Developers should employ an allowance system that limits the number of tokens an approval can move to only the immediately necessary amount. Implementing two-step transfers and locking periods can limit the impact of malicious actions, although this might reduce user-friendliness. For users, a good habit is periodically assessing given approvals and revoking outdated or unnecessary ones. There are platforms like https://revoke.cash/ that can help in this regard.