Vulnerable Spots of Lending Protocols

Author: Daniil Ogurtsov
Security researcher at MixBytes
Intro
In this article we will try to go through the core security vulnerabilities common to the lending protocol.

Lending protocols have compiled a long list of hacks which can be categorized. Understanding these vulnerabilities is a perfect tool for an auditor - the lending protocol has the same vulnerable spots and typical attack vectors. It is important to keep these vectors in mind when auditing as a tool applied to lending protocols. It is applicable both for forked lending protocols and protocols with unique logic.
The market overview for an auditor
The idea of lending protocols is very straightforward.
1) Users can borrow
You put 1 ETH as collateral (1000$ value) and it is allowed to take 700 USDC. Yes, it's required that you borrow less than your collateral. It may sound useless but the use case is as follows: no matter how token prices go, you can get back 1 ETH only if you return 700 USDC. In this sense, a lending protocol allows you to fix the price of USDC/ETH.
2) Users can lend
Previously borrowed 700 USDC don't come from nowhere. Lenders are the ones who put capital before. These tokens are stored on smart contracts, some portion of these tokens lies untouched, not borrowed by anyone. [Tokens Borrowed] / [Tokens Lent] that is called "Utilization". "60% Utilization" means that lenders put 10 ETH on smart contract, but borrowers borrowed only 6 ETH.
3) Lenders earn when funds are borrowed
Previously, we said that you should give back 700 USDC to receive your collateralized 1 ETH. Well, it is not completly true - be ready to give back 702 USDC. Funds borrowed accrue interest in time, like 6% annually, for instance. These 2 USDC is the earning for lenders, and it is their motivation to provide liquidity.
4) The rate is dynamic and usually (but not always) depends on Utilization
Protocols experiment with the exact formulas. But in the majority of cases the rate rises along with the utilization rise.
5) As prices move, protocols protect themselves so that your funds borrowed do not exceed funds collaterized
The market price of your assets changes in time. So, the protocol tracks that your value borrowed is not above the value of your collateral, even worse, not above ~80% of your collateral. This 80% is Max LTV - Max Loan-To-Value coefficient.
6) You can lose your right to repay your borrowed funds to receive your collateral
When the LTV of your position is above Max LTV, liquidation happens. Your collateral is sold and you lose your initial opportunity to receive your 1 ETH for 700 USDC, your position is closed.
7) You can make a leverage
It is very common for DeFi users to utilize a "folding" strategy.
Imagine you have 1000 ETH.
You deposit 1000 ETH on Lido and receive 1000 stETH. This is your investment in liquid staking and you will receive gradual returns.

Then you put 1000 stETH as collateral on Aave. Then you borrow 700 ETH. Then you deposit them on Lido and receive 700 stETH. Then put 700stETH as collateral on Aave and repeat it multiple times.

It turns out that you deposited on Lido: 1000 ETH + 700 ETH + 490 ETH, etc.
So, you receive "leverage" on your return on Lido.

It is simple when you expect the stETH and ETH exchange rate not to be volatile. But things become complicated when you have non-correlated assets - a little price movement would destroy all the folding, as positions will be liquidated.
Types of projects
The audit process for a lending protocol depends on its type. There are two categories:
1) Aave/Compound forks
These projects copy the codebase of Aave or Compound.

One of the options for an auditor is to focus on the spots where a project is different from Aave or Compound. As practice shows, these differences are usually the targets for a hacker, as they make new transaction flows that impossible on Aave and Compound.

The vast majority of projects are forks, and usually they differentiate with the list of allowed tokens and oracles for these tokens. They are straightforward for security analysis because:

  • The Aave and Compound code is highly researched in the industry, with many audits performed (for Aave and Compound themselves and for their forks). Also, it is likely that an auditor has already had experience auditing at least one Aave/Compound fork before.
  • The majority of lending protocol hacks touched these forks (usually because they introduced new logic, tokens or oracle logic). So, auditors are aware of things that attract vulnerabilities.
2) Projects with specifics
They are not forks. They introduce new mechanics.

Auditors treat such a project just as a DeFi project, in general, meaning the whole set of audit instruments and practices is helpful here. By the way, common attacks on Aave/Compound forks are still an issue here, as we have the same attack vectors possible. So, it is a great practice to try to prove a project is secure from these attacks.

Here are some examples of projects in this category:

  • Projects with the factory allowing to build an isolated market. Imagine that the project can create a few smaller mini-Aaves and mini-Compounds. They have different lists of tokens, and their liquidity is separated. In isolated markets risks of assets in one market has no effect on other markets and the whole system;
    (Fuse from Rari Capital)
  • Projects working with isolated pairs. They are similar to isolated markets, but only with two tokens: one token is allowed for borrowing&lending, and the second token is accepted as collateral;
    (Fraxlend, Kashi from Sushi)
  • Projects introducing fixed rates;
    (Examples: Notional Finance)
and etc.
Common attack vectors
The overall idea for all hacks is to borrow more, than it was deposited. Every type of attack is a technique to achieve this.
Oracle attacks
In this type of attacks the goal is the same - to borrow more than deposited. But in this category attackers try to make the protocol think that the value deposited is high, or that the value borrowed is low. It is a success if an attacker deposits 1000 USD, but oracle manipulations make the protocol think that 5000 USD was deposited. It will allow to borrow 3000 USD value, and LTV check will pass.

The most obvious issues happen when a protocol takes prices from Uniswap pairs either by getReserves() or by balanceOf(). This is a redflag in the majority of cases. Even a beginner hacker knows it is easy to take a flashloan and set any price in the pair: either very high or extremely low. Only very old projects made such a mistake, or scammy projects with low liquidity.

