Started at 11:50:41 AM +UTC, Nov-12–2020, Akropolis was attacked by exploiting its flawed handling of the deposit logic in its
SavingsModule smart contract. The hack results in a loss of 2,030,841.0177 DAI from the affected YCurve and sUSD pools in Akropolis. Here we elaborate the technical details in this blog post.
This incident was due to a bug in the protocol without (1) validating the supported tokens and (2) enforcing reentrancy protection on the deposit logic. The exploitation leads to a large number of pooltokens minted without being backed by valuable assets. The redemption of these minted pooltokens is then exercised to drain about 2.0mn DAI from the affected YCurve and sUSD pools.
The Hack Walk-through
We started the analysis from the transaction behind the hack: 0xe1f…e04d. This hack is initialized from a malicious ERC20-like contract (located at 0xe230). This malicious contract implements a hook that will be invoked whenever the transferFrom() (function signature: 0x23b872dd) is being called.
With this preparation, the bad actor launches the attack by firstly calling deposit() in SavingsModule.sol with the prepared 0xe230 contract as the token and, when the token’s transferFrom() function is being called, invoking the hook function that in essence re-enters the same deposit() but with true DAI assets (0x6b17). However, the way to compute the number of minted pooltokens depends on the balance difference before and after the deposit (lines 234–236). It turns out that the second deposit with the true DAI assets will be effectively counted twice to compute the number of minted pooltokens: one for 0xe230 deposit and another for DAI deposit. In other words, if the second deposit transfers 25K DAI, the total number of minted pooltokens will be doubled with the valuation of 50K DAI (because of the reentrancy).
Our analysis shows that the attacker launched 17 exploiting transactions and so far collected 2,030,841.0177 DAI in total. Note that at the very beginning, a dYdX flashloan is borrowed to kick-off the flurry of exploitation transactions.
The Deposit Logic
Next, we elaborate the vulnerable deposit logic. Any Akropolis user can deposit tokens to Delphi Savings Pools and the pool will mint corresponding pooltokens to the user. The core logic is written in
SavingsModule::deposit() (line 1944) and outlined in the following figure.
- Step 1: The attacker calls
deposit()with the specified
_tokensas the input. This function calculates the token balance before and after the deposit function
depositToProtocol(_protocol, _tokens, _dnAmounts). Then it uses the balance change to mint the poolTokens (line 1970). Between the calculation of balance change, the
depositToProtocol()function calls the
safeTransferFrom()function on the target tokens to perform the actual token transfer (line 2004). However, there is no reentrancy check on the
deposit()function and no validity check on the deposited tokens which might be crafted and malicious.
- Step 2: The attacker re-entered the
deposit()function again when its
transferFrom()function is called to invoke the hook routine.
- Step 3: Because of this second time deposit, the pool will mint poolTokens to the attacker since the real DAI assets are transferred into the protocol and changes the balance.
- Step 4: When this second time deposit completes, it returns back to the first time deposit’s context in
depositToProtocol()and then calculates the balance change again! It turns out the balance difference is exactly the same as the second deposit. Therefore, the same amount of poolTokens is minted to the attacker.
The Stolen Funds
The stolen funds from the above exploitations are currently held in this wallet: 0x9f26. We are actively monitoring this wallet for any movement.
PeckShield Inc. is an industry leading blockchain security company with the goal of elevating the security, privacy, and usability of current blockchain ecosystem. For any business or media inquiries (including the need for smart contract auditing), please contact us at telegram, twitter, or email.