Disclaimer: This article is based on the public transaction and our understanding of the Mirror protocol and the Terra ecosystem. Please let us know if there is anything inaccurate. Any comments about this blog are welcome.
The transaction https://finder.terra.money/classic/tx/29C9CFBBC9562100A5DB19D705E440CE24768D3BDE399507FA1C2EC2424413C4 is used to prepare the attack.
STEP 1: In this transaction, the attacker first sent 100,000 USTC to the lock contract. This is not necessary to open a position, but is critical for the attack.
STEP 2: After that, the attacker opened a position by depositing 10 USTC as the collateral and specifying the collateral_ratio as 2.5.
short_params is specified so that the mint contract will sell the minted mAssets (i.e., mETH) and add the obtained USTC to the position's locked amount.
STEP 2.1: Let’s walk through the transaction step by step. First, the function
open_position will be invoked to open a short position whose ID is
STEP 2.2: Since the optional short_params is added, the contract will first mint
0.001208 mETH (based on the current price of ETH) and then sell it by swapping in the
STEP 2.3: The
0.001208 mETH will be swapped into
4.06582 USTC, the swapped USTC will be sent to the lock contract after removing the related fees (e.g., tax). That's because the opened position can only be unlocked after a certain time period.
STEP 2.4: Then
lock_position_funds_hook will be invoked. In this function,
position_locked_amount will be calculated by querying the
current_balance and comparing the current_balance with the locked_funds.
However, as we have seen in Step 1,
100,000 USTC has been directly transferred into lock contract, the locked_amount will be around
100,004 USTC instead of
Till this end, the attacker opened a position by sending
100,000 USTC to the lock contract directly and
10 USTC as collateral. The position's locked amount is around
100,004 USTC and can be unlocked after a time period. The attacker opened many such kinds of positions by sending
The Mirror Protocol does not check the duplication of the position ID. In this case, the attacker can feed many duplicate position IDs to unlock the locked amount in one position over and over again. As such, the number of unlocked amount will be duplicated many times (which is much bigger than the correct value).
This transaction is the attack transaction. For instance, for the position ID 43186, the attacker duplicated 437 times.
Since the original contract code does not check the duplication, about
437 * 0.1M）USTC has been unlocked (in this single function call.)
Note that, the other positions have been unlocked with the same mechanism.
2. Bug Fix
The vulnerability was fixed in this commit.
unlockable_positions is a vector containing the position IDs to be unlocked. In the original code, there is no check on whether there are duplicate IDs in
unlockable_positions. The patched code adds a check for the duplication of the position IDs.
As pointed out by @FatMan and other community members, this bug existed for a couple months and has been exploited in the wild. We believe that silently patching a vulnerability which has already been exploited in the wild is not a good security practice. Besides, we also think the high-profile DeFi projects should deploy some gate keepers to actively monitor the status of their apps and get alerted when something unusual happened.