But, when analyzing the biggest Lending hacks, you may find that the mispriced assets are LP tokens or shares in vaults. Let's take the Warp Finance as an example. https://rekt.news/warp-finance-rekt/

Warp Finance allowed LP tokens to be deposited as collateral. LP tokens usually are not so liquid, so the protocol introduces some logic to price these tokens.

In Warp the team calculated LP token price as [Pair TVL] / [LP tokens minted]. It sounds quite reasonable, but it was a bad idea for a Uniswap pair. The Uniswap pair keeps k constant, but TVL is not guaranteed to remain constant.

The TVL formula was: TVL = (reserve0 * price0) + (reserve1 * price1). The team took secure price feeds (TWAP). Also, k is always constant, but the whole TVL return can go up and down dramatically when large swaps happen.

Source: https://cmichel.io/pricing-lp-tokens/

So, that is what happened - the hacker took a flashloan and manipulated the TVL calculation for the pair, effecting the LP token value.

This issue is perfectly explained here:
https://cmichel.io/pricing-lp-tokens/

You can find the same idea but with more complex steps in lending projects that accepted shares of vaults at Yearn. Let's take CREAM. CREAM has had plenitude of attacks, and one of them is related to yUSD - a vault share, an LP token with the simple stablecoin strategy on Curve.
https://mudit.blog/cream-hack-analysis/
CREAM used yUSD.pricePerShare() to receive the value of yUSD. Function logic is straightforward that is [TotalUnderlyingInPool] / [TotalSupplyShares]. The only option to manipulate this formula is to increase TotalUnderlyingInPool. It may seem like a one-way operation - anyone can transfer tokens to the pool, but there is no way to extract tokens from the pool, so the operation doesn't make much sense. But it is not. One can flashloan millions to deposit funds to Yearn and become the biggest Yearn vault investor, so that to withdraw the proportional share of token in anytime. So, these prerequisites allow anyone to manipulate the return of pricePerShare().

Check some other hacks based on wrong token evaluation:

Recommendations when auditing:
  1. Strictly check the price source for all tokens - you should be confident that no element in the formula can be manipulated.
  2. Use the Chainlink oracle.
  3. Use TWAP when the Chainlink price is not available (but it is bad advice in some cases, see the following chapter).
On using TWAP as the price feed
The price from TWAP is hardly manipulated in one transaction and block. But being atomic is not the requirement for an attacker. TWAP is just a formula using onchain sources, and it is the question of time and cost to attack TWAP.

Here is an article researching the cost of TWAP attacks.
https://eprint.iacr.org/2022/445.pdf

To sum up, an attack on TWAP for low liquidity tokens can be profitable.

Let's take a look at an Inverse Finance attack example:
https://twitter.com/FrankResearcher/status/1510239094777032713?s=20&t=RBNXo-6wEDCiBE5a9nO0-Q
In this hack no flashloan is used, and the attacker had to use own 901 ETH. This attack is not available for everyone, but still remains an option for some DeFi users. It cost 500 ETH to change the value of the collateral and borrow 15M USD when only 0.65M USD in INV tokens was deposited.

The same hack occured with Moola.
https://www.coindesk.com/markets/2022/10/19/celo-protocol-moola-market-loses-over-10m-in-market-manipulation-attack/

Take a look at the price chart - this is a good representation of how TWAP manipulation occurs in time.

So, be aware that using TWAP is not always helpful. But sometimes it is necessary when no better price feed is available. In this case, the cost of an attack must be considered. It is okay when it is more than potential returns, so that any TWAP attack will be unprofitable.

Also, TWAP can work differently in a different context. For instance, the aforementioned Warp Finance used TWAP correctly, for a pair with much liquidity (ETH/DAI). But Warp Finance used the TWAP to calculate the value of LP token. Reserves were still left manipulated, as well as the overall TVL calculation (based on reserves * price).
Reentrancy with hookable tokens
This is one of the most common types of hacks.
It happens when one of the tokens on a protocol is hookable.

Imagine a protocol that accepts USD as collateral and allows borrowing in hookUSD (with hooks).

Hookable tokens in most cases follow the ERC-777 standard:
https://eips.ethereum.org/EIPS/eip-777

Its transfer functions include three steps:

  • call the token sender before the balance change,
  • apply the balance change,
  • call the token receiver after the balance change.
These calls (hooks) are optional, and the call receiver must allow these calls. In our case - an attacker will definitely allow these calls to the token sender.

The flow without an attack will work this way:
After the hook, the attacker can call any protocol function once again. The goal is to bypass the LTV check. So, the attacker calls Borrow once again during the hook. The first Borrow call is still not complete, and token transfer still have not changed balances. So, the LTV check will pass because the protocol has not transferred any tokens yet, thus the attacker still has not borrowed anything (if observing the balances).
In this way, any hook with a call to Borrow allows to bypass multiple LTV checks. After all hooks the cascade of balance changes happens and the hacker will see the balance 700 * (number of hooks).

This is the simplest example of using hookable tokens for hacks.

In practice, they can have different forms. Check the list of some selected examples with the detailed hack descriptions:

Recommendations when auditing:
  1. Observe the code as if all tokens transferred were potentially hookable (it will help to identify vulnerable spots)
  2. Recommend clients to avoid hookable tokens at all
  3. Keep in mind that an ETH transfer can be a hook in some cases
  4. Insist on following the check-effect-interaction pattern (but it will not help in all cases)
  5. Reentrancy protection is likely a good choice
Summary
In this article, we have categorized major security incidents encountered by lending protocols in recent years. Two crucial points for deep analysis are 1) oracles and 2) listed tokens. Having a special focus on these vectors is a perfect investment of efforts during audits.
Other posts