We received warnings from the community who reported that the Uranium Finance project suffered an exploit. Uranium Finance published a post-mortem on Medium, but this fails to highlight the technical details regarding the exploit. Below, we dive deeper into the exploit through a technical analysis in order to uncover the vulnerability that lies in the contract alongside the workflow of the exploit.
The attacker's address:
The exploited contract(MasterUranium):
The vulnerable contract is a modified version of the yield farming MasterChief contract. The additional code implements the concept of "bonus reward" which introduces the vulnerability. The attack consists of the repeat of deposit(_pid, _amount), emergencyWithdraw(_pid), and withdraw(_pid, _amount) transactions. The attacker drains RADS/sRADS rewards tokens from the vulnerable contract and sells them for $1.3M worth of BUSD and BNB.
1. In the first step, the attacker calls the deposit(_pid, _amount) function.
When the deposit(_pid, _amount) function is called with the _amount input argument larger than "0", the "_bonusAmount" is calculated with
_bonusAmount = _amount.mul(userBonus(_pid, _user).add(10000)).div(10000);
The userBonus(_pid, _user) equals 0 in the context of the code. Simplify the equation, we get
On the next line, the user.amountWithBonus adds the _bonusAmount to become a larger value. At the end of the deposit function, the user.rewardDebt is calculated with
user.rewardDebt = user.amountWithBonus.mul(pool.accRadsPerShare).div(1e12);
After the deposit function is called, two variables relevant to the exploit are user.amountWithBonus and user.rewardDebt, they both contain a value larger than 0.
2. In the next step, the attacker calls the emergencyWithdraw(_pid) function
The purpose of the function call is
Recall two variables relevant to the exploit are the user.amountWithBonus and user.rewardDebt, now one of them, the user.rewardDebt equals 0 now while the other one user.amountWithBonus larger than 0.
user.rewardDebt = 0
user.amountWithBonus = x(x>0)
3. In the last step, the attacker calls the withdraw(_pid, _amount) function:
The attacker calls the withdraw function with the _amount variable equal to 0. On mark #1, the require statement is satisfied because user.amount and _amount both equal to 0. On mark #2, pending is calculate with
pending = user.amountWithBonus.mul(pool.accRadsPerShare).div(1e12).sub(user.rewardDebt);
From the last emergencyWithdraw function call, the user.rewardDebt equal to 0, the equation becomes
pending = user.amountWithBonus.mul(pool.accRadsPerShare).div(1e12);
Both pool.accRadsPerShare and user.amountWithBonus equal to a positive number, hence the product pending larger than 0 as well. Assume the _isSRadsRewards equal false, on mark#3, the contract transfers Rads token to the msg.sender which is the attacker. Because the input argument _amount equal to 0 which fails the if statement on mark #4, the code can't reach the line(mark#5) that adjusts the user.amountWithBonus variable to indicate the user claims the reward.
The user.amountWithBonus increases every time the attacker calls the deposit function in each iteration of the attack. The variable eventually used to calculate the pending variable which indicates how much reward token the attacker can get. This enables the attacker to drains more and more tokens in the process.
In short, the attacker calls the deposit function to increase the value of user.amountWithBonus, then calls the emergencyWithdraw function to get his deposit back and set user.rewardDebt equal to 0, finally he calls the withdraw function to receive the RADS/sRADS rewards tokens. Repeat the process to drain most of the reward tokens from the pool.
Robust security assessments are essential in the world of DeFi. Protocols hold assets contributed by the community, and project owners have an obligation to ensure that the protocol(s) underpinning their project are secure. CertiK offers security assessments utilizing best-in-class technologies conducted by experienced security professionals. Visit our Security Leaderboard at https://www.certik.org to access security insights of projects and stay alert to what's happening in the space.
The attacker's address
Contract got exploited(MasterUranium)
Transactions - The attack
Transactions - deposit
Transactions - emergencyWithdraw
Transactions - withdraw