Technique · OAK-T12.006 · emerging
OAK-T12.006 — NFT-Collateral Lending Manipulation
Description
A class of exploits against protocols that accept non-fungible tokens as loan collateral. The class is NFT-specific rather than a lending-protocol variant, because four properties of NFT collateral have no analogue in fungible lending and each of them is load-bearing in a documented incident:
- Collateral is indivisible and identified by
tokenId. A loan is bound to a specific token, not to a balance. That binding must be maintained as a piece of mutable state, and mutable state can desynchronise from custody. - Custody transfer executes attacker code. ERC-721's
safeTransferFromcallsonERC721Receivedon the recipient. Handing an NFT back to a borrower is therefore a call into untrusted code, in the middle of a state transition, by design of the token standard. - Valuation comes from a floor-price oracle over an illiquid market. There is no continuous order book for a specific Bored Ape. The collateral is priced from a collection-level statistic that a small number of trades can move.
- Liquidation cannot be partial. A fungible position can be liquidated 20% at a time; an NFT position resolves through auction of a whole item, which introduces auction-design surface (reserve prices, auction duration, bid incentives) that fungible markets do not have.
Three sub-shapes are distinguished, because their fixes are unrelated:
- (a) Collateral-state desynchronisation. The protocol's loan/order record is not invalidated when the NFT leaves custody, so the same collateral backs repeated or unbounded borrowing. XCarnival (2022-06):
orderIdremained borrowable after the pledged NFT had been withdrawn — one Bored Ape (BAYC #5110) served as collateral for loan after loan, draining 3,087 ETH. - (b) Transfer-callback reentrancy. The
onERC721Receivedhook re-enters a borrow, withdraw, or liquidation path before the caller's state has been committed. Omni Protocol (2022-07): a double-reentrancy across two functions of the same contract, both lacking reentrancy guards and both violating checks-effects-interactions aroundsafeTransferFrom; NFTs were withdrawn without repaying the loans they backed. - (c) Collateral-valuation manipulation. The value assigned to NFT-derived or yield-bearing collateral is inflated, and borrowing follows the inflated number. This shape overlaps T9.001 and OAK records it here only when the manipulated quantity is specific to NFT collateralisation (a floor-price feed, a fractionalisation index, a staked-NFT derivative). ParaSpace (2023-03) is the pipeline anchor: a rebasing index over pooled staked APE, inflated ~373%, lifted the value of the NFT-linked collateral token.
The unifying defender question is "what does the protocol believe it is holding, and can it still prove it?" Shapes (a) and (b) are both custody-belief failures — the protocol thinks the token is in the vault when it is not, or is about to not be. Shape (c) is a valuation-belief failure.
Observed indicators
- Borrowing against a
tokenIdwhose current owner, per the collection contract, is not the lending protocol's vault. - A loan/order record in
activestate whose collateral token has anownerOfthat has changed since origination. onERC721Receivedimplemented by a contract counterparty in a withdraw or liquidation flow, especially one whose code calls back into the lending protocol.- Repeated borrow calls referencing the same collateral identifier within a short window, with no intervening repayment.
- Total borrowed against a collection exceeding plausible bounds relative to the number of tokens actually escrowed.
- A floor-price feed moving sharply on very low trade counts immediately before a large borrow.
- Attack wallets funded from a mixer shortly before the first pledge (both anchors show pre-funding of this shape).
Detection signals
- Escrow reconciliation (highest value): for every active loan, assert
ownerOf(tokenId) == vault. Any drift is either an exploit in progress or an accounting bug, and there is no benign third case. - Borrow-capacity invariant: Σ(outstanding debt against collection C) ≤ Σ(escrowed tokens of C) × per-token limit. This bounds shape (a) whatever the state bug turns out to be.
- Reentrancy detection on NFT paths: flag any call sequence in which
safeTransferFromfrom the protocol is followed, within the same transaction, by a re-entry into a state-mutating protocol function. - Contract-recipient screening: withdrawals to recipients with non-empty bytecode in an NFT-lending flow are the precondition for shape (b) and are a small, reviewable population.
- Floor-feed plausibility: deviation circuit breaker plus trade-count minimum on the collection statistic feeding valuation.
- Post-liquidation auction health: auction duration, bid count, and clearing price against floor — the shape-(c)-adjacent design surface that turned BendDAO's August 2022 liquidity crisis into a near-cascade without any attacker involvement.
Real-world examples
examples/2022-06-xcarnival-withdrawn-nft-collateral-order-reuse.md— XCarnival — Ethereum — 2022-06-26/27 — 3,087 ETH (~$3.8M) (canonical shape-(a) anchor). The borrow path never checked whether the NFT referenced by a pledge order was still in the protocol's custody, so the attacker pledged BAYC #5110, withdrew it, and kept borrowing against the still-open order through a set of self-deployed contracts. Operating wallet funded with 120 ETH out of Tornado Cash. The attacker returned 1,467 ETH and accepted a 1,500 ETH bounty, with XCarnival publicly waiving legal action — roughly 50% recovery.examples/2022-07-omni-protocol-erc721-callback-double-reentrancy.md— Omni Protocol — Ethereum — 2022-07-10 — 1,300 ETH (~$1.4M) (canonical shape-(b) anchor). Two functions of the same contract lacked reentrancy locks and violated checks-effects-interactions around ERC-721safeTransferFrom; the attacker used Doodles NFTs to build a double-reentrancy that let the collateral be withdrawn without repaying. Only the protocol's own internal testing funds were lost — user funds were unaffected — which makes this the rare case where the whole loss landed on the party that shipped the bug. Proceeds laundered through Tornado Cash.
Pipeline anchors (shape (c) and design-surface cases)
examples/2023-03-paraspace.md— ParaSpace, 2023-03-17, ~$5M at risk / zero net loss. Rebasing-index manipulation over pooled staked APE inflated NFT-linked collateral value ~373%; recorded primarily under T9.001, cross-referenced here as the shape-(c) valuation anchor. BlockSec's whitehat redeployment of the attacker's own exploit rescued the funds.examples/2024-01-astaria-reinitialization.md— Astaria, 2024, pre-deployment disclosure. An NFT-lending protocol's upgradeable-proxy reinitialization finding (T9.009); included as evidence that the NFT-lending population carries the ordinary proxy-pattern surface in addition to the NFT-specific one.- BendDAO — 2022-08 — no attacker. A floor-price decline plus an auction design with a 48-hour window, a 95%-of-floor reserve, and bidder-unfriendly terms produced near-zero bids on defaulted loans and a run on the ETH pool. Not an attack and therefore not a worked example, but the canonical demonstration that NFT liquidation is an auction-design problem, and that the shape-(c) surface can fire without anyone attacking it.
Reference implementations
mg-detectors-rs— coverage gap. The escrow-reconciliation and borrow-capacity invariants are protocol-side checks rather than generic on-chain detectors.- OpenZeppelin
ReentrancyGuard+ checks-effects-interactions — the shape-(b) mitigation, and the reason shape (b) should be considered a solved problem that keeps recurring becausesafeTransferFrom's callback is not intuitively read as an external call. - Chainlink / DIA NFT floor-price oracles — the shape-(c) input; both publish methodology, and both are only as good as the trade population underneath an illiquid collection.
Mitigations
- Escrow reconciliation as a borrow precondition: verify
ownerOf(tokenId) == vaultat borrow time, not only at pledge time. Closes shape (a) directly. - Invalidate the order when custody ends: collateral withdrawal must atomically close or mark every order that references it — state transitions on the loan record and on custody must be the same transaction.
- Reentrancy guards and checks-effects-interactions on every path touching
safeTransferFrom: treat NFT transfer as an external call, because it is one. - Borrow-capacity invariant per collection, so a state bug produces a bounded loss rather than an unbounded one.
- Floor-price feeds with deviation circuit breakers and minimum trade counts, plus conservative LTV on illiquid collections.
- Auction design reviewed as a security control, not as a product parameter: reserve price, duration, and bidder incentives determine whether liquidation clears under stress.
Citations
- See
examples/2022-06-xcarnival-withdrawn-nft-collateral-order-reuse.mdandexamples/2022-07-omni-protocol-erc721-callback-double-reentrancy.mdfor the full citation sets.
Discussion
The reason this Technique sits under T12 rather than under T9 is the boundary rule stated in the T12 Tactic page: if the attack targets an NFT collection, its holders, or its market-signal layer, it belongs in T12; if it merely uses NFTs as instrumentation, it belongs in the generic Tactic. NFT-collateral lending is squarely the first case. The exploited properties — per-tokenId binding, the onERC721Received callback, floor-price valuation of an illiquid asset, whole-item liquidation — are properties of non-fungibility itself. A fungible money market has none of them. Filing XCarnival as "a lending bug" and Omni as "a reentrancy" is not wrong, but it loses the reason both protocols were exposed in the first place, and it separates two incidents whose shared lesson is precise: the protocol must be able to prove, at borrow time, that it still holds the specific thing the loan is against.
The second observation is about where the loss lands. Omni is unusual and instructive: only the team's own internal testing funds were taken, so the party that wrote the bug absorbed 100% of the consequence. XCarnival is the more typical distribution — user funds, followed by a negotiated return of roughly half against a 1,500 ETH bounty and a public waiver of legal action. Both outcomes are worth recording because the NFT-lending sector's recovery record is unusually good relative to its exploit record, and that is a fact about attacker incentives (NFT collateral is traceable and hard to launder — a Bored Ape is not a fungible balance) rather than about protocol quality.
Third, this class has a timing signature worth naming: both anchors landed within fourteen days of each other in mid-2022, at the peak of NFT-collateralised lending TVL, and the sector's subsequent contraction is a large part of why the class has few recent anchors. Contributors should not read that quiet as the class being solved. NFT-fi surfaces are returning through fractionalisation and hybrid token standards — see the June 2026 Flooring Protocol case under T12.002-adjacent accounting — and every one of those designs re-derives the same question about what the protocol can prove it holds. Promotion to stable awaits a third anchor, which this Technique's page predicts will come from the fractionalisation / hybrid-standard population rather than from classic peer-to-pool NFT lending.
Parent Tactics
Worked examples (2)
- Omni Protocol — handing an NFT back with safeTransferFrom calls the borrower's code mid-transition, and two unguarded functions turned that callback into a double reentrancy — Omni Protocol (Ethereum) — 2022-07-10
- XCarnival — the borrow path checked that a pledge order existed, never that the Ape was still in the vault, so one BAYC backed loan after loan — XCarnival (Ethereum) — 2022-06-26/27