BEOSIN: Summary of Token-level Security Issues
Token-level security issues cannot be ignored in blockchain security. BEOSIN’s security technical team have summarized some token-level issues occurred in DEFI and categorized them into two types: the first is the token-level issues by themselves; the second is the issues that may arise in interactions between tokens and DEFI.
#A. Token-level issues
1. Integer overflows
Why are integer overflows so important? The main reason is because they can cause huge losses once they occur. Before version 0.8.0, EVM did not have an overflow checking mechanism and needed to pay special attention to the integer overflow issue during numerical operations. After version 0.8.0, Solidity introduced its own overflow checking mechanism, which fundamentally avoids the integer overflow problem, but also note that when the unchecked keyword is used, the numerical operations involved are also not checked for overflow.
Integer overflow is generally divided into integer overflow and underflow. Integer overflow means that when the result of an operation is greater than the upper limit of the value specified by the uint data type, it will lead to overflow to the lower limit of the value to start recalculation (generally that is, 0).
For example, the value range of uint8 is 0–255, when a variable a of uint8 type is assigned to 260, it will overflow to 0+(260–256)=4, thus the value of a will become 4. Similarly, when a variable is assigned to a value smaller than 0, it will lead to underflow issue.For example, a variable b of uint8 type is assigned to b=0–5, then the value of b will not be -5 (uint8 type is unsigned integer, so there is no negative number), so the value of b will become 256–5=251.
The figure below shows the multiTransfer function of a token, which does not use SafeMath to perform an integer overflow check when accumulating the tokens array of the input parameters, giving an attacker the opportunity to construct an integer overflow attack. This will cause the value of totalTokensToTransfer to overflow to a smaller value and pass the balance check, transferring a huge amount of assets.
It is recommended to introduce SafeMath to perform arithmetic on the data before version 0.8.0. When using unchecked in version 0.8.0 and above, it is recommended to use functions such as require to check for overflow.
2. Function Permission Errors
Function permission errors are usually caused by contract developers’ negligence. Many internal functions will directly change the contract storage data in the running process without checking (e.g., changing contract manager permissions, calling key contract parameters, etc.). If the visibility of such function is set to public or external, it will create a major security vulnerability. This issue occurred in October of this year with AVATerra Finance, which set the visibility modifier of the mint function mint to public, allowing any attacker could perform mint operations.
Do strict permission checks on sensitive functions such as minting and permission changes, and determine the visibility of such functions based on business logic.
3. Excessive authority
Administrators with excessive contractual authority can have uncontrollable amounts of user assets and unstable asset values. If the administrator has the authority to transfer and destroy the user’s balance at will, the user’s assets can be returned to zero at any time; if the administrator has the authority to mint unlimited coins as a function, such tokens can be issued in large quantities, making the token price depreciate rapidly.
The following figure shows the function that allows the administrator to destroy user tokens at will. Here the onlyRole modifier is used, and the burnFrom function is overridden to cause the tokens of the specified account to be destroyed at will.
Do a strict review on the scope of administrator’s permission, and pay attention to operations such as transferring users’ tokens and destroying users’ tokens in violation. It is recommended to remove these codes with potential issues to protect users’ property.
4. Self-additional issuance vulnerability
This vulnerability is a special logic vulnerability, a self-additional issuance vulnerability caused by multiple local variables set in the transfer function when users transfer money to themselves, resulting in variables overwriting each other.
A typical example of this vulnerability is the Troncrashcoin token, whose transfer function logic is as follows:
1. create new variables oldFromVal and oldToVal to store old balances; 2. create new variables newFromVal and newToVal to store new balances, i.e. oldFromVal+_value and oldToVal+_value; 3. assign newFromVal and newToVal to balances[_from] and balances[_to].
Once the _to address and _from address are the same, balances[_from] will be overwritten by the value of balances[_to], resulting in the previously reduced _value not working, thus creating the vulnerability of self-increasing _value but not reducing _value.
After development, the project party needs to completely test whether the each function point is executed properly, and conduct a complete input test of all callable functions and their input parameters to verify if the business logic meets the requirements. For example, testing whether a series of special cases such as extreme values and self-transfer meet the logic.
5. Incoming parameters are not verified correctly
In the execution of the function, if the reasonableness of the incoming parameters is not verified, it may lead to the function not being executed as expected. For instance, if the permit function does not perform zero address verification and the corresponding tokens are destroyed by sending the tokens to the zero address, then the attacker can transfer the destroyed tokens in the zero address. Also for example, there will be a freeze function in some smart contracts, but when transferring tokens, only the source account is verified, while the transfer address is not verified, resulting in the transferred tokens cannot be withdrawn. Also note that the transferFrom needs to additionally verify the from address. There is a similar problem with blacklist verification.
Another example is the NaughtCoin question in the Ethernaut. Since the ERC20 only qualifies the transfer, not the transferfrom, the attacker can directly call approve and transferfrom through the standard ERC20 interface for token extraction.
All functions called by the user are checked for the reasonableness of the parameters passed in. Avoid exceptions caused by unreasonable use of parameters. When using functions with restrictions, verify if the inappropriate parameters that passed in will bypass the restriction or that there are other similar functions that can be bypassed.
6. Developer Backdoors
Some administrators will ask someone to develop it for them during the development stage. In this case, if the developer leaves a backdoor inside the Token, then the Token with the backdoor will cause losses to both the project and the users once it comes online. For example, the following is the backdoor left by token HJL in the minting function, resulting in 1% of additional tokens flowing into the address 0xfa every time the token is minted, resulting in the actual circulation of the token being greater than the displayed value.
It is recommended to conduct several more audits before the tokens go live and to verify if the hash of the deployed tokens are consistent with the final audited version.
#B. Token issues occurred in DEFI interactions
1. Deflationary tokens for differential arbitrage
This year DEFI has seen a number of deflationary tokens represented by safemoon. When users trade with such coins, they will destroy some of the tokens, resulting in the inconsistencies between the actual amount received and the amount spent. Therefore, if a DEFI project similar to a staking pool type records assets based on the amount of transfers, once interacting with such tokens, it is easy to have inconsistencies between the actual assets owned by the project and the recorded value, which can easily be exploited by hackers, such as the SafeDollar incident.
The SafeDollar exploit is an attacker who takes advantage of the fact that the actual amount of PLX tokens received is less than the amount sent, and that the logic flaw exists in the SdoRewardPool contract’s stake and reward calculation to control the amount of staked tokens in the pool with flash loan, and then manipulate the reward calculation to gain benefits.
Here is the formula: reward calculation factor A = newly generated reward tokens SAO / staking tokens PLX in the pool. The attacker first use the flash loan to obtain a large amount of funds, and then utilizes the logical vulnerability of updating the reward parameters in the SdoRewardPool contract and the special mechanism that the amount of staking tokens transferred may not be consistent with the actual amount received, resulting in a sharp increase in the reward calculation factor A. The attacking contract, which was staked in advance, was then used to claim a large amount of staking rewards, thus maliciously obtaining a large amount of SAO tokens.
Reward calculation factor A = newly generated reward tokens SAO / staking tokens PLX in the pool
In order to avoid situations such as tokens with their own fees or automatic destruction leading to inconsistencies between the actual amount and the recorded amount, it is recommended that contracts use the change in funds before and after the transfer as the actual number of transfers received by the contract, rather than the parameters passed in by the user, especially for liquid transactions.
2. Token interface specification issues
When DEFI interacts with tokens, it follows a unified token interface specification. If the token implementation does not follow the standard interface specification, it may lead to abnormal execution of the code logic during the interaction. For example, USDT does not comply with the ERC20 standard in some of its on-chain code implementations, and in both TRON and ETH，the USDT code have irregular return values. In ETH’s USDT main contract, the transfer function has a return value, but the function declaration is not declared, resulting in no subsequent return value.
Also in TRON-based USDT, the transfer function in the main contract inherits from the parent contract. While the transfer in the parent contract has a declared return value, there is no return value in the function, so the transfer of the master contract will permanently return false.
Therefore the USDT contracts on both chains are not ERC20 compliant, which could lead to funds being locked up in the contracts if DEFI fails to pay attention to the issue.
When performing a token transfer, it is needed to check that the transfer function of the calling token contract meets the ERC20 standard. For transfer functions without a return value, using SafeERC20 to perform transfers will cause the function to return a default false when the transfer function of TRON-based USDT does not follow its own TRC20 standard. This will lead to the failure of SafeTransfer execution. It is needed to write a specific function to call.
potential reentrancy risks
The reentrancy vulnerabilities are also seen in tokens. A typical example is the safeTransferFrom function in ERC1155 will call _doSafeTransferAcceptanceCheck function, however _do doSafeTransferAcceptanceCheck will check if the target address is a contract, it will call the onERC1155Received method. Here if the DEFI contract is not properly written, calling safeTransferFrom before important operations (such as modifying the balance) will trigger a reentrancy vulnerability.
For example, in the following contract, the amount of the withdraw function is placed after safeTransferFrom. Once a malicious contract constructs an onERC1155Received for re-entrancy, it will be able to extract all the tokens in the contract after multiple re-entrancies.
Reentrancy vulnerabilities can be avoided using the check-validate-interaction model, and transfer functions can be modified with openzeppelin’s official ReentrancyGuard. Be aware that the presence of important operations (e.g. modifying the balance) before external calls could lead to a reentrancy attack.
4. Unlimited permission
When users interact with DEFI tokens, some DEFI projects may directly ask users for unlimited authorization, however, this is actually a very insecure behavior. If there are some vulnerabilities or issues in the front-end or within the DEFI projects, users’ token security will not be guaranteed, so it is usually best for DEFI projects to let users selectively give authorization values to avoid loss of assets.
An unlimited permission exists in the liquidity mining project UniCats. Users can deposit Uniswap (UNI) tokens and then receive MEOW tokens issued by the project owner through liquidity mining. However, to participate in mining, the front-end requires users to provide an unlimited permission. Users who have staked UNI tokens on the project can also get the reward back together with the stake, but the project owner can transfer the tokens at any time using a backdoor in the contract. Users will receive wallet alerts when authorizing UniCats, but since authorization is generally a common DEFI operation, users tend to overlook the details and offer unlimited permission to the contract.
After users receive the mining reward to retrieve the stake, they have already withdrawn from the project, and it is often easier to be lax at this time. As the authorization has not been canceled, the project contract has a backdoor to allow the project party to transfer the user’s UNI through authorization. Then a rug pull is conducted by the project party, causing the users to suffer huge losses.
Another example is the Degen Money project. Instead of leaving a hidden backdoor in the smart contract, Degen Money creates a front-end to perform two times of authorized transactions. The first authorization is for the stake contract, and the second authorization is for a malicious address which can result in funds being withdrawn by an attacker using transferFrom. Users need to cancel the authorization to avoid such theft.
In order to prevent similar incidents from happening again, there are websites that can provide query services to facilitate users, which can enables users to know the authorization information in time.
Authorization is a common operation for tokens. Users are advised to attach importance to their property security and not to trust the project owner blindly. Use the inquiry website to determine whether there is a risk of over-authorization, and cancel it in time once over-authorization occurs. The following are the authorization query platforms: