Paraluni's MasterChef contract was attacked on March 13, 2022, at 12:04:30 AM +UTC. The attacker exploited a reentrancy vulnerability within the project’s smart contract. Around $1.7M was stolen from the project through multiple transactions. The Paraluni hack was made possible by a reentrancy vulnerability within the contract’s depositByAddLiquidity function.
On March 13 2022, Paraluni, the Metaverse Financial project, was the victim of a “classic” re-entrancy attack.
The depositByAddLiquidity function calls an internal depositByAddLiquidityInternal function that transfers the attacker’s deposit into the appropriate pool. However the pool ID value (_pid) used to look up the appropriate pool was not valdiated internally.
The attacker took advantage of this by directing this to an attacker-controlled contract, whose malicious transferFrom function is called. This function then exploits the reentrancy vulnerability to call the Masterchef deposit function before the internal state is updated. Between the initial and malicious deposits, the attacker is credited with excess tokens and able to extract more value from the contract than they deposited. The Paraluni team reached out to the hacker on-chain after the incident as seen in some of the conversations screen shotted below:
Response from the hacker:
Paraluni announced that they would overlook this exploit and consider it as a white-hat hack and offered the attacker a reward for finding this exploit if he promised to return the stolen funds. However, their attempt failed as there was no response.
Attacker address: 0x94bc1..
Attack transaction: 0x70f36..
Attack contract: 0x4770b..
Masterchef contract: 0xa386f..
The attacker first created ERC20 token contracts called UBT and UGT with a revised transferFrom function. Within the UBT token contract, there are two malicious implementations:
transferFrom() function, the attacker implement an invocation to MasterChef’s
deposit() function to deposit LP tokens.
A “withdrawAsset()” function that will call Masterchef’s “withdraw()” to withdraw LP tokens deposited.
The attacker first borrowed about 157,000 USDT and 157,000 BUSD from PancakeSwap via a flash loan.
The attacker then transferred flashloaned tokens to ParaPair and received 155,935 LP tokens in return
The exploiter called “depositByAddLiquidity()” function to 0x55d39.. deposit LP tokens to the pool:
As depositByAddLiquidity() will invoke the “UBT.transferFrom()”, MasterChef.deposit() was invoked to deposit 155,935 LP tokens to the contract.
In this case, 155,935 LP tokens are deposited twice and the attacker got two separated “userInfo” records (one is from the UBT, the other is from the attacker’s address)
The attacker withdrew twice: a. One is via the “UBT.withdrawAsset()”. b. The other is from the attacker’s invocation to “Masterchef.withdraw()”.
Finally, the attacker removed the liquidity, returned the flashloans, and received 310,000 in USDT and 310,000 in BUSD.
In the function
MasterChef.depositByAddLiquidity(), the passed
_tokens does not match with the tokens in the pool with
_pid, which should be validated.
depositByAddLiquidityInternal() which triggers the function
addLiduidityInternal() will call the malicious token contract UBT’s transferFrom() to invoke the
Following the incident, the exploiter exchanged BNB for ETH. The attacker then cross-chained ETH from BSC to Ethereum and finally deposited 660 ETH into Tornado in 12 instalments. In total ~$1.7M worth of asset was stolen.
The hack was made possible mainly due to the logic flaws in the contract code and lack of reentrancy prevention in the key functions for fund operations. The two vulnerabilities at stake here were: failing to validate user input (_pid) and a reentrancy vulnerability. Both of these are well-known types of vulnerabilities and could have been detected and prevented by a smart contract audit.