Ajna Vulnerability Post Mortem

Originally published on October 13th 2023

In early September 2023, users were advised to repay outstanding borrow positions in the Ajna Protocol. This recommendation was due to the discovery of a vulnerability by a whitehat hacker on ImmuneFi which allowed a third party to generate debt on a borrower's behalf. The team has since analyzed the attack, designed a fix, and is moving forward with an updated Ajna Protocol implementation that simultaneously removes the problem and simplifies the protocol.

The vulnerability was not due to an error in the protocol’s code, but rather due to a flaw in the mechanism design of the liquidation system. Note that this was all possible in a single block by using a flash loan.

The Attack

  1. Push the lowest utilized price (LUP) below the target loan’s threshold price (TP) by borrowing available quote token (or depositing quote token yourself to borrow) to render the target loan liquidatable.

  2. Kick the loan into liquidation, causing the loan to incur 90 days of interest as a penalty for being kicked.

  3. Move the LUP back above the target loan’s TP by repaying the debt incurred in step 1, pushing the loan back into a fully collateralized state.

  4. Repay a dust quantity of the target loan’s debt, removing it from liquidation.

  5. Repeat steps 1-4, repeatedly incurring the kick penalty.

  6. As the attack proceeds, the TP of the loan moves up and the LUP moves down due to the increase in borrower’s debt. The attack ceases once they reach each other.

  7. Since this increases the pool’s reserves, the attacker could profit by triggering a claimable reserve auction and claiming the 1% reward. This profit is trivial compared to the amount of debt generated on behalf of the borrower, hence the categorization as a grief.

TLDR; Overcollateralized borrowers in underutilized pools could find themselves with excessive debt burdens that they did not agree to.

What's been done to fix this?

The Ajna Labs team decided against applying a quick fix for the specific issue, instead, it would address the larger problem of griefing attacks towards both borrowers and liquidators by dramatically simplifying the liquidation mechanism. The new design requires each to incur a cost.

Planned Changes
Adjustments could still happen since new code is still under audit at time of publishing

  1. There is no longer a kick penalty.

  2. The calculations for the NP and the bond_factor were changed.

  3. The reference_price of the auction becomes max(HTP, NP).

  4. The grace period has been eliminated.

  5. Loans in liquidation cannot be recollateralized or repaid.

  6. If any take function is called above the NP of the loan, a take_penalty is applied to the amount of debt cleared The penalty is calculated as the take_amount * (bond_factor * 5/4 - ÂĽ * bond_payoff_factor).

  7. The auction price decay rate has changed.

  8. The auction now begins with a starting price of 256 * reference_price.

  9. The 1% Claimable Reserve Auction kick_reward was removed.

  10. The max interest rate has been capped at 400% APR.

Please see the latest whitepaper for specific details.

Most notable of the changes are the removal of the grace period and ability to recollateralize or repay loans that are in liquidation. The only way to remove a loan from liquidation is to complete the auction. The original logic for a grace period, the ability to repay, and recollateralize was to give borrowers the ability to save their positions in distress scenarios and to create a defense against spurious kicks. However, this opened several opportunities for borrowers and liquidators to grief each other. If a borrower used on-chain automation or demonstrated a pattern of recollateralizing after being kicked, the grief attack detailed above could be performed even without step 4. If they provably could not recollateralize their loan, there would be no reason for the grace period. Even after a loan is in auction the protocol needs to further prevent the borrower from griefing the kicker. If the borrower recollateralizes their loan, the kicker loses tx fees on their bond with no reward, and if the borrower purchases their own collateral above the NP, the kicker loses some portion of their bond. The previous solutions to this were the kick and take penalties which had the goal of preventing auctions altogether but were still insufficient. The vulnerability demonstrated a need for simplifying the mechanism, requiring both borrowers and liquidators to incur inescapable costs by being credibly committed to the auction.

The new implementation has a payoff curve for both borrowers and kickers that avoids the ability to grief without proportionate costs. Kickers now always face the credible threat of auction and a guaranteed loss if they kick spuriously. Borrowers are now proportionately disincentivized to allow auctions to clear at an artificially high price, and maximally benefit if they clear at the lowest price.

New Implementation payoffs visualization
New Implementation payoffs visualization

Closing Remarks

Overall this issue was a learning experience in process, complexity, and analysis. After implementing the fixes above, Ajna Labs had the protocol audited once again by Prototech Labs.

Today we are starting our public audit contest on Sherlock, in addition to audits by Certora and the whitehat who found the vulnerability. We encourage participation and thank everyone who helps make Ajna a more secure protocol. New Implementation payoffs visualization

Closing Remarks

Overall this issue was a learning experience in process, complexity, and analysis. After implementing the fixes above, Ajna Labs had the protocol audited once again by Prototech Labs.

Today we are starting our public audit contest on Sherlock, in addition to audits by Certora and the whitehat who found the vulnerability. We encourage participation and thank everyone who helps make Ajna a more secure protocol.

Subscribe to The Ajna Protocol Blog
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.