Skip to content
On this page

Curvy Puppet: When Overcollateralization Isn't Enough

The world of DeFi promises innovation and financial freedom, but it also harbors subtle vulnerabilities that even the most robust protocols can overlook. The "Curvy Puppet" CTF challenge from Damn Vulnerable DeFi brilliantly illustrates such a hidden danger, exposing how seemingly secure, overcollateralized positions can be exploited through clever oracle manipulation.

Let's dive into the scenario, dissect the underlying flaw, and understand the ingenious attack that saves user funds from an imminent threat.

The Setup: A Lending Contract on the Curve

Our story begins with a lending contract designed to let users borrow LP tokens from Curve's stETH/ETH stable swap pool. To do so, borrowers must deposit Damn Valuable Tokens (DVT) as collateral. A critical safety mechanism is in place: if a borrower's borrowed value exceeds 175% of their collateral value, anyone can liquidate the position by repaying the debt and seizing the collateral.

The contract relies on a few key components:

  • Permit2: For secure, gas-efficient token approvals.
  • Permissioned Price Oracle (CurvyPuppetOracle): This oracle provides the current prices of ETH and DVT. Crucially, it fetches the price of the LP token by combining the oracle's ETH price with the Curve pool's virtual_price.
  • Curve stETH/ETH Pool: The source of the borrowed LP tokens.

Alice, Bob, and Charlie, our innocent victims, have all opened positions, diligently overcollateralizing them to be extra safe. Yet, a bug report signals trouble. The challenge? Close all positions and rescue their DVT collateral before malicious actors can exploit the flaw.

The Core Vulnerability: A Puppet's String

The problem isn't with the overcollateralization itself, nor directly with the oracle's DVT or ETH prices. The Achilles' heel lies in how the lending contract calculates the value of the borrowed LP tokens:

LPTokenPrice = Oracle.getPrice(ETH).value * CurvePool.get_virtual_price()

While the ETH price is supplied by a permissioned oracle, the get_virtual_price() function comes directly from the Curve stETH/ETH pool. This virtual_price is a dynamic metric within Curve stable swap pools, reflecting the effective price of the LP token relative to its underlying assets.

Here's the critical insight: The virtual_price of a Curve stable swap pool can be significantly manipulated by performing a large, imbalanced swap within that pool. If an attacker injects a massive amount of one asset (e.g., ETH) and extracts the other (stETH), the pool's internal balance shifts, causing its virtual_price to spike.

If the virtual_price skyrockets, the calculated LPTokenPrice also skyrockets. This, in turn, inflates the borrowValue of Alice, Bob, and Charlie's positions, suddenly pushing their overcollateralized positions into an undercollateralized state, ripe for liquidation. The "Curvy Puppet" refers to how the Curve pool's virtual_price acts as a puppet, its strings easily pulled by an attacker.

The Attack: Flash Loan and Price Manipulation

The solution leverages a Uniswap V3 flash loan to orchestrate an atomic, multi-step exploit:

  1. Flash Loan Initiation: The attacker (represented by MyContract) takes a massive flash loan of WETH from a Uniswap V3 WETH/stETH pool. This provides the immediate capital needed for the price manipulation.

  2. Curve Pool Manipulation:

    • The borrowed WETH is unwrapped into native ETH.
    • This huge amount of ETH is then swapped for stETH on the Curve stETH/ETH pool. This single, massive trade significantly shifts the pool's internal balance, causing the curvePool.get_virtual_price() to inflate dramatically.
    • As a result, the borrowAsset (LP token) price, as seen by the CurvyPuppetLending contract, becomes extremely high.
  3. Liquidation Spree:

    • With the LP token price artificially inflated, Alice, Bob, and Charlie's borrow amounts now appear much higher in value.
    • Their collateralization ratio, despite their initial overcollateralization, falls below the 175% liquidation threshold.
    • The attacker iterates through each user's position and calls lending.liquidate(), repaying the (now relatively cheaper) LP token debt and seizing their DVT collateral.
  4. Collateral Rescue and Treasury Transfer:

    • All the DVT collateral seized from the liquidations is immediately transferred to the treasury account, fulfilling the challenge's requirement to save user funds.
  5. Rebalancing and Flash Loan Repayment:

    • The stETH acquired during the initial Curve swap (from manipulating the price) is now swapped back to ETH on the Curve pool, rebalancing it and returning the virtual_price to its normal state.
    • The ETH is wrapped back into WETH.
    • Finally, the attacker repays the Uniswap V3 flash loan (plus a small fee) with the re-obtained WETH.

Conclusion: A Puppet Show of Peril

The "Curvy Puppet" challenge serves as a stark reminder of critical vulnerabilities in DeFi protocol design:

  • Reliance on Internal Pool Metrics: Directly using a dynamic metric like virtual_price from a liquidity pool, especially one susceptible to large swaps, as part of an oracle's pricing mechanism is inherently risky.
  • Oracle Design: Even with permissioned oracles for direct assets, the composition of prices can introduce manipulation vectors. Robust oracle solutions often involve multiple sources, time-weighted averages, or circuit breakers.
  • Flash Loan Power: Flash loans enable atomic exploits that can execute a complex sequence of actions, including price manipulation and liquidation, all within a single transaction, leaving no time for intervention.

Ultimately, the challenge highlights that true security in DeFi requires not just overcollateralization, but also a meticulous understanding of all external dependencies and their potential for manipulation. Even the most seemingly "safe" positions can become a "curvy puppet" at the whim of a clever attacker.

Built with AiAda